diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
old mode 100755
new mode 100644
diff --git a/Classes/Command/ContentElementCommand.php b/Classes/Command/ContentElementCommand.php
old mode 100755
new mode 100644
index d27f63b..687e76d
--- a/Classes/Command/ContentElementCommand.php
+++ b/Classes/Command/ContentElementCommand.php
@@ -1,5 +1,7 @@
connectionPool = $connectionPool;
parent::__construct();
}
- protected function configure(): void
+ protected function configure()
{
$this
- ->setAliases(['ce'])
+ ->setAliases(['kc-sitepackage'])
->addOption(
'pageId',
'-p',
diff --git a/Classes/Configuration/AdditionalConfiguration.php b/Classes/Configuration/AdditionalConfiguration.php
old mode 100755
new mode 100644
index 540cbaa..e6b6165
--- a/Classes/Configuration/AdditionalConfiguration.php
+++ b/Classes/Configuration/AdditionalConfiguration.php
@@ -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(
diff --git a/Classes/DataProcessing/ContainerProcessor.php b/Classes/DataProcessing/ContainerProcessor.php
new file mode 100644
index 0000000..6d48d2a
--- /dev/null
+++ b/Classes/DataProcessing/ContainerProcessor.php
@@ -0,0 +1,58 @@
+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;
+ }
+}
diff --git a/Classes/DataProcessing/DatabaseQueryProcessor.php b/Classes/DataProcessing/DatabaseQueryProcessor.php
new file mode 100644
index 0000000..057ec66
--- /dev/null
+++ b/Classes/DataProcessing/DatabaseQueryProcessor.php
@@ -0,0 +1,831 @@
+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;
+ }
+}
diff --git a/Classes/EventListener/CssMerger.php b/Classes/EventListener/CssMerger.php
old mode 100755
new mode 100644
index b17ae2a..dfd8de0
--- a/Classes/EventListener/CssMerger.php
+++ b/Classes/EventListener/CssMerger.php
@@ -1,19 +1,7 @@
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);
}
}
diff --git a/Classes/EventListener/IsContentUsedOnPageLayout.php b/Classes/EventListener/IsContentUsedOnPageLayout.php
new file mode 100644
index 0000000..daafc1e
--- /dev/null
+++ b/Classes/EventListener/IsContentUsedOnPageLayout.php
@@ -0,0 +1,25 @@
+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;
+ }
+}
diff --git a/Classes/EventListener/JsMerger.php b/Classes/EventListener/JsMerger.php
old mode 100755
new mode 100644
index 3b4b873..52d7381
--- a/Classes/EventListener/JsMerger.php
+++ b/Classes/EventListener/JsMerger.php
@@ -1,19 +1,7 @@
isInline() && $event->isPriority()) {
diff --git a/Classes/Form/Element/PickColorFromImage.php b/Classes/Form/Element/PickColorFromImage.php
old mode 100755
new mode 100644
index bc3c9c9..705918b
--- a/Classes/Form/Element/PickColorFromImage.php
+++ b/Classes/Form/Element/PickColorFromImage.php
@@ -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;
- }
}
diff --git a/Classes/Hooks/PageLayoutView.php b/Classes/Hooks/PageLayoutView.php
new file mode 100644
index 0000000..608ccf1
--- /dev/null
+++ b/Classes/Hooks/PageLayoutView.php
@@ -0,0 +1,34 @@
+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;
+ }
+}
diff --git a/Classes/ToolbarItems/ReleaseToolbarItem.php b/Classes/ToolbarItems/ReleaseToolbarItem.php
old mode 100755
new mode 100644
index 097ba9a..4f01aa4
--- a/Classes/ToolbarItems/ReleaseToolbarItem.php
+++ b/Classes/ToolbarItems/ReleaseToolbarItem.php
@@ -1,5 +1,7 @@
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);
+ }
+}
diff --git a/Classes/Updates/GridelementsToContainerService.php b/Classes/Updates/GridelementsToContainerService.php
new file mode 100644
index 0000000..4b0cdac
--- /dev/null
+++ b/Classes/Updates/GridelementsToContainerService.php
@@ -0,0 +1,268 @@
+ [
+ '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;
+ }
+}
diff --git a/Classes/Updates/ParentChildToContainerMigration.php b/Classes/Updates/ParentChildToContainerMigration.php
new file mode 100644
index 0000000..685daab
--- /dev/null
+++ b/Classes/Updates/ParentChildToContainerMigration.php
@@ -0,0 +1,106 @@
+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);
+ }
+}
diff --git a/Classes/Updates/ParentChildToContainerService.php b/Classes/Updates/ParentChildToContainerService.php
new file mode 100644
index 0000000..737ed75
--- /dev/null
+++ b/Classes/Updates/ParentChildToContainerService.php
@@ -0,0 +1,168 @@
+ [ '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;
+ }
+}
diff --git a/Classes/User/AssetPath.php b/Classes/User/AssetPath.php
old mode 100755
new mode 100644
index 58c18b2..9659830
--- a/Classes/User/AssetPath.php
+++ b/Classes/User/AssetPath.php
@@ -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']), '/');
}
}
diff --git a/Classes/ViewHelpers/Array/AddViewHelper.php b/Classes/ViewHelpers/Array/AddViewHelper.php
old mode 100755
new mode 100644
index fed0fcb..1d0b5c9
--- a/Classes/ViewHelpers/Array/AddViewHelper.php
+++ b/Classes/ViewHelpers/Array/AddViewHelper.php
@@ -1,5 +1,7 @@
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();
diff --git a/Classes/ViewHelpers/Be/ThumbnailViewHelper.php b/Classes/ViewHelpers/Be/ThumbnailViewHelper.php
old mode 100755
new mode 100644
index f9bf1fd..8449574
--- a/Classes/ViewHelpers/Be/ThumbnailViewHelper.php
+++ b/Classes/ViewHelpers/Be/ThumbnailViewHelper.php
@@ -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'];
- }
}
diff --git a/Classes/ViewHelpers/Condition/InArrayViewHelper.php b/Classes/ViewHelpers/Condition/InArrayViewHelper.php
old mode 100755
new mode 100644
index 52390db..bc713f5
--- a/Classes/ViewHelpers/Condition/InArrayViewHelper.php
+++ b/Classes/ViewHelpers/Condition/InArrayViewHelper.php
@@ -1,18 +1,14 @@
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);
diff --git a/Classes/ViewHelpers/Condition/StringContainsViewHelper.php b/Classes/ViewHelpers/Condition/StringContainsViewHelper.php
old mode 100755
new mode 100644
index 1e6d3b8..efb6b6f
--- a/Classes/ViewHelpers/Condition/StringContainsViewHelper.php
+++ b/Classes/ViewHelpers/Condition/StringContainsViewHelper.php
@@ -1,18 +1,14 @@
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']);
}
}
diff --git a/Classes/ViewHelpers/Context/DevelopmentViewHelper.php b/Classes/ViewHelpers/Context/DevelopmentViewHelper.php
old mode 100755
new mode 100644
index 1d40d86..0875df1
--- a/Classes/ViewHelpers/Context/DevelopmentViewHelper.php
+++ b/Classes/ViewHelpers/Context/DevelopmentViewHelper.php
@@ -1,24 +1,24 @@
isDevelopment();
}
diff --git a/Classes/ViewHelpers/Context/ProductionViewHelper.php b/Classes/ViewHelpers/Context/ProductionViewHelper.php
old mode 100755
new mode 100644
index 957ac2d..f5356bb
--- a/Classes/ViewHelpers/Context/ProductionViewHelper.php
+++ b/Classes/ViewHelpers/Context/ProductionViewHelper.php
@@ -1,24 +1,24 @@
isProduction();
}
diff --git a/Classes/ViewHelpers/Context/StagingViewHelper.php b/Classes/ViewHelpers/Context/StagingViewHelper.php
old mode 100755
new mode 100644
index e3e6e29..a76003e
--- a/Classes/ViewHelpers/Context/StagingViewHelper.php
+++ b/Classes/ViewHelpers/Context/StagingViewHelper.php
@@ -1,25 +1,25 @@
+ *