[TYPO3] Migration von tt_news zu tx_news

[TYPO3] Migration von tt_news zu tx_news

Es gibt sie immer noch da draußen: Große und in die Jahre gekommene TYPO3 Installationen mit der Extension tt_news. Vielleicht wird es nun doch einmal Zeit auf tx_news umzustellen und die News Datensätze sollen migriert werden. Und eventuell müssen auch noch die News Plugins migriert werden. Hier kann euch EXT:migration helfen.

Die Extension migration ist ein kleines Schweizer Taschenmesser im TYPO3-Bereich. Neben der Möglichkeit ganze Seitenbäume auf der CLI zu ex- und wieder importieren, bietet die Extension auch die Möglichkeit, Migrationen innerhalb der bestehenden Datenbank durchzuführen. Die Dokumentation auf GitHub bietet hier ein paar hilfreiche Tipps.

In diesem Blogbeitrag geht es jedoch nur um die News-Migration. Wir haben in der Vergangenheit bereits eine Hand voll älterer Projekte von tt_news auf tx_news umgestellt und ich habe euch die benutzten Klassen einmal hier bereitgestellt. Falls ihr diese benötigt, könnt ihr euch direkt bedienen.

Hinweis: Über die Zeit hinweg gab es verschiedene Varianten von tt_news und natürlich auch von tx_news. Eventuell müsst ihr also an der einen oder anderen Stelle doch noch Hand anlegen. Wir hoffen jedoch, dass euch der Code dennoch weiter hilft - seht es vielleicht als eine Art Kickstarter.

1. Die Hauptkonfiguration

Datei EXT:migration_extend/Configuration/Migration.php:

<?php return [ // Default values if not given from CLI 'configuration' => [ 'key' => '', 'dryrun' => true, 'limitToRecord' => null, 'limitToPage' => 583, 'recursive' => true ], // Define your migrations 'migrations' => [ [ 'className' => \In2code\MigrationExtend\Migration\Importer\NewsCategoriesImporter::class, 'keys' => [ 'news', 'categories', 'categoriesimport' ] ], [ 'className' => \In2code\MigrationExtend\Migration\Migrator\CategoriesMigrator::class, 'keys' => [ 'news', 'categories', 'categoriesmigration' ] ], [ 'className' => \In2code\MigrationExtend\Migration\Importer\NewsImporter::class, 'keys' => [ 'news' ] ], [ 'className' => \In2code\MigrationExtend\Migration\Migrator\NewsMigrator::class, 'keys' => [ 'news' ] ], [ 'className' => \In2code\MigrationExtend\Migration\Migrator\ContentMigrator::class, 'keys' => [ 'content' ] ] ] ];

Datei EXT:migration_extend/ext_tables.sql

CREATE TABLE tx_news_domain_model_news ( _migrated tinyint(4) unsigned DEFAULT '0' NOT NULL, _migrated_uid int(11) unsigned DEFAULT '0' NOT NULL, _migrated_table varchar(255) DEFAULT '' NOT NULL, _migrated_twice tinyint(4) unsigned DEFAULT '0' NOT NULL ); CREATE TABLE sys_category ( _migrated tinyint(4) unsigned DEFAULT '0' NOT NULL, _migrated_uid int(11) DEFAULT '0' NOT NULL, _migrated_table varchar(255) DEFAULT '' NOT NULL, _migrated_twice tinyint(4) unsigned DEFAULT '0' NOT NULL );

Datei EXT:migration_extend/ext_localconf.php:

<?php if (!defined('TYPO3_MODE')) { die('Access denied.'); } call_user_func( function () { /** * Fluid Namespace */ $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['migrationextend'][] = 'In2code\MigrationExtend\ViewHelpers'; } );

2. Die Importer

Datei EXT:migration_extend/Classes/Migration/Importer/NewsImporter.php (Basis Import der Newsdatensätze):

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\Importer; use In2code\Migration\Migration\Importer\AbstractImporter; use In2code\Migration\Migration\Importer\ImporterInterface; /** * Class NewsImporter */ class NewsImporter extends AbstractImporter implements ImporterInterface { /** * @var string */ protected $tableName = 'tx_news_domain_model_news'; /** * @var string */ protected $tableNameOld = 'tt_news'; /** * @var bool */ protected $truncate = false; /** * @var bool */ protected $keepIdentifiers = false; /** * @var array */ protected $mapping = [ 'type' => 'type', 'title' => 'title', 'short' => 'teaser', 'bodytext' => 'bodytext', 'datetime' => 'datetime', 'author' => 'author', 'author_email' => 'author_email', 'keyword' => 'keywords', 'archivedate' => 'archive', 'editlock' => 'editlock', 'keywords' => 'keywords', 'page' => 'internalurl', 'ext_url' => 'externalurl', 'hidden' => 'hidden', 'fe_group' => 'fe_group', 'sys_language_uid' => 'sys_language_uid', 'l18n_parent' => 'l10n_parent', 'l18n_diffsource' => 'l10n_diffsource', 'category' => 'categories', ]; }

Datei EXT:migration_extend/Classes/Migration/Importer/NewsCategoriesImporter.php (Basis Import der News Kategorien):

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\Importer; use In2code\Migration\Migration\Importer\AbstractImporter; use In2code\Migration\Migration\Importer\ImporterInterface; use In2code\MigrationExtend\Migration\PropertyHelpers\CreateSortingNumberFromPropertyPropertyHelper; /** * Class NewsCategoriesImporter */ class NewsCategoriesImporter extends AbstractImporter implements ImporterInterface { /** * Table name where to migrate to * * @var string */ protected $tableName = 'sys_category'; /** * Table name from migrate to * * @var string */ protected $tableNameOld = 'tt_news_cat'; /** * @var bool */ protected $truncate = false; /** * @var bool */ protected $keepIdentifiers = false; /** * @var array */ protected $mapping = [ 'pid' => 'pid', 'title' => 'title', 'parent_category' => 'parent', 'fe_group' => 'fe_group', 'sorting' => 'sorting' ]; /** * PropertyHelpers are called after initial build via mapping * * "newProperty" => [ * [ * "className" => class1::class, * "configuration => ["red"] * ], * [ * "className" => class2::class * ] * ] * * @var array */ protected $propertyHelpers = [ 'sorting' => [ [ 'className' => CreateSortingNumberFromPropertyPropertyHelper::class, 'configuration' => [ 'property' => 'title' ] ] ] ]; }

3. Die Migratoren

Datei EXT:migration_extend/Classes/Migration/Migrator/ContentMigrator.php (migriert News Plugins):

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\Migrator; use In2code\Migration\Migration\Migrator\AbstractMigrator; use In2code\Migration\Migration\Migrator\MigratorInterface; use In2code\Migration\Migration\PropertyHelpers\FlexFormGeneratorPropertyHelper; /** * Class ContentMigrator */ class ContentMigrator extends AbstractMigrator implements MigratorInterface { /** * @var string */ protected $tableName = 'tt_content'; /** * @var array */ protected $propertyHelpers = [ 'pi_flexform' => [ [ // Build FlexForm for News plugin 'className' => FlexFormGeneratorPropertyHelper::class, 'configuration' => [ 'condition' => [ 'CType' => 'list', 'list_type' => '9' // Tt_news plugin ], 'flexFormTemplate' => 'EXT:migration_extend/Resources/Private/FlexForms/News.xml', 'flexFormField' => 'pi_flexform', 'overwriteValues' => [ 'list_type' => 'news_pi1' ], 'additionalMapping' => [ [ // create new variable {additionalMapping.switchableControllerActions} 'variableName' => 'switchableControllerActions', 'keyField' => 'flexForm:what_to_display', // "flexForm:path/path" or: "row:uid" 'mapping' => [ 'LIST' => 'News->list;News->detail', 'LIST2' => 'News->list;News->detail', 'LIST3' => 'News->list;News->detail', 'HEADER_LIST' => 'News->list;News->detail', 'LATEST' => 'News->list;News->detail', 'SINGLE' => 'News->detail', 'SINGLE2' => 'News->detail', 'AMENU' => 'News->list;News->detail', 'SEARCH' => 'News->list;News->detail', 'CATMENU' => 'News->list;News->detail', 'VERSION_PREVIEW' => 'News->list;News->detail', 'EVENT_FUTURE' => 'News->list;News->detail', 'EVENT_PAST' => 'News->list;News->detail', 'LATEST_EVENT_PAST' => 'News->list;News->detail', 'LATEST_EVENT_FUTURE' => 'News->list;News->detail', 'EVENT_CURRENT' => 'News->list;News->detail', 'LATEST_EVENT_CURRENT' => 'News->list;News->detail', 'EVENT_REGISTERABLE' => 'News->list;News->detail', 'LATEST_EVENT_REGISTERABLE' => 'News->list;News->detail' ] ], [ // create new variable {additionalMapping.categorySetting} 'variableName' => 'categorySetting', 'keyField' => 'flexForm:categoryMode', // "flexForm:path/path" or: "row:uid" 'mapping' => [ '0' => '', // show all '1' => 'or', // show from categories (OR) '2' => 'and', // show from categories (AND) '-1' => 'notand', // don't show from categories (AND) '-2' => 'notor', // don't show from categories (OR) ] ], [ // create new variable {additionalMapping.archiveSetting} 'variableName' => 'archiveSetting', 'keyField' => 'flexForm:archive', // "flexForm:path/path" or: "row:uid" 'mapping' => [ '0' => '', // don't care '1' => 'archived', // archived only '-1' => 'active', // not archived only ] ] ] ] ] ], ]; }

Datei EXT:migration_extend/Classes/Migration/Migrator/NewsMigrator.php (Erweiterte Migration der News Datensätze mit Relationen):

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\Migrator; use In2code\Migration\Migration\Migrator\AbstractMigrator; use In2code\Migration\Migration\Migrator\MigratorInterface; use In2code\Migration\Migration\PropertyHelpers\SlugPropertyHelper; use In2code\MigrationExtend\Migration\PropertyHelpers\CreateNewsCategoryRelationPropertyHelper; use In2code\MigrationExtend\Migration\PropertyHelpers\CreateNewsFileRelationsPropertyHelper; use In2code\MigrationExtend\Migration\PropertyHelpers\CreateNewsImageRelationAndMoveImagePropertyHelper; use In2code\MigrationExtend\Migration\PropertyHelpers\CreateNewsRelatedRelationsPropertyHelper; /** * Class NewsMigrator * To update previous imported news records with relations */ class NewsMigrator extends AbstractMigrator implements MigratorInterface { /** * @var string */ protected $tableName = 'tx_news_domain_model_news'; /** * @var bool */ protected $enforce = true; /** * Filter selection of old records like "and pid > 0" (to prevent elements in a workflow e.g.) * * @var string */ protected $additionalWhere = 'and _migrated=1 and _migrated_table="tt_news" and _migrated_twice=0'; /** * @var array */ protected $values = [ '_migrated_twice' => 1 // Don't migrate a second time (for other branches that should also be migrated) ]; /** * @var array */ protected $sql = [ 'end' => [ 'update sys_file_reference r left join tx_news_domain_model_news n on r.uid_foreign = n.uid set r.pid = n.pid where r.tablenames LIKE "tx_news_domain_model_news" and n.deleted=0' ] ]; /** * @var array */ protected $propertyHelpers = [ 'categories' => [ [ 'className' => CreateNewsCategoryRelationPropertyHelper::class ] ], 'fal_media' => [ [ 'className' => CreateNewsImageRelationAndMoveImagePropertyHelper::class ] ], 'fal_related_files' => [ [ 'className' => CreateNewsFileRelationsPropertyHelper::class ] ], 'related' => [ [ 'className' => CreateNewsRelatedRelationsPropertyHelper::class ] ], 'path_segment' => [ [ 'className' => SlugPropertyHelper::class, 'configuration' => [ 'conditions' => [ 'deleted' => [ '0' ] ] ] ] ] ]; }

Datei EXT:migration_extend/Classes/Migration/Migrator/CategoriesMigrator.php (Erweiterte Migration der Kategorien):

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\Migrator; use In2code\Migration\Migration\Migrator\AbstractMigrator; use In2code\Migration\Migration\Migrator\MigratorInterface; use In2code\MigrationExtend\Migration\PropertyHelpers\GetParentCategoryPropertyHelper; /** * Class CategoriesMigrator */ class CategoriesMigrator extends AbstractMigrator implements MigratorInterface { /** * @var string */ protected $tableName = 'sys_category'; /** * @var bool */ protected $enforce = true; /** * Filter selection of old records like "and pid > 0" (to prevent elements in a workflow e.g.) * * @var string */ protected $additionalWhere = 'and _migrated=1 and _migrated_table="tt_news_cat" and _migrated_twice=0'; /** * @var array */ protected $values = [ '_migrated_twice' => 1 // Don't migrate a second time (for other branches that should also be migrated) ]; /** * @var array */ protected $propertyHelpers = [ 'parent' => [ [ 'className' => GetParentCategoryPropertyHelper::class ] ] ]; }

4. PropertyHelpers

Datei EXT:migration_extend/Classes/Migration/PropertyHelpers/CreateSortingNumberFromPropertyPropertyHelper.php:

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\PropertyHelpers; use Doctrine\DBAL\DBALException; use In2code\Migration\Migration\PropertyHelpers\AbstractPropertyHelper; use In2code\Migration\Migration\PropertyHelpers\PropertyHelperInterface; use In2code\Migration\Utility\DatabaseUtility; /** * Class CreateSortingNumberFromPropertyPropertyHelper */ class CreateSortingNumberFromPropertyPropertyHelper extends AbstractPropertyHelper implements PropertyHelperInterface { /** * @return void * @throws DBALException */ public function manipulate(): void { $sortingArray = $this->getAllOldCategoriesSortedByProperty('sorting'); $sorting = 10000; if (array_key_exists($this->getPropertyFromRecord('uid'), $sortingArray)) { $sorting = $sortingArray[$this->getPropertyFromRecordOld('uid')]; } else { $this->log->addError('Category not sortable: ' . $this->getPropertyFromRecordOld('title')); } $this->setProperty($sorting); } /** * @param string $property * @return array * @throws DBALException */ protected function getAllOldCategoriesSortedByProperty(string $property): array { $connection = DatabaseUtility::getConnectionForTable('tt_news_cat'); $rows = (array)$connection->executeQuery( 'select uid from tt_news_cat where deleted=0 order by "' . $property . '"' )->fetchAll(); $categories = []; $sorting = 100; foreach ($rows as $row) { $categories[$sorting] = $row['uid']; $sorting += 100; } return array_flip($categories); } }

Datei EXT:migration_extend/Classes/Migration/PropertyHelpers/GetParentCategoryPropertyHelper.php:

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\PropertyHelpers; use Doctrine\DBAL\DBALException; use In2code\Migration\Migration\PropertyHelpers\AbstractPropertyHelper; use In2code\Migration\Migration\PropertyHelpers\PropertyHelperInterface; use In2code\Migration\Utility\DatabaseUtility; /** * Class GetParentCategoryPropertyHelper */ class GetParentCategoryPropertyHelper extends AbstractPropertyHelper implements PropertyHelperInterface { /** * @return void * @throws DBALException */ public function manipulate(): void { $queryBuilder = DatabaseUtility::getConnectionForTable($this->table); $sql = 'select uid from sys_category where _migrated_uid=' . (int)$this->getProperty(); $value = (string)$queryBuilder->executeQuery($sql)->fetchColumn(0); if ($value > 0) { $this->log->addMessage('Replace ' . $this->getProperty() . ' with ' . $value . ' in ' . __CLASS__); $this->setProperty($value); } } /** * @return bool */ public function shouldMigrate(): bool { return $this->getProperty() > 0; } }

Datei EXT:migration_extend/Classes/Migration/PropertyHelpers/CreateNewsCategoryRelationPropertyHelper.php:

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\PropertyHelpers; use Doctrine\DBAL\DBALException; use In2code\Migration\Migration\Helper\DatabaseHelper; use In2code\Migration\Migration\PropertyHelpers\AbstractPropertyHelper; use In2code\Migration\Migration\PropertyHelpers\PropertyHelperInterface; use In2code\Migration\Utility\DatabaseUtility; use In2code\Migration\Utility\ObjectUtility; /** * Class CreateNewsCategoryRelationPropertyHelper */ class CreateNewsCategoryRelationPropertyHelper extends AbstractPropertyHelper implements PropertyHelperInterface { /** * @var string */ protected $newTableName = 'sys_category_record_mm'; /** * @var string */ protected $oldTableName = 'tt_news_cat_mm'; /** * @return void * @throws DBALException */ public function manipulate(): void { $databaseHelper = ObjectUtility::getObjectManager()->get(DatabaseHelper::class); $newsUid = (int)$this->getPropertyFromRecord('uid'); $newsUidOld = (int)$this->getPropertyFromRecord('_migrated_uid'); $rows = $this->getOldProperties($newsUidOld); foreach ($rows as $row) { if ((int)$row['uid_foreign'] > 0) { $newCategoryUid = $this->getNewCategoryIdentifier((int)$row['uid_foreign']); if ($newCategoryUid > 0) { $newRow = [ 'uid_foreign' => $newsUid, 'uid_local' => $newCategoryUid, 'sorting' => $row['sorting'], 'tablenames' => 'tx_news_domain_model_news', 'fieldname' => $this->propertyName ]; $databaseHelper->createRecord($this->newTableName, $newRow); $this->log->addMessage('New relation to category with uid ' . $row['uid_foreign'] . ' created'); } } } } /** * @param int $newsUidOld * @return array * @throws DBALException */ protected function getOldProperties(int $newsUidOld): array { $connection = DatabaseUtility::getConnectionForTable($this->oldTableName); $rows = (array)$connection->executeQuery( 'select * from ' . $this->oldTableName . ' where uid_local=' . (int)$newsUidOld )->fetchAll(); return $rows; } /** * @param int $oldIdentifier * @return int */ protected function getNewCategoryIdentifier(int $oldIdentifier): int { $queryBuilder = DatabaseUtility::getQueryBuilderForTable('sys_category'); return (int)$queryBuilder ->select('uid') ->from('sys_category') ->where('_migrated_uid=' . $oldIdentifier . ' and _migrated_table="tt_news_cat"') ->setMaxResults(1) ->execute() ->fetchColumn(0); } }

Datei EXT:migration_extend/Classes/Migration/PropertyHelpers/CreateNewsImageRelationAndMoveImagePropertyHelper.php:

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\PropertyHelpers; use Doctrine\DBAL\DBALException; use In2code\Migration\Migration\Helper\FileHelper; use In2code\Migration\Migration\PropertyHelpers\AbstractPropertyHelper; use In2code\Migration\Migration\PropertyHelpers\PropertyHelperInterface; use In2code\Migration\Utility\DatabaseUtility; use In2code\Migration\Utility\ObjectUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; /** * Class CreateNewsImageRelationAndMoveImagePropertyHelper */ class CreateNewsImageRelationAndMoveImagePropertyHelper extends AbstractPropertyHelper implements PropertyHelperInterface { /** * @var string */ protected $targetFolder = 'files/_migrated/news_images/'; /** * @var string */ protected $oldFolder = 'uploads/pics/'; /** * @return void * @throws DBALException */ public function manipulate(): void { $fileHelper = ObjectUtility::getObjectManager()->get(FileHelper::class); $imageNames = $this->getImageNames(); foreach ($imageNames as $key => $imageName) { $fileHelper->copyFileAndCreateReference( $this->oldFolder . $imageName, $this->targetFolder, $this->table, $this->propertyName, $this->getPropertyFromRecord('uid'), $this->getAdditionalProperties($key) ); $this->log->addMessage('Image copied and created relation to it (' . $imageName . ')'); } } /** * @param int $key * @return array */ protected function getAdditionalProperties(int $key): array { $titleTexts = $this->getTitleTexts(); $altTexts = $this->getAltTexts(); $imageCaptions = $this->getImageCaptions(); $links = $this->getImageLinks(); $additionalProperties = ['showinpreview' => 1]; if (array_key_exists($key, $titleTexts)) { $additionalProperties['title'] = $titleTexts[$key]; } if (array_key_exists($key, $altTexts)) { $additionalProperties['alternative'] = $altTexts[$key]; } if (array_key_exists($key, $imageCaptions)) { $additionalProperties['description'] = $imageCaptions[$key]; } if (array_key_exists($key, $links)) { $additionalProperties['link'] = $links[$key]; } return $additionalProperties; } /** * @return array */ protected function getImageNames(): array { return GeneralUtility::trimExplode(',', $this->getPropertyFromRecordOld('image'), true); } /** * @return array */ protected function getTitleTexts(): array { return GeneralUtility::trimExplode(PHP_EOL, $this->getPropertyFromRecordOld('imagetitletext'), true); } /** * @return array */ protected function getAltTexts(): array { return GeneralUtility::trimExplode(PHP_EOL, $this->getPropertyFromRecordOld('imagealttext'), true); } /** * @return array */ protected function getImageCaptions(): array { return GeneralUtility::trimExplode(PHP_EOL, $this->getPropertyFromRecordOld('imagecaption'), true); } /** * @return array */ protected function getImageLinks(): array { return GeneralUtility::trimExplode(PHP_EOL, $this->getPropertyFromRecordOld('links'), true); } /** * Overrule original function and get values from original tt_news record * * @param string $propertyName * @return int|string */ protected function getPropertyFromRecordOld(string $propertyName) { $propertiesOld = $this->getPropertiesFromOldRecord(); if (array_key_exists($propertyName, $propertiesOld)) { return $propertiesOld[$propertyName]; } else { throw new \LogicException('Property does not exist in ' . __CLASS__, 1569587312); } } /** * @return array */ protected function getPropertiesFromOldRecord(): array { $queryBuilder = DatabaseUtility::getQueryBuilderForTable('tt_news'); return (array)$queryBuilder->select('*') ->from('tt_news') ->where('uid=' . (int)$this->getPropertyFromRecord('_migrated_uid')) ->execute() ->fetch(); } }

Datei EXT:migration_extend/Classes/Migration/PropertyHelpers/CreateNewsFileRelationsPropertyHelper.php:

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\PropertyHelpers; use Doctrine\DBAL\DBALException; use In2code\Migration\Migration\Helper\FileHelper; use In2code\Migration\Migration\PropertyHelpers\AbstractPropertyHelper; use In2code\Migration\Migration\PropertyHelpers\PropertyHelperInterface; use In2code\Migration\Utility\DatabaseUtility; use In2code\Migration\Utility\ObjectUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; /** * Class CreateNewsFileRelationsPropertyHelper */ class CreateNewsFileRelationsPropertyHelper extends AbstractPropertyHelper implements PropertyHelperInterface { /** * @var string */ protected $targetFolder = 'files/_migrated/news_files/'; /** * @var string */ protected $oldFolder = 'uploads/media/'; /** * @return void * @throws DBALException */ public function manipulate(): void { $fileHelper = ObjectUtility::getObjectManager()->get(FileHelper::class); $fileNames = GeneralUtility::trimExplode(',', $this->getPropertyFromRecordOld('news_files'), true); foreach ($fileNames as $fileName) { if (is_file(GeneralUtility::getFileAbsFileName($this->oldFolder . $fileName)) === true) { $fileHelper->copyFileAndCreateReference( $this->oldFolder . $fileName, $this->targetFolder, 'tx_news_domain_model_news', $this->propertyName, $this->getPropertyFromRecord('uid') ); $this->log->addMessage('Related file moved and created relation to it (' . $fileName . ')'); } } } /** * Overrule original function and get values from original tt_news record * * @param string $propertyName * @return int|string */ protected function getPropertyFromRecordOld(string $propertyName) { $propertiesOld = $this->getPropertiesFromOldRecord(); if (array_key_exists($propertyName, $propertiesOld)) { return $propertiesOld[$propertyName]; } else { throw new \LogicException('Property does not exist in ' . __CLASS__, 1569920259); } } /** * @return array */ protected function getPropertiesFromOldRecord(): array { $queryBuilder = DatabaseUtility::getQueryBuilderForTable('tt_news'); return (array)$queryBuilder->select('*') ->from('tt_news') ->where('uid=' . (int)$this->getPropertyFromRecord('_migrated_uid')) ->execute() ->fetch(); } }

Datei EXT:migration_extend/Classes/Migration/PropertyHelpers/CreateNewsRelatedRelationsPropertyHelper.php:

<?php declare(strict_types=1); namespace In2code\MigrationExtend\Migration\PropertyHelpers; use Doctrine\DBAL\DBALException; use In2code\Migration\Migration\Helper\DatabaseHelper; use In2code\Migration\Migration\PropertyHelpers\AbstractPropertyHelper; use In2code\Migration\Migration\PropertyHelpers\PropertyHelperInterface; use In2code\Migration\Utility\DatabaseUtility; use In2code\Migration\Utility\ObjectUtility; /** * Class CreateNewsRelatedRelationsPropertyHelper */ class CreateNewsRelatedRelationsPropertyHelper extends AbstractPropertyHelper implements PropertyHelperInterface { /** * @var string */ protected $mmTableName = 'tx_news_domain_model_news_related_mm'; /** * @return void * @throws DBALException */ public function manipulate(): void { $identifiersOld = $this->getRelatedTtNews(); if ($identifiersOld !== []) { foreach ($identifiersOld as $key => $identifierOld) { $identifier = $this->changeIdentifierFromOldToNew($identifierOld); $properties = [ 'uid_foreign' => (int)$this->getPropertyFromRecord('uid'), 'uid_local' => $identifier, 'sorting' => $key ]; $databaseHelper = ObjectUtility::getObjectManager()->get(DatabaseHelper::class); $databaseHelper->createRecord($this->mmTableName, $properties); $this->log->addMessage( 'new news relation added to news ' . $this->getPropertyFromRecord('uid') . '<=>' . $identifier ); } } } /** * @return int[] */ protected function getRelatedTtNews(): array { $queryBuilder = DatabaseUtility::getQueryBuilderForTable('tt_news_related_mm'); $rows = (array)$queryBuilder->select('*') ->from('tt_news_related_mm') ->where('uid_local=' . (int)$this->getPropertyFromRecord('_migrated_uid')) ->execute() ->fetchAll(); $identifiers = []; foreach ($rows as $row) { if ($row['uid_foreign'] > 0) { $identifiers[] = (int)$row['uid_foreign']; } } return $identifiers; } /** * @param int $oldIdentifier * @return int */ protected function changeIdentifierFromOldToNew(int $oldIdentifier): int { $queryBuilder = DatabaseUtility::getQueryBuilderForTable('tx_news_domain_model_news'); return (int)$queryBuilder->select('uid') ->from('tx_news_domain_model_news') ->where('_migrated_uid=' . (int)$oldIdentifier) ->execute() ->fetchColumn(0); } }

5. FlexForm Templates

Datei EXT:migration_extend/Resources/Private/FlexForms/News.xml:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?> <T3FlexForms> <data> <sheet index="sDEF"> <language index="lDEF"> <field index="settings.orderDirection"> <value index="vDEF">{flexForm.ascDesc}</value> </field> <field index="settings.categories"> <value index="vDEF">{migrationextend:convertNewsCategoryListToNewCategoryList(list:flexForm.categorySelection)}</value> </field> <field index="settings.categoryConjunction"> <value index="vDEF">{additionalMapping.categorySetting}</value> </field> <field index="settings.startingpoint"> <value index="vDEF">{flexForm.pages}</value> </field> <field index="switchableControllerActions"> <value index="vDEF">{additionalMapping.switchableControllerActions}</value> </field> <field index="settings.includeSubCategories"> <value index="vDEF">{flexForm.useSubCategories}</value> </field> <field index="settings.archiveRestriction"> <value index="vDEF">{additionalMapping.archiveSetting}</value> </field> </language> </sheet> <sheet index="additional"> <language index="lDEF"> <field index="settings.detailPid"> <value index="vDEF">{flexForm.PIDitemDisplay}</value> </field> <field index="settings.listPid"> <value index="vDEF">{flexForm.backPid}</value> </field> <field index="settings.limit"> <value index="vDEF">{flexForm.listLimit}</value> </field> <field index="settings.backPid"> <value index="vDEF">{flexForm.backPid}</value> </field> </language> </sheet> <sheet index="template"> <language index="lDEF"> <field index="settings.templateLayout"> <value index="vDEF"></value> </field> </language> </sheet> </data> </T3FlexForms>

6. ViewHelpers

Datei EXT:migration_extend/Classes/ViewHelpers/ConvertNewsCategoryListToNewCategoryListViewHelper.php:

<?php declare(strict_types=1); namespace In2code\MigrationExtend\ViewHelpers; use In2code\Migration\Utility\DatabaseUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; /** * Class ConvertNewsCategoryListToNewCategoryListViewHelper */ class ConvertNewsCategoryListToNewCategoryListViewHelper extends AbstractViewHelper { /** * @return void */ public function initializeArguments() { $this->registerArgument('list', 'string', 'list with tt_news_cat uids', true); } /** * @return string */ public function render(): string { $newList = []; $categoriesOld = GeneralUtility::intExplode(',', $this->arguments['list']); $queryBuilder = DatabaseUtility::getQueryBuilderForTable('sys_category'); foreach ($categoriesOld as $categoryOld) { $newList[] = (int)$queryBuilder ->select('uid') ->from('sys_category') ->where('_migrated_uid=' . $categoryOld . ' and _migrated_table="tt_news_cat"') ->setMaxResults(1) ->orderBy('uid', 'desc') ->execute() ->fetchColumn(0); } return implode(',', $newList); } }

Hinweis: Die vielen Dateien haben wir aus einem alten Projekt kopiert und ich hoffe, dass wir nichts Wichtiges vergessen haben. Es kann sein, das sich der Namespace, etc.. an der einen oder anderen Stelle leicht geändert hat. Auch sind nicht alle Funktionen hier aufgelistet: So gibt es immer wieder eine DatabaseUtility Klasse, die jedoch nur eine QueryBuilder oder Connection Klasse zurückliefert.

Alexander Kellner

Alex Kellner

Alex Kellner ist nicht nur für seine vielen TYPO3-Erweiterungen wie zum Beispiel powermail, femanager oder lux sondern auch für seinen Community-Einsatz bekannt. Er gibt auch gerne Administrations- oder Entwicklungsschulungen oder Worksshops.

Alexander Kellner  |  Geschäftsführung & COO

TYPO3: Finding pages in mixed mode

In TYPO3, Mixed Mode refers to translated pages that contain content only partially related to the corresponding content in the main language. This is indicated in the backend by an error message. But...

Zum Beitrag

Extbase Extensions: Think extensibility with data, site and language

Today, I have a small request for the TYPO3 extension authors out there: Make sure your extensions are extensible. This will also promote the distribution of the corresponding plugins.

Zum Beitrag

SQL: Show all tables sorted by size in descending order

Lately I've been using the SQL command more often to find out which tables in the TYPO3 database are the largest. I've published the snippet once.

Zum Beitrag

TYPO3 12 with CKEditor 5: Styles in a single selection

If you set a link in the RTE in TYPO3, you may have to choose between different link classes, for example to create buttons in the frontend. What's new in TYPO3 12 is that you can select not just one...

Zum Beitrag

Null-Safe Operator in the TYPO3 area

With the introduction of PHP8, problems with undefined arrays or variables in general can arise in many places. Here are a few examples and simple solutions.

Zum Beitrag

Delete the first/last lines of a (SQL) file

There isn't much to say about the following commands. Sometimes it can be useful to delete the first (or last) X lines from a file. And if the file is too large to open with a conventional program, a...

Zum Beitrag