Initial commit - Typo3 11.5.41

This commit is contained in:
Matteo Gallo
2026-07-03 17:53:31 +02:00
commit 5ca4743197
6811 changed files with 568848 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
use GeorgRinger\News\Domain\Model\DemandInterface;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface;
use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
/**
* Abstract demanded repository
*/
abstract class AbstractDemandedRepository extends Repository implements DemandedRepositoryInterface
{
/**
* @var \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface
*/
protected $storageBackend;
/**
* @param \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface $storageBackend
*
* @return void
*/
public function injectStorageBackend(
BackendInterface $storageBackend
): void {
$this->storageBackend = $storageBackend;
}
/**
* Returns an array of constraints created from a given demand object.
*
* @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
* @param DemandInterface $demand
* @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface[]
* @abstract
*/
abstract protected function createConstraintsFromDemand(
QueryInterface $query,
DemandInterface $demand
): array;
/**
* Returns an array of orderings created from a given demand object.
*
* @param DemandInterface $demand
* @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface[]
* @abstract
*/
abstract protected function createOrderingsFromDemand(DemandInterface $demand): array;
/**
* Returns the objects of this repository matching the demand.
*
* @param DemandInterface $demand
* @param bool $respectEnableFields
* @param bool $disableLanguageOverlayMode
*
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array
*/
public function findDemanded(DemandInterface $demand, $respectEnableFields = true, $disableLanguageOverlayMode = false)
{
$query = $this->generateQuery($demand, $respectEnableFields, $disableLanguageOverlayMode);
return $query->execute();
}
/**
* Returns the database query to get the matching result
*
* @param DemandInterface $demand
* @param bool $respectEnableFields
* @param bool $disableLanguageOverlayMode
* @return string
*/
public function findDemandedRaw(DemandInterface $demand, $respectEnableFields = true, $disableLanguageOverlayMode = false): string
{
$query = $this->generateQuery($demand, $respectEnableFields, $disableLanguageOverlayMode);
$versionInformation = GeneralUtility::makeInstance(Typo3Version::class);
if ($versionInformation->getMajorVersion() >= 11) {
$queryParser = GeneralUtility::makeInstance(Typo3DbQueryParser::class);
} else {
$queryParser = $this->objectManager->get(Typo3DbQueryParser::class);
}
$queryBuilder = $queryParser->convertQueryToDoctrineQueryBuilder($query);
$queryParameters = $queryBuilder->getParameters();
$params = [];
foreach ($queryParameters as $key => $value) {
// prefix array keys with ':'
$params[':' . $key] = (is_numeric($value)) ? $value : "'" . $value . "'"; //all non numeric values have to be quoted
unset($params[$key]);
}
// replace placeholders with real values
$query = strtr($queryBuilder->getSQL(), $params);
return $query;
}
/**
* @param DemandInterface $demand
* @param bool $respectEnableFields
* @param bool $disableLanguageOverlayMode
* @return \TYPO3\CMS\Extbase\Persistence\QueryInterface
*/
protected function generateQuery(DemandInterface $demand, $respectEnableFields = true, $disableLanguageOverlayMode = false): \TYPO3\CMS\Extbase\Persistence\QueryInterface
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
if ($disableLanguageOverlayMode) {
$query->getQuerySettings()->setLanguageOverlayMode(false);
}
$constraints = $this->createConstraintsFromDemand($query, $demand);
// Call hook functions for additional constraints
if ($hooks = $GLOBALS['TYPO3_CONF_VARS']['EXT']['news']['Domain/Repository/AbstractDemandedRepository.php']['findDemanded'] ?? []) {
$params = [
'demand' => $demand,
'respectEnableFields' => &$respectEnableFields,
'query' => $query,
'constraints' => &$constraints,
];
foreach ($hooks as $reference) {
GeneralUtility::callUserFunction($reference, $params, $this);
}
}
if ($respectEnableFields === false) {
$query->getQuerySettings()->setIgnoreEnableFields(true);
$constraints[] = $query->equals('deleted', 0);
}
if (!empty($constraints)) {
$query->matching(
$query->logicalAnd($constraints)
);
}
if ($orderings = $this->createOrderingsFromDemand($demand)) {
$query->setOrderings($orderings);
}
// @todo consider moving this to a separate function as well
if ($demand->getLimit() != null) {
$query->setLimit((int)$demand->getLimit());
}
// @todo consider moving this to a separate function as well
if ($demand->getOffset() != null) {
if (!$query->getLimit()) {
$query->setLimit(PHP_INT_MAX);
}
$query->setOffset((int)$demand->getOffset());
}
return $query;
}
/**
* Returns the total number objects of this repository matching the demand.
*
* @param DemandInterface $demand
* @return int
*/
public function countDemanded(DemandInterface $demand, $respectEnableFields = true, $disableLanguageOverlayMode = false): int
{
$query = $this->generateQuery($demand, $respectEnableFields, $disableLanguageOverlayMode);
if ($constraints = $this->createConstraintsFromDemand($query, $demand)) {
$query->matching(
$query->logicalAnd($constraints)
);
}
$result = $query->execute();
return $result->count();
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Get data used in the administration view
*/
class AdministrationRepository
{
public function getTotalCounts(): array
{
$count = [];
$queryBuilder = $this->getQueryBuilder('tx_news_domain_model_news');
$queryBuilder->getRestrictions()->removeAll();
$count['tx_news_domain_model_news'] = $queryBuilder
->count('*')
->from('tx_news_domain_model_news')
->execute()->fetchColumn(0);
$queryBuilder = $this->getQueryBuilder('sys_category_record_mm');
$count['category_relations'] = $queryBuilder
->count('*')
->from('sys_category_record_mm')
->where($queryBuilder->expr()->like(
'tablenames',
$queryBuilder->createNamedParameter('tx_news_domain_model_news', \PDO::PARAM_STR)
))
->execute()->fetchColumn(0);
if ($count['tx_news_domain_model_news'] > 0 && $count['category_relations']) {
$count['_both'] = true;
}
return $count;
}
private function getConnection(string $table): Connection
{
return GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
}
private function getQueryBuilder(string $table): QueryBuilder
{
return $this->getConnection($table)->createQueryBuilder();
}
}

View File

@@ -0,0 +1,252 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use Doctrine\DBAL\Connection;
use GeorgRinger\News\Domain\Model\Category;
use GeorgRinger\News\Domain\Model\DemandInterface;
use GeorgRinger\News\Service\CategoryService;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
/**
* Category repository with all callable functionality
*/
class CategoryRepository extends AbstractDemandedRepository
{
protected function createConstraintsFromDemand(QueryInterface $query, DemandInterface $demand): array
{
return [];
}
protected function createOrderingsFromDemand(DemandInterface $demand): array
{
return [];
}
/**
* Find category by import source and import id
*
* @param string $importSource import source
* @param int $importId import id
* @param bool $asArray return result as array
* @return Category|array
*/
public function findOneByImportSourceAndImportId($importSource, $importId, $asArray = false)
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->getQuerySettings()->setIgnoreEnableFields(true);
$result = $query->matching(
$query->logicalAnd(
$query->equals('importSource', $importSource),
$query->equals('importId', $importId)
)
)->execute($asArray);
if ($asArray) {
if (isset($result[0])) {
return $result[0];
}
return [];
}
return $result->getFirst();
}
/**
* Find categories by a given pid
*
* @param int $pid pid
*
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array
*/
public function findParentCategoriesByPid($pid)
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
return $query->matching(
$query->logicalAnd(
$query->equals('pid', (int)$pid),
$query->equals('parentcategory', 0)
)
)->execute();
}
/**
* Find category tree
*
* @param array $rootIdList list of id s
* @return QueryInterface|array
*/
public function findTree(array $rootIdList, $startingPoint = null)
{
$subCategories = CategoryService::getChildrenCategories(implode(',', $rootIdList));
$idList = explode(',', $subCategories);
if (empty($idList)) {
return [];
}
$ordering = ['sorting' => QueryInterface::ORDER_ASCENDING];
$categories = $this->findByIdList($idList, $ordering, $startingPoint);
$flatCategories = [];
/** @var Category $category */
foreach ($categories as $category) {
$flatCategories[$category->getUid()] = [
'item' => $category,
'parent' => ($category->getParentcategory()) ? $category->getParentcategory()->getUid() : null
];
}
$tree = [];
// If leaves are selected without its parents selected, those are shown as parent
foreach ($flatCategories as $id => &$flatCategory) {
if (!isset($flatCategories[$flatCategory['parent']])) {
$flatCategory['parent'] = null;
}
}
foreach ($flatCategories as $id => &$node) {
if ($node['parent'] === null) {
$tree[$id] = &$node;
} else {
$flatCategories[$node['parent']]['children'][$id] = &$node;
}
}
return $tree;
}
/**
* Find categories by a given pid
*
* @param array $idList list of id s
* @param array $ordering ordering
*
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array
*/
public function findByIdList(array $idList, array $ordering = [], $startingPoint = null)
{
if (empty($idList)) {
throw new \InvalidArgumentException('The given id list is empty.', 1484823597);
}
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->getQuerySettings()->setRespectSysLanguage(true);
if (count($ordering) > 0) {
$query->setOrderings($ordering);
}
$this->overlayTranslatedCategoryIds($idList);
$conditions = [];
$conditions[] = $query->in('uid', $idList);
if (is_null($startingPoint) === false) {
$conditions[] = $query->in('pid', GeneralUtility::trimExplode(',', $startingPoint, true));
}
return $query->matching(
$query->logicalAnd(
$conditions
)
)->execute();
}
/**
* Find categories by a given parent
*
* @param int $parent parent
*
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array
*/
public function findChildren($parent)
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
return $query->matching(
$query->logicalAnd(
$query->equals('parentcategory', (int)$parent)
)
)->execute();
}
/**
* Overlay the category ids with the ones from current language
*
* @param array $idList
* return void
*
* @return void
*/
protected function overlayTranslatedCategoryIds(array &$idList): void
{
$language = $this->getSysLanguageUid();
if ($language > 0 && !empty($idList)) {
if (isset($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE'])) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$rows = $queryBuilder
->select('l10n_parent', 'uid', 'sys_language_uid')
->from('sys_category')
->where(
$queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)),
$queryBuilder->expr()->in('l10n_parent', $queryBuilder->createNamedParameter($idList, Connection::PARAM_INT_ARRAY))
)
->execute()->fetchAll();
$idList = $this->replaceCategoryIds($idList, $rows);
}
// @todo currently only implemented for the frontend
}
}
/**
* Get the current sys language uid
*
* @return int
*/
protected function getSysLanguageUid(): int
{
$sysLanguage = 0;
if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() === 10) {
$sysLanguage = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('language', 'id');
} elseif (isset($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE'])) {
$sysLanguage = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('language', 'contentId');
} elseif ((int)GeneralUtility::_GP('L')) {
$sysLanguage = (int)GeneralUtility::_GP('L');
}
return $sysLanguage;
}
/**
* Replace ids in array by the given ones
*
* @param array $idList
* @param array $rows
* @return array
*/
protected function replaceCategoryIds(array $idList, array $rows): array
{
foreach ($rows as $row) {
$pos = array_search($row['l10n_parent'], $idList);
if ($pos !== false) {
$idList[$pos] = (int)$row['uid'];
}
}
return $idList;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use GeorgRinger\News\Domain\Model\DemandInterface;
/**
* Demand domain model interface
*/
interface DemandedRepositoryInterface
{
public function findDemanded(DemandInterface $demand, $respectEnableFields = true);
public function countDemanded(DemandInterface $demand);
}

View File

@@ -0,0 +1,18 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
/**
* Repository for file objects
*/
class FileRepository extends Repository
{
}

View File

@@ -0,0 +1,18 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
/**
* Repository for link objects
*/
class LinkRepository extends Repository
{
}

View File

@@ -0,0 +1,18 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
/**
* Repository for media objects
*/
class MediaRepository extends Repository
{
}

View File

@@ -0,0 +1,17 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
/**
* News repository with all the callable functionality
*/
class NewsDefaultRepository extends NewsRepository
{
}

View File

@@ -0,0 +1,519 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use GeorgRinger\News\Domain\Model\DemandInterface;
use GeorgRinger\News\Domain\Model\Dto\NewsDemand;
use GeorgRinger\News\Service\CategoryService;
use GeorgRinger\News\Utility\ConstraintHelper;
use GeorgRinger\News\Utility\Validation;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
/**
* News repository with all the callable functionality
*/
class NewsRepository extends AbstractDemandedRepository
{
/**
* Returns a category constraint created by
* a given list of categories and a junction string
*
* @param QueryInterface $query
* @param array $categories
* @param string $conjunction
* @param bool $includeSubCategories
* @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface|null
*/
protected function createCategoryConstraint(
QueryInterface $query,
$categories,
$conjunction,
$includeSubCategories = false
): ?\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface {
$constraint = null;
$categoryConstraints = [];
// If "ignore category selection" is used, nothing needs to be done
if (empty($conjunction)) {
return $constraint;
}
if (!is_array($categories)) {
$categories = GeneralUtility::intExplode(',', $categories, true);
}
foreach ($categories as $category) {
if ($includeSubCategories) {
$subCategories = GeneralUtility::trimExplode(
',',
CategoryService::getChildrenCategories($category, 0, '', true),
true
);
$subCategoryConstraint = [];
$subCategoryConstraint[] = $query->contains('categories', $category);
if (count($subCategories) > 0) {
foreach ($subCategories as $subCategory) {
$subCategoryConstraint[] = $query->contains('categories', $subCategory);
}
}
if ($subCategoryConstraint) {
$categoryConstraints[] = $query->logicalOr($subCategoryConstraint);
}
} else {
$categoryConstraints[] = $query->contains('categories', $category);
}
}
if ($categoryConstraints) {
switch (strtolower($conjunction)) {
case 'or':
$constraint = $query->logicalOr($categoryConstraints);
break;
case 'notor':
$constraint = $query->logicalNot($query->logicalOr($categoryConstraints));
break;
case 'notand':
$constraint = $query->logicalNot($query->logicalAnd($categoryConstraints));
break;
case 'and':
default:
$constraint = $query->logicalAnd($categoryConstraints);
}
}
return $constraint;
}
/**
* Returns an array of constraints created from a given demand object.
*
* @param QueryInterface $query
* @param DemandInterface $demand
*
* @throws \UnexpectedValueException
* @throws \InvalidArgumentException
* @throws \Exception
*
* @return (\TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface|\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface|\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface|\TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface|\TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface|null)[]
*
* @psalm-return array<string, \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface|\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface|\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface|\TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface|\TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface|null>
*/
protected function createConstraintsFromDemand(QueryInterface $query, DemandInterface $demand): array
{
/** @var NewsDemand $demand */
$constraints = [];
if ($demand->getCategories() && $demand->getCategories() !== '0') {
$constraints['categories'] = $this->createCategoryConstraint(
$query,
$demand->getCategories(),
$demand->getCategoryConjunction(),
$demand->getIncludeSubCategories()
);
}
if ($demand->getAuthor()) {
$constraints['author'] = $query->equals('author', $demand->getAuthor());
}
if ($demand->getTypes()) {
$constraints['types'] = $query->in('type', $demand->getTypes());
}
// archived
if ($demand->getArchiveRestriction() === 'archived') {
$constraints['archived'] = $query->logicalAnd(
$query->lessThan('archive', $GLOBALS['SIM_EXEC_TIME']),
$query->greaterThan('archive', 0)
);
} elseif ($demand->getArchiveRestriction() === 'active') {
$constraints['active'] = $query->logicalOr(
$query->greaterThanOrEqual('archive', $GLOBALS['SIM_EXEC_TIME']),
$query->equals('archive', 0)
);
}
// Time restriction greater than or equal
$timeRestrictionField = $demand->getDateField();
$timeRestrictionField = (empty($timeRestrictionField)) ? 'datetime' : $timeRestrictionField;
if ($demand->getTimeRestriction()) {
$timeLimit = ConstraintHelper::getTimeRestrictionLow($demand->getTimeRestriction());
$constraints['timeRestrictionGreater'] = $query->greaterThanOrEqual(
$timeRestrictionField,
$timeLimit
);
}
// Time restriction less than or equal
if ($demand->getTimeRestrictionHigh()) {
$timeLimit = ConstraintHelper::getTimeRestrictionHigh($demand->getTimeRestrictionHigh());
$constraints['timeRestrictionLess'] = $query->lessThanOrEqual(
$timeRestrictionField,
$timeLimit
);
}
// top news
if ($demand->getTopNewsRestriction() == 1) {
$constraints['topNews1'] = $query->equals('istopnews', 1);
} elseif ($demand->getTopNewsRestriction() == 2) {
$constraints['topNews2'] = $query->equals('istopnews', 0);
}
// storage page
if ($demand->getStoragePage()) {
$pidList = GeneralUtility::intExplode(',', $demand->getStoragePage(), true);
$constraints['pid'] = $query->in('pid', $pidList);
}
// month & year OR year only
if ($demand->getYear() > 0) {
if (null === $demand->getDateField()) {
throw new \InvalidArgumentException('No Datefield is set, therefore no Datemenu is possible!');
}
if ($demand->getMonth() > 0) {
if ($demand->getDay() > 0) {
$begin = mktime(0, 0, 0, $demand->getMonth(), $demand->getDay(), $demand->getYear());
$end = mktime(23, 59, 59, $demand->getMonth(), $demand->getDay(), $demand->getYear());
} else {
$begin = mktime(0, 0, 0, $demand->getMonth(), 1, $demand->getYear());
$end = mktime(23, 59, 59, ($demand->getMonth() + 1), 0, $demand->getYear());
}
} else {
$begin = mktime(0, 0, 0, 1, 1, $demand->getYear());
$end = mktime(23, 59, 59, 12, 31, $demand->getYear());
}
$constraints['datetime'] = $query->logicalAnd([
$query->greaterThanOrEqual($demand->getDateField(), $begin),
$query->lessThanOrEqual($demand->getDateField(), $end)
]);
}
// Tags
$tags = $demand->getTags();
if ($tags && is_string($tags)) {
$tagList = explode(',', $tags);
$subConstraints = [];
foreach ($tagList as $singleTag) {
$subConstraints[] = $query->contains('tags', $singleTag);
}
if (count($subConstraints) > 0) {
$constraints['tags'] = $query->logicalOr($subConstraints);
}
}
// Search
$searchConstraints = $this->getSearchConstraints($query, $demand);
if (!empty($searchConstraints)) {
$constraints['search'] = $query->logicalAnd($searchConstraints);
}
// Exclude already displayed
if ($demand->getExcludeAlreadyDisplayedNews() && isset($GLOBALS['EXT']['news']['alreadyDisplayed']) && !empty($GLOBALS['EXT']['news']['alreadyDisplayed'])) {
$constraints['excludeAlreadyDisplayedNews'] = $query->logicalNot(
$query->in(
'uid',
$GLOBALS['EXT']['news']['alreadyDisplayed']
)
);
}
// Hide id list
$hideIdList = $demand->getHideIdList();
if ($hideIdList) {
$constraints['hideIdInList'] = $query->logicalNot(
$query->in(
'uid',
GeneralUtility::intExplode(',', $hideIdList, true)
)
);
}
// Id list
$idList = $demand->getIdList();
if ($idList) {
$constraints['idList'] = $query->in('uid', GeneralUtility::intExplode(',', $idList, true));
}
// Clean not used constraints
foreach ($constraints as $key => $value) {
if (null === $value) {
unset($constraints[$key]);
}
}
return $constraints;
}
/**
* Returns an array of orderings created from a given demand object.
*
* @param DemandInterface $demand
*
* @return string[]
*
* @psalm-return array<string, string>
*/
protected function createOrderingsFromDemand(DemandInterface $demand): array
{
$orderings = [];
if ($demand->getTopNewsFirst()) {
$orderings['istopnews'] = QueryInterface::ORDER_DESCENDING;
}
if (Validation::isValidOrdering($demand->getOrder(), $demand->getOrderByAllowed())) {
$orderList = GeneralUtility::trimExplode(',', $demand->getOrder(), true);
if (!empty($orderList)) {
// go through every order statement
foreach ($orderList as $orderItem) {
$orderSplit = GeneralUtility::trimExplode(' ', $orderItem, true);
$orderField = $orderSplit[0];
$ascDesc = $orderSplit[1] ?? '';
if ($ascDesc) {
$orderings[$orderField] = ((strtolower($ascDesc) === 'desc') ?
QueryInterface::ORDER_DESCENDING :
QueryInterface::ORDER_ASCENDING);
} else {
$orderings[$orderField] = QueryInterface::ORDER_ASCENDING;
}
}
}
}
return $orderings;
}
/**
* Find first news by import and source id
*
* @param string $importSource import source
* @param int $importId import id
* @param bool $asArray return result as array
* @return \GeorgRinger\News\Domain\Model\News|array
*/
public function findOneByImportSourceAndImportId($importSource, $importId, $asArray = false)
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->getQuerySettings()->setRespectSysLanguage(false);
$query->getQuerySettings()->setIgnoreEnableFields(true);
$result = $query->matching(
$query->logicalAnd(
$query->equals('importSource', $importSource),
$query->equals('importId', $importId)
)
)->execute($asArray);
if ($asArray) {
if (isset($result[0])) {
return $result[0];
}
return [];
}
return $result->getFirst();
}
/**
* Override default findByUid function to enable also the option to turn of
* the enableField setting
*
* @param int $uid id of record
* @param bool $respectEnableFields if set to false, hidden records are shown
* @return \GeorgRinger\News\Domain\Model\News|null
*/
public function findByUid($uid, $respectEnableFields = true): ?\GeorgRinger\News\Domain\Model\News
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->getQuerySettings()->setRespectSysLanguage(false);
if (!$respectEnableFields) {
$query->getQuerySettings()->setIgnoreEnableFields(true);
$query->getQuerySettings()->setLanguageOverlayMode(false);
}
return $query->matching(
$query->logicalAnd(
$query->equals('uid', $uid),
$query->equals('deleted', 0)
)
)->execute()->getFirst();
}
/**
* Get the count of news records by month/year and
* returns the result compiled as array
*
* @param DemandInterface $demand
* @return array
*/
public function countByDate(DemandInterface $demand): array
{
$data = [];
$sql = $this->findDemandedRaw($demand);
// strip unwanted order by
$sql = $this->stripOrderBy($sql);
// Get the month/year into the result
$field = $demand->getDateField();
$field = empty($field) ? 'datetime' : $field;
$sql = 'SELECT MONTH(FROM_UNIXTIME(0) + INTERVAL ' . $field . ' SECOND ) AS "_Month",' .
' YEAR(FROM_UNIXTIME(0) + INTERVAL ' . $field . ' SECOND) AS "_Year" ,' .
' count(MONTH(FROM_UNIXTIME(0) + INTERVAL ' . $field . ' SECOND )) as count_month,' .
' count(YEAR(FROM_UNIXTIME(0) + INTERVAL ' . $field . ' SECOND)) as count_year' .
' FROM tx_news_domain_model_news ' . substr($sql, strpos($sql, 'WHERE '));
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tx_news_domain_model_news');
if (TYPO3_MODE === 'FE') {
$sql .= $GLOBALS['TSFE']->sys_page->enableFields('tx_news_domain_model_news');
} else {
$expressionBuilder = $connection
->createQueryBuilder()
->expr();
$sql .= BackendUtility::BEenableFields('tx_news_domain_model_news') .
' AND ' . $expressionBuilder->eq('deleted', 0);
}
// group by custom month/year fields
$orderDirection = strtolower($demand->getOrder());
if ($orderDirection !== 'desc' && $orderDirection !== 'asc') {
$orderDirection = 'asc';
}
$sql .= ' GROUP BY _Month, _Year ORDER BY _Year ' . $orderDirection . ', _Month ' . $orderDirection;
$res = $connection->query($sql);
while ($row = $res->fetch()) {
$month = strlen($row['_Month']) === 1 ? ('0' . $row['_Month']) : $row['_Month'];
$data['single'][$row['_Year']][$month] = $row['count_month'];
}
// Add totals
if (is_array($data['single'])) {
foreach ($data['single'] as $year => $months) {
$countOfYear = 0;
foreach ($months as $month) {
$countOfYear += $month;
}
$data['total'][$year] = $countOfYear;
}
}
return $data;
}
/**
* Get the search constraints
*
* @param QueryInterface $query
* @param DemandInterface $demand
* @return array
* @throws \UnexpectedValueException
*/
protected function getSearchConstraints(QueryInterface $query, DemandInterface $demand): array
{
$constraints = [];
if ($demand->getSearch() === null) {
return $constraints;
}
/* @var $searchObject \GeorgRinger\News\Domain\Model\Dto\Search */
$searchObject = $demand->getSearch();
$searchSubject = $searchObject->getSubject();
if (!empty($searchSubject)) {
$queryBuilder = $this->getQueryBuilder('tx_news_domain_model_news');
$searchFields = GeneralUtility::trimExplode(',', $searchObject->getFields(), true);
$searchConstraints = [];
if (count($searchFields) === 0) {
throw new \UnexpectedValueException('No search fields defined', 1318497755);
}
$searchSubjectSplitted = str_getcsv($searchSubject, ' ');
if ($searchObject->isSplitSubjectWords()) {
foreach ($searchFields as $field) {
$subConstraints = [];
foreach ($searchSubjectSplitted as $searchSubjectSplittedPart) {
$searchSubjectSplittedPart = trim($searchSubjectSplittedPart);
if ($searchSubjectSplittedPart) {
$subConstraints[] = $query->like($field, '%' . $searchSubjectSplittedPart . '%');
}
}
$searchConstraints[] = $query->logicalAnd($subConstraints);
}
if (count($searchConstraints)) {
$constraints[] = $query->logicalOr($searchConstraints);
}
} else {
if (!empty($searchSubject)) {
foreach ($searchFields as $field) {
$searchConstraints[] = $query->like($field, '%' . $searchSubject . '%');
}
}
if (count($searchConstraints)) {
$constraints[] = $query->logicalOr($searchConstraints);
}
}
}
$minimumDate = strtotime($searchObject->getMinimumDate());
if ($minimumDate) {
$field = $searchObject->getDateField();
if (empty($field)) {
throw new \UnexpectedValueException('No date field is defined', 1396348732);
}
$constraints[] = $query->greaterThanOrEqual($field, $minimumDate);
}
$maximumDate = strtotime($searchObject->getMaximumDate());
if ($maximumDate) {
$field = $searchObject->getDateField();
if (empty($field)) {
throw new \UnexpectedValueException('No date field is defined', 1396348733);
}
$maximumDate += 86400;
$constraints[] = $query->lessThanOrEqual($field, $maximumDate);
}
return $constraints;
}
/**
* @param string $table table name
* @return QueryBuilder
*/
protected function getQueryBuilder(string $table): QueryBuilder
{
return GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
}
/**
* Return stripped order sql
*
* @param string $str
* @return string
*/
private function stripOrderBy(string $str): string
{
/** @noinspection NotOptimalRegularExpressionsInspection */
return preg_replace('/(?:ORDER[[:space:]]*BY[[:space:]]*.*)+/i', '', trim($str));
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use GeorgRinger\News\Domain\Model\DemandInterface;
use GeorgRinger\News\Utility\Validation;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
/**
* Repository for tag objects
*/
class TagRepository extends AbstractDemandedRepository
{
/**
* Find categories by a given pid
*
* @param array $idList list of id s
* @param array $ordering ordering
* @param string $startingPoint starting point uid or comma separated list
*
* @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface|array
*/
public function findByIdList(array $idList, array $ordering = [], $startingPoint = null)
{
if (empty($idList)) {
throw new \InvalidArgumentException('The given id list is empty.', 1484823596);
}
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->getQuerySettings()->setRespectSysLanguage(false);
if (count($ordering) > 0) {
$query->setOrderings($ordering);
}
$conditions = [];
$conditions[] = $query->in('uid', $idList);
if ($startingPoint !== null) {
$conditions[] = $query->in('pid', GeneralUtility::trimExplode(',', $startingPoint, true));
}
return $query->matching(
$query->logicalAnd(
$conditions
)
)->execute();
}
/**
* Returns an array of constraints created from a given demand object.
*
* @param QueryInterface $query
* @param DemandInterface $demand
* @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface[]
*/
protected function createConstraintsFromDemand(QueryInterface $query, DemandInterface $demand): array
{
$constraints = [];
// Storage page
if ($demand->getStoragePage()) {
$pidList = GeneralUtility::intExplode(',', $demand->getStoragePage(), true);
$constraints[] = $query->in('pid', $pidList);
}
// Tags
if ($demand->getTags()) {
$tagList = GeneralUtility::intExplode(',', $demand->getTags(), true);
$constraints[] = $query->in('uid', $tagList);
}
// Clean not used constraints
foreach ($constraints as $key => $value) {
if (is_null($value)) {
unset($constraints[$key]);
}
}
return $constraints;
}
/**
* Returns an array of orderings created from a given demand object.
*
* @param DemandInterface $demand
*
* @return string[]
*
* @psalm-return array<string, string>
*/
protected function createOrderingsFromDemand(DemandInterface $demand): array
{
$orderings = [];
if (Validation::isValidOrdering($demand->getOrder(), $demand->getOrderByAllowed())) {
$orderList = GeneralUtility::trimExplode(',', $demand->getOrder(), true);
if (!empty($orderList)) {
// go through every order statement
foreach ($orderList as $orderItem) {
list($orderField, $ascDesc) = GeneralUtility::trimExplode(' ', $orderItem, true);
// count == 1 means that no direction is given
if ($ascDesc) {
$orderings[$orderField] = ((strtolower($ascDesc) == 'desc') ?
QueryInterface::ORDER_DESCENDING :
QueryInterface::ORDER_ASCENDING);
} else {
$orderings[$orderField] = QueryInterface::ORDER_ASCENDING;
}
}
}
}
return $orderings;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace GeorgRinger\News\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
/**
* This file is part of the "news" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
/**
* Repository for tt_content objects
*/
class TtContentRepository extends Repository
{
protected $objectType = '\GeorgRinger\News\Domain\Model\Ttcontent';
}