Last active
June 23, 2021 09:51
-
-
Save spoonerWeb/0313b75be9aebd16f6bd91bdcda6b401 to your computer and use it in GitHub Desktop.
Revisions
-
spoonerWeb revised this gist
Jun 23, 2021 . 1 changed file with 6 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -13,6 +13,12 @@ * Add and adapt the attached command * Make "Contao fileadmin" (tl_files) accessible into fileadmin (`fileadmin/tl_files/`) ## Extensions I used these extensions: * `blog` for migrating articles (news) into it ## Additional SQL commands In my case I needed to add or change some things via SQL query. For this I created several SQL files in folder `migrations` for adjusting the records after migration. -
spoonerWeb created this gist
Jun 23, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,534 @@ <?php declare(strict_types = 1); namespace Vendor\Extension\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Page\PageRepository; class Migration extends Command { protected Connection $connectionContao; protected Connection $connectionTypo3; protected array $linkCaches = []; const IMAGE_ORIENTATION_MAPPING = [ '' => 0, 'right' => 25, 'left' => 26, 'below' => 8, 'above' => 0, ]; protected function configure() { parent::configure(); // TODO: Change the autogenerated stub } protected function initialize(InputInterface $input, OutputInterface $output) { parent::initialize($input, $output); $this->connectionTypo3 = GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); $this->connectionContao = GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionByName('Contao'); } protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln('Starting migration...'); $output->writeln('Checking for migration db...'); if ($this->connectionContao) { $output->writeln('Connection to migration db successful!'); } else { $output->writeln('Connection to migration db not possible!'); return self::FAILURE; } $pagesMigrated = $this->migratePages(); $output->writeln('Migrated ' . $pagesMigrated . ' pages.'); $contentMigrated = $this->migrateContent(); foreach ($contentMigrated as $type => $count) { $output->writeln('Migrated ' . $count . ' elements of type "' . $type . '".'); } $backendUserMigrated = $this->migrateBackendUser(); $output->writeln('Migrated ' . $backendUserMigrated . ' backend users.'); $frontendUserMigrated = $this->migrateFrontendUser(); $output->writeln('Migrated ' . $frontendUserMigrated['groups'] . ' frontend groups.'); $output->writeln('Migrated ' . $frontendUserMigrated['users'] . ' frontend users.'); $news = $this->migrateNews(); $output->writeln('Migrated ' . $news . ' news articles.'); $this->finalizeMigration(); return self::SUCCESS; } private function migrateFrontendUser(): array { $this->connectionTypo3->truncate('fe_users'); $usersToMigrate = $this->connectionContao->select(['*'], 'tl_member')->fetchAll(); $this->connectionTypo3->insert( 'pages', [ 'pid' => 1, 'doktype' => PageRepository::DOKTYPE_SYSFOLDER, 'title' => 'Benutzer' ] ); $storagePid = $this->connectionTypo3->lastInsertId('pages'); $this->connectionTypo3->truncate('fe_groups'); $groups = $this->connectionContao->select(['*'], 'tl_member_group')->fetchAll(); foreach ($groups as $group) { $newGroup = [ 'pid' => $storagePid, 'uid' => $group['id'], 'tstamp' => $group['tstamp'], 'crdate' => $group['tstamp'], 'title' => $group['name'], 'felogin_redirectPid' => $group['jumpTo'], ]; $this->connectionTypo3->insert('fe_groups', $newGroup); } foreach ($usersToMigrate as $user) { $zip = ''; if (preg_match('/([\d]{4,5})/', $user['postal'], $matches)) { $zip = $matches[0]; } $newUser = [ 'uid' => $user['id'], 'pid' => $storagePid, 'tstamp' => $user['tstamp'], 'first_name' => $user['firstname'], 'last_name' => $user['lastname'], 'company' => $user['company'], 'address' => $user['street'], 'zip' => $zip, 'city' => $user['city'], 'telephone' => $user['phone'], 'email' => $user['email'], 'www' => $user['website'], 'username' => $user['username'], 'password' => $user['password'], 'crdate' => $user['dateAdded'], 'lastlogin' => $user['lastLogin'], 'title' => $user['jobtitle'], 'date_of_birth' => $user['dateOfBirth'] ?: 0, 'favourite_club' => $user['campaign'], 'referer' => $user['business_connection'], 'allow_email' => $user['allowEmail'] === 'email_member' ? 1 : 0, 'gender' => $user['gender'] === 'female' ? 1 : 0, 'usergroup' => implode(',', unserialize($user['groups'])) ]; if ($user['avatar']) { $fileIdentifier = '/' . $user['avatar']; if (file_exists(Environment::getPublicPath() . '/fileadmin' . $fileIdentifier)) { $image = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier(1, $fileIdentifier); $this->connectionTypo3->insert( 'sys_file_reference', [ 'uid_local' => $image->getUid(), 'uid_foreign' => $user['id'], 'tstamp' => time(), 'crdate' => time(), 'tablenames' => 'fe_users', 'fieldname' => 'image', 'title' => $user['firstname'] . ' ' . $user['lastname'], ] ); $newUser['image'] = 1; } } $this->connectionTypo3->insert('fe_users', $newUser); } return [ 'users' => count($usersToMigrate), 'groups' => count($groups) ]; } private function migrateBackendUser(): int { $this->connectionTypo3->truncate('be_users'); $usersToMigrate = $this->connectionContao->select(['*'], 'tl_user')->fetchAll(); foreach ($usersToMigrate as $user) { $newUser = [ 'uid' => $user['id'], 'tstamp' => $user['tstamp'], 'username' => $user['username'], 'realName' => $user['name'], 'email' => $user['email'], 'password' => $user['password'], 'admin' => $user['admin'] ? 1 : 0, 'lastlogin' => 0, ]; $this->connectionTypo3->insert('be_users', $newUser); } return count($usersToMigrate); } private function migratePages(): int { $this->connectionTypo3->truncate('pages'); $pagesToMigrate = $this->connectionContao ->select( ['*'], 'tl_page' ) ->fetchAll(); foreach ($pagesToMigrate as $pageToMigrate) { $noIndex = str_contains($pageToMigrate['robots'], 'noindex'); $noFollow = str_contains($pageToMigrate['robots'], 'nofollow'); $newPage = [ 'uid' => $pageToMigrate['id'], 'pid' => $pageToMigrate['pid'] ?: 0, 'sorting' => $pageToMigrate['sorting'], 'tstamp' => $pageToMigrate['tstamp'], 'crdate' => $pageToMigrate['tstamp'], 'title' => $pageToMigrate['title'], 'slug' => '/' . $pageToMigrate['alias'], 'is_siteroot' => $pageToMigrate['type'] === 'root' ? 1 : 0, 'fe_group' => $pageToMigrate['protected'] ? -2 : 0, 'no_search' => $pageToMigrate['noSearch'] ?: 0, 'doktype' => 1, 'nav_hide' => $pageToMigrate['hide'] ? 1 : 0, 'hidden' => $pageToMigrate['published'] ? 0 : 1, 'no_index' => $noIndex === true ? 1 : 0, 'no_follow' => $noFollow === true ? 1 : 0, ]; if ($pageToMigrate['type'] === 'forward') { $newPage['doktype'] = PageRepository::DOKTYPE_SHORTCUT; $newPage['shortcut'] = $pageToMigrate['jumpTo']; } if ($pageToMigrate['type'] === 'root') { $newPage['slug'] = '/'; $this->connectionTypo3->truncate('sys_template'); $this->connectionTypo3 ->insert( 'sys_template', [ 'title' => $pageToMigrate['title'], 'pid' => $pageToMigrate['id'], 'root' => 1, 'clear' => 3 ] ); } if (!GeneralUtility::inList('root,regular,forward', $pageToMigrate['type'])) { $newPage['nav_hide'] = 1; } $this->connectionTypo3->insert('pages', $newPage); } return count($pagesToMigrate); } private function migrateContent(): array { $containerConfiguration = [ '2cols3366' => [ 'children' => [ 201, 202 ] ], '3cols' => [ 'children' => [ 301, 302, 303 ] ], ]; $migrated = []; $this->connectionTypo3->truncate('tt_content'); $this->connectionTypo3->truncate('sys_file_reference'); $articles = $this->connectionContao->select(['*'], 'tl_article')->fetchAll(); foreach ($articles as $article) { $contentElements = $this->connectionContao->select( ['*'], 'tl_content', ['pid' => $article['id']], [], ['sorting' => 'ASC'] )->fetchAll(); $column2With3366 = false; $column3 = false; $containerUid = 0; foreach ($contentElements as $key => $contentElement) { $newContentElement = []; if ($key === 0 && str_contains($contentElement['cssID'], 'grid_1 alpha')) { $column2With3366 = true; $containerUid = $contentElement['id'] + 1000; $newContainerElement = [ 'uid' => $containerUid, 'pid' => $article['pid'], 'tstamp' => $contentElement['tstamp'], 'crdate' => $contentElement['tstamp'], 'CType' => '2cols-33-66', 'colPos' => 0, ]; $this->connectionTypo3->insert( 'tt_content', $newContainerElement ); } if ($key % 3 === 0 && str_contains($contentElement['cssID'], 'grid_1 omega')) { $column3 = true; $containerUid = $contentElement['id'] + 1000; $newContainerElement = [ 'uid' => $containerUid, 'pid' => $article['pid'], 'tstamp' => $contentElement['tstamp'], 'crdate' => $contentElement['tstamp'], 'sorting' => $contentElement['sorting'], 'CType' => '3cols', 'colPos' => 0, ]; $this->connectionTypo3->insert( 'tt_content', $newContainerElement ); } switch ($contentElement['type']) { case 'text': case 'image': case 'headline': $header = unserialize($contentElement['headline']); $bodytext = $this->cleanText($contentElement['text']); $bodytext = $this->migrateLinks($bodytext); $colPos = 0; if ($column2With3366) { $colPos = $containerConfiguration['2cols3366']['children'][$key]; } if ($column3) { $column = $key % 3; $colPos = $containerConfiguration['3cols']['children'][$column]; } $newContentElement = [ 'uid' => $contentElement['id'], 'pid' => $article['pid'], 'sorting' => $contentElement['sorting'], 'tstamp' => $contentElement['tstamp'], 'crdate' => $contentElement['tstamp'], 'hidden' => $contentElement['invisible'] ? 1 : 0, 'CType' => 'text', 'colPos' => $colPos, 'bodytext' => $bodytext, 'header' => $header['value'], 'header_layout' => (int)substr($header['unit'], 1, 1), 'tx_container_parent' => $containerUid ]; if ($contentElement['singleSRC'] && !str_contains($contentElement['cssID'], 'grid_1 omega')) { $newContentElement['CType'] = 'textmedia'; $fileIdentifier = '/' . $contentElement['singleSRC']; if (file_exists(Environment::getPublicPath() . '/fileadmin' . $fileIdentifier)) { $image = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier(1, $fileIdentifier); $this->connectionTypo3->insert( 'sys_file_reference', [ 'uid_local' => $image->getUid(), 'uid_foreign' => $contentElement['id'], 'tstamp' => time(), 'crdate' => time(), 'tablenames' => 'tt_content', 'fieldname' => 'assets', 'title' => $contentElement['title'], 'alternative' => $contentElement['alt'], 'description' => $contentElement['caption'], ] ); $newContentElement['assets'] = 1; $newContentElement['imagecols'] = 1; $newContentElement['imageorient'] = self::IMAGE_ORIENTATION_MAPPING[$contentElement['floating']]; } } } if ($newContentElement) { $migrated[$newContentElement['CType']]++; $this->connectionTypo3->insert( 'tt_content', $newContentElement ); } } } return $migrated; } public function migrateNews(): int { $articles = $this->connectionContao->select(['*'], 'tl_news')->fetchAll(); foreach ($articles as $article) { $this->connectionTypo3->insert( 'pages', [ 'title' => $article['headline'], 'pid' => $article['pid'], 'slug' => $article['alias'] ?? '', 'publish_date' => $article['date'], 'crdate' => $article['time'], 'tstamp' => $article['time'], 'doktype' => \T3G\AgencyPack\Blog\Constants::DOKTYPE_BLOG_POST ] ); $pageUid = $this->connectionTypo3->lastInsertId('pages'); $this->connectionTypo3->insert( 'tt_content', [ 'pid' => $pageUid, 'crdate' => $article['time'], 'tstamp' => $article['time'], 'CType' => 'text', 'colPos' => 0, 'bodytext' => $this->cleanText($article['teaser']), 'sorting' => 1, ] ); $newContentElement = [ 'pid' => $pageUid, 'crdate' => $article['time'], 'tstamp' => $article['time'], 'CType' => 'textmedia', 'colPos' => 0, 'bodytext' => $this->cleanText($article['text']), 'sorting' => 2, ]; $this->connectionTypo3->insert( 'tt_content', $newContentElement ); $newContentElement['uid'] = $this->connectionTypo3->lastInsertId('tt_content'); if ($article['singleSRC']) { $fileIdentifier = '/' . $article['singleSRC']; if (file_exists(Environment::getPublicPath() . '/fileadmin' . $fileIdentifier)) { $image = ResourceFactory::getInstance()->getFileObjectByStorageAndIdentifier(1, $fileIdentifier); $this->connectionTypo3->insert( 'sys_file_reference', [ 'uid_local' => $image->getUid(), 'uid_foreign' => $newContentElement['uid'], 'tstamp' => time(), 'crdate' => time(), 'tablenames' => 'tt_content', 'fieldname' => 'assets', 'description' => $article['caption'], ] ); $updateContentElement['assets'] = 1; $updateContentElement['imagecols'] = 1; $updateContentElement['imageorient'] = self::IMAGE_ORIENTATION_MAPPING[$article['floating']]; $this->connectionTypo3->update( 'tt_content', $updateContentElement, ['uid' => $newContentElement['uid']] ); } } } return count($articles); } private function cleanText(?string $text): string { if (is_null($text)) { return ''; } $remove = [ '[nbsp]', '<p>[nbsp]</p>', ]; $text = str_replace($remove, '', $text); $text = preg_replace('/style="[^\"]*"/', '', $text); $text = trim($text); return $text; } private function migrateLinks(string $text): string { $linksFound = preg_match_all('/<a\s+(?:[^>]*?\s+)?href=(["\'])(.*?)\1/', $text, $links); if ($linksFound) { foreach ($links[2] as $originalLink) { if (!isset($this->linkCaches[$originalLink])) { $newLink = $originalLink; preg_match('/\{\{link_url::(\d+)\}\}/', $originalLink, $directTargetPage); if ($directTargetPage) { $newLink = 't3://page?uid=' . $directTargetPage[1]; } else { $path = parse_url($originalLink, PHP_URL_PATH); $slug = str_replace('.html', '', $path); $slug = $slug[0] !== '/' ? '/' . $slug : $slug; $targetPage = $this->connectionTypo3->select(['uid'], 'pages', ['slug' => $slug])->fetch(); if ($targetPage) { $newLink = 't3://page?uid=' . $targetPage['uid']; } } $this->linkCaches[$originalLink] = $newLink; } else { $newLink = $this->linkCaches[$originalLink]; } $text = str_replace($originalLink, $newLink, $text); } } return $text; } private function finalizeMigration(): void { foreach (glob(Environment::getProjectPath() . '/migrations/*.sql') as $file) { if (file_exists($file)) { $stmt = $this->connectionTypo3->prepare(file_get_contents($file)); $stmt->execute(); } } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,13 @@ services: _defaults: autowire: true autoconfigure: true public: false Vendor\Extension\: resource: '../Classes/*' Vendor\Extension\Command\Migration: tags: - name: 'console.command' command: 'vendor:migrate' This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,22 @@ # Migration of a Contao page to TYPO3 ## Prerequisites * DB dump of Contao system * Setup basic TYPO3 v10 * Second database for Contao dump ## Usage * Use a sitepackage extension * Configure Contao DB as second database in LocalConfiguration.php * Add and adapt the attached command * Make "Contao fileadmin" (tl_files) accessible into fileadmin (`fileadmin/tl_files/`) ## Additional SQL commands In my case I needed to add or change some things via SQL query. For this I created several SQL files in folder `migrations` for adjusting the records after migration. ## Questions and improvements? Just ping me via comment.