From 38741fcbd8e7367c6d6b8842fc093fdd5b4e3bf6 Mon Sep 17 00:00:00 2001 From: Francesco Placella <plach@183211.no-reply.drupal.org> Date: Sat, 12 Oct 2019 00:41:42 +0200 Subject: [PATCH] Issue #2044435 by mondrake, kim.pepper, Mile23, andypost, martin107, daffie, jibran, beejeebus, alexpott, larowlan, dawehner, catch, pwolanin: Convert pager.inc to a service --- core/core.services.yml | 8 +- core/globals.api.php | 38 ++++- core/includes/pager.inc | 122 +++++++------- .../Component/Utility/DeprecatedArray.php | 69 ++++++++ .../Core/Cache/Context/PagersCacheContext.php | 38 ++++- .../Database/Query/PagerSelectExtender.php | 6 +- .../Drupal/Core/Entity/Query/QueryBase.php | 4 +- core/lib/Drupal/Core/Pager/Pager.php | 121 ++++++++++++++ core/lib/Drupal/Core/Pager/PagerManager.php | 132 +++++++++++++++ .../Core/Pager/PagerManagerInterface.php | 155 ++++++++++++++++++ .../lib/Drupal/Core/Pager/PagerParameters.php | 72 ++++++++ .../Core/Pager/PagerParametersInterface.php | 59 +++++++ core/lib/Drupal/Core/Render/Element/Pager.php | 19 ++- .../modules/pager_test/pager_test.module | 7 +- .../src/Controller/PagerTestController.php | 28 +++- .../src/Kernel/Pager/PagerDeprecationTest.php | 43 +++++ .../taxonomy/src/Form/OverviewTerms.php | 28 +++- .../views/src/Plugin/views/pager/SqlBase.php | 109 +++++++----- core/modules/views/views.theme.inc | 19 ++- .../Core/Pager/PagerManagerTest.php | 95 +++++++++++ .../Core/Pager/RequestPagerTest.php | 49 ++++++ 21 files changed, 1082 insertions(+), 139 deletions(-) create mode 100644 core/lib/Drupal/Component/Utility/DeprecatedArray.php create mode 100644 core/lib/Drupal/Core/Pager/Pager.php create mode 100644 core/lib/Drupal/Core/Pager/PagerManager.php create mode 100644 core/lib/Drupal/Core/Pager/PagerManagerInterface.php create mode 100644 core/lib/Drupal/Core/Pager/PagerParameters.php create mode 100644 core/lib/Drupal/Core/Pager/PagerParametersInterface.php create mode 100644 core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php create mode 100644 core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php create mode 100644 core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php diff --git a/core/core.services.yml b/core/core.services.yml index 091289c2d265..b093d4bfac95 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -106,7 +106,7 @@ services: - { name: cache.context } cache_context.url.query_args.pagers: class: Drupal\Core\Cache\Context\PagersCacheContext - arguments: ['@request_stack'] + arguments: ['@pager.parameters'] tags: - { name: cache.context } @@ -1742,3 +1742,9 @@ services: arguments: ['@keyvalue.expirable', '@lock', '@request_stack', '%tempstore.expire%'] tags: - { name: backend_overridable } + pager.manager: + class: Drupal\Core\Pager\PagerManager + arguments: ['@pager.parameters'] + pager.parameters: + class: Drupal\Core\Pager\PagerParameters + arguments: ['@request_stack'] diff --git a/core/globals.api.php b/core/globals.api.php index 0f60ad558532..375c4fd8be2a 100644 --- a/core/globals.api.php +++ b/core/globals.api.php @@ -5,6 +5,8 @@ * These are the global variables that Drupal uses. */ +use Drupal\Component\Utility\DeprecatedArray; + /** * The insecure base URL of the Drupal installation. * @@ -81,33 +83,53 @@ * * The array index is the pager element index (0 by default). * - * @see pager_default_initialize() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Do not + * directly set or get values from this array. Use the pager.manager service + * instead. + * + * @see https://www.drupal.org/node/2779457 + * @see \Drupal\Core\Pager\PagerManagerInterface */ -global $pager_limits; +$GLOBALS['pager_limits'] = new DeprecatedArray([], 'Global variable $pager_limits is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457'); /** * Array of current page numbers for each pager. * * The array index is the pager element index (0 by default). * - * @see pager_default_initialize() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Do not + * directly set or get values from this array. Use the pager.manager service + * instead. + * + * @see https://www.drupal.org/node/2779457 + * @see \Drupal\Core\Pager\PagerManagerInterface */ -global $pager_page_array; +$GLOBALS['pager_page_array'] = new DeprecatedArray([], 'Global variable $pager_page_array is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457'); /** * Array of the total number of pages for each pager. * * The array index is the pager element index (0 by default). * - * @see pager_default_initialize() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Do not + * directly set or get values from this array. Use the pager.manager service + * instead. + * + * @see https://www.drupal.org/node/2779457 + * @see \Drupal\Core\Pager\PagerManagerInterface */ -global $pager_total; +$GLOBALS['pager_total'] = new DeprecatedArray([], 'Global variable $pager_total is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457'); /** * Array of the total number of items for each pager. * * The array index is the pager element index (0 by default). * - * @see pager_default_initialize() + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Do not + * directly set or get values from this array. Use the pager.manager service + * instead. + * + * @see https://www.drupal.org/node/2779457 + * @see \Drupal\Core\Pager\PagerManagerInterface */ -global $pager_total_items; +$GLOBALS['pager_total_items'] = new DeprecatedArray([], 'Global variable $pager_total_items is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457'); diff --git a/core/includes/pager.inc b/core/includes/pager.inc index 81daa34c6e53..c18507331aa2 100644 --- a/core/includes/pager.inc +++ b/core/includes/pager.inc @@ -7,7 +7,6 @@ use Drupal\Core\Template\Attribute; use Drupal\Core\Url; -use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\Html; /** @@ -26,15 +25,17 @@ * 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() + * @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 + * @see \Drupal\Core\Pager\PagerParametersInterface::findPage() */ function pager_find_page($element = 0) { - $page = \Drupal::request()->query->get('page', ''); - $page_array = explode(',', $page); - if (!isset($page_array[$element])) { - $page_array[$element] = 0; - } - return (int) $page_array[$element]; + @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); + /* @var $pager_parameters \Drupal\Core\Pager\PagerParametersInterface */ + $pager_parameters = \Drupal::service('pager.parameters'); + return $pager_parameters->findPage($element); } /** @@ -126,18 +127,19 @@ function pager_find_page($element = 0) { * 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. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Pager\PagerManagerInterface->defaultInitialize() instead. + * + * @see https://www.drupal.org/node/2779457 + * @see \Drupal\Core\Pager\PagerManagerInterface::createPager() */ 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]; + @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); + /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */ + $pager_manager = \Drupal::service('pager.manager'); + $pager = $pager_manager->createPager($total, $limit, $element); + return $pager->getCurrentPage(); } /** @@ -146,13 +148,18 @@ function pager_default_initialize($total, $limit, $element = 0) { * @return array * A URL query parameter array that consists of all components of the current * page request except for those pertaining to paging. + * + * @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 + * @see \Drupal\Core\Pager\PagerParametersInterface::getQueryParameters() */ function pager_get_query_parameters() { - $query = &drupal_static(__FUNCTION__); - if (!isset($query)) { - $query = UrlHelper::filterQueryParameters(\Drupal::request()->query->all(), ['page']); - } - return $query; + @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); + /* @var $pager_params \Drupal\Core\Pager\PagerParametersInterface */ + $pager_params = \Drupal::service('pager.parameters'); + return $pager_params->getQueryParameters(); } /** @@ -181,10 +188,21 @@ function template_preprocess_pager(&$variables) { $quantity = $variables['pager']['#quantity']; $route_name = $variables['pager']['#route_name']; $route_parameters = isset($variables['pager']['#route_parameters']) ? $variables['pager']['#route_parameters'] : []; - global $pager_page_array, $pager_total; + + /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */ + $pager_manager = \Drupal::service('pager.manager'); + + $pager = $pager_manager->getPager($element); + + // Nothing to do if there is no pager. + if (!isset($pager)) { + return; + } + + $pager_max = $pager->getTotalPages(); // Nothing to do if there is only one page. - if ($pager_total[$element] <= 1) { + if ($pager_max <= 1) { return; } @@ -193,14 +211,13 @@ function template_preprocess_pager(&$variables) { // 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). + $current_page = $pager->getCurrentPage(); + // The current pager is the page we are currently paged to. + $pager_current = $current_page + 1; + // The first pager 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). + // The 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. @@ -218,11 +235,11 @@ function template_preprocess_pager(&$variables) { // End of generation loop preparation. // Create the "first" and "previous" links if we are not on the first page. - if ($pager_page_array[$element] > 0) { + if ($current_page > 0) { $items['first'] = []; $items['first']['attributes'] = new Attribute(); $options = [ - 'query' => pager_query_add_page($parameters, $element, 0), + 'query' => $pager_manager->getUpdatedParameters($parameters, $element, 0), ]; $items['first']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); if (isset($tags[0])) { @@ -232,7 +249,7 @@ function template_preprocess_pager(&$variables) { $items['previous'] = []; $items['previous']['attributes'] = new Attribute(); $options = [ - 'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1), + 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current_page - 1), ]; $items['previous']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); if (isset($tags[1])) { @@ -248,7 +265,7 @@ function template_preprocess_pager(&$variables) { // Now generate the actual pager piece. for (; $i <= $pager_last && $i <= $pager_max; $i++) { $options = [ - 'query' => pager_query_add_page($parameters, $element, $i - 1), + 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $i - 1), ]; $items['pages'][$i]['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); $items['pages'][$i]['attributes'] = new Attribute(); @@ -263,11 +280,11 @@ function template_preprocess_pager(&$variables) { } // Create the "next" and "last" links if we are not on the last page. - if ($pager_page_array[$element] < ($pager_max - 1)) { + if ($current_page < ($pager_max - 1)) { $items['next'] = []; $items['next']['attributes'] = new Attribute(); $options = [ - 'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1), + 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current_page + 1), ]; $items['next']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); if (isset($tags[3])) { @@ -277,7 +294,7 @@ function template_preprocess_pager(&$variables) { $items['last'] = []; $items['last']['attributes'] = new Attribute(); $options = [ - 'query' => pager_query_add_page($parameters, $element, $pager_max - 1), + 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $pager_max - 1), ]; $items['last']['href'] = Url::fromRoute($route_name, $route_parameters, $options)->toString(); if (isset($tags[4])) { @@ -316,25 +333,16 @@ function template_preprocess_pager(&$variables) { * * @return array * The altered $query parameter array. + * + * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use + * \Drupal\Core\Pager\PagerManagerInterface->queryAddPage() instead. + * + * @see https://www.drupal.org/node/2779457 + * @see \Drupal\Core\Pager\PagerManagerInterface::getUpdatedParameters() */ function pager_query_add_page(array $query, $element, $index) { - global $pager_page_array; - - // Build the 'page' query parameter. This is built based on the current - // page of each pager element (or NULL if the pager is not set), with the - // exception of the requested page index for the current element. - $max_element = max(array_keys($pager_page_array)); - $element_pages = []; - for ($i = 0; $i <= $max_element; $i++) { - $element_pages[] = ($i == $element) ? $index : (isset($pager_page_array[$i]) ? $pager_page_array[$i] : NULL); - } - $query['page'] = implode(',', $element_pages); - - // Merge the query parameters passed to this function with the parameters - // from the current request. In case of collision, the parameters passed into - // this function take precedence. - if ($current_request_query = pager_get_query_parameters()) { - $query = array_merge($current_request_query, $query); - } - return $query; + @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->queryAddPage() instead. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED); + /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */ + $pager_manager = \Drupal::service('pager.manager'); + return $pager_manager->getUpdatedParameters($query, $element, $index); } diff --git a/core/lib/Drupal/Component/Utility/DeprecatedArray.php b/core/lib/Drupal/Component/Utility/DeprecatedArray.php new file mode 100644 index 000000000000..88d028723267 --- /dev/null +++ b/core/lib/Drupal/Component/Utility/DeprecatedArray.php @@ -0,0 +1,69 @@ +<?php + +namespace Drupal\Component\Utility; + +/** + * An array that triggers a deprecation warning when accessed. + */ +class DeprecatedArray implements \ArrayAccess { + + /** + * The array values. + * + * @var array + */ + protected $values = []; + + /** + * The deprecation message. + * + * @var string + */ + protected $message; + + /** + * DeprecatedArray constructor. + * + * @param array $values + * The array values. + * @param $message + * The deprecation message. + */ + public function __construct(array $values, $message) { + $this->values = $values; + $this->message = $message; + } + + /** + * {@inheritdoc} + */ + public function offsetExists($offset) { + @trigger_error($this->message, E_USER_DEPRECATED); + return isset($this->values[$offset]); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($offset) { + @trigger_error($this->message, E_USER_DEPRECATED); + return $this->values[$offset] ?? NULL; + } + + /** + * {@inheritdoc} + */ + public function offsetSet($offset, $value) { + @trigger_error($this->message, E_USER_DEPRECATED); + return $this->values[$offset] = $value; + } + + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) { + @trigger_error($this->message, E_USER_DEPRECATED); + unset($this->values[$offset]); + } + +} diff --git a/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php index cb34256dc7a1..ed462f24f15d 100644 --- a/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php +++ b/core/lib/Drupal/Core/Cache/Context/PagersCacheContext.php @@ -3,6 +3,8 @@ namespace Drupal\Core\Cache\Context; use Drupal\Core\Cache\CacheableMetadata; +use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait; +use Drupal\Core\Pager\PagerParametersInterface; /** * Defines a cache context for "per page in a pager" caching. @@ -11,7 +13,35 @@ * Calculated cache context ID: 'url.query_args.pagers:%pager_id', e.g. * 'url.query_args.pagers:1' (to vary by the pager with ID 1). */ -class PagersCacheContext extends RequestStackCacheContextBase implements CalculatedCacheContextInterface { +class PagersCacheContext implements CalculatedCacheContextInterface { + + use DeprecatedServicePropertyTrait; + + /** + * {@inheritdoc} + */ + protected $deprecatedProperties = ['requestStack' => 'request_stack']; + + /** + * The pager parameters. + * + * @var \Drupal\Core\Pager\PagerParametersInterface + */ + protected $pagerParams; + + /** + * Constructs a new PagersCacheContext object. + * + * @param \Drupal\Core\Pager\PagerParametersInterface $pager_params + * The pager parameters. + */ + public function __construct($pager_params) { + if (!($pager_params instanceof PagerParametersInterface)) { + @trigger_error('Calling ' . __METHOD__ . ' with a $pager_params argument that does not implement \Drupal\Core\Pager\PagerParametersInterface is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED); + $pager_params = \Drupal::service('pager.parameters'); + } + $this->pagerParams = $pager_params; + } /** * {@inheritdoc} @@ -23,16 +53,16 @@ public static function getLabel() { /** * {@inheritdoc} * - * @see pager_find_page() + * @see \Drupal\Core\Pager\PagerParametersInterface::findPage() */ public function getContext($pager_id = NULL) { // The value of the 'page' query argument contains the information that // controls *all* pagers. if ($pager_id === NULL) { - return $this->requestStack->getCurrentRequest()->query->get('page', ''); + return $this->pagerParams->getPagerParameter(); } - return $pager_id . '.' . pager_find_page($pager_id); + return $pager_id . '.' . $this->pagerParams->findPage($pager_id); } /** diff --git a/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php index 5207f58cb92e..f32b1c733cf8 100644 --- a/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php +++ b/core/lib/Drupal/Core/Database/Query/PagerSelectExtender.php @@ -73,8 +73,10 @@ public function execute() { $this->ensureElement(); $total_items = $this->getCountQuery()->execute()->fetchField(); - $current_page = pager_default_initialize($total_items, $this->limit, $this->element); - $this->range($current_page * $this->limit, $this->limit); + /** @var \Drupal\Core\Pager\PagerManagerInterface $pager_manager */ + $pager_manager = \Drupal::service('pager.manager'); + $pager = $pager_manager->createPager($total_items, $this->limit, $this->element); + $this->range($pager->getCurrentPage() * $this->limit, $this->limit); // Now that we've added our pager-based range instructions, run the query normally. return $this->query->execute(); diff --git a/core/lib/Drupal/Core/Entity/Query/QueryBase.php b/core/lib/Drupal/Core/Entity/Query/QueryBase.php index 57dd9025a33d..e7fbb82fad0a 100644 --- a/core/lib/Drupal/Core/Entity/Query/QueryBase.php +++ b/core/lib/Drupal/Core/Entity/Query/QueryBase.php @@ -310,11 +310,11 @@ public function pager($limit = 10, $element = NULL) { */ protected function initializePager() { if ($this->pager && !empty($this->pager['limit']) && !$this->count) { - $page = pager_find_page($this->pager['element']); + $page = \Drupal::service('pager.parameters')->findPage($this->pager['element']); $count_query = clone $this; $this->pager['total'] = $count_query->count()->execute(); $this->pager['start'] = $page * $this->pager['limit']; - pager_default_initialize($this->pager['total'], $this->pager['limit'], $this->pager['element']); + \Drupal::service('pager.manager')->createPager($this->pager['total'], $this->pager['limit'], $this->pager['element']); $this->range($this->pager['start'], $this->pager['limit']); } } diff --git a/core/lib/Drupal/Core/Pager/Pager.php b/core/lib/Drupal/Core/Pager/Pager.php new file mode 100644 index 000000000000..dedeed2657f9 --- /dev/null +++ b/core/lib/Drupal/Core/Pager/Pager.php @@ -0,0 +1,121 @@ +<?php + +namespace Drupal\Core\Pager; + +/** + * A value object that represents a pager. + */ +class Pager { + + /** + * The total number of items . + * + * @var int + */ + protected $totalItems; + + /** + * The total number of pages. + * + * @var int + */ + protected $totalPages; + + /** + * The current page of the pager. + * + * @var int + */ + protected $currentPage; + + /** + * The maximum number of items per page. + * + * @var int + */ + protected $limit; + + /** + * Pager constructor. + * + * @param int $totalItems + * The total number of items. + * @param int $limit + * The maximum number of items per page. + * @param int $currentPage + * The current page. + */ + public function __construct($totalItems, $limit, $currentPage = 0) { + $this->totalItems = $totalItems; + $this->limit = $limit; + $this->setTotalPages($totalItems, $limit); + $this->setCurrentPage($currentPage); + } + + /** + * Sets the current page to a valid value within range. + * + * If a page that does not correspond to the actual range of the result set + * was provided, this function will set the closest page actually within + * the result set. + * + * @param int $currentPage + * (optional) The current page. + */ + protected function setCurrentPage($currentPage = 0) { + $this->currentPage = max(0, min($currentPage, $this->getTotalPages() - 1)); + } + + /** + * Sets the total number of pages. + * + * @param int $totalItems + * The total number of items. + * @param int $limit + * The maximum number of items per page. + */ + protected function setTotalPages($totalItems, $limit) { + $this->totalPages = (int) ceil($totalItems / $limit); + } + + /** + * Gets the total number of items. + * + * @return int + * The total number of items. + */ + public function getTotalItems() { + return $this->totalItems; + } + + /** + * Gets the total number of pages. + * + * @return int + * The total number of pages. + */ + public function getTotalPages() { + return $this->totalPages; + } + + /** + * Gets the current page. + * + * @return int + * The current page. + */ + public function getCurrentPage() { + return $this->currentPage; + } + + /** + * Gets the maximum number of items per page. + * + * @return int + * The the maximum number of items per page. + */ + public function getLimit() { + return $this->limit; + } + +} diff --git a/core/lib/Drupal/Core/Pager/PagerManager.php b/core/lib/Drupal/Core/Pager/PagerManager.php new file mode 100644 index 000000000000..b97b813d7d81 --- /dev/null +++ b/core/lib/Drupal/Core/Pager/PagerManager.php @@ -0,0 +1,132 @@ +<?php + +namespace Drupal\Core\Pager; + +use Drupal\Component\Utility\DeprecatedArray; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; + +/** + * Provides a manager for pagers. + * + * Pagers are cached, and can be retrieved when rendering. + */ +class PagerManager implements PagerManagerInterface { + + use DependencySerializationTrait; + + /** + * The pager parameters. + * + * @var \Drupal\Core\Pager\PagerParametersInterface + */ + protected $pagerParams; + + /** + * An associative array of pagers. + * + * Implemented as an array consisting of: + * - key: the element id integer. + * - value: a \Drupal\Core\Pager\Pager. + * + * @var array + */ + protected $pagers; + + /** + * Construct a PagerManager object. + * + * @param \Drupal\Core\Pager\PagerParametersInterface $pager_params + * The pager parameters. + */ + public function __construct(PagerParametersInterface $pager_params) { + $this->pagerParams = $pager_params; + } + + /** + * {@inheritdoc} + */ + public function createPager($total, $limit, $element = 0) { + $currentPage = $this->pagerParams->findPage($element); + $pager = new Pager($total, $limit, $currentPage); + $this->setPager($pager, $element); + return $pager; + } + + /** + * {@inheritdoc} + */ + public function getPager($element = 0) { + return isset($this->pagers[$element]) ? $this->pagers[$element] : NULL; + } + + /** + * {@inheritdoc} + */ + public function getUpdatedParameters(array $query, $element, $index) { + // Build the 'page' query parameter. This is built based on the current + // page of each pager element (or NULL if the pager is not set), with the + // exception of the requested page index for the current element. + $element_pages = []; + $max = $this->getMaxPagerElementId(); + for ($i = 0; $i <= $max; $i++) { + $currentPage = ($pager = $this->getPager($i)) ? $pager->getCurrentPage() : NULL; + $element_pages[] = ($i == $element) ? $index : $currentPage; + } + $query['page'] = implode(',', $element_pages); + + // Merge the query parameters passed to this function with the parameters + // from the current request. In case of collision, the parameters passed + // into this function take precedence. + if ($current_query = $this->pagerParams->getQueryParameters()) { + $query = array_merge($current_query, $query); + } + return $query; + } + + /** + * Gets the extent of the pager page element IDs. + * + * @return int + * The maximum element ID available, -1 if there are no elements. + */ + protected function getMaxPagerElementId() { + return empty($this->pagers) ? -1 : max(array_keys($this->pagers)); + } + + /** + * Saves a pager to the static cache. + * + * @param \Drupal\Core\Pager\Pager $pager + * The pager. + * @param int $element + * The pager index. + */ + protected function setPager(Pager $pager, $element = 0) { + $this->pagers[$element] = $pager; + $this->updateGlobals(); + } + + /** + * Updates global variables with a pager data for backwards compatibility. + */ + protected function updateGlobals() { + $pager_total_items = []; + $pager_total = []; + $pager_page_array = []; + $pager_limits = []; + + /** @var $pager \Drupal\Core\Pager\Pager */ + foreach ($this->pagers as $pager_id => $pager) { + $pager_total_items[$pager_id] = $pager->getTotalItems(); + $pager_total[$pager_id] = $pager->getTotalPages(); + $pager_page_array[$pager_id] = $pager->getCurrentPage(); + $pager_limits[$pager_id] = $pager->getLimit(); + } + + $GLOBALS['pager_total_items'] = new DeprecatedArray($pager_total_items, 'Global variable $pager_total_items is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457'); + $GLOBALS['pager_total'] = new DeprecatedArray($pager_total, 'Global variable $pager_total is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457'); + $GLOBALS['pager_page_array'] = new DeprecatedArray($pager_page_array, 'Global variable $pager_page_array is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457'); + $GLOBALS['pager_limits'] = new DeprecatedArray($pager_limits, 'Global variable $pager_limits is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457'); + } + +} diff --git a/core/lib/Drupal/Core/Pager/PagerManagerInterface.php b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php new file mode 100644 index 000000000000..77ead948cc5b --- /dev/null +++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php @@ -0,0 +1,155 @@ +<?php + +namespace Drupal\Core\Pager; + +/** + * This is a service for pager information. + * + * The pager.manager service manages the pager information which will eventually + * be rendered into pager elements in the response. To gather information + * related to pager information in the request, use the pager.parameters + * service. + * + * Since there can be multiple pagers per requested page, each one is + * represented by an 'element' ID. This is an integer. It represents the index + * of the pager element within the 'page' query. The value of the element is an + * integer telling us the current page number for that pager. + * + * This class generally replaces the functions in core/includes/pager.inc. Those + * functions use globals to store data which they all use. Since we require + * backwards compatibility with this behavior, this class presents a public API + * for using pager information, which is implemented using the same globals as a + * 'backend.' + * + * @see \Drupal\Core\Pager\PagerParametersInterface + */ +interface PagerManagerInterface { + + /** + * Initializes a pager. + * + * This function sets up the necessary variables so that the render system + * will correctly process #type 'pager' render arrays to output pagers that + * correspond to the items being displayed. + * + * If the items being displayed result from a database query performed using + * 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 can extend the query object with the 'PagerSelectExtender' extender + * before executing it. For example: + * @code + * $query = db_select('some_table') + * ->extend('Drupal\Core\Database\Query\PagerSelectExtender'); + * @endcode + * + * However, if you are using a different method for generating the items to be + * paged through, then you should call this service in preparation. + * + * The following example shows how this service can be used in a controller + * 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 = \Drupal::config('mymodule.settings')->get('num_per_page'); + * $pager = \Drupal::service('pager.manager')->createPager($total, $num_per_page); + * $page = $pager->getCurrentPage(); + * + * // Next, retrieve the items for the current page and put them into a + * // render array. + * $offset = $num_per_page * $page; + * $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll(); + * $render = []; + * $render[] = [ + * '#theme' => 'mymodule_results', + * '#result' => $result, + * ]; + * + * // Finally, add the pager to the render array, and return. + * $render[] = ['#type' => 'pager']; + * return $render; + * @endcode + * + * A second example involves a controller 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 PagerManagerInterface->findPage() + * to calculate the desired offset before the search is invoked: + * @code + * + * // Perform the query, using the requested offset from + * // PagerManagerInterface::findPage(). 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. + * $pager_parameters = \Drupal::service('pager.parameters'); + * $page = $pager_parameters->findPage(); + * $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page'); + * $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_manager = \Drupal::service('pager.manager'); + * $pager_manager->createPager($result->total, $num_per_page); + * + * // Create a render array with the search results. + * $render = []; + * $render[] = [ + * '#theme' => 'search_results', + * '#results' => $result->data, + * '#type' => 'remote', + * ]; + * + * // Finally, add the pager to the render array, and return. + * $render[] = ['#type' => 'pager']; + * return $render; + * @endcode + * + * @param int $total + * The total number of items to be paged. + * @param int $limit + * The number of items the calling code will display per page. + * @param int $element + * (optional) An integer to distinguish between multiple pagers on one page. + * + * @return \Drupal\Core\Pager\Pager + * The pager. + */ + public function createPager($total, $limit, $element = 0); + + /** + * Gets a pager from the static cache. + * + * @param int $element + * The pager element index. + * + * @return \Drupal\Core\Pager\Pager|null + * The pager, or null if not found. + */ + public function getPager($element = 0); + + /** + * Gets the URL query parameter array of a pager link. + * + * Adds to or adjusts the 'page' URL query parameter so that if you follow the + * link, you'll get page $index for pager $element on the page. + * + * The 'page' URL query parameter is a comma-delimited string, where each + * value is the target content page for the corresponding pager $element. For + * instance, if we have 5 pagers on a single page, and we want to have a link + * to a page that should display the 6th content page for the 3rd pager, and + * the 1st content page for all the other pagers, then the URL query will look + * like this: ?page=0,0,5,0,0 (page numbering starts at zero). + * + * @param array $query + * An associative array of URL query parameters to add to. + * @param int $element + * An integer to distinguish between multiple pagers on one page. + * @param int $index + * The index of the target page, for the given element, in the pager array. + * + * @return array + * The altered $query parameter array. + */ + public function getUpdatedParameters(array $query, $element, $index); + +} diff --git a/core/lib/Drupal/Core/Pager/PagerParameters.php b/core/lib/Drupal/Core/Pager/PagerParameters.php new file mode 100644 index 000000000000..9a70c74d1a5b --- /dev/null +++ b/core/lib/Drupal/Core/Pager/PagerParameters.php @@ -0,0 +1,72 @@ +<?php + +namespace Drupal\Core\Pager; + +use Drupal\Component\Utility\UrlHelper; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Provides pager information contained within the current request. + * + * @see \Drupal\Core\Pager\PagerManagerInterface + */ +class PagerParameters implements PagerParametersInterface { + + /** + * The HTTP request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack + */ + protected $requestStack; + + /** + * Construct a PagerManager object. + * + * @param \Symfony\Component\HttpFoundation\RequestStack $stack + * The current HTTP request stack. + */ + public function __construct(RequestStack $stack) { + $this->requestStack = $stack; + } + + /** + * {@inheritdoc} + */ + public function getQueryParameters() { + $request = $this->requestStack->getCurrentRequest(); + if ($request) { + return UrlHelper::filterQueryParameters( + $request->query->all(), ['page'] + ); + } + return []; + } + + /** + * {@inheritdoc} + */ + public function findPage($pager_id = 0) { + $pages = $this->getPagerQuery(); + return (int) ($pages[$pager_id] ?? 0); + } + + /** + * {@inheritdoc} + */ + public function getPagerQuery() { + $query = $this->getPagerParameter(); + return !empty($query) ? explode(',', $query) : []; + } + + /** + * {@inheritdoc} + */ + public function getPagerParameter() { + $request = $this->requestStack->getCurrentRequest(); + if ($request) { + return $request->query->get('page', ''); + } + return ''; + } + +} diff --git a/core/lib/Drupal/Core/Pager/PagerParametersInterface.php b/core/lib/Drupal/Core/Pager/PagerParametersInterface.php new file mode 100644 index 000000000000..a88286e51278 --- /dev/null +++ b/core/lib/Drupal/Core/Pager/PagerParametersInterface.php @@ -0,0 +1,59 @@ +<?php + +namespace Drupal\Core\Pager; + +/** + * Interface describing pager information contained within the request. + * + * @see \Drupal\Core\Pager\PagerManagerInterface + */ +interface PagerParametersInterface { + + /** + * Gets all request URL query parameters that are unrelated to paging. + * + * @return array + * A URL query parameter array that consists of all components of the + * current page request except for those pertaining to paging. + */ + public function getQueryParameters(); + + /** + * Returns the current page being requested for display within a pager. + * + * @param int $pager_id + * (optional) An integer to distinguish between multiple pagers on one page. + * + * @return int + * The number of the current requested page, within the pager represented by + * $element. This is determined from the URL query parameter + * \Drupal::request()->query->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 user + * 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. + */ + public function findPage($pager_id = 0); + + /** + * Gets the request query parameter. + * + * @return int[] + * Array of pagers. Keys are integers which are the element ID. Values are + * the zero-based current page from the request. The first page is 0, the + * second page is 1, etc. + */ + public function getPagerQuery(); + + /** + * Gets the 'page' query parameter for the current request. + * + * @return string + * The 'page' query parameter for the current request. This is a + * comma-delimited string of pager element values. Defaults to empty string + * if the query does not have a 'page' parameter. + */ + public function getPagerParameter(); + +} diff --git a/core/lib/Drupal/Core/Render/Element/Pager.php b/core/lib/Drupal/Core/Render/Element/Pager.php index 6ba3dff75779..948fd7789f09 100644 --- a/core/lib/Drupal/Core/Render/Element/Pager.php +++ b/core/lib/Drupal/Core/Render/Element/Pager.php @@ -5,10 +5,10 @@ /** * Provides a render element for a pager. * - * The pager must be initialized with a call to pager_default_initialize() in - * order to render properly. When used with database queries, this is performed - * for you when you extend a select query with - * \Drupal\Core\Database\Query\PagerSelectExtender. + * The pager must be initialized with a call to + * \Drupal\Core\Pager\PagerManagerInterface::createPager() in order to render + * properly. When used with database queries, this is performed for you when you + * extend a select query with \Drupal\Core\Database\Query\PagerSelectExtender. * * Properties: * - #element: (optional, int) The pager ID, to distinguish between multiple @@ -68,11 +68,12 @@ public function getInfo() { */ public static function preRenderPager(array $pager) { // Note: the default pager theme process function - // template_preprocess_pager() also calls pager_query_add_page(), which - // maintains the existing query string. Therefore - // template_preprocess_pager() adds the 'url.query_args' cache context, - // which causes the more specific cache context below to be optimized away. - // In other themes, however, that may not be the case. + // template_preprocess_pager() also calls + // \Drupal\Core\Pager\PagerManagerInterface::queryAddPage(), which maintains + // the existing query string. Therefore template_preprocess_pager() adds the + // 'url.query_args' cache context, which causes the more specific cache + // context below to be optimized away. In other themes, however, that may + // not be the case. $pager['#cache']['contexts'][] = 'url.query_args.pagers:' . $pager['#element']; return $pager; } diff --git a/core/modules/system/tests/modules/pager_test/pager_test.module b/core/modules/system/tests/modules/pager_test/pager_test.module index 1961f2d51e17..fe09c30467be 100644 --- a/core/modules/system/tests/modules/pager_test/pager_test.module +++ b/core/modules/system/tests/modules/pager_test/pager_test.module @@ -9,11 +9,12 @@ * Implements hook_preprocess_HOOK(). */ function pager_test_preprocess_pager(&$variables) { - global $pager_total; - // Nothing to do if there is only one page. $element = $variables['pager']['#element']; - if ($pager_total[$element] <= 1) { + /** @var \Drupal\Core\Pager\PagerManagerInterface $pager_manager */ + $pager_manager = \Drupal::service('pager.manager'); + $pager = $pager_manager->getPager($element); + if ($pager->getTotalPages() <= 1) { return; } diff --git a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php index 5048e131bd47..2cdba6601203 100644 --- a/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php +++ b/core/modules/system/tests/modules/pager_test/src/Controller/PagerTestController.php @@ -5,13 +5,39 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Database\Database; use Drupal\Core\Database\Query\PagerSelectExtender; +use Drupal\Core\Pager\PagerParametersInterface; use Drupal\Core\Security\TrustedCallbackInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Controller routine for testing the pager. */ class PagerTestController extends ControllerBase implements TrustedCallbackInterface { + /** + * The pager request service. + * + * @var \Drupal\Core\Pager\PagerParametersInterface + */ + protected $pagerParams; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('pager.parameters')); + } + + /** + * Construct a new PagerTestController object. + * + * @param \Drupal\Core\Pager\PagerParametersInterface $pager_params + * The pager parameters. + */ + public function __construct(PagerParametersInterface $pager_params) { + $this->pagerParams = $pager_params; + } + /** * Builds a render array for a pageable test table. * @@ -59,7 +85,7 @@ public function queryParameters() { $build['pager_table_0'] = $this->buildTestTable(0, 5); // Counter of calls to the current pager. - $query_params = pager_get_query_parameters(); + $query_params = $this->pagerParams->getQueryParameters(); $pager_calls = isset($query_params['pager_calls']) ? ($query_params['pager_calls'] ? $query_params['pager_calls'] : 0) : 0; $build['l_pager_pager_0'] = ['#markup' => $this->t('Pager calls: @pager_calls', ['@pager_calls' => $pager_calls])]; diff --git a/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php b/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php new file mode 100644 index 000000000000..37ea40fc870d --- /dev/null +++ b/core/modules/system/tests/src/Kernel/Pager/PagerDeprecationTest.php @@ -0,0 +1,43 @@ +<?php + +namespace Drupal\Tests\system\Kernel\Pager; + +use Drupal\KernelTests\KernelTestBase; + +/** + * Ensure that deprecated pager functions trigger deprecation errors. + * + * @group Pager + * @group legacy + */ +class PagerDeprecationTest extends KernelTestBase { + + /** + * @expectedDeprecation pager_find_page 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 + */ + public function testFindPage() { + $this->assertInternalType('int', pager_find_page()); + } + + /** + * @expectedDeprecation pager_default_initialize 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 + */ + public function testDefaultInitialize() { + $this->assertInternalType('int', pager_default_initialize(1, 1)); + } + + /** + * @expectedDeprecation pager_get_query_parameters 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 + */ + public function testGetQueryParameters() { + $this->assertInternalType('array', pager_get_query_parameters()); + } + + /** + * @expectedDeprecation pager_query_add_page is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface->queryAddPage() instead. See https://www.drupal.org/node/2779457 + */ + public function testQueryAddPage() { + $this->assertArrayHasKey('page', pager_query_add_page([], 1, 1)); + } + +} diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php index 75fe42cd7677..502900b87d12 100644 --- a/core/modules/taxonomy/src/Form/OverviewTerms.php +++ b/core/modules/taxonomy/src/Form/OverviewTerms.php @@ -10,6 +10,7 @@ use Drupal\Core\Form\FormBase; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Pager\PagerManagerInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Url; use Drupal\taxonomy\VocabularyInterface; @@ -70,6 +71,13 @@ class OverviewTerms extends FormBase { */ protected $entityRepository; + /** + * The pager manager. + * + * @var \Drupal\Core\Pager\PagerManagerInterface + */ + protected $pagerManager; + /** * Constructs an OverviewTerms object. * @@ -81,8 +89,10 @@ class OverviewTerms extends FormBase { * The renderer service. * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository * The entity repository. + * @param \Drupal\Core\Pager\PagerManagerInterface|null $pager_manager + * The pager manager. */ - public function __construct(ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer = NULL, EntityRepositoryInterface $entity_repository = NULL) { + public function __construct(ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer = NULL, EntityRepositoryInterface $entity_repository = NULL, PagerManagerInterface $pager_manager = NULL) { $this->moduleHandler = $module_handler; $this->entityTypeManager = $entity_type_manager; $this->storageController = $entity_type_manager->getStorage('taxonomy_term'); @@ -93,6 +103,11 @@ public function __construct(ModuleHandlerInterface $module_handler, EntityTypeMa $entity_repository = \Drupal::service('entity.repository'); } $this->entityRepository = $entity_repository; + if (!$pager_manager) { + @trigger_error('Calling OverviewTerms::__construct() without the $pager_manager argument is deprecated in drupal:8.8.0 and the $pager_manager argument will be required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED); + $pager_manager = \Drupal::service('pager.manager'); + } + $this->pagerManager = $pager_manager; } /** @@ -103,7 +118,8 @@ public static function create(ContainerInterface $container) { $container->get('module_handler'), $container->get('entity_type.manager'), $container->get('renderer'), - $container->get('entity.repository') + $container->get('entity.repository'), + $container->get('pager.manager') ); } @@ -131,10 +147,6 @@ public function getFormId() { * The form structure. */ public function buildForm(array $form, FormStateInterface $form_state, VocabularyInterface $taxonomy_vocabulary = NULL) { - // @todo Remove global variables when https://www.drupal.org/node/2044435 is - // in. - global $pager_page_array, $pager_total, $pager_total_items; - $form_state->set(['taxonomy', 'vocabulary'], $taxonomy_vocabulary); $vocabulary_hierarchy = $this->storageController->getVocabularyHierarchyType($taxonomy_vocabulary->id()); $parent_fields = FALSE; @@ -227,9 +239,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular // Because we didn't use a pager query, set the necessary pager variables. $total_entries = $before_entries + $page_entries + $after_entries; - $pager_total_items[0] = $total_entries; - $pager_page_array[0] = $page; - $pager_total[0] = ceil($total_entries / $page_increment); + $this->pagerManager->createPager($total_entries, $page_increment); // If this form was already submitted once, it's probably hit a validation // error. Ensure the form is rebuilt in the same order as the user diff --git a/core/modules/views/src/Plugin/views/pager/SqlBase.php b/core/modules/views/src/Plugin/views/pager/SqlBase.php index bba7848c7868..29b3ba850f66 100644 --- a/core/modules/views/src/Plugin/views/pager/SqlBase.php +++ b/core/modules/views/src/Plugin/views/pager/SqlBase.php @@ -5,11 +5,77 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Pager\PagerManagerInterface; +use Drupal\Core\Pager\PagerParameters; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * A common base class for sql based pager. */ -abstract class SqlBase extends PagerPluginBase implements CacheableDependencyInterface { +abstract class SqlBase extends PagerPluginBase implements CacheableDependencyInterface, ContainerFactoryPluginInterface { + + /** + * The pager manager. + * + * @var \Drupal\Core\Pager\PagerManagerInterface + */ + protected $pagerManager; + + /** + * The request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack + */ + protected $requestStack; + + /** + * The pager parameters. + * + * @var \Drupal\Core\Pager\PagerParametersInterface + */ + protected $pagerParameters; + + /** + * Constructs a SqlBase object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Pager\PagerManagerInterface $pager_manager + * The pager manager. + * @param \Drupal\Core\Pager\PagerParameters|null $pager_parameters + * The pager parameters. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, PagerManagerInterface $pager_manager = NULL, PagerParameters $pager_parameters = NULL) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + if (!$pager_manager) { + @trigger_error('Calling ' . __METHOD__ . ' without the $pager_manager argument is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED); + $pager_manager = \Drupal::service('pager.manager'); + } + $this->pagerManager = $pager_manager; + if (!$pager_parameters) { + @trigger_error('Calling ' . __METHOD__ . ' without the $pager_parameters argument is deprecated in drupal:8.8.0 and is required in drupal:9.0.0. See https://www.drupal.org/node/2779457', E_USER_DEPRECATED); + $pager_parameters = \Drupal::service('pager.parameters'); + } + $this->pagerParameters = $pager_parameters; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('pager.manager'), + $container->get('pager.parameters') + ); + } protected function defineOptions() { $options = parent::defineOptions(); @@ -242,7 +308,7 @@ public function query() { * * @param $number * If provided, the page number will be set to this. If NOT provided, - * the page number will be set from the global page array. + * the page number will be set from the pager manager service. */ public function setCurrentPage($number = NULL) { if (isset($number)) { @@ -250,25 +316,7 @@ public function setCurrentPage($number = NULL) { return; } - // If the current page number was not specified, extract it from the global - // page array. - global $pager_page_array; - - if (empty($pager_page_array)) { - $pager_page_array = []; - } - - // Fill in missing values in the global page array, in case the global page - // array hasn't been initialized before. - $page = $this->view->getRequest()->query->get('page'); - $page = isset($page) ? explode(',', $page) : []; - - for ($i = 0; $i <= $this->options['id'] || $i < count($pager_page_array); $i++) { - $pager_page_array[$i] = empty($page[$i]) ? 0 : $page[$i]; - } - - // Don't allow the number to be less than zero. - $this->current_page = max(0, intval($pager_page_array[$this->options['id']])); + $this->current_page = max(0, $this->pagerParameters->findPage($this->options['id'])); } public function getPagerTotal() { @@ -297,24 +345,11 @@ public function updatePageInfo() { // Don't set pager settings for items per page = 0. $items_per_page = $this->getItemsPerPage(); if (!empty($items_per_page)) { - // Dump information about what we already know into the globals. - global $pager_page_array, $pager_total, $pager_total_items, $pager_limits; - // Set the limit. - $pager_limits[$this->options['id']] = $this->options['items_per_page']; - // Set the item count for the pager. - $pager_total_items[$this->options['id']] = $this->total_items; - // Calculate and set the count of available pages. - $pager_total[$this->options['id']] = $this->getPagerTotal(); - + $pager = $this->pagerManager->createPager($this->getTotalItems(), $this->options['items_per_page'], $this->options['id']); // See if the requested page was within range: - if ($this->current_page >= $pager_total[$this->options['id']]) { - // Pages are numbered from 0 so if there are 10 pages, the last page is 9. - $this->setCurrentPage($pager_total[$this->options['id']] - 1); + if ($this->getCurrentPage() >= $pager->getTotalPages()) { + $this->setCurrentPage($pager->getTotalPages() - 1); } - - // Put this number in to guarantee that we do not generate notices when the pager - // goes to look for it later. - $pager_page_array[$this->options['id']] = $this->current_page; } } diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc index f2ea74682912..509a146e0381 100644 --- a/core/modules/views/views.theme.inc +++ b/core/modules/views/views.theme.inc @@ -1000,18 +1000,25 @@ function template_preprocess_views_exposed_form(&$variables) { * exposed input. */ function template_preprocess_views_mini_pager(&$variables) { - global $pager_page_array, $pager_total; + /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */ + $pager_manager = \Drupal::service('pager.manager'); $tags = &$variables['tags']; $element = $variables['element']; $parameters = $variables['parameters']; + $pager = $pager_manager->getPager($element); + if (!$pager) { + return; + } + $current = $pager->getCurrentPage(); + $total = $pager->getTotalPages(); // Current is the page we are currently paged to. - $variables['items']['current'] = $pager_page_array[$element] + 1; + $variables['items']['current'] = $current + 1; - if ($pager_total[$element] > 1 && $pager_page_array[$element] > 0) { + if ($total > 1 && $current > 0) { $options = [ - 'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1), + 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current - 1), ]; $variables['items']['previous']['href'] = Url::fromRoute('<current>', [], $options)->toString(); if (isset($tags[1])) { @@ -1020,9 +1027,9 @@ function template_preprocess_views_mini_pager(&$variables) { $variables['items']['previous']['attributes'] = new Attribute(); } - if ($pager_page_array[$element] < ($pager_total[$element] - 1)) { + if ($current < ($total - 1)) { $options = [ - 'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1), + 'query' => $pager_manager->getUpdatedParameters($parameters, $element, $current + 1), ]; $variables['items']['next']['href'] = Url::fromRoute('<current>', [], $options)->toString(); if (isset($tags[3])) { diff --git a/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php b/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php new file mode 100644 index 000000000000..a03e759ad301 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php @@ -0,0 +1,95 @@ +<?php + +namespace Drupal\KernelTests\Core\Pager; + +use Drupal\KernelTests\KernelTestBase; +use Symfony\Component\HttpFoundation\Request; + +/** + * @group Pager + * + * @coversDefaultClass \Drupal\Core\Pager\PagerManager + */ +class PagerManagerTest extends KernelTestBase { + + /** + * @covers ::createPager + */ + public function testDefaultInitializeGlobals() { + $pager_globals = [ + 'pager_page_array', + 'pager_total_items', + 'pager_total', + 'pager_limits', + ]; + foreach ($pager_globals as $pager_global) { + $this->assertFalse(isset($GLOBALS[$pager_global])); + } + /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */ + $pager_manager = $this->container->get('pager.manager'); + + $pager_manager->createPager(5, 1); + + foreach ($pager_globals as $pager_global) { + $this->assertTrue(isset($GLOBALS[$pager_global])); + } + } + + /** + * @covers ::getUpdatedParameters + */ + public function testQueryAddPage() { + $element = 2; + $index = 5; + $test_parameters = [ + 'other' => 'arbitrary', + ]; + $request = Request::create('http://example.com', 'GET', $test_parameters); + + /* @var $request_stack \Symfony\Component\HttpFoundation\RequestStack */ + $request_stack = $this->container->get('request_stack'); + $request_stack->push($request); + + /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */ + $pager_manager = $this->container->get('pager.manager'); + + $pager_manager->createPager(30, 10, $element); + $query = $pager_manager->getUpdatedParameters($request->query->all(), $element, $index); + + $this->assertArrayHasKey('other', $query); + + $this->assertEquals(",,$index", $query['page']); + } + + /** + * @group legacy + * @expectedDeprecation Global variable $pager_page_array is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457 + * @expectedDeprecation Global variable $pager_total_items is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457 + * @expectedDeprecation Global variable $pager_total is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457 + * @expectedDeprecation Global variable $pager_limits is deprecated in drupal:8.8.0 and is removed in drupal:9.0.0. Use \Drupal\Core\Pager\PagerManagerInterface instead. See https://www.drupal.org/node/2779457 + */ + public function testGlobalsSafety() { + + /* @var $pager_manager \Drupal\Core\Pager\PagerManagerInterface */ + $pager_manager = $this->container->get('pager.manager'); + + $pager_manager->createPager(30, 10); + + $pager_globals = [ + 'pager_page_array', + 'pager_total_items', + 'pager_total', + 'pager_limits', + ]; + // Check globals were set. + foreach ($pager_globals as $pager_global) { + $this->assertTrue(isset($GLOBALS[$pager_global])); + } + + $this->assertEquals(0, $GLOBALS['pager_page_array'][0]); + $this->assertEquals(30, $GLOBALS['pager_total_items'][0]); + $this->assertEquals(3, $GLOBALS['pager_total'][0]); + $this->assertEquals(10, $GLOBALS['pager_limits'][0]); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php b/core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php new file mode 100644 index 000000000000..bb1bc9066b7d --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Pager/RequestPagerTest.php @@ -0,0 +1,49 @@ +<?php + +namespace Drupal\KernelTests\Core\Pager; + +use Drupal\KernelTests\KernelTestBase; +use Symfony\Component\HttpFoundation\Request; + +/** + * @group Pager + * + * @coversDefaultClass \Drupal\Core\Pager\PagerParameters + */ +class RequestPagerTest extends KernelTestBase { + + /** + * @covers ::findPage + */ + public function testFindPage() { + $request = Request::create('http://example.com', 'GET', ['page' => '0,10']); + + /* @var $request_stack \Symfony\Component\HttpFoundation\RequestStack */ + $request_stack = $this->container->get('request_stack'); + $request_stack->push($request); + + $pager_params = $this->container->get('pager.parameters'); + + $this->assertEquals(10, $pager_params->findPage(1)); + } + + /** + * @covers ::getQueryParameters + */ + public function testGetQueryParameters() { + $test_parameters = [ + 'other' => 'arbitrary', + ]; + $request = Request::create('http://example.com', 'GET', array_merge(['page' => '0,10'], $test_parameters)); + + /* @var $request_stack \Symfony\Component\HttpFoundation\RequestStack */ + $request_stack = $this->container->get('request_stack'); + $request_stack->push($request); + + $pager_params = $this->container->get('pager.parameters'); + + $this->assertEquals($test_parameters, $pager_params->getQueryParameters()); + $this->assertEquals(0, $pager_params->findPage()); + } + +} -- GitLab