pager.inc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <?php
  2. /**
  3. * @file
  4. * Functions to aid in presenting database results as a set of pages.
  5. */
  6. use Drupal\Core\Template\Attribute;
  7. use Drupal\Core\Url;
  8. use Drupal\Component\Utility\Html;
  9. /**
  10. * Returns the current page being requested for display within a pager.
  11. *
  12. * @param int $element
  13. * (optional) An integer to distinguish between multiple pagers on one page.
  14. *
  15. * @return int
  16. * The number of the current requested page, within the pager represented by
  17. * $element. This is determined from the URL query parameter
  18. * \Drupal::request()->query->get('page'), or 0 by default. Note that this
  19. * number may differ from the actual page being displayed. For example, if a
  20. * search for "example text" brings up three pages of results, but a user
  21. * visits search/node/example+text?page=10, this function will return 10,
  22. * even though the default pager implementation adjusts for this and still
  23. * displays the third page of search results at that URL.
  24. *
  25. * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
  26. * \Drupal\Core\Pager\RequestPagerInterface->findPage() instead.
  27. *
  28. * @see https://www.drupal.org/node/2779457
  29. * @see \Drupal\Core\Pager\PagerParametersInterface::findPage()
  30. */
  31. function pager_find_page($element = 0) {
  32. @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->findPage() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
  33. /* @var $pager_parameters \Drupal\Core\Pager\PagerParametersInterface */
  34. $pager_parameters = \Drupal::service('pager.parameters');
  35. return $pager_parameters->findPage($element);
  36. }
  37. /**
  38. * Initializes a pager.
  39. *
  40. * This function sets up the necessary global variables so that the render
  41. * system will correctly process #type 'pager' render arrays to output pagers
  42. * that correspond to the items being displayed.
  43. *
  44. * If the items being displayed result from a database query performed using
  45. * Drupal's database API, and if you have control over the construction of the
  46. * database query, you do not need to call this function directly; instead, you
  47. * can simply extend the query object with the 'PagerSelectExtender' extender
  48. * before executing it. For example:
  49. * @code
  50. * $query = \Drupal::database()->select('some_table')
  51. * ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
  52. * @endcode
  53. *
  54. * However, if you are using a different method for generating the items to be
  55. * paged through, then you should call this function in preparation.
  56. *
  57. * The following example shows how this function can be used in a controller
  58. * that invokes an external datastore with an SQL-like syntax:
  59. * @code
  60. * // First find the total number of items and initialize the pager.
  61. * $where = "status = 1";
  62. * $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
  63. * $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
  64. * $page = pager_default_initialize($total, $num_per_page);
  65. *
  66. * // Next, retrieve the items for the current page and put them into a
  67. * // render array.
  68. * $offset = $num_per_page * $page;
  69. * $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll();
  70. * $render = [];
  71. * $render[] = [
  72. * '#theme' => 'mymodule_results',
  73. * '#result' => $result,
  74. * ];
  75. *
  76. * // Finally, add the pager to the render array, and return.
  77. * $render[] = ['#type' => 'pager'];
  78. * return $render;
  79. * @endcode
  80. *
  81. * A second example involves a controller that invokes an external search
  82. * service where the total number of matching results is provided as part of
  83. * the returned set (so that we do not need a separate query in order to obtain
  84. * this information). Here, we call pager_find_page() to calculate the desired
  85. * offset before the search is invoked:
  86. * @code
  87. * // Perform the query, using the requested offset from pager_find_page().
  88. * // This comes from a URL parameter, so here we are assuming that the URL
  89. * // parameter corresponds to an actual page of results that will exist
  90. * // within the set.
  91. * $page = pager_find_page();
  92. * $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
  93. * $offset = $num_per_page * $page;
  94. * $result = mymodule_remote_search($keywords, $offset, $num_per_page);
  95. *
  96. * // Now that we have the total number of results, initialize the pager.
  97. * pager_default_initialize($result->total, $num_per_page);
  98. *
  99. * // Create a render array with the search results.
  100. * $render = [];
  101. * $render[] = [
  102. * '#theme' => 'search_results',
  103. * '#results' => $result->data,
  104. * '#type' => 'remote',
  105. * ];
  106. *
  107. * // Finally, add the pager to the render array, and return.
  108. * $render[] = ['#type' => 'pager'];
  109. * return $render;
  110. * @endcode
  111. *
  112. * @param int $total
  113. * The total number of items to be paged.
  114. * @param int $limit
  115. * The number of items the calling code will display per page.
  116. * @param int $element
  117. * (optional) An integer to distinguish between multiple pagers on one page.
  118. *
  119. * @return int
  120. * The number of the current page, within the pager represented by $element.
  121. * This is determined from the URL query parameter
  122. * \Drupal::request()->query->get('page), or 0 by default. However, if a page
  123. * that does not correspond to the actual range of the result set was
  124. * requested, this function will return the closest page actually within the
  125. * result set.
  126. *
  127. * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
  128. * \Drupal\Core\Pager\PagerManagerInterface->defaultInitialize() instead.
  129. *
  130. * @see https://www.drupal.org/node/2779457
  131. * @see \Drupal\Core\Pager\PagerManagerInterface::createPager()
  132. */
  133. function pager_default_initialize($total, $limit, $element = 0) {
  134. @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->createPager() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
  135. /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
  136. $pager_manager = \Drupal::service('pager.manager');
  137. $pager = $pager_manager->createPager($total, $limit, $element);
  138. return $pager->getCurrentPage();
  139. }
  140. /**
  141. * Compose a URL query parameter array for pager links.
  142. *
  143. * @return array
  144. * A URL query parameter array that consists of all components of the current
  145. * page request except for those pertaining to paging.
  146. *
  147. * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
  148. * \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead.
  149. *
  150. * @see https://www.drupal.org/node/2779457
  151. * @see \Drupal\Core\Pager\PagerParametersInterface::getQueryParameters()
  152. */
  153. function pager_get_query_parameters() {
  154. @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\RequestPagerInterface->getQueryParameters() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
  155. /* @var $pager_params \Drupal\Core\Pager\PagerParametersInterface */
  156. $pager_params = \Drupal::service('pager.parameters');
  157. return $pager_params->getQueryParameters();
  158. }
  159. /**
  160. * Prepares variables for pager templates.
  161. *
  162. * Default template: pager.html.twig.
  163. *
  164. * Menu callbacks that display paged query results should use #type => pager
  165. * to retrieve a pager control so that users can view other results. Format a
  166. * list of nearby pages with additional query results.
  167. *
  168. * @param array $variables
  169. * An associative array containing:
  170. * - pager: A render element containing:
  171. * - #tags: An array of labels for the controls in the pager.
  172. * - #element: An optional integer to distinguish between multiple pagers on
  173. * one page.
  174. * - #parameters: An associative array of query string parameters to append
  175. * to the pager links.
  176. * - #route_parameters: An associative array of the route parameters.
  177. * - #quantity: The number of pages in the list.
  178. */
  179. function template_preprocess_pager(&$variables) {
  180. $element = $variables['pager']['#element'];
  181. $parameters = $variables['pager']['#parameters'];
  182. $quantity = empty($variables['pager']['#quantity']) ? 0 : $variables['pager']['#quantity'];
  183. $route_name = $variables['pager']['#route_name'];
  184. $route_parameters = isset($variables['pager']['#route_parameters']) ? $variables['pager']['#route_parameters'] : [];
  185. /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
  186. $pager_manager = \Drupal::service('pager.manager');
  187. $pager = $pager_manager->getPager($element);
  188. // Nothing to do if there is no pager.
  189. if (!isset($pager)) {
  190. return;
  191. }
  192. $pager_max = $pager->getTotalPages();
  193. // Nothing to do if there is only one page.
  194. if ($pager_max <= 1) {
  195. return;
  196. }
  197. $tags = $variables['pager']['#tags'];
  198. // Calculate various markers within this pager piece:
  199. // Middle is used to "center" pages around the current page.
  200. $pager_middle = ceil($quantity / 2);
  201. $current_page = $pager->getCurrentPage();
  202. // The current pager is the page we are currently paged to.
  203. $pager_current = $current_page + 1;
  204. // The first pager is the first page listed by this pager piece (re quantity).
  205. $pager_first = $pager_current - $pager_middle + 1;
  206. // The last is the last page listed by this pager piece (re quantity).
  207. $pager_last = $pager_current + $quantity - $pager_middle;
  208. // End of marker calculations.
  209. // Prepare for generation loop.
  210. $i = $pager_first;
  211. if ($pager_last > $pager_max) {
  212. // Adjust "center" if at end of query.
  213. $i = $i + ($pager_max - $pager_last);
  214. $pager_last = $pager_max;
  215. }
  216. if ($i <= 0) {
  217. // Adjust "center" if at start of query.
  218. $pager_last = $pager_last + (1 - $i);
  219. $i = 1;
  220. }
  221. // End of generation loop preparation.
  222. // Create the "first" and "previous" links if we are not on the first page.
  223. if ($current_page > 0) {
  224. $items['first'] = [];
  225. $items['first']['attributes'] = new Attribute();
  226. $options = [
  227. 'query' => $pager_manager->getUpdatedParameters($parameters, $element, 0),
  228. ];
  229. $items['first']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
  230. if (isset($tags[0])) {
  231. $items['first']['text'] = $tags[0];
  232. }
  233. $items['previous'] = [];
  234. $items['previous']['attributes'] = new Attribute();
  235. $options = [
  236. 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current_page - 1),
  237. ];
  238. $items['previous']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
  239. if (isset($tags[1])) {
  240. $items['previous']['text'] = $tags[1];
  241. }
  242. }
  243. if ($i != $pager_max) {
  244. // Add an ellipsis if there are further previous pages.
  245. if ($i > 1) {
  246. $variables['ellipses']['previous'] = TRUE;
  247. }
  248. // Now generate the actual pager piece.
  249. for (; $i <= $pager_last && $i <= $pager_max; $i++) {
  250. $options = [
  251. 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $i - 1),
  252. ];
  253. $items['pages'][$i]['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
  254. $items['pages'][$i]['attributes'] = new Attribute();
  255. if ($i == $pager_current) {
  256. $variables['current'] = $i;
  257. }
  258. }
  259. // Add an ellipsis if there are further next pages.
  260. if ($i < $pager_max + 1) {
  261. $variables['ellipses']['next'] = TRUE;
  262. }
  263. }
  264. // Create the "next" and "last" links if we are not on the last page.
  265. if ($current_page < ($pager_max - 1)) {
  266. $items['next'] = [];
  267. $items['next']['attributes'] = new Attribute();
  268. $options = [
  269. 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current_page + 1),
  270. ];
  271. $items['next']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
  272. if (isset($tags[3])) {
  273. $items['next']['text'] = $tags[3];
  274. }
  275. $items['last'] = [];
  276. $items['last']['attributes'] = new Attribute();
  277. $options = [
  278. 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $pager_max - 1),
  279. ];
  280. $items['last']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString();
  281. if (isset($tags[4])) {
  282. $items['last']['text'] = $tags[4];
  283. }
  284. }
  285. $variables['items'] = $items;
  286. $variables['heading_id'] = Html::getUniqueId('pagination-heading');
  287. // The rendered link needs to play well with any other query parameter used
  288. // on the page, like exposed filters, so for the cacheability all query
  289. // parameters matter.
  290. $variables['#cache']['contexts'][] = 'url.query_args';
  291. }
  292. /**
  293. * Gets the URL query parameter array of a pager link.
  294. *
  295. * Adds to or adjusts the 'page' URL query parameter so that if you follow the
  296. * link, you'll get page $index for pager $element on the page.
  297. *
  298. * The 'page' URL query parameter is a comma-delimited string, where each value
  299. * is the target content page for the corresponding pager $element. For
  300. * instance, if we have 5 pagers on a single page, and we want to have a link
  301. * to a page that should display the 6th content page for the 3rd pager, and
  302. * the 1st content page for all the other pagers, then the URL query will look
  303. * like this: ?page=0,0,5,0,0 (page numbering starts at zero).
  304. *
  305. * @param array $query
  306. * An associative array of URL query parameters to add to.
  307. * @param int $element
  308. * An integer to distinguish between multiple pagers on one page.
  309. * @param int $index
  310. * The index of the target page, for the given element, in the pager array.
  311. *
  312. * @return array
  313. * The altered $query parameter array.
  314. *
  315. * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
  316. * \Drupal\Core\Pager\PagerManagerInterface::getUpdatedParameters() instead.
  317. *
  318. * @see https://www.drupal.org/node/2779457
  319. * @see \Drupal\Core\Pager\PagerManagerInterface::getUpdatedParameters()
  320. */
  321. function pager_query_add_page(array $query, $element, $index) {
  322. @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->getUpdatedParameters() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED);
  323. /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */
  324. $pager_manager = \Drupal::service('pager.manager');
  325. return $pager_manager->getUpdatedParameters($query, $element, $index);
  326. }