chore: remove the gridelements migration and the address check table
The grids are migrated in production, and the production database is the master that is never replaced by a TYPO3 10 dump, so ep:migrate-gridelements has nothing left to do; its tx_gridelements_* columns are dropped. The address check forms are gone (pages removed), and so is the table that stored their submissions: personal data that has not been processed since 2019. The tables and columns themselves are dropped by hand: extension:setup only adds and changes. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
d86efd72e3
commit
dfed542af1
3 files changed
+1
-260
No files matched your search
@@ -1,218 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace EP\EpTheme\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
|
||||
/**
|
||||
* Turns gridelements grids into b13/container elements (see .ddev/plans/GRIDELEMENTS-TO-CONTAINER.md).
|
||||
*
|
||||
* Plain SQL instead of DataHandler: no hooks, no history, deterministic. Idempotent: every migrated row
|
||||
* loses its tx_gridelements_* values, so a second run finds nothing to do.
|
||||
*/
|
||||
#[AsCommand(name: 'ep:migrate-gridelements', description: 'Migrates gridelements grids and their children to container elements')]
|
||||
class MigrateGridelementsCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Gridelements layout => [container CType, first container colPos - 1, valid gridelements columns]
|
||||
*/
|
||||
private const LAYOUTS = [
|
||||
'halves' => ['ep_container_halves', 200, [21, 22]],
|
||||
'thirds' => ['ep_container_thirds', 210, [31, 32, 33]],
|
||||
'quarters' => ['ep_container_columns_quarters', 220, [41, 42, 43, 44]],
|
||||
'halfAndTwoQuarters' => ['ep_container_half_and_two_quarters', 230, [51, 52, 53]],
|
||||
'twoQuartersAndHalf' => ['ep_container_two_quarters_and_half', 240, [61, 62, 63]],
|
||||
];
|
||||
|
||||
/**
|
||||
* Content types whose template renders the header with settings.defaultHeaderType for layout 0
|
||||
*/
|
||||
private const DEFAULT_HEADER_TYPES = ['text', 'textmedia', 'textpic'];
|
||||
|
||||
private const RESET = [
|
||||
'tx_gridelements_container' => 0,
|
||||
'tx_gridelements_columns' => 0,
|
||||
'tx_gridelements_children' => 0,
|
||||
'tx_gridelements_backend_layout' => '',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly ConnectionPool $connectionPool,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Only show what would change')
|
||||
->addOption('report', null, InputOption::VALUE_REQUIRED, 'Write every row that is not migrated 1:1 to this CSV file');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$connection = $this->connectionPool->getConnectionForTable('tt_content');
|
||||
if (!$this->hasGridelementsColumns($connection)) {
|
||||
$output->writeln('No tx_gridelements_* columns in tt_content, nothing to do.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// Plain SQL on purpose: Connection::select() would apply the frontend restrictions (deleted, hidden, …)
|
||||
$grids = [];
|
||||
foreach ($connection->executeQuery('SELECT * FROM tt_content WHERE CType = ?', ['gridelements_pi1'])->iterateAssociative() as $row) {
|
||||
$grids[(int)$row['uid']] = $row;
|
||||
}
|
||||
$children = $connection->executeQuery(
|
||||
'SELECT * FROM tt_content WHERE tx_gridelements_container > 0 AND CType <> ? ORDER BY uid',
|
||||
['gridelements_pi1']
|
||||
)->fetchAllAssociative();
|
||||
|
||||
if ((int)$connection->executeQuery('SELECT COUNT(*) FROM tt_content WHERE CType = ? AND sys_language_uid > 0', ['gridelements_pi1'])->fetchOne() > 0) {
|
||||
$output->writeln('<error>Translated grids found; translations are not handled by this command.</error>');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
$counts = [];
|
||||
$report = [];
|
||||
$count = static function (string $key) use (&$counts): void {
|
||||
$counts[$key] = ($counts[$key] ?? 0) + 1;
|
||||
};
|
||||
|
||||
foreach ($grids as $uid => $grid) {
|
||||
$layout = (string)$grid['tx_gridelements_backend_layout'];
|
||||
if (isset(self::LAYOUTS[$layout])) {
|
||||
$updates[$uid] = ['CType' => self::LAYOUTS[$layout][0], 'pi_flexform' => ''] + self::RESET;
|
||||
$count('grid ' . $layout . ((int)$grid['deleted'] ? ' (deleted)' : ''));
|
||||
continue;
|
||||
}
|
||||
// A grid without layout renders nothing; keep it restorable as an empty container
|
||||
$updates[$uid] = ['CType' => 'ep_container_halves', 'pi_flexform' => '', 'deleted' => 1] + self::RESET;
|
||||
$count('grid without layout → deleted');
|
||||
if (!(int)$grid['deleted']) {
|
||||
$report[] = [$grid, 'grid', 'grid without layout', 'deleted'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($children as $child) {
|
||||
$uid = (int)$child['uid'];
|
||||
$parent = $grids[(int)$child['tx_gridelements_container']] ?? null;
|
||||
$column = (int)$child['tx_gridelements_columns'];
|
||||
|
||||
if ((int)$child['colPos'] >= 0) {
|
||||
// Rendered in its page column, not by the grid (gridelements only queries colPos -1)
|
||||
$updates[$uid] = self::RESET;
|
||||
$count('child outside the grid (colPos ' . $child['colPos'] . '), stays');
|
||||
if (!(int)$child['deleted']) {
|
||||
$report[] = [$child, 'page column', 'colPos ' . $child['colPos'] . ' instead of -1', 'unchanged, grid fields cleared'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($parent !== null && (int)$child['pid'] !== (int)$parent['pid']) {
|
||||
// A container only renders children of its own page
|
||||
$parent = null;
|
||||
}
|
||||
$config = self::LAYOUTS[(string)($parent['tx_gridelements_backend_layout'] ?? '')] ?? null;
|
||||
if ($parent !== null && $config !== null && (int)$child['colPos'] === -1 && in_array($column, $config[2], true)) {
|
||||
$updates[$uid] = [
|
||||
'tx_container_parent' => (int)$parent['uid'],
|
||||
'colPos' => $config[1] + $column % 10,
|
||||
] + self::RESET;
|
||||
$count('A: child migrated' . ((int)$child['deleted'] ? ' (deleted)' : ''));
|
||||
// Inside a grid, TYPO3 10 did not apply settings.defaultHeaderType, so the header of these
|
||||
// types stayed invisible with layout "default". Keep it invisible, and visible as such to editors.
|
||||
if ((int)$child['header_layout'] === 0 && trim((string)$child['header']) !== '' && in_array($child['CType'], self::DEFAULT_HEADER_TYPES, true)) {
|
||||
$updates[$uid]['header_layout'] = 100;
|
||||
$count('A: header layout default → hidden');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// B (column not in the layout) and C (parent is no grid): invisible in TYPO3 10, soft deleted
|
||||
$group = $parent === null ? 'C' : 'B';
|
||||
$reason = $parent === null
|
||||
? 'parent ' . $child['tx_gridelements_container'] . ' is not a grid on the same page'
|
||||
: 'column ' . $column . ' (colPos ' . $child['colPos'] . ') not in layout ' . $parent['tx_gridelements_backend_layout'];
|
||||
$updates[$uid] = ['deleted' => 1] + self::RESET;
|
||||
$count($group . ': child deleted' . ((int)$child['deleted'] ? ' (was deleted already)' : ''));
|
||||
if (!(int)$child['deleted']) {
|
||||
$report[] = [$child, $group, $reason, 'deleted'];
|
||||
}
|
||||
}
|
||||
|
||||
// Other rows still carrying grid values (e.g. a former grid whose CType was changed by hand)
|
||||
$leftovers = (int)$connection->executeQuery(
|
||||
'SELECT COUNT(*) FROM tt_content WHERE (tx_gridelements_children > 0 OR tx_gridelements_backend_layout NOT IN (\'\', \'0\')) AND CType <> ? AND tx_gridelements_container = 0',
|
||||
['gridelements_pi1']
|
||||
)->fetchOne();
|
||||
|
||||
ksort($counts);
|
||||
foreach ($counts as $label => $number) {
|
||||
$output->writeln(sprintf('%6d %s', $number, $label));
|
||||
}
|
||||
if ($leftovers > 0) {
|
||||
$output->writeln(sprintf('%6d other rows with grid values, cleared', $leftovers));
|
||||
}
|
||||
if ($input->getOption('report')) {
|
||||
if (!$this->writeReport((string)$input->getOption('report'), $report)) {
|
||||
$output->writeln(sprintf('<error>Cannot write the report to %s, nothing migrated.</error>', $input->getOption('report')));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln(sprintf('Report: %d rows in %s', count($report), $input->getOption('report')));
|
||||
}
|
||||
if ($updates === [] && $leftovers === 0) {
|
||||
$output->writeln('Nothing to migrate.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
if ($input->getOption('dry-run')) {
|
||||
$output->writeln('<comment>Dry run, nothing written.</comment>');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$connection->transactional(function (Connection $connection) use ($updates): void {
|
||||
foreach ($updates as $uid => $values) {
|
||||
$connection->update('tt_content', $values, ['uid' => $uid]);
|
||||
}
|
||||
$connection->executeStatement(
|
||||
'UPDATE tt_content SET tx_gridelements_children = 0, tx_gridelements_backend_layout = \'\' WHERE tx_gridelements_children > 0 OR tx_gridelements_backend_layout NOT IN (\'\', \'0\')'
|
||||
);
|
||||
});
|
||||
|
||||
$output->writeln(sprintf('<info>%d rows migrated.</info> Next: referenceindex:update, container:integrity, cache:flush', count($updates)));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function hasGridelementsColumns(Connection $connection): bool
|
||||
{
|
||||
return array_key_exists('tx_gridelements_container', $connection->createSchemaManager()->listTableColumns('tt_content'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{0: array<string, mixed>, 1: string, 2: string, 3: string}> $rows
|
||||
*/
|
||||
private function writeReport(string $file, array $rows): bool
|
||||
{
|
||||
$handle = @fopen($file, 'wb');
|
||||
if ($handle === false) {
|
||||
return false;
|
||||
}
|
||||
$titles = [];
|
||||
foreach ($this->connectionPool->getConnectionForTable('pages')->executeQuery('SELECT uid, title FROM pages WHERE sys_language_uid = 0')->iterateAssociative() as $page) {
|
||||
$titles[(int)$page['uid']] = $page['title'];
|
||||
}
|
||||
fputcsv($handle, ['uid', 'pid', 'page', 'CType', 'header', 'hidden', 'group', 'reason', 'action'], escape: '');
|
||||
foreach ($rows as [$row, $group, $reason, $action]) {
|
||||
fputcsv($handle, [
|
||||
$row['uid'], $row['pid'], $titles[(int)$row['pid']] ?? '', $row['CType'], $row['header'], $row['hidden'], $group, $reason, $action,
|
||||
], escape: '');
|
||||
}
|
||||
return fclose($handle);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ call_user_func(function () {
|
||||
)
|
||||
);
|
||||
|
||||
// Column layouts, migrated from gridelements (ep:migrate-gridelements). colPos = 200 + 10 × layout + column
|
||||
// Column layouts, migrated from gridelements in 2026 (commit 4172ed9). colPos = 200 + 10 × layout + column
|
||||
$menus = 'menu_abstract,menu_categorized_content,menu_categorized_pages,menu_recently_updated,menu_related_pages,menu_section_pages,menu_sitemap,menu_sitemap_pages';
|
||||
$columnContainers = [
|
||||
'ep_container_halves' => ['Spalten 50/50', 'Zwei gleich breite Spalten', 'halves', [[201], [202]]],
|
||||
|
||||
@@ -182,44 +182,3 @@ CREATE TABLE tx_eptheme_ttcontent_tx_eptheme_disrupter_mm
|
||||
KEY uid_local (uid_local),
|
||||
KEY uid_foreign (uid_foreign)
|
||||
);
|
||||
|
||||
CREATE TABLE tx_eptheme_domain_model_addresscheck
|
||||
(
|
||||
uid int(11) NOT NULL auto_increment,
|
||||
pid int(11) DEFAULT '0' NOT NULL,
|
||||
tstamp int(11) DEFAULT '0' NOT NULL,
|
||||
crdate int(11) DEFAULT '0' NOT NULL,
|
||||
cruser_id int(11) DEFAULT '0' NOT NULL,
|
||||
deleted tinyint(3) DEFAULT '0',
|
||||
hidden tinyint(4) DEFAULT '0' NOT NULL,
|
||||
|
||||
name tinytext,
|
||||
first_name tinytext,
|
||||
last_name tinytext,
|
||||
customer_id varchar(255) DEFAULT '' NOT NULL,
|
||||
title varchar(255) DEFAULT '' NOT NULL,
|
||||
email varchar(255) DEFAULT '' NOT NULL,
|
||||
address tinytext,
|
||||
city varchar(255) DEFAULT '' NOT NULL,
|
||||
zip varchar(20) DEFAULT '' NOT NULL,
|
||||
country varchar(128) DEFAULT '' NOT NULL,
|
||||
|
||||
no_postal tinyint(4) DEFAULT '0' NOT NULL,
|
||||
no_email tinyint(4) DEFAULT '0' NOT NULL,
|
||||
address_current tinyint(4) DEFAULT '0' NOT NULL,
|
||||
|
||||
t3ver_oid int(11) DEFAULT '0' NOT NULL,
|
||||
t3ver_id int(11) DEFAULT '0' NOT NULL,
|
||||
t3ver_wsid int(11) DEFAULT '0' NOT NULL,
|
||||
t3ver_label varchar(30) DEFAULT '' NOT NULL,
|
||||
t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
|
||||
t3ver_stage tinyint(4) DEFAULT '0' NOT NULL,
|
||||
t3ver_count int(11) DEFAULT '0' NOT NULL,
|
||||
t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
|
||||
t3ver_move_id int(11) DEFAULT '0' NOT NULL,
|
||||
t3_origuid int(11) DEFAULT '0' NOT NULL,
|
||||
|
||||
PRIMARY KEY (uid),
|
||||
KEY parent (pid),
|
||||
KEY pid (pid, email)
|
||||
);
|
||||
Reference in new issue
Block a user