diff --git a/Classes/Controller/AbstractController.php b/Classes/Controller/AbstractController.php index 7bb3121191c4ea4956ed8dc57c070f43f5de84e0..92a944cd75129a345e6e9ae3f341d7dfdb015c65 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($this->getFlattenedValidationErrorMessage()); } - /** - * 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 19b8628a71e5c5ccee01a91bbdbabb48b4272924..d6b07cc6727ea7061fac078a92a0404360b20bfd 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 * @@ -124,7 +130,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; @@ -161,4 +167,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 181e7d572dd2b694bd4c128c3624131092c82181..184c8c555f23a4c4ff3b1b7504a83707d185fb8c 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\Http\ImmediateResponseException; @@ -57,6 +58,11 @@ class ListByCategoryController extends AbstractController { */ protected $newsRepository; + /** + * @var NewsService + */ + protected $newsService; + /** * @param CategoryRepository $categoryRepository */ @@ -80,6 +86,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 * @@ -186,7 +199,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 7321064d68354b1d6750a5a989b5a299e45d471b..2e391648eda30c5046b4f3eb19d875916fbffa92 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. * @@ -130,7 +143,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 a5c802b2573cc6f6b2b2db2e099ffe9a3ebec314..5b002811c71f32d22e80395f42762c173c3515bd 100644 --- a/Classes/Controller/OverviewController.php +++ b/Classes/Controller/OverviewController.php @@ -28,16 +28,15 @@ namespace SGalinski\SgNews\Controller; use SGalinski\SgNews\Domain\Model\Category; 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\Domain\Service\NewsService; use SGalinski\SgNews\Service\ConfigurationService; use SGalinski\SgNews\Service\HeaderMetaDataService; use TYPO3\CMS\Core\Http\ImmediateResponseException; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; 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; @@ -62,11 +61,15 @@ class OverviewController extends AbstractController { */ protected $newsRepository; + /** + * @var NewsService + */ + protected $newsService; + /** * @param CategoryRepository $categoryRepository */ - public function injectCategoryRepository( - CategoryRepository $categoryRepository + public function injectCategoryRepository(CategoryRepository $categoryRepository ) { $this->categoryRepository = $categoryRepository; } @@ -85,15 +88,20 @@ 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 * * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentNameException */ public function initializeOverviewAction() { - $currentPageBrowserPage = GeneralUtility::_GP('tx_sgnews_pagebrowser') ? (int) GeneralUtility::_GP( - 'tx_sgnews_pagebrowser' - )['currentPage'] : 0; + $currentPageBrowserPage = (int) GeneralUtility::_GP('tx_sgnews_pagebrowser')['currentPage']; if ($currentPageBrowserPage > 0) { $this->request->setArgument('currentPageBrowserPage', $currentPageBrowserPage); } @@ -104,288 +112,171 @@ class OverviewController extends AbstractController { * * @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 - ): ?\Psr\Http\Message\ResponseInterface { - switch ((int) $this->settings['groupBy']) { - case 1: - $this->overviewWithCategories([], [], $newsFilter, $currentPageBrowserPage); - break; - case 2: - $this->overviewWithTags([], [], $newsFilter, $currentPageBrowserPage); - break; - default: - if (version_compare( - \TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(), '11.0.0', '<' - )) { - $this->forward('overviewWithoutCategories', NULL, NULL, $this->request->getArguments()); - } else { - return (new \TYPO3\CMS\Extbase\Http\ForwardResponse( - 'overviewWithoutCategories' - ))->withControllerName('Record') - ->withExtensionName('Extension') - ->withArguments($this->request->getArguments()); - } - break; - } - - if (version_compare(\TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(), '11.0.0', '<')) { - return NULL; - } else { - return $this->htmlResponse(); - } - } - - /** - * 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 = 0; - if (isset($this->settings['starttime'])) { - $startTime = (int) $this->settings['starttime']; + 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()); } - $endTime = 0; - if (isset($this->settings['endtime'])) { - $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->getMetaDataForNews($highlightedNews, $category); - } + // 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; - if (!version_compare(ExtensionManagementUtility::getExtensionVersion('sg_seo'), '5.0.0', '>=')) { - if (isset($highlightedNewsMetaData['imageObject'])) { - HeaderMetaDataService::addOgImageObjectToHeader( - $highlightedNewsMetaData['imageObject'] - ? $highlightedNewsMetaData['imageObject']->getOriginalResource() - : NULL - ); - } elseif (isset($highlightedNewsMetaData['teaserImageObject'])) { - HeaderMetaDataService::addOgImageObjectToHeader( - $highlightedNewsMetaData['teaserImageObject'] - ? $highlightedNewsMetaData['teaserImageObject']->getOriginalResource() - : NULL - ); - } + // Get tag pid + $tagPid = (int) $this->settings['tagPid']; + if (!$tagPid) { + $tagPid = $GLOBALS['TSFE']->id; } - } - - /** - * Renders the news overview grouped by categories - * - * @param array $newsByCategory - * @param array $allNews - * @param array $newsFilter - * @param int $currentPageBrowserPage - * @return void - * @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 - ) { - $newsLimitPerCategory = (int) $this->settings['newsLimit']; - $this->categoryRepository->setDefaultOrderings(['sorting' => Query::ORDER_ASCENDING]); - - $offset = $this->calculatePaginationOffset($currentPageBrowserPage, $newsLimitPerCategory); 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(); } - if (count($categories) <= 0) { - return; + // 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(); - if (isset($this->settings['categoryRestrictions'])) { - $categoryRestrictions = GeneralUtility::intExplode(',', $this->settings['categoryRestrictions'], TRUE); - 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(); - } + // 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]); - } + if (!in_array($categoryId, $categoryRestrictions, TRUE) && + !in_array($categoryIdTranslated, $categoryRestrictions, TRUE) + ) { + unset($categories[$key]); } } } - if (count($categories) <= 0) { - return; - } - - $startTime = 0; - if (isset($this->settings['starttime'])) { - $startTime = (int) $this->settings['starttime']; - } - - $endTime = 0; - if (isset($this->settings['endtime'])) { - $endTime = (int) $this->settings['endtime']; + // Apply tag restrictions + $tagRestrictions = GeneralUtility::intExplode(',', $this->settings['tagRestrictions'], TRUE); + if (!$isTagFiltered) { + $tagRestrictions = []; } - - $configurationService = GeneralUtility::makeInstance(ConfigurationService::class); - $sortBy = $configurationService->getConfiguration('sortBy', $this->settings); - $sortDirection = $configurationService->getConfiguration('sortDirection', $this->settings); - - $categoryIds = []; - $categoriesById = []; - $newsMetaData = []; - foreach ($categories as $category) { - /** @var Category $category */ - $categoryId = $category->getUid(); - $categoryIdsForSelect = [$categoryId]; - $categoryIds[] = $category->getUid(); - $categoriesById[$categoryId] = $category; - if ($category->_getProperty('_languageUid') > 0) { - $originalLangCategory = $this->categoryRepository->findOriginalLanguageById($category->getUid()); - if ($originalLangCategory) { - $originalLangCategoryId = $originalLangCategory->getUid(); - $categoryIdsForSelect[] = $originalLangCategoryId; - $categoryIds[] = $originalLangCategoryId; - $categoriesById[$originalLangCategoryId] = $originalLangCategory; - } - } - - $tagIds = NULL; - if (isset($newsFilter['tag']) && $newsFilter['tag']) { - $tagIds = [(int) $newsFilter['tag']]; - } - foreach ($categoryIdsForSelect as $categoryIdsForSelectId) { - $news = $this->newsRepository->findAllSortedNewsByCategories( - [$categoryIdsForSelectId], - $newsLimitPerCategory, - $offset, - $sortBy, - $tagIds, - $startTime, - $endTime, - $sortDirection - ); - - $newsMetaData[$categoryIdsForSelectId] = []; - foreach ($news as $newsEntry) { - /** @var News $newsEntry */ - $category = $categoriesById[$categoryIdsForSelectId]; - if (!$category) { - // Category isn't visible. - continue; - } - - $data = $this->getMetaDataForNews($newsEntry, $category); - $newsMetaData[$categoryIdsForSelectId][] = $data; + if (count($tagRestrictions) > 0) { + foreach ($tags as $key => $tag) { + if (!in_array($tag->getUid(), $tagRestrictions, TRUE)) { + unset($tags[$key]); } } } - $maxNewsPerCategory = 0; - foreach ($categoriesById as $categoryId => $category) { - if (!isset($newsMetaData[$categoryId]) || count($newsMetaData[$categoryId]) <= 0) { - // Hide empty categories. - continue; - } - - /** @var Category $category */ - if (isset($newsByCategory[$categoryId])) { - /** @var Category $category */ - $newsByCategory[$categoryId]['newsMetaData'] = - array_merge($newsByCategory[$categoryId]['newsMetaData'], $newsMetaData[$categoryId], $newsFilter); - } else { - $newsByCategory[$categoryId] = [ - 'record' => $category, - 'recordId' => $categoryId, - 'recordType' => 'category', - 'newsMetaData' => $newsMetaData[$categoryId], - 'newsCount' => count($newsMetaData[$categoryId]) - ]; + // 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(); } - - $maxNewsPerCategory = \max($maxNewsPerCategory, \count($newsByCategory[$categoryId]['newsMetaData'])); + } else { + $categoryIds = NULL; } - $tagIds = NULL; - if (isset($newsFilter['tag']) && $newsFilter['tag']) { + // 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; } - if ($newsFilter['category']) { - $categoryIds = [(int) $newsFilter['category']]; - } + + // Get all news by category and tag ids $news = $this->newsRepository->findAllSortedNewsByCategories( - $categoryIds, - $newsLimitPerCategory, - $offset, - $sortBy, - $tagIds, - $startTime, - $endTime, - $sortDirection + $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 */ - $categoryId = $newsEntry->getPid(); - $category = $categoriesById[$categoryId]; - if (!$category) { - // Category isn't visible. - continue; + $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; + } + } } - $data = $this->getMetaDataForNews($newsEntry, $category); - $allNews[] = $data; + $allNews[] = $newsMetaData; } - $this->highlightBestFitNews($categoryIds); + $this->highlightBestFitNews($categoryIds, $tagIds); + // Check to achieve less Ajax calls. $newsCount = $this->newsRepository->newsCountByCategories($categoryIds, $tagIds, $startTime, $endTime); - $numberOfPages = (int) ($newsLimitPerCategory <= 0 ? 0 : ceil($newsCount / $newsLimitPerCategory)); + $numberOfPages = (int) ($newsLimit <= 0 ? 0 : ceil($newsCount / $newsLimit)); if ($numberOfPages !== 0 && $currentPageBrowserPage >= $numberOfPages) { /** @var ErrorController $errorController */ $errorController = GeneralUtility::makeInstance(ErrorController::class); @@ -397,214 +288,94 @@ class OverviewController extends AbstractController { throw new ImmediateResponseException($response); } - // find all tags - $tagPid = $GLOBALS['TSFE']->id; - if ($this->settings['onlyNewsWithinThisPageSection']) { - $tags = $this->tagRepository->findTagsInRootLine($tagPid); - } else { - $tags = $this->tagRepository->findAll()->toArray(); - } - // remember selection of the filter values, if any - $selectedTag = NULL; - if (isset($newsFilter['tag'])) { - $selectedTag = $this->tagRepository->findByUid((int) $newsFilter['tag']); - } - $selectedCategory = NULL; - if(isset($newsFilter['category'])) { - $selectedCategory = $this->categoryRepository->findByUid((int) $newsFilter['category']); - } + $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', $newsByCategory); - $this->view->assign('groupBy', 'category'); + $this->view->assign('newsItems', $newsItems); $this->view->assign('allNews', $allNews); - $this->view->assign('categoryTabs', TRUE); + $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'); + } } /** - * Renders the news overview grouped by tags + * Assign the grid column classes to the frontend * - * @param array $newsByTag - * @param array $allNews - * @param array $newsFilter - * @param int $currentPageBrowserPage * @return void - * @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 - ) { - $startTime = (int) $this->settings['starttime']; - $endTime = (int) $this->settings['endtime']; - $newsLimitPerTag = (int) $this->settings['newsLimit']; - $offset = $this->calculatePaginationOffset($currentPageBrowserPage, $newsLimitPerTag); - $tagPid = (int) $this->settings['tagPid']; - if (!$tagPid) { - $tagPid = $GLOBALS['TSFE']->id; + protected function setupGridColumns(): void { + if (!isset($this->settings['gridColumns'])) { + $this->view->assign('gridColumnClasses', 'col-md-4 col-sm-6 col-xs-12'); + return; } - $configurationService = GeneralUtility::makeInstance(ConfigurationService::class); - $sortBy = $configurationService->getConfiguration('sortBy', $this->settings); - $sortDirection = $configurationService->getConfiguration('sortDirection', $this->settings); + $columnAmount = (int) $this->settings['gridColumns']; + $columnClasses = ''; - $categoryIds = NULL; - if ($newsFilter['category']) { - $categoryIds = [(int) $newsFilter['category']]; - } - - $this->tagRepository->setDefaultOrderings(['sorting' => QueryInterface::ORDER_ASCENDING]); - if ($this->settings['onlyNewsWithinThisPageSection']) { - $tags = $this->tagRepository->findTagsInRootLine($tagPid); - } else { - $tags = $this->tagRepository->findAll()->toArray(); + 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'; } - $tagRestrictions = GeneralUtility::intExplode(',', $this->settings['tagRestrictions'], TRUE); - if (count($tagRestrictions) > 0) { - foreach ($tags as $key => $tag) { - if (!in_array($tag->getUid(), $tagRestrictions, TRUE)) { - unset($tags[$key]); - } - } - } + $this->view->assign('gridColumnClasses', $columnClasses); + } - // Get news by tag id - $tagIds = []; - $tagsById = []; - $categoriesById = []; - $newsMetaData = []; - foreach ($tags as $tag) { - /** @var Tag $tag */ - $tagId = $tag->getUid(); - $tagIds[] = $tagId; - $tagsById[$tagId] = $tag; - - $news = $this->newsRepository->findAllSortedNewsByCategories( - $categoryIds, - $newsLimitPerTag, - $offset, - $sortBy, - [$tagId], - $startTime, - $endTime, - $sortDirection - ); - $newsMetaData[$tagId] = []; - foreach ($news as $newsEntry) { - /** @var News $newsEntry */ - $categoryId = $newsEntry->getPid(); - if (!isset($categoriesById[$categoryId])) { - $categoriesById[$categoryId] = $this->categoryRepository->findByUid($categoryId); - } - $category = $categoriesById[$categoryId]; - if (!$category) { - // Category isn't visible. - continue; - } + /** + * 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']; - $newsMetaData[$tagId][] = $this->getMetaDataForNews($newsEntry, $category); - } + /** @var News $highlightedNews */ + $highlightedNews = $this->newsRepository + ->findLastUpdatedOrHighlightedNewsByCategories( + 1, FALSE, $categoryIds, 0, FALSE, $this->settings['sortBy'], $tagIds, $startTime, $endTime + )->getFirst(); + if (!$highlightedNews) { + return; } - // Process news by tag id - $maxNewsPerTag = 0; - foreach ($tagsById as $tagId => $tag) { - if (\count($newsMetaData[$tagId]) <= 0) { - // Hide empty tags. - continue; - } - - if (isset($newsByTag[$tagId])) { - $newsByTag[$tagId]['newsMetaData'] = - \array_merge($newsByTag[$tagId]['newsMetaData'], $newsMetaData[$tagId]); - } else { - $newsByTag[$tagId] = [ - 'record' => $tag, - 'recordId' => $tagId, - 'recordType' => 'tag', - 'newsMetaData' => $newsMetaData[$tagId], - 'newsCount' => \count($newsMetaData[$tagId]) - ]; - } - - $maxNewsPerTag = \max($maxNewsPerTag, \count($newsByTag[$tagId]['newsMetaData'])); + /** @var Category $category */ + $category = $this->categoryRepository->findByUid($highlightedNews->getPid()); + $highlightedNewsMetaData = NULL; + if ($category) { + $highlightedNewsMetaData = $this->newsService->getMetaDataForNews($highlightedNews, $category); } - // Get all news by tags. - if ($newsFilter['tag']) { - $tagIds = [(int) $newsFilter['tag']]; - } - $news = $this->newsRepository->findAllSortedNewsByCategories( - $categoryIds, - $newsLimitPerTag, - $offset, - $sortBy, - $tagIds, - $startTime, - $endTime, - $sortDirection - ); - foreach ($news as $newsEntry) { - /** @var News $newsEntry */ - $categoryId = $newsEntry->getPid(); - if (!isset($categoriesById[$categoryId])) { - $categoriesById[$categoryId] = $this->categoryRepository->findByUid($categoryId); - } - /** @var Category $category */ - $category = $categoriesById[$categoryId]; - if (!$category) { - // Category isn't visible. - continue; + if (!version_compare(ExtensionManagementUtility::getExtensionVersion('sg_seo'), '5.0.0', '>=')) { + if ($highlightedNewsMetaData['image']) { + HeaderMetaDataService::addOgImageToHeader($highlightedNewsMetaData['image']); + } elseif ($highlightedNewsMetaData['teaserImage']) { + HeaderMetaDataService::addOgImageToHeader($highlightedNewsMetaData['teaserImage']); } - - $allNews[] = $this->getMetaDataForNews($newsEntry, $category); - } - - $this->highlightBestFitNews([], $tagIds); - - // Check to achieve less Ajax calls. - $newsCount = $this->newsRepository->newsCountByCategories([], $tagIds, $startTime, $endTime); - $numberOfPages = (int) ($newsLimitPerTag <= 0 ? 0 : ceil($newsCount / $newsLimitPerTag)); - 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); - } - - if ($this->settings['onlyNewsWithinThisPageSection']) { - $categories = $this->categoryRepository->findCategoriesInRootLine($GLOBALS['TSFE']->id); - } else { - $categories = $this->categoryRepository->findAll()->toArray(); } - - // 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', $newsByTag); - $this->view->assign('groupBy', 'tag'); - $this->view->assign('allNews', $allNews); - $this->view->assign('tagTabs', TRUE); } /** @@ -625,17 +396,15 @@ class OverviewController extends AbstractController { * @param array $newsMetaData * @param array|null $newsFilter * @param int $currentPageBrowserPage - * @return null|\Psr\Http\Message\ResponseInterface + * @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 - ): ?\Psr\Http\Message\ResponseInterface { + 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']); @@ -696,13 +465,7 @@ class OverviewController extends AbstractController { } if ($newsCount <= 0) { - if (version_compare( - \TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(), '11.0.0', '<' - )) { - return NULL; - } else { - return $this->htmlResponse(); - } + return; } // filter by category and tag if selected in the filter @@ -716,18 +479,11 @@ class OverviewController extends AbstractController { } $news = $this->newsRepository->findAllSortedNewsByCategories( - $categoryIds, - $newsPerPage, - $offset, - $sortBy, - $tagIds, - $startTime, - $endTime, - $sortDirection + $categoryIds, $newsPerPage, $offset, $sortBy, $tagIds, $startTime, $endTime, $sortDirection ); foreach ($news as $newsEntry) { /** @var News $newsEntry */ - $data = $this->getMetaDataForNews($newsEntry, $categoriesById[$newsEntry->getPid()]); + $data = $this->newsService->getMetaDataForNews($newsEntry, $categoriesById[$newsEntry->getPid()]); $newsMetaData[] = $data; } @@ -756,11 +512,7 @@ class OverviewController extends AbstractController { $this->view->assign('categories', $categories); $this->view->assign('numberOfPages', $numberOfPages); $this->view->assign('newsMetaData', $newsMetaData); - if (version_compare(\TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(), '11.0.0', '<')) { - return NULL; - } else { - return $this->htmlResponse(); - } + $this->setupGridColumns(); } /** diff --git a/Classes/Controller/SingleViewController.php b/Classes/Controller/SingleViewController.php index d65d6834ca0bf6415d9fe02926f51136abf35bff..d05abdf9b1cb7fd764fff8f7ea0725c701b56a72 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 * @@ -91,7 +97,7 @@ class SingleViewController extends AbstractController { } } - $newsMetaData = $this->getMetaDataForNews($news, $newsCategory); + $newsMetaData = $this->newsService->getMetaDataForNews($news, $newsCategory); if (!version_compare(ExtensionManagementUtility::getExtensionVersion('sg_seo'), '5.0.0', '>=')) { if (isset($newsMetaData['imageObject'])) { HeaderMetaDataService::addOgImageObjectToHeader( @@ -158,4 +164,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/Model/News.php b/Classes/Domain/Model/News.php index f6e06c12d0c02c5a76e2658b1778f2fedc2b8c35..d042f4b004717147ad9e35f700652254dc8786b1 100644 --- a/Classes/Domain/Model/News.php +++ b/Classes/Domain/Model/News.php @@ -97,14 +97,19 @@ class News extends CategoryAndNews { */ protected $contentFromAnotherPage = 0; + /** + * @var bool + */ + protected $enableComments = TRUE; + /** * Constructor */ public function __construct() { parent::__construct(); - $this->relatedNews = new ObjectStorage(); $this->tags = new ObjectStorage(); $this->newsAuthor = new ObjectStorage(); + $this->relatedNews = new ObjectStorage(); } /** @@ -383,4 +388,18 @@ class News extends CategoryAndNews { public function setContentFromAnotherPage(int $contentFromAnotherPage): void { $this->contentFromAnotherPage = $contentFromAnotherPage; } + + /** + * @return bool + */ + public function getEnableComments(): bool { + return $this->enableComments; + } + + /** + * @param bool $enableComments + */ + public function setEnableComments(bool $enableComments): void { + $this->enableComments = $enableComments; + } } diff --git a/Classes/Domain/Repository/NewsRepository.php b/Classes/Domain/Repository/NewsRepository.php index d7c474bdc9b0e6c6d6273b1c3df282a7b3f71fb7..216bd862376f686dd833c08e4de5d1bfd77f47b3 100644 --- a/Classes/Domain/Repository/NewsRepository.php +++ b/Classes/Domain/Repository/NewsRepository.php @@ -26,8 +26,10 @@ namespace SGalinski\SgNews\Domain\Repository; * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ -use SGalinski\SgNews\Domain\Model\Author; use SGalinski\SgNews\Domain\Model\News; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\Generic\QueryResult; use TYPO3\CMS\Extbase\Persistence\QueryInterface; use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; @@ -533,4 +535,102 @@ class NewsRepository extends AbstractRepository { $query->matching($query->equals('uid', $uid)); return $query->execute()->getFirst(); } + + /** + * This method finds news related by Tag or Category to the given news record + * + * @param News $news The news to find related news to + * @param int $limit Limit the amount of related news + * @return QueryResultInterface + * @throws \Doctrine\DBAL\Driver\Exception + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + public function findRelated(News $news, int $limit = 0): QueryResultInterface { + $connection = $this->getConnection(); + $qb = $connection->createQueryBuilder(); + // We need to build the constraint for the tags/categories + $constraints = []; + $tags = $news->getTags(); + if ($tags->count() > 0) { + $qb->leftJoin('pages', 'sys_category_record_mm', 'tags', 'pages.uid=tags.uid_foreign'); + $tagConstraints = []; + foreach ($tags as $tag) { + $tagConstraints[] = $qb->expr()->eq('tags.uid_local', $tag->getUid()); + } + $constraints[] = $qb->expr()->eq('tags.tablenames', $qb->createNamedParameter('pages')); + $constraints[] = $qb->expr()->eq('tags.fieldname', $qb->createNamedParameter('tx_sgnews_tags')); + $constraints[] = $qb->expr()->orX(...$tagConstraints); + } else { + $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', 'pages') + ->where( + $qb->expr()->eq('doktype', $qb->createNamedParameter(News::DOK_TYPE_NEWS, Connection::PARAM_INT)), + $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->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([ + 'lastUpdated' => QueryInterface::ORDER_DESCENDING + ]); + $constraints = [ + $query->logicalNot( + $query->equals('uid', $news->getUid()) + ) + ]; + if ($newest) { + $constraints[] = $query->lessThanOrEqual('lastUpdated', $newest); + } + if ($oldest) { + $constraints[] = $query->greaterThanOrEqual('lastUpdated', $oldest); + } + + // Now we fetch the $limit amount of news via extbase query and limit them to the newest and oldest date + // remember, we fetched the oldest and newest date from the $limit amount of news newer and older then + // the given news. If we limit the result of the following query to $limit, we get $limit amount of news + // "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) { + $tagConstraints[] = $query->contains('tags', $tag); + } + + $constraints[] = $query->logicalOr($tagConstraints); + } else { + $constraints[] = $query->equals('pid', $news->getPid()); + } + + $query->matching($query->logicalAnd($constraints)); + if ($limit > 0) { + $query->setLimit($limit); + } + + return $query->execute(); + } + + protected function getConnection(): Connection { + return GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable('pages'); + } } 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 new file mode 100644 index 0000000000000000000000000000000000000000..69b8e781d353728940d593b4788c082e934de247 --- /dev/null +++ b/Classes/ViewHelpers/RelatedViewHelper.php @@ -0,0 +1,120 @@ +<?php + +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; + +/** + * Renders related news based on the current news + */ +class RelatedViewHelper extends AbstractViewHelper { + use CompileWithRenderStatic; + + /** + * @var bool + */ + protected $escapeOutput = false; + + /** + * Initialize the view helper arguments + */ + public function initializeArguments() { + $this->registerArgument( + 'news', + 'SGalinski\SgNews\Domain\Model\News', + '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( + '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 + ); + } + + /** + * It works like the for-ViewHelper by running through the child content and adding the related news records to it + * + * @param array $arguments + * @param \Closure $renderChildrenClosure + * @param RenderingContextInterface $renderingContext + * @return string + * @throws \Doctrine\DBAL\Driver\Exception + * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException + */ + 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); + if ($arguments['relatedNews']) { + $related = $arguments['relatedNews']; + } else { + $related = $newsRepository->findRelated($news, (int) $arguments['limit']); + } + + if (isset($arguments['iteration'])) { + $iterationData = [ + 'index' => 0, + 'cycle' => 1, + 'total' => $related->count() + ]; + } + + $output = ''; + $categories = []; + foreach ($related 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']; + $iterationData['isEven'] = $iterationData['cycle'] % 2 === 0; + $iterationData['isOdd'] = !$iterationData['isEven']; + $templateVariableContainer->add($arguments['iteration'], $iterationData); + $iterationData['index']++; + $iterationData['cycle']++; + } + + $output .= $renderChildrenClosure(); + $templateVariableContainer->remove($arguments['as']); + if (isset($arguments['iteration'])) { + $templateVariableContainer->remove($arguments['iteration']); + } + } + + return $output; + } +} diff --git a/Configuration/Extbase/Persistence/Classes.php b/Configuration/Extbase/Persistence/Classes.php index cadba116249d6ee6fbed375b6fbdf39900b19986..8513d90f04b09ad8247980b88ed4fcf15c2cb14b 100644 --- a/Configuration/Extbase/Persistence/Classes.php +++ b/Configuration/Extbase/Persistence/Classes.php @@ -47,6 +47,9 @@ return [ ], 'dateEnd' => [ 'fieldName' => 'tx_sgnews_date_end' + ], + 'enableComments' => [ + 'fieldName' => 'tx_sgnews_comments_enable' ] ] ], diff --git a/Configuration/FlexForms/Overview.xml b/Configuration/FlexForms/Overview.xml index 3825dd256d5b310f69bfbeceb8fabfa9cf9b3fa3..10b59fda4f6bbbac6dcaddcc5f0e878460b3b5f2 100644 --- a/Configuration/FlexForms/Overview.xml +++ b/Configuration/FlexForms/Overview.xml @@ -13,6 +13,7 @@ <settings.groupBy> <TCEforms> <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.groupBy</label> + <description>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.groupBy.description</description> <onChange>reload</onChange> <config> <type>select</type> @@ -36,11 +37,28 @@ </TCEforms> </settings.groupBy> + <settings.enableFilter> + <TCEforms> + <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.enableFilter</label> + <description>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.enableFilter.description</description> + <onChange>reload</onChange> + <config> + <type>check</type> + <default>0</default> + </config> + </TCEforms> + </settings.enableFilter> + <settings.categoryRestrictions> <TCEforms> <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.categoryRestrictions</label> <description>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.categoryRestrictions.description</description> - <displayCond>FIELD:settings.groupBy:=:1</displayCond> + <displayCond> + <or> + <value1>FIELD:settings.groupBy:=:1</value1> + <value2>FIELD:settings.enableFilter:=:1</value2> + </or> + </displayCond> <config> <type>select</type> <renderType>selectMultipleSideBySide</renderType> @@ -57,7 +75,12 @@ <TCEforms> <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.tags</label> <description>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.tags.description</description> - <displayCond>FIELD:settings.groupBy:=:2</displayCond> + <displayCond> + <or> + <value1>FIELD:settings.groupBy:=:2</value1> + <value2>FIELD:settings.enableFilter:=:1</value2> + </or> + </displayCond> <config> <type>select</type> <renderType>selectTree</renderType> @@ -77,16 +100,6 @@ </TCEforms> </settings.tagRestrictions> - <settings.enableFilter> - <TCEforms> - <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.enableFilter</label> - <description>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.enableFilter.description</description> - <config> - <type>check</type> - <default>0</default> - </config> - </TCEforms> - </settings.enableFilter> <settings.categoryLabel> <TCEforms> <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.categoryLabel</label> @@ -98,11 +111,20 @@ <settings.tagLabel> <TCEforms> <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.tagLabel</label> + <description>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.tagLabel.description</description> <config> <type>input</type> </config> </TCEforms> </settings.tagLabel> + <settings.allLabel> + <TCEforms> + <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.allLabel</label> + <config> + <type>input</type> + </config> + </TCEforms> + </settings.allLabel> <settings.newsLimit> <TCEforms> <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.overview.flexForm.newsLimit</label> @@ -192,6 +214,53 @@ </config> </TCEforms> </settings.sortDirection> + <settings.layout> + <TCEforms> + <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.layout</label> + <config> + <type>select</type> + <renderType>selectSingle</renderType> + <items> + <numIndex index="0"> + <numIndex index="0">LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.layout.1</numIndex> + <numIndex index="1">1</numIndex> + </numIndex> + <numIndex index="1"> + <numIndex index="0">LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.layout.2</numIndex> + <numIndex index="1">2</numIndex> + </numIndex> + </items> + </config> + </TCEforms> + </settings.layout> + <settings.gridColumns> + <TCEforms> + <label>LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.gridColumns</label> + <config> + <type>select</type> + <renderType>selectSingle</renderType> + <default>3</default> + <items> + <numIndex index="0"> + <numIndex index="0">LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.gridColumns.1</numIndex> + <numIndex index="1">1</numIndex> + </numIndex> + <numIndex index="1"> + <numIndex index="0">LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.gridColumns.2</numIndex> + <numIndex index="1">2</numIndex> + </numIndex> + <numIndex index="2"> + <numIndex index="0">LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.gridColumns.3</numIndex> + <numIndex index="1">3</numIndex> + </numIndex> + <numIndex index="3"> + <numIndex index="0">LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:plugin.flexForm.gridColumns.4</numIndex> + <numIndex index="1">4</numIndex> + </numIndex> + </items> + </config> + </TCEforms> + </settings.gridColumns> </el> </ROOT> </main> diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php index 5a0f60d4c0a0138971216c3faaa831a5ba2ffad1..db490478170880bde2bba8c6b11230074f1f67ac 100644 --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -449,6 +449,14 @@ if (version_compare(\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getExten 'allowLanguageSynchronization' => TRUE ] ] + ], + 'tx_sgnews_comments_enable' => [ + 'exclude' => TRUE, + 'label' => $localLangDbPath . 'pages.tx_sgnews_comments_enable', + 'config' => [ + 'type' => 'check', + 'default' => 1 + ] ] ] ); @@ -460,7 +468,8 @@ $GLOBALS['TCA']['pages']['palettes']['titleDescriptionAndHighlightFlag'] = [ --linebreak--, slug, --linebreak--, tx_projectbase_path_segment, tx_projectbase_excludefromsluggeneration, --linebreak--, tx_realurl_pathsegment, tx_realurl_exclude, - --linebreak--, tx_sgnews_highlighted, tx_sgnews_never_highlighted', + --linebreak--, tx_sgnews_highlighted, tx_sgnews_never_highlighted, + --linebreak--, tx_sgnews_comments_enable', 'canNotCollapse' => 1, ]; @@ -496,6 +505,7 @@ foreach ($GLOBALS['TCA']['pages']['columns'] as $languageExcludeField => $_) { 'tx_sgnews_date_end', 'tx_sgnews_highlighted', 'tx_sgnews_never_highlighted', + 'tx_sgnews_comments_enable', 'og_title', 'og_description', 'og_image', @@ -527,6 +537,7 @@ foreach ($GLOBALS['TCA']['pages']['columns'] as $languageExcludeField => $_) { 'tx_sgnews_date_end', 'tx_sgnews_highlighted', 'tx_sgnews_never_highlighted', + 'tx_sgnews_comments_enable' ]; } if (!in_array($languageExcludeField, $fieldNames)) { 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/Language/de.locallang_db.xlf b/Resources/Private/Language/de.locallang_db.xlf index a2d5f49d314880b118eca77799f3d33e8e92dcdc..10d21c38c025ad2e9731b8d48b500ae8578ab2b9 100644 --- a/Resources/Private/Language/de.locallang_db.xlf +++ b/Resources/Private/Language/de.locallang_db.xlf @@ -81,6 +81,10 @@ <source><![CDATA[Use Image Cropping]]></source> <target><![CDATA[Bild-Cropping aktivieren]]></target> </trans-unit> + <trans-unit id="pages.tx_sgnews_comments_enable" approved="yes"> + <source><![CDATA[Enable comments on this page]]></source> + <target><![CDATA[Kommentare auf dieser Seite aktivieren]]></target> + </trans-unit> <trans-unit id="plugin.flexForm" approved="yes"> <source><![CDATA[Settings]]></source> <target><![CDATA[Einstellungen]]></target> @@ -109,6 +113,26 @@ <source><![CDATA[News which are excluded from the list]]></source> <target><![CDATA[News, welche nicht in der Liste dargestellt werden]]></target> </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns" approved="yes"> + <source><![CDATA[Columns]]></source> + <target><![CDATA[Anzahl der Spalten]]></target> + </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns.1" approved="yes"> + <source><![CDATA[1 Column]]></source> + <target><![CDATA[1 Spalte]]></target> + </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns.2" approved="yes"> + <source><![CDATA[2 Columns]]></source> + <target><![CDATA[2 Spalten]]></target> + </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns.3" approved="yes"> + <source><![CDATA[3 Columns]]></source> + <target><![CDATA[3 Spalten]]></target> + </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns.4" approved="yes"> + <source><![CDATA[4 Columns]]></source> + <target><![CDATA[4 Spalten]]></target> + </trans-unit> <trans-unit id="plugin.flexForm.layout" approved="yes"> <source><![CDATA[Layout]]></source> <target><![CDATA[Layout]]></target> @@ -189,6 +213,14 @@ <source><![CDATA[Categories]]></source> <target><![CDATA[Kategorien]]></target> </trans-unit> + <trans-unit id="plugin.overview.flexForm.allLabel" approved="yes"> + <source><![CDATA[Override all label]]></source> + <target><![CDATA[Alle-Label überschreiben]]></target> + </trans-unit> + <trans-unit id="plugin.overview.flexForm.allLabel.description" approved="yes"> + <source><![CDATA[Will be used when filtering by all criteria is disabled.]]></source> + <target><![CDATA[Wird nur bei nicht aktivierten Filtern von allen Kriterien verwendet.]]></target> + </trans-unit> <trans-unit id="plugin.overview.flexForm.categoryLabel" approved="yes"> <source><![CDATA[Override category label]]></source> <target><![CDATA[Kategorie-Label überschreiben]]></target> @@ -220,16 +252,20 @@ If none are selected, all categories will be available in the frontend.]]></sour Wenn keine ausgewählt werden, sind alle Kategorien im Frontend verfügbar.]]></target> </trans-unit> <trans-unit id="plugin.overview.flexForm.enableFilter" approved="yes"> - <source><![CDATA[Enable filtering by all criteria.]]></source> - <target><![CDATA[Ermögliche das Filtern mit allen Krierien.]]></target> + <source><![CDATA[Show all filters]]></source> + <target><![CDATA[Zeige alle Filter an]]></target> </trans-unit> <trans-unit id="plugin.overview.flexForm.enableFilter.description" approved="yes"> <source><![CDATA[Filters will be rendered as select boxes and tabs will be hidden.]]></source> <target><![CDATA[Filter werden als Dropdown-Box angezeigt und die Tabs ausgeblendet.]]></target> </trans-unit> <trans-unit id="plugin.overview.flexForm.groupBy" approved="yes"> - <source><![CDATA[Group news pages in tabs by]]></source> - <target><![CDATA[News-Seiten nach folgendem Kriterium in Tabs gruppieren]]></target> + <source><![CDATA[Group news pages by]]></source> + <target><![CDATA[News-Seiten nach folgendem Kriterium gruppieren]]></target> + </trans-unit> + <trans-unit id="plugin.overview.flexForm.groupBy.description" approved="yes"> + <source><![CDATA[News pages will be grouped in tabs.]]></source> + <target><![CDATA[News-Seiten werden in Tabs gruppiert.]]></target> </trans-unit> <trans-unit id="plugin.overview.flexForm.groupBy.I.0" approved="yes"> <source><![CDATA[No grouping]]></source> diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf index cc259e90a064d8df03944dd502232d342da356e6..4d42520c1b82b563b95e9ebf859d6c61489b0978 100644 --- a/Resources/Private/Language/locallang_db.xlf +++ b/Resources/Private/Language/locallang_db.xlf @@ -63,6 +63,9 @@ <trans-unit id="pages.tx_sgnews_use_image_crop"> <source><![CDATA[Use Image Cropping]]></source> </trans-unit> + <trans-unit id="pages.tx_sgnews_comments_enable"> + <source><![CDATA[Enable comments on this page]]></source> + </trans-unit> <trans-unit id="plugin.flexForm"> <source><![CDATA[Settings]]></source> </trans-unit> @@ -84,6 +87,21 @@ <trans-unit id="plugin.flexForm.excludedNews"> <source><![CDATA[News which are excluded from the list]]></source> </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns"> + <source><![CDATA[Columns]]></source> + </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns.1"> + <source><![CDATA[1 Column]]></source> + </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns.2"> + <source><![CDATA[2 Columns]]></source> + </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns.3"> + <source><![CDATA[3 Columns]]></source> + </trans-unit> + <trans-unit id="plugin.flexForm.gridColumns.4"> + <source><![CDATA[4 Columns]]></source> + </trans-unit> <trans-unit id="plugin.flexForm.layout"> <source><![CDATA[Layout]]></source> </trans-unit> @@ -156,6 +174,12 @@ <trans-unit id="plugin.overview.flexForm.categoryRestrictions"> <source><![CDATA[Category Selection]]></source> </trans-unit> + <trans-unit id="plugin.overview.flexForm.allLabel"> + <source><![CDATA[Override all label]]></source> + </trans-unit> + <trans-unit id="plugin.overview.flexForm.allLabel.description"> + <source><![CDATA[Will be used when filtering by all criteria is disabled.]]></source> + </trans-unit> <trans-unit id="plugin.overview.flexForm.categoryLabel"> <source><![CDATA[Override category label]]></source> </trans-unit> @@ -167,13 +191,16 @@ If none are selected, all categories will be available in the frontend.]]></source> </trans-unit> <trans-unit id="plugin.overview.flexForm.enableFilter"> - <source><![CDATA[Enable filtering by all criteria.]]></source> + <source><![CDATA[Show all filters]]></source> </trans-unit> <trans-unit id="plugin.overview.flexForm.enableFilter.description"> <source><![CDATA[Filters will be rendered as select boxes and tabs will be hidden.]]></source> </trans-unit> <trans-unit id="plugin.overview.flexForm.groupBy"> - <source><![CDATA[Group news pages in tabs by]]></source> + <source><![CDATA[Group news pages by]]></source> + </trans-unit> + <trans-unit id="plugin.overview.flexForm.groupBy.description"> + <source><![CDATA[News pages will be grouped in tabs.]]></source> </trans-unit> <trans-unit id="plugin.overview.flexForm.groupBy.I.0"> <source><![CDATA[No grouping]]></source> 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/Overview/Overview.html b/Resources/Private/Templates/Overview/Overview.html index 6735798bce05105076c454a3543a11d0c6e626cc..1538104f9f84b4923c4178dc4b71ea434922ad66 100644 --- a/Resources/Private/Templates/Overview/Overview.html +++ b/Resources/Private/Templates/Overview/Overview.html @@ -18,7 +18,14 @@ <div class="tx-sgnews-categories m-tabs" data-more-label="Mehr"> <div class="m-tabs__tablist" role="tablist"> <button class="m-tabs__tab" role="tab" aria-controls="news-category-0" id="tab-news-0" aria-selected="true"> - <f:translate key="frontend.overview.allTabLabel" /> + <f:if condition="{settings.allLabel}"> + <f:then> + {settings.allLabel} + </f:then> + <f:else> + <f:translate key="frontend.overview.allTabLabel" /> + </f:else> + </f:if> </button> <f:for each="{newsItems}" as="dataItems" iteration="iterator"> <button class="m-tabs__tab" role="tab" aria-controls="news-category-{iterator.index + 1}" id="tab-news-{iterator.index + 1}" aria-selected="false"> @@ -51,7 +58,7 @@ <ul class="tx-sgnews-list tx-sgnews-list-{dataItems.record.uid} row" data-record="{dataItems.record.uid}"> <f:for each="{dataItems.newsMetaData}" as="newsMetaDataEntry"> <f:if condition="{newsMetaDataEntry.news}"> - <li class="col-md-4 col-sm-6 col-xs-12"> + <li class="{gridColumnClasses}"> <f:render partial="Teaser" arguments="{ newsMetaData: newsMetaDataEntry, headerTag: '<h2>', @@ -85,7 +92,7 @@ <f:section name="content"> <ul class="tx-sgnews-list tx-sgnews-list-0 row" data-record="0"> <f:for each="{allNews}" as="newsMetaDataEntry"> - <li class="col-md-4 col-sm-6 col-xs-12"> + <li class="{gridColumnClasses}"> <f:render partial="Teaser" arguments="{ newsMetaData: newsMetaDataEntry, headerTag: '<h2>', diff --git a/Resources/Private/Templates/Overview/OverviewWithoutCategories.html b/Resources/Private/Templates/Overview/OverviewWithoutCategories.html index e41b2fc4b9b96bd21054ebb41c371f2b1bd5e5b7..f1839f91f8ac6ff3ec81ff96df463c8bd3b3d5c1 100644 --- a/Resources/Private/Templates/Overview/OverviewWithoutCategories.html +++ b/Resources/Private/Templates/Overview/OverviewWithoutCategories.html @@ -18,7 +18,7 @@ <f:then> <ul class="tx-sgnews-list tx-sgnews-list-0 row" data-record="0"> <f:for each="{newsMetaData}" as="newsMetaDataEntry"> - <li class="col-md-4 col-sm-6 col-xs-12"> + <li class="{gridColumnClasses}"> <f:render partial="Teaser" arguments="{ newsMetaData: newsMetaDataEntry, headerTag: '<h2>', diff --git a/Resources/Private/Templates/SingleView/SingleView.html b/Resources/Private/Templates/SingleView/SingleView.html index e41e216fb8066430e905713e216397888239bf95..bbbb26abdbc10e04953aebdaedf7bfccd65ae54a 100644 --- a/Resources/Private/Templates/SingleView/SingleView.html +++ b/Resources/Private/Templates/SingleView/SingleView.html @@ -1,4 +1,4 @@ -<f:layout name="Default" /> +<f:layout name="Default"/> {namespace base=SGalinski\ProjectBase\ViewHelpers} {namespace sg=SGalinski\SgNews\ViewHelpers} @@ -10,14 +10,15 @@ <div class="intro-section"> <section class="dark-bg"> - <div class="tx-sgnews-single-header {f:if(condition: newsMetaData.imageObject, then: 'tx-sgnews-single-header-image')}"> + <div + class="tx-sgnews-single-header {f:if(condition: newsMetaData.imageObject, then: 'tx-sgnews-single-header-image')}"> <f:if condition="{newsMetaData.imageObject}"> <div class="tx-sgnews-single-image"> <picture> <source media="(max-width: 1200px)" - srcset="{f:uri.image(src: newsMetaData.imageObject.uid, treatIdAsReference: '1', width: '1200c', height: '403c', cropVariant: 'small')}"> + srcset="{f:uri.image(src: newsMetaData.imageObject.uid, treatIdAsReference: '1', width: '1200c', height: '403c', cropVariant: 'small')}"> <f:image image="{newsMetaData.imageObject}" alt="" width="1845c" - height="619c" loading="lazy"/> + height="619c" loading="lazy"/> </picture> </div> </f:if> @@ -30,11 +31,15 @@ <div class="tx-sgnews-teaser-meta"> <f:if condition="{authorCount}"> <span class="author"> - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="#FFF" d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z" /></svg> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path + fill="#FFF" + d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"/></svg> </span> - <f:for each="{newsMetaData.news.newsAuthor}" as="newsAuthor" iteration="iterator"> - <f:alias map="{divider: '{f:if(condition: \'{iterator.cycle} < {authorCount}\', then: \',\')}'}"> + <f:for each="{newsMetaData.news.newsAuthor}" as="newsAuthor" + iteration="iterator"> + <f:alias + map="{divider: '{f:if(condition: \'{iterator.cycle} < {authorCount}\', then: \',\')}'}"> <a class="tx-sgnews-teaser-meta-author" href="#author{newsAuthor.uid}"> <f:if condition="{newsAuthor.name}"> {newsAuthor.name}{divider} @@ -46,26 +51,32 @@ <f:if condition="{newsMetaData.news.location}"> <span class="location"> - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="#FFF" d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z" /></svg> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path + fill="#FFF" + d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"/></svg> {newsMetaData.news.location} </span> </f:if> <span class="date"> - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFF" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm57.1 350.1L224.9 294c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h48c6.6 0 12 5.4 12 12v137.7l63.5 46.2c5.4 3.9 6.5 11.4 2.6 16.8l-28.2 38.8c-3.9 5.3-11.4 6.5-16.8 2.6z" /></svg> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFF" + d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm57.1 350.1L224.9 294c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h48c6.6 0 12 5.4 12 12v137.7l63.5 46.2c5.4 3.9 6.5 11.4 2.6 16.8l-28.2 38.8c-3.9 5.3-11.4 6.5-16.8 2.6z"/></svg> <f:format.date format="{f:translate(key:'frontend.dateformat')}">{newsMetaData.news.lastUpdated}</f:format.date> </span> <span class="category"> - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFF" d="M0 252.118V48C0 21.49 21.49 0 48 0h204.118a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882L293.823 497.941c-18.745 18.745-49.137 18.745-67.882 0L14.059 286.059A48 48 0 0 1 0 252.118zM112 64c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48z" /></svg> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFF" + d="M0 252.118V48C0 21.49 21.49 0 48 0h204.118a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882L293.823 497.941c-18.745 18.745-49.137 18.745-67.882 0L14.059 286.059A48 48 0 0 1 0 252.118zM112 64c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48z"/></svg> <a href="{f:uri.page(pageUid: '{newsMetaData.category.uid}')}"> {newsMetaData.category.subtitleWithFallbackToTitle} </a> </span> <f:if condition="{newsMetaData.news.tags}"> - <br /> + <br/> <span class="tags"> - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#FFF" d="M497.941 225.941L286.059 14.059A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v204.118a48 48 0 0 0 14.059 33.941l211.882 211.882c18.744 18.745 49.136 18.746 67.882 0l204.118-204.118c18.745-18.745 18.745-49.137 0-67.882zM112 160c-26.51 0-48-21.49-48-48s21.49-48 48-48 48 21.49 48 48-21.49 48-48 48zm513.941 133.823L421.823 497.941c-18.745 18.745-49.137 18.745-67.882 0l-.36-.36L527.64 323.522c16.999-16.999 26.36-39.6 26.36-63.64s-9.362-46.641-26.36-63.64L331.397 0h48.721a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882z" /></svg> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path + fill="#FFF" + d="M497.941 225.941L286.059 14.059A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v204.118a48 48 0 0 0 14.059 33.941l211.882 211.882c18.744 18.745 49.136 18.746 67.882 0l204.118-204.118c18.745-18.745 18.745-49.137 0-67.882zM112 160c-26.51 0-48-21.49-48-48s21.49-48 48-48 48 21.49 48 48-21.49 48-48 48zm513.941 133.823L421.823 497.941c-18.745 18.745-49.137 18.745-67.882 0l-.36-.36L527.64 323.522c16.999-16.999 26.36-39.6 26.36-63.64s-9.362-46.641-26.36-63.64L331.397 0h48.721a48 48 0 0 1 33.941 14.059l211.882 211.882c18.745 18.745 18.745 49.137 0 67.882z"/></svg> <f:for each="{newsMetaData.news.tags}" as="tag" iteration="it"> {tag.title}<f:if condition="{it.isLast}"><f:else>, </f:else></f:if> </f:for> @@ -77,20 +88,22 @@ </div> </div> </section> - </div> + <section class="content"> <div class="container"> <div class="tx-sgnews-single"> - <div class="tx-sgnews-single-container"> - <f:alias map="{content: '{f:cObject(typoscriptObjectPath: \'{f:if(condition: \\\'{newsMetaData.news.contentFromAnotherPage}\\\', then: \\\'lib.contentFromAnotherPage\\\', else: \\\'lib.mainContent\\\')}\')}'}"> + <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"> <p> - <a href="#comments">{f:cObject(typoscriptObjectPath: "lib.sgCommentsGetCountWithLabel")}</a> + <a href="#comments">{f:cObject(typoscriptObjectPath: + "lib.sgCommentsGetCountWithLabel")}</a> <span>//</span> - <f:translate key="frontend.singleview.readingTime" /> + <f:translate key="frontend.singleview.readingTime"/> <strong> - <sg:getReadingTime content="{content}" /> + <sg:getReadingTime content="{content}"/> </strong> </p> @@ -100,7 +113,7 @@ <f:if condition="{newsMetaData.news.newsAuthor -> f:count()}"> <h2> - <f:translate key="frontend.singleview.authors" /> + <f:translate key="frontend.singleview.authors"/> </h2> <hr> @@ -108,7 +121,8 @@ <f:for each="{newsMetaData.news.newsAuthor}" as="newsAuthor" iteration="iterator"> <div id="author{newsAuthor.uid}" class="tx-sgnews-author"> <f:if condition="{newsAuthor.image}"> - <f:image class="tx-sgnews-author-image" image="{newsAuthor.image}" width="150c" height="150px" /> + <f:image class="tx-sgnews-author-image" image="{newsAuthor.image}" width="150c" + height="150px"/> </f:if> <div class="tx-sgnews-author-information"> @@ -122,7 +136,8 @@ <div class="tx-sgnews-author-information-bar"> <f:if condition="{newsAuthor.email}"> <div class="tx-sgnews-author-information-email"> - <span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> + <span class="glyphicon glyphicon-envelope" + aria-hidden="true"></span> <f:link.email email="{newsAuthor.email}" target="_blank"> {newsAuthor.email} </f:link.email> @@ -131,7 +146,8 @@ <f:if condition="{newsAuthor.website}"> <div class="tx-sgnews-author-information-website"> - <span class="glyphicon glyphicon-home" aria-hidden="true"></span> + <span class="glyphicon glyphicon-home" + aria-hidden="true"></span> <f:link.external uri="{newsAuthor.website}" target="_blank"> {newsAuthor.website} </f:link.external> @@ -149,8 +165,10 @@ <f:if condition="{settings.authorNewsPid}"> <div class="tx-sgnews-author-information-articles"> <span class="glyphicon glyphicon-tag" aria-hidden="true"></span> - <f:link.action pageUid="{settings.authorNewsPid}" pluginName="NewsByAuthor" controller="NewsByAuthor" action="list" arguments="{authorId: newsAuthor.uid}"> - <f:translate key="frontend.singleview.authorsAdditionalArticles" /> + <f:link.action pageUid="{settings.authorNewsPid}" + pluginName="NewsByAuthor" controller="NewsByAuthor" + action="list" arguments="{authorId: newsAuthor.uid}"> + <f:translate key="frontend.singleview.authorsAdditionalArticles"/> </f:link.action> </div> </f:if> @@ -165,35 +183,67 @@ <hr> <div class="tx-sgnews-meta-bar"> - <base:sharer /> + <base:sharer/> <div class="tx-sgnews-likes" id="tx-sgnews-likes" data-uid="{newsMetaData.news.uid}"> - <button class="tx-sgnews-like-buton btn btn-sm btn-info" data-ajaxurl="{sgajax:uri.ajax(extensionName: 'SgNews', controller: 'Ajax\Like', action: 'addLike', format: 'json')}"> + <button class="tx-sgnews-like-buton btn btn-sm btn-info" + data-ajaxurl="{sgajax:uri.ajax(extensionName: 'SgNews', controller: 'Ajax\Like', action: 'addLike', format: 'json')}"> <span class="tx-sgnews-number-of-likes"> <span class="badge"><i class="fa fa-star-o" aria-hidden="true"></i> <span class="tx-sgnews-number-of-likes-value">{newsMetaData.news.likes}</span> </span> </span> - <f:translate key="frontend.singleview.likeButton" /> + <f:translate key="frontend.singleview.likeButton"/> </button> </div> </div> <f:if condition="{newsMetaData.news.relatedNews}"> - <div class="tx-sgnews-single-related"> - <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> - </li> - </f:for> - </ul> - </div> + <f:then> + <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"> + <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> + </sg:related> + </ul> + </div> + </f:then> + <f:else> + <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> </div> @@ -206,16 +256,24 @@ <f:if condition="{previousNews} || {nextNews}"> <div class="tx-sgnews-single-footer-browser"> <f:if condition="{previousNews}"> - <a href="{f:uri.page(pageUid: '{previousNews.uid}')}" class="btn btn-md btn-info tx-sgnews-footer-browser-previous"> - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFF" d="M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248-111 248-248 248zM142.1 273l135.5 135.5c9.4 9.4 24.6 9.4 33.9 0l17-17c9.4-9.4 9.4-24.6 0-33.9L226.9 256l101.6-101.6c9.4-9.4 9.4-24.6 0-33.9l-17-17c-9.4-9.4-24.6-9.4-33.9 0L142.1 239c-9.4 9.4-9.4 24.6 0 34z" /></svg> - <f:translate key="frontend.singleview.previousArticle" /> + <a href="{f:uri.page(pageUid: '{previousNews.uid}')}" + class="btn btn-md btn-info tx-sgnews-footer-browser-previous"> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <path fill="#FFF" + d="M256 504C119 504 8 393 8 256S119 8 256 8s248 111 248 248-111 248-248 248zM142.1 273l135.5 135.5c9.4 9.4 24.6 9.4 33.9 0l17-17c9.4-9.4 9.4-24.6 0-33.9L226.9 256l101.6-101.6c9.4-9.4 9.4-24.6 0-33.9l-17-17c-9.4-9.4-24.6-9.4-33.9 0L142.1 239c-9.4 9.4-9.4 24.6 0 34z"/> + </svg> + <f:translate key="frontend.singleview.previousArticle"/> </a> </f:if> <f:if condition="{nextNews}"> - <a href="{f:uri.page(pageUid: '{nextNews.uid}')}" class="btn btn-md btn-info tx-sgnews-footer-browser-next"> - <f:translate key="frontend.singleview.nextArticle" /> - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFF" d="M256 8c137 0 248 111 248 248S393 504 256 504 8 393 8 256 119 8 256 8zm113.9 231L234.4 103.5c-9.4-9.4-24.6-9.4-33.9 0l-17 17c-9.4 9.4-9.4 24.6 0 33.9L285.1 256 183.5 357.6c-9.4 9.4-9.4 24.6 0 33.9l17 17c9.4 9.4 24.6 9.4 33.9 0L369.9 273c9.4-9.4 9.4-24.6 0-34z" /></svg> + <a href="{f:uri.page(pageUid: '{nextNews.uid}')}" + class="btn btn-md btn-info tx-sgnews-footer-browser-next"> + <f:translate key="frontend.singleview.nextArticle"/> + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <path fill="#FFF" + d="M256 8c137 0 248 111 248 248S393 504 256 504 8 393 8 256 119 8 256 8zm113.9 231L234.4 103.5c-9.4-9.4-24.6-9.4-33.9 0l-17 17c-9.4 9.4-9.4 24.6 0 33.9L285.1 256 183.5 357.6c-9.4 9.4-9.4 24.6 0 33.9l17 17c9.4 9.4 24.6 9.4 33.9 0L369.9 273c9.4-9.4 9.4-24.6 0-34z"/> + </svg> </a> </f:if> </div> @@ -224,14 +282,16 @@ </div> </section> - <section class="content"> - <div class="container"> - <div class="tx-sgnews-single-comments"> - <div id="comments" class="tx-sgnews-comments-inner"> - <f:cObject typoscriptObjectPath="lib.sgCommentsIndex" /> + <f:if condition="{newsMetaData.news.enableComments}"> + <section class="content"> + <div class="container"> + <div class="tx-sgnews-single-comments"> + <div id="comments" class="tx-sgnews-comments-inner"> + <f:cObject typoscriptObjectPath="lib.sgCommentsIndex"/> + </div> </div> </div> - </div> - </section> + </section> + </f:if> </f:alias> </f:section> diff --git a/Resources/Public/Sass/_sg-news.scss b/Resources/Public/Sass/_sg-news.scss index f90f33c0dffaa070dc7bd6e43c1e650f57b1ac71..63843a397acbc4a3091e0e2d072b42f4c2105843 100644 --- a/Resources/Public/Sass/_sg-news.scss +++ b/Resources/Public/Sass/_sg-news.scss @@ -1,11 +1,5 @@ @import "layout-content"; -// sg_news SingleView pages have two intro-sections, -// so we need to remove the duplicate white space created by the first one -.intro-section + .main-content .tx-sgnews { - margin-top: $margin-base-vertical * -2; -} - // reduce the gap between the .dark-bg and the comment section .tx-sgnews .content.dark-bg + .content { margin-top: $margin-base-vertical * -1; diff --git a/composer.json b/composer.json index be7a715926154138e4c318990a6f41f42738279c..8ced56d988cfe6df39a70134a45783213c2a8151 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "license": [ "GPL-2.0-or-later" ], - "version": "9.6.3", + "version": "9.8.3", "support": { }, "repositories": [ diff --git a/ext_emconf.php b/ext_emconf.php index c50566f6a4cd7035a8942ba88313927dce7fe4b1..c02387d4a7ad7e4f0874a0f223a30748f9d8d46c 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -19,7 +19,7 @@ $EM_CONF['sg_news'] = [ 'modify_tables' => '', 'clearCacheOnLoad' => 0, 'lockType' => '', - 'version' => '9.6.3', + 'version' => '9.8.3', 'constraints' => [ 'depends' => [ 'typo3' => '10.4.0-11.5.99', diff --git a/ext_tables.sql b/ext_tables.sql index cf5da4c80990185b37ed49d6cb7afb4664e7e346..82c2a724a4ac75c867450c6e421a505080acd1cf 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -10,6 +10,7 @@ CREATE TABLE pages ( tx_sgnews_location mediumtext, tx_sgnews_date_end int(10) unsigned DEFAULT '0' NOT NULL, tx_sgnews_content_from_another_page int(11) unsigned DEFAULT '0' NOT NULL, + tx_sgnews_comments_enable tinyint(4) unsigned DEFAULT '1' NOT NULL, KEY news_author (tx_sgnews_news_author) );