[ Index ]
 

Code source de b2evolution 2.1.0-beta

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/blogs/inc/_core/ui/results/ -> _results.class.php (source)

   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 '&nbsp;';
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.'&amp;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.'&amp;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  ?>


Généré le : Thu Nov 29 23:58:50 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics