[ '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, ParameterType::INTEGER) ) ) ->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; } }