From 83546286d26719a8993bf588f1c8c21ef8a00ae1 Mon Sep 17 00:00:00 2001
From: Sybille Peters <sypets@gmx.de>
Date: Sun, 13 Aug 2023 10:08:44 +0200
Subject: [PATCH] [BUGFIX] Fix Linkvalidator checks on hidden pages

The following problems are now fixed:

1. do not check links on hidden pages (if checkhidden=0)
2. show links on hidden pages (if checkhidden=1)

Also, unit tests are introduced for PagesRepository.
More functional tests can be added in additional patches.

Resolves: #93895
Releases: main, 12.4
Change-Id: Id8fc2ea6df4d0c8d1adbaee114d5a4a6d556b8d1
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/80661
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: core-ci <typo3@b13.com>
---
 .../Repository/BrokenLinkRepository.php       |  6 +++
 .../Classes/Repository/PagesRepository.php    | 22 ++++++----
 .../Unit/Repository/PagesRepositoryTest.php   | 41 +++++++++++++++++++
 3 files changed, 60 insertions(+), 9 deletions(-)
 create mode 100644 typo3/sysext/linkvalidator/Tests/Unit/Repository/PagesRepositoryTest.php

diff --git a/typo3/sysext/linkvalidator/Classes/Repository/BrokenLinkRepository.php b/typo3/sysext/linkvalidator/Classes/Repository/BrokenLinkRepository.php
index c6db2bc723f9..a1b42cd21d17 100644
--- a/typo3/sysext/linkvalidator/Classes/Repository/BrokenLinkRepository.php
+++ b/typo3/sysext/linkvalidator/Classes/Repository/BrokenLinkRepository.php
@@ -21,6 +21,7 @@ use Doctrine\DBAL\Exception\TableNotFoundException;
 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\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Linkvalidator\QueryRestrictions\EditableRestriction;
 
@@ -250,6 +251,11 @@ class BrokenLinkRepository
         foreach (array_chunk($pageIds, (int)floor($maxChunk / 2)) as $pageIdsChunk) {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                 ->getQueryBuilderForTable(self::TABLE);
+
+            // remove hidden restriction here because we join with pages and checkhidden=1 might be set
+            // we already correctly check for hidden / extendToSubpages when checking the links
+            $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
+
             if (!$GLOBALS['BE_USER']->isAdmin()) {
                 $queryBuilder->getRestrictions()
                     ->add(GeneralUtility::makeInstance(EditableRestriction::class, $searchFields, $queryBuilder));
diff --git a/typo3/sysext/linkvalidator/Classes/Repository/PagesRepository.php b/typo3/sysext/linkvalidator/Classes/Repository/PagesRepository.php
index ab907d77cef2..e429a29efe32 100644
--- a/typo3/sysext/linkvalidator/Classes/Repository/PagesRepository.php
+++ b/typo3/sysext/linkvalidator/Classes/Repository/PagesRepository.php
@@ -34,24 +34,28 @@ class PagesRepository
     protected const TABLE = 'pages';
 
     /**
-     * Check if rootline contains a hidden page
+     * Check if rootline contains a hidden page (including current page)
      *
      * @param array $pageInfo Array with uid, title, hidden, extendToSubpages from pages table
+     * @param int $recursionLevel starts with 0 on current page and gets incremented with each recursion (when moving up the rootline)
      * @return bool TRUE if rootline contains a hidden page, FALSE if not
      */
-    public function doesRootLineContainHiddenPages(array $pageInfo): bool
+    public function doesRootLineContainHiddenPages(array $pageInfo, int $recursionLevel = 0): bool
     {
         $pid = (int)($pageInfo['pid'] ?? 0);
-        if ($pid === 0) {
-            return false;
-        }
-        $isHidden = (bool)($pageInfo['hidden']);
-        $extendToSubpages = (bool)($pageInfo['extendToSubpages']);
+        $isHidden = (bool)($pageInfo['hidden'] ?? false);
+        $extendToSubpages = (bool)($pageInfo['extendToSubpages'] ?? false);
 
-        if ($extendToSubpages === true && $isHidden === true) {
+        // is current page hidden ($recursionLevel=0) or is page in rootline hidden and $extendToSubpages=true
+        if ($isHidden === true && ($extendToSubpages === true || $recursionLevel === 0)) {
             return true;
         }
 
+        if ($pid === 0) {
+            // has no (further) parent pages and no hidden pages detected
+            return false;
+        }
+
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
             ->getQueryBuilderForTable(self::TABLE);
         $queryBuilder->getRestrictions()->removeAll();
@@ -69,7 +73,7 @@ class PagesRepository
             ->fetchAssociative();
 
         if ($row !== false) {
-            return $this->doesRootLineContainHiddenPages($row);
+            return $this->doesRootLineContainHiddenPages($row, ++$recursionLevel);
         }
         return false;
     }
diff --git a/typo3/sysext/linkvalidator/Tests/Unit/Repository/PagesRepositoryTest.php b/typo3/sysext/linkvalidator/Tests/Unit/Repository/PagesRepositoryTest.php
new file mode 100644
index 000000000000..e7e50ce33397
--- /dev/null
+++ b/typo3/sysext/linkvalidator/Tests/Unit/Repository/PagesRepositoryTest.php
@@ -0,0 +1,41 @@
+<?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\Tests\Unit\Repository;
+
+use TYPO3\CMS\Linkvalidator\Repository\PagesRepository;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+final class PagesRepositoryTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function doesRootLineContainHiddenPagesReturnTrueForCurrentPage(): void
+    {
+        $subject = new PagesRepository();
+        $pageInfo = [
+            'uid' => 1,
+            'pid' => 0,
+            'hidden' => 1,
+            'extendToSubpages' => 0,
+        ];
+        $result = $subject->doesRootLineContainHiddenPages($pageInfo);
+
+        self::assertTrue($result);
+    }
+}
-- 
GitLab