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 @@ + * .... + * + * + * fixed flexform data for extbase controller + * + * + * @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; + } +} diff --git a/Classes/ViewHelpers/FlexFormViewHelper.php b/Classes/ViewHelpers/FlexFormViewHelper.php new file mode 100644 index 0000000..cd7fc27 --- /dev/null +++ b/Classes/ViewHelpers/FlexFormViewHelper.php @@ -0,0 +1,80 @@ + + * .... + * + * + * fixed flexform data for extbase controller + * + * + * @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; + } +} diff --git a/Classes/ViewHelpers/HashViewHelper.php b/Classes/ViewHelpers/HashViewHelper.php old mode 100755 new mode 100644 index ac71cac..0b61138 --- a/Classes/ViewHelpers/HashViewHelper.php +++ b/Classes/ViewHelpers/HashViewHelper.php @@ -1,5 +1,7 @@ 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; diff --git a/Classes/ViewHelpers/Iterator/AddViewHelper.php b/Classes/ViewHelpers/Iterator/AddViewHelper.php new file mode 100644 index 0000000..bb24422 --- /dev/null +++ b/Classes/ViewHelpers/Iterator/AddViewHelper.php @@ -0,0 +1,56 @@ +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]); + } +} diff --git a/Classes/ViewHelpers/Iterator/ExplodeViewHelper.php b/Classes/ViewHelpers/Iterator/ExplodeViewHelper.php new file mode 100644 index 0000000..e50139f --- /dev/null +++ b/Classes/ViewHelpers/Iterator/ExplodeViewHelper.php @@ -0,0 +1,93 @@ + ewb:iterator.explode(glue: 'constant:LF')} + * + * + * {as} + * + */ +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; + } +} diff --git a/Classes/ViewHelpers/PublicPathViewHelper.php b/Classes/ViewHelpers/PublicPathViewHelper.php old mode 100755 new mode 100644 index 899b19d..769f001 --- a/Classes/ViewHelpers/PublicPathViewHelper.php +++ b/Classes/ViewHelpers/PublicPathViewHelper.php @@ -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); } } diff --git a/Classes/ViewHelpers/ReplaceViewHelper.php b/Classes/ViewHelpers/ReplaceViewHelper.php old mode 100755 new mode 100644 index f05612d..77a9837 --- a/Classes/ViewHelpers/ReplaceViewHelper.php +++ b/Classes/ViewHelpers/ReplaceViewHelper.php @@ -1,5 +1,7 @@ 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(); diff --git a/Classes/ViewHelpers/SvgViewHelper.php b/Classes/ViewHelpers/SvgViewHelper.php old mode 100755 new mode 100644 index e06b1df..2290936 --- a/Classes/ViewHelpers/SvgViewHelper.php +++ b/Classes/ViewHelpers/SvgViewHelper.php @@ -1,5 +1,7 @@ * */ -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); diff --git a/Classes/ViewHelpers/TrimViewHelper.php b/Classes/ViewHelpers/TrimViewHelper.php old mode 100755 new mode 100644 index 1d9876e..68683ff --- a/Classes/ViewHelpers/TrimViewHelper.php +++ b/Classes/ViewHelpers/TrimViewHelper.php @@ -1,5 +1,7 @@ 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); diff --git a/Configuration/Icons.php b/Configuration/Icons.php old mode 100755 new mode 100644 diff --git a/Configuration/JavaScriptModules.php b/Configuration/JavaScriptModules.php old mode 100755 new mode 100644 diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml old mode 100755 new mode 100644 index 87caa87..8050c5b --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -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 diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php old mode 100755 new mode 100644 index 5a8dda2..6a697aa --- a/Configuration/TCA/Overrides/pages.php +++ b/Configuration/TCA/Overrides/pages.php @@ -15,14 +15,14 @@ $newColumns = [ 'suggestOptions' => [ 'default' => [ 'additionalSearchFields' => 'header, bodytext', - 'searchWholePhrase' => false, - ], + 'searchWholePhrase' => false + ] ], 'default' => 0, 'behaviour' => [ - 'allowLanguageSynchronization' => true, - ], - ], + 'allowLanguageSynchronization' => true + ] + ] ], ]; diff --git a/Configuration/Yaml/Csp/Cookiebot.yaml b/Configuration/Yaml/Csp/Cookiebot.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAdsConversions.yaml b/Configuration/Yaml/Csp/GoogleAdsConversions.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAdsRemarketing.yaml b/Configuration/Yaml/Csp/GoogleAdsRemarketing.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAnalytics4.yaml b/Configuration/Yaml/Csp/GoogleAnalytics4.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAnalytics4Signals.yaml b/Configuration/Yaml/Csp/GoogleAnalytics4Signals.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleAnalytics4SignalsEu.yaml b/Configuration/Yaml/Csp/GoogleAnalytics4SignalsEu.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleOptimize.yaml b/Configuration/Yaml/Csp/GoogleOptimize.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/GoogleTagManagerPreview.yaml b/Configuration/Yaml/Csp/GoogleTagManagerPreview.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/MapBox.yaml b/Configuration/Yaml/Csp/MapBox.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/UniversalAnalytics.yaml b/Configuration/Yaml/Csp/UniversalAnalytics.yaml old mode 100755 new mode 100644 diff --git a/Configuration/Yaml/Csp/Youtube.yaml b/Configuration/Yaml/Csp/Youtube.yaml old mode 100755 new mode 100644 diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/Resources/Private/Language/locallang_core.xlf b/Resources/Private/Language/locallang_core.xlf old mode 100755 new mode 100644 diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf old mode 100755 new mode 100644 diff --git a/Resources/Private/Language/locallang_siteconfiguration.xlf b/Resources/Private/Language/locallang_siteconfiguration.xlf old mode 100755 new mode 100644 diff --git a/Resources/Private/Templates/Form/ImageManipulationElement.html b/Resources/Private/Templates/Form/ImageManipulationElement.html old mode 100755 new mode 100644 diff --git a/Resources/Private/Templates/ToolbarItems/ShowReleaseDropDown.html b/Resources/Private/Templates/ToolbarItems/ShowReleaseDropDown.html old mode 100755 new mode 100644 index fea0895..aa01a16 --- a/Resources/Private/Templates/ToolbarItems/ShowReleaseDropDown.html +++ b/Resources/Private/Templates/ToolbarItems/ShowReleaseDropDown.html @@ -1,4 +1,6 @@ - + diff --git a/Resources/Private/Templates/ToolbarItems/ShowReleaseToolbarItem.html b/Resources/Private/Templates/ToolbarItems/ShowReleaseToolbarItem.html old mode 100755 new mode 100644 diff --git a/Resources/Public/Icons/Extension.svg b/Resources/Public/Icons/Extension.svg old mode 100755 new mode 100644 diff --git a/Resources/Public/Icons/Extension_16.svg b/Resources/Public/Icons/Extension_16.svg old mode 100755 new mode 100644 diff --git a/Resources/Public/Icons/usercentrics.svg b/Resources/Public/Icons/usercentrics.svg old mode 100755 new mode 100644 diff --git a/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.css b/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.css old mode 100755 new mode 100644 diff --git a/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.js b/Resources/Public/JavaScript/form-engine/element/pick-color-from-image.js old mode 100755 new mode 100644 diff --git a/composer.json b/composer.json old mode 100755 new mode 100644 index 51e0956..d9f7594 --- a/composer.json +++ b/composer.json @@ -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/" - } } } diff --git a/ext_localconf.php b/ext_localconf.php old mode 100755 new mode 100644 index 90c27cd..9f47c15 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,6 +1,6 @@