From c6fd9ba8d94ae670d8607ed6f9bb46f4d84e9dc5 Mon Sep 17 00:00:00 2001
From: Stefan Galinski <stefan@sgalinski.de>
Date: Tue, 7 Sep 2021 19:26:32 +0200
Subject: [PATCH] [BUGFIX] Disallow pagination pages greater than the allowed
 max number of pages (SEO), Cleanup in parallel

---
 .../Controller/ListByCategoryController.php   | 27 ++++---
 Classes/Controller/OverviewController.php     | 81 +++++++++++--------
 .../Domain/Repository/CategoryRepository.php  |  9 +--
 3 files changed, 67 insertions(+), 50 deletions(-)

diff --git a/Classes/Controller/ListByCategoryController.php b/Classes/Controller/ListByCategoryController.php
index 99c6281..609a754 100644
--- a/Classes/Controller/ListByCategoryController.php
+++ b/Classes/Controller/ListByCategoryController.php
@@ -32,7 +32,10 @@ use SGalinski\SgNews\Domain\Repository\NewsRepository;
 use SGalinski\SgNews\Domain\Repository\TagRepository;
 use SGalinski\SgNews\Service\ConfigurationService;
 use SGalinski\SgNews\Service\HeaderMetaDataService;
+use TYPO3\CMS\Core\Http\ImmediateResponseException;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Controller\ErrorController;
+use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
 
 /**
  * Controller that handles the list rendering of a single category
@@ -96,8 +99,10 @@ class ListByCategoryController extends AbstractController {
 	 * @throws \InvalidArgumentException
 	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
 	 * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException
+	 * @throws ImmediateResponseException
+	 * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
 	 */
-	public function indexAction(array $newsMetaData = [], $currentPageBrowserPage = 0) {
+	public function indexAction(array $newsMetaData = [], int $currentPageBrowserPage = 0) {
 		$filterByCategories = FALSE;
 		$categoryUids = GeneralUtility::intExplode(',', $this->settings['categories']);
 		$tagUids = GeneralUtility::intExplode(',', $this->settings['tags'], TRUE);
@@ -130,11 +135,20 @@ class ListByCategoryController extends AbstractController {
 
 		$startTime = (int) $this->settings['starttime'];
 		$endTime = (int) $this->settings['endtime'];
-
 		$newsPerPage = (int) $this->settings['newsLimitPerPage'];
 
 		$newsCount = $this->newsRepository->newsCountByCategories($categoryUids, $tagUids, $startTime, $endTime);
 		$numberOfPages = ($newsPerPage <= 0 ? 0 : ceil($newsCount / $newsPerPage));
+		if ($currentPageBrowserPage >= $numberOfPages) {
+			/** @var ErrorController $errorController */
+			$errorController = GeneralUtility::makeInstance(ErrorController::class);
+			$response = $errorController->pageNotFoundAction(
+				$GLOBALS['TYPO3_REQUEST'],
+				'The requested page does not exist',
+				['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
+			);
+			throw new ImmediateResponseException($response);
+		}
 
 		$headerSet = FALSE;
 		$offset = $this->calculatePaginationOffset($currentPageBrowserPage, $newsPerPage);
@@ -163,15 +177,6 @@ class ListByCategoryController extends AbstractController {
 			}
 		}
 
-		// Redo this function, until one variable get the amount of $newsPerPage. Reduces the amount of ajax calls. Needed because of languagevisibility.
-		if (count($newsMetaData) < $newsPerPage) {
-			$nextPage = $currentPageBrowserPage + 1;
-			if ($nextPage <= $numberOfPages) {
-				$this->indexAction($newsMetaData, $nextPage);
-				return;
-			}
-		}
-
 		$this->view->assign('numberOfPages', $numberOfPages);
 		$this->view->assign('newsMetaData', $newsMetaData);
 		$this->view->assign('categories', $categories);
diff --git a/Classes/Controller/OverviewController.php b/Classes/Controller/OverviewController.php
index 809957e..a43f8a2 100644
--- a/Classes/Controller/OverviewController.php
+++ b/Classes/Controller/OverviewController.php
@@ -27,17 +27,20 @@ namespace SGalinski\SgNews\Controller;
  ***************************************************************/
 
 use SGalinski\SgNews\Domain\Model\Category;
-use SGalinski\SgNews\Domain\Model\Tag;
 use SGalinski\SgNews\Domain\Model\News;
+use SGalinski\SgNews\Domain\Model\Tag;
 use SGalinski\SgNews\Domain\Repository\CategoryRepository;
 use SGalinski\SgNews\Domain\Repository\NewsRepository;
 use SGalinski\SgNews\Domain\Repository\TagRepository;
 use SGalinski\SgNews\Service\ConfigurationService;
 use SGalinski\SgNews\Service\HeaderMetaDataService;
+use TYPO3\CMS\Core\Http\ImmediateResponseException;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Persistence\Generic\Query;
 use TYPO3\CMS\Extbase\Persistence\Generic\QueryResult;
 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Frontend\Controller\ErrorController;
+use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
 
 /**
  * Controller that handles the overview page of categories and their news
@@ -162,6 +165,8 @@ class OverviewController extends AbstractController {
 	 * @throws \InvalidArgumentException
 	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
 	 * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException
+	 * @throws ImmediateResponseException
+	 * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
 	 */
 	protected function overviewWithCategories(
 		array $newsByCategory = [], array $allNews = [], array $newsFilter = [], int $currentPageBrowserPage = 0
@@ -185,14 +190,19 @@ class OverviewController extends AbstractController {
 		if (count($categoryRestrictions) > 0) {
 			foreach ($categories as $key => $category) {
 				$categoryId = $category->getUid();
+
+				// older version compatibility with selection of categories in translations and so on
+				$categoryIdTranslated = $categoryId;
 				if ($category->_getProperty('_languageUid') > 0) {
-					$originalLangCategory = $this->categoryRepository->findOriginalLanguageById($category->getUid());
+					$originalLangCategory = $this->categoryRepository->findOriginalLanguageById($categoryId);
 					if ($originalLangCategory) {
-						$categoryId = $originalLangCategory->getUid();
+						$categoryIdTranslated = $originalLangCategory->getUid();
 					}
 				}
 
-				if (!in_array($categoryId, $categoryRestrictions, TRUE)) {
+				if (!in_array($categoryId, $categoryRestrictions, TRUE) &&
+					!in_array($categoryIdTranslated, $categoryRestrictions, TRUE)
+				) {
 					unset($categories[$key]);
 				}
 			}
@@ -303,14 +313,15 @@ class OverviewController extends AbstractController {
 
 		$newsCount = $this->newsRepository->newsCountByCategories($categoryIds, $tagIds, $startTime, $endTime);
 		$numberOfPages = ($newsLimitPerCategory <= 0 ? 0 : ceil($newsCount / $newsLimitPerCategory));
-		// Redo this function, until one variable get the amount of newsLimitPerTag. Reduces the amount of ajax calls. Needed because of languagevisibility.
-		if ($maxNewsPerCategory < $newsLimitPerCategory && \count($allNews) < $newsLimitPerCategory) {
-			$nextPage = $currentPageBrowserPage + 1;
-			if ($nextPage <= $numberOfPages) {
-				$this->setPageBrowserPage($nextPage);
-				$this->overviewWithCategories($newsByCategory, $allNews, $newsFilter, $nextPage);
-				return;
-			}
+		if ($currentPageBrowserPage >= $numberOfPages) {
+			/** @var ErrorController $errorController */
+			$errorController = GeneralUtility::makeInstance(ErrorController::class);
+			$response = $errorController->pageNotFoundAction(
+				$GLOBALS['TYPO3_REQUEST'],
+				'The requested page does not exist',
+				['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
+			);
+			throw new ImmediateResponseException($response);
 		}
 
 		// find all tags
@@ -344,6 +355,8 @@ class OverviewController extends AbstractController {
 	 * @throws \InvalidArgumentException
 	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
 	 * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException
+	 * @throws ImmediateResponseException
+	 * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
 	 */
 	protected function overviewWithTags(
 		array $newsByTag = [], array $allNews = [], array $newsFilter = [], int $currentPageBrowserPage = 0
@@ -462,14 +475,15 @@ class OverviewController extends AbstractController {
 		// Check to achieve less Ajax calls.
 		$newsCount = $this->newsRepository->newsCountByCategories([], $tagIds, $startTime, $endTime);
 		$numberOfPages = ($newsLimitPerTag <= 0 ? 0 : ceil($newsCount / $newsLimitPerTag));
-		// Redo this function, until one variable get the amount of newsLimitPerTag. Reduces the amount of ajax calls. Needed because of languagevisibility.
-		if ($maxNewsPerTag < $newsLimitPerTag && \count($allNews) < $newsLimitPerTag) {
-			$nextPage = $currentPageBrowserPage + 1;
-			if ($nextPage <= $numberOfPages) {
-				$this->setPageBrowserPage($nextPage);
-				$this->overviewWithTags($newsByTag, $allNews, $newsFilter, $nextPage);
-				return;
-			}
+		if ($currentPageBrowserPage >= $numberOfPages) {
+			/** @var ErrorController $errorController */
+			$errorController = GeneralUtility::makeInstance(ErrorController::class);
+			$response = $errorController->pageNotFoundAction(
+				$GLOBALS['TYPO3_REQUEST'],
+				'The requested page does not exist',
+				['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
+			);
+			throw new ImmediateResponseException($response);
 		}
 
 		if ($this->settings['onlyNewsWithinThisPageSection']) {
@@ -513,6 +527,8 @@ class OverviewController extends AbstractController {
 	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
 	 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
 	 * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException
+	 * @throws ImmediateResponseException
+	 * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException
 	 */
 	protected function overviewWithoutCategoriesAction(
 		array $newsMetaData = [], array $newsFilter = NULL, int $currentPageBrowserPage = 0
@@ -531,13 +547,14 @@ class OverviewController extends AbstractController {
 		$offset = $this->calculatePaginationOffset($currentPageBrowserPage, $newsPerPage);
 
 		$newsCount = 0;
+		$categoryIds = NULL;
+		$categoriesById = [];
 		$startTime = (int) $this->settings['starttime'];
 		$endTime = (int) $this->settings['endtime'];
 		if ($this->settings['onlyNewsWithinThisPageSection']) {
 			$categories = $this->categoryRepository->findCategoriesInRootLine($GLOBALS['TSFE']->id);
 			if (count($categories) > 0) {
 				$categoryIds = [];
-				$categoriesById = [];
 				/** @var QueryResult $categories */
 				foreach ($categories as $category) {
 					/** @var $category Category */
@@ -568,9 +585,6 @@ class OverviewController extends AbstractController {
 			}
 		} else {
 			$newsCount = $this->newsRepository->countAll($startTime, $endTime);
-			$categoryIds = NULL;
-
-			$categoriesById = [];
 			$categories = $this->categoryRepository->findAll()->toArray();
 			foreach ($categories as $category) {
 				/** @var $category Category */
@@ -602,17 +616,16 @@ class OverviewController extends AbstractController {
 		}
 
 		$this->highlightBestFitNews($categoryIds);
-
 		$numberOfPages = ($newsPerPage <= 0 ? 0 : ceil($newsCount / $newsPerPage));
-
-		// Redo this function, until one variable get the amount of newsLimitPerTag. Reduces the amount of ajax calls. Needed because of languagevisibility.
-		if (\count($newsMetaData) < ($newsPerPage - 1)) {
-			$nextPage = $currentPageBrowserPage + 1;
-			if ($nextPage <= $numberOfPages) {
-				$this->setPageBrowserPage($nextPage);
-				$this->overviewWithoutCategoriesAction($newsMetaData, $newsFilter, $nextPage);
-				return;
-			}
+		if ($currentPageBrowserPage >= $numberOfPages) {
+			/** @var ErrorController $errorController */
+			$errorController = GeneralUtility::makeInstance(ErrorController::class);
+			$response = $errorController->pageNotFoundAction(
+				$GLOBALS['TYPO3_REQUEST'],
+				'The requested page does not exist',
+				['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
+			);
+			throw new ImmediateResponseException($response);
 		}
 
 		// find all tags
diff --git a/Classes/Domain/Repository/CategoryRepository.php b/Classes/Domain/Repository/CategoryRepository.php
index 81f0076..83d5324 100644
--- a/Classes/Domain/Repository/CategoryRepository.php
+++ b/Classes/Domain/Repository/CategoryRepository.php
@@ -51,7 +51,7 @@ class CategoryRepository extends AbstractRepository {
 	 * @param int $uid
 	 * @return Category|null
 	 */
-	public function findOriginalLanguageById(int $uid) {
+	public function findOriginalLanguageById(int $uid): ?Category {
 		$dataMapper = $this->objectManager->get(DataMapper::class);
 		$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
 		$row = $queryBuilder->select('default.*')
@@ -62,18 +62,17 @@ class CategoryRepository extends AbstractRepository {
 			)
 			->setMaxResults(1)
 			->execute()->fetch();
-		if ($row) {
+		if ($row && (int) $row['uid'] !== 0) {
 			return current($dataMapper->map($this->objectType, [$row]));
-		} else {
-			return NULL;
 		}
+
+		return NULL;
 	}
 
 	/**
 	 * Returns all categories, with given page id in it's rootline.
 	 *
 	 * @param int $pageId
-	 *
 	 * @return array
 	 */
 	public function findCategoriesInRootLine(int $pageId): array {
-- 
GitLab