diff --git a/core/lib/Drupal/Core/Pager/PagerManager.php b/core/lib/Drupal/Core/Pager/PagerManager.php index b3efb3143f..92014e93ec 100644 --- a/core/lib/Drupal/Core/Pager/PagerManager.php +++ b/core/lib/Drupal/Core/Pager/PagerManager.php @@ -10,23 +10,20 @@ * * This class does not deal with pager render elements. * - * Pager information can be sent through an HTTP request using the 'pager' query - * key. The value for this key tells us the current page for an arbitrary number - * of pagers. + * Since there can be multiple pagers per requested page, each one is + * represented by an 'element' ID. It represents the index of the pager element + * within the 'page' query. The pager element is an integer telling us the + * current page number for that pager. (Page numbering is zero-based.) * - * Since there can be multiple pagers, each one has an 'element' ID. This is an - * integer and is the location of the current page info within the 'pager' - * query. - * - * This class generally replaces the pager.inc functions. 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 + * 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.' * * @todo Re-implement the parts of this class that use globals in Drupal 9. */ -class PagerManager { +class PagerManager implements PagerManagerInterface { /** * The HTTP request stack. @@ -53,94 +50,7 @@ public function __construct(RequestStack $stack) { } /** - * Initializes a pager. - * - * This function sets up the necessary global 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 simply 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 function in preparation. - * - * The following example shows how this function 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'); - * $page = pager_default_initialize($total, $num_per_page); - * - * // 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 pager_find_page() to calculate the desired - * offset before the search is invoked: - * @code - * // Perform the query, using the requested offset from pager_find_page(). - * // This comes from a URL parameter, so here we are assuming that the URL - * // parameter corresponds to an actual page of results that will exist - * // within the set. - * $page = pager_find_page(); - * $num_per_page = \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_default_initialize($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 int - * The number of the current page, within the pager represented by $element. - * This is determined from the URL query parameter - * \Drupal::request()->query->get('page), or 0 by default. However, if a page - * that does not correspond to the actual range of the result set was - * requested, this function will return the closest page actually within the - * result set. + * {@inheritdoc} */ public function defaultInitialize($total, $limit, $element = 0) { $page = $this->findPage($element); @@ -154,20 +64,7 @@ public function defaultInitialize($total, $limit, $element = 0) { } /** - * Returns the current page being requested for display within a pager. - * - * @param int $element - * (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. + * {@inheritdoc} */ public function findPage($element = 0) { $page = $this->requestStack->getCurrentRequest()->query->get('page', ''); @@ -179,64 +76,45 @@ public function findPage($element = 0) { } /** - * Compose a URL query parameter array for pager links. - * - * @return array - * A URL query parameter array that consists of all components of the current - * page request except for those pertaining to paging. + * {@inheritdoc} */ public function getQueryParameters() { if (empty($this->queryParameters)) { - $this->queryParameters = UrlHelper::filterQueryParameters($this->requestStack->getCurrentRequest()->query->all(), ['page']); + $this->queryParameters = UrlHelper::filterQueryParameters( + $this->requestStack->getCurrentRequest()->query->all(), ['page'] + ); } return $this->queryParameters; } /** - * 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. + * {@inheritdoc} */ public function queryAddPage(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 = []; - for ($i = 0; $i <= $this->getMaxPagerPage(); $i++) { + $max = $this->getMaxPagerPage(); + for ($i = 0; $i <= $max; $i++) { $element_pages[] = ($i == $element) ? $index : $this->getPagerPage($i); } $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. + // from the current request. In case of collision, the parameters passed + // into this function take precedence. if ($current_request_query = $this->getQueryParameters()) { $query = array_merge($current_request_query, $query); } return $query; } + /** * Get the pager page for the given pager element. * * @param int $element + * The pager element. * * @return int|null * Page number if present, NULL if not present. Counting starts at 0, @@ -249,6 +127,9 @@ protected function getPagerPage($element) { /** * Gets the extent of the pager page element IDs. + * + * @return int + * The maximum element ID available. */ protected function getMaxPagerPage() { global $pager_page_array; @@ -259,6 +140,8 @@ protected function getMaxPagerPage() { * Get the total items available for the given pager element. * * @param int $element + * The pager element. + * * @return int * The total items available to page through for the given pager element. */ @@ -271,7 +154,10 @@ protected function getTotalItems($element) { * Get the total for the given pager element. * * @param int $element + * The pager element. + * * @return int + * Total for the element. */ protected function getTotal($element) { global $pager_total; @@ -283,7 +169,7 @@ protected function getTotal($element) { * * @param int $element * The pager element. - * @param type $total + * @param int $total * The total number value. */ protected function setTotalItems($element, $total) { @@ -295,7 +181,9 @@ protected function setTotalItems($element, $total) { * Set the total for the given pager element. * * @param int $element + * The pager element. * @param int $total + * The total. */ protected function setTotal($element, $total) { global $pager_total; @@ -306,7 +194,9 @@ protected function setTotal($element, $total) { * Set the current page for the given pager element. * * @param int $element + * The pager element. * @param int $page + * The new current page. */ protected function setPagerPage($element, $page) { global $pager_page_array; @@ -317,7 +207,7 @@ protected function setPagerPage($element, $page) { * Set the number of items displayed per page, for the given pager element. * * @param int $element - * The element. + * The pager element. * @param int $limit * The number of items that will be displayed per page, for the given * element. diff --git a/core/lib/Drupal/Core/Pager/PagerManagerInterface.php b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php new file mode 100644 index 0000000000..ff6d9e2233 --- /dev/null +++ b/core/lib/Drupal/Core/Pager/PagerManagerInterface.php @@ -0,0 +1,170 @@ +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 function in preparation. + * + * The following example shows how this function 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'); + * $page = \Drupal::service('pager.manager')->defaultInitialize($total, $num_per_page); + * + * // 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 pager_find_page() to calculate the + * desired offset before the search is invoked: + * @code + * $pager_manager = \Drupal::service('pager.manager'); + * // Perform the query, using the requested offset from pager_find_page(). + * // This comes from a URL parameter, so here we are assuming that the URL + * // parameter corresponds to an actual page of results that will exist + * // within the set. + * $page = $pager_manager->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->defaultInitialize($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 int + * The number of the current page, within the pager represented by $element. + * This is determined from the URL query parameter + * \Drupal::request()->query->get('page), or 0 by default. However, if a + * page that does not correspond to the actual range of the result set was + * requested, this function will return the closest page actually within the + * result set. + */ + public function defaultInitialize($total, $limit, $element = 0); + + /** + * Returns the current page being requested for display within a pager. + * + * @param int $element + * (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($element = 0); + + /** + * Compose a URL query parameter array for pager links. + * + * @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(); + + /** + * 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 queryAddPage(array $query, $element, $index); + +} 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 0000000000..f22f969446 --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Pager/PagerManagerTest.php @@ -0,0 +1,96 @@ +assertFalse(isset($GLOBALS[$pager_global])); + } + + $pm = new PagerManager($this->container->get('request_stack')); + $pm->defaultInitialize(5, 1); + + foreach ($pager_globals as $pager_global) { + $this->assertTrue(isset($GLOBALS[$pager_global])); + } + } + + /** + * @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); + + $pm = new PagerManager($request_stack); + + $this->assertEquals(10, $pm->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); + + $pm = new PagerManager($request_stack); + + $this->assertEquals($test_parameters, $pm->getQueryParameters()); + } + + /** + * @covers ::queryAddPage + */ + 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); + + $pm = new PagerManager($request_stack); + $pm->defaultInitialize(30, 10, $element); + + $query = $pm->queryAddPage($request->query->all(), $element, $index); + + $this->assertArrayHasKey('other', $query); + + $this->assertEquals(",,$index", $query['page']); + } + +}