[TASK] Update base extension

This commit is contained in:
Sebastian Fischer 2024-12-14 14:03:20 +01:00
parent 2e75454bb0
commit 47e55d8f48
62 changed files with 2152 additions and 290 deletions

0
.gitlab-ci.yml Executable file → Normal file
View File

13
Classes/Command/ContentElementCommand.php Executable file → Normal file
View File

@ -1,5 +1,7 @@
<?php
namespace Evoweb\EwBase\Command;
/*
* This file is developed by evoWeb.
*
@ -11,8 +13,6 @@
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
@ -28,15 +28,18 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
class ContentElementCommand extends Command
{
public function __construct(protected ConnectionPool $connectionPool)
protected ConnectionPool $connectionPool;
public function __construct(ConnectionPool $connectionPool)
{
$this->connectionPool = $connectionPool;
parent::__construct();
}
protected function configure(): void
protected function configure()
{
$this
->setAliases(['ce'])
->setAliases(['kc-sitepackage'])
->addOption(
'pageId',
'-p',

55
Classes/Configuration/AdditionalConfiguration.php Executable file → Normal file
View File

@ -2,19 +2,18 @@
declare(strict_types=1);
namespace Evoweb\EwBase\Configuration;
/*
* This file is developed by evoWeb.
* This file is part of TYPO3 CMS-based extension "container" by b13.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\Configuration;
use Mfc\OAuth2\ResourceServer\GitLab;
use Mfc\OAuth2\ResourceServer\Registry;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
@ -23,6 +22,24 @@ class AdditionalConfiguration
{
protected string $extensionKey = 'ew_base';
protected array $oauthOptions = [
'enabled' => true, // Enable/Disable the provider
'arguments' => [
'appId' => '',
'appSecret' => '',
'projectName' => '',
'gitlabServer' => 'https://github.com',
// User level at which the user will be given admin permissions
'gitlabAdminUserLevel' => 30,
// Groups to assign to the User (comma separated list possible)
'gitlabDefaultGroups' => 1,
// UserConfig db and/or file mount from groups
'gitlabUserOption' => 3,
// Blocks users with flag external from access the backend
'blockExternalUser' => false,
],
];
protected array $developConfig = [
'BE' => [
'debug' => true,
@ -57,8 +74,7 @@ class AdditionalConfiguration
*/
protected array $mailConfig = [
'transport' => 'smtp',
'transport_smtp_server' => '127.0.0.1:1025',
'defaultMailFromAddress' => 'test@dev.arpa',
'transport_smtp_server' => '127.0.0.1:1025'
];
public function initialize(array $configuration = []): void
@ -66,7 +82,6 @@ class AdditionalConfiguration
$this->addContextToSitename();
$this->addContextConfiguration($configuration);
if (Environment::getContext() == 'Development') {
$this->addBaseUrl();
$this->addDebugConfiguration();
}
$this->addFurtherConfigurationFiles();
@ -77,28 +92,6 @@ class AdditionalConfiguration
$GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] .= ' - ' . Environment::getContext();
}
/**
* Set the baseurl on local environments automatically
*/
protected function addBaseUrl(): void
{
if (Environment::isCli()) {
return;
}
$remoteHost = GeneralUtility::getIndpEnv('HTTP_HOST');
ExtensionManagementUtility::addTypoScript(
$this->extensionKey,
'constants',
'
// condition should trigger different cache hashes
[request && request.getNormalizedParams().getHttpHost() == \'' . $remoteHost . '\']
config.baseURL = ' . $remoteHost . '
[end]
',
'defaultContentRendering'
);
}
protected function addDebugConfiguration(): void
{
$GLOBALS['TYPO3_CONF_VARS'] = $this->arrayMergeRecursive(

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Evoweb\EwBase\DataProcessing;
/*
* This file is part of TYPO3 CMS-based extension "container" by b13.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*/
use B13\Container\DataProcessing\ContainerProcessor as BaseContainerProcessor;
use B13\Container\Domain\Model\Container;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\RecordsContentObject;
class ContainerProcessor extends BaseContainerProcessor
{
protected function processColPos(
ContentObjectRenderer $cObj,
Container $container,
int $colPos,
string $as,
array $processedData,
array $processorConfiguration
): array {
$children = $container->getChildrenByColPos($colPos);
if (!$processorConfiguration['doNotProcessChildren']) {
$contentRecordRenderer = new RecordsContentObject();
$conf = [
'tables' => 'tt_content'
];
foreach ($children as &$child) {
if ($child['l18n_parent'] > 0) {
$conf['source'] = $child['l18n_parent'];
} else {
$conf['source'] = $child['uid'];
}
if ($child['t3ver_oid'] > 0) {
$conf['source'] = $child['t3ver_oid'];
}
$child['renderedContent'] = $cObj->render($contentRecordRenderer, $conf);
/** @var ContentObjectRenderer $recordContentObjectRenderer */
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$recordContentObjectRenderer->start($child, 'tt_content');
$child = $this->contentDataProcessor->process($recordContentObjectRenderer, $processorConfiguration, $child);
}
}
$processedData[$as] = $children;
return $processedData;
}
}

View File

@ -0,0 +1,831 @@
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace Evoweb\EwBase\DataProcessing;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\Result;
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\DocumentTypeExclusionRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Versioning\VersionState;
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Fetch records from the database, using the default .select syntax from TypoScript.
*
* This way, e.g. a FLUIDTEMPLATE cObject can iterate over the array of records.
*
* Example TypoScript configuration:
*
* 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
* 10 {
* table = tt_address
* pidInList = 123
* where = company="Acme" AND first_name="Ralph"
* orderBy = sorting DESC
* as = addresses
* dataProcessing {
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
* 10 {
* references.fieldName = image
* }
* }
* }
*
* where "as" means the variable to be containing the result-set from the DB query.
*/
class DatabaseQueryProcessor implements DataProcessorInterface
{
protected ContentDataProcessor $contentDataProcessor;
protected ContentObjectRenderer $cObj;
/**
* Constructor
*/
public function __construct()
{
$this->contentDataProcessor = GeneralUtility::makeInstance(ContentDataProcessor::class);
}
/**
* Fetches records from the database as an array
*
* @param ContentObjectRenderer $cObj The data of the content element or page
* @param array $contentObjectConfiguration The configuration of Content Object
* @param array $processorConfiguration The configuration of this processor
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
*
* @return array the processed data as key/value store
*/
public function process(
ContentObjectRenderer $cObj,
array $contentObjectConfiguration,
array $processorConfiguration,
array $processedData
) {
$this->cObj = $cObj;
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
return $processedData;
}
// the table to query, if none given, exit
$tableName = $cObj->stdWrapValue('table', $processorConfiguration);
if (empty($tableName)) {
return $processedData;
}
if (isset($processorConfiguration['table.'])) {
unset($processorConfiguration['table.']);
}
if (isset($processorConfiguration['table'])) {
unset($processorConfiguration['table']);
}
// The variable to be used within the result
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'records');
// Execute a SQL statement to fetch the records
$records = $this->getRecords($tableName, $processorConfiguration);
$request = $cObj->getRequest();
$processedRecordVariables = [];
foreach ($records as $key => $record) {
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$recordContentObjectRenderer->start($record, $tableName, $request);
$processedRecordVariables[$key] = ['data' => $record];
$processedRecordVariables[$key] = $this->contentDataProcessor->process(
$recordContentObjectRenderer,
$processorConfiguration,
$processedRecordVariables[$key]
);
}
$processedData[$targetVariableName] = $processedRecordVariables;
return $processedData;
}
protected function getRecords(string $tableName, array $queryConfiguration): array
{
$records = [];
$statement = $this->exec_getQuery($tableName, $queryConfiguration);
$tsfe = $this->getTypoScriptFrontendController();
while ($row = $statement->fetchAssociative()) {
// Versioning preview:
$tsfe->sys_page->versionOL($tableName, $row, true);
// Language overlay:
if (is_array($row)) {
$row = $tsfe->sys_page->getLanguageOverlay($tableName, $row);
}
// Might be unset in the language overlay
if (is_array($row)) {
$records[] = $row;
}
}
return $records;
}
protected function exec_getQuery(string $table, array $conf): Result
{
$statement = $this->getQuery($table, $conf);
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
return $connection->executeQuery($statement);
}
public function getQuery($table, $conf, $returnQueryArray = false)
{
// Resolve stdWrap in these properties first
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
$properties = [
'pidInList',
'uidInList',
'languageField',
'selectFields',
'max',
'begin',
'groupBy',
'orderBy',
'join',
'leftjoin',
'rightjoin',
'recursive',
'where',
];
foreach ($properties as $property) {
$conf[$property] = trim(
isset($conf[$property . '.'])
? (string)$this->cObj->stdWrap($conf[$property] ?? '', $conf[$property . '.'] ?? [])
: (string)($conf[$property] ?? '')
);
if ($conf[$property] === '') {
unset($conf[$property]);
} elseif (in_array($property, ['languageField', 'selectFields', 'join', 'leftjoin', 'rightjoin', 'where'], true)) {
$conf[$property] = QueryHelper::quoteDatabaseIdentifiers($connection, $conf[$property]);
}
if (isset($conf[$property . '.'])) {
// stdWrapping already done, so remove the sub-array
unset($conf[$property . '.']);
}
}
// Handle PDO-style named parameter markers first
$queryMarkers = $this->getQueryMarkers($table, $conf);
// Replace the markers in the non-stdWrap properties
foreach ($queryMarkers as $marker => $markerValue) {
$properties = [
'uidInList',
'selectFields',
'where',
'max',
'begin',
'groupBy',
'orderBy',
'join',
'leftjoin',
'rightjoin',
];
foreach ($properties as $property) {
if ($conf[$property] ?? false) {
$conf[$property] = str_replace('###' . $marker . '###', (string)$markerValue, $conf[$property]);
}
}
}
// Construct WHERE clause:
// Handle recursive function for the pidInList
if (isset($conf['recursive'])) {
$conf['recursive'] = (int)$conf['recursive'];
if ($conf['recursive'] > 0) {
$pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true);
array_walk($pidList, function (&$storagePid) {
if ($storagePid === 'this') {
$storagePid = $this->getTypoScriptFrontendController()->id;
}
});
$expandedPidList = $this->getTypoScriptFrontendController()->sys_page->getPageIdsRecursive($pidList, $conf['recursive']);
$conf['pidInList'] = implode(',', $expandedPidList);
}
}
if ((string)($conf['pidInList'] ?? '') === '') {
$conf['pidInList'] = 'this';
}
$queryParts = $this->getQueryConstraints($table, $conf);
$queryBuilder = $connection->createQueryBuilder();
// @todo Check against getQueryConstraints, can probably use FrontendRestrictions
// @todo here and remove enableFields there.
$queryBuilder->getRestrictions()->removeAll();
$queryBuilder->select('*')->from($table);
if ($queryParts['where'] ?? false) {
$queryBuilder->where($queryParts['where']);
}
if ($queryParts['groupBy'] ?? false) {
$queryBuilder->groupBy(...$queryParts['groupBy']);
}
if (is_array($queryParts['orderBy'] ?? false)) {
foreach ($queryParts['orderBy'] as $orderBy) {
$queryBuilder->addOrderBy(...$orderBy);
}
}
// Fields:
if ($conf['selectFields'] ?? false) {
$queryBuilder->selectLiteral($this->sanitizeSelectPart($conf['selectFields'], $table));
}
// Setting LIMIT:
$error = false;
if (($conf['max'] ?? false) || ($conf['begin'] ?? false)) {
// Finding the total number of records, if used:
if (str_contains(strtolower(($conf['begin'] ?? '') . ($conf['max'] ?? '')), 'total')) {
$countQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$countQueryBuilder->getRestrictions()->removeAll();
$countQueryBuilder->count('*')
->from($table)
->where($queryParts['where']);
if ($queryParts['groupBy']) {
$countQueryBuilder->groupBy(...$queryParts['groupBy']);
}
try {
$count = $countQueryBuilder->executeQuery()->fetchOne();
if (isset($conf['max'])) {
$conf['max'] = str_ireplace('total', $count, (string)$conf['max']);
}
if (isset($conf['begin'])) {
$conf['begin'] = str_ireplace('total', $count, (string)$conf['begin']);
}
} catch (DBALException $e) {
$this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage());
$error = true;
}
}
if (!$error) {
if (isset($conf['begin']) && $conf['begin'] > 0) {
$conf['begin'] = MathUtility::forceIntegerInRange((int)ceil($this->calc($conf['begin'])), 0);
$queryBuilder->setFirstResult($conf['begin']);
}
if (isset($conf['max'])) {
$conf['max'] = MathUtility::forceIntegerInRange((int)ceil($this->calc($conf['max'])), 0);
$queryBuilder->setMaxResults($conf['max'] ?: 100000);
}
}
}
if (!$error) {
// Setting up tablejoins:
if ($conf['join'] ?? false) {
$joinParts = QueryHelper::parseJoin($conf['join']);
$queryBuilder->join(
$table,
$joinParts['tableName'],
$joinParts['tableAlias'],
$joinParts['joinCondition']
);
} elseif ($conf['leftjoin'] ?? false) {
$joinParts = QueryHelper::parseJoin($conf['leftjoin']);
$queryBuilder->leftJoin(
$table,
$joinParts['tableName'],
$joinParts['tableAlias'],
$joinParts['joinCondition']
);
} elseif ($conf['rightjoin'] ?? false) {
$joinParts = QueryHelper::parseJoin($conf['rightjoin']);
$queryBuilder->rightJoin(
$table,
$joinParts['tableName'],
$joinParts['tableAlias'],
$joinParts['joinCondition']
);
}
// Convert the QueryBuilder object into a SQL statement.
$query = $queryBuilder->getSQL();
// Replace the markers in the queryParts to handle stdWrap enabled properties
foreach ($queryMarkers as $marker => $markerValue) {
// @todo Ugly hack that needs to be cleaned up, with the current architecture
// @todo for exec_Query / getQuery it's the best we can do.
$query = str_replace('###' . $marker . '###', (string)$markerValue, $query);
}
return $returnQueryArray ? $this->getQueryArray($queryBuilder) : $query;
}
return '';
}
/**
* Helper to transform a QueryBuilder object into a queryParts array that can be used
* with exec_SELECT_queryArray
*
* @return array
* @throws \RuntimeException
*/
protected function getQueryArray(QueryBuilder $queryBuilder)
{
$fromClauses = [];
$knownAliases = [];
$queryParts = [];
// Loop through all FROM clauses
foreach ($queryBuilder->getQueryPart('from') as $from) {
if ($from['alias'] === null) {
$tableSql = $from['table'];
$tableReference = $from['table'];
} else {
$tableSql = $from['table'] . ' ' . $from['alias'];
$tableReference = $from['alias'];
}
$knownAliases[$tableReference] = true;
$fromClauses[$tableReference] = $tableSql . $this->getQueryArrayJoinHelper(
$tableReference,
$queryBuilder->getQueryPart('join'),
$knownAliases
);
}
$queryParts['SELECT'] = implode(', ', $queryBuilder->getQueryPart('select'));
$queryParts['FROM'] = implode(', ', $fromClauses);
$queryParts['WHERE'] = (string)$queryBuilder->getQueryPart('where') ?: '';
$queryParts['GROUPBY'] = implode(', ', $queryBuilder->getQueryPart('groupBy'));
$queryParts['ORDERBY'] = implode(', ', $queryBuilder->getQueryPart('orderBy'));
if ($queryBuilder->getFirstResult() > 0) {
$queryParts['LIMIT'] = $queryBuilder->getFirstResult() . ',' . $queryBuilder->getMaxResults();
} elseif ($queryBuilder->getMaxResults() > 0) {
$queryParts['LIMIT'] = $queryBuilder->getMaxResults();
}
return $queryParts;
}
/**
* Helper to transform the QueryBuilder join part into a SQL fragment.
*
* @throws \RuntimeException
*/
protected function getQueryArrayJoinHelper(string $fromAlias, array $joinParts, array &$knownAliases): string
{
$sql = '';
if (isset($joinParts['join'][$fromAlias])) {
foreach ($joinParts['join'][$fromAlias] as $join) {
if (array_key_exists($join['joinAlias'], $knownAliases)) {
throw new \RuntimeException(
'Non unique join alias: "' . $join['joinAlias'] . '" found.',
1472748872
);
}
$sql .= ' ' . strtoupper($join['joinType'])
. ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
. ' ON ' . ((string)$join['joinCondition']);
$knownAliases[$join['joinAlias']] = true;
}
foreach ($joinParts['join'][$fromAlias] as $join) {
$sql .= $this->getQueryArrayJoinHelper($join['joinAlias'], $joinParts, $knownAliases);
}
}
return $sql;
}
/**
* Builds list of marker values for handling PDO-like parameter markers in select parts.
* Marker values support stdWrap functionality thus allowing a way to use stdWrap functionality in various
* properties of 'select' AND prevents SQL-injection problems by quoting and escaping of numeric values, strings,
* NULL values and comma separated lists.
*
* @param string $table Table to select records from
* @param array $conf Select part of CONTENT definition
* @return array List of values to replace markers with
* @internal
* @see getQuery()
*/
public function getQueryMarkers(string $table, array $conf): array
{
if (!isset($conf['markers.']) || !is_array($conf['markers.'])) {
return [];
}
// Parse markers and prepare their values
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
$markerValues = [];
foreach ($conf['markers.'] as $dottedMarker => $dummy) {
$marker = rtrim($dottedMarker, '.');
if ($dottedMarker != $marker . '.') {
continue;
}
// Parse definition
// todo else value is always null
$tempValue = isset($conf['markers.'][$dottedMarker])
? $this->cObj->stdWrap($conf['markers.'][$dottedMarker]['value'] ?? '', $conf['markers.'][$dottedMarker])
: $conf['markers.'][$dottedMarker]['value'];
// Quote/escape if needed
if (is_numeric($tempValue)) {
if ((int)$tempValue == $tempValue) {
// Handle integer
$markerValues[$marker] = (int)$tempValue;
} else {
// Handle float
$markerValues[$marker] = (float)$tempValue;
}
} elseif ($tempValue === null) {
// It represents NULL
$markerValues[$marker] = 'NULL';
} elseif (!empty($conf['markers.'][$dottedMarker]['commaSeparatedList'])) {
// See if it is really a comma separated list of values
$explodeValues = GeneralUtility::trimExplode(',', $tempValue);
if (count($explodeValues) > 1) {
// Handle each element of list separately
$tempArray = [];
foreach ($explodeValues as $listValue) {
if (is_numeric($listValue)) {
if ((int)$listValue == $listValue) {
$tempArray[] = (int)$listValue;
} else {
$tempArray[] = (float)$listValue;
}
} else {
// If quoted, remove quotes before
// escaping.
if (preg_match('/^\'([^\']*)\'$/', $listValue, $matches)) {
$listValue = $matches[1];
} elseif (preg_match('/^\\"([^\\"]*)\\"$/', $listValue, $matches)) {
$listValue = $matches[1];
}
$tempArray[] = $connection->quote($listValue);
}
}
$markerValues[$marker] = implode(',', $tempArray);
} else {
// Handle remaining values as string
$markerValues[$marker] = $connection->quote($tempValue);
}
} else {
// Handle remaining values as string
$markerValues[$marker] = $connection->quote($tempValue);
}
}
return $markerValues;
}
/**
* Helper function for getQuery(), creating the WHERE clause of the SELECT query
*
* @param string $table The table name
* @param array $conf The TypoScript configuration properties
* @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments
* @throws \InvalidArgumentException
* @see getQuery()
*/
protected function getQueryConstraints(string $table, array $conf): array
{
// Init:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$expressionBuilder = $queryBuilder->expr();
$tsfe = $this->getTypoScriptFrontendController();
$constraints = [];
$pid_uid_flag = 0;
$enableFieldsIgnore = [];
$queryParts = [
'where' => null,
'groupBy' => null,
'orderBy' => null,
];
$isInWorkspace = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'isOffline');
$considerMovePointers = (
$isInWorkspace && $table !== 'pages'
&& !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
);
if (trim($conf['uidInList'] ?? '')) {
$listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$tsfe->contentPid, $conf['uidInList']));
// If moved records shall be considered, select via t3ver_oid
if ($considerMovePointers) {
$constraints[] = (string)$expressionBuilder->orX(
$expressionBuilder->in($table . '.uid', $listArr),
$expressionBuilder->andX(
$expressionBuilder->eq(
$table . '.t3ver_state',
(int)(string)VersionState::cast(VersionState::MOVE_POINTER)
),
$expressionBuilder->in($table . '.t3ver_oid', $listArr)
)
);
} else {
$constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr);
}
$pid_uid_flag++;
}
// Static_* tables are allowed to be fetched from root page
if (strpos($table, 'static_') === 0) {
$pid_uid_flag++;
}
if (trim($conf['pidInList'])) {
$listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$tsfe->contentPid, $conf['pidInList']));
// Removes all pages which are not visible for the user!
$listArr = $this->checkPidArray($listArr);
if (GeneralUtility::inList($conf['pidInList'], 'root')) {
$listArr[] = 0;
}
if (GeneralUtility::inList($conf['pidInList'], '-1')) {
$listArr[] = -1;
$enableFieldsIgnore['pid'] = true;
}
if (!empty($listArr)) {
$constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr));
$pid_uid_flag++;
} else {
// If not uid and not pid then uid is set to 0 - which results in nothing!!
$pid_uid_flag = 0;
}
}
// If not uid and not pid then uid is set to 0 - which results in nothing!!
if (!$pid_uid_flag && trim($conf['pidInList'] ?? '') != 'ignore') {
$constraints[] = $expressionBuilder->eq($table . '.uid', 0);
}
$where = trim((string)$this->cObj->stdWrapValue('where', $conf ?? []));
if ($where) {
$constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
}
// Check if the default language should be fetched (= doing overlays), or if only the records of a language should be fetched
// but only do this for TCA tables that have languages enabled
$languageConstraint = $this->getLanguageRestriction($expressionBuilder, $table, $conf, GeneralUtility::makeInstance(Context::class));
if ($languageConstraint !== null) {
$constraints[] = $languageConstraint;
}
// Enablefields
if ($table === 'pages') {
$constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_hid_del);
$constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->where_groupAccess);
} else {
$constraints[] = QueryHelper::stripLogicalOperatorPrefix($tsfe->sys_page->enableFields($table, -1, $enableFieldsIgnore));
}
// MAKE WHERE:
if (count($constraints) !== 0) {
$queryParts['where'] = $expressionBuilder->andX(...$constraints);
}
// GROUP BY
$groupBy = trim((string)$this->cObj->stdWrapValue('groupBy', $conf ?? []));
if ($groupBy) {
$queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy);
}
// ORDER BY
$orderByString = trim((string)$this->cObj->stdWrapValue('orderBy', $conf ?? []));
if ($orderByString) {
$queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString);
}
// Return result:
return $queryParts;
}
/**
* Removes Page UID numbers from the input array which are not available due to enableFields() or the list of bad doktype numbers ($this->checkPid_badDoktypeList)
*
* @param int[] $pageIds Array of Page UID numbers for select and for which pages with enablefields and bad doktypes should be removed.
* @return array Returns the array of remaining page UID numbers
* @internal
*/
public function checkPidArray(array $pageIds): array
{
if (empty($pageIds)) {
return [];
}
$restrictionContainer = GeneralUtility::makeInstance(FrontendRestrictionContainer::class);
$restrictionContainer->add(GeneralUtility::makeInstance(
DocumentTypeExclusionRestriction::class,
GeneralUtility::intExplode(',', (string)$this->cObj->checkPid_badDoktypeList, true)
));
return $this->getTypoScriptFrontendController()->sys_page->filterAccessiblePageIds($pageIds, $restrictionContainer);
}
/**
* Adds parts to the WHERE clause that are related to language.
* This only works on TCA tables which have the [ctrl][languageField] field set or if they
* have select.languageField = my_language_field set explicitly.
*
* It is also possible to disable the language restriction for a query by using select.languageField = 0,
* if select.languageField is not explicitly set, the TCA default values are taken.
*
* If the table is "localizeable" (= any of the criteria above is met), then the DB query is restricted:
*
* If the current language aspect has overlays enabled, then the only records with language "0" or "-1" are
* fetched (the overlays are taken care of later-on).
* if the current language has overlays but also records without localization-parent (free mode) available,
* then these are fetched as well. This can explicitly set via select.includeRecordsWithoutDefaultTranslation = 1
* which overrules the overlayType within the language aspect.
*
* If the language aspect has NO overlays enabled, it behaves as in "free mode" (= only fetch the records
* for the current language.
*
* @param ExpressionBuilder $expressionBuilder
* @param string $table
* @param array $conf
* @param Context $context
* @return string|\TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression|null
* @throws \TYPO3\CMS\Core\Context\Exception\AspectNotFoundException
*/
protected function getLanguageRestriction(ExpressionBuilder $expressionBuilder, string $table, array $conf, Context $context)
{
$languageField = '';
$localizationParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null;
// Check if the table is translatable, and set the language field by default from the TCA information
if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
$languageField = $conf['languageField'];
} elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($localizationParentField)) {
$languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
}
}
// No language restriction enabled explicitly or available via TCA
if (empty($languageField)) {
return null;
}
/** @var LanguageAspect $languageAspect */
$languageAspect = $context->getAspect('language');
if ($languageAspect->doOverlays() && !empty($localizationParentField)) {
// Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
// OVERLAY the records with localized versions!
$languageQuery = $expressionBuilder->in($languageField, [0, -1]);
// Use this option to include records that don't have a default language counterpart ("free mode")
// (originalpointerfield is 0 and the language field contains the requested language)
if (isset($conf['includeRecordsWithoutDefaultTranslation']) || !empty($conf['includeRecordsWithoutDefaultTranslation.'])) {
$includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.'])
? $this->cObj->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.'])
: $conf['includeRecordsWithoutDefaultTranslation'];
$includeRecordsWithoutDefaultTranslation = trim($includeRecordsWithoutDefaultTranslation) !== '';
} else {
// Option was not explicitly set, check what's in for the language overlay type.
$includeRecordsWithoutDefaultTranslation = $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_ON_WITH_FLOATING;
}
if ($includeRecordsWithoutDefaultTranslation) {
$languageQuery = $expressionBuilder->orX(
$languageQuery,
$expressionBuilder->andX(
$expressionBuilder->eq($table . '.' . $localizationParentField, 0),
$expressionBuilder->eq($languageField, $languageAspect->getContentId())
)
);
}
return $languageQuery;
}
// No overlays = only fetch records given for the requested language and "all languages"
return $expressionBuilder->in($languageField, [$languageAspect->getContentId(), -1]);
}
/**
* Helper function for getQuery, sanitizing the select part
*
* This functions checks if the necessary fields are part of the select
* and adds them if necessary.
*
* @param string $selectPart Select part
* @param string $table Table to select from
* @return string Sanitized select part
* @internal
* @see getQuery
*/
protected function sanitizeSelectPart(string $selectPart, string $table): string
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
// Pattern matching parts
$matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)';
$matchEnd = '(\\s*,|\\s*$)/';
$necessaryFields = ['uid', 'pid'];
$wsFields = ['t3ver_state'];
if (isset($GLOBALS['TCA'][$table]) && !preg_match($matchStart . '\\*' . $matchEnd, $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)|distinct/i', $selectPart)) {
foreach ($necessaryFields as $field) {
$match = $matchStart . $field . $matchEnd;
if (!preg_match($match, $selectPart)) {
$selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
}
}
if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] ?? false) {
foreach ($wsFields as $field) {
$match = $matchStart . $field . $matchEnd;
if (!preg_match($match, $selectPart)) {
$selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
}
}
}
}
return $selectPart;
}
/**
* Performs basic mathematical evaluation of the input string. Does NOT take parenthesis and operator precedence into account! (for that, see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction())
*
* @param string $val The string to evaluate. Example: "3+4*10/5" will generate "35". Only integer numbers can be used.
* @return int The result (might be a float if you did a division of the numbers).
* @see \TYPO3\CMS\Core\Utility\MathUtility::calculateWithPriorityToAdditionAndSubtraction()
*/
public function calc($val)
{
$parts = GeneralUtility::splitCalc($val, '+-*/');
$value = 0;
foreach ($parts as $part) {
$theVal = $part[1];
$sign = $part[0];
if ((string)(int)$theVal === (string)$theVal) {
$theVal = (int)$theVal;
} else {
$theVal = 0;
}
if ($sign === '-') {
$value -= $theVal;
}
if ($sign === '+') {
$value += $theVal;
}
if ($sign === '/') {
if ((int)$theVal) {
$value /= (int)$theVal;
}
}
if ($sign === '*') {
$value *= $theVal;
}
}
return $value;
}
/**
* @return TimeTracker
*/
protected function getTimeTracker()
{
return GeneralUtility::makeInstance(TimeTracker::class);
}
/**
* Returns the current BE user.
*
* @return FrontendBackendUserAuthentication
*/
protected function getFrontendBackendUser()
{
return $GLOBALS['BE_USER'];
}
/**
* @return TypoScriptFrontendController|null
*/
protected function getTypoScriptFrontendController()
{
return $GLOBALS['TSFE'] ?? null;
}
}

18
Classes/EventListener/CssMerger.php Executable file → Normal file
View File

@ -1,19 +1,7 @@
<?php
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Page\AssetCollector;
use TYPO3\CMS\Core\Page\Event\BeforeStylesheetsRenderingEvent;
@ -22,9 +10,10 @@ use TYPO3\CMS\Core\Utility\PathUtility;
class CssMerger
{
public function __construct(protected ExtensionConfiguration $extensionConfiguration) {}
public function __construct(protected ExtensionConfiguration $extensionConfiguration)
{
}
#[AsEventListener('evoweb-ew-base-beforestylesheets', BeforeStylesheetsRenderingEvent::class)]
public function __invoke(BeforeStylesheetsRenderingEvent $event): void
{
if ($event->isInline() && $event->isPriority()) {
@ -45,7 +34,6 @@ class CssMerger
$assetCollector->addInlineStyleSheet('ew_base', $styles, [], ['priority' => true]);
} else {
$temporaryFile = GeneralUtility::writeStyleSheetContentToTemporaryFile($styles);
// @extensionScannerIgnoreLine
$assetCollector->addStyleSheet('combined_styles', $temporaryFile);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Evoweb\EwBase\EventListener;
use TYPO3\CMS\Backend\ViEvoweb\Event\IsContentUsedOnPageLayoutEvent;
class IsContentUsedOnPageLayout
{
public function __invoke(IsContentUsedOnPageLayoutEvent $event): void
{
$event->setUsed($event->isRecordUsed() || $this->findCTypeBegin($event->getRecord()['CType']));
}
public function findCTypeBegin($cType): bool
{
$found = false;
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['ContentUsedStrings'] ?? [] as $search) {
if (strpos($cType, $search) !== false) {
$found = true;
break;
}
}
return $found;
}
}

13
Classes/EventListener/JsMerger.php Executable file → Normal file
View File

@ -1,19 +1,7 @@
<?php
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\EventListener;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Page\AssetCollector;
use TYPO3\CMS\Core\Page\Event\BeforeJavaScriptsRenderingEvent;
use TYPO3\CMS\Core\Utility\GeneralUtility;
@ -21,7 +9,6 @@ use TYPO3\CMS\Core\Utility\PathUtility;
class JsMerger
{
#[AsEventListener('evoweb-ew-base-beforejavascript', BeforeJavaScriptsRenderingEvent::class)]
public function __invoke(BeforeJavaScriptsRenderingEvent $event): void
{
if ($event->isInline() && $event->isPriority()) {

67
Classes/Form/Element/PickColorFromImage.php Executable file → Normal file
View File

@ -2,6 +2,8 @@
declare(strict_types=1);
namespace Evoweb\EwBase\Form\Element;
/*
* This file is developed by evoWeb.
*
@ -13,14 +15,8 @@ declare(strict_types=1);
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\Form\Element;
use Psr\EventDispatcher\EventDispatcherInterface;
use TYPO3\CMS\Backend\Form\Element\AbstractFormElement;
use TYPO3\CMS\Backend\Form\Event\ModifyImageManipulationPreviewUrlEvent;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\View\BackendViewFactory;
use TYPO3\CMS\Core\Crypto\HashService;
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Imaging\ImageManipulation\InvalidConfigurationException;
@ -34,8 +30,6 @@ use TYPO3\CMS\Core\Utility\StringUtility;
class PickColorFromImage extends AbstractFormElement
{
private string $wizardRouteName = 'ajax_wizard_image_manipulation';
/**
* Default element configuration
*/
@ -114,11 +108,9 @@ class PickColorFromImage extends AbstractFormElement
public function __construct(
private readonly BackendViewFactory $backendViewFactory,
private readonly UriBuilder $uriBuilder,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly ResourceFactory $resourceFactory,
private readonly HashService $hashService,
) {}
) {
}
public function render(): array
{
@ -146,6 +138,14 @@ class PickColorFromImage extends AbstractFormElement
$fieldWizardHtml = $fieldWizardResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
$width = $this->formMaxWidth(
MathUtility::forceIntegerInRange(
$config['size'] ?? $this->defaultInputWidth,
$this->minimumInputWidth,
$this->maxInputWidth
)
);
$arguments = [
'fieldInformation' => $fieldInformationHtml,
'fieldControl' => $fieldControlHtml,
@ -164,15 +164,12 @@ class PickColorFromImage extends AbstractFormElement
'validation' => '[]',
],
'config' => $config,
'wizardUri' => $this->getWizardUri(),
'wizardPayload' => json_encode($this->getWizardPayload($config['cropVariants'], $file)),
'previewUrl' => $this->eventDispatcher->dispatch(
new ModifyImageManipulationPreviewUrlEvent($this->data['databaseRow'], $config, $file)
)->getPreviewUrl(),
'width' => $width,
];
if ($arguments['isAllowedFileExtension']) {
$fieldId = StringUtility::getUniqueId('formengine-color-');
$resultArray['stylesheetFiles'][] =
'EXT:ew_base/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.css';
$resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create(
@ -189,6 +186,9 @@ class PickColorFromImage extends AbstractFormElement
}
}
$view = $this->backendViewFactory->create($this->data['request']);
$templatePaths = $view->getRenderingContext()->getTemplatePaths();
$templatePaths->setTemplateRootPaths(['EXT:ew_base/Resources/Private/Templates']);
$templatePaths->setPartialRootPaths(['EXT:ew_base/Resources/Private/Partials']);
$view->assignMultiple($arguments);
$resultArray['html'] = $view->render('Form/ImageManipulationElement');
@ -211,19 +211,12 @@ class PickColorFromImage extends AbstractFormElement
return $file;
}
/**
* @throws InvalidConfigurationException
*/
protected function populateConfiguration(array $baseConfiguration): array
{
$defaultConfig = self::$defaultConfig;
// If ratios are set do not add default options
if (isset($baseConfiguration['cropVariants'])) {
unset($defaultConfig['cropVariants']);
}
$config = array_replace_recursive($defaultConfig, $baseConfiguration);
$imageConfig = $this->data['processedTca']['columns'][$config['imageField']];
$config['cropVariants'] = $imageConfig['config']['cropVariants'] ?? $defaultConfig['cropVariants'];
if (!is_array($config['cropVariants'])) {
throw new InvalidConfigurationException('Crop variants configuration must be an array', 1485377267);
@ -235,7 +228,7 @@ class PickColorFromImage extends AbstractFormElement
$cropVariant['allowedAspectRatios'] = array_filter(
$cropVariant['allowedAspectRatios'] ?? [],
static function ($aspectRatio) {
return !($aspectRatio['disabled'] ?? false);
return !(bool)($aspectRatio['disabled'] ?? false);
}
);
@ -261,8 +254,8 @@ class PickColorFromImage extends AbstractFormElement
$config['cropVariants'] = $cropVariants;
// By default, we allow all image extensions that can be handled by the GFX functionality
$config['allowedExtensions'] ??= $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'];
return $config;
}
@ -280,22 +273,4 @@ class PickColorFromImage extends AbstractFormElement
);
return $config;
}
protected function getWizardUri(): string
{
return (string)$this->uriBuilder->buildUriFromRoute($this->wizardRouteName);
}
protected function getWizardPayload(array $cropVariants, File $image): array
{
$uriArguments = [];
$arguments = [
'cropVariants' => $cropVariants,
'image' => $image->getUid(),
];
$uriArguments['arguments'] = json_encode($arguments);
$uriArguments['signature'] = $this->hashService->hmac((string)($uriArguments['arguments']), $this->wizardRouteName);
return $uriArguments;
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Evoweb\EwBase\Hooks;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
class PageLayoutView
{
public function contentIsUsed(array $params): bool
{
return $params['used'] || $this->findCTypeBegin($params['record']['CType']);
}
public function findCTypeBegin($cType): bool
{
$found = false;
foreach ($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['ContentUsedStrings'] ?? [] as $search) {
if (str_contains($cType, $search)) {
$found = true;
break;
}
}
return $found;
}
}

9
Classes/ToolbarItems/ReleaseToolbarItem.php Executable file → Normal file
View File

@ -1,5 +1,7 @@
<?php
namespace Evoweb\EwBase\ToolbarItems;
/*
* This file is developed by evoWeb.
*
@ -11,8 +13,6 @@
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ToolbarItems;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Toolbar\RequestAwareToolbarItemInterface;
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
@ -25,7 +25,10 @@ class ReleaseToolbarItem implements ToolbarItemInterface, RequestAwareToolbarIte
{
private ServerRequestInterface $request;
public function __construct(private readonly BackendViewFactory $backendViewFactory) {}
public function __construct(
private readonly BackendViewFactory $backendViewFactory,
) {
}
public function setRequest(ServerRequestInterface $request): void
{

View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Evoweb\EwBase\Updates;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Attribute\UpgradeWizard;
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
/**
* Migrate gridelements elements to container elements
*/
#[UpgradeWizard('gridelementsToContainer')]
class GridelementsToContainerMigration implements UpgradeWizardInterface
{
private const TABLE_NAME = 'tt_content';
protected LoggerInterface $logger;
public function __construct(
protected GridelementsToContainerService $service,
protected LogManager $logManager,
) {
$this->logger = $logManager->getLogger(self::class);
}
public function getTitle(): string
{
return 'Migrate gridelements to container';
}
public function getDescription(): string
{
return 'Migrates content elements that are type gridelements to corresponding container elements';
}
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class
];
}
public function updateNecessary(): bool
{
return $this->hasRecordsToUpdate();
}
public function executeUpdate(): bool
{
$updateSuccessful = true;
try {
$this->service->migrate();
} catch (\Exception $exception) {
$this->logger->log(LogLevel::ERROR, $exception->getMessage());
$updateSuccessful = false;
}
return $updateSuccessful;
}
protected function hasRecordsToUpdate(): bool
{
return (bool)$this->getPreparedQueryBuilder()->count('uid')->executeQuery()->fetchOne();
}
protected function getPreparedQueryBuilder(): QueryBuilder
{
$queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE_NAME);
$queryBuilder->getRestrictions()
->removeByType(HiddenRestriction::class)
->removeByType(StartTimeRestriction::class)
->removeByType(EndTimeRestriction::class);
$queryBuilder
->from(self::TABLE_NAME)
->where(
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('gridelements_pi1'))
);
return $queryBuilder;
}
protected function getConnectionPool(): ConnectionPool
{
return GeneralUtility::makeInstance(ConnectionPool::class);
}
}

View File

@ -0,0 +1,268 @@
<?php
declare(strict_types=1);
namespace Evoweb\EwBase\Updates;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['migrationResolveContainer'] = [];
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['migrationColPosOffset'] = 0;
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['migrationMapping'] = [
'130' => [
'CType' => 'three-col-columns',
],
'140' => [
'CType' => [
'search' => 'pi_flexform/type',
'matches' => [
0 => 'two-col-columns-11',
1 => 'two-col-columns-12',
2 => 'two-col-columns-21',
],
],
'map' => [
'pi_flexform/row_class' => 'frame_class',
]
],
];
*/
class GridelementsToContainerService
{
private const TABLE_NAME = 'tt_content';
protected array $resolveContainer = [];
protected int $colPosOffset = 0;
protected array $configuration = [];
public function __construct(
protected ConnectionPool $connectionPool,
protected DataHandler $dataHandler,
protected FlexFormService $flexFormService,
) {
$config =& $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base'];
$this->resolveContainer = $config['migrationResolveContainer'] ?? [];
$this->colPosOffset = $config['migrationColPosOffset'] ?? 0;
$this->configuration = $config['migrationMapping'] ?? [];
}
public function migrate(): void
{
$this->initializeDataHandler();
// Move children out of container and remove container
foreach ($this->resolveContainer as $containerId) {
$this->resolveContainers((string)$containerId);
}
$this->migrateConfiguredContainer();
}
protected function initializeDataHandler(): void
{
$backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
$backendUser->user = [
'uid' => 0,
'admin' => 1,
];
$this->dataHandler->start([], [], $backendUser);
}
protected function resolveContainers(string $layout): void
{
$containers = $this->getGridElementsByLayout($layout);
foreach ($containers as $container) {
$this->processContainerResolve($container);
}
}
protected function processContainerResolve(array $container): void
{
$children = $this->getGridContainerChildren($container['uid']);
// move first child after container
$moveAfterThis = $container;
foreach ($children as $child) {
[$moveAfterThis, $container] = $this->processContainerResolveChild($child, $moveAfterThis, $container);
}
$this->deleteElement($container['uid']);
}
protected function processContainerResolveChild(array $child, array $moveAfterThis, array $container): array
{
$this->updateElement(
$child['uid'],
[
'tx_gridelements_container' => 0,
'colPos' => $container['colPos'],
'header' => $child['header'] ?: $container['header']
]
);
$this->moveElementAfterElement($child['uid'], $moveAfterThis['uid']);
// use this child to move the next child after
$moveAfterThis = $child;
// empty container header so only the first child gets the header
$container['header'] = '';
return [$moveAfterThis, $container];
}
protected function deleteElement(int $uid): void
{
$this->connectionPool
->getConnectionForTable(self::TABLE_NAME)
->update(self::TABLE_NAME, ['delete' => 1], ['uid' => $uid]);
}
protected function moveElementAfterElement(int $elementToMove, int $elementToMoveAfter): void
{
$this->dataHandler->moveRecord(self::TABLE_NAME, $elementToMove, $elementToMoveAfter * -1);
}
protected function migrateConfiguredContainer(): void
{
array_walk($this->configuration, function($config, $key) {
$containers = $this->getGridElementsByLayout((string)$key);
foreach ($containers as $container) {
$container['pi_flexform'] = $this->flexFormService->convertFlexFormContentToArray(
$container['pi_flexform']
);
$this->processContainerMigration($container, $config);
}
});
}
protected function processContainerMigration(array $container, array $config): void
{
$children = $this->getGridContainerChildren($container['uid']);
foreach ($children as $child) {
$this->processContainerMigrationChild($child, $container);
}
$data = [
'CType' => $this->getCType($container, $config),
'tx_gridelements_backend_layout' => ''
];
if (isset($config['map']) && is_array($config['map']) && !empty($container['pi_flexform'])) {
$data = $this->addMappedValues($data, $container, $config['map']);
}
$this->updateElement($container['uid'], $data);
}
protected function processContainerMigrationChild(array $child, array $container): void
{
$this->updateElement(
$child['uid'],
[
'hidden' => $child['hidden'] ?: $container['hidden'],
'colPos' => $child['tx_gridelements_columns'] + $this->colPosOffset,
'tx_container_parent' => $child['tx_gridelements_container'],
'tx_gridelements_columns' => 0,
'tx_gridelements_container' => 0,
]
);
}
protected function getCType(array $container, array $config): string
{
if (is_array($config['CType'])) {
$value = ArrayUtility::getValueByPath($container, $config['CType']['search']);
$result = $config['CType']['matches'][$value] ?? null;
} else {
$result = $config['CType'] ?? null;
}
if (empty($result)) {
throw new \Exception('CType must always be set');
}
return $result;
}
protected function addMappedValues(array $data, array $container, array $config): array
{
foreach ($config as $from => $to) {
try {
$value = ArrayUtility::getValueByPath($container, $from);
if (empty($container[$to]) && !empty($value)) {
$data[$to] = $value;
}
} catch (\throwable) {}
}
return $data;
}
protected function updateElement(int $uid, array $changes): void
{
$this->connectionPool
->getConnectionForTable(self::TABLE_NAME)
->update(self::TABLE_NAME, $changes, ['uid' => $uid]);
}
protected function getGridContainerChildren(int $containerUid): array
{
$queryBuilder = $this->getQueryBuilderForTable();
return $queryBuilder
->select('*')
->where(
$queryBuilder->expr()->eq(
'tx_gridelements_container',
$queryBuilder->createNamedParameter($containerUid, \PDO::PARAM_INT)
)
)
->orderBy('sorting')
->executeQuery()
->fetchAllAssociative();
}
protected function getGridElementsByLayout(string $layout): array
{
$queryBuilder = $this->getQueryBuilderForTable();
$expr = $queryBuilder->expr();
return $queryBuilder
->select('*')
->where(
$expr->eq('CType', $queryBuilder->createNamedParameter('gridelements_pi1')),
$expr->eq('tx_gridelements_backend_layout', $queryBuilder->createNamedParameter($layout))
)
->orderBy('sorting')
->executeQuery()
->fetchAllAssociative();
}
protected function getQueryBuilderForTable(string $table = 'tt_content'): QueryBuilder
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()
->removeByType(HiddenRestriction::class)
->removeByType(StartTimeRestriction::class)
->removeByType(EndTimeRestriction::class);
$queryBuilder->from($table);
return $queryBuilder;
}
}

View File

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Evoweb\EwBase\Updates;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Attribute\UpgradeWizard;
use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
/**
* Migrate parent child elements to container elements
*/
#[UpgradeWizard('parentChildToContainer')]
class ParentChildToContainerMigration implements UpgradeWizardInterface
{
private const TABLE_NAME = 'tt_content';
protected LoggerInterface $logger;
public function __construct(
protected ParentChildToContainerService $service,
protected LogManager $logManager,
) {
$this->logger = $logManager->getLogger(self::class);
}
public function getTitle(): string
{
return 'Migrate parent/child to container';
}
public function getDescription(): string
{
return 'Migrates content elements that are type parent/child to corresponding container elements';
}
public function getPrerequisites(): array
{
return [
DatabaseUpdatedPrerequisite::class
];
}
public function updateNecessary(): bool
{
return $this->hasRecordsToUpdate();
}
public function executeUpdate(): bool
{
$updateSuccessful = true;
try {
$this->service->migrate();
} catch (\Exception $exception) {
$this->logger->log(LogLevel::ERROR, $exception->getMessage());
$updateSuccessful = false;
}
return $updateSuccessful;
}
protected function hasRecordsToUpdate(): bool
{
$queryBuilder = $this->getPreparedQueryBuilder();
$tableColumns = $queryBuilder
->getConnection()
->createSchemaManager()
->listTableColumns(self::TABLE_NAME);
return isset($tableColumns['parent'])
&& $queryBuilder
->count('uid')
->executeQuery()
->fetchOne() > 0;
}
protected function getPreparedQueryBuilder(): QueryBuilder
{
$queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE_NAME);
$queryBuilder->getRestrictions()
->removeByType(HiddenRestriction::class)
->removeByType(StartTimeRestriction::class)
->removeByType(EndTimeRestriction::class);
$queryBuilder
->from(self::TABLE_NAME)
->where(
// check if there are still records that have parent instead of tx_container_parent
$queryBuilder->expr()->gt('parent', 0)
);
return $queryBuilder;
}
protected function getConnectionPool(): ConnectionPool
{
return GeneralUtility::makeInstance(ConnectionPool::class);
}
}

View File

@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
namespace Evoweb\EwBase\Updates;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['childParentColPosOffset'] = 0;
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base']['childParentMigrationMapping'] = [
'downloads' => [ 'CType' => 'container-downloads' ],
];
*/
class ParentChildToContainerService
{
private const TABLE_NAME = 'tt_content';
protected int $colPosOffset = 0;
protected array $configuration = [];
public function __construct(
protected ConnectionPool $connectionPool,
protected DataHandler $dataHandler,
protected FlexFormService $flexFormService,
) {
$config =& $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['ew_base'];
$this->colPosOffset = $config['childParentColPosOffset'] ?? 0;
$this->configuration = $config['childParentMigrationMapping'] ?? [];
}
public function migrate(): void
{
$this->initializeDataHandler();
$this->migrateConfiguredContainer();
}
protected function initializeDataHandler(): void
{
$backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
$backendUser->user = [
'uid' => 0,
'admin' => 1,
];
$this->dataHandler->start([], [], $backendUser);
}
protected function migrateConfiguredContainer(): void
{
array_walk($this->configuration, function($config, $key) {
$parents = $this->getParentsByCType((string)$key);
foreach ($parents as $parent) {
$this->processParentMigration($parent, $config);
}
});
}
protected function processParentMigration(array $parent, array $config): void
{
$children = $this->getChildren($parent['uid']);
foreach ($children as $child) {
$this->processChildMigration($child, $parent);
}
$this->updateElement(
$parent['uid'],
[
'CType' => $this->getCType($parent, $config),
'children' => 0,
]
);
}
protected function processChildMigration(array $child, array $parent): void
{
$this->updateElement(
$child['uid'],
[
'tx_container_parent' => $parent['uid'],
'colPos' => $child['colPos'] + $this->colPosOffset,
'parent' => 0,
]
);
}
protected function getCType(array $container, array $config): string
{
if (is_array($config['CType'])) {
$value = ArrayUtility::getValueByPath($container, $config['CType']['search']);
$result = $config['CType']['matches'][$value] ?? null;
} else {
$result = $config['CType'] ?? null;
}
if (empty($result)) {
throw new \Exception('CType must always be set');
}
return $result;
}
protected function updateElement(int $uid, array $changes): void
{
$this->connectionPool
->getConnectionForTable(self::TABLE_NAME)
->update(self::TABLE_NAME, $changes, ['uid' => $uid]);
}
protected function getChildren(int $parentUid): array
{
$queryBuilder = $this->getQueryBuilderForTable();
return $queryBuilder
->select('*')
->where(
$queryBuilder->expr()->eq(
'parent',
$queryBuilder->createNamedParameter($parentUid, \PDO::PARAM_INT)
)
)
->orderBy('sorting')
->executeQuery()
->fetchAllAssociative();
}
protected function getParentsByCType(string $cType): array
{
$queryBuilder = $this->getQueryBuilderForTable();
$expr = $queryBuilder->expr();
return $queryBuilder
->select('*')
->where(
$expr->eq('CType', $queryBuilder->createNamedParameter($cType))
)
->orderBy('sorting')
->executeQuery()
->fetchAllAssociative();
}
protected function getQueryBuilderForTable(string $table = 'tt_content'): QueryBuilder
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()
->removeByType(HiddenRestriction::class)
->removeByType(StartTimeRestriction::class)
->removeByType(EndTimeRestriction::class);
$queryBuilder->from($table);
return $queryBuilder;
}
}

16
Classes/User/AssetPath.php Executable file → Normal file
View File

@ -2,19 +2,9 @@
declare(strict_types=1);
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\User;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
@ -28,6 +18,8 @@ class AssetPath
{
public function getAbsolutePublicPath(string $content, array $conf): string
{
return $content . PathUtility::getAbsoluteWebPath($conf['file']);
return $content
. GeneralUtility::getIndpEnv('TYPO3_SITE_URL')
. ltrim(PathUtility::getPublicResourceWebPath($conf['file']), '/');
}
}

15
Classes/ViewHelpers/Array/AddViewHelper.php Executable file → Normal file
View File

@ -1,5 +1,7 @@
<?php
namespace Evoweb\EwBase\ViewHelpers\Array;
/*
* This file is developed by evoWeb.
*
@ -11,8 +13,6 @@
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers\Array;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
@ -26,7 +26,7 @@ class AddViewHelper extends AbstractViewHelper
*/
protected $escapeOutput = false;
public function initializeArguments(): void
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('array', 'array', 'Array to add value to');
@ -34,11 +34,18 @@ class AddViewHelper extends AbstractViewHelper
$this->registerArgument('value', 'mixed', 'Value to add');
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return array
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): array {
) {
$array = $arguments['array'] ?: [];
$key = $arguments['key'];
$value = !is_null($arguments['value']) ? $arguments['value'] : $renderChildrenClosure();

25
Classes/ViewHelpers/Be/ThumbnailViewHelper.php Executable file → Normal file
View File

@ -15,7 +15,6 @@ declare(strict_types=1);
namespace Evoweb\EwBase\ViewHelpers\Be;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
@ -30,7 +29,10 @@ class ThumbnailViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
public function initializeArguments(): void
/**
* Initializes the arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('row', 'array', 'content data', true);
@ -38,11 +40,20 @@ class ThumbnailViewHelper extends AbstractViewHelper
$this->registerArgument('fieldName', 'string', 'field name', true);
}
/**
* Render a constant
*
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return string Value of constant
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): string {
) {
$row = $arguments['row'];
$tableName = $arguments['tableName'];
$fieldName = $arguments['fieldName'];
@ -75,8 +86,7 @@ class ThumbnailViewHelper extends AbstractViewHelper
$row['uid'] => 'edit',
],
],
// @extensionScannerIgnoreLine
'returnUrl' => self::getRequest()->getAttribute('normalizedParams')->getRequestUri()
'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri()
. '#element-tt_content-' . $row['uid'],
];
/** @var UriBuilder $uriBuilder */
@ -100,9 +110,4 @@ class ThumbnailViewHelper extends AbstractViewHelper
{
return $GLOBALS['LANG'];
}
protected static function getRequest(): ServerRequestInterface
{
return $GLOBALS['TYPO3_REQUEST'];
}
}

23
Classes/ViewHelpers/Condition/InArrayViewHelper.php Executable file → Normal file
View File

@ -1,18 +1,14 @@
<?php
namespace Evoweb\EwBase\ViewHelpers\Condition;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
* LICENSE.md file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers\Condition;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
/**
@ -23,14 +19,21 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
*/
class InArrayViewHelper extends AbstractConditionViewHelper
{
public function initializeArguments(): void
/**
* Initialize arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('haystack', 'array', 'haystack', true);
$this->registerArgument('needle', 'mixed', 'needle', true);
}
protected static function evaluateCondition($arguments = null): bool
/**
* @param array $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null)
{
$array = $arguments['haystack']->toArray();
return in_array($arguments['needle'], $array);

View File

@ -1,18 +1,14 @@
<?php
namespace Evoweb\EwBase\ViewHelpers\Condition;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
* LICENSE.md file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers\Condition;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
/**
@ -23,15 +19,22 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
*/
class StringContainsViewHelper extends AbstractConditionViewHelper
{
public function initializeArguments(): void
/**
* Initialize arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('haystack', 'string', 'haystack', true);
$this->registerArgument('needle', 'string', 'need', true);
}
protected static function evaluateCondition($arguments = null): bool
/**
* @param array $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null)
{
return str_contains($arguments['haystack'], $arguments['needle']);
return false !== strpos($arguments['haystack'], $arguments['needle']);
}
}

18
Classes/ViewHelpers/Context/DevelopmentViewHelper.php Executable file → Normal file
View File

@ -1,24 +1,24 @@
<?php
namespace Evoweb\EwBase\ViewHelpers\Context;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
* LICENSE.md file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers\Context;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
class DevelopmentViewHelper extends AbstractConditionViewHelper
{
protected static function evaluateCondition($arguments = null): bool
/**
* @param array $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null)
{
return Environment::getContext()->isDevelopment();
}

18
Classes/ViewHelpers/Context/ProductionViewHelper.php Executable file → Normal file
View File

@ -1,24 +1,24 @@
<?php
namespace Evoweb\EwBase\ViewHelpers\Context;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
* LICENSE.md file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers\Context;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
class ProductionViewHelper extends AbstractConditionViewHelper
{
protected static function evaluateCondition($arguments = null): bool
/**
* @param array $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null)
{
return Environment::getContext()->isProduction();
}

20
Classes/ViewHelpers/Context/StagingViewHelper.php Executable file → Normal file
View File

@ -1,25 +1,25 @@
<?php
namespace Evoweb\EwBase\ViewHelpers\Context;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
* LICENSE.md file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers\Context;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
class StagingViewHelper extends AbstractConditionViewHelper
{
protected static function evaluateCondition($arguments = null): bool
/**
* @param array $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null)
{
return (string)Environment::getContext() === 'Production/Staging';
return 'Production/Staging' === (string)Environment::getContext();
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace Evoweb\EwBase\ViewHelpers;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*/
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* = Examples =
*
* <code title="Simple Loop">
* <ewb:fixFlexformForExtbase data="{data}">....</ewb:fixFlexformForExtbase>
* </code>
* <output>
* fixed flexform data for extbase controller
* </output>
*
* @api
*/
class FixFlexformForExtbaseViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* @var boolean
*/
protected $escapeOutput = false;
public function initializeArguments(): void
{
parent::initializeArguments();
$this->registerArgument('data', 'array', 'The data array of content element', true);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return array
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$templateVariableContainer = $renderingContext->getVariableProvider();
/** @var FlexFormTools $flexFormTools */
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
$data = $arguments['data'];
if (is_array($data['pi_flexform'])) {
$data['pi_flexform'] = $flexFormTools->flexArray2Xml($data['pi_flexform']);
}
$templateVariableContainer->add('data', $data);
$output = $renderChildrenClosure();
$templateVariableContainer->remove('data');
return $output;
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace Evoweb\EwBase\ViewHelpers;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* 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;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
/**
* = Examples =
*
* <code title="Simple Loop">
* <ewb:flexForm data="{data}">....</ewb:flexForm>
* </code>
* <output>
* fixed flexform data for extbase controller
* </output>
*
* @api
*/
class FlexFormViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* @var boolean
*/
protected $escapeOutput = false;
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('data', 'array', 'Array to get flex form data from', true);
$this->registerArgument('fieldName', 'string', 'Field name', false, 'pi_flexform');
$this->registerArgument('as', 'string', 'Name of the variable to assign', false, 'flexFormData');
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return array
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$data = [];
if (is_array($arguments['data'])) {
if (isset($arguments['data'][$arguments['fieldName']])) {
$data = is_array($arguments['data'][$arguments['fieldName']]) ?
$arguments['data'][$arguments['fieldName']] :
GeneralUtility::xml2array($arguments['data'][$arguments['fieldName']]);
$data = $data['data'] ?? $data;
}
}
$templateVariableContainer = $renderingContext->getVariableProvider();
$templateVariableContainer->add($arguments['as'], $data);
$content = $renderChildrenClosure();
$templateVariableContainer->remove($arguments['as']);
return $content;
}
}

24
Classes/ViewHelpers/HashViewHelper.php Executable file → Normal file
View File

@ -1,5 +1,7 @@
<?php
namespace Evoweb\EwBase\ViewHelpers;
/*
* This file is developed by evoWeb.
*
@ -11,10 +13,6 @@
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers;
use TYPO3\CMS\Core\Crypto\HashService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
@ -24,31 +22,37 @@ class HashViewHelper extends AbstractViewHelper
use CompileWithRenderStatic;
/**
* @var bool
* @var boolean
*/
protected $escapeOutput = false;
public function initializeArguments(): void
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('action', 'string', 'Target action');
$this->registerArgument('arguments', 'array', 'Arguments for the controller action, associative array');
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return string
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): string {
) {
$result = '';
if (
$arguments['action'] !== null
&& $arguments['arguments'] !== null
&& isset($arguments['arguments']['user'])
) {
/** @var HashService $hashService */
$hashService = GeneralUtility::makeInstance(HashService::class);
$result = $hashService->hmac($arguments['action'] . '::' . $arguments['arguments']['user'], '');
$result = \TYPO3\CMS\Core\Utility\GeneralUtility::hmac(
$arguments['action'] . '::' . $arguments['arguments']['user']
);
}
return $result;

View File

@ -0,0 +1,56 @@
<?php
namespace Evoweb\EwBase\ViewHelpers\Iterator;
/*
* This file is developed by evoWeb.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* 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;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
class AddViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* @var bool
*/
protected $escapeOutput = false;
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('array', 'array', 'Array to add value to');
$this->registerArgument('key', 'string', 'Key to add value by', true);
$this->registerArgument('value', 'mixed', 'Value to add');
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return array
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$array = $arguments['array'] ?: [];
$key = $arguments['key'];
$value = $arguments['value'] ?: $renderChildrenClosure();
return array_merge($array, [$key => $value]);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Evoweb\EwBase\ViewHelpers\Iterator;
/*
* This file is part of the FluidTYPO3/Vhs project under GPLv2 or later.
*
* For the full copyright and license information, please read the
* LICENSE.md file that was distributed with this source code.
*/
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
/**
* Explode ViewHelper
*
* Explodes a string by $glue.
*
* {data.header -> ewb:iterator.explode(glue: 'constant:LF')}
*
* <ewb:iterator.explode content="{data.header}" as="as" glue="constant:LF">
* <span>{as}</span>
* </ewb:iterator.explode>
*/
class ExplodeViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
protected static string $method = 'explode';
public function initializeArguments()
{
$this->registerArgument(
'as',
'string',
'Template variable name to assign; if not specified the ViewHelper returns the variable instead.'
);
$this->registerArgument('content', 'string', 'String to be exploded by glue');
$this->registerArgument(
'glue',
'string',
'String used as glue in the string to be exploded. Use glue value of "constant:NAMEOFCONSTANT" ' .
'(fx "constant:LF" for linefeed as glue)',
false,
','
);
}
/**
* Render method
*
* @return string|array
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$content = $arguments['content'] ?? $renderChildrenClosure();
$glue = static::resolveGlue($arguments);
$content = call_user_func_array(static::$method, [$glue, $content]);
$as = $arguments['as'];
if (true === empty($as)) {
$output = $content;
} else {
$templateVariableContainer = $renderingContext->getVariableProvider();
$templateVariableContainer->add($as, $content);
$output = $renderChildrenClosure();
$templateVariableContainer->remove($as);
}
return $output;
}
protected static function resolveGlue(array $arguments): string
{
$glue = $arguments['glue'];
if (false !== strpos($glue, ':') && 1 < strlen($glue)) {
// glue contains a special type identifier, resolve the actual glue
list ($type, $value) = explode(':', $glue);
switch ($type) {
case 'constant':
$glue = constant($value);
break;
default:
$glue = $value;
}
}
return $glue;
}
}

26
Classes/ViewHelpers/PublicPathViewHelper.php Executable file → Normal file
View File

@ -2,6 +2,8 @@
declare(strict_types=1);
namespace Evoweb\EwBase\ViewHelpers;
/*
* This file is developed by evoWeb.
*
@ -13,8 +15,6 @@ declare(strict_types=1);
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
@ -37,27 +37,31 @@ class PublicPathViewHelper extends AbstractViewHelper
use CompileWithRenderStatic;
/**
* @var bool
* @var boolean
*/
protected $escapeOutput = false;
public function initializeArguments(): void
protected static ?array $frontendGroupIds = null;
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('path', 'string', 'Extension resource path', true);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return bool
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): string {
) {
$path = (string)$arguments['path'];
try {
$path = PathUtility::getPublicResourceWebPath($path);
} catch (\Exception) {
$path = '';
}
return $path;
return PathUtility::getPublicResourceWebPath($path);
}
}

14
Classes/ViewHelpers/ReplaceViewHelper.php Executable file → Normal file
View File

@ -1,5 +1,7 @@
<?php
namespace Evoweb\EwBase\ViewHelpers;
/*
* This file is developed by evoWeb.
*
@ -11,8 +13,6 @@
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithContentArgumentAndRenderStatic;
@ -44,18 +44,24 @@ class ReplaceViewHelper extends AbstractViewHelper
{
use CompileWithContentArgumentAndRenderStatic;
public function initializeArguments(): void
public function initializeArguments()
{
$this->registerArgument('value', 'string', 'String to replace in');
$this->registerArgument('search', 'string', 'Search string');
$this->registerArgument('replace', 'string', 'Replace value');
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return null
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): string {
) {
$content = $arguments['value'];
if ($content === null) {
$content = $renderChildrenClosure();

63
Classes/ViewHelpers/SvgViewHelper.php Executable file → Normal file
View File

@ -1,5 +1,7 @@
<?php
namespace Evoweb\EwBase\ViewHelpers;
/*
* This file is developed by evoWeb.
*
@ -11,15 +13,11 @@
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\Imaging\IconState;
use TYPO3\CMS\Core\Type\Icon\IconState;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
/**
@ -63,7 +61,7 @@ use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
* </span>
* </span>
*/
class SvgViewHelper extends AbstractViewHelper
class SvgViewHelper extends \TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper
{
use CompileWithRenderStatic;
@ -76,53 +74,30 @@ class SvgViewHelper extends AbstractViewHelper
public function initializeArguments(): void
{
$this->registerArgument(
'identifier',
'string',
'Identifier of the icon as registered in the Icon Registry.',
true
);
$this->registerArgument(
'size',
'string',
'Desired size of the icon. All values of the Icons.sizes enum are allowed,'
. ' these are: "small", "default", "large" and "overlay".',
false,
IconSize::SMALL
);
$this->registerArgument(
'overlay',
'string',
'Identifier of an overlay icon as registered in the Icon Registry.',
false,
''
);
$this->registerArgument(
'state',
'string',
'Sets the state of the icon. All values of the Icons.states enum are allowed,'
. ' these are: "default" and "disabled".',
false,
IconState::STATE_DEFAULT
);
$this->registerArgument(
'alternativeMarkupIdentifier',
'string',
'Alternative icon identifier. Takes precedence over the identifier if supported by the IconProvider.',
false,
''
);
$this->registerArgument('identifier', 'string', 'Identifier of the icon as registered in the Icon Registry.', true);
$this->registerArgument('size', 'string', 'Desired size of the icon. All values of the Icons.sizes enum are allowed, these are: "small", "default", "large" and "overlay".', false, Icon::SIZE_SMALL);
$this->registerArgument('overlay', 'string', 'Identifier of an overlay icon as registered in the Icon Registry.', false);
$this->registerArgument('state', 'string', 'Sets the state of the icon. All values of the Icons.states enum are allowed, these are: "default" and "disabled".', false, IconState::STATE_DEFAULT);
$this->registerArgument('alternativeMarkupIdentifier', 'string', 'Alternative icon identifier. Takes precedence over the identifier if supported by the IconProvider.', false);
}
/**
* Prints icon html for $identifier key
*
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return string
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): Icon {
) {
$identifier = $arguments['identifier'];
$size = $arguments['size'];
$overlay = $arguments['overlay'];
$state = IconState::tryFrom($arguments['state']);
$state = IconState::cast($arguments['state']);
$alternativeMarkupIdentifier = $arguments['alternativeMarkupIdentifier'];
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
return $iconFactory->getIcon($identifier, $size, $overlay, $state)->getMarkup($alternativeMarkupIdentifier);

21
Classes/ViewHelpers/TrimViewHelper.php Executable file → Normal file
View File

@ -1,5 +1,7 @@
<?php
namespace Evoweb\EwBase\ViewHelpers;
/*
* This file is developed by evoWeb.
*
@ -11,8 +13,6 @@
* LICENSE.txt file that was distributed with this source code.
*/
namespace Evoweb\EwBase\ViewHelpers;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
@ -26,20 +26,27 @@ class TrimViewHelper extends AbstractViewHelper
*/
protected $escapeOutput = false;
public function initializeArguments(): void
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('content', 'string', 'Content to be trimmed');
$this->registerArgument('characters', 'string', 'Characters to be removed');
$this->registerArgument('characters', 'string', 'Characters to be removed', false);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return string
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): string {
$content = $arguments['content'] ?: $renderChildrenClosure();
$characters = $arguments['characters'] ?: null;
) {
$content = $arguments['content'] ? $arguments['content'] : $renderChildrenClosure();
$characters = $arguments['characters'] ? $arguments['characters'] : null;
if ($characters !== null) {
$content = trim($content, $characters);

0
Configuration/Icons.php Executable file → Normal file
View File

0
Configuration/JavaScriptModules.php Executable file → Normal file
View File

27
Configuration/Services.yaml Executable file → Normal file
View File

@ -14,5 +14,32 @@ services:
description: 'Command to list all content elements for a given subtree'
schedulable: false
Evoweb\EwBase\EventListener\CssMerger:
tags: ['event.listener']
Evoweb\EwBase\EventListener\JsMerger:
tags: ['event.listener']
Evoweb\EwBase\EventListener\IsContentUsedOnPageLayout:
tags: ['event.listener']
Evoweb\EwBase\ToolbarItems\ReleaseToolbarItem:
tags: ['backend.toolbar.item']
Evoweb\EwBase\Hooks\UsercentricsPostRenderHook:
public: true
Evoweb\EwBase\Updates\GridelementsToContainerMigration:
public: true
Evoweb\EwBase\Updates\GridelementsToContainerService:
public: true
Evoweb\EwBase\Updates\ParentChildToContainerMigration:
public: true
Evoweb\EwBase\Updates\ParentChildToContainerService:
public: true
Evoweb\EwBase\Form\FormDataProvider\UsercentricsDatabaseEditRow:
public: true

10
Configuration/TCA/Overrides/pages.php Executable file → Normal file
View File

@ -15,14 +15,14 @@ $newColumns = [
'suggestOptions' => [
'default' => [
'additionalSearchFields' => 'header, bodytext',
'searchWholePhrase' => false,
],
'searchWholePhrase' => false
]
],
'default' => 0,
'behaviour' => [
'allowLanguageSynchronization' => true,
],
],
'allowLanguageSynchronization' => true
]
]
],
];

0
Configuration/Yaml/Csp/Cookiebot.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/GoogleAdsConversions.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/GoogleAdsRemarketing.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/GoogleAnalytics4.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/GoogleAnalytics4Signals.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/GoogleAnalytics4SignalsEu.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/GoogleOptimize.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/GoogleTagManagerPreview.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/MapBox.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/UniversalAnalytics.yaml Executable file → Normal file
View File

0
Configuration/Yaml/Csp/Youtube.yaml Executable file → Normal file
View File

0
README.md Executable file → Normal file
View File

0
Resources/Private/Language/locallang_core.xlf Executable file → Normal file
View File

0
Resources/Private/Language/locallang_db.xlf Executable file → Normal file
View File

View File

View File

View File

@ -1,4 +1,6 @@
<html xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers" data-namespace-typo3-fluid="true">
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
data-namespace-typo3-fluid="true">
<h3 class="dropdown-headline"><f:translate id="LLL:EXT:ew_base/Resources/Private/Language/locallang_core.xlf:rm.release" /></h3>
<table class="dropdown-table">

View File

0
Resources/Public/Icons/Extension.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

0
Resources/Public/Icons/Extension_16.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 413 B

After

Width:  |  Height:  |  Size: 413 B

0
Resources/Public/Icons/usercentrics.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 677 B

24
composer.json Executable file → Normal file
View File

@ -1,12 +1,10 @@
{
"name": "evoweb/ew-base",
"type": "typo3-cms-extension",
"license": ["GPL-2.0-or-later"],
"config": {
"bin-dir": "bin",
"allow-plugins": {
"typo3/class-alias-loader": true,
"typo3/cms-composer-installers": true
"version": "1.0.0",
"autoload": {
"psr-4": {
"Evoweb\\EwBase\\": "Classes/"
}
},
"require": {
@ -38,24 +36,12 @@
"typo3/cms-tstemplate": "*",
"typo3/cms-scheduler": "*",
"helhum/typo3-console": ">8.0",
"helhum/typo3-console": "*",
"clickstorm/cs-seo": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.57.1",
"phpunit/phpunit": "^11.0.3",
"typo3/testing-framework": "dev-main"
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"typo3/cms": {
"extension-key": "ew_base"
}
},
"autoload": {
"psr-4": {
"Evoweb\\EwBase\\": "Classes/"
}
}
}

2
ext_localconf.php Executable file → Normal file
View File

@ -1,6 +1,6 @@
<?php
defined('TYPO3') or die('access denied');
defined('TYPO3') or die();
use Evoweb\EwBase\Form\Element\PickColorFromImage;

0
ext_tables.sql Executable file → Normal file
View File