Skip to content

Instantly share code, notes, and snippets.

@spoonerWeb
Last active June 23, 2021 09:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spoonerWeb/0313b75be9aebd16f6bd91bdcda6b401 to your computer and use it in GitHub Desktop.
Save spoonerWeb/0313b75be9aebd16f6bd91bdcda6b401 to your computer and use it in GitHub Desktop.
Migration Contao to TYPO3

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/)

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.

Questions and improvements?

Just ping me via comment.

<?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();
}
}
}
}
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\Extension\:
resource: '../Classes/*'
Vendor\Extension\Command\Migration:
tags:
- name: 'console.command'
command: 'vendor:migrate'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment