diff --git a/Classes/Controller/AbstractController.php b/Classes/Controller/AbstractController.php index d4d44b186b280961a27327a1d275649559436abb..d7fc77278262b39e3bdffa00738268ea33e73c24 100644 --- a/Classes/Controller/AbstractController.php +++ b/Classes/Controller/AbstractController.php @@ -27,32 +27,17 @@ namespace SGalinski\SgNews\Controller; ***************************************************************/ use RuntimeException; -use SGalinski\SgNews\Domain\Model\Category; -use SGalinski\SgNews\Domain\Model\News; -use SGalinski\SgNews\Service\ImageService; -use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Domain\Model\FileReference; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; /** * Abstract Controller */ abstract class AbstractController extends ActionController { - /** - * @var ImageService - */ - protected $imageService; - /** * @var array */ protected $extensionConfiguration = []; - /** - * @var array - */ - protected $cachedSingleNews = []; - /** * Initializes any action * @@ -65,13 +50,6 @@ abstract class AbstractController extends ActionController { parent::initializeAction(); } - /** - * @param ImageService $imageService - */ - public function injectImageService(ImageService $imageService) { - $this->imageService = $imageService; - } - /** * Error Handler * @@ -82,129 +60,6 @@ abstract class AbstractController extends ActionController { throw new RuntimeException(parent::errorAction()); } - /** - * Returns the metadata of the given news. - * - * @param News $news - * @param Category $category - * @return array - * @throws \InvalidArgumentException - */ - protected function getMetaDataForNews(News $news, Category $category): array { - $newsId = $news->getUid(); - if (isset($this->cachedSingleNews[$newsId])) { - return $this->cachedSingleNews[$newsId]; - } - - $fileRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\FileRepository::class); - $fileObjects = $fileRepository->findByRelation('pages', 'media', $news->getUid()); - - $singleNewsImageData = $this->getDataForSingleViewImage($news, $category); - $teaserImageData = $this->getDataForTeaserImage($news, $category); - - // Use single news image data for teaser image data, if the teaser imaga data are empty. - $teaserIsEmpty = $teaserImageData['teaserImage'] === NULL && $teaserImageData['teaserImageObject'] === NULL; - $singleNewsIsEmpty = $singleNewsImageData['image'] === NULL && $singleNewsImageData['imageObject'] === NULL; - if ($teaserIsEmpty && !$singleNewsIsEmpty) { - $teaserImageData = [ - 'teaserImage' => $singleNewsImageData['image'], - 'teaserImageObject' => $singleNewsImageData['imageObject'], - ]; - } - - $newsRecord = array_merge( - [ - 'category' => $category, - 'news' => $news, - ], - $singleNewsImageData, - $teaserImageData, - ['media' => $fileObjects] - ); - - $this->cachedSingleNews[$newsId] = $newsRecord; - - return $newsRecord; - } - - /** - * Returns the single view image data as an array for the given news and category as fallback. - * - * @param News $news - * @param Category $category - * @return array - * @throws \InvalidArgumentException - */ - protected function getDataForSingleViewImage(News $news, Category $category): array { - /** @var FileReference $singleNewsImage */ - $singleNewsImage = $singleNewsImageObject = NULL; - $singleNewsImages = $news->getTeaser2Image(); - if (count($singleNewsImages)) { - $singleNewsImage = $singleNewsImages->current(); - } else { - $categoryImages = $category->getTeaser2Image(); - if (count($categoryImages)) { - $singleNewsImage = $categoryImages->current(); - } - } - - if ($singleNewsImage) { - $singleNewsImageObject = $singleNewsImage; - $originalResource = $singleNewsImage->getOriginalResource(); - if ($originalResource) { - $singleNewsImage = $originalResource->getPublicUrl(); - } - - if ($singleNewsImage) { - $singleNewsImage = $GLOBALS['TSFE']->absRefPrefix . $singleNewsImage; - } - } - - return [ - 'image' => $singleNewsImage, - 'imageObject' => $singleNewsImageObject, - ]; - } - - /** - * Returns the teaser image data as an array for the given news and category as fallback. - * - * @param News $news - * @param Category $category - * @return array - * @throws \InvalidArgumentException - */ - protected function getDataForTeaserImage(News $news, Category $category): array { - /** @var FileReference $teaserImage */ - $teaserImage = $teaserImageObject = NULL; - $teaserImages = $news->getTeaser1Image(); - if (count($teaserImages)) { - $teaserImage = $teaserImages->current(); - } else { - $categoryImages = $category->getTeaser1Image(); - if (count($categoryImages)) { - $teaserImage = $categoryImages->current(); - } - } - - if ($teaserImage) { - $teaserImageObject = $teaserImage; - $originalResource = $teaserImage->getOriginalResource(); - if ($originalResource) { - $teaserImage = $originalResource->getPublicUrl(); - } - - if ($teaserImage) { - $teaserImage = $GLOBALS['TSFE']->absRefPrefix . $teaserImage; - } - } - - return [ - 'teaserImage' => $teaserImage, - 'teaserImageObject' => $teaserImageObject, - ]; - } - /** * Calculate the pagination offset * diff --git a/Classes/Controller/LatestController.php b/Classes/Controller/LatestController.php index 393c956362a98045addca3de04b42f4d7db69029..e5eb3ea0cdeaa830bdf41fca500797a9a4bf9e68 100644 --- a/Classes/Controller/LatestController.php +++ b/Classes/Controller/LatestController.php @@ -31,6 +31,7 @@ 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 TYPO3\CMS\Core\Utility\GeneralUtility; @@ -53,6 +54,11 @@ class LatestController extends AbstractController { */ protected $newsRepository; + /** + * @var NewsService + */ + protected $newsService; + /** * Renders the news overview * @@ -96,7 +102,7 @@ class LatestController extends AbstractController { $categories[$categoryUid] = $category; } - $newsMetaData[] = $this->getMetaDataForNews($latestNewsEntry, $category); + $newsMetaData[] = $this->newsService->getMetaDataForNews($latestNewsEntry, $category); // Make sure, that the amount is the same as the configured limit. if (count($newsMetaData) >= $limit) { break; @@ -127,4 +133,11 @@ class LatestController extends AbstractController { public function injectTagRepository(TagRepository $tagRepository) { $this->tagRepository = $tagRepository; } + + /** + * @param NewsService $newsService + */ + public function injectNewsService(NewsService $newsService) { + $this->newsService = $newsService; + } } diff --git a/Classes/Controller/ListByCategoryController.php b/Classes/Controller/ListByCategoryController.php index 2f405c986df67aa5f2925f43c1ac84e697c36880..2a1c9619255b8ee6dee8a1e990475adf98afad87 100644 --- a/Classes/Controller/ListByCategoryController.php +++ b/Classes/Controller/ListByCategoryController.php @@ -30,6 +30,7 @@ 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 TYPO3\CMS\Core\Utility\ExtensionManagementUtility; @@ -57,6 +58,11 @@ class ListByCategoryController extends AbstractController { */ protected $newsRepository; + /** + * @var NewsService + */ + protected $newsService; + /** * @param CategoryRepository $categoryRepository */ @@ -79,6 +85,13 @@ class ListByCategoryController extends AbstractController { $this->tagRepository = $tagRepository; } + /** + * @param NewsService $newsService + */ + public function injectNewsService(NewsService $newsService) { + $this->newsService = $newsService; + } + /** * Initialize the indexAction to set the currentPageBrowserPage parameter * @@ -164,7 +177,7 @@ class ListByCategoryController extends AbstractController { foreach ($news as $newsEntry) { /** @var News $newsEntry */ - $data = $this->getMetaDataForNews($newsEntry, $categories[$newsEntry->getPid()]); + $data = $this->newsService->getMetaDataForNews($newsEntry, $categories[$newsEntry->getPid()]); $newsMetaData[] = $data; if (!$headerSet) { diff --git a/Classes/Controller/NewsByAuthorController.php b/Classes/Controller/NewsByAuthorController.php index 4ace64d3cd03019ceb74e63fe1c9a6e1e63a5abd..217140fc9f465753eee084f7699e37f1d5186e67 100644 --- a/Classes/Controller/NewsByAuthorController.php +++ b/Classes/Controller/NewsByAuthorController.php @@ -32,6 +32,7 @@ use SGalinski\SgNews\Domain\Model\News; use SGalinski\SgNews\Domain\Repository\AuthorRepository; use SGalinski\SgNews\Domain\Repository\CategoryRepository; use SGalinski\SgNews\Domain\Repository\NewsRepository; +use SGalinski\SgNews\Domain\Service\NewsService; use SGalinski\SgSeo\Service\HeadTagService; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -40,6 +41,18 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; * News Author List Controller */ class NewsByAuthorController extends AbstractController { + /** + * @var NewsService + */ + protected $newsService; + + /** + * @param NewsService $newsService + */ + public function injectNewsService(NewsService $newsService) { + $this->newsService = $newsService; + } + /** * Renders the news author list. * @@ -118,7 +131,7 @@ class NewsByAuthorController extends AbstractController { $categories[$categoryId] = $category; } - $newsMetaData[] = $this->getMetaDataForNews($newsEntry, $categories[$categoryId]); + $newsMetaData[] = $this->newsService->getMetaDataForNews($newsEntry, $categories[$categoryId]); } } diff --git a/Classes/Controller/OverviewController.php b/Classes/Controller/OverviewController.php index 0a524e79e7966522e1870240086221a28012e863..5b002811c71f32d22e80395f42762c173c3515bd 100644 --- a/Classes/Controller/OverviewController.php +++ b/Classes/Controller/OverviewController.php @@ -31,6 +31,7 @@ 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 TYPO3\CMS\Core\Http\ImmediateResponseException; @@ -60,6 +61,11 @@ class OverviewController extends AbstractController { */ protected $newsRepository; + /** + * @var NewsService + */ + protected $newsService; + /** * @param CategoryRepository $categoryRepository */ @@ -82,6 +88,13 @@ class OverviewController extends AbstractController { $this->tagRepository = $tagRepository; } + /** + * @param NewsService $newsService + */ + public function injectNewsService(NewsService $newsService) { + $this->newsService = $newsService; + } + /** * Initialize the overviewAction to set the currentPageBrowserPage parameter * @@ -216,7 +229,7 @@ class OverviewController extends AbstractController { $newsEntry->getPid() ) ?? $this->categoryRepository->findByUid($newsEntry->getPid()); $newsCategoryId = $newsCategory->getUid(); - $newsMetaData = $this->getMetaDataForNews($newsEntry, $newsCategory); + $newsMetaData = $this->newsService->getMetaDataForNews($newsEntry, $newsCategory); if ((int) $this->settings['groupBy'] === 1) { if (!$areCategoriesCreated) { @@ -353,7 +366,7 @@ class OverviewController extends AbstractController { $category = $this->categoryRepository->findByUid($highlightedNews->getPid()); $highlightedNewsMetaData = NULL; if ($category) { - $highlightedNewsMetaData = $this->getMetaDataForNews($highlightedNews, $category); + $highlightedNewsMetaData = $this->newsService->getMetaDataForNews($highlightedNews, $category); } if (!version_compare(ExtensionManagementUtility::getExtensionVersion('sg_seo'), '5.0.0', '>=')) { @@ -470,7 +483,7 @@ class OverviewController extends AbstractController { ); foreach ($news as $newsEntry) { /** @var News $newsEntry */ - $data = $this->getMetaDataForNews($newsEntry, $categoriesById[$newsEntry->getPid()]); + $data = $this->newsService->getMetaDataForNews($newsEntry, $categoriesById[$newsEntry->getPid()]); $newsMetaData[] = $data; } diff --git a/Classes/Controller/SingleViewController.php b/Classes/Controller/SingleViewController.php index 3890778233b62dd28c5da41b2cb48fdad8a87969..a7d929ff11fc51619c7593d661568217c2a553aa 100644 --- a/Classes/Controller/SingleViewController.php +++ b/Classes/Controller/SingleViewController.php @@ -31,6 +31,7 @@ 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\HeaderMetaDataService; use TYPO3\CMS\Core\Charset\CharsetConverter; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; @@ -59,6 +60,11 @@ class SingleViewController extends AbstractController { */ protected $characterSetConverter; + /** + * @var NewsService + */ + protected $newsService; + /** * Renders the news single view * @@ -92,7 +98,7 @@ class SingleViewController extends AbstractController { // $similarNewsMetaData[] = $this->getMetaDataForNews($similarNewsEntry, $category); // } - $newsMetaData = $this->getMetaDataForNews($news, $newsCategory); + $newsMetaData = $this->newsService->getMetaDataForNews($news, $newsCategory); if (!version_compare(ExtensionManagementUtility::getExtensionVersion('sg_seo'), '5.0.0', '>=')) { if ($newsMetaData['image']) { HeaderMetaDataService::addOgImageToHeader($newsMetaData['image']); @@ -145,4 +151,11 @@ class SingleViewController extends AbstractController { public function injectTagRepository(TagRepository $tagRepository) { $this->tagRepository = $tagRepository; } + + /** + * @param NewsService $newsService + */ + public function injectNewsService(NewsService $newsService) { + $this->newsService = $newsService; + } } diff --git a/Classes/Domain/Repository/NewsRepository.php b/Classes/Domain/Repository/NewsRepository.php index 7d8011dcaf9a53d148a98e36023c90ed3b50d8f9..7b5f136564ed227a55505911e83fa3e85127a967 100644 --- a/Classes/Domain/Repository/NewsRepository.php +++ b/Classes/Domain/Repository/NewsRepository.php @@ -536,29 +536,40 @@ class NewsRepository extends AbstractRepository { $constraints[] = $qb->expr()->eq('pid', $news->getPid()); } + if ($limit > 0) { + $qb->setMaxResults($limit); + } + // here we fetch the lastUpdated of the $limit amount of news with newer lastUpdated dates $result = $qb->select('lastUpdated') - ->from('pages') + ->from('pages', 'pages') ->where( $qb->expr()->eq('doktype', $qb->createNamedParameter(News::DOK_TYPE_NEWS, Connection::PARAM_INT)), - $qb->expr()->gt('lastUpdated', $news->getLastUpdated()->getTimestamp()), - $qb->expr()->andX(...$constraints) - ) - ->setMaxResults($limit) + $qb->expr()->gte('lastUpdated', $news->getLastUpdated()->getTimestamp()) + )->andWhere(...$constraints) ->orderBy('lastUpdated', 'desc') ->execute(); $newest = $result->fetchOne(); // Here we fetch the lastUpdated of the $limit amount of news with older lastUpdated dates - $result = $qb->orderBy('lastUpdated', 'asc')->execute(); + $result = $qb->where( + $qb->expr()->eq('doktype', $qb->createNamedParameter(News::DOK_TYPE_NEWS, Connection::PARAM_INT)), + $qb->expr()->lte('lastUpdated', $news->getLastUpdated()->getTimestamp()) + )->andWhere(...$constraints) + ->orderBy('lastUpdated', 'asc') + ->execute(); $oldest = $result->fetchOne(); $query = $this->createQuery(); $query->getQuerySettings()->setRespectStoragePage(FALSE); $query->setOrderings([ - 'crdate' => QueryInterface::ORDER_DESCENDING + 'lastUpdated' => QueryInterface::ORDER_DESCENDING ]); - $constraints = []; + $constraints = [ + $query->logicalNot( + $query->equals('uid', $news->getUid()) + ) + ]; if ($newest) { $constraints[] = $query->lessThanOrEqual('lastUpdated', $newest); } @@ -572,9 +583,12 @@ class NewsRepository extends AbstractRepository { // "around" the given news, where newer news are preferred due to the ordering. $tags = $news->getTags(); if ($tags->count() > 0) { + $tagConstraints = []; foreach ($tags as $tag) { - $constraints[] = $query->contains('tags', $tag); + $tagConstraints[] = $query->contains('tags', $tag); } + + $constraints[] = $query->logicalOr($tagConstraints); } else { $constraints[] = $query->equals('pid', $news->getPid()); } diff --git a/Classes/Domain/Service/NewsService.php b/Classes/Domain/Service/NewsService.php new file mode 100644 index 0000000000000000000000000000000000000000..7ba3ad6d291e7a412b78cf1b3145c203321c4595 --- /dev/null +++ b/Classes/Domain/Service/NewsService.php @@ -0,0 +1,167 @@ +<?php + +namespace SGalinski\SgNews\Domain\Service; + +/*************************************************************** + * 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 TYPO3\CMS\Core\SingletonInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Domain\Model\FileReference; +use TYPO3\CMS\Core\Resource\FileRepository; + +/** + * This service takes care of meta data generation for the news objects + */ +class NewsService implements SingletonInterface { + /** + * @var array + */ + protected $cachedSingleNews = []; + + /** + * Returns the metadata of the given news. + * + * @param News $news + * @param Category $category + * @return array + * @throws \InvalidArgumentException + */ + public function getMetaDataForNews(News $news, Category $category): array { + $newsId = $news->getUid(); + if (isset($this->cachedSingleNews[$newsId])) { + return $this->cachedSingleNews[$newsId]; + } + + $fileRepository = GeneralUtility::makeInstance(FileRepository::class); + $fileObjects = $fileRepository->findByRelation('pages', 'media', $news->getUid()); + + $singleNewsImageData = $this->getDataForSingleViewImage($news, $category); + $teaserImageData = $this->getDataForTeaserImage($news, $category); + + // Use single news image data for teaser image data, if the teaser imaga data are empty. + $teaserIsEmpty = $teaserImageData['teaserImage'] === NULL && $teaserImageData['teaserImageObject'] === NULL; + $singleNewsIsEmpty = $singleNewsImageData['image'] === NULL && $singleNewsImageData['imageObject'] === NULL; + if ($teaserIsEmpty && !$singleNewsIsEmpty) { + $teaserImageData = [ + 'teaserImage' => $singleNewsImageData['image'], + 'teaserImageObject' => $singleNewsImageData['imageObject'], + ]; + } + + $newsRecord = array_merge( + [ + 'category' => $category, + 'news' => $news, + ], + $singleNewsImageData, + $teaserImageData, + ['media' => $fileObjects] + ); + + $this->cachedSingleNews[$newsId] = $newsRecord; + + return $newsRecord; + } + + /** + * Returns the single view image data as an array for the given news and category as fallback. + * + * @param News $news + * @param Category $category + * @return array + * @throws \InvalidArgumentException + */ + public function getDataForSingleViewImage(News $news, Category $category): array { + /** @var FileReference $singleNewsImage */ + $singleNewsImage = $singleNewsImageObject = NULL; + $singleNewsImages = $news->getTeaser2Image(); + if (count($singleNewsImages)) { + $singleNewsImage = $singleNewsImages->current(); + } else { + $categoryImages = $category->getTeaser2Image(); + if (count($categoryImages)) { + $singleNewsImage = $categoryImages->current(); + } + } + + if ($singleNewsImage) { + $singleNewsImageObject = $singleNewsImage; + $originalResource = $singleNewsImage->getOriginalResource(); + if ($originalResource) { + $singleNewsImage = $originalResource->getPublicUrl(); + } + + if ($singleNewsImage) { + $singleNewsImage = $GLOBALS['TSFE']->absRefPrefix . $singleNewsImage; + } + } + + return [ + 'image' => $singleNewsImage, + 'imageObject' => $singleNewsImageObject, + ]; + } + + /** + * Returns the teaser image data as an array for the given news and category as fallback. + * + * @param News $news + * @param Category $category + * @return array + * @throws \InvalidArgumentException + */ + public function getDataForTeaserImage(News $news, Category $category): array { + /** @var FileReference $teaserImage */ + $teaserImage = $teaserImageObject = NULL; + $teaserImages = $news->getTeaser1Image(); + if (count($teaserImages)) { + $teaserImage = $teaserImages->current(); + } else { + $categoryImages = $category->getTeaser1Image(); + if (count($categoryImages)) { + $teaserImage = $categoryImages->current(); + } + } + + if ($teaserImage) { + $teaserImageObject = $teaserImage; + $originalResource = $teaserImage->getOriginalResource(); + if ($originalResource) { + $teaserImage = $originalResource->getPublicUrl(); + } + + if ($teaserImage) { + $teaserImage = $GLOBALS['TSFE']->absRefPrefix . $teaserImage; + } + } + + return [ + 'teaserImage' => $teaserImage, + 'teaserImageObject' => $teaserImageObject, + ]; + } +} diff --git a/Classes/ViewHelpers/RelatedViewHelper.php b/Classes/ViewHelpers/RelatedViewHelper.php index 6a5ec43565fbde946b20321afd08819e3d957570..69b8e781d353728940d593b4788c082e934de247 100644 --- a/Classes/ViewHelpers/RelatedViewHelper.php +++ b/Classes/ViewHelpers/RelatedViewHelper.php @@ -2,8 +2,11 @@ namespace SGalinski\SgNews\ViewHelpers; +use SGalinski\SgNews\Domain\Repository\CategoryRepository; use SGalinski\SgNews\Domain\Repository\NewsRepository; +use SGalinski\SgNews\Domain\Service\NewsService; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Extbase\Persistence\ObjectStorage; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; @@ -13,6 +16,11 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic; class RelatedViewHelper extends AbstractViewHelper { use CompileWithRenderStatic; + /** + * @var bool + */ + protected $escapeOutput = false; + /** * Initialize the view helper arguments */ @@ -23,12 +31,30 @@ class RelatedViewHelper extends AbstractViewHelper { 'The news record from which to find related news', TRUE ); - $this->registerArgument('limit', 'int', 'Limit the amount of related news to display', FALSE, 5); - $this->registerArgument('as', 'string', 'The name of the iteration variable', TRUE); + $this->registerArgument( + 'limit', + 'int', + 'Limit the amount of related news to display', + FALSE, + 5 + ); + $this->registerArgument( + 'as', + 'string', + 'The name of the iteration variable', + TRUE + ); $this->registerArgument( 'iteration', 'string', 'The name of the variable to store iteration information (index, cycle, isFirst, isLast, isEven, isOdd)' ); + $this->registerArgument( + 'relatedNews', + ObjectStorage::class, + 'An optional list of related news to take instead of finding some via the news repository', + FALSE, + NULL + ); } /** @@ -44,21 +70,34 @@ class RelatedViewHelper extends AbstractViewHelper { public static function renderStatic( array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext ) { + $newsService = GeneralUtility::makeInstance(NewsService::class); + $categoryRepository = GeneralUtility::makeInstance(CategoryRepository::class); $templateVariableContainer = $renderingContext->getVariableProvider(); $news = $arguments['news']; $newsRepository = GeneralUtility::makeInstance(NewsRepository::class); - $related = $newsRepository->findRelated($news); + if ($arguments['relatedNews']) { + $related = $arguments['relatedNews']; + } else { + $related = $newsRepository->findRelated($news, (int) $arguments['limit']); + } + if (isset($arguments['iteration'])) { $iterationData = [ 'index' => 0, 'cycle' => 1, - 'total' => count($arguments['each']) + 'total' => $related->count() ]; } $output = ''; + $categories = []; foreach ($related as $relatedNews) { - $templateVariableContainer->add($arguments['as'], $relatedNews); + if (!isset($categories[$relatedNews->getPid()])) { + $categories[$relatedNews->getPid()] = $categoryRepository->findByUid($relatedNews->getPid()); + } + + $newsMetaData = $newsService->getMetaDataForNews($relatedNews, $categories[$relatedNews->getPid()]); + $templateVariableContainer->add($arguments['as'], $newsMetaData); if (isset($iterationData)) { $iterationData['isFirst'] = $iterationData['cycle'] === 1; $iterationData['isLast'] = $iterationData['cycle'] === $iterationData['total']; diff --git a/Configuration/TypoScript/Frontend/constants.typoscript b/Configuration/TypoScript/Frontend/constants.typoscript index 34a9c4da599228c1cc1b6f3069b7b9a49eab04db..58f8138af09e551c7aec4fbb1efd6f6b9db7ca00 100644 --- a/Configuration/TypoScript/Frontend/constants.typoscript +++ b/Configuration/TypoScript/Frontend/constants.typoscript @@ -19,5 +19,8 @@ plugin.tx_sgnews { # sort direction (DESC / ASC) sortDirection = DESC + + # This enables the output of related news in regards to the news category or tags + enableAutomaticRelatedNews = 0 } } diff --git a/Configuration/TypoScript/Frontend/setup.typoscript b/Configuration/TypoScript/Frontend/setup.typoscript index 9a52fd4ef64e299d3c2fd12ce028ac75c9ebbf6a..fb32038793c9f09240980a30d131cdea7f83f512 100644 --- a/Configuration/TypoScript/Frontend/setup.typoscript +++ b/Configuration/TypoScript/Frontend/setup.typoscript @@ -109,6 +109,9 @@ plugin.tx_sgnews { # The logo value for the structured data implementation (see single view template) publisherLogo = + + # This enables the output of related news in regards to the news category or tags + enableAutomaticRelatedNews = {$plugin.tx_sgnews.settings.enableAutomaticRelatedNews} } features { diff --git a/Resources/Private/Partials/TeaserRelated.html b/Resources/Private/Partials/TeaserRelated.html new file mode 100644 index 0000000000000000000000000000000000000000..2360faca6abc939c58cd947f821469b07d090b7b --- /dev/null +++ b/Resources/Private/Partials/TeaserRelated.html @@ -0,0 +1,36 @@ +{namespace sg=SGalinski\SgNews\ViewHelpers} + +<f:comment> + <!-- + Usage Example: + + <f:render partial="Teaser" arguments="{ + newsMetaData: newsMetaData, + headerTag: '<h3>', + closingHeaderTag: '</h3>', + showCategory: 1 + }" /> + + newsMetaData -> news element + headerTag -> hierarchy type of the header tag + showCategory -> defines if the category may be shown + + Use <f:debug>{_all}</f:debug> to see all parameters and fields. + --> +</f:comment> + +<f:link.page pageUid="{news.uid}" class="tx-sgnews-teaser"> + <f:if condition="{news.teaser1Image}"> + <f:for each="{news.teaser1Image}" as="teaserImage"> + <div class="tx-sgnews-teaser-image tx-sgnews-teaser-image-stretched" + style="background-image: url({f:uri.image(image: teaserImage)});"></div> + </f:for> + </f:if> + <div class="tx-sgnews-teaser-inner"> + <div class="tx-sgnews-teaser-title"> + {headerTag -> f:format.raw()} + {news.subtitleWithFallbackToTitle} + {closingHeaderTag -> f:format.raw()} + </div> + </div> +</f:link.page> diff --git a/Resources/Private/Templates/SingleView/SingleView.html b/Resources/Private/Templates/SingleView/SingleView.html index 7ee717d638ecc101d1777cbd7ec4f29ee8bfcd61..bbbb26abdbc10e04953aebdaedf7bfccd65ae54a 100644 --- a/Resources/Private/Templates/SingleView/SingleView.html +++ b/Resources/Private/Templates/SingleView/SingleView.html @@ -88,12 +88,12 @@ </div> </div> </section> - </div> + <section class="content"> <div class="container"> <div class="tx-sgnews-single"> - <div class="tx-sgnews-single-container"> + <ul class="tx-sgnews-single-container"> <f:alias map="{content: '{f:cObject(typoscriptObjectPath: \'{f:if(condition: \\\'{newsMetaData.news.contentFromAnotherPage}\\\', then: \\\'lib.contentFromAnotherPage\\\', else: \\\'lib.mainContent\\\')}\')}'}"> <div class="tx-sgnews-single-content"> @@ -199,38 +199,50 @@ <f:if condition="{newsMetaData.news.relatedNews}"> <f:then> - <div class="tx-sgnews-single-related"> + <div class="tx-sgnews-single-related tx-sgnews-categories"> <h3> <f:translate key="frontend.singleview.relatedArticles"/> </h3> - - <ul> - <f:for each="{newsMetaData.news.relatedNews}" as="relatedNewsEntry"> - <li> - <a href="{f:uri.page(pageUid: '{relatedNewsEntry.uid}')}"> - {relatedNewsEntry.subtitleWithFallbackToTitle} - </a> + <ul class="tx-sgnews-list tx-sgnews-overview row"> + <sg:related news="{newsMetaData.news}" + relatedNews="{newsMetaData.news.relatedNews}" + as="relatedNewsEntry"> + <li class="col-md-4 col-sm-6 col-xs-12"> + <f:render partial="Teaser" arguments="{ + newsMetaData: relatedNewsEntry, + headerTag: '<h2>', + closingHeaderTag: '</h2>', + showCategory: '1' + }" /> </li> - </f:for> + </sg:related> </ul> </div> </f:then> <f:else> - <div class="tx-sgnews-single-related"> - <h3> - <f:translate key="frontend.singleview.relatedArticles"/> - </h3> - - <ul> - <sg:related news="{newsMetaData.news}" limit="5" as="relatedNewsEntry"> - <li> - <a href="{f:uri.page(pageUid: '{relatedNewsEntry.uid}')}"> - {relatedNewsEntry.subtitleWithFallbackToTitle} - </a> - </li> - </sg:related> - </ul> - </div> + <f:if condition="{settings.enableAutomaticRelatedNews}"> + <sg:related news="{newsMetaData.news}" iteration="iterator" limit="3" as="relatedNewsEntry"> + <f:if condition="{iterator.isFirst}"> + <div class="tx-sgnews-single-related tx-sgnews-categories"> + <h3> + <f:translate key="frontend.singleview.relatedArticles"/> + </h3> + <ul class="tx-sgnews-list tx-sgnews-overview row"> + </f:if> + <li class="col-md-4 col-sm-6 col-xs-12"> + <f:render partial="Teaser" arguments="{ + newsMetaData: relatedNewsEntry, + headerTag: '<h2>', + closingHeaderTag: '</h2>', + showCategory: '1' + }" /> + </li> + <f:if condition="{iterator.isLast}"> + </ul> + </div> + </f:if> + </sg:related> + </f:if> </f:else> </f:if> </div>