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
@@ -0,0 +1,101 @@
<?php
namespace GeorgRinger\News\Utility;
use GeorgRinger\News\Domain\Model\Dto\NewsDemand;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* 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\Frontend\ContentObject\ContentObjectRenderer;
/**
* Cache Utility class
*/
class Cache
{
/**
* Stack for processed cObjs which has added news relevant cache tags.
* @var array
*/
protected static $processedContentRecords = [];
/**
* Marks as cObj as processed.
*
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj
*
* @return void
*/
public function markContentRecordAsProcessed(ContentObjectRenderer $cObj): void
{
$key = 'tt_content_' . $cObj->data['uid'];
self::$processedContentRecords[$key] = true;
}
/**
* Checks if a cObj has already added cache tags.
*
* @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj
* @return bool
*/
public function isContentRecordAlreadyProcessed(ContentObjectRenderer $cObj): bool
{
$key = 'tt_content_' . $cObj->data['uid'];
return array_key_exists($key, self::$processedContentRecords);
}
/**
* Adds cache tags to page cache by news-records.
*
* Following cache tags will be added to tsfe:
* "tx_news_uid_[news:uid]"
*
* @param array|\TYPO3\CMS\Extbase\Persistence\QueryResult $newsRecords with news records
*
* @return void
*/
public static function addCacheTagsByNewsRecords($newsRecords): void
{
$cacheTags = [];
foreach ($newsRecords as $news) {
// cache tag for each news record
$cacheTags[] = 'tx_news_uid_' . $news->getUid();
if ($news->_getProperty('_localizedUid')) {
$cacheTags[] = 'tx_news_uid_' . $news->_getProperty('_localizedUid');
}
}
if (count($cacheTags) > 0) {
$GLOBALS['TSFE']->addCacheTags($cacheTags);
}
}
/**
* Adds page cache tags by used storagePages.
* This adds tags with the scheme tx_news_pid_[news:pid]
*
* @param \GeorgRinger\News\Domain\Model\Dto\NewsDemand $demand
*
* @return void
*/
public static function addPageCacheTagsByDemandObject(NewsDemand $demand): void
{
$cacheTags = [];
if ($demand->getStoragePage()) {
// Add cache tags for each storage page
foreach (GeneralUtility::trimExplode(',', $demand->getStoragePage()) as $pageId) {
$cacheTags[] = 'tx_news_pid_' . $pageId;
}
} else {
$cacheTags[] = 'tx_news_domain_model_news';
}
if (count($cacheTags) > 0) {
$GLOBALS['TSFE']->addCacheTags($cacheTags);
}
}
}
@@ -0,0 +1,227 @@
<?php
namespace GeorgRinger\News\Utility;
/**
* 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\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Class ClassCacheManager
*/
class ClassCacheManager
{
/**
* Cache instance
*
* @var PhpFrontend
*/
protected $classCache;
/**
* @var array
*/
protected $constructorLines = [];
/**
* @param PhpFrontend $classCache
*/
public function __construct(PhpFrontend $classCache = null)
{
if ($classCache === null) {
$cacheManager = GeneralUtility::makeInstance(CacheManager::class);
if (!$cacheManager->hasCache('news')) {
$cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
}
$this->classCache = $cacheManager->getCache('news');
} else {
$this->classCache = $classCache;
}
}
public function reBuildSimple()
{
$classPath = 'Classes/';
if (!function_exists('token_get_all')) {
throw new \Exception(('The function token_get_all must exist. Please install the module PHP Module Tokenizer'));
}
if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['news']['classes'])) {
return;
}
$files = GeneralUtility::getFilesInDir(Environment::getVarPath() . '/cache/code/news/', 'php');
if (empty($files)) {
$this->reBuild();
}
}
public function reBuild()
{
$classPath = 'Classes/';
if (!function_exists('token_get_all')) {
throw new \Exception(('The function token_get_all must exist. Please install the module PHP Module Tokenizer'));
}
if (!isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['news']['classes'])) {
return;
}
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXT']['news']['classes'] as $key => $extensionsWithThisClass) {
$this->constructorLines = [];
$extendingClassFound = false;
$path = ExtensionManagementUtility::extPath('news') . $classPath . $key . '.php';
if (!is_file($path)) {
throw new \Exception('Given file "' . $path . '" does not exist');
}
$code = $this->parseSingleFile($path, true);
// Get the files from all other extensions
foreach (array_unique($extensionsWithThisClass) as $extensionKey) {
$path = ExtensionManagementUtility::extPath($extensionKey) . $classPath . $key . '.php';
if (is_file($path)) {
$extendingClassFound = true;
$code .= $this->parseSingleFile($path, false);
}
}
if (isset($this->constructorLines['code']) && count($this->constructorLines['code'])) {
$code .= LF . implode("\n", $this->constructorLines['doc']);
$code .= LF . ' public function __construct(' . implode(',', $this->constructorLines['parameters'] ?? []) . ')' . LF . ' {' . LF . implode(LF, $this->constructorLines['code'] ?? []) . LF . ' }' . LF;
}
$code = $this->closeClassDefinition($code);
// If an extending class is found, the file is written and
// added to the autoloader info
if ($extendingClassFound) {
$cacheEntryIdentifier = 'tx_news_' . strtolower(str_replace('/', '_', $key));
try {
$this->classCache->set($cacheEntryIdentifier, $code);
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
}
}
/**
* Parse a single file and does some magic
* - Remove the <?php tags
* - Remove the class definition (if set)
*
* @param string $filePath path of the file
* @param bool $baseClass If class definition should be removed
* @return string path of the saved file
* @throws \Exception
* @throws \InvalidArgumentException
*/
protected function parseSingleFile($filePath, $baseClass = false): string
{
if (!is_file($filePath)) {
throw new \InvalidArgumentException(sprintf('File "%s" could not be found', $filePath));
}
$code = GeneralUtility::getUrl($filePath);
$classParser = GeneralUtility::makeInstance(ClassParser::class);
$classParser->parse($filePath);
$classParserInformation = $classParser->getFirstClass();
$code = str_replace('<?php', '', $code);
$codeInLines = explode(LF, str_replace(CR, '', $code));
$offsetForInnerPart = 0;
if ($baseClass) {
$innerPart = $codeInLines;
} else {
$offsetForInnerPart = $classParserInformation['start'];
if (isset($classParserInformation['eol'])) {
$innerPart = array_slice(
$codeInLines,
$classParserInformation['start'],
($classParserInformation['eol'] - $classParserInformation['start'] - 1)
);
} else {
$innerPart = array_slice($codeInLines, $classParserInformation['start']);
}
}
if (trim($innerPart[0]) === '{') {
unset($innerPart[0]);
}
$innerPartLine = function ($line) use ($offsetForInnerPart) {
return $line - $offsetForInnerPart;
};
// unset the constructor and save it's lines
if (isset($classParserInformation['functions']['__construct'])) {
$constructorInfo = $classParserInformation['functions']['__construct'];
$constructorInfo['inner_start'] = $constructorInfo['start'] - $offsetForInnerPart;
$constructorInfo['inner_end'] = $constructorInfo['end'] - $offsetForInnerPart;
if ($baseClass) {
$this->constructorLines['doc'] = explode("\n", $constructorInfo['doc']);
} else {
array_splice(
$this->constructorLines['doc'],
-1,
0,
array_filter(explode("\n", $constructorInfo['doc'] ?? ''), function ($value) {
return strpos($value, '@param') !== false;
})
);
}
$codePart = false;
for ($i = $constructorInfo['inner_start']; $i < $constructorInfo['inner_end']; $i++) {
if ($codePart) {
$this->constructorLines['code'][] = $innerPart[$i];
} elseif (trim($innerPart[$i]) === ') {' || trim($innerPart[$i]) === '{') {
$codePart = true;
} elseif (trim($innerPart[$i]) !== ')' && $i >= ($constructorInfo['inner_start'])) {
$this->constructorLines['parameters'][] = LF . rtrim($innerPart[$i], ',');
}
unset($innerPart[$i]);
}
unset($innerPart[$constructorInfo['inner_start'] - 1]);
unset($innerPart[$constructorInfo['inner_end']]);
}
$codePart = implode(LF, $innerPart);
$closingBracket = strrpos($codePart, '}');
$codePart = substr($codePart, 0, $closingBracket);
return $this->getPartialInfo($filePath) . $codePart;
}
/**
* @param string $filePath
* @return string
*/
protected function getPartialInfo($filePath): string
{
return LF . '/*' . str_repeat('*', 70) . LF . "\t" .
'this is partial from: ' . LF . "\t" . str_replace(Environment::getPublicPath(), '', $filePath) . LF . str_repeat(
'*',
70
) . '*/' . LF;
}
/**
* @param string $code
* @return string
*/
protected function closeClassDefinition($code): string
{
return $code . LF . '}';
}
}
@@ -0,0 +1,153 @@
<?php
namespace GeorgRinger\News\Utility;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* 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.
*/
/**
* Class ClassLoader
*/
class ClassLoader implements SingletonInterface
{
/**
* @var PhpFrontend
*/
protected $classCache;
/** @var ClassCacheManager */
protected $classCacheManager;
/** @var bool */
protected $isValidInstance = false;
/**
* ClassLoader constructor.
*
* @param PhpFrontend $classCache
*/
public function __construct(PhpFrontend $classCache = null, ClassCacheManager $classCacheManager = null)
{
$versionInformation = GeneralUtility::makeInstance(Typo3Version::class);
if ($versionInformation->getMajorVersion() === 10) {
// Use DI
// something might fail, e.g loading checks in Install Tool
if ($classCacheManager !== null) {
$this->classCacheManager = $classCacheManager;
$this->isValidInstance = true;
}
} else {
$this->classCacheManager = GeneralUtility::makeInstance(ClassCacheManager::class);
$this->isValidInstance = true;
}
if ($this->isValidInstance) {
if ($classCache === null) {
$this->classCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('news');
} else {
$this->classCache = $classCache;
}
}
}
/**
* Register instance of this class as spl autoloader
*
* @return void
*/
public static function registerAutoloader(): void
{
spl_autoload_register([GeneralUtility::makeInstance(self::class), 'loadClass'], true, true);
}
/**
* Loads php files containing classes or interfaces part of the
* classes directory of an extension.
*
* @param string $className Name of the class/interface to load
* @return bool
*/
public function loadClass($className): bool
{
if (!$this->isValidInstance) {
return false;
}
$className = ltrim($className, '\\');
if (!$this->isValidClassName($className)) {
return false;
}
$cacheEntryIdentifier = 'tx_news_' . strtolower(str_replace('/', '_', $this->changeClassName($className)));
if (!$this->classCache->has($cacheEntryIdentifier)) {
$this->classCacheManager->reBuild();
}
$this->classCache->requireOnce($cacheEntryIdentifier);
return true;
}
/**
* Get extension key from namespaced classname
*
* @param string $className
*
* @return null|string
*/
protected function getExtensionKey($className): ?string
{
$extensionKey = null;
if (strpos($className, '\\') !== false) {
$namespaceParts = GeneralUtility::trimExplode(
'\\',
$className,
0,
(substr($className, 0, 9) === 'TYPO3\\CMS' ? 4 : 3)
);
array_pop($namespaceParts);
$extensionKey = GeneralUtility::camelCaseToLowerCaseUnderscored(array_pop($namespaceParts));
}
return $extensionKey;
}
/**
* Find out if a class name is valid
*
* @param string $className
* @return bool
*/
protected function isValidClassName($className): bool
{
if ($this->isFirstPartOfStr($className, 'GeorgRinger\\News\\Domain\\') || $this->isFirstPartOfStr($className, 'GeorgRinger\\News\\Controller\\')) {
$modifiedClassName = $this->changeClassName($className);
if (isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['news']['classes'][$modifiedClassName])) {
return true;
}
}
return false;
}
protected function isFirstPartOfStr(string $str, string $partStr): bool
{
return $partStr !== '' && strpos($str, $partStr) === 0;
}
protected function changeClassName(string $className): string
{
return str_replace('\\', '/', str_replace('GeorgRinger\\News\\', '', $className));
}
}
@@ -0,0 +1,175 @@
<?php
namespace GeorgRinger\News\Utility;
/**
* 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.
*/
class ClassParser
{
private $classes = [];
private $extends = [];
private $implements = [];
const STATE_CLASS_HEAD = 100001;
const STATE_FUNCTION_HEAD = 100002;
public function getClasses()
{
return $this->classes;
}
public function getFirstClass()
{
return array_shift($this->classes);
}
public function getClassesImplementing($interface): array
{
$implementers = [];
if (isset($this->implements[$interface])) {
foreach ($this->implements[$interface] as $name) {
$implementers[$name] = $this->classes[$name];
}
}
return $implementers;
}
public function getClassesExtending($class): array
{
$extenders = [];
if (isset($this->extends[$class])) {
foreach ($this->extends[$class] as $name) {
$extenders[$name] = $this->classes[$name];
}
}
return $extenders;
}
public function parse($file): void
{
$file = realpath($file);
$tokens = token_get_all(file_get_contents($file));
$classes = [];
$clsc = 0;
$si = null;
$depth = 0;
$mod = [];
$doc = null;
$state = null;
$inFunction = false;
$functionName = '';
$lastLine = 0;
foreach ($tokens as $idx => &$token) {
if (is_array($token)) {
switch ($token[0]) {
case T_DOC_COMMENT:
$doc = $token[1];
break;
case T_PUBLIC:
case T_PRIVATE:
case T_ABSTRACT:
case T_PROTECTED:
$mod[] = $token[1];
break;
case T_CLASS:
case T_FUNCTION:
$state = $token[0];
break;
case T_EXTENDS:
case T_IMPLEMENTS:
switch ($state) {
case self::STATE_CLASS_HEAD:
case T_EXTENDS:
$state = $token[0];
break;
}
break;
case T_CLOSE_TAG:
$classes[$depth]['eol'] = $token[2];
break;
case T_STRING:
switch ($state) {
case T_CLASS:
$state = self::STATE_CLASS_HEAD;
$si = $token[1];
$classes[] = [
'name' => $token[1],
'modifiers' => $mod,
'doc' => $doc,
'start' => $token[2]
];
break;
case T_FUNCTION:
$state = self::STATE_FUNCTION_HEAD;
$clsc = count($classes);
if ($depth > 0 && $clsc) {
$inFunction = true;
$functionName = $token[1];
$classes[$clsc - 1]['functions'][$token[1]] = [
'modifiers' => $mod,
'doc' => $doc,
'start' => $token[2]
];
}
break;
case T_IMPLEMENTS:
case T_EXTENDS:
$clsc = count($classes);
$classes[$clsc - 1][$state == T_IMPLEMENTS ? 'implements' : 'extends'][] = $token[1];
break;
}
break;
}
$lastLine = $token[2];
} else {
switch ($token) {
case '{':
$depth++;
break;
case '}':
$depth--;
break;
}
if ($token === '}') {
if ($inFunction) {
$classes[$clsc - 1]['functions'][$functionName]['end'] = $lastLine;
$inFunction = false;
}
}
switch ($token) {
case '{':
case '}':
case ';':
$state = 0;
$doc = null;
$mod = [];
break;
}
}
}
foreach ($classes as $class) {
$class['file'] = $file;
$this->classes[$class['name']] = $class;
if (!empty($class['implements'])) {
foreach ($class['implements'] as $name) {
$this->implements[$name][] = $class['name'];
}
}
if (!empty($class['extends'])) {
foreach ($class['extends'] as $name) {
$this->extends[$name][] = $class['name'];
}
}
}
}
}
@@ -0,0 +1,72 @@
<?php
namespace GeorgRinger\News\Utility;
/**
* 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 DateTime;
use Exception;
use TYPO3\CMS\Core\Utility\MathUtility;
/**
* Helper class for building constraints
*/
class ConstraintHelper
{
/**
* @param string|int $timeInput
* @return int
* @throws Exception
*/
public static function getTimeRestrictionLow($timeInput): int
{
$timeLimit = 0;
// integer = timestamp
if (MathUtility::canBeInterpretedAsInteger($timeInput)) {
$timeLimit = $GLOBALS['SIM_EXEC_TIME'] - $timeInput;
} else {
$timeByFormat = DateTime::createFromFormat('HH:mm DD-MM-YYYY', $timeInput);
if ($timeByFormat) {
$timeLimit = $timeByFormat->getTimestamp();
} else {
// try to check strtotime
$timeFromString = strtotime($timeInput);
if ($timeFromString) {
$timeLimit = $timeFromString;
} else {
throw new Exception('Time limit Low could not be resolved to an integer. Given was: ' . htmlspecialchars($timeLimit));
}
}
}
return $timeLimit;
}
/**
* @param string|int $timeInput
* @return int
* @throws Exception
*/
public static function getTimeRestrictionHigh($timeInput): int
{
$timeLimit = 0;
// integer = timestamp
if (MathUtility::canBeInterpretedAsInteger($timeInput)) {
$timeLimit = $GLOBALS['SIM_EXEC_TIME'] + $timeInput;
return $timeLimit;
}
// try to check strtotime
$timeFromStringHigh = strtotime($timeInput);
if ($timeFromStringHigh) {
$timeLimit = $timeFromStringHigh;
return $timeLimit;
}
throw new Exception('Time limit High could not be resolved to an integer. Given was: ' . htmlspecialchars($timeLimit));
}
}
@@ -0,0 +1,53 @@
<?php
namespace GeorgRinger\News\Utility;
/**
* 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.
*/
/**
* Utility class for import jobs
*/
class ImportJob
{
/**
* @var array
*/
protected static $registeredJobs = [];
/**
* Register an import job.
*
* @param string $className class name
* @param string $title title
* @param string $description description
*
* @static
*
* @return void
*/
public static function register($className, $title, $description): void
{
self::$registeredJobs[] = [
'className' => $className,
'title' => $title,
'description' => $description
];
}
/**
* Get all registered import jobs
*
* @static
* @return array
*/
public static function getRegisteredJobs(): array
{
return self::$registeredJobs;
}
}
+143
View File
@@ -0,0 +1,143 @@
<?php
namespace GeorgRinger\News\Utility;
/**
* 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\Backend\Tree\View\PageTreeView;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\QueryGenerator;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* Page Utility class
*/
class Page
{
/**
* Find all ids from given ids and level
*
* @param string $pidList comma separated list of ids
* @param int $recursive recursive levels
* @return string comma separated list of ids
*/
public static function extendPidListByChildren($pidList = '', $recursive = 0): string
{
$recursive = (int)$recursive;
if ($recursive <= 0) {
return $pidList ?? '';
}
$queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
$recursiveStoragePids = $pidList;
$storagePids = GeneralUtility::intExplode(',', $pidList);
foreach ($storagePids as $startPid) {
if ($startPid >= 0) {
$pids = $queryGenerator->getTreeList($startPid, $recursive);
if (strlen($pids) > 0) {
$recursiveStoragePids .= ',' . $pids;
}
}
}
return GeneralUtility::uniqueList($recursiveStoragePids);
}
/**
* Set properties of an object/array in cobj->LOAD_REGISTER which can then
* be used to be loaded via TS with register:name
*
* @param string $properties comma separated list of properties
* @param mixed $object object or array to get the properties
* @param string $prefix optional prefix
*
* @return void
*/
public static function setRegisterProperties($properties, $object, $prefix = 'news'): void
{
if (!empty($properties) && $object !== null) {
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$items = GeneralUtility::trimExplode(',', $properties, true);
$register = [];
foreach ($items as $item) {
$key = $prefix . ucfirst($item);
if (is_object($object)) {
$getter = 'get' . ucfirst($item);
try {
$value = $object->$getter();
if ($value instanceof \DateTime) {
$value = $value->getTimestamp();
}
$register[$key] = $value;
} catch (\Exception $e) {
$logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__);
$logger->warning($e->getMessage());
}
}
if (is_array($object)) {
$value = $object[$item];
$register[$key] = $value;
}
}
$cObj->cObjGetSingle('LOAD_REGISTER', $register);
}
}
/**
* Return a page tree
*
* @param int $pageUid page to start with
* @param int $treeLevel count of levels
* @return PageTreeView
* @throws \Exception
*/
public static function pageTree($pageUid, $treeLevel): PageTreeView
{
if (TYPO3_MODE !== 'BE') {
throw new \Exception('Page::pageTree does only work in the backend!');
}
$pageUid = (int)$pageUid;
if ($pageUid === 0 && !self::getBackendUser()->isAdmin()) {
$mounts = self::getBackendUser()->returnWebmounts();
$pageUid = array_shift($mounts);
}
/* @var $tree PageTreeView */
$tree = GeneralUtility::makeInstance(PageTreeView::class);
$tree->init('AND ' . self::getBackendUser()->getPagePermsClause(1));
$treeStartingRecord = BackendUtility::getRecord('pages', $pageUid);
BackendUtility::workspaceOL('pages', $treeStartingRecord);
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
// Creating top icon; the current page
$tree->tree[] = [
'row' => $treeStartingRecord,
'HTML' => is_array($treeStartingRecord) ? $iconFactory->getIconForRecord('pages', $treeStartingRecord, Icon::SIZE_SMALL)->render() : ''
];
$tree->getTree($pageUid, $treeLevel, '');
return $tree;
}
/**
* Get backend user
*
* @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
*/
protected static function getBackendUser(): \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
}
@@ -0,0 +1,66 @@
<?php
namespace GeorgRinger\News\Utility;
/**
* 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\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* TemplateLayout utility class
*/
class TemplateLayout implements SingletonInterface
{
/**
* Get available template layouts for a certain page
*
* @param int $pageUid
* @return array
*/
public function getAvailableTemplateLayouts($pageUid): array
{
$templateLayouts = [];
// Check if the layouts are extended by ext_tables
if (isset($GLOBALS['TYPO3_CONF_VARS']['EXT']['news']['templateLayouts'])
&& is_array($GLOBALS['TYPO3_CONF_VARS']['EXT']['news']['templateLayouts'])
) {
$templateLayouts = $GLOBALS['TYPO3_CONF_VARS']['EXT']['news']['templateLayouts'];
}
// Add TsConfig values
foreach ($this->getTemplateLayoutsFromTsConfig($pageUid) as $templateKey => $title) {
if (str_starts_with($title, '--div--')) {
$optGroupParts = GeneralUtility::trimExplode(',', $title, true, 2);
$title = $optGroupParts[1];
$templateKey = $optGroupParts[0];
}
$templateLayouts[] = [$title, $templateKey];
}
return $templateLayouts;
}
/**
* Get template layouts defined in TsConfig
*
* @param $pageUid
* @return array
*/
protected function getTemplateLayoutsFromTsConfig(int $pageUid): array
{
$templateLayouts = [];
$pagesTsConfig = BackendUtility::getPagesTSconfig($pageUid);
if (isset($pagesTsConfig['tx_news.']['templateLayouts.']) && is_array($pagesTsConfig['tx_news.']['templateLayouts.'])) {
$templateLayouts = $pagesTsConfig['tx_news.']['templateLayouts.'];
}
return $templateLayouts;
}
}
@@ -0,0 +1,121 @@
<?php
namespace GeorgRinger\News\Utility;
/**
* 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\Utility\GeneralUtility;
/**
* TypoScript Utility class
*/
class TypoScript
{
/**
* @param array $base
* @param array $overload
* @return array
*/
public function override(array $base, array $overload): array
{
$configuration = $overload['settings']['overrideFlexformSettingsIfEmpty'] ?? '';
$validFields = GeneralUtility::trimExplode(',', $configuration, true);
foreach ($validFields as $fieldName) {
// Multilevel field
if (strpos($fieldName, '.') !== false) {
$keyAsArray = explode('.', $fieldName);
$foundInCurrentTs = $this->getValue($base, $keyAsArray);
if (is_string($foundInCurrentTs) && strlen($foundInCurrentTs) === 0) {
$foundInOriginal = $this->getValue($overload['settings'], $keyAsArray);
if ($foundInOriginal) {
$base = $this->setValue($base, $keyAsArray, $foundInOriginal);
}
}
} else {
// if flexform setting is empty and value is available in TS
if (((!isset($base[$fieldName]) || $base[$fieldName] === '0') || (strlen($base[$fieldName]) === 0))
&& isset($overload['settings'][$fieldName])
) {
$base[$fieldName] = $overload['settings'][$fieldName];
}
}
}
return $base;
}
/**
* Get value from array by path
*
* @param mixed $data
* @param mixed $path
* @return mixed
*/
protected function getValue($data, $path)
{
$found = true;
for ($x = 0; ($x < count($path) && $found); $x++) {
$key = $path[$x];
if (isset($data[$key])) {
$data = $data[$key];
} else {
$found = false;
}
}
if ($found) {
return $data;
}
return null;
}
/**
* Set value in array by path
*
* @param array $array
* @param array $path
* @param mixed $value
* @param string[] $path
*
* @return array
*/
protected function setValue(array $array, array $path, $value): array
{
$this->setValueByReference($array, $path, $value);
$final = array_merge_recursive([], $array);
return $final;
}
/**
* Set value by reference
*
* @param array $array
* @param array $path
* @param $value
*
* @return void
*/
private function setValueByReference(array &$array, array $path, $value): void
{
while (count($path) > 1) {
$key = array_shift($path);
if (!isset($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$key = reset($path);
$array[$key] = $value;
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace GeorgRinger\News\Utility;
/**
* 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\Utility\GeneralUtility;
/**
* Url Utility class
*/
class Url
{
/**
* Prepend current url if url is relative
*
* @param string $url given url
* @return string
*/
public static function prependDomain(string $url): string
{
if (!str_starts_with($url, GeneralUtility::getIndpEnv('TYPO3_SITE_URL'))) {
$url = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . $url;
}
return $url;
}
}
@@ -0,0 +1,63 @@
<?php
namespace GeorgRinger\News\Utility;
/**
* 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\Utility\GeneralUtility;
/**
* Validation
*/
class Validation
{
/**
* Validate ordering as extbase can't handle that currently
*
* @param string $fieldToCheck
* @param string $allowedSettings
* @return bool
*/
public static function isValidOrdering($fieldToCheck, $allowedSettings): bool
{
$isValid = true;
if (empty($fieldToCheck)) {
return $isValid;
}
if (empty($allowedSettings)) {
return false;
}
$fields = GeneralUtility::trimExplode(',', $fieldToCheck, true);
foreach ($fields as $field) {
if ($isValid === true) {
$split = GeneralUtility::trimExplode(' ', $field, true);
$count = count($split);
switch ($count) {
case 1:
if (!GeneralUtility::inList($allowedSettings, $split[0])) {
$isValid = false;
}
break;
case 2:
if ((strtolower($split[1]) !== 'desc' && strtolower($split[1]) !== 'asc') ||
!GeneralUtility::inList($allowedSettings, $split[0])
) {
$isValid = false;
}
break;
default:
$isValid = false;
}
}
}
return $isValid;
}
}