Skip to content
Snippets Groups Projects
BackendNewsUtility.php 18.9 KiB
Newer Older
Paul Ilea's avatar
Paul Ilea committed
<?php

namespace SGalinski\SgNews\Utility;
Paul Ilea's avatar
Paul Ilea committed

/***************************************************************
 *  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!
 ***************************************************************/

Paul Ilea's avatar
Paul Ilea committed
use SGalinski\SgNews\Domain\Model\News;
use SGalinski\SgNews\Domain\Model\Tag;
use SGalinski\SgNews\Domain\Repository\NewsRepository;
Paul Ilea's avatar
Paul Ilea committed
use SGalinski\SgNews\Domain\Repository\TagRepository;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
Paul Ilea's avatar
Paul Ilea committed
use TYPO3\CMS\Core\Database\QueryGenerator;
use TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider;
use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
use TYPO3\CMS\Core\Imaging\IconRegistry;
Paul Ilea's avatar
Paul Ilea committed
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;

/**
 * Class Utility
 */
class BackendNewsUtility {

	/**
	 * @var int The category page doktype
	 */
	const CATEGORY_DOKTYPE = 117;

	/**
	 * @var int The news page doktype
	 */
	const NEWS_DOKTYPE = 116;

	/**
	 * Retrieves the next site root in the page hierarchy from the current page
	 *
	 * @param int $currentPid
	 * @return int
	 */
	public static function getRootUidByPageUid($currentPid): int {
Paul Ilea's avatar
Paul Ilea committed
		$rootLine = BackendUtility::BEgetRootLine((int) $currentPid);
		$siteRoot = ['uid' => 0];

		foreach ($rootLine as $page) {
			if ((int) $page['is_siteroot'] === 1) {
				$siteRoot = $page;
				break;
			}
		}

		return (int) $siteRoot['uid'];
	}

	/**
	 * Get an array of alternative pages for the BE Module view
	 *
	 * @return array
	 * @throws \InvalidArgumentException
	 */
	public static function getAlternativePageOptions(): array {
Paul Ilea's avatar
Paul Ilea committed
		$options = [];
		$andWhere = ' AND sys_language_uid IN (0,-1)';
Paul Ilea's avatar
Paul Ilea committed
		/** @var array $rootOptionRows */
		$rootOptionRows = self::getRecordsByField(
			'pages', 'is_siteroot', 1, $andWhere, '', 'sorting'
Paul Ilea's avatar
Paul Ilea committed
		if ($rootOptionRows) {
			foreach ($rootOptionRows as $row) {
				$pageInfo = BackendUtility::readPageAccess($row['uid'], $GLOBALS['BE_USER']->getPagePermsClause(1));
				if ($pageInfo) {
					$options[] = self::getOptionPageInfo($pageInfo);
				}
Paul Ilea's avatar
Paul Ilea committed
				$categories = self::getCategoriesForSiteRoot((int) $row['uid']);
				/** @var int $categoryUid */
				foreach ($categories as $categoryUid => $categoryTitle) {
					if ((int) $pageInfo['uid'] !== $categoryUid) {
						$categoryPageInfo = BackendUtility::readPageAccess(
							$categoryUid, $GLOBALS['BE_USER']->getPagePermsClause(1)
						);
						if ($categoryPageInfo) {
							$options[] = self::getOptionPageInfo($categoryPageInfo);
						}
					}
				}
			}
		}
Paul Ilea's avatar
Paul Ilea committed
		return $options;
	}

	/**
	 * Get an array of alternative pages for the BE Module view
	 *
	 * @param array $pageInfo
	 * @return array
	 */
	private static function getOptionPageInfo(array $pageInfo = []): array {
Paul Ilea's avatar
Paul Ilea committed
		if (isset($pageInfo['uid']) && (int) $pageInfo['uid']) {
			$rootline = BackendUtility::BEgetRootLine($pageInfo['uid'], '', TRUE);
			ksort($rootline);
			$path = '/root';
			foreach ($rootline as $page) {
				$path .= '/p' . dechex($page['uid']);
			}
Paul Ilea's avatar
Paul Ilea committed
			$pageInfo['path'] = $path;
			$pageInfo['_thePathFull'] = substr($pageInfo['_thePathFull'], 1);
			$pageInfo['_thePathFull'] = substr($pageInfo['_thePathFull'], 0, -1);
		}
Paul Ilea's avatar
Paul Ilea committed
		return $pageInfo;
	}

	/**
	 * Get an array of all category uids => titles below the given site root id
	 *
	 * @param int $siteRootUid
	 * @return array
	 * @throws \InvalidArgumentException
	 */
	public static function getCategoriesForSiteRoot($siteRootUid): array {
Paul Ilea's avatar
Paul Ilea committed
		$siteRootUid = (int) $siteRootUid;
		// get all pageids below the given siteroot
		$queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
		$childPids = $queryGenerator->getTreeList(
			$siteRootUid, PHP_INT_MAX, 0, $GLOBALS['BE_USER']->getPagePermsClause(1)
		);

		$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
		$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
		$result = $queryBuilder->select('uid', 'title')
			->from('pages')
			->where(
				$queryBuilder->expr()->andX(
					$queryBuilder->expr()->eq('doktype', $queryBuilder->createNamedParameter(self::CATEGORY_DOKTYPE, \PDO::PARAM_INT)),
					$queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter(explode(',', $childPids), Connection::PARAM_INT_ARRAY))
				)
			)
			->execute()->fetchAll();
Paul Ilea's avatar
Paul Ilea committed
		$categories = [];
		foreach ($result as $page) {
			$categoryPageInfo = BackendUtility::readPageAccess(
				(int) $page['uid'], $GLOBALS['BE_USER']->getPagePermsClause(1)
			);
			if ($categoryPageInfo) {
				$categories[(int) $page['uid']] = $page['title'];
			}
		}

		return $categories;
	}

	/**
	 * Get an array of all tags uids => titles below the given site root id
	 *
	 * @param int $pageUid
	 * @param int $languageUid
	 * @return array
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
	 * @throws \InvalidArgumentException
	 */
	public static function getTagsForPage($pageUid, $languageUid = 0): array {
Paul Ilea's avatar
Paul Ilea committed
		$temporaryTSFEInstance = FALSE;
		if (!isset($GLOBALS['TSFE'])) {
			$temporaryTSFEInstance = TRUE;
			$GLOBALS['TSFE'] = new \stdClass();
			$GLOBALS['TSFE']->gr_list = '';
		}
Paul Ilea's avatar
Paul Ilea committed
		$pageUid = (int) $pageUid;
		$languageUid = (int) $languageUid;
		$tags = [];
		$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
		$tagRepository = $objectManager->get(TagRepository::class);
		$query = $tagRepository->createQuery();
		$querySettings = $query->getQuerySettings();
		$querySettings->setLanguageUid($languageUid);
		$querySettings->setLanguageOverlayMode(TRUE);
		$querySettings->setLanguageMode('content_fallback');
		$query->setQuerySettings($querySettings);
		if ($pageUid) {
			$rootline = BackendUtility::BEgetRootLine($pageUid, '', TRUE);
			$pageTS = BackendUtility::getPagesTSconfig($pageUid, $rootline);

			$tagsPid = 0;
			if (isset($pageTS['TCEFORM.']['pages.']['tx_sgnews_tags.']['PAGE_TSCONFIG_ID'])) {
				$tagsPid = (int) $pageTS['TCEFORM.']['pages.']['tx_sgnews_tags.']['PAGE_TSCONFIG_ID'];
			}
Paul Ilea's avatar
Paul Ilea committed
			if ($tagsPid) {
				$query->matching($query->equals('pid', $tagsPid));
			}
		}
Paul Ilea's avatar
Paul Ilea committed
		$query->setOrderings(['title' => QueryInterface::ORDER_ASCENDING]);
		$resultTags = $query->execute(TRUE);
		if ($temporaryTSFEInstance) {
			unset($GLOBALS['TSFE']);
		}
Paul Ilea's avatar
Paul Ilea committed
		foreach ($resultTags as $tag) {
			$tags[(int) $tag['uid']] = trim($tag['title']);
		}
Paul Ilea's avatar
Paul Ilea committed
		return $tags;
	}

Paul Ilea's avatar
Paul Ilea committed
	/**
	 * Get an array of all tags uids => titles for the specified news uid
	 *
	 * @param int $newsItemUid
	 * @param int $languageUid
	 * @return array
	 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException
	 * @throws \InvalidArgumentException
	 */
	public static function getTagsForNewsItem($newsItemUid, $languageUid = 0): array {
Paul Ilea's avatar
Paul Ilea committed
		$temporaryTSFEInstance = FALSE;
		if (!isset($GLOBALS['TSFE'])) {
			$temporaryTSFEInstance = TRUE;
			$GLOBALS['TSFE'] = new \stdClass();
			$GLOBALS['TSFE']->gr_list = '';
		}
Paul Ilea's avatar
Paul Ilea committed
		$newsItemUid = (int) $newsItemUid;
		$languageUid = (int) $languageUid;
		$tags = [];
		$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
		$newsRepository = $objectManager->get(NewsRepository::class);
		$query = $newsRepository->createQuery();
		$querySettings = $query->getQuerySettings();
		$querySettings->setLanguageUid($languageUid);
		$querySettings->setLanguageOverlayMode(TRUE);
		$querySettings->setLanguageMode('content_fallback');
		$query->setQuerySettings($querySettings);
		if ($newsItemUid) {
			$query->matching($query->equals('uid', $newsItemUid));
		}
Paul Ilea's avatar
Paul Ilea committed
		/** @var News $newsItem */
		$newsItem = $query->execute()->getFirst();
		if ($temporaryTSFEInstance) {
			unset($GLOBALS['TSFE']);
		}
Paul Ilea's avatar
Paul Ilea committed
		if ($newsItem) {
			/** @var Tag $tag */
			foreach ($newsItem->getTags() as $tag) {
				$tags[(int) $tag->getUid()] = trim($tag->getTitle());
			}
		}
Paul Ilea's avatar
Paul Ilea committed
		return $tags;
	}

Paul Ilea's avatar
Paul Ilea committed
	/**
	 * Get all news for the given categories (all if filter is empty)
	 *
	 * @param int $rootPageUid
	 * @param array $filters
	 * @param int $languageUid
	 * @return array
	 * @throws \InvalidArgumentException
	 */
	public static function getNewsByFilters($rootPageUid = 0, array $filters = [], $languageUid = 0): array {
Paul Ilea's avatar
Paul Ilea committed
		$out = [];
		$rootPageUid = (int) $rootPageUid;
		$languageUid = (int) $languageUid;
		if (!$rootPageUid) {
			return $out;
		}
Paul Ilea's avatar
Paul Ilea committed
		$categories = [];
		if (!isset($filters['categories']) || !is_array($filters['categories']) || !count($filters['categories'])) {
			$rootCategories = self::getCategoriesForSiteRoot($rootPageUid);
			foreach ($rootCategories as $categoryUid => $categoryTitle) {
				$categories[] = (int) $categoryUid;
			}
		} else {
			/** @var array $filterCategories */
			$filterCategories = $filters['categories'];
			foreach ($filterCategories as $categoryUid) {
Paul Ilea's avatar
Paul Ilea committed
				$categoryPageInfo = BackendUtility::readPageAccess(
					(int) $categoryUid, $GLOBALS['BE_USER']->getPagePermsClause(1)
				);
				if ($categoryPageInfo) {
					$categories[] = (int) $categoryUid;
				}
			}
		}
Paul Ilea's avatar
Paul Ilea committed
		if (!count($categories)) {
			return $out;
		}
Paul Ilea's avatar
Paul Ilea committed
		$queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
		$allowedUids = [];
		foreach ($categories as $categoryUid) {
			$allowedUidsTemp = GeneralUtility::intExplode(
				',', $queryGenerator->getTreeList(
				$categoryUid, 1, 0, $GLOBALS['BE_USER']->getPagePermsClause(1)
			), TRUE
			);
			$allowedUids = array_unique(array_merge($allowedUids, $allowedUidsTemp));
		}

		if (!count($allowedUids)) {
Paul Ilea's avatar
Paul Ilea committed
			return $out;
		}

		$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
		$deletedRestriction = GeneralUtility::makeInstance(DeletedRestriction::class);
		$queryBuilder = $connectionPool->getQueryBuilderForTable('pages');
		$queryBuilder->getRestrictions()->removeAll()->add($deletedRestriction);
		$queryBuilder->select('p.uid',
				'p.pid',
				'p.hidden',
				'p.sorting',
				'p.doktype',
				'p.title'
			)->from('pages', 'p')
			->where(
				$queryBuilder->expr()->andX(
					$queryBuilder->expr()->in('p.uid', $queryBuilder->createNamedParameter($allowedUids, Connection::PARAM_INT_ARRAY)),
					$queryBuilder->expr()->eq('p.doktype', $queryBuilder->createNamedParameter(self::NEWS_DOKTYPE, \PDO::PARAM_INT))
				)
			)
			->groupBy('p.uid')
			->orderBy('p.sorting');
Paul Ilea's avatar
Paul Ilea committed
		if ($languageUid) {
			$queryBuilder->leftJoin('p', 'pages', 'translation',
				$queryBuilder->expr()->andX(
					$queryBuilder->expr()->eq('translation.l10n_parent', 'p.uid'),
					$queryBuilder->expr()->eq('translation.sys_language_uid', $queryBuilder->createNamedParameter($languageUid, \PDO::PARAM_INT))
				)
			);
Paul Ilea's avatar
Paul Ilea committed
		}
		if (isset($filters['tags']) && is_array($filters['tags']) && count($filters['tags'])) {
			$queryBuilder->innerJoin('p', 'sys_category_record_mm', 'tag',
				$queryBuilder->expr()->andX(
					$queryBuilder->expr()->eq('tag.tablenames', $queryBuilder->createNamedParameter('pages')),
					$queryBuilder->expr()->eq('tag.fieldname', $queryBuilder->createNamedParameter('tx_sgnews_tags')),
					$queryBuilder->expr()->eq('tag.uid_foreign', 'p.uid'),
					$queryBuilder->expr()->in('tag.uid_local', $queryBuilder->createNamedParameter(\array_unique($filters['tags']), Connection::PARAM_INT_ARRAY))
		if (isset($filters['search']) && trim($filters['search'])) {
			$searchParameter = $queryBuilder->createNamedParameter('%' . trim($filters['search']) . '%');
			$expressions = [
				$queryBuilder->expr()->like('p.title', $searchParameter),
				$queryBuilder->expr()->like('p.description', $searchParameter),
				$queryBuilder->expr()->like('p.abstract', $searchParameter),
			];
			if ($languageUid) {
				$expressions[] = $queryBuilder->expr()->like('translation.title', $searchParameter);
				$expressions[] = $queryBuilder->expr()->like('translation.description', $searchParameter);
				$expressions[] = $queryBuilder->expr()->like('translation.abstract', $searchParameter);
			}

			$authorQueryBuilder = $connectionPool->getQueryBuilderForTable('tx_sgnews_domain_model_author');
			$authorQueryBuilder->getRestrictions()->removeAll()->add($deletedRestriction);
			$authorSearchParameter = $authorQueryBuilder->createNamedParameter('%' . trim($filters['search']) . '%');
			$authors = $authorQueryBuilder->select('author.uid')->from('tx_sgnews_domain_model_author', 'author')
				->where(
					$authorQueryBuilder->expr()->orX(
						$authorQueryBuilder->expr()->like('author.name', $authorSearchParameter),
						$authorQueryBuilder->expr()->like('author.email', $authorSearchParameter)
					)
				)->execute()->fetchAll();

			if (count($authors) > 0) {
				foreach (array_column($authors, 'uid') as $authorUid) {
					$expressions[] = $queryBuilder->expr()->inSet('p.tx_sgnews_news_author', $authorUid);
				}
Paul Ilea's avatar
Paul Ilea committed
			}
			$queryBuilder->andWhere(
				$queryBuilder->expr()->orX(
Paul Ilea's avatar
Paul Ilea committed
		}

		return $queryBuilder->execute()->fetchAll();
Paul Ilea's avatar
Paul Ilea committed
	}

	/**
	 * Returns the available languages for the current BE user
	 *
	 * @param int $pageUid
	 * @return array
	 * @throws \InvalidArgumentException
	 */
	public static function getAvailableLanguages($pageUid = 0): array {
Paul Ilea's avatar
Paul Ilea committed
		$pageUid = (int) $pageUid;
		$rootline = BackendUtility::BEgetRootLine($pageUid, '', TRUE);
		$defaultLanguage = LocalizationUtility::translate('backend.language.default', 'SgNews');
		$pageTS = BackendUtility::getPagesTSconfig($pageUid, $rootline);
		if (isset($pageTS['mod.']['SHARED.']['defaultLanguageLabel'])) {
			$defaultLanguage = $pageTS['mod.']['SHARED.']['defaultLanguageLabel'] . ' (' . $defaultLanguage . ')';
		}
Paul Ilea's avatar
Paul Ilea committed
		$defaultLanguageFlag = 'empty-empty';
		if (isset($pageTS['mod.']['SHARED.']['defaultLanguageFlag'])) {
			$defaultLanguageFlag = 'flags-' . $pageTS['mod.']['SHARED.']['defaultLanguageFlag'];
		}
Paul Ilea's avatar
Paul Ilea committed
		$languages = [
			0 => ['title' => $defaultLanguage, 'flag' => $defaultLanguageFlag]
		];
		$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
		$languageRows = $queryBuilder->select('uid', 'title', 'flag')
			->from('sys_language')
			->orderBy('sorting')
			->execute()->fetchAll();
Paul Ilea's avatar
Paul Ilea committed
		if ($languageRows) {
			/** @var BackendUserAuthentication $backendUser */
			$backendUser = $GLOBALS['BE_USER'];
			foreach ($languageRows as $languageRow) {
				if ($backendUser->checkLanguageAccess($languageRow['uid'])) {
					$languages[(int) $languageRow['uid']] = [
						'title' => $languageRow['title'],
						'flag' => 'flags-' . $languageRow['flag'],
					];
				}
			}
		}
Paul Ilea's avatar
Paul Ilea committed
		return $languages;
	}

	/**
	 * Returns records from table, $theTable, where a field ($theField) equals the value, $theValue
	 * The records are returned in an array
	 * If no records were selected, the function returns nothing
	 *
	 * @param string $theTable Table name present in $GLOBALS['TCA']
	 * @param string $theField Field to select on
	 * @param string $theValue Value that $theField must match
	 * @param string $whereClause Optional additional WHERE clauses put in the end of the query. DO NOT PUT IN GROUP BY, ORDER BY or LIMIT!
	 * @param string $groupBy Optional GROUP BY field(s), if none, supply blank string.
	 * @param string $orderBy Optional ORDER BY field(s), if none, supply blank string.
	 * @param string $limit Optional LIMIT value ([begin,]max), if none, supply blank string.
	 * @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
	 * @return mixed Multidimensional array with selected records (if any is selected)
	 */
	public static function getRecordsByField(
		$theTable, $theField, $theValue, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '',
		$useDeleteClause = TRUE
	) {
		if (is_array($GLOBALS['TCA'][$theTable])) {
			$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($theTable);
			$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));;
			if ($useDeleteClause) {
				$queryBuilder->getRestrictions()
					->add(GeneralUtility::makeInstance(DeletedRestriction::class));

			$queryBuilder->select('*')
				->from($theTable)
				->where(
					$queryBuilder->expr()->eq($theField, $queryBuilder->createNamedParameter($theValue))
				);
			// additional where
			if ($whereClause) {
				$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($whereClause));

			// group by
			if ($groupBy !== '') {
				$queryBuilder->groupBy(QueryHelper::parseGroupBy($groupBy));
			}

			// order by
			if ($orderBy !== '') {
				foreach (QueryHelper::parseOrderBy($orderBy) as $orderPair) {
					list($fieldName, $order) = $orderPair;
					$queryBuilder->addOrderBy($fieldName, $order);
				}
			}

			// limit
			if ($limit !== '') {
				if (strpos($limit, ',')) {
					$limitOffsetAndMax = GeneralUtility::intExplode(',', $limit);
					$queryBuilder->setFirstResult((int)$limitOffsetAndMax[0]);
					$queryBuilder->setMaxResults((int)$limitOffsetAndMax[1]);
				} else {
					$queryBuilder->setMaxResults((int)$limit);
				}
			}

			return $queryBuilder->execute()->fetchAll();
		return NULL;
	}

	/**
	 * Register the extension icons
	 * For use in ext_localconf.php
	 */
	public static function registerIcons() {
		$iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
		$iconRegistry->registerIcon(
			'actions-document-open-white',
			SvgIconProvider::class,
			['source' => 'EXT:sg_news/Resources/Public/Icons/actions-document-open-white.svg']
		);
		$iconRegistry->registerIcon(
			'sg_news-module',
			SvgIconProvider::class,
			['source' => 'EXT:sg_news/Resources/Public/Icons/module-sgnews.svg']
		);
		$iconRegistry->registerIcon(
			'sg_news-module-transparent',
			SvgIconProvider::class,
			['source' => 'EXT:sg_news/Resources/Public/Icons/module-sgnews-transparent.svg']
		);
		$iconRegistry->registerIcon(
			'tcarecords-pages-' . self::CATEGORY_DOKTYPE,
			BitmapIconProvider::class,
			['source' => 'EXT:sg_news/Resources/Public/Images/Category.png']
		);
		$iconRegistry->registerIcon(
			'tcarecords-pages-' . self::NEWS_DOKTYPE,
			BitmapIconProvider::class,
			['source' => 'EXT:sg_news/Resources/Public/Images/News.png']
		);
	}
Paul Ilea's avatar
Paul Ilea committed
}