Commit b60cf59f authored by Eric Chavaillaz's avatar Eric Chavaillaz Committed by Georg Ringer
Browse files

[FEATURE] Introduce sliding window pagination

This adds the NumberedPagination class from the
ext:numbered_pagination of Georg Ringer as
"SlidingWindowPagination" to the Core.

Resolves: #94625
Releases: master
Change-Id: If8631da405d95103c7a2f3a58a831835f08e45b9
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70069


Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Torben Hansen's avatarTorben Hansen <derhansen@gmail.com>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Torben Hansen's avatarTorben Hansen <derhansen@gmail.com>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
parent 67846838
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Pagination;
final class SlidingWindowPagination implements PaginationInterface
{
protected int $displayRangeStart = 0;
protected int $displayRangeEnd = 0;
protected bool $hasLessPages = false;
protected bool $hasMorePages = false;
protected int $maximumNumberOfLinks = 0;
protected PaginatorInterface $paginator;
public function __construct(PaginatorInterface $paginator, int $maximumNumberOfLinks = 0)
{
$this->paginator = $paginator;
if ($maximumNumberOfLinks > 0) {
$this->maximumNumberOfLinks = $maximumNumberOfLinks;
}
$this->calculateDisplayRange();
}
public function getPreviousPageNumber(): ?int
{
$previousPage = $this->paginator->getCurrentPageNumber() - 1;
if ($previousPage > $this->paginator->getNumberOfPages()) {
return null;
}
return $previousPage >= $this->getFirstPageNumber() ? $previousPage : null;
}
public function getNextPageNumber(): ?int
{
$nextPage = $this->paginator->getCurrentPageNumber() + 1;
return $nextPage <= $this->paginator->getNumberOfPages() ? $nextPage : null;
}
public function getFirstPageNumber(): int
{
return 1;
}
public function getLastPageNumber(): int
{
return $this->paginator->getNumberOfPages();
}
public function getStartRecordNumber(): int
{
if ($this->paginator->getCurrentPageNumber() > $this->paginator->getNumberOfPages()) {
return 0;
}
return $this->paginator->getKeyOfFirstPaginatedItem() + 1;
}
public function getEndRecordNumber(): int
{
if ($this->paginator->getCurrentPageNumber() > $this->paginator->getNumberOfPages()) {
return 0;
}
return $this->paginator->getKeyOfLastPaginatedItem() + 1;
}
public function getAllPageNumbers(): array
{
return range($this->displayRangeStart, $this->displayRangeEnd);
}
public function getDisplayRangeStart(): int
{
return $this->displayRangeStart;
}
public function getDisplayRangeEnd(): int
{
return $this->displayRangeEnd;
}
public function getHasLessPages(): bool
{
return $this->hasLessPages;
}
public function getHasMorePages(): bool
{
return $this->hasMorePages;
}
public function getMaximumNumberOfLinks(): int
{
return $this->maximumNumberOfLinks;
}
public function getPaginator(): PaginatorInterface
{
return $this->paginator;
}
protected function calculateDisplayRange(): void
{
$maximumNumberOfLinks = $this->maximumNumberOfLinks;
$numberOfPages = $this->paginator->getNumberOfPages();
if ($maximumNumberOfLinks > $numberOfPages) {
$maximumNumberOfLinks = $numberOfPages;
}
$currentPage = $this->paginator->getCurrentPageNumber();
$delta = floor($maximumNumberOfLinks / 2);
$this->displayRangeStart = (int)($currentPage - $delta);
$this->displayRangeEnd = (int)($currentPage + $delta - ($maximumNumberOfLinks % 2 === 0 ? 1 : 0));
if ($this->displayRangeStart < 1) {
$this->displayRangeEnd -= $this->displayRangeStart - 1;
}
if ($this->displayRangeEnd > $numberOfPages) {
$this->displayRangeStart -= $this->displayRangeEnd - $numberOfPages;
}
$this->displayRangeStart = (int)max($this->displayRangeStart, 1);
$this->displayRangeEnd = (int)min($this->displayRangeEnd, $numberOfPages);
$this->hasLessPages = $this->displayRangeStart > 2;
$this->hasMorePages = $this->displayRangeEnd + 1 < $this->paginator->getNumberOfPages();
}
}
.. include:: ../../Includes.txt
=====================================================
Feature: #94625 - Introduce sliding window pagination
=====================================================
See :issue:`94625`
Description
===========
Since TYPO3 10 a new `Pagination API <https://docs.typo3.org/m/typo3/reference-coreapi/master/en-us/ApiOverview/Pagination/Index.html>`__
is shipped, which supersedes the pagination widget controller, which had
been removed in TYPO3 11.
This patch provides an improved pagination which can be used to paginate array
items or query results from Extbase. The main advantage is that it reduces the
amount of pages shown.
**Example**: Imagine 1000 records and 20 items per page which would lead to
50 links. Using the `SlidingWindowPagination`, you will get something like
`< 1 2 ... 21 22 23 24 ... 100 >`.
Usage
=====
Just replace the usage of :php:`SimplePagination` with
:php:`\TYPO3\CMS\Core\Pagination\SlidingWindowPagination` and you are done.
Set the 2nd argument to the maximum number of links which should be rendered.
.. code-block:: php
$currentPage = $this->request->hasArgument('currentPage')
? (int)$this->request->getArgument('currentPage')
: 1;
$itemsPerPage = 10;
$maximumLinks = 15;
$paginator = new \TYPO3\CMS\Extbase\Pagination\QueryResultPaginator(
$allItems,
$currentPage,
$itemsPerPage
);
$pagination = new \TYPO3\CMS\Core\Pagination\SlidingWindowPagination(
$paginator,
$maximumLinks
);
$this->view->assign(
'pagination',
[
'pagination' => $pagination,
'paginator' => $paginator
]
);
Credits
=======
This patch is loosely based on the "`numbered_pagination <https://github.com/georgringer/numbered_pagination>`__"
extension by Georg Ringer.
Thanks to him.
.. index:: PHP-API, ext:core
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Tests\Unit\Pagination;
use TYPO3\CMS\Core\Pagination\ArrayPaginator;
use TYPO3\CMS\Core\Pagination\SlidingWindowPagination;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
class SlidingWindowPaginationTest extends UnitTestCase
{
protected $paginator = [];
/**
* @test
*/
public function checkSlidingWindowPaginationWithAPaginatorWithDefaultSettings(): void
{
$pagination = new SlidingWindowPagination($this->paginator, 5);
self::assertSame(1, $pagination->getStartRecordNumber());
self::assertSame(10, $pagination->getEndRecordNumber());
self::assertSame(1, $pagination->getFirstPageNumber());
self::assertSame(2, $pagination->getLastPageNumber());
self::assertNull($pagination->getPreviousPageNumber());
self::assertSame(2, $pagination->getNextPageNumber());
self::assertSame([1, 2], $pagination->getAllPageNumbers());
self::assertSame(1, $pagination->getDisplayRangeStart());
self::assertSame(2, $pagination->getDisplayRangeEnd());
self::assertFalse($pagination->getHasLessPages());
self::assertFalse($pagination->getHasMorePages());
self::assertSame(5, $pagination->getMaximumNumberOfLinks());
}
/**
* @test
*/
public function checkSlidingWindowPaginationWithAnIncreasedCurrentPageNumber(): void
{
$paginator = $this->paginator->withCurrentPageNumber(2);
$pagination = new SlidingWindowPagination($paginator, 5);
self::assertSame(11, $pagination->getStartRecordNumber());
self::assertSame(14, $pagination->getEndRecordNumber());
self::assertSame(1, $pagination->getFirstPageNumber());
self::assertSame(2, $pagination->getLastPageNumber());
self::assertSame(1, $pagination->getPreviousPageNumber());
self::assertNull($pagination->getNextPageNumber());
self::assertSame([1, 2], $pagination->getAllPageNumbers());
self::assertSame(1, $pagination->getDisplayRangeStart());
self::assertSame(2, $pagination->getDisplayRangeEnd());
self::assertFalse($pagination->getHasLessPages());
self::assertFalse($pagination->getHasMorePages());
self::assertSame(5, $pagination->getMaximumNumberOfLinks());
}
/**
* @test
*/
public function checkSlidingWindowPaginationWithAnIncreasedCurrentPageNumberAndItemsPerPage(): void
{
$paginator = $this->paginator
->withCurrentPageNumber(2)
->withItemsPerPage(3);
$pagination = new SlidingWindowPagination($paginator, 5);
self::assertSame(4, $pagination->getStartRecordNumber());
self::assertSame(6, $pagination->getEndRecordNumber());
self::assertSame(1, $pagination->getFirstPageNumber());
self::assertSame(5, $pagination->getLastPageNumber());
self::assertSame(1, $pagination->getPreviousPageNumber());
self::assertSame(3, $pagination->getNextPageNumber());
self::assertSame([1, 2, 3, 4, 5], $pagination->getAllPageNumbers());
self::assertSame(1, $pagination->getDisplayRangeStart());
self::assertSame(5, $pagination->getDisplayRangeEnd());
self::assertFalse($pagination->getHasLessPages());
self::assertFalse($pagination->getHasMorePages());
self::assertSame(5, $pagination->getMaximumNumberOfLinks());
}
/**
* @test
*/
public function checkPaginationWithAPaginatorThatOnlyHasOnePage(): void
{
$paginator = $this->paginator->withItemsPerPage(50);
$pagination = new SlidingWindowPagination($paginator, 5);
self::assertSame(1, $pagination->getStartRecordNumber());
self::assertSame(14, $pagination->getEndRecordNumber());
self::assertSame(1, $pagination->getFirstPageNumber());
self::assertSame(1, $pagination->getLastPageNumber());
self::assertNull($pagination->getPreviousPageNumber());
self::assertNull($pagination->getNextPageNumber());
self::assertSame([1], $pagination->getAllPageNumbers());
self::assertSame(1, $pagination->getDisplayRangeStart());
self::assertSame(1, $pagination->getDisplayRangeEnd());
self::assertFalse($pagination->getHasLessPages());
self::assertFalse($pagination->getHasMorePages());
self::assertSame(5, $pagination->getMaximumNumberOfLinks());
}
/**
* @test
*/
public function checkPaginatorWithOutOfBoundsCurrentPage(): void
{
$paginator = $this->paginator
->withItemsPerPage(5)
->withCurrentPageNumber(100);
$pagination = new SlidingWindowPagination($paginator, 5);
self::assertSame(11, $pagination->getStartRecordNumber());
self::assertSame(14, $pagination->getEndRecordNumber());
self::assertSame(3, $paginator->getCurrentPageNumber());
self::assertSame(1, $pagination->getFirstPageNumber());
self::assertSame(2, $pagination->getPreviousPageNumber());
self::assertNull($pagination->getNextPageNumber());
self::assertSame(3, $pagination->getLastPageNumber());
self::assertSame([1, 2, 3], $pagination->getAllPageNumbers());
self::assertSame(1, $pagination->getDisplayRangeStart());
self::assertSame(3, $pagination->getDisplayRangeEnd());
self::assertFalse($pagination->getHasLessPages());
self::assertFalse($pagination->getHasMorePages());
self::assertSame(5, $pagination->getMaximumNumberOfLinks());
}
protected function setUp(): void
{
parent::setUp();
$this->paginator = new ArrayPaginator(range(1, 14));
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment