Zum Inhalt springen
Marketing Factory Digital GmbH
Kontakt
Logo Marketing Factory Digital GmbH
  • Agentur
    • Aktuelles
    • Über uns
    • Geschichte
  • Leistungen
    • Beratung, Analyse und Strategie
    • Programmierung und Entwicklung
      • Schnittstellen
      • PIM-/ERP-Anbindungen
      • Individualentwicklungen
      • Seamless CMS Integration
    • Hosting und Betreuung
      • Betrieb auf unserer Colocation-Hardware
      • Cloud-Strategien
    • Leistungen mit Dritten
  • Technologie
    • TYPO3
      • Aktuelle TYPO3-Versionen
    • Shopware
    • IT-Sicherheit
      • DDoS-Protection
      • Continuous Upgrading
      • Privacy First
    • Tech Stack
      • Bekenntnis zu Open Source
      • Technologieauswahl
      • PHP-Ökosystem
      • Containerisierung & Clustering
      • Content Delivery Networks
      • Suchtechnologien
  • Referenzen
    • Projekte
    • Kunden
      • Kundenliste
    • Screenshot der Homepage der neuen Maxion Wheels WebsiteNEU: Relaunch der Corporate Website von Maxion Wheels
  • Community
    • Community-Initiativen
  • Blog
  • Kontakt
  • Deutsch
  • English

Sie sind here:

  1. Blog
  2. Hilf mir, TYPO3 Upgrade Wizard! Gridelements-Datensätze sauber migrieren.
  • Development
  • TYPO3
10.03.2022

Hilf mir, TYPO3 Upgrade Wizard! Gridelements-Datensätze sauber migrieren.


Der vierte und letzte Teil dieser Blogreihe: Unsere neuen Container und Inhalts­elemente sind konfiguriert und einsatzbereit. Aber im Projekt existieren immer noch die alten Datensätze, die mit Gridelements und deren Kind­elementen 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.

Zeige größere Version von: Tab-Element
Eine Gruppe von Tabs (Reitern), zwischen denen der Nutzer durch Klick blättern kann (Screenshot von www.bootstrap-package.com)

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?

Sebastian Klein

Steht irgendwo zwischen Front- und Backend. Mit einem Faible für Usability und Dokumentation. Immer auf der Suche nach Good Practices.

Weitere Beiträge dieses Autors

Blog als RSS-Feed abonnieren

Alle Teile der Blogreihe

  1. Fehler der Vergangenheit: Wie wir Gridelements systematisch ersetzen
  2. Planung ist die halbe Migration: Was beim Gridelements-Wechsel zu beachten ist
  3. Container-Elemente sinnvoll erweitern
  4. Hilf mir, TYPO3 Upgrade Wizard! Gridelements-Datensätze sauber migrieren.

Wir freuen uns, wenn Ihr diesen Beitrag teilt.


Kommentare

Keine Kommentare gefunden.

Kommentar verfassen.

Ich bin darauf hingewiesen worden, dass die Verarbeitung meiner Daten auf freiwilliger Basis erfolgt und dass ich mein Einverständnis ohne für mich nachteilige Folgen verweigern bzw. jederzeit gegenüber der Marketing Factory Digital GmbH per Post (Marienstraße 14, D-40212 Düsseldorf) oder E-Mail (info@marketing-factory.de) widerrufen kann.

Mir ist bekannt, dass die oben angegebenen Daten so lange gespeichert werden, wie ich die Kontaktaufnahme durch Marketing Factory wünsche. Nach meinem Widerruf werden meine Daten gelöscht. Eine weitergehende Speicherung kann im Einzelfall erfolgen, wenn dies gesetzlich vorgeschrieben ist.

  • Datenschutzerklärung
  • Impressum

© Marketing Factory Digital GmbH

Bildnachweise
  1. "Tab-Element": www.bootstrap-package.com
  2. "Beitragsbild Gridelements-Migration": © Sebastian Klein / Marketing Factory Digital GmbH