[ Index ] |
|
Code source de b2evolution 2.1.0-beta |
1 <?php 2 /** 3 * This file implements the Results class. 4 * 5 * This file is part of the evoCore framework - {@link http://evocore.net/} 6 * See also {@link http://sourceforge.net/projects/evocms/}. 7 * 8 * @copyright (c)2003-2007 by Francois PLANQUE - {@link http://fplanque.net/} 9 * Parts of this file are copyright (c)2005-2006 by PROGIDISTRI - {@link http://progidistri.com/}. 10 * 11 * {@internal License choice 12 * - If you have received this file as part of a package, please find the license.txt file in 13 * the same folder or the closest folder above for complete license terms. 14 * - If you have received this file individually (e-g: from http://evocms.cvs.sourceforge.net/) 15 * then you must choose one of the following licenses before using the file: 16 * - GNU General Public License 2 (GPL) - http://www.opensource.org/licenses/gpl-license.php 17 * - Mozilla Public License 1.1 (MPL) - http://www.opensource.org/licenses/mozilla1.1.php 18 * }} 19 * 20 * {@internal Open Source relicensing agreement: 21 * PROGIDISTRI S.A.S. grants Francois PLANQUE the right to license 22 * PROGIDISTRI S.A.S.'s contributions to this file and the b2evolution project 23 * under any OSI approved OSS license (http://www.opensource.org/licenses/). 24 * }} 25 * 26 * @package evocore 27 * 28 * {@internal Below is a list of authors who have contributed to design/coding of this file: }} 29 * @author fplanque: Francois PLANQUE 30 * @author fsaya: Fabrice SAYA-GASNIER / PROGIDISTRI 31 * 32 * @version $Id: _results.class.php,v 1.4 2007/11/03 21:04:26 fplanque Exp $ 33 */ 34 if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' ); 35 36 load_funcs( '_core/ui/_uiwidget.class.php' ); 37 38 /** 39 * Results class 40 * 41 * @package evocore 42 * @todo Support $cols[]['order_rows_callback'] / order_objects_callback also if there's a LIMIT? 43 */ 44 class Results extends Table 45 { 46 /** 47 * SQL query 48 */ 49 var $sql; 50 51 /** 52 * Total number of rows (if > {@link $limit}, it will result in multiple pages) 53 */ 54 var $total_rows; 55 56 /** 57 * Number of lines per page 58 */ 59 var $limit; 60 61 /** 62 * Number of rows in result set for current page. 63 */ 64 var $result_num_rows; 65 66 /** 67 * Current page 68 */ 69 var $page; 70 71 /** 72 * Array of DB rows for current page. 73 */ 74 var $rows; 75 76 /** 77 * List of IDs for current page. 78 * @uses Results::$ID_col 79 */ 80 var $page_ID_list; 81 82 /** 83 * Array of IDs for current page. 84 * @uses Results::$ID_col 85 */ 86 var $page_ID_array; 87 88 /** 89 * Current object idx in $rows array 90 * @var integer 91 */ 92 var $current_idx = 0; 93 94 /** 95 * idx relative to whole list (range: 0 to total_rows-1) 96 * @var integer 97 */ 98 var $global_idx; 99 100 /** 101 * Is this gobally the 1st item in the list? (NOT just the 1st in current page) 102 */ 103 var $global_is_first; 104 105 /** 106 * Is this gobally the last item in the list? (NOT just the last in current page) 107 */ 108 var $global_is_last; 109 110 111 /** 112 * Cache to use to instantiate an object and cache it for each line of results. 113 * 114 * For this to work, all columns of the related table must be selected in the query 115 * 116 * @var DataObjectCache 117 */ 118 var $Cache; 119 120 /** 121 * This will hold the object instantiated by the Cache for the current line. 122 */ 123 var $current_Obj; 124 125 126 /** 127 * Definitions for each column: 128 * - th 129 * - td 130 * - order: SQL column name(s) to sort by (delimited by comma) 131 * - order_objects_callback: a PHP callback function (can be array($Object, $method)). 132 * This gets three params: $a, $b, $desc. 133 * $a and $b are instantiated objects from {@link Results::$Cache} 134 * $desc is either 'ASC' or 'DESC'. The function has to return -1, 0 or 1, 135 * according to if the $a < $b, $a == $b or $a > $b. 136 * - order_rows_callback: a PHP callback function (can be array($Object, $method)). 137 * This gets three params: $a, $b, $desc. 138 * $a and $b are DB row objects 139 * $desc is either 'ASC' or 'DESC'. The function has to return -1, 0 or 1, 140 * according to if the $a < $b, $a == $b or $a > $b. 141 * - td_class 142 * 143 */ 144 var $cols; 145 146 /** 147 * Do we want to display column headers? 148 * @var boolean 149 */ 150 var $col_headers = true; 151 152 153 /** 154 * DB fieldname to group on. 155 * 156 * Leave empty if you don't want to group. 157 * 158 * NOTE: you have to use ORDER BY goup_column in your query for this to work correctly. 159 * 160 * @var mixed string or array 161 */ 162 var $group_by = ''; 163 164 /** 165 * Object property/properties to group on. 166 * 167 * Objects get instantiated and grouped by the given property/member value. 168 * 169 * NOTE: this requires {@link Result::$Cache} to be set and is probably only useful, 170 * if you do not use {@link Result::$limit}, because grouping appears after 171 * the relevant data has been pulled from DB. 172 * 173 * @var mixed string or array 174 */ 175 var $group_by_obj_prop; 176 177 /** 178 * Current group identifier (by level/depth) 179 * @var array 180 */ 181 var $current_group_ID; 182 183 /** 184 * Definitions for each GROUP column: 185 * -td 186 * -td_start. A column with no def will de displayed using 187 * the default defs from Results::$params, that is to say, one of these: 188 * - $this->params['grp_col_start_first']; 189 * - $this->params['grp_col_start_last']; 190 * - $this->params['grp_col_start']; 191 */ 192 var $grp_cols = NULL; 193 194 /** 195 * Fieldname to detect empty data rows. 196 * 197 * Empty data rows can happen when left joining on groups. 198 * Leave empty if you don't want to detect empty datarows. 199 * 200 * @var string 201 */ 202 var $ID_col = ''; 203 204 /** 205 * URL param names 206 */ 207 var $param_prefix; 208 var $page_param; 209 var $order_param; 210 211 /** 212 * List of sortable fields 213 */ 214 var $order_field_list; 215 216 /** 217 * List of sortable columns by callback ("order_objects_callback" and "order_rows_callback") 218 * @var array 219 */ 220 var $order_callbacks; 221 222 223 /** 224 * Parameters for the filter area: 225 */ 226 var $filter_area; 227 228 229 /** 230 * Parameters for the functions area (to display functions at the end of results array): 231 */ 232 var $functions_area; 233 234 235 /** 236 * Constructor 237 * 238 * @todo we might not want to count total rows when not needed... 239 * @todo fplanque: I am seriously considering putting $count_sql into 2nd or 3rd position. Any prefs? 240 * @todo dh> We might just use "SELECT SQL_CALC_FOUND_ROWS ..." and "FOUND_ROWS()"..! - available since MySQL 4 - would save one query just for counting! 241 * 242 * @param string SQL query 243 * @param string prefix to differentiate page/order params when multiple Results appear one same page 244 * @param string default ordering of columns (special syntax) if not specified in the URL params 245 * example: -A-- will sort in ascending order on 2nd column 246 * example: ---D will sort in descending order on 4th column 247 * @param integer number of lines displayed on one page (0 to disable paging; null to use $UserSettings/results_per_page) 248 * @param string SQL to get the total count of results 249 * @param boolean 250 * @param NULL|string SQL query used to count the total # of rows (if NULL, we'll try to COUNT(*) by ourselves) 251 */ 252 function Results( $sql, $param_prefix = '', $default_order = '', $limit = NULL, $count_sql = NULL, $init_page = true ) 253 { 254 global $UserSettings; 255 256 parent::Table(); 257 258 $this->sql = $sql; 259 260 $this->limit = is_null($limit) ? $UserSettings->get('results_per_page') : $limit; 261 $this->param_prefix = $param_prefix; 262 263 // Count total rows: 264 // TODO: check if this can be done later instead 265 $this->count_total_rows( $count_sql ); 266 267 if( $init_page ) 268 { // attribution of a page number 269 $this->page_param = 'results_'.$param_prefix.'page'; 270 $page = param( $this->page_param, 'integer', 1, true ); 271 $this->page = min( $page, $this->total_pages ); 272 } 273 274 // attribution of an order type 275 $this->order_param = 'results_'.$param_prefix.'order'; 276 $this->order = param( $this->order_param, 'string', $default_order, true ); 277 } 278 279 280 /** 281 * Rewind resultset 282 */ 283 function restart() 284 { 285 // Make sure query has exexuted: 286 $this->query( $this->sql ); 287 288 $this->current_idx = 0; 289 290 $this->global_idx = (($this->page-1) * $this->limit) + $this->current_idx; 291 292 $this->global_is_first = ( $this->global_idx <= 0 ) ? true : false; 293 294 $this->global_is_last = ( $this->global_idx >= $this->total_rows-1 ) ? true : false; 295 296 $this->current_group_ID = NULL; 297 } 298 299 300 /** 301 * Increment and update all necessary counters before processing a new line in result set 302 */ 303 function next_idx() 304 { 305 $this->current_idx++; 306 307 $this->global_idx = (($this->page-1) * $this->limit) + $this->current_idx; 308 309 $this->global_is_first = ( $this->global_idx <= 0 ) ? true : false; 310 311 $this->global_is_last = ( $this->global_idx >= $this->total_rows-1 ) ? true : false; 312 313 return $this->current_idx; 314 } 315 316 317 /** 318 * Run the query now! 319 * 320 * Will only run if it has not executed before. 321 */ 322 function query( $create_default_cols_if_needed = true, $append_limit = true, $append_order_by = true, 323 $query_title = 'Results::Query()' ) 324 { 325 global $DB; 326 if( !is_null( $this->rows ) ) 327 { // Query has already executed: 328 return; 329 } 330 331 // Make sure we have colum definitions: 332 if( is_null( $this->cols ) && $create_default_cols_if_needed ) 333 { // Let's create default column definitions: 334 $this->cols = array(); 335 336 if( !preg_match( '#^(SELECT.*?(\([^)]*?FROM[^)]*\).*)*)FROM#six', $this->sql, $matches ) ) 337 { 338 debug_die( 'Results->query() : No SELECT clause!' ); 339 } 340 // Split requested columns by commata 341 foreach( preg_split( '#\s*,\s*#', $matches[1] ) as $l_select ) 342 { 343 if( is_numeric( $l_select ) ) 344 { // just a single value (would produce parse error as '$x$') 345 $this->cols[] = array( 'td' => $l_select ); 346 } 347 elseif( preg_match( '#^(\w+)$#i', $l_select, $match ) ) 348 { // regular column 349 $this->cols[] = array( 'td' => '$'.$match[1].'$' ); 350 } 351 elseif( preg_match( '#^(.*?) AS (\w+)#i', $l_select, $match ) ) 352 { // aliased column 353 $this->cols[] = array( 'td' => '$'.$match[2].'$' ); 354 } 355 } 356 357 if( !isset($this->cols[0]) ) 358 { 359 debug_die( 'No columns selected!' ); 360 } 361 } 362 363 364 // Make a copy of the SQL, that we may change and that gets executed: 365 $sql = $this->sql; 366 367 // Append ORDER clause if necessary: 368 if( $append_order_by && ($orders = $this->get_order_field_list()) ) 369 { // We have orders to append 370 371 if( strpos( $sql, 'ORDER BY') === false ) 372 { // there is no ORDER BY clause in the original SQL query 373 $sql .= ' ORDER BY '.$orders.' '; 374 } 375 else 376 { // try to insert the chosen order at an existing '*' point 377 $inserted_sql = preg_replace( '# \s ORDER \s+ BY (.+) \* #xi', ' ORDER BY $1 '.$orders, $sql ); 378 379 if( $inserted_sql != $sql ) 380 { // Insertion ok: 381 $sql = $inserted_sql; 382 } 383 else 384 { // No insert point found: 385 // the chosen order must be appended to an existing ORDER BY clause 386 $sql .= ', '.$orders; 387 } 388 } 389 } 390 else 391 { // Make sure there is no * in order clause: 392 $sql = preg_replace( '# \s ORDER \s+ BY (.+) \* #xi', ' ORDER BY $1 ', $sql ); 393 } 394 395 396 if( $append_limit && !empty($this->limit) ) 397 { // Limit lien range to requested page 398 $sql .= ' LIMIT '.max(0, ($this->page-1)*$this->limit).', '.$this->limit; 399 } 400 401 // Execute query and store results 402 $this->rows = $DB->get_results( $sql, OBJECT, $query_title ); 403 404 // Store row count 405 $this->result_num_rows = $DB->num_rows; 406 407 408 // Sort with callbacks: 409 if( $this->order_callbacks ) 410 { 411 if( $append_limit && !empty($this->limit) ) 412 { // Check for sorting with callbacks: 413 debug_die( '"order_objects_callback"/"order_rows_callback" are not supported with LIMIT.' ); 414 } 415 416 foreach( $this->order_callbacks as $order_callback ) 417 { 418 #echo 'order_callback: '; var_dump($order_callback); 419 420 $this->order_callback_wrapper_data = $order_callback; // to pass ASC/DESC param and callback itself through the wrapper to the callback 421 422 if( empty($order_callback['use_rows']) ) 423 { // default: instantiate objects for the callback: 424 usort( $this->rows, array( &$this, 'order_callback_wrapper_objects' ) ); 425 } 426 else 427 { 428 usort( $this->rows, array( &$this, 'order_callback_wrapper_rows' ) ); 429 } 430 } 431 } 432 433 // Group by object property: 434 if( ! empty($this->group_by_obj_prop) ) 435 { 436 if( ! is_array($this->group_by_obj_prop) ) 437 { 438 $this->group_by_obj_prop = array($this->group_by_obj_prop); 439 } 440 441 $this->mergesort( $this->rows, array( &$this, 'callback_group_by_obj_prop' ) ); 442 } 443 444 // echo '<br />rows on page='.$this->result_num_rows; 445 } 446 447 448 /** 449 * Merge sort. This is required to not re-order items when sorting for e.g. grouping at the end. 450 * 451 * @see http://de2.php.net/manual/en/function.usort.php#38827 452 * 453 * @param array List of items to sort 454 * @param callback Sort function/method 455 */ 456 function mergesort(&$array, $cmp_function) 457 { 458 // Arrays of size < 2 require no action. 459 if (count($array) < 2) return; 460 // Split the array in half 461 $halfway = count($array) / 2; 462 $array1 = array_slice($array, 0, $halfway); 463 $array2 = array_slice($array, $halfway); 464 // Recurse to sort the two halves 465 $this->mergesort($array1, $cmp_function); 466 $this->mergesort($array2, $cmp_function); 467 // If all of $array1 is <= all of $array2, just append them. 468 if (call_user_func($cmp_function, end($array1), $array2[0]) < 1) { 469 $array = array_merge($array1, $array2); 470 return; 471 } 472 // Merge the two sorted arrays into a single sorted array 473 $array = array(); 474 $ptr1 = $ptr2 = 0; 475 while ($ptr1 < count($array1) && $ptr2 < count($array2)) { 476 if (call_user_func($cmp_function, $array1[$ptr1], $array2[$ptr2]) < 1) { 477 $array[] = $array1[$ptr1++]; 478 } 479 else { 480 $array[] = $array2[$ptr2++]; 481 } 482 } 483 // Merge the remainder 484 while ($ptr1 < count($array1)) $array[] = $array1[$ptr1++]; 485 while ($ptr2 < count($array2)) $array[] = $array2[$ptr2++]; 486 return; 487 } 488 489 490 /** 491 * Callback, to sort {@link Result::$rows} according to {@link Result::$group_by_obj_prop}. 492 * 493 * @param array DB row for object A 494 * @param array DB row for object B 495 * @param integer Depth, used internally (you can group on a list of member properties) 496 * @return integer 497 */ 498 function callback_group_by_obj_prop( $row_a, $row_b, $depth = 0 ) 499 { 500 $obj_prop = $this->group_by_obj_prop[$depth]; 501 502 $a = & $this->Cache->instantiate($row_a); 503 $a_value = $a->$obj_prop; 504 $b = & $this->Cache->instantiate($row_b); 505 $b_value = $b->$obj_prop; 506 507 if( $a_value == $b_value ) 508 { 509 if( $depth+1 < count($this->group_by_obj_prop) ) 510 { 511 return $this->callback_group_by_obj_prop( $row_a, $row_b, ($depth + 1) ); 512 } 513 else 514 { // on the last level of grouping: 515 return 0; 516 } 517 } 518 519 // Sort empty group_by-values to the bottom 520 if( empty($a_value) ) 521 return 1; 522 if( empty($b_value) ) 523 return -1; 524 525 return strcasecmp( $a_value, $b_value ); 526 } 527 528 529 /** 530 * Wrapper method to {@link usort()}, which instantiates objects and passed them on to the 531 * order callback. 532 * 533 * @return integer 534 */ 535 function order_callback_wrapper_objects( $row_a, $row_b ) 536 { 537 $a = $this->Cache->instantiate($row_a); 538 $b = $this->Cache->instantiate($row_b); 539 540 return (int)call_user_func( $this->order_callback_wrapper_data['callback'], 541 $a, $b, $this->order_callback_wrapper_data['order'] ); 542 } 543 544 545 /** 546 * Wrapper method to {@link usort()}, which passes the rows to the order callback. 547 * 548 * @return integer 549 */ 550 function order_callback_wrapper_rows( $row_a, $row_b ) 551 { 552 return (int)call_user_func( $this->order_callback_wrapper_data['callback'], 553 $row_a, $row_b, $this->order_callback_wrapper_data['order'] ); 554 } 555 556 557 /** 558 * Get a list of IDs for current page 559 * 560 * @uses Results::$ID_col 561 */ 562 function get_page_ID_list() 563 { 564 if( is_null( $this->page_ID_list ) ) 565 { 566 $this->page_ID_list = implode( ',', $this->get_page_ID_array() ); 567 //echo '<br />'.$this->page_ID_list; 568 } 569 570 return $this->page_ID_list; 571 } 572 573 574 /** 575 * Get an array of IDs for current page 576 * 577 * @uses Results::$ID_col 578 */ 579 function get_page_ID_array() 580 { 581 if( is_null( $this->page_ID_array ) ) 582 { 583 $this->page_ID_array = array(); 584 585 foreach( $this->rows as $row ) 586 { // For each row/line: 587 $this->page_ID_array[] = $row->{$this->ID_col}; 588 } 589 } 590 591 return $this->page_ID_array; 592 } 593 594 595 /** 596 * Count the total number of rows of the SQL result (all pages) 597 * 598 * This is done by dynamically modifying the SQL query and forging a COUNT() into it. 599 * 600 * @todo allow overriding? 601 * @todo handle problem of empty groups! 602 */ 603 function count_total_rows( $sql_count = NULL ) 604 { 605 global $DB; 606 607 if( empty( $sql_count ) ) 608 { 609 if( is_null($this->sql) ) 610 { // We may want to remove this later... 611 $this->total_rows = 0; 612 $this->total_pages = 0; 613 return; 614 } 615 616 $sql_count = $this->sql; 617 // echo $sql_count; 618 619 /* 620 * 621 * On a un problème avec la recherche sur les sociétés 622 * si on fait un select count(*), ça sort un nombre de réponses énorme 623 * mais on ne sait pas pourquoi... la solution est de lister des champs dans le COUNT() 624 * MAIS malheureusement ça ne fonctionne pas pour d'autres requêtes. 625 * L'idéal serait de réussir à isoler qu'est-ce qui, dans la requête SQL, provoque le comportement 626 * bizarre.... 627 */ 628 // Tentative 1: 629 // if( !preg_match( '#FROM(.*?)((WHERE|ORDER BY|GROUP BY) .*)?$#si', $sql_count, $matches ) ) 630 // debug_die( "Can't understand query..." ); 631 // if( preg_match( '#(,|JOIN)#si', $matches[1] ) ) 632 // { // there was a coma or a JOIN clause in the FROM clause of the original query, 633 // Tentative 2: 634 // fplanque: je pense que la différence est sur la présence de DISTINCT ou non. 635 // if( preg_match( '#\s DISTINCT \s#six', $sql_count, $matches ) ) 636 if( preg_match( '#\s DISTINCT \s+ ([A-Za-z_]+)#six', $sql_count, $matches ) ) 637 { // 638 // Get rid of any Aliases in colmun names: 639 // $sql_count = preg_replace( '#\s AS \s+ ([A-Za-z_]+) #six', ' ', $sql_count ); 640 // ** We must use field names in the COUNT ** 641 //$sql_count = preg_replace( '#SELECT \s+ (.+?) \s+ FROM#six', 'SELECT COUNT( $1 ) FROM', $sql_count ); 642 643 //Tentative 3: we do a distinct on the first field only when counting: 644 $sql_count = preg_replace( '#^ \s* SELECT \s+ (.+?) \s+ FROM#six', 'SELECT COUNT( DISTINCT '.$matches[1].' ) FROM', $sql_count ); 645 } 646 else 647 { // Single table request: we must NOT use field names in the count. 648 $sql_count = preg_replace( '#^ \s* SELECT \s+ (.+?) \s+ FROM#six', 'SELECT COUNT( * ) FROM', $sql_count ); 649 } 650 651 652 // Make sure there is no ORDER BY clause at the end: 653 $sql_count = preg_replace( '# \s ORDER \s+ BY .* $#xi', '', $sql_count ); 654 655 // echo $sql_count; 656 } 657 658 $this->total_rows = $DB->get_var( $sql_count, 0, 0, get_class($this).'::count_total_rows()' ); //count total rows 659 660 $this->total_pages = empty($this->limit) ? 1 : ceil($this->total_rows / $this->limit); 661 662 // Make sure we're not requesting a page out of range: 663 if( $this->page > $this->total_pages ) 664 { 665 $this->page = $this->total_pages; 666 } 667 } 668 669 670 /** 671 * Note: this function might actually not be very useful. 672 * If you define ->Cache before display, all rows will be instantiated on the fly. 673 * No need to restart et go through the rows a second time here. 674 * 675 * @param DataObjectCache 676 */ 677 function instantiate_page_to_Cache( & $Cache ) 678 { 679 $this->Cache = & $Cache; 680 681 // Make sure query has executed and we're at the top of the resultset: 682 $this->restart(); 683 684 foreach( $this->rows as $row ) 685 { // For each row/line: 686 687 // Instantiate an object for the row and cache it: 688 $this->Cache->instantiate( $row ); 689 } 690 691 } 692 693 694 /** 695 * Display paged list/table based on object parameters 696 * 697 * This is the meat of this class! 698 * 699 * @param array|NULL 700 * @param array Fadeout settings array( 'key column' => array of values ) or 'session' 701 * @return int # of rows displayed 702 */ 703 function display( $display_params = NULL, $fadeout = NULL ) 704 { 705 // Initialize displaying: 706 $this->display_init( $display_params, $fadeout ); 707 708 // ------------------------- 709 // Proceed with display: 710 // ------------------------- 711 echo $this->params['before']; 712 713 if( $this->total_pages == 0 ) 714 { // There are no results! Nothing to display! 715 716 // START OF LIST/TABLE: 717 $this->display_list_start(); 718 719 // DISPLAY FILTERS: 720 $this->display_filters(); 721 722 // END OF LIST/TABLE: 723 $this->display_list_end(); 724 } 725 else 726 { // We have rows to display: 727 728 // GLOBAL (NAV) HEADER: 729 $this->display_nav( 'header' ); 730 731 // START OF LIST/TABLE: 732 $this->display_list_start(); 733 734 // TITLE / FILTERS / COLUMN HEADERS: 735 $this->display_head(); 736 737 // GROUP & DATA ROWS: 738 $this->display_body(); 739 740 // Totals line 741 $this->display_totals(); 742 743 // Functions 744 $this->display_functions(); 745 746 // END OF LIST/TABLE: 747 $this->display_list_end(); 748 749 // GLOBAL (NAV) FOOTER: 750 $this->display_nav( 'footer' ); 751 } 752 753 echo $this->params['after']; 754 755 // Return number of rows diplayed: 756 return $this->current_idx; 757 } 758 759 760 /** 761 * Initialize things in order to be ready for displaying. 762 * 763 * This is useful when manually displaying, i-e: not by using Results::display() 764 * 765 * @param array ***please document*** 766 * @param array Fadeout settings array( 'key column' => array of values ) or 'session' 767 */ 768 function display_init( $display_params = NULL, $fadeout = NULL ) 769 { 770 // Lazy fill $this->params: 771 parent::display_init( $display_params, $fadeout ); 772 773 // Make sure query has executed and we're at the top of the resultset: 774 $this->restart(); 775 } 776 777 778 /** 779 * Display options area 780 * 781 * @param string name of the option ( ma_colselect, tsk_filter....) 782 * @param string area name ( colselect_area, filter_area ) 783 * @param string option title 784 * @param string submit button title 785 * @param string default folde state when is empty in the session 786 * 787 */ 788 function display_option_area( $option_name, $area_name, $option_title, $submit_title, $default_folde_state = 'expanded' ) 789 { 790 global $debug, $Session; 791 792 // Do we already have a form? 793 $create_new_form = ! isset( $this->Form ); 794 795 echo $this->replace_vars( $this->params['filters_start'] ); 796 797 $fold_state = $Session->get( $option_name ); 798 799 if( empty( $fold_state ) ) 800 { 801 $fold_state = $default_folde_state; 802 } 803 804 //__________________________________ Toogle link _______________________________________ 805 806 if( $fold_state == 'collapsed' ) 807 { 808 echo '<a class="filters_title" href="'.regenerate_url( '', 'expand='.$option_name ).'" 809 onclick="return toggle_filter_area(\''.$option_name.'\');" >' 810 .get_icon( 'expand', 'imgtag', array( 'id' => 'clickimg_'.$option_name ) ); 811 } 812 else 813 { 814 echo '<a class="filters_title" href="'.regenerate_url( '', 'collapse='.$option_name ).'" 815 onclick="return toggle_filter_area(\''.$option_name.'\');" >' 816 .get_icon( 'collapse', 'imgtag', array( 'id' => 'clickimg_'.$option_name ) ); 817 } 818 echo $option_title.'</a>:'; 819 820 //_____________________________ Filters preset ___________________________________________ 821 822 if( !empty( $this->{$area_name}['presets'] ) ) 823 { // We have preset filters 824 $r = array(); 825 // Loop on all preset filters: 826 foreach( $this->{$area_name}['presets'] as $key => $preset ) 827 { 828 if( method_exists( $this, 'is_filtered' ) && !$this->is_filtered() 829 && get_param( $this->param_prefix.'filter_preset' ) == $key ) 830 { // The list is not filtered and the filter preset is selected, so no link on: 831 $r[] = '['.$preset[0].']'; 832 } 833 else 834 { // Display preset filter link: 835 $r[] = '[<a href="'.$preset[1].'">'.$preset[0].'</a>]'; 836 } 837 } 838 839 echo ' '.implode( ' ', $r ); 840 } 841 842 //_________________________________________________________________________________________ 843 844 if( $debug > 1 ) 845 { 846 echo ' <span class="notes">('.$option_name.':'.$fold_state.')</span>'; 847 echo ' <span id="asyncResponse"></span>'; 848 } 849 850 // Begining of the div: 851 echo '<div id="clickdiv_'.$option_name.'"'; 852 if( $fold_state == 'collapsed' ) 853 { 854 echo ' style="display:none;"'; 855 } 856 echo '>'; 857 858 //_____________________________ Form and callback _________________________________________ 859 860 if( !empty($this->{$area_name}['callback']) ) 861 { // We want to display filtering form fields: 862 863 if( $create_new_form ) 864 { // We do not already have a form surrounding the whole results list: 865 866 if( !empty( $this->{$area_name}['url_ignore'] ) ) 867 { 868 $ignore = $this->{$area_name}['url_ignore']; 869 } 870 else 871 { 872 $ignore = $this->page_param; 873 } 874 875 $this->Form = new Form( regenerate_url( $ignore ), $this->param_prefix.'form_search', 'post', 'blockspan' ); // COPY!! 876 877 $this->Form->begin_form( '' ); 878 } 879 880 $submit_name = empty( $this->{$area_name}['submit'] ) ? 'colselect_submit' : $this->{$area_name}['submit']; 881 $this->Form->submit( array( $submit_name, $submit_title, 'filter' ) ); 882 883 $func = $this->{$area_name}['callback']; 884 $func( $this->Form ); 885 886 if( $create_new_form ) 887 { // We do not already have a form surrounding the whole result list: 888 $this->Form->end_form( '' ); 889 } 890 } 891 892 echo '</div>'; 893 894 echo $this->params['filters_end']; 895 } 896 897 898 /** 899 * Display the column selection 900 */ 901 function display_colselect() 902 { 903 if( empty( $this->colselect_area ) ) 904 { // We don't want to display a col selection section: 905 return; 906 } 907 908 $option_name = $this->param_prefix.'colselect'; 909 910 $this->display_option_area( $option_name, 'colselect_area', T_('Columns'), T_('Apply'), 'collapsed'); 911 } 912 913 914 /** 915 * Display the filtering form 916 */ 917 function display_filters() 918 { 919 if( empty( $this->filter_area ) ) 920 { // We don't want to display a filters section: 921 return; 922 } 923 924 $option_name = $this->param_prefix.'filters'; 925 926 $this->display_option_area( $option_name, 'filter_area', T_('Filters'), T_('Filter list'), 'expanded' ); 927 } 928 929 930 /** 931 * Display list/table head. 932 * 933 * This includes list head/title and column headers. 934 * EXPERIMENTAL: also dispays <tfoot> 935 * 936 * @access protected 937 */ 938 function display_head() 939 { 940 echo $this->params['head_start']; 941 942 943 // DISPLAY TITLE: 944 if( isset($this->title) ) 945 { // A title has been defined for this result set: 946 echo $this->replace_vars( $this->params['head_title'] ); 947 } 948 949 950 // DISPLAY COL SELECTION 951 $this->display_colselect(); 952 953 954 // DISPLAY FILTERS: 955 $this->display_filters(); 956 957 958 // DISPLAY COLUMN HEADERS: 959 $this->display_col_headers(); 960 961 962 echo $this->params['head_end']; 963 964 965 // Experimental: 966 echo $this->params['tfoot_start']; 967 echo $this->params['tfoot_end']; 968 } 969 970 971 /** 972 * Display list/table body. 973 * 974 * This includes groups and data rows. 975 * 976 * @access protected 977 */ 978 function display_body() 979 { 980 // BODY START: 981 $this->display_body_start(); 982 983 // Prepare data for grouping: 984 $group_by_all = array(); 985 if( ! empty($this->group_by) ) 986 { 987 $group_by_all['row'] = is_array($this->group_by) ? $this->group_by : array($this->group_by); 988 } 989 if( ! empty($this->group_by_obj_prop) ) 990 { 991 $group_by_all['obj_prop'] = is_array($this->group_by_obj_prop) ? $this->group_by_obj_prop : array($this->group_by_obj_prop); 992 } 993 994 $this->current_group_count = array(); // useful in parse_col_content() 995 996 997 foreach( $this->rows as $row ) 998 { // For each row/line: 999 1000 /* 1001 * GROUP ROW stuff: 1002 */ 1003 if( ! empty($group_by_all) ) 1004 { // We are grouping (by SQL and/or object property)... 1005 1006 $group_depth = 0; 1007 $group_changed = false; 1008 foreach( $group_by_all as $type => $names ) 1009 { 1010 foreach( $names as $name ) 1011 { 1012 if( $type == 'row' ) 1013 { 1014 $value = $row->$name; 1015 } 1016 elseif( $type == 'obj_prop' ) 1017 { 1018 $this->current_Obj = $this->Cache->instantiate($row); // useful also for parse_col_content() below 1019 $value = $this->current_Obj->$name; 1020 } 1021 else debug_die( 'Invalid Results-group_by-type: '.var_export( $type, true ) ); 1022 1023 1024 if( $this->current_group_ID[$group_depth] != $value ) 1025 { // Group changed here: 1026 $this->current_group_ID[$group_depth] = $value; 1027 1028 if( ! isset($this->current_group_count[$group_depth]) ) 1029 { 1030 $this->current_group_count[$group_depth] = 0; 1031 } 1032 else 1033 { 1034 $this->current_group_count[$group_depth]++; 1035 } 1036 1037 // unset sub-group identifiers: 1038 for( $i = $group_depth+1, $n = count($this->current_group_ID); $i < $n; $i++ ) 1039 { 1040 unset($this->current_group_ID[$i]); 1041 } 1042 1043 $group_changed = true; 1044 break 2; 1045 } 1046 1047 $group_depth++; 1048 } 1049 } 1050 1051 if( $group_changed ) 1052 { // We have just entered a new group! 1053 1054 echo $this->params['grp_line_start']; // TODO: dh> support grp_line_start_odd, grp_line_start_last, grp_line_start_odd_last - as defined in _adminUI_general.class.php 1055 1056 $col_count = 0; 1057 foreach( $this->grp_cols as $grp_col ) 1058 { // For each column: 1059 1060 if( isset( $grp_col['td_class'] ) ) 1061 { // We have a class for the total column 1062 $class = $grp_col['td_class']; 1063 } 1064 else 1065 { // We have no class for the total column 1066 $class = ''; 1067 } 1068 1069 if( ($col_count==0) && isset($this->params['grp_col_start_first']) ) 1070 { // Display first column column start: 1071 $output = $this->params['grp_col_start_first']; 1072 1073 // Add the total column class in the grp col start first param class: 1074 $output = str_replace( '$class$', $class, $output ); 1075 } 1076 elseif( ($col_count==count($this->grp_cols)-1) && isset($this->params['grp_col_start_last']) ) 1077 { // Last column can get special formatting: 1078 $output = $this->params['grp_col_start_last']; 1079 1080 // Add the total column class in the grp col start end param class: 1081 $output = str_replace( '$class$', $class, $output ); 1082 } 1083 else 1084 { // Display regular column start: 1085 $output = $this->params['grp_col_start']; 1086 1087 // Replace the "class_attrib" in the grp col start param by the td column class 1088 $output = str_replace( '$class_attrib$', 'class="'.$class.'"', $output ); 1089 } 1090 1091 if( isset( $grp_col['td_colspan'] ) ) 1092 { 1093 $colspan = $grp_col['td_colspan']; 1094 if( $colspan < 0 ) 1095 { // We want to substract columns from the total count 1096 $colspan = $this->nb_cols + $colspan; 1097 } 1098 elseif( $colspan == 0 ) 1099 { // use $nb_cols 1100 $colspan = $this->nb_cols; 1101 } 1102 $output = str_replace( '$colspan_attrib$', 'colspan="'.$colspan.'"', $output ); 1103 } 1104 else 1105 { // remove non-HTML attrib: 1106 $output = str_replace( '$colspan_attrib$', '', $output ); 1107 } 1108 1109 // Contents to output: 1110 $output .= $this->parse_col_content( $grp_col['td'] ); 1111 //echo $output; 1112 eval( "echo '$output';" ); 1113 1114 echo '</td>'; 1115 $col_count++; 1116 } 1117 1118 echo $this->params['grp_line_end']; 1119 } 1120 } 1121 1122 1123 /* 1124 * DATA ROW stuff: 1125 */ 1126 if( !empty($this->ID_col) && empty($row->{$this->ID_col}) ) 1127 { // We have detected an empty data row which we want to ignore... (happens with empty groups) 1128 continue; 1129 } 1130 1131 1132 if( ! is_null( $this->Cache ) ) 1133 { // We want to instantiate an object for the row and cache it: 1134 // We also keep a local ref in case we want to use it for display: 1135 $this->current_Obj = & $this->Cache->instantiate( $row ); 1136 } 1137 1138 1139 // Check for fadeout 1140 $fadeout_line = false; 1141 if( !empty( $this->fadeout_array ) ) 1142 { 1143 foreach( $this->fadeout_array as $key => $crit ) 1144 { 1145 // echo 'fadeout '.$key.'='.$crit; 1146 if( isset( $row->$key ) && in_array( $row->$key, $crit ) ) 1147 { // Col is in the fadeout list 1148 // TODO: CLEAN THIS UP! 1149 $fadeout_line = true; 1150 break; 1151 } 1152 } 1153 } 1154 1155 // LINE START: 1156 $this->display_line_start( $this->current_idx == count($this->rows)-1, $fadeout_line ); 1157 1158 foreach( $this->cols as $col ) 1159 { // For each column: 1160 1161 // COL START: 1162 $this->display_col_start(); 1163 1164 // Contents to output: 1165 $output = $this->parse_col_content( $col['td'] ); 1166 #pre_dump( '{'.$output.'}' ); 1167 eval( "echo '$output';" ); 1168 1169 // COL START: 1170 $this->display_col_end(); 1171 } 1172 1173 // LINE END: 1174 $this->display_line_end(); 1175 1176 $this->next_idx(); 1177 } 1178 1179 // BODY END: 1180 $this->display_body_end(); 1181 } 1182 1183 1184 /** 1185 * Display totals line if set. 1186 */ 1187 function display_totals() 1188 { 1189 $total_enable = false; 1190 1191 // Search if we have totals line to display: 1192 foreach( $this->cols as $col ) 1193 { 1194 if( isset( $col['total'] ) ) 1195 { // We have to display a totals line 1196 $total_enable = true; 1197 break; 1198 } 1199 } 1200 1201 if( $total_enable ) 1202 { // We have to dispaly a totals line 1203 1204 // <tr> 1205 echo $this->params['total_line_start']; 1206 1207 $loop = 0; 1208 1209 foreach( $this->cols as $col ) 1210 { 1211 if( isset( $col['total_class'] ) ) 1212 { // We have a class for the total column 1213 $class = $col['total_class']; 1214 } 1215 else 1216 { // We have no class for the total column 1217 $class = ''; 1218 } 1219 1220 if( $loop == 0) 1221 { // The column is the first 1222 $output = $this->params['total_col_start_first']; 1223 // Add the total column class in the total col start first param class: 1224 $output = str_replace( '$class$', $class, $output ); 1225 } 1226 elseif( $loop ==( count( $this->cols ) -1 ) ) 1227 { // The column is the last 1228 $output = $this->params['total_col_start_last']; 1229 // Add the total column class in the total col start end param class: 1230 $output = str_replace( '$class$', $class, $output ); 1231 } 1232 else 1233 { 1234 $output = $this->params['total_col_start']; 1235 // Replace the "class_attrib" in the total col start param by the total column class 1236 $output = str_replace( '$class_attrib$', 'class="'.$class.'"', $output ); 1237 } 1238 1239 // <td class="...."> 1240 echo $output; 1241 1242 if( isset( $col['total'] ) ) 1243 { // The column has a total set, so display it: 1244 $output = $col['total']; 1245 $output = $this->parse_col_content( $output ); 1246 eval( "echo '$output';" ); 1247 } 1248 else 1249 { // The column has no total 1250 echo ' '; 1251 } 1252 // </td> 1253 echo $this->params['total_col_end']; 1254 1255 $loop++; 1256 } 1257 // </tr> 1258 echo $this->params['total_line_end']; 1259 } 1260 } 1261 1262 1263 /** 1264 * Display the functions 1265 */ 1266 function display_functions() 1267 { 1268 if( empty( $this->functions_area ) ) 1269 { // We don't want to display a functions section: 1270 return; 1271 } 1272 1273 echo $this->replace_vars( $this->params['functions_start'] ); 1274 1275 if( !empty( $this->functions_area['callback'] ) ) 1276 { // We want to display functions: 1277 if( is_array( $this->functions_area['callback'] ) ) 1278 { // The callback is an object function 1279 $obj_name = $this->functions_area['callback'][0]; 1280 if( $obj_name != 'this' ) 1281 { // We need the global object 1282 global $$obj_name; 1283 } 1284 $func = $this->functions_area['callback'][1]; 1285 1286 if( isset( $this->Form ) ) 1287 { // There is a created form 1288 $$obj_name->$func( $this->Form ); 1289 } 1290 else 1291 { // There is not a created form 1292 $$obj_name->$func(); 1293 } 1294 } 1295 else 1296 { // The callback is a function 1297 $func = $this->functions_area['callback']; 1298 1299 if( isset( $this->Form ) ) 1300 { // There is a created form 1301 $func( $this->Form ); 1302 } 1303 else 1304 { // There is not a created form 1305 $func(); 1306 } 1307 } 1308 1309 } 1310 1311 echo $this->params['functions_end']; 1312 } 1313 1314 1315 /** 1316 * Display navigation text, based on template. 1317 * 1318 * @param string template: 'header' or 'footer' 1319 * @access protected 1320 */ 1321 function display_nav( $template ) 1322 { 1323 echo $this->params[$template.'_start']; 1324 1325 if( empty($this->limit) && isset($this->params[$template.'_text_no_limit']) ) 1326 { // No LIMIT (there's always only one page) 1327 echo $this->params[$template.'_text_no_limit']; 1328 } 1329 elseif( ( $this->total_pages <= 1 ) ) 1330 { // Single page (we probably don't want to show navigation in this case) 1331 echo $this->params[$template.'_text_single']; 1332 } 1333 else 1334 { // Several pages 1335 echo $this->replace_vars( $this->params[$template.'_text'] ); 1336 } 1337 1338 echo $this->params[$template.'_end']; 1339 } 1340 1341 1342 /** 1343 * Returns values needed to make sort links for a given column 1344 * 1345 * Returns an array containing the following values: 1346 * - current_order : 'ASC', 'DESC' or '' 1347 * - order_asc : url to order in ascending order 1348 * - order_desc 1349 * - order_toggle : url to toggle sort order 1350 * 1351 * @param integer column to sort 1352 * @return array 1353 */ 1354 function get_col_sort_values( $col_idx ) 1355 { 1356 1357 // Current order: 1358 $order_char = substr( $this->order, $col_idx, 1 ); 1359 if( $order_char == 'A' ) 1360 { 1361 $col_sort_values['current_order'] = 'ASC'; 1362 } 1363 elseif( $order_char == 'D' ) 1364 { 1365 $col_sort_values['current_order'] = 'DESC'; 1366 } 1367 else 1368 { 1369 $col_sort_values['current_order'] = ''; 1370 } 1371 1372 1373 // Generate sort values to use for sorting on the current column: 1374 $order_asc = ''; 1375 $order_desc = ''; 1376 for( $i = 0; $i < $this->nb_cols; $i++ ) 1377 { 1378 if( $i == $col_idx ) 1379 { // Link ordering the current column 1380 $order_asc .= 'A'; 1381 $order_desc .= 'D'; 1382 } 1383 else 1384 { 1385 $order_asc .= '-'; 1386 $order_desc .= '-'; 1387 } 1388 } 1389 1390 $col_sort_values['order_asc'] = regenerate_url( $this->order_param, $this->order_param.'='.$order_asc ); 1391 $col_sort_values['order_desc'] = regenerate_url( $this->order_param, $this->order_param.'='.$order_desc ); 1392 1393 1394 if( !$col_sort_values['current_order'] && isset( $this->cols[$col_idx]['default_dir'] ) ) 1395 { // There is no current order on this column and a default order direction is set for it 1396 // So set a default order direction for it 1397 1398 if( $this->cols[$col_idx]['default_dir'] == 'A' ) 1399 { // The default order direction is A, so set its toogle order to the order_asc 1400 $col_sort_values['order_toggle'] = $col_sort_values['order_asc']; 1401 } 1402 else 1403 { // The default order direction is A, so set its toogle order to the order_desc 1404 $col_sort_values['order_toggle'] = $col_sort_values['order_desc']; 1405 } 1406 } 1407 elseif( $col_sort_values['current_order'] == 'ASC' ) 1408 { // There is an ASC current order on this column, so set its toogle order to the order_desc 1409 $col_sort_values['order_toggle'] = $col_sort_values['order_desc']; 1410 } 1411 else 1412 { // There is a DESC or NO current order on this column, so set its toogle order to the order_asc 1413 $col_sort_values['order_toggle'] = $col_sort_values['order_asc']; 1414 } 1415 1416 return $col_sort_values; 1417 } 1418 1419 1420 /** 1421 * Returns order field list add to SQL query: 1422 * @return string May be empty 1423 */ 1424 function get_order_field_list() 1425 { 1426 if( is_null( $this->order_field_list ) ) 1427 { // Order list is not defined yet 1428 if( empty( $this->order ) ) 1429 { // We have no user provided order: 1430 if( empty( $this->cols ) ) 1431 { // We have no columns to pick an automatic order from: 1432 // echo 'Can\'t determine automatic order'; 1433 return ''; 1434 } 1435 1436 foreach( $this->cols as $col ) 1437 { 1438 if( isset( $col['order'] ) || isset( $col['order_objects_callback'] ) || isset( $col['order_rows_callback'] ) ) 1439 { // We have found the first orderable column: 1440 $this->order .= 'A'; 1441 break; 1442 } 1443 else 1444 { 1445 $this->order .= '-'; 1446 } 1447 } 1448 1449 if( empty( $this->cols ) ) 1450 { // We did not find any column to order on... 1451 return ''; 1452 } 1453 } 1454 1455 // echo ' order='.$this->order.' '; 1456 1457 $orders = array(); 1458 $this->order_callbacks = array(); 1459 1460 for( $i = 0; $i <= strlen( $this->order ); $i++ ) 1461 { // For each position in order string: 1462 if( isset( $this->cols[$i]['order'] ) ) 1463 { // if column is sortable: 1464 switch( substr( $this->order, $i, 1 ) ) 1465 { 1466 case 'A': 1467 $orders[] = str_replace( ',', ' ASC,', $this->cols[$i]['order']).' ASC'; 1468 break; 1469 1470 case 'D': 1471 $orders[] = str_replace( ',', ' DESC,', $this->cols[$i]['order']).' DESC'; 1472 break; 1473 } 1474 } 1475 1476 if( isset( $this->cols[$i]['order_objects_callback'] ) ) 1477 { // if column is sortable by object callback: 1478 switch( substr( $this->order, $i, 1 ) ) 1479 { 1480 case 'A': 1481 $this->order_callbacks[] = array( 1482 'callback' => $this->cols[$i]['order_objects_callback'], 1483 'use_rows' => false, 1484 'order'=>'ASC' ); 1485 break; 1486 1487 case 'D': 1488 $this->order_callbacks[] = array( 1489 'callback' => $this->cols[$i]['order_objects_callback'], 1490 'use_rows' => false, 1491 'order' => 'DESC' ); 1492 break; 1493 } 1494 } 1495 1496 if( isset( $this->cols[$i]['order_rows_callback'] ) ) 1497 { // if column is sortable by callback: 1498 switch( substr( $this->order, $i, 1 ) ) 1499 { 1500 case 'A': 1501 $this->order_callbacks[] = array( 1502 'callback' => $this->cols[$i]['order_rows_callback'], 1503 'use_rows' => true, 1504 'order'=>'ASC' ); 1505 break; 1506 1507 case 'D': 1508 $this->order_callbacks[] = array( 1509 'callback' => $this->cols[$i]['order_rows_callback'], 1510 'use_rows' => true, 1511 'order' => 'DESC' ); 1512 break; 1513 } 1514 } 1515 } 1516 $this->order_field_list = implode( ',', $orders ); 1517 1518 #pre_dump( $this->order_field_list ); 1519 #pre_dump( $this->order_callbacks ); 1520 } 1521 return $this->order_field_list; // May be empty 1522 } 1523 1524 1525 /** 1526 * Handle variable subtitutions for column contents. 1527 * 1528 * This is one of the key functions to look at when you want to use the Results class. 1529 * - $var$ 1530 * - £var£ 1531 * - #var# 1532 * - {row} 1533 * - %func()% 1534 * - ¤func()¤ 1535 */ 1536 function parse_col_content( $content ) 1537 { 1538 // Make variable substitution for STRINGS: 1539 $content = preg_replace( '#\$ (\w+) \$#ix', "'.format_to_output(\$row->$1).'", $content ); 1540 // Make variable substitution for URL STRINGS: 1541 $content = preg_replace( '#\£ (\w+) \£#ix', "'.format_to_output(\$row->$1, 'urlencoded').'", $content ); 1542 // Make variable substitution for escaped strings: 1543 $content = preg_replace( '#² (\w+) ²#ix', "'.htmlentities(\$row->$1).'", $content ); 1544 // Make variable substitution for RAWS: 1545 $content = preg_replace( '!\# (\w+) \#!ix', "\$row->$1", $content ); 1546 // Make variable substitution for full ROW: 1547 $content = str_replace( '{row}', '$row', $content ); 1548 // Make callback function substitution: 1549 $content = preg_replace( '#% (.+?) %#ix', "'.$1.'", $content ); 1550 // Make variable substitution for intanciated Object: 1551 $content = str_replace( '{Obj}', "\$this->current_Obj", $content ); 1552 // Make callback for Object method substitution: 1553 $content = preg_replace( '#@ (.+?) @#ix', "'.\$this->current_Obj->$1.'", $content ); 1554 // Sometimes we need embedded function call, so we provide a second sign: 1555 $content = preg_replace( '#¤ (.+?) ¤#ix', "'.$1.'", $content ); 1556 1557 // Make callback function move_icons for oderable lists // dh> what does it do? 1558 $content = str_replace( '{move}', "'.\$this->move_icons().'", $content ); 1559 1560 1561 return $content; 1562 } 1563 1564 1565 /** 1566 * 1567 * @todo Support {@link Results::$order_callbacks} 1568 */ 1569 function move_icons( ) 1570 { 1571 $r = ''; 1572 1573 $reg = '#^'.$this->param_prefix.'order (ASC|DESC).*#'; 1574 1575 if( preg_match( $reg, $this->order_field_list, $res ) ) 1576 { // The table is sorted by the order column 1577 $sort = $res[1]; 1578 1579 // get the element ID 1580 $idname = $this->param_prefix . 'ID'; 1581 $id = $this->rows[$this->current_idx]->$idname; 1582 1583 // Move up arrow 1584 if( $this->global_is_first ) 1585 { // The element is the first so it can't move up, display a no move arrow 1586 $r .= get_icon( 'nomove' ).' '; 1587 } 1588 else 1589 { 1590 if( $sort == 'ASC' ) 1591 { // ASC sort, so move_up action for move up arrow 1592 $action = 'move_up'; 1593 $alt = T_( 'Move up!' ); 1594 } 1595 else 1596 { // Reverse sort, so action and alt are reverse too 1597 $action = 'move_down'; 1598 $alt = T_('Move down! (reverse sort)'); 1599 } 1600 $r .= action_icon( $alt, 'move_up', regenerate_url( 'action,'.$this->param_prefix.'ID' , $this->param_prefix.'ID='.$id.'&action='.$action ) ); 1601 } 1602 1603 // Move down arrow 1604 if( $this->global_is_last ) 1605 { // The element is the last so it can't move up, display a no move arrow 1606 $r .= get_icon( 'nomove' ).' '; 1607 } 1608 else 1609 { 1610 if( $sort == 'ASC' ) 1611 { // ASC sort, so move_down action for move down arrow 1612 $action = 'move_down'; 1613 $alt = T_( 'Move down!' ); 1614 } 1615 else 1616 { // Reverse sort, so action and alt are reverse too 1617 $action = 'move_up'; 1618 $alt = T_('Move up! (reverse sort)'); 1619 } 1620 $r .= action_icon( $alt, 'move_down', regenerate_url( 'action,'.$this->param_prefix.'ID', $this->param_prefix.'ID='.$id.'&action='.$action ) ); 1621 } 1622 1623 return $r; 1624 } 1625 else 1626 { // The table is not sorted by the order column, so we display no move arrows 1627 1628 if( $this->global_is_first ) 1629 { 1630 // The element is the first so it can't move up, display a no move up arrow 1631 $r = get_icon( 'nomove' ).' '; 1632 } 1633 else 1634 { // Display no move up arrow 1635 $r = action_icon( T_( 'Sort by order' ), 'nomove_up', regenerate_url( 'action', 'action=sort_by_order' ) ); 1636 } 1637 1638 if( $this->global_is_last ) 1639 { 1640 // The element is the last so it can't move down, display a no move down arrow 1641 $r .= get_icon( 'nomove' ).' '; 1642 } 1643 else 1644 { // Display no move down arrow 1645 $r .= action_icon( T_( 'Sort by order' ), 'nomove_down', regenerate_url( 'action','action=sort_by_order' ) ); 1646 } 1647 1648 return $r; 1649 } 1650 } 1651 1652 1653 /** 1654 * Widget callback for template vars. 1655 * 1656 * This allows to replace template vars, see {@link Widget::replace_callback()}. 1657 * 1658 * @return string 1659 */ 1660 function replace_callback( $matches ) 1661 { 1662 // echo '['.$matches[1].']'; 1663 switch( $matches[1] ) 1664 { 1665 case 'start' : 1666 //total number of rows in the sql query 1667 return ( ($this->page-1)*$this->limit+1 ); 1668 1669 case 'end' : 1670 return ( min( $this->total_rows, $this->page*$this->limit ) ); 1671 1672 case 'total_rows' : 1673 return ( $this->total_rows ); 1674 1675 case 'page' : 1676 //current page number 1677 return ( $this->page ); 1678 1679 case 'total_pages' : 1680 //total number of pages 1681 return ( $this->total_pages ); 1682 1683 case 'prev' : 1684 //inits the link to previous page 1685 return ( $this->page > 1 ) 1686 ? '<a href="' 1687 .regenerate_url( $this->page_param, $this->page_param.'='.($this->page-1), $this->params['page_url'] ) 1688 .'">'.$this->params['prev_text'].'</a>' 1689 : $this->params['no_prev_text']; 1690 1691 case 'next' : 1692 //inits the link to next page 1693 return ( $this->page < $this->total_pages ) 1694 ? '<a href="' 1695 .regenerate_url( $this->page_param, $this->page_param.'='.($this->page+1), $this->params['page_url'] ) 1696 .'">'.$this->params['next_text'].'</a>' 1697 : $this->params['no_next_text']; 1698 1699 case 'list' : 1700 //inits the page list 1701 return $this->page_list( $this->first(), $this->last(), $this->params['page_url'] ); 1702 1703 case 'scroll_list' : 1704 //inits the scrolling list of pages 1705 return $this->page_scroll_list(); 1706 1707 case 'first' : 1708 //inits the link to first page 1709 return $this->display_first( $this->params['page_url'] ); 1710 1711 case 'last' : 1712 //inits the link to last page 1713 return $this->display_last( $this->params['page_url'] ); 1714 1715 case 'list_prev' : 1716 //inits the link to previous page range 1717 return $this->display_prev( $this->params['page_url'] ); 1718 1719 case 'list_next' : 1720 //inits the link to next page range 1721 return $this->display_next( $this->params['page_url'] ); 1722 1723 default : 1724 return parent::replace_callback( $matches ); 1725 } 1726 } 1727 1728 1729 /** 1730 * Returns the first page number to be displayed in the list 1731 */ 1732 function first() 1733 { 1734 if( $this->page <= intval( $this->params['list_span']/2 )) 1735 { // the current page number is small 1736 return 1; 1737 } 1738 elseif( $this->page > $this->total_pages-intval( $this->params['list_span']/2 )) 1739 { // the current page number is big 1740 return max( 1, $this->total_pages-$this->params['list_span']+1); 1741 } 1742 else 1743 { // the current page number can be centered 1744 return $this->page - intval($this->params['list_span']/2); 1745 } 1746 } 1747 1748 1749 /** 1750 * returns the last page number to be displayed in the list 1751 */ 1752 function last() 1753 { 1754 if( $this->page > $this->total_pages-intval( $this->params['list_span']/2 )) 1755 { //the current page number is big 1756 return $this->total_pages; 1757 } 1758 else 1759 { 1760 return min( $this->total_pages, $this->first()+$this->params['list_span']-1 ); 1761 } 1762 } 1763 1764 1765 /** 1766 * returns the link to the first page, if necessary 1767 */ 1768 function display_first( $page_url = '' ) 1769 { 1770 if( $this->first() > 1 ) 1771 { //the list doesn't contain the first page 1772 return '<a href="'.regenerate_url( $this->page_param, $this->page_param.'=1', $page_url ).'">1</a>'; 1773 } 1774 else 1775 { //the list already contains the first page 1776 return NULL; 1777 } 1778 } 1779 1780 1781 /** 1782 * returns the link to the last page, if necessary 1783 */ 1784 function display_last( $page_url = '' ) 1785 { 1786 if( $this->last() < $this->total_pages ) 1787 { //the list doesn't contain the last page 1788 return '<a href="'.regenerate_url( $this->page_param, $this->page_param.'='.$this->total_pages, $page_url ).'">'.$this->total_pages.'</a>'; 1789 } 1790 else 1791 { //the list already contains the last page 1792 return NULL; 1793 } 1794 } 1795 1796 1797 /** 1798 * returns a link to previous pages, if necessary 1799 */ 1800 function display_prev( $page_url = '' ) 1801 { 1802 if( $this->display_first() != NULL ) 1803 { //the list has to be displayed 1804 return '<a href="'.regenerate_url( $this->page_param, $this->page_param.'='.($this->first()-1), $page_url ).'">' 1805 .$this->params['list_prev_text'].'</a>'; 1806 } 1807 1808 } 1809 1810 1811 /** 1812 * returns a link to next pages, if necessary 1813 */ 1814 function display_next( $page_url = '' ) 1815 { 1816 if( $this->display_last() != NULL ) 1817 { //the list has to be displayed 1818 return '<a href="'.regenerate_url( $this->page_param,$this->page_param.'='.($this->last()+1), $page_url ).'">' 1819 .$this->params['list_next_text'].'</a>'; 1820 } 1821 } 1822 1823 1824 /** 1825 * Returns the page link list under the table 1826 */ 1827 function page_list( $min, $max, $page_url = '' ) 1828 { 1829 $i = 0; 1830 $list = ''; 1831 1832 for( $i=$min; $i<=$max; $i++) 1833 { 1834 if( $i == $this->page ) 1835 { //no link for the current page 1836 $list .= '<strong class="current_page">'.$i.'</strong> '; 1837 } 1838 else 1839 { //a link for non-current pages 1840 $list .= '<a href="' 1841 .regenerate_url( $this->page_param, $this->page_param.'='.$i, $page_url ) 1842 .'">'.$i.'</a> '; 1843 } 1844 } 1845 return $list; 1846 } 1847 1848 1849 /* 1850 * Returns a scrolling page list under the table 1851 */ 1852 function page_scroll_list() 1853 { 1854 $scroll = ''; 1855 $i = 0; 1856 $range = $this->params['scroll_list_range']; 1857 $min = 1; 1858 $max = 1; 1859 $option = ''; 1860 $selected = ''; 1861 $range_display=''; 1862 1863 if( $range > $this->total_pages ) 1864 { //the range is greater than the total number of pages, the list goes up to the number of pages 1865 $max = $this->total_pages; 1866 } 1867 else 1868 { //initialisation of the range 1869 $max = $range; 1870 } 1871 1872 //initialization of the form 1873 $scroll ='<form class="inline" method="post" action="'.regenerate_url( $this->page_param ).'"> 1874 <select name="'.$this->page_param.'" onchange="parentNode.submit()">';//javascript to change page clicking in the scroll list 1875 1876 while( $max <= $this->total_pages ) 1877 { //construction loop 1878 if( $this->page <= $max && $this->page >= $min ) 1879 { //display all the pages belonging to the range where the current page is located 1880 for( $i = $min ; $i <= $max ; $i++) 1881 { //construction of the <option> tags 1882 $selected = ($i == $this->page) ? ' selected' : '';//the "selected" option is applied to the current page 1883 $option = '<option'.$selected.' value="'.$i.'">'.$i.'</option>'; 1884 $scroll = $scroll.$option; 1885 } 1886 } 1887 else 1888 { //inits the ranges inside the list 1889 $range_display = '<option value="'.$min.'">' 1890 .T_('Pages').' '.$min.' '. /* TRANS: Pages x _to_ y */ T_('to').' '.$max; 1891 $scroll = $scroll.$range_display; 1892 } 1893 1894 if( $max+$range > $this->total_pages && $max != $this->total_pages) 1895 { //$max has to be the total number of pages 1896 $max = $this->total_pages; 1897 } 1898 else 1899 { 1900 $max = $max+$range;//incrementation of the maximum value by the range 1901 } 1902 1903 $min = $min+$range;//incrementation of the minimum value by the range 1904 1905 1906 } 1907 /*$input =''; 1908 $input = '<input type="submit" value="submit" />';*/ 1909 $scroll = $scroll.'</select>'./*$input.*/'</form>';//end of the form*/ 1910 1911 return $scroll; 1912 } 1913 1914 1915 /** 1916 * Get number of rows available for display 1917 * 1918 * @return integer 1919 */ 1920 function get_num_rows() 1921 { 1922 return $this->result_num_rows; 1923 } 1924 1925 1926 /** 1927 * Template function: display message if list is empty 1928 * 1929 * @return boolean true if empty 1930 */ 1931 function display_if_empty( $params = array() ) 1932 { 1933 if( $this->result_num_rows == 0 ) 1934 { 1935 // Make sure we are not missing any param: 1936 $params = array_merge( array( 1937 'before' => '<p class="msg_nothing">', 1938 'after' => '</p>', 1939 'msg_empty' => T_('Sorry, there is nothing to display...'), 1940 ), $params ); 1941 1942 echo $params['before']; 1943 echo $params['msg_empty']; 1944 echo $params['after']; 1945 1946 return true; 1947 } 1948 return false; 1949 } 1950 1951 } 1952 1953 1954 // _________________ Helper callback functions __________________ 1955 1956 function conditional( $condition, $on_true, $on_false = '' ) 1957 { 1958 if( $condition ) 1959 { 1960 return $on_true; 1961 } 1962 else 1963 { 1964 return $on_false; 1965 } 1966 } 1967 1968 1969 1970 1971 /* 1972 * $Log: _results.class.php,v $ 1973 * Revision 1.4 2007/11/03 21:04:26 fplanque 1974 * skin cleanup 1975 * 1976 * Revision 1.3 2007/09/22 22:11:18 fplanque 1977 * minor 1978 * 1979 * Revision 1.2 2007/07/24 23:29:25 blueyed 1980 * todo 1981 * 1982 * Revision 1.1 2007/06/25 10:59:03 fplanque 1983 * MODULES (refactored MVC) 1984 * 1985 * Revision 1.56 2007/06/24 22:19:18 fplanque 1986 * minor 1987 * 1988 * Revision 1.55 2007/06/20 23:00:14 blueyed 1989 * doc fixes 1990 * 1991 * Revision 1.54 2007/06/19 23:15:08 blueyed 1992 * doc fixes 1993 * 1994 * Revision 1.53 2007/05/26 22:21:32 blueyed 1995 * Made $limit for Results configurable per user 1996 * 1997 * Revision 1.52 2007/04/26 00:11:08 fplanque 1998 * (c) 2007 1999 * 2000 * Revision 1.51 2007/03/19 21:56:45 fplanque 2001 * minor 2002 * 2003 * Revision 1.50 2007/03/19 21:15:57 blueyed 2004 * todo for api change of Results $limit param 2005 * 2006 * Revision 1.49 2007/02/16 17:29:14 waltercruz 2007 * A more tricky regexp is needed to handle tre FROM part with the EXTRACT syntax 2008 * 2009 * Revision 1.48 2007/01/23 22:08:49 fplanque 2010 * cleanup 2011 * 2012 * Revision 1.47 2007/01/14 22:06:48 fplanque 2013 * support for customized 'no results' messages 2014 * 2015 * Revision 1.46 2007/01/14 17:32:41 blueyed 2016 * Always replace/remove "$colspan_attrib$" 2017 * 2018 * Revision 1.45 2007/01/14 03:00:02 blueyed 2019 * typo and use $this->params['grp_line_end'] instead of '</tr>' 2020 * 2021 * Revision 1.44 2007/01/13 22:28:12 fplanque 2022 * doc 2023 * 2024 * Revision 1.43 2007/01/13 19:19:24 blueyed 2025 * Grouping by object properties 2026 * 2027 * Revision 1.42 2007/01/13 16:55:00 blueyed 2028 * Removed $DB member of Results class and use global $DB instead 2029 * 2030 * Revision 1.41 2007/01/13 16:41:51 blueyed 2031 * doc 2032 * 2033 * Revision 1.40 2007/01/11 21:06:05 fplanque 2034 * bugfix 2035 * 2036 * Revision 1.39 2007/01/11 02:25:06 fplanque 2037 * refactoring of Table displays 2038 * body / line / col / fadeout 2039 * 2040 * Revision 1.38 2007/01/08 23:44:19 fplanque 2041 * inserted Table widget 2042 * WARNING: this has nothing to do with ComponentWidgets... 2043 * (except that I'm gonna need the Table Widget when handling the ComponentWidgets :> 2044 * 2045 * Revision 1.37 2007/01/07 05:27:41 fplanque 2046 * extended fadeout, but still not fixed everywhere 2047 * 2048 * Revision 1.36 2006/12/14 19:15:53 fplanque 2049 * minor fix 2050 * 2051 * Revision 1.35 2006/12/07 23:13:13 fplanque 2052 * @var needs to have only one argument: the variable type 2053 * Otherwise, I can't code! 2054 * 2055 * Revision 1.34 2006/11/24 18:27:27 blueyed 2056 * Fixed link to b2evo CVS browsing interface in file docblocks 2057 * 2058 * Revision 1.33 2006/11/14 00:47:32 fplanque 2059 * doc 2060 * 2061 * Revision 1.32 2006/11/01 12:20:24 blueyed 2062 * doc/todo 2063 * 2064 * Revision 1.31 2006/10/06 20:52:23 blueyed 2065 * Small fix, doc todo 2066 */ 2067 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Thu Nov 29 23:58:50 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |