pager.inc 14.2 KB
Newer Older
Dries's avatar
Dries committed
1 2
<?php

3
/**
4 5
 * @file
 * Functions to aid in presenting database results as a set of pages.
6
 */
7

8 9
use Drupal\Core\Template\Attribute;

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
/**
 * Returns the current page being requested for display within a pager.
 *
 * @param $element
 *  An optional integer to distinguish between multiple pagers on one page.
 *
 * @return
 *  The number of the current requested page, within the pager represented by
 *  $element. This is determined from the URL query parameter $_GET['page'], or
 *  0 by default. Note that this number may differ from the actual page being
 *  displayed. For example, if a search for "example text" brings up three
 *  pages of results, but a users visits search/node/example+text?page=10, this
 *  function will return 10, even though the default pager implementation
 *  adjusts for this and still displays the third page of search results at
 *  that URL.
 *
 * @see pager_default_initialize()
 */
function pager_find_page($element = 0) {
  $page = isset($_GET['page']) ? $_GET['page'] : '';
  $page_array = explode(',', $page);
  if (!isset($page_array[$element])) {
    $page_array[$element] = 0;
  }
  return (int) $page_array[$element];
}

/**
 * Initializes a pager for theme('pager').
 *
 * This function sets up the necessary global variables so that future calls
 * to theme('pager') will render a pager that correctly corresponds to the
 * items being displayed.
 *
44
 * If the items being displayed result from a database query performed using
45 46
 * Drupal's database API, and if you have control over the construction of the
 * database query, you do not need to call this function directly; instead, you
47 48
 * can simply extend the query object with the 'PagerSelectExtender' extender
 * before executing it. For example:
49
 * @code
50 51
 *   $query = db_select('some_table')
 *     ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 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
 * @endcode
 *
 * However, if you are using a different method for generating the items to be
 * paged through, then you should call this function in preparation.
 *
 * The following example shows how this function can be used in a page callback
 * that invokes an external datastore with an SQL-like syntax:
 * @code
 *   // First find the total number of items and initialize the pager.
 *   $where = "status = 1";
 *   $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
 *   $num_per_page = variable_get('mymodule_num_per_page', 10);
 *   $page = pager_default_initialize($total, $num_per_page);
 *
 *   // Next, retrieve and display the items for the current page.
 *   $offset = $num_per_page * $page;
 *   $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll();
 *   $output = theme('mymodule_results', array('result' => $result));
 *
 *   // Finally, display the pager controls, and return.
 *   $output .= theme('pager');
 *   return $output;
 * @endcode
 *
 * A second example involves a page callback that invokes an external search
 * service where the total number of matching results is provided as part of
 * the returned set (so that we do not need a separate query in order to obtain
 * this information). Here, we call pager_find_page() to calculate the desired
 * offset before the search is invoked:
 * @code
 *   // Perform the query, using the requested offset from pager_find_page().
 *   // This comes from a URL parameter, so here we are assuming that the URL
 *   // parameter corresponds to an actual page of results that will exist
 *   // within the set.
 *   $page = pager_find_page();
 *   $num_per_page = variable_get('mymodule_num_per_page', 10);
 *   $offset = $num_per_page * $page;
 *   $result = mymodule_remote_search($keywords, $offset, $num_per_page);
 *
 *   // Now that we have the total number of results, initialize the pager.
 *   pager_default_initialize($result->total, $num_per_page);
 *
 *   // Display the search results.
 *   $output = theme('search_results', array('results' => $result->data, 'type' => 'remote'));
 *
 *   // Finally, display the pager controls, and return.
 *   $output .= theme('pager');
 *   return $output;
 * @endcode
 *
 * @param $total
 *  The total number of items to be paged.
 * @param $limit
 *  The number of items the calling code will display per page.
 * @param $element
 *  An optional integer to distinguish between multiple pagers on one page.
 *
 * @return
 *   The number of the current page, within the pager represented by $element.
 *   This is determined from the URL query parameter $_GET['page'], or 0 by
 *   default. However, if a page that does not correspond to the actual range
 *   of the result set was requested, this function will return the closest
 *   page actually within the result set.
 */
function pager_default_initialize($total, $limit, $element = 0) {
  global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;

  $page = pager_find_page($element);

  // We calculate the total of pages as ceil(items / limit).
  $pager_total_items[$element] = $total;
  $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
  $pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1));
  $pager_limits[$element] = $limit;
  return $pager_page_array[$element];
}

129
/**
130
 * Compose a URL query parameter array for pager links.
131 132
 *
 * @return
133 134
 *   A URL query parameter array that consists of all components of the current
 *   page request except for those pertaining to paging.
135
 */
136 137 138
function pager_get_query_parameters() {
  $query = &drupal_static(__FUNCTION__);
  if (!isset($query)) {
139
    $query = drupal_get_query_parameters($_GET, array('page'));
140
  }
141
  return $query;
142 143
}

144
/**
145
 * Returns HTML for a query pager.
146 147
 *
 * Menu callbacks that display paged query results should call theme('pager') to
148 149
 * retrieve a pager control so that users can view other results. Format a list
 * of nearby pages with additional query results.
150
 *
151 152 153 154 155 156 157 158 159
 * @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.
 *
160
 * @ingroup themeable
161
 */
162 163 164 165 166
function theme_pager($variables) {
  $tags = $variables['tags'];
  $element = $variables['element'];
  $parameters = $variables['parameters'];
  $quantity = $variables['quantity'];
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
  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.

196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 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
  $li_first = '';
  $li_previous = '';
  $li_next = '';
  $li_last = '';

  // Create the "first" and "previous" links if we are not on the first page.
  if ($pager_page_array[$element] > 0) {
    $li_first = theme('pager_link__first', array(
      'text' => (isset($tags[0]) ? $tags[0] : t('« first')),
      'page_new' => pager_load_array(0, $element, $pager_page_array),
      'element' => $element,
      'parameters' => $parameters,
      'attributes' => array('rel' => 'first'),
    ));
    $li_previous = theme('pager_link__previous', array(
      'text' => isset($tags[1]) ? $tags[1] : t('‹ previous'),
      'page_new' => pager_load_array($pager_page_array[$element] - 1, $element, $pager_page_array),
      'element' => $element,
      'parameters' => $parameters,
      'attributes' => array('rel' => 'prev'),
    ));
  }

  // Create the "last" and "next" links if we are not on the last page.
  if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
    $li_next = theme('pager_link__next', array(
      'text' => isset($tags[3]) ? $tags[3] : t('next ›'),
      'page_new' => pager_load_array($pager_page_array[$element] + 1, $element, $pager_page_array),
      'element' => $element,
      'parameters' => $parameters,
      'attributes' => array('rel' => 'next'),
    ));
    $li_last = theme('pager_link__last', array(
      'text' => (isset($tags[4]) ? $tags[4] : t('last »')),
      'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array),
      'element' => $element,
      'parameters' => $parameters,
      'attributes' => array('rel' => 'last'),
    ));
  }
236

237
  if ($pager_total[$element] > 1) {
238 239
    if ($li_first) {
      $items[] = array(
240 241
        '#wrapper_attributes' => array('class' => array('pager-first')),
        '#markup' => $li_first,
242 243 244 245
      );
    }
    if ($li_previous) {
      $items[] = array(
246 247
        '#wrapper_attributes' => array('class' => array('pager-previous')),
        '#markup' => $li_previous,
248 249 250 251 252 253 254
      );
    }

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

/**
320
 * Returns HTML for a link to a specific query result page.
321
 *
322 323
 * @param $variables
 *   An associative array containing:
324 325 326 327 328
 *   - text: The link text. Also used to figure out the title attribute of the
 *     link, if it is not provided in $variables['attributes']['title']; in
 *     this case, $variables['text'] must be one of the standard pager link
 *     text strings that would be generated by the pager theme functions, such
 *     as a number or t('« first').
329 330 331 332 333
 *   - 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.
334 335 336 337
 *   - attributes: An associative array of HTML attributes to apply to the
 *     pager link.
 *
 * @see theme_pager()
338
 *
339
 * @ingroup themeable
340
 */
341 342 343 344 345 346 347
function theme_pager_link($variables) {
  $text = $variables['text'];
  $page_new = $variables['page_new'];
  $element = $variables['element'];
  $parameters = $variables['parameters'];
  $attributes = $variables['attributes'];

348 349
  $page = isset($_GET['page']) ? $_GET['page'] : '';
  if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
350
    $parameters['page'] = $new_page;
351
  }
352

353
  $query = array();
354
  if (count($parameters)) {
355
    $query = drupal_get_query_parameters($parameters, array());
356
  }
357 358
  if ($query_pager = pager_get_query_parameters()) {
    $query = array_merge($query, $query_pager);
Dries's avatar
Dries committed
359
  }
360

361
  // Set each pager link title
362
  if (!isset($attributes['title'])) {
363
    static $titles = NULL;
364 365 366 367 368 369 370 371 372 373 374
    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];
    }
375
    elseif (is_numeric($text)) {
376
      $attributes['title'] = t('Go to page @number', array('@number' => $text));
377 378
    }
  }
379

380
  return l($text, current_path(), array('query' => $query, 'attributes' => $attributes));
Dries's avatar
Dries committed
381
}
382

383 384 385 386 387 388
/**
 * 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
389 390
function pager_load_array($value, $element, $old_array) {
  $new_array = $old_array;
391
  // Look for empty elements.
Dries's avatar
Dries committed
392
  for ($i = 0; $i < $element; $i++) {
393
    if (empty($new_array[$i])) {
394
      // Load found empty element with 0.
Dries's avatar
Dries committed
395 396 397
      $new_array[$i] = 0;
    }
  }
398
  // Update the changed element.
399
  $new_array[$element] = (int) $value;
Dries's avatar
Dries committed
400 401
  return $new_array;
}