Commit eff4fd02 authored by Sybille Peters's avatar Sybille Peters 🙋 Committed by Benni Mack
Browse files

[TASK] Move database queries of linkvalidator to separate class

Move the database queries of EXT:linkvalidator into a new class
PagesRepository to make the functionality more flexible and easier to
test.

Resolves: #93006
Releases: master
Change-Id: Id068d0ef876005db11aceffb11ad968a08f377cf
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/67040


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 5e5725b5
......@@ -19,7 +19,6 @@ use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Html\HtmlParser;
......@@ -462,102 +461,6 @@ class LinkAnalyzer
return $this->brokenLinkRepository->getNumberOfBrokenLinksForRecordsOnPages($this->pids, $this->searchFields);
}
/**
* Calls TYPO3\CMS\Backend\FrontendBackendUserAuthentication::extGetTreeList.
* Although this duplicates the function TYPO3\CMS\Backend\FrontendBackendUserAuthentication::extGetTreeList
* this is necessary to create the object that is used recursively by the original function.
*
* Generates a list of page uids from $id. List does not include $id itself.
* The only pages excluded from the list are deleted pages.
*
* @param int $id Start page id
* @param int $depth Depth to traverse down the page tree.
* @param int $begin is an optional integer that determines at which level to start. use "0" from outside usage
* @param string $permsClause Perms clause
* @param bool $considerHidden Whether to consider hidden pages or not
* @return string Returns the list with a comma in the end (if any pages selected!)
*/
public function extGetTreeList($id, $depth, $begin, $permsClause, $considerHidden = false)
{
$depth = (int)$depth;
$begin = (int)$begin;
$id = (int)$id;
$theList = '';
if ($depth === 0) {
return $theList;
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$result = $queryBuilder
->select('uid', 'title', 'hidden', 'extendToSubpages')
->from('pages')
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
),
QueryHelper::stripLogicalOperatorPrefix($permsClause)
)
->execute();
while ($row = $result->fetch()) {
if ($begin <= 0 && ($row['hidden'] == 0 || $considerHidden)) {
$theList .= $row['uid'] . ',';
}
if ($depth > 1 && (!($row['hidden'] == 1 && $row['extendToSubpages'] == 1) || $considerHidden)) {
$theList .= $this->extGetTreeList(
$row['uid'],
$depth - 1,
$begin - 1,
$permsClause,
$considerHidden
);
}
}
return $theList;
}
/**
* Check if rootline contains a hidden page
*
* @param array $pageInfo Array with uid, title, hidden, extendToSubpages from pages table
* @return bool TRUE if rootline contains a hidden page, FALSE if not
*/
public function getRootLineIsHidden(array $pageInfo)
{
if ($pageInfo['pid'] === 0) {
return false;
}
if ($pageInfo['extendToSubpages'] == 1 && $pageInfo['hidden'] == 1) {
return true;
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()->removeAll();
$row = $queryBuilder
->select('uid', 'title', 'hidden', 'extendToSubpages')
->from('pages')
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($pageInfo['pid'], \PDO::PARAM_INT)
)
)
->execute()
->fetch();
if ($row !== false) {
return $this->getRootLineIsHidden($row);
}
return false;
}
/**
* @return LanguageService
*/
......
......@@ -19,9 +19,6 @@ use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
......@@ -34,6 +31,7 @@ use TYPO3\CMS\Info\Controller\InfoModuleController;
use TYPO3\CMS\Linkvalidator\LinkAnalyzer;
use TYPO3\CMS\Linkvalidator\Linktype\LinktypeInterface;
use TYPO3\CMS\Linkvalidator\Repository\BrokenLinkRepository;
use TYPO3\CMS\Linkvalidator\Repository\PagesRepository;
/**
* Module 'Link validator' as sub module of Web -> Info
......@@ -134,6 +132,11 @@ class LinkValidatorReport
*/
protected $brokenLinkRepository;
/**
* @var PagesRepository
*/
protected $pagesRepository;
/**
* @var ModuleTemplate
*/
......@@ -144,9 +147,11 @@ class LinkValidatorReport
*/
protected $view;
public function __construct()
public function __construct(PagesRepository $pagesRepository = null, BrokenLinkRepository $brokenLinkRepository = null)
{
$this->brokenLinkRepository = GeneralUtility::makeInstance(BrokenLinkRepository::class);
$this->pagesRepository = $pagesRepository ?? GeneralUtility::makeInstance(PagesRepository::class);
$this->brokenLinkRepository = $brokenLinkRepository ??
GeneralUtility::makeInstance(BrokenLinkRepository::class);
}
/**
......@@ -366,7 +371,7 @@ class LinkValidatorReport
}
}
$rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($this->pObj->pageinfo);
$rootLineHidden = $this->pagesRepository->doesRootLineContainHiddenPages($this->pObj->pageinfo);
if (!$rootLineHidden || $this->modTS['checkhidden'] == 1) {
$this->linkAnalyzer->init(
$this->searchFields,
......@@ -406,7 +411,7 @@ class LinkValidatorReport
$linkTypes = array_keys($this->checkOpt['report'], '1');
}
$items = [];
$rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($this->pObj->pageinfo);
$rootLineHidden = $this->pagesRepository->doesRootLineContainHiddenPages($this->pObj->pageinfo);
if (!$rootLineHidden || (bool)$this->modTS['checkhidden'] && !empty($linkTypes)) {
$brokenLinks = $this->brokenLinkRepository->getAllBrokenLinksForPages(
$this->getPageList(),
......@@ -432,18 +437,22 @@ class LinkValidatorReport
*/
protected function getPageList(): array
{
$permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
$pageList = $this->linkAnalyzer->extGetTreeList(
$checkForHiddenPages = (bool)$this->modTS['checkhidden'];
$permsClause = (string)$this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
$pageList = $this->pagesRepository->getAllSubpagesForPage(
$this->id,
$this->searchLevel['report'],
0,
(int)$this->searchLevel['report'],
$permsClause,
$this->modTS['checkhidden']
$checkForHiddenPages
);
// Always add the current page, because we are just displaying the results
$pageList .= $this->id;
$pageList = $this->addPageTranslationsToPageList($pageList, $permsClause);
return GeneralUtility::intExplode(',', $pageList, true);
$pageList[] = $this->id;
$pageTranslations = $this->pagesRepository->getTranslationForPage(
$this->id,
$permsClause,
$checkForHiddenPages
);
return array_merge($pageList, $pageTranslations);
}
/**
......@@ -616,32 +625,4 @@ class LinkValidatorReport
{
return $GLOBALS['BE_USER'];
}
protected function addPageTranslationsToPageList(string $theList, string $permsClause): string
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$result = $queryBuilder
->select('uid', 'title', 'hidden')
->from('pages')
->where(
$queryBuilder->expr()->eq(
'l10n_parent',
$queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
),
QueryHelper::stripLogicalOperatorPrefix($permsClause)
)
->execute();
while ($row = $result->fetch()) {
if ($row['hidden'] === 0 || $this->modTS['checkhidden']) {
$theList .= ',' . $row['uid'];
}
}
return $theList;
}
}
<?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\Linkvalidator\Repository;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Class for pages database queries.
*
* @internal not part of the TYPO3 Core API.
*/
class PagesRepository
{
protected const TABLE = 'pages';
/**
* Check if rootline contains a hidden page
*
* @param array $pageInfo Array with uid, title, hidden, extendToSubpages from pages table
* @return bool TRUE if rootline contains a hidden page, FALSE if not
*/
public function doesRootLineContainHiddenPages(array $pageInfo): bool
{
$pid = (int)($pageInfo['pid']);
if ($pid === 0) {
return false;
}
$isHidden = (bool)($pageInfo['hidden']);
$extendToSubpages = (bool)($pageInfo['extendToSubpages']);
if ($extendToSubpages === true && $isHidden === true) {
return true;
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable(self::TABLE);
$queryBuilder->getRestrictions()->removeAll();
$row = $queryBuilder
->select('uid', 'title', 'hidden', 'extendToSubpages')
->from(self::TABLE)
->where(
$queryBuilder->expr()->eq(
'uid',
$queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
)
)
->execute()
->fetch();
if ($row !== false) {
return $this->doesRootLineContainHiddenPages($row);
}
return false;
}
/**
* Generates a list of page uids from $id. List does not include $id itself.
* The only pages excluded from the list are deleted pages.
*
* @param int $id Start page id
* @param int $depth Depth to traverse down the page tree.
* @param string $permsClause Perms clause
* @param bool $considerHidden Whether to consider hidden pages or not
* @return int[] Returns the list of subpages (if any pages selected!)
*/
public function getAllSubpagesForPage(
int $id,
int $depth,
string $permsClause,
bool $considerHidden = false
): array {
$subPageIds = [];
if ($depth === 0) {
return $subPageIds;
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable(self::TABLE);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$result = $queryBuilder
->select('uid', 'title', 'hidden', 'extendToSubpages')
->from(self::TABLE)
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
),
QueryHelper::stripLogicalOperatorPrefix($permsClause)
)
->execute();
while ($row = $result->fetch()) {
$subpageId = (int)$row['uid'];
$isHidden = (bool)$row['hidden'];
if (!$isHidden || $considerHidden) {
$subPageIds[] = $subpageId;
}
if ($depth > 1 && (!($isHidden && $row['extendToSubpages'] == 1) || $considerHidden)) {
$subPageIds = array_merge($subPageIds, $this->getAllSubpagesForPage(
$subpageId,
$depth - 1,
$permsClause,
$considerHidden
));
}
}
return $subPageIds;
}
/**
* Add page translations to list of pages
*
* @param int $currentPage
* @param string $permsClause
* @param bool $considerHiddenPages
* @param int[] $limitToLanguageIds
* @return int[]
*/
public function getTranslationForPage(
int $currentPage,
string $permsClause,
bool $considerHiddenPages,
array $limitToLanguageIds = []
): array {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
if (!$considerHiddenPages) {
$queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
}
$constraints = [
$queryBuilder->expr()->eq(
'l10n_parent',
$queryBuilder->createNamedParameter($currentPage, \PDO::PARAM_INT)
)
];
if (!empty($limitToLanguageIds)) {
$constraints[] = $queryBuilder->expr()->in(
'sys_language_uid',
$queryBuilder->createNamedParameter($limitToLanguageIds, Connection::PARAM_INT_ARRAY)
);
}
if ($permsClause) {
$constraints[] = QueryHelper::stripLogicalOperatorPrefix($permsClause);
}
$result = $queryBuilder
->select('uid', 'title', 'hidden')
->from(self::TABLE)
->where(...$constraints)
->execute();
$translatedPages = [];
while ($row = $result->fetch()) {
$translatedPages[] = (int)$row['uid'];
}
return $translatedPages;
}
}
......@@ -18,13 +18,13 @@ declare(strict_types=1);
namespace TYPO3\CMS\Linkvalidator\Result;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Linkvalidator\LinkAnalyzer;
use TYPO3\CMS\Linkvalidator\Repository\BrokenLinkRepository;
use TYPO3\CMS\Linkvalidator\Repository\PagesRepository;
/**
* Used to work with LinkAnalyzer results
......@@ -43,6 +43,11 @@ class LinkAnalyzerResult
*/
protected $brokenLinkRepository;
/**
* @var PagesRepository
*/
protected $pagesRepository;
/**
* @var ConnectionPool
*/
......@@ -85,11 +90,13 @@ class LinkAnalyzerResult
public function __construct(
LinkAnalyzer $linkAnalyzer,
BrokenLinkRepository $brokenLinkRepository,
ConnectionPool $connectionPool
ConnectionPool $connectionPool,
PagesRepository $pagesRepository
) {
$this->linkAnalyzer = $linkAnalyzer;
$this->brokenLinkRepository = $brokenLinkRepository;
$this->connectionPool = $connectionPool;
$this->pagesRepository = $pagesRepository;
}
/**
......@@ -114,41 +121,49 @@ class LinkAnalyzerResult
array $linkTypes = [],
string $languages = ''
): self {
$rootLineHidden = $this->linkAnalyzer->getRootLineIsHidden($pageRow);
$rootLineHidden = $this->pagesRepository->doesRootLineContainHiddenPages($pageRow);
$checkHidden = $modTSconfig['checkhidden'] === 1;
if ($rootLineHidden && !$checkHidden) {
return $this;
}
$treeList = $this->linkAnalyzer->extGetTreeList($page, $depth, 0, '1=1', $modTSconfig['checkhidden']);
$pageIds = $this->pagesRepository->getAllSubpagesForPage(
$page,
$depth,
'',
$checkHidden
);
if ($pageRow['hidden'] === 0 || $checkHidden) {
$treeList .= $page;
$pageIds[] = $page;
}
if ($treeList === '') {
if (empty($pageIds)) {
return $this;
}
$pageList = $this->addPageTranslationsToPageList(
$treeList,
$languageIds = GeneralUtility::intExplode(',', $languages, true);
$pageTranslations = $this->pagesRepository->getTranslationForPage(
$page,
(bool)$modTSconfig['checkhidden'],
$languages
'',
$checkHidden,
$languageIds
);
$this->linkAnalyzer->init($searchFields, $pageList, $modTSconfig);
$pageIds = array_merge($pageIds, $pageTranslations);
$this->linkAnalyzer->init($searchFields, implode(',', $pageIds), $modTSconfig);
$this->oldBrokenLinkCounts = $this->linkAnalyzer->getLinkCounts();
$this->linkAnalyzer->getLinkStatistics($linkTypes, $modTSconfig['checkhidden']);
$this->linkAnalyzer->getLinkStatistics($linkTypes, $checkHidden);
$this->newBrokenLinkCounts = $this->linkAnalyzer->getLinkCounts();
$this->brokenLinks = $this->brokenLinkRepository->getAllBrokenLinksForPages(
GeneralUtility::intExplode(',', $pageList, true),
$pageIds,
array_keys($linkTypes),
$searchFields,
GeneralUtility::intExplode(',', $languages, true)
$languageIds
);
$this
......@@ -255,54 +270,6 @@ class LinkAnalyzerResult
return $this;
}
/**
* Add localized page ids to the list of pages to get broken links from
*
* @param string $pageList
* @param int $page
* @param bool $checkHidden
* @param string $languages
* @return string
*/
protected function addPageTranslationsToPageList(
string $pageList,
int $page,
bool $checkHidden,
string $languages = ''
): string {
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$constraints[] = $queryBuilder->expr()->eq(
'l10n_parent',
$queryBuilder->createNamedParameter($page, Connection::PARAM_INT)
);
if ($languages !== '') {
$constraints[] = $queryBuilder->expr()->in(
'sys_language_uid',
$queryBuilder->createNamedParameter(
GeneralUtility::intExplode(',', $languages, true),
Connection::PARAM_INT_ARRAY
)
);
}
$result = $queryBuilder
->select('uid', 'hidden')
->from('pages')
->where(...$constraints)
->execute();
while ($row = $result->fetch()) {
if ($row['hidden'] === 0 || $checkHidden) {
$pageList .= ',' . $row['uid'];
}
}
return $pageList;
}
/**
* Get language iso code and store it in the local property languageCodes
*
......
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