TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
<?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;
abstract class AbstractPaginator implements PaginatorInterface
{
/**
* @var int
*/
protected $numberOfPages = 1;
/**
* @var int
*/
protected $keyOfFirstPaginatedItem = 0;
/**
* @var int
*/
protected $keyOfLastPaginatedItem = 0;
/**
* @var int
*/
private $currentPageNumber = 1;
/**
* @var int
*/
private $itemsPerPage = 10;
public function withItemsPerPage(int $itemsPerPage): PaginatorInterface
{
if ($itemsPerPage === $this->itemsPerPage) {
return $this;
}
$new = clone $this;
$new->setItemsPerPage($itemsPerPage);
$new->updateInternalState();
return $new;
}
public function withCurrentPageNumber(int $currentPageNumber): PaginatorInterface
{
if ($currentPageNumber === $this->currentPageNumber) {
return $this;
}
$new = clone $this;
$new->setCurrentPageNumber($currentPageNumber);
$new->updateInternalState();
return $new;
}
public function getNumberOfPages(): int
{
return $this->numberOfPages;
}
public function getCurrentPageNumber(): int
{
return $this->currentPageNumber;
}
public function getKeyOfFirstPaginatedItem(): int
{
return $this->keyOfFirstPaginatedItem;
}
public function getKeyOfLastPaginatedItem(): int
{
return $this->keyOfLastPaginatedItem;
}
/**
* Must update the paginated items, i.e. the subset of all items, limited and defined by
* the given amount of items per page and offset
*/
abstract protected function updatePaginatedItems(int $itemsPerPage, int $offset): void;
/**
* Must return the total amount of all unpaginated items
*/
abstract protected function getTotalAmountOfItems(): int;
/**
* Must return the amount of paginated items on the current page
*/
abstract protected function getAmountOfItemsOnCurrentPage(): int;
/**
* States whether there are items on the current page
*/
protected function hasItemsOnCurrentPage(): bool
{
return $this->getAmountOfItemsOnCurrentPage() > 0;
}
/**
* This method is the heart of the pagination. It updates all internal params and then calls the
* {@see updatePaginatedItems} method which must update the set of paginated items.
*/
protected function updateInternalState(): void
{
$offset = (int)($this->itemsPerPage * ($this->currentPageNumber - 1));
$totalAmountOfItems = $this->getTotalAmountOfItems();
/*
* If the total amount of items is zero, then the number of pages is mathematically zero as
* well. As that looks strange in the frontend, the number of pages is forced to be at least
* one.
*/
$this->numberOfPages = max(1, (int)ceil($totalAmountOfItems / $this->itemsPerPage));
/*
* To prevent empty results in case the given current page number exceeds the maximum number
* of pages, we set the current page number to the last page and update the internal state
* with this value again. Such situation should in the first place be prevented by not allowing
* those values to be passed, e.g. by using the "max" attribute in the view. However there are
* valid cases. For example when a user deletes a record while the pagination is already visible
* to another user with, until then, a valid "max" value. Passing invalid values unintentionally
* should therefore just silently be resolved.
*/
if ($this->currentPageNumber > $this->numberOfPages) {
$this->currentPageNumber = $this->numberOfPages;
$this->updateInternalState();
return;
}
$this->updatePaginatedItems($this->itemsPerPage, $offset);
if (!$this->hasItemsOnCurrentPage()) {
$this->keyOfFirstPaginatedItem = 0;
$this->keyOfLastPaginatedItem = 0;
return;
}
$indexOfLastPaginatedItem = min($offset + $this->itemsPerPage, $totalAmountOfItems);
$this->keyOfFirstPaginatedItem = $offset;
$this->keyOfLastPaginatedItem = $indexOfLastPaginatedItem - 1;
}
protected function setItemsPerPage(int $itemsPerPage): void
{
if ($itemsPerPage < 1) {
throw new \InvalidArgumentException(
'Argument $itemsPerPage must be greater than 0',
1573061766
);
}
$this->itemsPerPage = $itemsPerPage;
}
protected function setCurrentPageNumber(int $currentPageNumber): void
{
if ($currentPageNumber < 1) {
throw new \InvalidArgumentException(
'Argument $currentPageNumber must be greater than 0',
1573047338
);
}
$this->currentPageNumber = $currentPageNumber;
}
}
+66
View File
@@ -0,0 +1,66 @@
<?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 ArrayPaginator extends AbstractPaginator
{
/**
* @var array
*/
private $items;
/**
* @var array
*/
private $paginatedItems = [];
public function __construct(
array $items,
int $currentPageNumber = 1,
int $itemsPerPage = 10
) {
$this->items = $items;
$this->setCurrentPageNumber($currentPageNumber);
$this->setItemsPerPage($itemsPerPage);
$this->updateInternalState();
}
/**
* @return iterable|array
*/
public function getPaginatedItems(): iterable
{
return $this->paginatedItems;
}
protected function updatePaginatedItems(int $itemsPerPage, int $offset): void
{
$this->paginatedItems = array_slice($this->items, $offset, $itemsPerPage);
}
protected function getTotalAmountOfItems(): int
{
return count($this->items);
}
protected function getAmountOfItemsOnCurrentPage(): int
{
return count($this->paginatedItems);
}
}
@@ -0,0 +1,84 @@
<?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;
/**
* An interface that defines methods needed to implement a pagination
*
* A pagination is an object that takes a paginator and calculates variables
* to render a pagination for the paginated objects in the given paginator
*/
interface PaginationInterface
{
public function __construct(PaginatorInterface $paginator);
/**
* Must return the previous page number
*
* Is allowed to return null to indicate that there is no
* previous page, e.g. when being on the first page
*/
public function getPreviousPageNumber(): ?int;
/**
* Must return the next page number
*
* Is allowed to return null to indicate that there is no
* next page, e.g. when being on the last page
*/
public function getNextPageNumber(): ?int;
/**
* Must return the first page number, usually this will return 1
*/
public function getFirstPageNumber(): int;
/**
* Must return the last page number, usually this will return the total amount of pages
*/
public function getLastPageNumber(): int;
/**
* Must return the human-readable index of the first paginated item
*
* Example: given a set of 10 total items, 5 items per page and the current page being 2,
* the start record number is 6:
*
* Page 1: Records 1-5
* Page 2: Records 6-10
*/
public function getStartRecordNumber(): int;
/**
* Must return the human-readable index of the last paginated item
*
* Example: given a set of 10 total items, 5 items per page and the current page being 2,
* the end record number is 10.
*
* Page 1: Records 1-5
* Page 2: Records 6-10
*/
public function getEndRecordNumber(): int;
/**
* Must return a list of all page numbers.
*
* @return int[]
*/
public function getAllPageNumbers(): array;
}
+71
View File
@@ -0,0 +1,71 @@
<?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;
/**
* An interface that defines methods needed to implement a paginator, i.e. an object that handles
* a set of items and returns a sub set of items, given by a configuration.
*/
interface PaginatorInterface
{
/**
* Sets the amount of paginated items per page
*
* Must return a new instance of the Paginator with an updated internal state
*/
public function withItemsPerPage(int $itemsPerPage): PaginatorInterface;
/**
* Sets the current page to calculate paginated items for
*
* Must return a new instance of the Paginator with an updated internal state
*/
public function withCurrentPageNumber(int $currentPageNumber): PaginatorInterface;
/**
* Returns an iterable, sub set of the original set of items
*/
public function getPaginatedItems(): iterable;
/**
* Returns the total number of pages, given the total number of non paginated items and the
* items per page configuration
*/
public function getNumberOfPages(): int;
/**
* Returns the current page number
*/
public function getCurrentPageNumber(): int;
/**
* Returns the key of the first paginated item
*
* This is useful to display the exact range of
* items that are available via getPaginatedItems
*/
public function getKeyOfFirstPaginatedItem(): int;
/**
* Returns the key of the last paginated item
*
* This is useful to display the exact range of
* items that are available via getPaginatedItems
*/
public function getKeyOfLastPaginatedItem(): int;
}
@@ -0,0 +1,103 @@
<?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;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
/**
* Provides a paginator implementation to be used with {@see QueryBuilder} as
* data source.
*
* **Be aware** that this comes with a couple of things to be considered:
*
* * QueryBuilder is used in a generic way by this Paginator and does not take care of proper language overlay
* handling and cannot do that in a easy way and applying overlays on the result set can lead to weired item
* count jumps on pages in case some of them are removed. For example 5 items on page 1, 6 on page two albeit
* 10 items per page has been requested.
*
* * The paginator is completely in charge handling the pagination (offset/limit) and **does** not take
* existing constraints of the passed QueryBuilder into account to match the expectation shared across
* pagination handling throughout different frameworks and other Paginator implementation of TYPO3.
*/
final class QueryBuilderPaginator extends AbstractPaginator
{
private array $paginatedItems = [];
private ?int $totalItems = null;
public function __construct(
private readonly QueryBuilder $queryBuilder,
int $currentPageNumber = 1,
int $itemsPerPage = 10,
) {
$this->setCurrentPageNumber($currentPageNumber);
$this->setItemsPerPage($itemsPerPage);
$this->updateInternalState();
}
public function getPaginatedItems(): iterable
{
return $this->paginatedItems;
}
protected function updatePaginatedItems(int $itemsPerPage, int $offset): void
{
$paginatedQueryBuilder = clone $this->queryBuilder;
$this->paginatedItems = $paginatedQueryBuilder
->setMaxResults($itemsPerPage)
->setFirstResult($offset)
->executeQuery()
->fetchAllAssociative();
}
protected function getTotalAmountOfItems(): int
{
return $this->getTotalItems();
}
protected function getAmountOfItemsOnCurrentPage(): int
{
return count($this->paginatedItems);
}
private function getTotalItems(): int
{
if ($this->totalItems === null) {
$clonedQueryBuilder = clone $this->queryBuilder;
// Remove obsolete query parts. There is no need to enforce any ordering improving
// the performance and pagination constraints (LIMIT and OFFSET) are removed because
// otherwise we would not get the total items count.
$clonedQueryBuilder
->resetOrderBy()
->setMaxResults(null)
->setFirstResult(0);
$this->totalItems = (int)$clonedQueryBuilder->getConnection()->createQueryBuilder()
// @todo Upstream doctrine/dbal with() is not adopted in the decoration pattern and the reason to use
// typo3 internal implementation for the common table expression here. Replace it when upstream
// with() support has been integrated into the decoration chain.
->typo3_with('cte_count', $clonedQueryBuilder)
->count('*')
->from('cte_count')
->setParameters($clonedQueryBuilder->getParameters(), $clonedQueryBuilder->getParameterTypes())
->executeQuery()
->fetchOne();
}
return $this->totalItems;
}
}
+93
View File
@@ -0,0 +1,93 @@
<?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 SimplePagination implements PaginationInterface
{
private PaginatorInterface $paginator;
public function __construct(PaginatorInterface $paginator)
{
$this->paginator = $paginator;
}
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;
}
/**
* @return int[]
*/
public function getAllPageNumbers(): array
{
return range($this->getFirstPageNumber(), $this->getLastPageNumber());
}
public function getPaginator(): PaginatorInterface
{
return $this->paginator;
}
}
@@ -0,0 +1,149 @@
<?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
{
private int $displayRangeStart = 0;
private int $displayRangeEnd = 0;
private bool $hasLessPages = false;
private bool $hasMorePages = false;
private int $maximumNumberOfLinks = 0;
private 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;
}
private 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();
}
}