|
<?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(); |
|
} |
|
} |
|
} |
|
} |