pager.inc 16.5 KB
Newer Older
Dries's avatar
 
Dries committed
1
<?php
Dries's avatar
 
Dries committed
2
// $Id$
Dries's avatar
 
Dries committed
3

4
/**
5 6
 * @file
 * Functions to aid in presenting database results as a set of pages.
Dries's avatar
 
Dries committed
7
 */
8

9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

/**
 * Query extender for pager queries.
 *
 * This is the "default" pager mechanism.  It creates a paged query with a fixed
 * number of entries per page.
 */
class PagerDefault extends SelectQueryExtender {

  /**
   * The highest element we've autogenerated so far.
   *
   * @var int
   */
  static protected $maxElement = 0;

  /**
   * The number of elements per page to allow.
   *
   * @var int
   */
  protected $limit = 10;

  /**
   * The unique ID of this pager on this page.
   *
   * @var int
   */
  protected $element = NULL;

  /**
   * The count query that will be used for this pager.
   *
   * @var SelectQueryInterface
   */
  protected $customCountQuery = FALSE;

46 47 48 49 50 51 52 53
  public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) {
    parent::__construct($query, $connection);

    // Add pager tag. Do this here to ensure that it is always added before
    // preExecute() is called.
    $this->addTag('pager');
  }

54 55 56 57 58 59 60
  /**
   * Override the execute method.
   *
   * Before we run the query, we need to add pager-based range() instructions
   * to it.
   */
  public function execute() {
61
    global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
62

63 64 65 66 67 68 69
    // Add convenience tag to mark that this is an extended query. We have to
    // do this in the constructor to ensure that it is set before preExecute()
    // gets called.
    if (!$this->preExecute($this)) {
      return NULL;
    }

70 71 72 73 74 75 76 77 78 79 80
    // A NULL limit is the "kill switch" for pager queries.
    if (empty($this->limit)) {
      return;
    }
    $this->ensureElement();

    $page = isset($_GET['page']) ? $_GET['page'] : '';

    // Convert comma-separated $page to an array, used by other functions.
    $pager_page_array = explode(',', $page);

81 82 83 84
    if (!isset($pager_page_array[$this->element])) {
      $pager_page_array[$this->element] = 0;
    }

85 86 87
    // We calculate the total of pages as ceil(items / limit).
    $pager_total_items[$this->element] = $this->getCountQuery()->execute()->fetchField();
    $pager_total[$this->element] = ceil($pager_total_items[$this->element] / $this->limit);
88
    $pager_page_array[$this->element] = max(0, min((int) $pager_page_array[$this->element], ((int) $pager_total[$this->element]) - 1));
89
    $pager_limits[$this->element] = $this->limit;
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
    $this->range($pager_page_array[$this->element] * $this->limit, $this->limit);

    // Now that we've added our pager-based range instructions, run the query normally.
    return $this->query->execute();
  }

  /**
   * Ensure that there is an element associated with this query.
   *
   * After running this query, access $this->element to get the element for this
   * query.
   */
  protected function ensureElement() {
    if (!empty($this->element)) {
      return;
    }

    $this->element = self::$maxElement++;
  }

  /**
   * Specify the count query object to use for this pager.
   *
   * You will rarely need to specify a count query directly.  If not specified,
   * one is generated off of the pager query itself.
   *
   * @param SelectQueryInterface $query
   *   The count query object.  It must return a single row with a single column,
   *   which is the total number of records.
   */
  public function setCountQuery(SelectQueryInterface $query) {
    $this->customCountQuery = $query;
  }

  /**
   * Retrieve the count query for this pager.
   *
   * The count query may be specified manually or, by default, taken from the
   * query we are extending.
   *
130 131
   * @return SelectQueryInterface
   *   A count query object.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
   */
  protected function getCountQuery() {
    if ($this->customCountQuery) {
      return $this->customCountQuery;
    }
    else {
      return $this->query->countQuery();
    }
  }

  /**
   * Specify the maximum number of elements per page for this query.
   *
   * The default if not specified is 10 items per page.
   *
   * @param $limit
   *   An integer specifying the number of elements per page.  If passed a false
   *   value (FALSE, 0, NULL), the pager is disabled.
   */
  public function limit($limit = 10) {
    $this->limit = $limit;
    return $this;
  }

  /**
   * Specify the element ID for this pager query.
   *
   * The element is used to differentiate different pager queries on the same
   * page so that they may be operated independently.  If you do not specify an
   * element, every pager query on the page will get a unique element.  If for
   * whatever reason you want to explicitly define an element for a given query,
   * you may do so here.
   *
   * @param $element
   */
  public function element($element) {
    $this->element = $element;
    return $this;
  }
}

173
/**
174
 * Compose a URL query parameter array for pager links.
175 176
 *
 * @return
177 178
 *   A URL query parameter array that consists of all components of the current
 *   page request except for those pertaining to paging.
179
 */
180 181 182
function pager_get_query_parameters() {
  $query = &drupal_static(__FUNCTION__);
  if (!isset($query)) {
183
    $query = drupal_get_query_parameters($_GET, array('q', 'page'));
184
  }
185
  return $query;
186 187
}

Dries's avatar
 
Dries committed
188
/**
189
 * Returns HTML for a query pager.
190 191
 *
 * Menu callbacks that display paged query results should call theme('pager') to
192 193
 * retrieve a pager control so that users can view other results. Format a list
 * of nearby pages with additional query results.
194
 *
195 196 197 198 199 200 201 202 203
 * @param $variables
 *   An associative array containing:
 *   - tags: An array of labels for the controls in the pager.
 *   - element: An optional integer to distinguish between multiple pagers on
 *     one page.
 *   - parameters: An associative array of query string parameters to append to
 *     the pager links.
 *   - quantity: The number of pages in the list.
 *
Dries's avatar
 
Dries committed
204
 * @ingroup themeable
Dries's avatar
 
Dries committed
205
 */
206 207 208 209 210
function theme_pager($variables) {
  $tags = $variables['tags'];
  $element = $variables['element'];
  $parameters = $variables['parameters'];
  $quantity = $variables['quantity'];
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
  global $pager_page_array, $pager_total;

  // Calculate various markers within this pager piece:
  // Middle is used to "center" pages around the current page.
  $pager_middle = ceil($quantity / 2);
  // current is the page we are currently paged to
  $pager_current = $pager_page_array[$element] + 1;
  // first is the first page listed by this pager piece (re quantity)
  $pager_first = $pager_current - $pager_middle + 1;
  // last is the last page listed by this pager piece (re quantity)
  $pager_last = $pager_current + $quantity - $pager_middle;
  // max is the maximum page number
  $pager_max = $pager_total[$element];
  // End of marker calculations.

  // Prepare for generation loop.
  $i = $pager_first;
  if ($pager_last > $pager_max) {
    // Adjust "center" if at end of query.
    $i = $i + ($pager_max - $pager_last);
    $pager_last = $pager_max;
  }
  if ($i <= 0) {
    // Adjust "center" if at start of query.
    $pager_last = $pager_last + (1 - $i);
    $i = 1;
  }
  // End of generation loop preparation.

240 241 242 243
  $li_first = theme('pager_first', array('text' => (isset($tags[0]) ? $tags[0] : t('« first')), 'element' => $element, 'parameters' => $parameters));
  $li_previous = theme('pager_previous', array('text' => (isset($tags[1]) ? $tags[1] : t('‹ previous')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
  $li_next = theme('pager_next', array('text' => (isset($tags[3]) ? $tags[3] : t('next ›')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
  $li_last = theme('pager_last', array('text' => (isset($tags[4]) ? $tags[4] : t('last »')), 'element' => $element, 'parameters' => $parameters));
Dries's avatar
 
Dries committed
244

245
  if ($pager_total[$element] > 1) {
246 247
    if ($li_first) {
      $items[] = array(
248
        'class' => array('pager-first'),
249 250 251 252 253
        'data' => $li_first,
      );
    }
    if ($li_previous) {
      $items[] = array(
254
        'class' => array('pager-previous'),
255 256 257 258 259 260 261 262
        'data' => $li_previous,
      );
    }

    // When there is more than one page, create the pager list.
    if ($i != $pager_max) {
      if ($i > 1) {
        $items[] = array(
263
          'class' => array('pager-ellipsis'),
264 265 266 267 268 269 270
          'data' => '…',
        );
      }
      // Now generate the actual pager piece.
      for (; $i <= $pager_last && $i <= $pager_max; $i++) {
        if ($i < $pager_current) {
          $items[] = array(
271
            'class' => array('pager-item'),
272
            'data' => theme('pager_previous', array('text' => $i, 'element' => $element, 'interval' => ($pager_current - $i), 'parameters' => $parameters)),
273 274 275 276
          );
        }
        if ($i == $pager_current) {
          $items[] = array(
277
            'class' => array('pager-current'),
278 279 280 281 282
            'data' => $i,
          );
        }
        if ($i > $pager_current) {
          $items[] = array(
283
            'class' => array('pager-item'),
284
            'data' => theme('pager_next', array('text' => $i, 'element' => $element, 'interval' => ($i - $pager_current), 'parameters' => $parameters)),
285 286 287 288 289
          );
        }
      }
      if ($i < $pager_max) {
        $items[] = array(
290
          'class' => array('pager-ellipsis'),
291 292 293 294 295 296 297
          'data' => '…',
        );
      }
    }
    // End generation.
    if ($li_next) {
      $items[] = array(
298
        'class' => array('pager-next'),
299 300 301 302 303
        'data' => $li_next,
      );
    }
    if ($li_last) {
      $items[] = array(
304
        'class' => array('pager-last'),
305 306 307
        'data' => $li_last,
      );
    }
308
    return '<h2 class="element-invisible">' . t('Pages') . '</h2>' . theme('item_list', array('items' => $items, 'title' => NULL, 'type' => 'ul', 'attributes' => array('class' => array('pager'))));
Dries's avatar
Dries committed
309
  }
Dries's avatar
 
Dries committed
310
}
311

312

313
/**
Kjartan's avatar
Kjartan committed
314
 * @name Pager pieces
315
 * @{
Dries's avatar
 
Dries committed
316 317
 * Use these pieces to construct your own custom pagers in your theme. Note that
 * you should NOT modify this file to customize your pager.
318
 */
Dries's avatar
 
Dries committed
319

Dries's avatar
 
Dries committed
320
/**
321
 * Returns HTML for the "first page" link in a query pager.
Dries's avatar
 
Dries committed
322
 *
323 324 325 326 327 328 329 330
 * @param $variables
 *   An associative array containing:
 *   - text: The name (or image) of the link.
 *   - element: An optional integer to distinguish between multiple pagers on
 *     one page.
 *   - parameters: An associative array of query string parameters to append to
 *     the pager links.
 *
Dries's avatar
 
Dries committed
331
 * @ingroup themeable
Dries's avatar
 
Dries committed
332
 */
333 334 335 336
function theme_pager_first($variables) {
  $text = $variables['text'];
  $element = $variables['element'];
  $parameters = $variables['parameters'];
337
  global $pager_page_array;
338
  $output = '';
Dries's avatar
 
Dries committed
339

340 341
  // If we are anywhere but the first page
  if ($pager_page_array[$element] > 0) {
342
    $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
Dries's avatar
 
Dries committed
343
  }
344

Dries's avatar
 
Dries committed
345
  return $output;
Dries's avatar
 
Dries committed
346 347
}

Dries's avatar
 
Dries committed
348
/**
349
 * Returns HTML for the "previous page" link in a query pager.
Dries's avatar
 
Dries committed
350
 *
351 352 353 354 355 356 357 358 359
 * @param $variables
 *   An associative array containing:
 *   - text: The name (or image) of the link.
 *   - element: An optional integer to distinguish between multiple pagers on
 *     one page.
 *   - interval: The number of pages to move backward when the link is clicked.
 *   - parameters: An associative array of query string parameters to append to
 *     the pager links.
 *
Dries's avatar
 
Dries committed
360
 * @ingroup themeable
Dries's avatar
 
Dries committed
361
 */
362 363 364 365 366
function theme_pager_previous($variables) {
  $text = $variables['text'];
  $element = $variables['element'];
  $interval = $variables['interval'];
  $parameters = $variables['parameters'];
367
  global $pager_page_array;
368 369
  $output = '';

370 371 372
  // If we are anywhere but the first page
  if ($pager_page_array[$element] > 0) {
    $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
373

374 375
    // If the previous page is the first page, mark the link as such.
    if ($page_new[$element] == 0) {
376
      $output = theme('pager_first', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
377 378 379
    }
    // The previous page is not the first page.
    else {
380
      $output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
381
    }
Dries's avatar
 
Dries committed
382
  }
383

Dries's avatar
 
Dries committed
384
  return $output;
Dries's avatar
 
Dries committed
385 386
}

Dries's avatar
 
Dries committed
387
/**
388
 * Returns HTML for the "next page" link in a query pager.
389
 *
390 391 392 393 394 395 396 397 398
 * @param $variables
 *   An associative array containing:
 *   - text: The name (or image) of the link.
 *   - element: An optional integer to distinguish between multiple pagers on
 *     one page.
 *   - interval: The number of pages to move forward when the link is clicked.
 *   - parameters: An associative array of query string parameters to append to
 *     the pager links.
 *
Dries's avatar
 
Dries committed
399
 * @ingroup themeable
Dries's avatar
 
Dries committed
400
 */
401 402 403 404 405
function theme_pager_next($variables) {
  $text = $variables['text'];
  $element = $variables['element'];
  $interval = $variables['interval'];
  $parameters = $variables['parameters'];
406
  global $pager_page_array, $pager_total;
407 408
  $output = '';

409 410 411 412 413
  // If we are anywhere but the last page
  if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
    $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
    // If the next page is the last page, mark the link as such.
    if ($page_new[$element] == ($pager_total[$element] - 1)) {
414
      $output = theme('pager_last', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
415 416 417
    }
    // The next page is not the last page.
    else {
418
      $output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
419
    }
Dries's avatar
 
Dries committed
420
  }
421

Dries's avatar
 
Dries committed
422
  return $output;
Dries's avatar
 
Dries committed
423 424
}

Dries's avatar
 
Dries committed
425
/**
426
 * Returns HTML for the "last page" link in query pager.
Dries's avatar
 
Dries committed
427
 *
428 429 430 431 432 433 434 435
 * @param $variables
 *   An associative array containing:
 *   - text: The name (or image) of the link.
 *   - element: An optional integer to distinguish between multiple pagers on
 *     one page.
 *   - parameters: An associative array of query string parameters to append to
 *     the pager links.
 *
Dries's avatar
 
Dries committed
436
 * @ingroup themeable
Dries's avatar
 
Dries committed
437
 */
438 439 440 441
function theme_pager_last($variables) {
  $text = $variables['text'];
  $element = $variables['element'];
  $parameters = $variables['parameters'];
442
  global $pager_page_array, $pager_total;
443
  $output = '';
Dries's avatar
 
Dries committed
444

445 446
  // If we are anywhere but the last page
  if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
447
    $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
Dries's avatar
 
Dries committed
448
  }
449

Dries's avatar
 
Dries committed
450
  return $output;
Dries's avatar
 
Dries committed
451 452 453
}


454
/**
455
 * Returns HTML for a link to a specific query result page.
456
 *
457 458 459 460 461 462 463 464 465 466
 * @param $variables
 *   An associative array containing:
 *   - page_new: The first result to display on the linked page.
 *   - element: An optional integer to distinguish between multiple pagers on
 *     one page.
 *   - parameters: An associative array of query string parameters to append to
 *     the pager link.
 *   - attributes: An associative array of HTML attributes to apply to a pager
 *     anchor tag.
 *
467
 * @ingroup themeable
468
 */
469 470 471 472 473 474 475
function theme_pager_link($variables) {
  $text = $variables['text'];
  $page_new = $variables['page_new'];
  $element = $variables['element'];
  $parameters = $variables['parameters'];
  $attributes = $variables['attributes'];

476 477
  $page = isset($_GET['page']) ? $_GET['page'] : '';
  if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
478
    $parameters['page'] = $new_page;
479
  }
Dries's avatar
 
Dries committed
480

481
  $query = array();
482
  if (count($parameters)) {
483
    $query = drupal_get_query_parameters($parameters, array());
484
  }
485 486
  if ($query_pager = pager_get_query_parameters()) {
    $query = array_merge($query, $query_pager);
Dries's avatar
 
Dries committed
487
  }
Dries's avatar
 
Dries committed
488

489
  // Set each pager link title
490
  if (!isset($attributes['title'])) {
491
    static $titles = NULL;
492 493 494 495 496 497 498 499 500 501 502
    if (!isset($titles)) {
      $titles = array(
        t('« first') => t('Go to first page'),
        t('‹ previous') => t('Go to previous page'),
        t('next ›') => t('Go to next page'),
        t('last »') => t('Go to last page'),
      );
    }
    if (isset($titles[$text])) {
      $attributes['title'] = $titles[$text];
    }
503
    elseif (is_numeric($text)) {
504
      $attributes['title'] = t('Go to page @number', array('@number' => $text));
505 506
    }
  }
507

508
  return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => $query));
Dries's avatar
 
Dries committed
509
}
510

511 512 513
/**
 * @} End of "Pager pieces".
 */
Dries's avatar
 
Dries committed
514

515 516 517 518 519 520
/**
 * Helper function
 *
 * Copies $old_array to $new_array and sets $new_array[$element] = $value
 * Fills in $new_array[0 .. $element - 1] = 0
 */
Dries's avatar
 
Dries committed
521 522
function pager_load_array($value, $element, $old_array) {
  $new_array = $old_array;
Dries's avatar
 
Dries committed
523
  // Look for empty elements.
Dries's avatar
 
Dries committed
524
  for ($i = 0; $i < $element; $i++) {
525
    if (empty($new_array[$i])) {
Dries's avatar
 
Dries committed
526
      // Load found empty element with 0.
Dries's avatar
 
Dries committed
527 528 529
      $new_array[$i] = 0;
    }
  }
Dries's avatar
 
Dries committed
530
  // Update the changed element.
531
  $new_array[$element] = (int) $value;
Dries's avatar
 
Dries committed
532 533
  return $new_array;
}