pager.inc 14.1 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
 * Perform a paged database query.
 *
Dries's avatar
 
Dries committed
12 13 14
 * Use this function when doing select queries you wish to be able to page. The
 * pager uses LIMIT-based queries to fetch only the records required to render a
 * certain page. However, it has to learn the total number of records returned
15 16 17 18 19 20
 * by the query to compute the number of pages (the number of records / records
 * per page). This is done by inserting "COUNT(*)" in the original query. For
 * example, the query "SELECT nid, type FROM node WHERE status = '1' ORDER BY
 * sticky DESC, created DESC" would be rewritten to read "SELECT COUNT(*) FROM
 * node WHERE status = '1' ORDER BY sticky DESC, created DESC". Rewriting the
 * query is accomplished using a regular expression.
21
 *
Dries's avatar
 
Dries committed
22
 * Unfortunately, the rewrite rule does not always work as intended for queries
23
 * that already have a "COUNT(*)" or a "GROUP BY" clause, and possibly for
Dries's avatar
 
Dries committed
24 25
 * other complex queries. In those cases, you can optionally pass a query that
 * will be used to count the records.
26
 *
27 28
 * For example, if you want to page the query "SELECT COUNT(*), TYPE FROM node
 * GROUP BY TYPE", pager_query() would invoke the incorrect query "SELECT
Dries's avatar
 
Dries committed
29 30
 * COUNT(*) FROM node GROUP BY TYPE". So instead, you should pass "SELECT
 * COUNT(DISTINCT(TYPE)) FROM node" as the optional $count_query parameter.
31
 *
32 33 34 35 36 37 38 39
 * @param $query
 *   The SQL query that needs paging.
 * @param $limit
 *   The number of query results to display per page.
 * @param $element
 *   An optional integer to distinguish between multiple pagers on one page.
 * @param $count_query
 *   An SQL query used to count matching records.
Dries's avatar
 
Dries committed
40 41
 * @param ...
 *   A variable number of arguments which are substituted into the query (and
42 43 44
 *   the count query) using printf() syntax. Instead of a variable number of
 *   query arguments, you may also pass a single array containing the query
 *   arguments.
45 46 47
 * @return
 *   A database query result resource, or FALSE if the query was not executed
 *   correctly.
Dries's avatar
 
Dries committed
48 49
 *
 * @ingroup database
50
 */
Dries's avatar
 
Dries committed
51
function pager_query($query, $limit = 10, $element = 0, $count_query = NULL) {
52
  global $pager_page_array, $pager_total, $pager_total_items;
53
  $page = isset($_GET['page']) ? $_GET['page'] : '';
54

Dries's avatar
 
Dries committed
55 56 57
  // Substitute in query arguments.
  $args = func_get_args();
  $args = array_slice($args, 4);
58
  // Alternative syntax for '...'
59
  if (isset($args[0]) && is_array($args[0])) {
60 61
    $args = $args[0];
  }
Dries's avatar
 
Dries committed
62

63
  // Construct a count query if none was given.
Dries's avatar
 
Dries committed
64
  if (!isset($count_query)) {
65
    $count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY .*/'), array('SELECT COUNT(*) FROM ', ''), $query);
66 67
  }

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

71
  // We calculate the total of pages as ceil(items / limit).
72 73 74 75
  $pager_total_items[$element] = db_result(db_query($count_query, $args));
  $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
  $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
  return db_query_range($query, $args, $pager_page_array[$element] * $limit, $limit);
76 77
}

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
/**
 * Compose a query string to append to pager requests.
 *
 * @return
 *   A query string that consists of all components of the current page request
 *   except for those pertaining to paging.
 */
function pager_get_querystring() {
  static $string = NULL;
  if (!isset($string)) {
    $string = drupal_query_string_encode($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE)));
  }
  return $string;
}

Dries's avatar
 
Dries committed
93
/**
94 95 96 97
 * Format a query pager.
 *
 * Menu callbacks that display paged query results should call theme('pager') to
 * retrieve a pager control so that users can view other results.
98
 * Format a list of nearby pages with additional query results.
99 100 101 102 103 104 105
 *
 * @param $tags
 *   An array of labels for the controls in the pager.
 * @param $limit
 *   The number of query results to display per page.
 * @param $element
 *   An optional integer to distinguish between multiple pagers on one page.
106
 * @param $parameters
107
 *   An associative array of query string parameters to append to the pager links.
108 109
 * @param $quantity
 *   The number of pages in the list.
110 111
 * @return
 *   An HTML string that generates the query pager.
Dries's avatar
 
Dries committed
112 113
 *
 * @ingroup themeable
Dries's avatar
 
Dries committed
114
 */
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
function theme_pager($tags = array(), $limit = 10, $element = 0, $parameters = array(), $quantity = 9) {
  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.

145 146 147 148
  $li_first = theme('pager_first', (isset($tags[0]) ? $tags[0] : t('« first')), $limit, $element, $parameters);
  $li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] : t('‹ previous')), $limit, $element, 1, $parameters);
  $li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('next ›')), $limit, $element, 1, $parameters);
  $li_last = theme('pager_last', (isset($tags[4]) ? $tags[4] : t('last »')), $limit, $element, $parameters);
Dries's avatar
 
Dries committed
149

150
  if ($pager_total[$element] > 1) {
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
    if ($li_first) {
      $items[] = array(
        'class' => 'pager-first',
        'data' => $li_first,
      );
    }
    if ($li_previous) {
      $items[] = array(
        'class' => 'pager-previous',
        'data' => $li_previous,
      );
    }

    // When there is more than one page, create the pager list.
    if ($i != $pager_max) {
      if ($i > 1) {
        $items[] = array(
          'class' => 'pager-ellipsis',
          'data' => '…',
        );
      }
      // Now generate the actual pager piece.
      for (; $i <= $pager_last && $i <= $pager_max; $i++) {
        if ($i < $pager_current) {
          $items[] = array(
            'class' => 'pager-item',
            'data' => theme('pager_previous', $i, $limit, $element, ($pager_current - $i), $parameters),
          );
        }
        if ($i == $pager_current) {
          $items[] = array(
            'class' => 'pager-current',
            'data' => $i,
          );
        }
        if ($i > $pager_current) {
          $items[] = array(
            'class' => 'pager-item',
            'data' => theme('pager_next', $i, $limit, $element, ($i - $pager_current), $parameters),
          );
        }
      }
      if ($i < $pager_max) {
        $items[] = array(
          'class' => 'pager-ellipsis',
          'data' => '…',
        );
      }
    }
    // End generation.
    if ($li_next) {
      $items[] = array(
        'class' => 'pager-next',
        'data' => $li_next,
      );
    }
    if ($li_last) {
      $items[] = array(
        'class' => 'pager-last',
        'data' => $li_last,
      );
    }
    return theme('item_list', $items, NULL, 'ul', array('class' => 'pager'));
Dries's avatar
Dries committed
214
  }
Dries's avatar
 
Dries committed
215
}
216

217

218
/**
Kjartan's avatar
Kjartan committed
219
 * @name Pager pieces
220
 * @{
Dries's avatar
 
Dries committed
221 222
 * Use these pieces to construct your own custom pagers in your theme. Note that
 * you should NOT modify this file to customize your pager.
223
 */
Dries's avatar
 
Dries committed
224

Dries's avatar
 
Dries committed
225
/**
226
 * Format a "first page" link.
Dries's avatar
 
Dries committed
227
 *
228 229 230 231 232 233
 * @param $text
 *   The name (or image) of the link.
 * @param $limit
 *   The number of query results to display per page.
 * @param $element
 *   An optional integer to distinguish between multiple pagers on one page.
234
 * @param $parameters
235 236 237
 *   An associative array of query string parameters to append to the pager links.
 * @return
 *   An HTML string that generates this piece of the query pager.
Dries's avatar
 
Dries committed
238 239
 *
 * @ingroup themeable
Dries's avatar
 
Dries committed
240
 */
241
function theme_pager_first($text, $limit, $element = 0, $parameters = array()) {
242
  global $pager_page_array;
243
  $output = '';
Dries's avatar
 
Dries committed
244

245 246
  // If we are anywhere but the first page
  if ($pager_page_array[$element] > 0) {
247
    $output = theme('pager_link', $text, pager_load_array(0, $element, $pager_page_array), $element, $parameters);
Dries's avatar
 
Dries committed
248
  }
249

Dries's avatar
 
Dries committed
250
  return $output;
Dries's avatar
 
Dries committed
251 252
}

Dries's avatar
 
Dries committed
253
/**
254
 * Format a "previous page" link.
Dries's avatar
 
Dries committed
255
 *
256 257 258 259 260 261 262 263
 * @param $text
 *   The name (or image) of the link.
 * @param $limit
 *   The number of query results to display per page.
 * @param $element
 *   An optional integer to distinguish between multiple pagers on one page.
 * @param $interval
 *   The number of pages to move backward when the link is clicked.
264
 * @param $parameters
265 266 267
 *   An associative array of query string parameters to append to the pager links.
 * @return
 *   An HTML string that generates this piece of the query pager.
Dries's avatar
 
Dries committed
268 269
 *
 * @ingroup themeable
Dries's avatar
 
Dries committed
270
 */
271
function theme_pager_previous($text, $limit, $element = 0, $interval = 1, $parameters = array()) {
272
  global $pager_page_array;
273 274
  $output = '';

275 276 277
  // 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);
278

279 280
    // If the previous page is the first page, mark the link as such.
    if ($page_new[$element] == 0) {
281
      $output = theme('pager_first', $text, $limit, $element, $parameters);
282 283 284
    }
    // The previous page is not the first page.
    else {
285
      $output = theme('pager_link', $text, $page_new, $element, $parameters);
286
    }
Dries's avatar
 
Dries committed
287
  }
288

Dries's avatar
 
Dries committed
289
  return $output;
Dries's avatar
 
Dries committed
290 291
}

Dries's avatar
 
Dries committed
292
/**
293
 * Format a "next page" link.
294
 *
295 296 297 298 299 300 301 302
 * @param $text
 *   The name (or image) of the link.
 * @param $limit
 *   The number of query results to display per page.
 * @param $element
 *   An optional integer to distinguish between multiple pagers on one page.
 * @param $interval
 *   The number of pages to move forward when the link is clicked.
303
 * @param $parameters
304 305 306
 *   An associative array of query string parameters to append to the pager links.
 * @return
 *   An HTML string that generates this piece of the query pager.
Dries's avatar
 
Dries committed
307 308
 *
 * @ingroup themeable
Dries's avatar
 
Dries committed
309
 */
310
function theme_pager_next($text, $limit, $element = 0, $interval = 1, $parameters = array()) {
311
  global $pager_page_array, $pager_total;
312 313
  $output = '';

314 315 316 317 318
  // 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)) {
319
      $output = theme('pager_last', $text, $limit, $element, $parameters);
320 321 322
    }
    // The next page is not the last page.
    else {
323
      $output = theme('pager_link', $text, $page_new, $element, $parameters);
324
    }
Dries's avatar
 
Dries committed
325
  }
326

Dries's avatar
 
Dries committed
327
  return $output;
Dries's avatar
 
Dries committed
328 329
}

Dries's avatar
 
Dries committed
330
/**
331
 * Format a "last page" link.
Dries's avatar
 
Dries committed
332
 *
333 334 335 336 337 338
 * @param $text
 *   The name (or image) of the link.
 * @param $limit
 *   The number of query results to display per page.
 * @param $element
 *   An optional integer to distinguish between multiple pagers on one page.
339
 * @param $parameters
340 341 342
 *   An associative array of query string parameters to append to the pager links.
 * @return
 *   An HTML string that generates this piece of the query pager.
Dries's avatar
 
Dries committed
343 344
 *
 * @ingroup themeable
Dries's avatar
 
Dries committed
345
 */
346
function theme_pager_last($text, $limit, $element = 0, $parameters = array()) {
347
  global $pager_page_array, $pager_total;
348
  $output = '';
Dries's avatar
 
Dries committed
349

350 351
  // If we are anywhere but the last page
  if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
352
    $output = theme('pager_link', $text, pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), $element, $parameters);
Dries's avatar
 
Dries committed
353
  }
354

Dries's avatar
 
Dries committed
355
  return $output;
Dries's avatar
 
Dries committed
356 357 358
}


359 360 361
/**
 * Format a link to a specific query result page.
 *
362
 * @param $page_new
363 364 365
 *   The first result to display on the linked page.
 * @param $element
 *   An optional integer to distinguish between multiple pagers on one page.
366
 * @param $parameters
367
 *   An associative array of query string parameters to append to the pager link.
368 369
 * @param $attributes
 *   An associative array of HTML attributes to apply to a pager anchor tag.
370 371
 * @return
 *   An HTML string that generates the link.
372 373
 *
 * @ingroup themeable
374
 */
375
function theme_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
376 377
  $page = isset($_GET['page']) ? $_GET['page'] : '';
  if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
378
    $parameters['page'] = $new_page;
379
  }
Dries's avatar
 
Dries committed
380

381
  $query = array();
382 383 384 385 386 387
  if (count($parameters)) {
    $query[] = drupal_query_string_encode($parameters, array());
  }
  $querystring = pager_get_querystring();
  if ($querystring != '') {
    $query[] = $querystring;
Dries's avatar
 
Dries committed
388
  }
Dries's avatar
 
Dries committed
389

390
  // Set each pager link title
391
  if (!isset($attributes['title'])) {
392
    static $titles = NULL;
393 394 395 396 397 398 399 400 401 402 403
    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];
    }
404
    elseif (is_numeric($text)) {
405
      $attributes['title'] = t('Go to page @number', array('@number' => $text));
406 407
    }
  }
408

409
  return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => count($query) ? implode('&', $query) : NULL));
Dries's avatar
 
Dries committed
410
}
411

412 413 414
/**
 * @} End of "Pager pieces".
 */
Dries's avatar
 
Dries committed
415

416 417 418 419 420 421
/**
 * 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
422 423
function pager_load_array($value, $element, $old_array) {
  $new_array = $old_array;
Dries's avatar
 
Dries committed
424
  // Look for empty elements.
Dries's avatar
 
Dries committed
425 426
  for ($i = 0; $i < $element; $i++) {
    if (!$new_array[$i]) {
Dries's avatar
 
Dries committed
427
      // Load found empty element with 0.
Dries's avatar
 
Dries committed
428 429 430
      $new_array[$i] = 0;
    }
  }
Dries's avatar
 
Dries committed
431
  // Update the changed element.
Dries's avatar
 
Dries committed
432 433 434
  $new_array[$element] = (int)$value;
  return $new_array;
}