pager.inc

  1. drupal
    1. 4.6 includes/pager.inc
    2. 4.7 includes/pager.inc
    3. 5 includes/pager.inc
    4. 6 includes/pager.inc
    5. 7 includes/pager.inc
    6. 8 core/includes/pager.inc

Functions to aid in presenting database results as a set of pages.

Classes

NameDescription
PagerDefaultQuery extender for pager queries.

Functions & methods

NameDescription
pager_default_initializeInitializes a pager for theme('pager').
pager_find_pageReturns the current page being requested for display within a pager.
pager_get_query_parametersCompose a URL query parameter array for pager links.
pager_load_arrayHelper function
theme_pagerReturns HTML for a query pager.
theme_pager_firstReturns HTML for the "first page" link in a query pager.
theme_pager_lastReturns HTML for the "last page" link in query pager.
theme_pager_linkReturns HTML for a link to a specific query result page.
theme_pager_nextReturns HTML for the "next page" link in a query pager.
theme_pager_previousReturns HTML for the "previous page" link in a query pager.

File

core/includes/pager.inc
View source
  1. <?php
  2. use DrupalCoreDatabaseConnection;
  3. use DrupalCoreDatabaseQuerySelectExtender;
  4. use DrupalCoreDatabaseQuerySelectInterface;
  5. /**
  6. * @file
  7. * Functions to aid in presenting database results as a set of pages.
  8. */
  9. /**
  10. * Query extender for pager queries.
  11. *
  12. * This is the "default" pager mechanism. It creates a paged query with a fixed
  13. * number of entries per page.
  14. */
  15. class PagerDefault extends SelectExtender {
  16. /**
  17. * The highest element we've autogenerated so far.
  18. *
  19. * @var int
  20. */
  21. static $maxElement = 0;
  22. /**
  23. * The number of elements per page to allow.
  24. *
  25. * @var int
  26. */
  27. protected $limit = 10;
  28. /**
  29. * The unique ID of this pager on this page.
  30. *
  31. * @var int
  32. */
  33. protected $element = NULL;
  34. /**
  35. * The count query that will be used for this pager.
  36. *
  37. * @var SelectQueryInterface
  38. */
  39. protected $customCountQuery = FALSE;
  40. public function __construct(SelectInterface $query, Connection $connection) {
  41. parent::__construct($query, $connection);
  42. // Add pager tag. Do this here to ensure that it is always added before
  43. // preExecute() is called.
  44. $this->addTag('pager');
  45. }
  46. /**
  47. * Override the execute method.
  48. *
  49. * Before we run the query, we need to add pager-based range() instructions
  50. * to it.
  51. */
  52. public function execute() {
  53. // Add convenience tag to mark that this is an extended query. We have to
  54. // do this in the constructor to ensure that it is set before preExecute()
  55. // gets called.
  56. if (!$this->preExecute($this)) {
  57. return NULL;
  58. }
  59. // A NULL limit is the "kill switch" for pager queries.
  60. if (empty($this->limit)) {
  61. return;
  62. }
  63. $this->ensureElement();
  64. $total_items = $this->getCountQuery()->execute()->fetchField();
  65. $current_page = pager_default_initialize($total_items, $this->limit, $this->element);
  66. $this->range($current_page * $this->limit, $this->limit);
  67. // Now that we've added our pager-based range instructions, run the query normally.
  68. return $this->query->execute();
  69. }
  70. /**
  71. * Ensure that there is an element associated with this query.
  72. * If an element was not specified previously, then the value of the
  73. * $maxElement counter is taken, after which the counter is incremented.
  74. *
  75. * After running this method, access $this->element to get the element for this
  76. * query.
  77. */
  78. protected function ensureElement() {
  79. if (!isset($this->element)) {
  80. $this->element = self::$maxElement++;
  81. }
  82. }
  83. /**
  84. * Specify the count query object to use for this pager.
  85. *
  86. * You will rarely need to specify a count query directly. If not specified,
  87. * one is generated off of the pager query itself.
  88. *
  89. * @param SelectQueryInterface $query
  90. * The count query object. It must return a single row with a single column,
  91. * which is the total number of records.
  92. */
  93. public function setCountQuery(SelectInterface $query) {
  94. $this->customCountQuery = $query;
  95. }
  96. /**
  97. * Retrieve the count query for this pager.
  98. *
  99. * The count query may be specified manually or, by default, taken from the
  100. * query we are extending.
  101. *
  102. * @return SelectQueryInterface
  103. * A count query object.
  104. */
  105. public function getCountQuery() {
  106. if ($this->customCountQuery) {
  107. return $this->customCountQuery;
  108. }
  109. else {
  110. return $this->query->countQuery();
  111. }
  112. }
  113. /**
  114. * Specify the maximum number of elements per page for this query.
  115. *
  116. * The default if not specified is 10 items per page.
  117. *
  118. * @param $limit
  119. * An integer specifying the number of elements per page. If passed a false
  120. * value (FALSE, 0, NULL), the pager is disabled.
  121. */
  122. public function limit($limit = 10) {
  123. $this->limit = $limit;
  124. return $this;
  125. }
  126. /**
  127. * Specify the element ID for this pager query.
  128. *
  129. * The element is used to differentiate different pager queries on the same
  130. * page so that they may be operated independently. If you do not specify an
  131. * element, every pager query on the page will get a unique element. If for
  132. * whatever reason you want to explicitly define an element for a given query,
  133. * you may do so here.
  134. *
  135. * Setting the element here also increments the static $maxElement counter,
  136. * which is used for determining the $element when there's none specified.
  137. *
  138. * Note that no collision detection is done when setting an element ID
  139. * explicitly, so it is possible for two pagers to end up using the same ID
  140. * if both are set explicitly.
  141. *
  142. * @param $element
  143. */
  144. public function element($element) {
  145. $this->element = $element;
  146. if ($element >= self::$maxElement) {
  147. self::$maxElement = $element + 1;
  148. }
  149. return $this;
  150. }
  151. }
  152. /**
  153. * Returns the current page being requested for display within a pager.
  154. *
  155. * @param $element
  156. * An optional integer to distinguish between multiple pagers on one page.
  157. *
  158. * @return
  159. * The number of the current requested page, within the pager represented by
  160. * $element. This is determined from the URL query parameter $_GET['page'], or
  161. * 0 by default. Note that this number may differ from the actual page being
  162. * displayed. For example, if a search for "example text" brings up three
  163. * pages of results, but a users visits search/node/example+text?page=10, this
  164. * function will return 10, even though the default pager implementation
  165. * adjusts for this and still displays the third page of search results at
  166. * that URL.
  167. *
  168. * @see pager_default_initialize()
  169. */
  170. function pager_find_page($element = 0) {
  171. $page = isset($_GET['page']) ? $_GET['page'] : '';
  172. $page_array = explode(',', $page);
  173. if (!isset($page_array[$element])) {
  174. $page_array[$element] = 0;
  175. }
  176. return (int) $page_array[$element];
  177. }
  178. /**
  179. * Initializes a pager for theme('pager').
  180. *
  181. * This function sets up the necessary global variables so that future calls
  182. * to theme('pager') will render a pager that correctly corresponds to the
  183. * items being displayed.
  184. *
  185. * If the items being displayed result from a database query performed using
  186. * Drupal's database API, and if you have control over the construction of the
  187. * database query, you do not need to call this function directly; instead, you
  188. * can simply extend the query object with the 'PagerDefault' extender before
  189. * executing it. For example:
  190. * @code
  191. * $query = db_select('some_table')->extend('PagerDefault');
  192. * @endcode
  193. *
  194. * However, if you are using a different method for generating the items to be
  195. * paged through, then you should call this function in preparation.
  196. *
  197. * The following example shows how this function can be used in a page callback
  198. * that invokes an external datastore with an SQL-like syntax:
  199. * @code
  200. * // First find the total number of items and initialize the pager.
  201. * $where = "status = 1";
  202. * $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
  203. * $num_per_page = variable_get('mymodule_num_per_page', 10);
  204. * $page = pager_default_initialize($total, $num_per_page);
  205. *
  206. * // Next, retrieve and display the items for the current page.
  207. * $offset = $num_per_page * $page;
  208. * $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll();
  209. * $output = theme('mymodule_results', array('result' => $result));
  210. *
  211. * // Finally, display the pager controls, and return.
  212. * $output .= theme('pager');
  213. * return $output;
  214. * @endcode
  215. *
  216. * A second example involves a page callback that invokes an external search
  217. * service where the total number of matching results is provided as part of
  218. * the returned set (so that we do not need a separate query in order to obtain
  219. * this information). Here, we call pager_find_page() to calculate the desired
  220. * offset before the search is invoked:
  221. * @code
  222. * // Perform the query, using the requested offset from pager_find_page().
  223. * // This comes from a URL parameter, so here we are assuming that the URL
  224. * // parameter corresponds to an actual page of results that will exist
  225. * // within the set.
  226. * $page = pager_find_page();
  227. * $num_per_page = variable_get('mymodule_num_per_page', 10);
  228. * $offset = $num_per_page * $page;
  229. * $result = mymodule_remote_search($keywords, $offset, $num_per_page);
  230. *
  231. * // Now that we have the total number of results, initialize the pager.
  232. * pager_default_initialize($result->total, $num_per_page);
  233. *
  234. * // Display the search results.
  235. * $output = theme('search_results', array('results' => $result->data, 'type' => 'remote'));
  236. *
  237. * // Finally, display the pager controls, and return.
  238. * $output .= theme('pager');
  239. * return $output;
  240. * @endcode
  241. *
  242. * @param $total
  243. * The total number of items to be paged.
  244. * @param $limit
  245. * The number of items the calling code will display per page.
  246. * @param $element
  247. * An optional integer to distinguish between multiple pagers on one page.
  248. *
  249. * @return
  250. * The number of the current page, within the pager represented by $element.
  251. * This is determined from the URL query parameter $_GET['page'], or 0 by
  252. * default. However, if a page that does not correspond to the actual range
  253. * of the result set was requested, this function will return the closest
  254. * page actually within the result set.
  255. */
  256. function pager_default_initialize($total, $limit, $element = 0) {
  257. global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
  258. $page = pager_find_page($element);
  259. // We calculate the total of pages as ceil(items / limit).
  260. $pager_total_items[$element] = $total;
  261. $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
  262. $pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1));
  263. $pager_limits[$element] = $limit;
  264. return $pager_page_array[$element];
  265. }
  266. /**
  267. * Compose a URL query parameter array for pager links.
  268. *
  269. * @return
  270. * A URL query parameter array that consists of all components of the current
  271. * page request except for those pertaining to paging.
  272. */
  273. function pager_get_query_parameters() {
  274. $query = &drupal_static(__FUNCTION__);
  275. if (!isset($query)) {
  276. $query = drupal_get_query_parameters($_GET, array('page'));
  277. }
  278. return $query;
  279. }
  280. /**
  281. * Returns HTML for a query pager.
  282. *
  283. * Menu callbacks that display paged query results should call theme('pager') to
  284. * retrieve a pager control so that users can view other results. Format a list
  285. * of nearby pages with additional query results.
  286. *
  287. * @param $variables
  288. * An associative array containing:
  289. * - tags: An array of labels for the controls in the pager.
  290. * - element: An optional integer to distinguish between multiple pagers on
  291. * one page.
  292. * - parameters: An associative array of query string parameters to append to
  293. * the pager links.
  294. * - quantity: The number of pages in the list.
  295. *
  296. * @ingroup themeable
  297. */
  298. function theme_pager($variables) {
  299. $tags = $variables['tags'];
  300. $element = $variables['element'];
  301. $parameters = $variables['parameters'];
  302. $quantity = $variables['quantity'];
  303. global $pager_page_array, $pager_total;
  304. // Calculate various markers within this pager piece:
  305. // Middle is used to "center" pages around the current page.
  306. $pager_middle = ceil($quantity / 2);
  307. // current is the page we are currently paged to
  308. $pager_current = $pager_page_array[$element] + 1;
  309. // first is the first page listed by this pager piece (re quantity)
  310. $pager_first = $pager_current - $pager_middle + 1;
  311. // last is the last page listed by this pager piece (re quantity)
  312. $pager_last = $pager_current + $quantity - $pager_middle;
  313. // max is the maximum page number
  314. $pager_max = $pager_total[$element];
  315. // End of marker calculations.
  316. // Prepare for generation loop.
  317. $i = $pager_first;
  318. if ($pager_last > $pager_max) {
  319. // Adjust "center" if at end of query.
  320. $i = $i + ($pager_max - $pager_last);
  321. $pager_last = $pager_max;
  322. }
  323. if ($i <= 0) {
  324. // Adjust "center" if at start of query.
  325. $pager_last = $pager_last + (1 - $i);
  326. $i = 1;
  327. }
  328. // End of generation loop preparation.
  329. $li_first = theme('pager_first', array('text' => (isset($tags[0]) ? $tags[0] : t('« first')), 'element' => $element, 'parameters' => $parameters));
  330. $li_previous = theme('pager_previous', array('text' => (isset($tags[1]) ? $tags[1] : t('‹ previous')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
  331. $li_next = theme('pager_next', array('text' => (isset($tags[3]) ? $tags[3] : t('next ›')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
  332. $li_last = theme('pager_last', array('text' => (isset($tags[4]) ? $tags[4] : t('last »')), 'element' => $element, 'parameters' => $parameters));
  333. if ($pager_total[$element] > 1) {
  334. if ($li_first) {
  335. $items[] = array(
  336. 'class' => array('pager-first'),
  337. 'data' => $li_first,
  338. );
  339. }
  340. if ($li_previous) {
  341. $items[] = array(
  342. 'class' => array('pager-previous'),
  343. 'data' => $li_previous,
  344. );
  345. }
  346. // When there is more than one page, create the pager list.
  347. if ($i != $pager_max) {
  348. if ($i > 1) {
  349. $items[] = array(
  350. 'class' => array('pager-ellipsis'),
  351. 'data' => '…',
  352. );
  353. }
  354. // Now generate the actual pager piece.
  355. for (; $i <= $pager_last && $i <= $pager_max; $i++) {
  356. if ($i < $pager_current) {
  357. $items[] = array(
  358. 'class' => array('pager-item'),
  359. 'data' => theme('pager_previous', array('text' => $i, 'element' => $element, 'interval' => ($pager_current - $i), 'parameters' => $parameters)),
  360. );
  361. }
  362. if ($i == $pager_current) {
  363. $items[] = array(
  364. 'class' => array('pager-current'),
  365. 'data' => $i,
  366. );
  367. }
  368. if ($i > $pager_current) {
  369. $items[] = array(
  370. 'class' => array('pager-item'),
  371. 'data' => theme('pager_next', array('text' => $i, 'element' => $element, 'interval' => ($i - $pager_current), 'parameters' => $parameters)),
  372. );
  373. }
  374. }
  375. if ($i < $pager_max) {
  376. $items[] = array(
  377. 'class' => array('pager-ellipsis'),
  378. 'data' => '…',
  379. );
  380. }
  381. }
  382. // End generation.
  383. if ($li_next) {
  384. $items[] = array(
  385. 'class' => array('pager-next'),
  386. 'data' => $li_next,
  387. );
  388. }
  389. if ($li_last) {
  390. $items[] = array(
  391. 'class' => array('pager-last'),
  392. 'data' => $li_last,
  393. );
  394. }
  395. return '<h2 class="element-invisible">' . t('Pages') . '</h2>' . theme('item_list', array(
  396. 'items' => $items,
  397. 'attributes' => array('class' => array('pager')),
  398. ));
  399. }
  400. }
  401. /**
  402. * @defgroup pagerpieces Pager pieces
  403. * @{
  404. * Theme functions for customizing pager elements.
  405. *
  406. * Note that you should NOT modify this file to customize your pager.
  407. */
  408. /**
  409. * Returns HTML for the "first page" link in a query pager.
  410. *
  411. * @param $variables
  412. * An associative array containing:
  413. * - text: The name (or image) of the link.
  414. * - element: An optional integer to distinguish between multiple pagers on
  415. * one page.
  416. * - parameters: An associative array of query string parameters to append to
  417. * the pager links.
  418. *
  419. * @ingroup themeable
  420. */
  421. function theme_pager_first($variables) {
  422. $text = $variables['text'];
  423. $element = $variables['element'];
  424. $parameters = $variables['parameters'];
  425. global $pager_page_array;
  426. $output = '';
  427. // If we are anywhere but the first page
  428. if ($pager_page_array[$element] > 0) {
  429. $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
  430. }
  431. return $output;
  432. }
  433. /**
  434. * Returns HTML for the "previous page" link in a query pager.
  435. *
  436. * @param $variables
  437. * An associative array containing:
  438. * - text: The name (or image) of the link.
  439. * - element: An optional integer to distinguish between multiple pagers on
  440. * one page.
  441. * - interval: The number of pages to move backward when the link is clicked.
  442. * - parameters: An associative array of query string parameters to append to
  443. * the pager links.
  444. *
  445. * @ingroup themeable
  446. */
  447. function theme_pager_previous($variables) {
  448. $text = $variables['text'];
  449. $element = $variables['element'];
  450. $interval = $variables['interval'];
  451. $parameters = $variables['parameters'];
  452. global $pager_page_array;
  453. $output = '';
  454. // If we are anywhere but the first page
  455. if ($pager_page_array[$element] > 0) {
  456. $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
  457. // If the previous page is the first page, mark the link as such.
  458. if ($page_new[$element] == 0) {
  459. $output = theme('pager_first', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
  460. }
  461. // The previous page is not the first page.
  462. else {
  463. $output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
  464. }
  465. }
  466. return $output;
  467. }
  468. /**
  469. * Returns HTML for the "next page" link in a query pager.
  470. *
  471. * @param $variables
  472. * An associative array containing:
  473. * - text: The name (or image) of the link.
  474. * - element: An optional integer to distinguish between multiple pagers on
  475. * one page.
  476. * - interval: The number of pages to move forward when the link is clicked.
  477. * - parameters: An associative array of query string parameters to append to
  478. * the pager links.
  479. *
  480. * @ingroup themeable
  481. */
  482. function theme_pager_next($variables) {
  483. $text = $variables['text'];
  484. $element = $variables['element'];
  485. $interval = $variables['interval'];
  486. $parameters = $variables['parameters'];
  487. global $pager_page_array, $pager_total;
  488. $output = '';
  489. // If we are anywhere but the last page
  490. if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
  491. $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
  492. // If the next page is the last page, mark the link as such.
  493. if ($page_new[$element] == ($pager_total[$element] - 1)) {
  494. $output = theme('pager_last', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
  495. }
  496. // The next page is not the last page.
  497. else {
  498. $output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
  499. }
  500. }
  501. return $output;
  502. }
  503. /**
  504. * Returns HTML for the "last page" link in query pager.
  505. *
  506. * @param $variables
  507. * An associative array containing:
  508. * - text: The name (or image) of the link.
  509. * - element: An optional integer to distinguish between multiple pagers on
  510. * one page.
  511. * - parameters: An associative array of query string parameters to append to
  512. * the pager links.
  513. *
  514. * @ingroup themeable
  515. */
  516. function theme_pager_last($variables) {
  517. $text = $variables['text'];
  518. $element = $variables['element'];
  519. $parameters = $variables['parameters'];
  520. global $pager_page_array, $pager_total;
  521. $output = '';
  522. // If we are anywhere but the last page
  523. if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
  524. $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
  525. }
  526. return $output;
  527. }
  528. /**
  529. * Returns HTML for a link to a specific query result page.
  530. *
  531. * @param $variables
  532. * An associative array containing:
  533. * - text: The link text. Also used to figure out the title attribute of the
  534. * link, if it is not provided in $variables['attributes']['title']; in
  535. * this case, $variables['text'] must be one of the standard pager link
  536. * text strings that would be generated by the pager theme functions, such
  537. * as a number or t('« first').
  538. * - page_new: The first result to display on the linked page.
  539. * - element: An optional integer to distinguish between multiple pagers on
  540. * one page.
  541. * - parameters: An associative array of query string parameters to append to
  542. * the pager link.
  543. * - attributes: An associative array of HTML attributes to apply to the
  544. * pager link.
  545. *
  546. * @see theme_pager()
  547. *
  548. * @ingroup themeable
  549. */
  550. function theme_pager_link($variables) {
  551. $text = $variables['text'];
  552. $page_new = $variables['page_new'];
  553. $element = $variables['element'];
  554. $parameters = $variables['parameters'];
  555. $attributes = $variables['attributes'];
  556. $page = isset($_GET['page']) ? $_GET['page'] : '';
  557. if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
  558. $parameters['page'] = $new_page;
  559. }
  560. $query = array();
  561. if (count($parameters)) {
  562. $query = drupal_get_query_parameters($parameters, array());
  563. }
  564. if ($query_pager = pager_get_query_parameters()) {
  565. $query = array_merge($query, $query_pager);
  566. }
  567. // Set each pager link title
  568. if (!isset($attributes['title'])) {
  569. static $titles = NULL;
  570. if (!isset($titles)) {
  571. $titles = array(
  572. t('« first') => t('Go to first page'),
  573. t('‹ previous') => t('Go to previous page'),
  574. t('next ›') => t('Go to next page'),
  575. t('last »') => t('Go to last page'),
  576. );
  577. }
  578. if (isset($titles[$text])) {
  579. $attributes['title'] = $titles[$text];
  580. }
  581. elseif (is_numeric($text)) {
  582. $attributes['title'] = t('Go to page @number', array('@number' => $text));
  583. }
  584. }
  585. // @todo l() cannot be used here, since it adds an 'active' class based on the
  586. // path only (which is always the current path for pager links). Apparently,
  587. // none of the pager links is active at any time - but it should still be
  588. // possible to use l() here.
  589. // @see http://drupal.org/node/1410574
  590. $attributes['href'] = url(current_path(), array('query' => $query));
  591. return '<a' . drupal_attributes($attributes) . '>' . check_plain($text) . '</a>';
  592. }
  593. /**
  594. * @} End of "Pager pieces".
  595. */
  596. /**
  597. * Helper function
  598. *
  599. * Copies $old_array to $new_array and sets $new_array[$element] = $value
  600. * Fills in $new_array[0 .. $element - 1] = 0
  601. */
  602. function pager_load_array($value, $element, $old_array) {
  603. $new_array = $old_array;
  604. // Look for empty elements.
  605. for ($i = 0; $i < $element; $i++) {
  606. if (empty($new_array[$i])) {
  607. // Load found empty element with 0.
  608. $new_array[$i] = 0;
  609. }
  610. }
  611. // Update the changed element.
  612. $new_array[$element] = (int) $value;
  613. return $new_array;
  614. }
Login or register to post comments