<?php namespace SGalinski\SgNews\Controller; /*************************************************************** * Copyright notice * * (c) sgalinski Internet Services (https://www.sgalinski.de) * * All rights reserved * * This script is part of the TYPO3 project. The TYPO3 project is * free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * * This script is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ use SGalinski\SgNews\Domain\Model\Category; use SGalinski\SgNews\Domain\Model\News; use SGalinski\SgNews\Domain\Repository\CategoryRepository; use SGalinski\SgNews\Domain\Repository\NewsRepository; use SGalinski\SgNews\Domain\Repository\TagRepository; use SGalinski\SgNews\Domain\Service\NewsService; use SGalinski\SgNews\Service\ConfigurationService; use SGalinski\SgNews\Service\HeaderMetaDataService; use SGalinski\SgSeo\Service\HeadTagService; use TYPO3\CMS\Core\Http\ImmediateResponseException; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; 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 */ class OverviewController extends AbstractController { /** * @var CategoryRepository */ protected $categoryRepository; /** * @var TagRepository */ protected $tagRepository; /** * @var NewsRepository */ protected $newsRepository; /** * @var NewsService */ protected $newsService; /** * @param CategoryRepository $categoryRepository */ public function injectCategoryRepository(CategoryRepository $categoryRepository ) { $this->categoryRepository = $categoryRepository; } /** * @param NewsRepository $newsRepository */ public function injectNewsRepository(NewsRepository $newsRepository) { $this->newsRepository = $newsRepository; } /** * @param TagRepository $tagRepository */ public function injectTagRepository(TagRepository $tagRepository) { $this->tagRepository = $tagRepository; } /** * @param NewsService $newsService */ public function injectNewsService(NewsService $newsService) { $this->newsService = $newsService; } /** * Initialize the overviewAction to set the currentPageBrowserPage parameter * * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException */ public function initializeOverviewAction() { $currentPageBrowserPage = (int) GeneralUtility::_GP('tx_sgnews_pagebrowser')['currentPage']; if ($currentPageBrowserPage > 0) { $this->request->setArgument('currentPageBrowserPage', $currentPageBrowserPage); } } /** * Renders the news overview * * @param array $newsFilter * @param int $currentPageBrowserPage * @return void * @throws \InvalidArgumentException * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException * @throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException */ public function overviewAction(array $newsFilter = [], int $currentPageBrowserPage = 0) { if ((int) $this->settings['groupBy'] === 0 && (bool) $this->settings['enableFilter'] === FALSE) { $this->forward('overviewWithoutCategories', NULL, NULL, $this->request->getArguments()); } // Setup settings $startTime = (int) $this->settings['starttime']; $endTime = (int) $this->settings['endtime']; $newsLimit = (int) $this->settings['newsLimit']; $offset = $this->calculatePaginationOffset($currentPageBrowserPage, $newsLimit); $configurationService = GeneralUtility::makeInstance(ConfigurationService::class); $sortBy = $configurationService->getConfiguration('sortBy', $this->settings); $sortDirection = $configurationService->getConfiguration('sortDirection', $this->settings); $this->tagRepository->setDefaultOrderings(['sorting' => QueryInterface::ORDER_ASCENDING]); $this->categoryRepository->setDefaultOrderings(['sorting' => QueryInterface::ORDER_ASCENDING]); $useAllFilters = (bool) $this->settings['enableFilter']; $isCategoryFiltered = $useAllFilters || (int) $this->settings['groupBy'] === 1; $isTagFiltered = $useAllFilters || (int) $this->settings['groupBy'] === 2; HeaderMetaDataService::addPageNumberToCanonical($currentPageBrowserPage); // Get tag pid $tagPid = (int) $this->settings['tagPid']; if (!$tagPid) { $tagPid = $GLOBALS['TSFE']->id; } if ($this->settings['onlyNewsWithinThisPageSection']) { $categories = $this->categoryRepository->findCategoriesInRootLine($GLOBALS['TSFE']->id); $tags = $this->tagRepository->findTagsInRootLine($tagPid); } else { $tags = $this->tagRepository->findAll()->toArray(); $categories = $this->categoryRepository->findAll()->toArray(); } // Apply category restrictions $categoryRestrictions = GeneralUtility::intExplode(',', $this->settings['categoryRestrictions'], TRUE); if (!$isCategoryFiltered) { $categoryRestrictions = []; } 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($categoryId); if ($originalLangCategory) { $categoryIdTranslated = $originalLangCategory->getUid(); } } if (!in_array($categoryId, $categoryRestrictions, TRUE) && !in_array($categoryIdTranslated, $categoryRestrictions, TRUE) ) { unset($categories[$key]); } } } // Apply tag restrictions $tagRestrictions = GeneralUtility::intExplode(',', $this->settings['tagRestrictions'], TRUE); if (!$isTagFiltered) { $tagRestrictions = []; } if (count($tagRestrictions) > 0) { foreach ($tags as $key => $tag) { if (!in_array($tag->getUid(), $tagRestrictions, TRUE)) { unset($tags[$key]); } } } // Get category ids or use the one in the filter $categoryIds = []; if ($newsFilter['category']) { $categoryIds = [(int) $newsFilter['category']]; } elseif ($isCategoryFiltered) { foreach ($categories as $category) { $categoryIds[] = $category->getUid(); } } else { $categoryIds = NULL; } // Get tag ids or use the one in the filter $tagIds = []; if ($newsFilter['tag']) { $tagIds = [(int) $newsFilter['tag']]; } elseif ($isTagFiltered) { foreach ($tags as $tag) { $tagIds[] = $tag->getUid(); } } else { $tagIds = NULL; } // Get all news by category and tag ids $news = $this->newsRepository->findAllSortedNewsByCategories( $categoryIds, $newsLimit, $offset, $sortBy, $tagIds, $startTime, $endTime, $sortDirection ); // Process news result query based on filters $allNews = []; $newsItems = []; $areCategoriesCreated = FALSE; foreach ($news as $newsEntry) { /** @var News $newsEntry */ $newsCategory = $this->categoryRepository->findOriginalLanguageById( $newsEntry->getPid() ) ?? $this->categoryRepository->findByUid($newsEntry->getPid()); $newsCategoryId = $newsCategory->getUid(); $newsMetaData = $this->newsService->getMetaDataForNews($newsEntry, $newsCategory); if ((int) $this->settings['groupBy'] === 1) { if (!$areCategoriesCreated) { // Add all required categories foreach ($categories as $category) { /** @var Category $category */ $newsItems[$category->getUid()] = [ 'record' => $category, 'recordId' => $category->getUid(), 'recordType' => 'category', 'newsMetaData' => [], 'newsCount' => 0 ]; } $areCategoriesCreated = TRUE; } $newsItems[$newsCategoryId]['newsMetaData'][] = $newsMetaData; $newsItems[$newsCategoryId]['newsCount'] += 1; } else { foreach ($tags as $tag) { $tagId = $tag->getUid(); if (!isset($newsItems[$tagId])) { $newsItems[$tagId] = [ 'record' => $tag, 'recordId' => $tagId, 'recordType' => 'tag', 'newsMetaData' => [], 'newsCount' => 0 ]; } if ($newsEntry->getTags()->contains($tag)) { $newsItems[$tagId]['newsMetaData'][] = $newsMetaData; $newsItems[$tagId]['newsCount'] += 1; } } } $allNews[] = $newsMetaData; } $this->highlightBestFitNews($categoryIds, $tagIds); // Check to achieve less Ajax calls. $newsCount = $this->newsRepository->newsCountByCategories($categoryIds, $tagIds, $startTime, $endTime); $numberOfPages = (int) ($newsLimit <= 0 ? 0 : ceil($newsCount / $newsLimit)); if ($numberOfPages !== 0 && $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); } // remember selection of the filter values, if any $selectedTag = $this->tagRepository->findByUid((int) $newsFilter['tag']); $selectedCategory = $this->categoryRepository->findByUid((int) $newsFilter['category']); $this->view->assign('selectedCategory', $selectedCategory); $this->view->assign('selectedTag', $selectedTag); $this->view->assign('tags', $tags); $this->view->assign('categories', $categories); $this->view->assign('numberOfPages', $numberOfPages); $this->view->assign('newsItems', $newsItems); $this->view->assign('allNews', $allNews); $this->setupGridColumns(); switch ($this->settings['groupBy']) { case 1: $this->view->assign('groupBy', 'category'); $this->view->assign('categoryTabs', TRUE); break; case 2: $this->view->assign('groupBy', 'tag'); $this->view->assign('tagTabs', TRUE); break; default: $this->view->assign('groupBy', 'none'); } } /** * Assign the grid column classes to the frontend * * @return void */ protected function setupGridColumns(): void { if (!isset($this->settings['gridColumns'])) { $this->view->assign('gridColumnClasses', 'col-md-4 col-sm-6 col-xs-12'); return; } $columnAmount = (int) $this->settings['gridColumns']; $columnClasses = ''; switch ($columnAmount) { case 4: $columnClasses .= 'col-lg-3 '; case 3: $columnClasses .= 'col-md-4 '; case 2: $columnClasses .= 'col-sm-6 '; default: $columnClasses .= 'col-xs-12'; } $this->view->assign('gridColumnClasses', $columnClasses); } /** * Highlights the best fitting news in the metadata of the page * * @param array|null $categoryIds * @param array|null $tagIds * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException */ protected function highlightBestFitNews(array $categoryIds = NULL, array $tagIds = NULL) { $startTime = (int) $this->settings['starttime']; $endTime = (int) $this->settings['endtime']; /** @var News $highlightedNews */ $highlightedNews = $this->newsRepository ->findLastUpdatedOrHighlightedNewsByCategories( 1, FALSE, $categoryIds, 0, FALSE, $this->settings['sortBy'], $tagIds, $startTime, $endTime )->getFirst(); if (!$highlightedNews) { return; } /** @var Category $category */ $category = $this->categoryRepository->findByUid($highlightedNews->getPid()); $highlightedNewsMetaData = NULL; if ($category) { $highlightedNewsMetaData = $this->newsService->getMetaDataForNews($highlightedNews, $category); } if (!version_compare(ExtensionManagementUtility::getExtensionVersion('sg_seo'), '5.0.0', '>=')) { if ($highlightedNewsMetaData['image']) { HeaderMetaDataService::addOgImageToHeader($highlightedNewsMetaData['image']); } elseif ($highlightedNewsMetaData['teaserImage']) { HeaderMetaDataService::addOgImageToHeader($highlightedNewsMetaData['teaserImage']); } } } /** * Initialize the overviewWithoutCategoriesAction to set the currentPageBrowserPage parameter * * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException */ public function initializeOverviewWithoutCategoriesAction() { $currentPageBrowserPage = (int) GeneralUtility::_GP('tx_sgnews_pagebrowser')['currentPage']; if ($currentPageBrowserPage > 0) { $this->request->setArgument('currentPageBrowserPage', $currentPageBrowserPage); } } /** * Renders the news in a paginated list * * @param array $newsMetaData * @param array|null $newsFilter * @param int $currentPageBrowserPage * @return void * @throws ImmediateResponseException * @throws \TYPO3\CMS\Core\Error\Http\PageNotFoundException * @throws \TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException */ protected function overviewWithoutCategoriesAction( array $newsMetaData = [], array $newsFilter = NULL, int $currentPageBrowserPage = 0 ) { // remember selection of the filter values, if any $selectedTag = $this->tagRepository->findByUid((int) $newsFilter['tag']); $selectedCategory = $this->categoryRepository->findByUid((int) $newsFilter['category']); $this->view->assign('selectedTag', $selectedTag); $this->view->assign('selectedCategory', $selectedCategory); $configurationService = GeneralUtility::makeInstance(ConfigurationService::class); $sortBy = $configurationService->getConfiguration('sortBy', $this->settings); $sortDirection = $configurationService->getConfiguration('sortDirection', $this->settings); $newsPerPage = (int) $this->settings['newsLimit']; $offset = $this->calculatePaginationOffset($currentPageBrowserPage, $newsPerPage); HeaderMetaDataService::addPageNumberToCanonical($currentPageBrowserPage); $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 = []; /** @var QueryResult $categories */ foreach ($categories as $category) { /** @var Category $category */ $categoriesById[$category->getUid()] = $category; $categoryIds[] = $category->getUid(); if ($category->_getProperty('_languageUid') > 0) { $originalLangCategory = $this->categoryRepository->findOriginalLanguageById( $category->getUid() ); if ($originalLangCategory) { $categoryIds[] = $originalLangCategory->getUid(); $categoriesById[$originalLangCategory->getUid()] = $originalLangCategory; } } } // filter by category and tag if selected in the filter if ($newsFilter['category']) { $categoryIds = [(int) $newsFilter['category']]; } $tagIds = NULL; if ($newsFilter['tag']) { $tagIds = [(int) $newsFilter['tag']]; } $newsCount = $this->newsRepository->newsCountByCategories($categoryIds, $tagIds, $startTime, $endTime); } } else { $newsCount = $this->newsRepository->countAll($startTime, $endTime); $categories = $this->categoryRepository->findAll()->toArray(); foreach ($categories as $category) { /** @var Category $category */ $categoriesById[$category->getUid()] = $category; } } if ($newsCount <= 0) { return; } // filter by category and tag if selected in the filter if ($newsFilter['category']) { $categoryIds = [(int) $newsFilter['category']]; } $tagIds = NULL; if ($newsFilter['tag']) { $tagIds = [(int) $newsFilter['tag']]; } $news = $this->newsRepository->findAllSortedNewsByCategories( $categoryIds, $newsPerPage, $offset, $sortBy, $tagIds, $startTime, $endTime, $sortDirection ); foreach ($news as $newsEntry) { /** @var News $newsEntry */ $data = $this->newsService->getMetaDataForNews($newsEntry, $categoriesById[$newsEntry->getPid()]); $newsMetaData[] = $data; } $this->highlightBestFitNews($categoryIds); $numberOfPages = (int) ($newsPerPage <= 0 ? 0 : ceil($newsCount / $newsPerPage)); if ($numberOfPages !== 0 && $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 $currentPageId = $GLOBALS['TSFE']->id; if ($this->settings['onlyNewsWithinThisPageSection']) { $tags = $this->tagRepository->findTagsInRootLine($currentPageId); } else { $tags = $this->tagRepository->findAll()->toArray(); } $this->view->assign('tags', $tags); $this->view->assign('categories', $categories); $this->view->assign('numberOfPages', $numberOfPages); $this->view->assign('newsMetaData', $newsMetaData); $this->setupGridColumns(); } /** * Sets the pagebrowser page to the given new page. * * @param int $newPage * @return void */ protected function setPageBrowserPage($newPage) { $_GET['tx_sgnews_pagebrowser']['currentPage'] = $newPage; } }