Hilf mir, TYPO3 Upgrade Wizard! Gridelements-Datensätze sauber migrieren.
Der vierte und letzte Teil dieser Blogreihe: Unsere neuen Container und Inhaltselemente sind konfiguriert und einsatzbereit. Aber im Projekt existieren immer noch die alten Datensätze, die mit Gridelements und deren Kindelementen erstellt wurden.
Daher zeigen wir euch diesmal, wie ihr bestehende Daten migrieren könnt.
Mögliche Wege der Migration
In unserem konkreten Projekt haben wir Daten auf drei Arten migriert:
1. Mittels einfachem SQL-Skript
Wenn es nur um kleine Anpassungen in der Datenbank ging, haben wir diese über SQL-Skripte durchgeführt. Beispielsweise hatten wir im Zuge des Upgrades Backend-Layouts umbenannt; deren Identifier mussten dann in den Seiteneigenschaften aktualisiert werden.
UPDATE `pages` SET `backend_layout` = 'pagets__1_column' WHERE `backend_layout` = 'pagets__7';
UPDATE `pages` SET `backend_layout_next_level` = 'pagets__1_column' WHERE `backend_layout_next_level` = 'pagets__7';
Auf ähnliche Weise haben wir auch einige Layouts und Frames durch neue Lösungen ersetzt.
2. Manuelles Anlegen der neuen Datensätze
Klar, Automatisierung ist immer zu bevorzugen. Aber seien wir ehrlich: bei einer geringen Menge von Datensätzen geht das schneller, als ein Update-Skript zu schreiben. Dieses Vorgehen ist allerdings auch nicht in jedem Projekt möglich.
3. Mithilfe eigener TYPO3 Upgrade Wizards
Upgrade Wizards kennt ihr aus dem TYPO3 Install Tool. Bei einem Wechsel von TYPO3 v9 auf v10 müsst ihr zum Beispiel die Datensätze aus der veralteten Tabelle “pages_language_overlay
” in die Tabelle “pages” migrieren.
TYPO3 stellt ein Interface bereit, mit dem Entwickler eigene Upgrade Wizards ergänzen können.
Ein Upgrade Wizard eignet sich für komplexe Migrationen:
- Anlegen neuer Datensätze in einer beliebigen Tabelle
- Änderung des CTypes
- Kopieren alter Daten in neue Tabellen(-felder)
- Aktualisierung von FAL-Relationen
- Anpassung von Feldwerten
- Löschen der alten Datensätze
- Beschränkung der Migration auf bestimmte CTypes, Eltern-Gridelemente, …
Unser vollständiges Beispiel weiter unten wird das sehr anschaulich zeigen.
Aufbau eines TYPO3 Upgrade Wizard
Ein gut verständliches Tutorial zur Erstellung eigener Upgrade Wizards findet ihr in der offiziellen TYPO3-Dokumentation.
Daher führen wir hier nur ein paar Eckdaten auf:
- Eigene Upgrade Wizards können im Sitepackage oder anderen Extensions ergänzt werden.
- Vor Ausführung des Upgrades lässt sich prüfen, ob eine Migration notwendig ist.
- Die Reihenfolge auszuführender Upgrade Wizards lässt sich bei Bedarf festlegen.
Es kann auch weiterhelfen, sich andere Upgrade Wizards aus dem TYPO3 Core oder aus Extensions näher anzusehen.
Beispiel: Migration von Gridelements zum Tab-Element des Bootstrap Package
Den folgenden, mustergültigen Upgrade Wizard hat meine Kollegin Mirena Peneva geschrieben.
Die Ausgangslage im Projekt:
- Ein einspaltiges Gridelement “Tab-Container”
- Jedes dieser Gridelemente kann mehrere Inhaltselemente vom Typ “Text & Images” (CType “
textpic
”) beinhalten - Einige Inhaltselemente beinhalten Bilder (FAL-Relationen)
- Im Frontend werden die so gruppierten Inhalte als Reiter (“Tabs”) ausgegeben
Unser Ziel:
- Verwendung des Tab-Elements (mit Inline-Elementen) aus dem Bootstrap Package
Aufgaben:
- Alle betroffenen Elemente auswählen und deren CType entsprechend ändern
- Neue Inline-Elemente für die bisherigen Gridelements-Kindelemente erstellen und bestehende Inhalte dahin migrieren
- Falls vorhanden, FAL-Relationen in der Tabelle “
sys_file_reference
” mit dem neuen Inline-Element verknüpfen - Alte Datensätze löschen
ext_localconf.php:
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['tabs']
= \MyProject\Sitepackage\Updates\MigrateTabs::class;
Classes/Updates/MigrateTabs.php:
<?php
namespace MyProject\Sitepackage\Updates;
use Exception;
use InvalidArgumentException;
use RuntimeException;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
/**
* Migrates existing "Tab Container" content elements to CType "tab".
* In TYPO3 v8, the website contains a gridelements container with layout "Tab Container".
* The contents of this container have to be migrated to use the CType "tab" for bootstrap package tabs.
* "Tab Container" content elements are restricted to colPos "0" in all backend layouts.
*
*/
class MigrateTabs implements UpgradeWizardInterface
{
/**
* @var int
*/
protected int $gridElementBELayout = 14;
/**
* @var string
*/
protected string $tableTab = 'tt_content';
/**
* @var string
*/
protected string $targetTableTabItem = 'tx_bootstrappackage_tab_item';
/**
* @var string
*/
protected string $targetCTypeTab = 'tab';
/**
* @var int
*/
protected int $colPos = 0;
/**
* @var string
*/
protected string $sourceFieldNameImage = 'image';
/**
* @var string
*/
protected string $targetFieldNameImage = 'media';
/**
* Return the identifier for this wizard
* This should be the same string as used in the ext_localconf class registration
*
* @return string
*/
public function getIdentifier(): string
{
return 'tabs';
}
/**
* Return the speaking name of this wizard
*
* @return string
*/
public function getTitle(): string
{
return 'Tabs: Migrate existing tabs with Grid Layout "Tab Container"';
}
/**
* Return the description for this wizard
*
* @return string
*/
public function getDescription(): string
{
return 'Migrate existing tabs with Grid Layout "Tab Container"';
}
/**
* Execute the update
*
* Called when a wizard reports that an update is necessary
*
* @return bool Whether everything went smoothly or not
*/
public function executeUpdate(): bool
{
$this->migrateTabs();
return true;
}
/**
* Migrate existing content elements to new CType "tab".
* 1. Get all gridelements containers with Grid Layout "Tab Container" and change their CType to "tab".
* 2. Add a new content element for each of the container's children.
* 3. Migrate the fields "header", "bodytext" and "image" to the fields in the new elements:
* "header", "bodytext" and "media"
*/
protected function migrateTabs()
{
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->tableTab);
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
/* @var QueryBuilder $queryBuilder */
$queryBuilder = $connectionPool->getQueryBuilderForTable($this->tableTab);
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
try {
// 1. Get all gridelements containers with Grid Layout "Tab Container"
$gridElementsContainers = $queryBuilder
->select('*')
->from($this->tableTab)
->where(
$queryBuilder->expr()->eq('tx_gridelements_backend_layout', $this->gridElementBELayout)
)
->orderBy('uid')
->execute()
->fetchAllAssociative();
foreach ($gridElementsContainers as $container) {
// and change their CType to "tab"
$fields = [
'CType' => $this->targetCTypeTab,
'tx_gridelements_container' => 0,
'header' => 'Tab Container',
'header_layout' => 100,
'colPos' => $this->colPos
];
$updatedRows = $connection->update(
$this->tableTab,
$fields,
['uid' => $container['uid']]
);
if ($updatedRows > 0) {
// Get all children elements in the selected gridelements containers
$tabItems = $queryBuilder
->select('*')
->from($this->tableTab)
->where(
$queryBuilder->expr()->eq('tx_gridelements_container', $container['uid'])
)
->orderBy('uid')
->execute();
foreach ($tabItems as $item) {
// 2. Add a new content element for each of the container's children
$queryBuilderTabItem = $connectionPool->getQueryBuilderForTable($this->targetTableTabItem);
$insertSuccessful = $queryBuilderTabItem
->insert($this->targetTableTabItem)
->values([
'pid' => $item['pid'],
'tt_content' => $item['tx_gridelements_container'],
'header' => $item['header'],
'bodytext' => $item['bodytext'],
'tstamp' => $GLOBALS['EXEC_TIME'],
'crdate' => $GLOBALS['EXEC_TIME'],
'mediaorient' => 'right',
$this->targetFieldNameImage => $item[$this->sourceFieldNameImage]
])
->execute();
if ($insertSuccessful) {
// Migrate existing images
$newItemUid = $queryBuilder->getConnection()->lastInsertId();
$fileReferenceQueryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
$fileReferenceQueryBuilder
->update('sys_file_reference')
->where(
$queryBuilderTabItem->expr()->andX(
$queryBuilderTabItem->expr()->eq(
'fieldname',
$queryBuilderTabItem->quote($this->sourceFieldNameImage)
),
$queryBuilderTabItem->expr()->eq('uid_foreign', $item['uid']),
$queryBuilderTabItem->expr()->eq(
'tablenames',
$queryBuilder->quote($this->tableTab)
)
)
)
->set('uid_foreign', $newItemUid)
->set('tablenames', $this->targetTableTabItem)
->set('fieldname', $this->targetFieldNameImage)
->execute();
}
}
// Delete old tab items
$queryBuilderTabItemsOld = $connectionPool->getQueryBuilderForTable($this->tableTab);
$queryBuilderTabItemsOld
->delete($this->tableTab)
->where(
$queryBuilderTabItemsOld->expr()->eq('tx_gridelements_container', $container['uid'])
)
->execute();
}
}
} catch (Exception $e) {
throw new RuntimeException(
'Database query failed. Error was: ' . $e->getMessage(),
1605857008
);
}
}
/**
* Is an update necessary?
*
* Is used to determine whether a wizard needs to be run.
* Check if data for migration exists.
*
* @return bool
*/
public function updateNecessary(): bool
{
$updateNeeded = false;
if ($this->checkIfWizardIsRequired()) {
$updateNeeded = true;
}
return $updateNeeded;
}
/**
* Returns an array of class names of prerequisite classes
*
* @return string[]
*/
public function getPrerequisites(): array
{
return [];
}
/**
* Check if there are gridelements containers with matching Grid Layout.
*
* @return bool
* @throws InvalidArgumentException
*/
protected function checkIfWizardIsRequired(): bool
{
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$queryBuilder = $connectionPool->getQueryBuilderForTable($this->tableTab);
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$numberOfEntries = $queryBuilder
->count('uid')
->from($this->tableTab)
->where(
$queryBuilder->expr()->eq('tx_gridelements_backend_layout', $this->gridElementBELayout)
)
->execute()
->fetchOne();
return $numberOfEntries > 0;
}
}
Dieser Upgrade Wizard ist eine praktische Blaupause, die ihr an euer Projekt anpassen könnt. Etwas Erfahrung mit dem TYPO3 QueryBuilder ist von Vorteil; es bietet sich aber auch eine gute Gelegenheit zur Einarbeitung.
Wir wünschen euch viel Erfolg beim Ausprobieren! Habt ihr noch Fragen zum Thema?
Wir freuen uns, wenn Ihr diesen Beitrag teilt.
Kommentare
Keine Kommentare gefunden.