diff --git a/Classes/Controller/BrokenLinkListController.php b/Classes/Controller/BrokenLinkListController.php index 6b997d002..33dc0e2ec 100644 --- a/Classes/Controller/BrokenLinkListController.php +++ b/Classes/Controller/BrokenLinkListController.php @@ -10,6 +10,7 @@ use Sypets\Brofix\CheckLinks\LinkTargetResponse\LinkTargetResponse; use Sypets\Brofix\Configuration\Configuration; use Sypets\Brofix\Controller\Filter\BrokenLinkListFilter; +use Sypets\Brofix\Controller\Pagination\PaginateInfo; use Sypets\Brofix\LinkAnalyzer; use Sypets\Brofix\Linktype\LinktypeInterface; use Sypets\Brofix\Repository\BrokenLinkRepository; @@ -46,6 +47,8 @@ */ class BrokenLinkListController extends AbstractBrofixController { + protected const PAGINATE_ITEMS_PER_PAGE = 100; + protected const MODULE_NAME = 'web_brofix_broken_links'; protected const DEFAULT_ORDER_BY = 'page'; @@ -186,6 +189,8 @@ class BrokenLinkListController extends AbstractBrofixController protected bool $backendUserHasPermissionsForBrokenLinklist = false; protected bool $backendUserHasPermissionsForExcludes = false; + protected ?PaginateInfo $paginateInfo = null; + public function __construct( protected PagesRepository $pagesRepository, protected BrokenLinkRepository $brokenLinkRepository, @@ -440,10 +445,10 @@ protected function getSettingsFromQueryParameters(ServerRequestInterface $reques if ($paginationPage !== null) { $this->paginationCurrentPage = (int)$paginationPage; } else { - $this->paginationCurrentPage = 1; + $this->paginationCurrentPage = PaginateInfo::PAGE_NUMBER_START; } - if ($this->paginationCurrentPage < 1) { - $this->paginationCurrentPage = 1; + if ($this->paginationCurrentPage < PaginateInfo::PAGE_NUMBER_START) { + $this->paginationCurrentPage = PaginateInfo::PAGE_NUMBER_START; } } @@ -562,11 +567,9 @@ protected function initializeLinkAnalyzer(): void */ protected function initializeViewForBrokenLinks(): void { - $this->moduleTemplate->assign('depth', $this->depth); - - $items = []; - $totalCount = 0; - + /** Determines whether link list should be displayed, if no page selected and traverse by subpages: + * no + */ $shouldShow = true; $howToTraverse = $this->filter->getHowtotraverse(); if ($howToTraverse === BrokenLinkListFilter::HOW_TO_TRAVERSE_PAGES) { @@ -583,37 +586,16 @@ protected function initializeViewForBrokenLinks(): void } if ($shouldShow) { - /** - * @todo Currently, we fetch all and then paginate. We would like to optimize this to fetch only the broken - * links for one page. However, this would make it necessary to first fetch the total amount, which - * is similarly complicated to getBrokenLinks because of the array_chunking the pids. Possibly, set - * a hard limit to the number of pages and do away with array_chunking. - * There is already traverseMaxNumberOfPagesInBackend extension configuration which would have to - * effectively be set to a hard limit corresponding to (int)(BrokenLinkRepository::getMaxBindParameters() /2 - 4); - */ - $brokenLinks = $this->brokenLinkRepository->getBrokenLinks( - $this->pageList, - $this->linkTypes, - $this->configuration->getSearchFields(), - $this->filter, - $this->configuration, - self::ORDER_BY_VALUES[$this->orderBy] ?? [] - ); - if ($brokenLinks) { - $totalCount = count($brokenLinks); + // todo: do this in fetchPaginatedLinkList + //$totalCount = $this->fetchTotalCountOfBrokenLinks(); - $itemsPerPage = 100; - if (($this->paginationCurrentPage - 1) * $itemsPerPage >= $totalCount) { - $this->resetPagination(); - } - $paginator = GeneralUtility::makeInstance(ArrayPaginator::class, $brokenLinks, $this->paginationCurrentPage, $itemsPerPage); - $this->pagination = GeneralUtility::makeInstance(SimplePagination::class, $paginator); - // move end - foreach ($paginator->getPaginatedItems() as $row) { - $items[] = $this->renderTableRow($row['table_name'], $row); - } - $this->moduleTemplate->assign('listUri', $this->constructBackendUri()); - } + // todo: do this in fetchPaginatedLinkList + //$paginateInfo = new PaginateInfo($totalCount, $this->paginationCurrentPage); + + $items = $this->fetchPaginatedLinkList(); + + $howToTraverse = $this->filter->getHowtotraverse(); + // check if limit was reached if ($howToTraverse !== BrokenLinkListFilter::HOW_TO_TRAVERSE_ALL && $this->configuration->getTraverseMaxNumberOfPagesInBackend() && is_countable($this->pageList) @@ -629,13 +611,18 @@ protected function initializeViewForBrokenLinks(): void ); } } else { - $this->pagination = null; + // todo : show message to select page + $totalCount = 0; + $items = []; + $paginateInfo = null; } - $this->moduleTemplate->assign('totalCount', $totalCount); + + $this->moduleTemplate->assign('depth', $this->depth); + $this->moduleTemplate->assign('totalCount', $this->paginateInfo ? $this->paginateInfo->getNumberOfItems() : 0); $this->moduleTemplate->assign('filter', $this->filter); $this->moduleTemplate->assign('viewMode', $this->viewMode); - if ($this->id === 0) { - $this->createFlashMessagesForRootPage(); + if (!$shouldShow) { + $this->createFlashMessageToSelectPageInPagetree(); } elseif (empty($items)) { $this->createFlashMessagesForNoBrokenLinks(); } @@ -645,9 +632,10 @@ protected function initializeViewForBrokenLinks(): void $this->moduleTemplate->assign('linktypes', $linktypes); } - $this->moduleTemplate->assign('pagination', $this->pagination); + // todo replace with pageinateInfo + //$this->moduleTemplate->assign('pagination', $this->pagination); $this->moduleTemplate->assign('orderBy', $this->orderBy); - $this->moduleTemplate->assign('paginationPage', $this->paginationCurrentPage ?: 1); + $this->moduleTemplate->assign('paginationPage', $this->paginationCurrentPage ?: PaginateInfo::PAGE_NUMBER_START); // todo: only pass configuration $this->moduleTemplate->assign('showPageLayoutButton', $this->configuration->isShowPageLayoutButton()); @@ -665,6 +653,40 @@ protected function initializeViewForBrokenLinks(): void $this->moduleTemplate->assign('tableHeader', $this->getVariablesForTableHeader($sortActions)); } + protected function fetchPaginatedLinkList(): array + { + $brokenLinks = []; + + // fetch total count + $totalCount = $this->brokenLinkRepository->getBrokenLinksCount($this->pageList, + $this->linkTypes, + $this->configuration->getSearchFields(), + $this->filter, + $this->configuration, 'tx_brofix_tmp_page_ids'); + + if ($this->paginateInfo === null) { + $this->paginateInfo = new PaginateInfo($totalCount, $this->paginationCurrentPage); + } + + $dbalResult = $this->brokenLinkRepository->getBrokenLinks( + $this->pageList, + $this->linkTypes, + $this->configuration->getSearchFields(), + $this->filter, + $this->configuration, + self::ORDER_BY_VALUES[$this->orderBy] ?? [], + $this->paginateInfo,'tx_brofix_tmp_page_ids' + ); + + while ($row = $dbalResult->fetchAssociative()) { + // enrich content + $brokenLinks[] = $this->renderTableRow($row['table_name'], $row); + } + $this->moduleTemplate->assign('listUri', $this->constructBackendUri()); + + return $brokenLinks; + } + /** * Used when there are no broken links found. */ @@ -698,7 +720,10 @@ protected function createFlashMessagesForNoBrokenLinks(): void ); } - protected function createFlashMessagesForRootPage(): void + /** + * "Select a page in the page tree!" + */ + protected function createFlashMessageToSelectPageInPagetree(): void { $this->createFlashMessage($this->getLanguageService()->sL('LLL:EXT:brofix/Resources/Private/Language/Module/locallang.xlf:list.rootpage')); } @@ -1143,7 +1168,7 @@ protected function isShowRecordIds(): bool return true; } - protected function resetPagination(int $pageNr = 1): void + protected function resetPagination(int $pageNr = PaginateInfo::PAGE_NUMBER_START): void { $this->paginationCurrentPage = $pageNr; } diff --git a/Classes/Controller/Pagination/PaginateInfo.php b/Classes/Controller/Pagination/PaginateInfo.php new file mode 100644 index 000000000..a2dd740dc --- /dev/null +++ b/Classes/Controller/Pagination/PaginateInfo.php @@ -0,0 +1,91 @@ +numberOfItems; + } + + public function getCurrentPageNumber(): ?int + { + return $this->currentPage; + } + + public function getCurrentItemNumber(): ?int + { + $currentItemNumber = ($this->currentPage - self::PAGE_NUMBER_START) * $this->getItemsPerPage(); + return $currentItemNumber; + } + + public function getItemsPerPage(): int + { + return $this->itemsPerPage; + } + + public function getPreviousPageNumber(): ?int + { + if ($this->currentPage > 1) { + return $this->currentPage - 1; + } + return null; + } + + public function getNextPageNumber(): ?int + { + if ($this->currentPage * $this->itemsPerPage > $this->numberOfItems) { + return $this->currentPage + 1; + } + return null; + } + + public function getFirstPageNumber(): int + { + return 0; + } + + public function getLastPageNumber(): int + { + return $this->getNumberOfPages(); + } + + public function getNumberOfPages(): int + { + return (int) ($this->numberOfItems-1) / $this->itemsPerPage; + } + + public function getStartRecordNumber(): int + { + return 0; + } + + public function getEndRecordNumber(): int + { + return $this->numberOfItems-1; + } + + public function getAllPageNumbers(): array + { + // not implemented + return []; + } + + +} diff --git a/Classes/Repository/BrokenLinkRepository.php b/Classes/Repository/BrokenLinkRepository.php index 50bb56f19..42fb4232a 100644 --- a/Classes/Repository/BrokenLinkRepository.php +++ b/Classes/Repository/BrokenLinkRepository.php @@ -5,6 +5,7 @@ namespace Sypets\Brofix\Repository; use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\Result; use Hoa\File\Link\Link; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; @@ -12,9 +13,11 @@ use Sypets\Brofix\CheckLinks\LinkTargetResponse\LinkTargetResponse; use Sypets\Brofix\Configuration\Configuration; use Sypets\Brofix\Controller\Filter\BrokenLinkListFilter; +use Sypets\Brofix\Controller\Pagination\PaginateInfo; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Platform\PlatformInformation; +use TYPO3\CMS\Core\Database\Query\BulkInsertQuery; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -59,6 +62,9 @@ public function getMaxBindParameters(): int /** * Get broken links. * + * Will chunk array of pages if over max DB can handle as parameters in prepared statement. + * + * * Will only return broken links which the current user has edit access to. * * @param int[] $pageList Pages to check for broken links. If null, do not constrain @@ -70,17 +76,21 @@ public function getMaxBindParameters(): int * @return mixed[] * * @see LinkTargetResponse - * @todo Instead of array_chunking the pids, set a hard limit. $max is usually something around 30000. It does not make sense to use more for performance reasons. This - * way, it is possible to simplify the function slightly and we do not need to iterate. + * + * @deprecated This is deprecated. On the one hand, we do not strictly need to use prepared statements and + * quoting for security reasons, we get an array of ints. Additionally, this is a problem, if we want + * to fetch only a max number (e.g. if paginating). */ - public function getBrokenLinks( + public function getBrokenLinksWithArrayChunking( ?array $pageList, array $linkTypes, array $searchFields, BrokenLinkListFilter $filter, Configuration $configuration, - array $orderBy = [] - ): array { + array $orderBy = [], + ?PaginateInfo $paginateInfo = null + ): array + { $results = []; if ($pageList === []) { @@ -93,213 +103,375 @@ public function getBrokenLinks( */ $max = $this->getMaxNumberOfPagesForDbQuery(); foreach (array_chunk($pageList ?? [1], $max) as $pageIdsChunk) { - $queryBuilder = $this->generateQueryBuilder(self::TABLE); + $results = array_merge($results, + getBrokenLinks( + $pageIdsChunk, + $linkTypes, + $searchFields, + $filter, + $configuration, + $orderBy = [], + $paginateInfo) + ); + } + return $results; - if (!$GLOBALS['BE_USER']->isAdmin()) { - /** - * @var EditableRestriction $editableRestriction - */ - $editableRestriction = GeneralUtility::makeInstance(EditableRestriction::class, $searchFields, $queryBuilder); - $queryBuilder->getRestrictions() - ->add($editableRestriction); - } + } + + protected function createTemporaryTable(Connection $connection, string $tableName): Connection + { + $connection->executeStatement('CREATE TEMPORARY TABLE ' . $tableName . ' (id INT PRIMARY KEY)'); + return $connection; + } + + protected function removeTemporaryTable(Connection $connection, string $tableName): Connection + { + $connection->executeStatement('DROP TABLE ' . $tableName); + return $connection; + } + + public function insertPagesIntoTemporaryTable(Connection $connection, string $tableName, array $pageIds): void + { + $connection->beginTransaction(); + $this->createTemporaryTable($connection, $tableName); + + try { + // use bulk insert: https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Database/DoctrineDbal/Connection/Index.html#bulkinsert + $connection->bulkInsert( + $tableName, + [$pageIds], + [ + 'id' + ], + [ + Connection::PARAM_INT + ], + ); + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + throw $e; + } + } + + + + /** + * Get broken links. + * + * Will only return broken links which the current user has edit access to. + * + * @param int[] $pageList Pages to check for broken links. If null, do not constrain + * @param string[] $linkTypes Link types to validate + * @param array> $searchFields + * @param array> $orderBy + * @param BrokenLinkListFilter $filter + * @param array $orderBy + * @return mixed[] + * + * @see LinkTargetResponse + * @todo Instead of array_chunking the pids, set a hard limit. $max is usually something around 30000. It does not make sense to use more for performance reasons. This + * way, it is possible to simplify the function slightly and we do not need to iterate. + */ + public function getBrokenLinks( + ?array $pageList, + array $linkTypes, + array $searchFields, + BrokenLinkListFilter $filter, + Configuration $configuration, + array $orderBy = [], + ?PaginateInfo $paginateInfo = null, + string $tmpTable = null + ): Result + { + if ($pageList === []) { + return []; + } + $queryBuilder = $this->getBrokenLinksQueryBuilder($pageList,$linkTypes, $searchFields, + $filter, $configuration,$orderBy, $paginateInfo, $tmpTable); + if ($tmpTable !== null) { + $connection = $queryBuilder->getConnection(); + $this->insertPagesIntoTemporaryTable($connection, $tmpTable, $pageList); + // join with tmp table + $queryBuilder->join( + self::TABLE, + $tmpTable, + 'tmp_pages', + $queryBuilder->expr()->eq( + self::TABLE . '.record_pageid', + $queryBuilder->quoteIdentifier('tmp_pages.id') + ) + ); + } + + $queryBuilder->select(self::TABLE . '.*'); + $result = $queryBuilder->executeQuery(); + + if ($tmpTable !== null) { + $this->removeTemporaryTable($connection, $tmpTable); + } + return $result; + } + + public function getBrokenLinksCount( + ?array $pageList, + array $linkTypes, + array $searchFields, + BrokenLinkListFilter $filter, + Configuration $configuration, + string $tmpTable = null + ): int + { + if ($pageList === []) { + return 0; + } + $queryBuilder = $this->getBrokenLinksQueryBuilder($pageList,$linkTypes, $searchFields, + $filter, $configuration); + if ($tmpTable !== null) { + $connection = $queryBuilder->getConnection(); + $this->insertPagesIntoTemporaryTable($connection, $tmpTable, $pageList); + // join with tmp table + $queryBuilder->join( + self::TABLE, + $tmpTable, + 'tmp_pages', + $queryBuilder->expr()->eq( + self::TABLE . '.record_pageid', + $queryBuilder->quoteIdentifier('tmp_pages.id') + ) + ); + } + $queryBuilder->count(self::TABLE . '.uid'); + $result = $queryBuilder->executeQuery()->fetchOne(); + if ($tmpTable !== null) { + $this->removeTemporaryTable($connection, $tmpTable); + } + return $result; + } + + /** + * Build the QueryBuilder for fetching list of broken links or count of broken links. + * This QueryBuilder can be used with a count or select query. + * + * In order to use, you must add the select and also the executeQuery and fetch... + */ + public function getBrokenLinksQueryBuilder( + ?array $pageList, + array $linkTypes, + array $searchFields, + BrokenLinkListFilter $filter, + Configuration $configuration, + array $orderBy = [], + ?PaginateInfo $paginateInfo = null, + string $tmpTable = null + ): QueryBuilder + { + + $queryBuilder = $this->generateQueryBuilder(self::TABLE); + if (!$GLOBALS['BE_USER']->isAdmin()) { + /** + * @var EditableRestriction $editableRestriction + */ + $editableRestriction = GeneralUtility::makeInstance(EditableRestriction::class, $searchFields, $queryBuilder); + $queryBuilder->getRestrictions() + ->add($editableRestriction); + } + + $queryBuilder + //->select(self::TABLE . '.*') + ->from(self::TABLE) + ->join( + self::TABLE, + 'pages', + 'pages', + // we ise record_pageid now instead of record_pid + $queryBuilder->expr()->eq( + self::TABLE . '.record_pageid', + $queryBuilder->quoteIdentifier('pages.uid') + ) + ); + if ($tmpTable !== null) { + + } + if ($pageList && !$tmpTable) { + // now use only one field 'record_pageid' instead of record_uid for table pages and record_pid for not table pages $queryBuilder - ->select(self::TABLE . '.*') - ->from(self::TABLE) - ->join( - self::TABLE, - 'pages', - 'pages', - // we ise record_pageid now instead of record_pid - $queryBuilder->expr()->eq( + ->where( + $queryBuilder->expr()->in( self::TABLE . '.record_pageid', - $queryBuilder->quoteIdentifier('pages.uid') + $queryBuilder->createNamedParameter($pageList, Connection::PARAM_INT_ARRAY) ) ); - if ($pageList) { - // now use only one field 'record_pageid' instead of record_uid for table pages and record_pid for not table pages - $queryBuilder - ->where( - $queryBuilder->expr()->in( - self::TABLE . '.record_pageid', - $queryBuilder->createNamedParameter($pageIdsChunk, Connection::PARAM_INT_ARRAY) - ) - ); - } + } - if ($filter->getUidFilter() != '') { - $queryBuilder->andWhere( - $queryBuilder->expr()->eq(self::TABLE . '.record_uid', $queryBuilder->createNamedParameter($filter->getUidFilter(), Connection::PARAM_INT)) - ); - } + if ($filter->getUidFilter() != '') { + $queryBuilder->andWhere( + $queryBuilder->expr()->eq(self::TABLE . '.record_uid', $queryBuilder->createNamedParameter($filter->getUidFilter(), Connection::PARAM_INT)) + ); + } - if ($filter->getTypeFilter() != '') { - $parts = explode('.', $filter->getTypeFilter()); - $table_name = $parts[0]; - $field = $parts[1] ?? ''; + if ($filter->getTypeFilter() != '') { + $parts = explode('.', $filter->getTypeFilter()); + $table_name = $parts[0]; + $field = $parts[1] ?? ''; + $queryBuilder->andWhere( + $queryBuilder->expr()->eq( + self::TABLE . '.table_name', + $queryBuilder->createNamedParameter($table_name) + ) + ); + if ($field) { $queryBuilder->andWhere( $queryBuilder->expr()->eq( - self::TABLE . '.table_name', - $queryBuilder->createNamedParameter($table_name) + self::TABLE . '.field', + $queryBuilder->createNamedParameter($field) ) ); - if ($field) { - $queryBuilder->andWhere( - $queryBuilder->expr()->eq( - self::TABLE . '.field', - $queryBuilder->createNamedParameter($field) - ) - ); - } } + } - // errorFilter, might be 'custom:13' or 'custom:13|httpErrorCode:404' etc. Several combinations, separated - // by '|', each combination with : - - $errorFilter = $filter->getErrorFilter(); - $errorConstraintsOr = []; - if ($errorFilter !== '') { - $errorCombinations = explode('|', $errorFilter); - foreach ($errorCombinations as $errorCombination) { - $parts = explode(':', $errorCombination); - if (count($parts) === 2) { - $errorType = $parts[0]; - $errno = (int)$parts[1]; - $errorConstraintsOr[] = $queryBuilder->expr()->and( - $queryBuilder->expr()->eq( - self::TABLE . '.error_type', - $queryBuilder->createNamedParameter($errorType) - ), - $queryBuilder->expr()->eq( - self::TABLE . '.errno', - $queryBuilder->createNamedParameter($errno, Connection::PARAM_INT) - ) - ); - } elseif (count($parts) === 1) { - $errorType = $parts[0]; - $errorConstraintsOr[] = $queryBuilder->expr()->eq( + // errorFilter, might be 'custom:13' or 'custom:13|httpErrorCode:404' etc. Several combinations, separated + // by '|', each combination with : + + $errorFilter = $filter->getErrorFilter(); + $errorConstraintsOr = []; + if ($errorFilter !== '') { + $errorCombinations = explode('|', $errorFilter); + foreach ($errorCombinations as $errorCombination) { + $parts = explode(':', $errorCombination); + if (count($parts) === 2) { + $errorType = $parts[0]; + $errno = (int)$parts[1]; + $errorConstraintsOr[] = $queryBuilder->expr()->and( + $queryBuilder->expr()->eq( self::TABLE . '.error_type', $queryBuilder->createNamedParameter($errorType) - ); - } + ), + $queryBuilder->expr()->eq( + self::TABLE . '.errno', + $queryBuilder->createNamedParameter($errno, Connection::PARAM_INT) + ) + ); + } elseif (count($parts) === 1) { + $errorType = $parts[0]; + $errorConstraintsOr[] = $queryBuilder->expr()->eq( + self::TABLE . '.error_type', + $queryBuilder->createNamedParameter($errorType) + ); } } - if ($errorConstraintsOr) { - $queryBuilder->andWhere( - $queryBuilder->expr()->or(...$errorConstraintsOr) - ); - } + } + if ($errorConstraintsOr) { + $queryBuilder->andWhere( + $queryBuilder->expr()->or(...$errorConstraintsOr) + ); + } - $urlFilter = $filter->getUrlFilter(); - if ($urlFilter != '') { - switch ($filter->getUrlFilterMatch()) { - case 'partial': - $urlFilters = explode('|', $filter->getUrlFilter()); - $urlFilterConstraints = []; - foreach ($urlFilters as $urlFilter) { - $urlFilterConstraints[] = $queryBuilder->expr()->like( - self::TABLE . '.url', - $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($urlFilter) . '%') - ); - } - $queryBuilder->andWhere( - $queryBuilder->expr()->or(...$urlFilterConstraints) + $urlFilter = $filter->getUrlFilter(); + if ($urlFilter != '') { + switch ($filter->getUrlFilterMatch()) { + case 'partial': + $urlFilters = explode('|', $filter->getUrlFilter()); + $urlFilterConstraints = []; + foreach ($urlFilters as $urlFilter) { + $urlFilterConstraints[] = $queryBuilder->expr()->like( + self::TABLE . '.url', + $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($urlFilter) . '%') ); - break; - case 'exact': - $urlFilters = explode('|', $filter->getUrlFilter()); - $urlFilterConstraints = []; - foreach ($urlFilters as $urlFilter) { - $urlFilterConstraints[] = $queryBuilder->expr()->eq( - self::TABLE . '.url', - $queryBuilder->createNamedParameter($urlFilter) - ); - } - $queryBuilder->andWhere( - $queryBuilder->expr()->or(...$urlFilterConstraints) - ); - break; - case 'partialnot': - $urlFilters = explode('|', $filter->getUrlFilter()); - $urlFilterConstraints = []; - foreach ($urlFilters as $urlFilter) { - $urlFilterConstraints[] = $queryBuilder->expr()->notLike( - self::TABLE . '.url', - $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($urlFilter) . '%') - ); - } - $queryBuilder->andWhere( - $queryBuilder->expr()->and(...$urlFilterConstraints) + } + $queryBuilder->andWhere( + $queryBuilder->expr()->or(...$urlFilterConstraints) + ); + break; + case 'exact': + $urlFilters = explode('|', $filter->getUrlFilter()); + $urlFilterConstraints = []; + foreach ($urlFilters as $urlFilter) { + $urlFilterConstraints[] = $queryBuilder->expr()->eq( + self::TABLE . '.url', + $queryBuilder->createNamedParameter($urlFilter) ); - break; - case 'exactnot': - /* - $queryBuilder->andWhere( - $queryBuilder->expr()->neq( - self::TABLE . '.url', - $queryBuilder->createNamedParameter(mb_substr($urlFilter, 1)) - ) + } + $queryBuilder->andWhere( + $queryBuilder->expr()->or(...$urlFilterConstraints) + ); + break; + case 'partialnot': + $urlFilters = explode('|', $filter->getUrlFilter()); + $urlFilterConstraints = []; + foreach ($urlFilters as $urlFilter) { + $urlFilterConstraints[] = $queryBuilder->expr()->notLike( + self::TABLE . '.url', + $queryBuilder->createNamedParameter('%' . $queryBuilder->escapeLikeWildcards($urlFilter) . '%') ); - */ - $urlFilters = explode('|', $filter->getUrlFilter()); - $urlFilterConstraints = []; - foreach ($urlFilters as $urlFilter) { - $urlFilterConstraints[] = $queryBuilder->expr()->neq( - self::TABLE . '.url', - $queryBuilder->createNamedParameter($urlFilter) - ); - } - $queryBuilder->andWhere( - $queryBuilder->expr()->and(...$urlFilterConstraints) + } + $queryBuilder->andWhere( + $queryBuilder->expr()->and(...$urlFilterConstraints) + ); + break; + case 'exactnot': + $urlFilters = explode('|', $filter->getUrlFilter()); + $urlFilterConstraints = []; + foreach ($urlFilters as $urlFilter) { + $urlFilterConstraints[] = $queryBuilder->expr()->neq( + self::TABLE . '.url', + $queryBuilder->createNamedParameter($urlFilter) ); - break; - } - } - $linktypeFilter = $filter->getLinkTypeFilter() ?: 'all'; - if ($linktypeFilter != 'all') { - $queryBuilder->andWhere( - $queryBuilder->expr()->eq(self::TABLE . '.link_type', $queryBuilder->createNamedParameter($linktypeFilter)) - ); + } + $queryBuilder->andWhere( + $queryBuilder->expr()->and(...$urlFilterConstraints) + ); + break; } + } + $linktypeFilter = $filter->getLinkTypeFilter() ?: 'all'; + if ($linktypeFilter != 'all') { + $queryBuilder->andWhere( + $queryBuilder->expr()->eq(self::TABLE . '.link_type', $queryBuilder->createNamedParameter($linktypeFilter)) + ); + } - if ($configuration->isShowAllLinks()) { - $checkStatus = $filter->getCheckStatusFilter(); - } else { - // default is show error only - $checkStatus = LinkTargetResponse::RESULT_BROKEN; - } - if ($checkStatus !== LinkTargetResponse::RESULT_ALL) { - $queryBuilder->andWhere( - $queryBuilder->expr()->eq(self::TABLE . '.check_status', $queryBuilder->createNamedParameter($checkStatus, Connection::PARAM_INT)) - ); - } + if ($configuration->isShowAllLinks()) { + $checkStatus = $filter->getCheckStatusFilter(); + } else { + // default is show error only + $checkStatus = LinkTargetResponse::RESULT_BROKEN; + } + if ($checkStatus !== LinkTargetResponse::RESULT_ALL) { + $queryBuilder->andWhere( + $queryBuilder->expr()->eq(self::TABLE . '.check_status', $queryBuilder->createNamedParameter($checkStatus, Connection::PARAM_INT)) + ); + } - if ($orderBy !== []) { - $values = array_shift($orderBy); - if ($values && is_array($values) && count($values) === 2) { - $queryBuilder->orderBy($values[0], $values[1]); - foreach ($orderBy as $values) { - if (!is_array($values) || count($values) != 2) { - break; - } - $queryBuilder->addOrderBy(self::TABLE . '.' . $values[0], $values[1]); + if ($orderBy !== []) { + $values = array_shift($orderBy); + if ($values && is_array($values) && count($values) === 2) { + $queryBuilder->orderBy($values[0], $values[1]); + foreach ($orderBy as $values) { + if (!is_array($values) || count($values) != 2) { + break; } + $queryBuilder->addOrderBy(self::TABLE . '.' . $values[0], $values[1]); } } + } - if (!empty($linkTypes)) { - $queryBuilder->andWhere( - $queryBuilder->expr()->in( - self::TABLE . '.link_type', - $queryBuilder->createNamedParameter($linkTypes, Connection::PARAM_STR_ARRAY) - ) - ); - } + if (!empty($linkTypes)) { + $queryBuilder->andWhere( + $queryBuilder->expr()->in( + self::TABLE . '.link_type', + $queryBuilder->createNamedParameter($linkTypes, Connection::PARAM_STR_ARRAY) + ) + ); + } - $results = array_merge($results, $queryBuilder->executeQuery()->fetchAllAssociative()); + if ($paginateInfo) { + $queryBuilder->setFirstResult($paginateInfo->getCurrentItemNumber()) + ->setMaxResults($paginateInfo->getItemsPerPage()); } - return $results; + + return $queryBuilder; } /** @@ -351,7 +523,7 @@ public function getLinkCountForPage(int $pageId, bool $withEditableByUser = true } /** - * Fill a marker array with the number of links found in a list of pages + * Fill a marker array with the number of links found by link type in a list of pages * * @param array $pageIds page uids * @param array $linkTypes diff --git a/ext_tables.sql b/ext_tables.sql index 5f5b70db7..eb377bca4 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -30,6 +30,7 @@ CREATE TABLE tx_brofix_broken_links ( exclude_link_targets_pid int(11) DEFAULT '0' NOT NULL, KEY url_combined (url_hash,link_type,check_status), + KEY pageid (record_pageid), PRIMARY KEY (uid) );