<?php namespace SGalinski\SgNews\Command; /*************************************************************** * Copyright notice * * (c) sgalinski Internet Services (https://www.sgalinski.de) * * All rights reserved * * This script is part of the TYPO3 project. The TYPO3 project is * free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * * This script is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ use SGalinski\SgNews\Domain\Model\News; use SGalinski\SgNews\Domain\Repository\FileReferenceRepository; use SGalinski\SgNews\Domain\Repository\NewsRepository; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use TYPO3\CMS\Core\Core\Bootstrap; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction; use TYPO3\CMS\Core\DataHandling\DataHandler; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use Symfony\Component\Console\Command\Command; use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException; use TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException; use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager; /** * Command controller, that migrates data from tx_news to sg_news */ class MigrateNewsCommandController extends Command { /** * this array maps new pages to their original entry in the tx_news table * * @var array $newsPagesMap */ protected $newsPagesMap = []; /** * @var array $languageMap */ protected $languageMap = []; /** * @var array $languageMap */ protected $categoryMap = []; /** * Configure the command */ public function configure() { $this->setDescription('Migrate data from tx_news to sg_news') ->addArgument('copyPageId', InputArgument::REQUIRED, 'The page id of the template that should be copied') ->addArgument('categoryPid', InputArgument::REQUIRED, 'The page id of the category page') ->addArgument('year', InputArgument::OPTIONAL, 'Only news from that year will be migrated', 2015) ->addArgument('languageMapAsJson', InputArgument::OPTIONAL, 'A json, mapping language ids (old => new). this is needed if the sys_language_uids have changed', '{"3":1,"1":0,"2":2,"0":3}') ->addArgument('categoryMapAsJson', InputArgument::OPTIONAL, 'A json, mapping sys_category ids (old => new).', '{"2":17,"3":16,"4":15,"5":14,"6":14,"7":15,"8":16,"9":17}') ->addArgument('pId', InputArgument::OPTIONAL, 'Only news from that pid will be migrated', 52); } /** * Execute the command * * @param InputInterface $input * @param OutputInterface $output * @return int|void * @throws IllegalObjectTypeException * @throws UnknownObjectException * @throws \JsonException * @throws \TYPO3\CMS\Extbase\Object\Exception */ public function execute(InputInterface $input, OutputInterface $output) { $objectManager = GeneralUtility::makeInstance(ObjectManager::class); $persistenceManager = $objectManager->get(PersistenceManager::class); $fileReferenceRepository = $objectManager->get(FileReferenceRepository::class); $newsRepository = $objectManager->get(NewsRepository::class); $copyPageId = $input->getArgument('copyPageId'); $categoryPid = $input->getArgument('categoryPid'); $year = $input->getArgument('year'); $languageMapAsJson = $input->getArgument('languageMapAsJson'); $categoryMapAsJson = $input->getArgument('categoryMapAsJson'); $pId = $input->getArgument('pId'); $this->languageMap = json_decode($languageMapAsJson, TRUE, 512, JSON_THROW_ON_ERROR); $this->categoryMap = json_decode($categoryMapAsJson, TRUE, 512, JSON_THROW_ON_ERROR); $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_news_domain_model_news'); $queryBuilder->getRestrictions()->removeByType(StartTimeRestriction::class); $rows = $queryBuilder->select('*') ->from('tx_news_domain_model_news') ->where( $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pId, Connection::PARAM_INT)) ) ->execute()->fetchAll(); $localDataHandler = GeneralUtility::makeInstance(DataHandler::class); Bootstrap::initializeBackendAuthentication(); $localCommandMap = [ 'pages' => [ $copyPageId => [ 'copy' => (int) $categoryPid ] ] ]; foreach ($rows as $row) { // ignore the entry if its not within the given year if ((int) date('Y', $row['datetime']) !== $year) { continue; } // if no l10n_parent exists, create a copy of the page if ((int) $row['l10n_parent'] === 0) { $localDataHandler->start([], $localCommandMap); $localDataHandler->bypassAccessCheckForRecords = TRUE; $localDataHandler->checkModifyAccessList('pages'); $localDataHandler->process_cmdmap(); // get the id of the new object $newPageId = $localDataHandler->copyMappingArray['pages'][$copyPageId]; // make entry in news map, so we can fetch it later $this->newsPagesMap[$row['uid']] = $newPageId; /** @var News $newsPage */ $newsPage = $newsRepository->findByUidIgnoreEnableFields($newPageId); if ($newsPage !== NULL) { $title = date('Y-m-d', $row['datetime']) . ' - ' . $row['title']; $newsPage->setTitle($title); $newsPage->setSubtitle($row['title']); $date = new \DateTime('@' . $row['datetime']); $newsPage->setLastUpdated($date); $this->setMatchingTag($row); /** @var File $image */ $file = $this->getMatchingFile($row); if ($file instanceof File) { $teaserImage1 = $fileReferenceRepository->addFileReferenceFromFile( $file, $this->newsPagesMap[$row['uid']], $this->newsPagesMap[$row['uid']], 'pages', 'tx_sgnews_teaser1_image' ); if ($teaserImage1) { $newsPage->addTeaser1Image($teaserImage1); $teaserImage2 = $fileReferenceRepository->addFileReferenceFromFile( $file, $this->newsPagesMap[$row['uid']], $this->newsPagesMap[$row['uid']], 'pages', 'tx_sgnews_teaser2_image' ); $newsPage->addTeaser2Image($teaserImage2); } } $newsRepository->update($newsPage); $persistenceManager->persistAll(); // update content element from the new page $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content'); $queryBuilder->getRestrictions()->removeAll(); $queryBuilder->update('tt_content') ->where( $queryBuilder->expr()->andX( $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($newPageId, Connection::PARAM_INT)), $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($this->languageMap[(int) $row['sys_language_uid']], \PDO::PARAM_INT)) ) ) ->set('bodytext', $row['bodytext'], TRUE) ->execute(); } } else { // this row is a translation and should simply update the content accordingly $this->updateTranslation($row); } } } /** * Get the image file matching the news * * @param array $row * @return null|FileInterface * @throws \Exception */ private function getMatchingFile(array $row) { // match old page id with old file reference id $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference_news_migration'); $queryBuilder->getRestrictions()->removeAll(); $fileReferenceResult = $queryBuilder->select('uid', 'uid_local') ->from('sys_file_reference_news_migration') ->where( $queryBuilder->expr()->andX( $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_news_domain_model_news')), $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('fal_media')), $queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($row['uid'], Connection::PARAM_INT)) ) ) ->execute()->fetch(); if (!$fileReferenceResult) { return NULL; } $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_news_migration'); $queryBuilder->getRestrictions()->removeAll(); $fileResult = $queryBuilder->select('identifier') ->from('sys_file_news_migration') ->where( $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($fileReferenceResult['uid_local'], Connection::PARAM_INT)) ) ->execute()->fetch(); if (!$fileResult) { return NULL; } $oldIdentifier = $fileResult['identifier']; $resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class); $storage = $resourceFactory->getStorageObject(1); if (!$storage->hasFile($oldIdentifier)) { return NULL; } return $storage->getFile($oldIdentifier); } /** * Get the tag / category, matching the news * * @param array $row */ private function setMatchingTag(array $row) { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_category_record_mm_news_migration'); $queryBuilder->getRestrictions()->removeAll(); $mmRows = $queryBuilder->select('uid_local', 'sorting_foreign') ->from('sys_category_record_mm_news_migration') ->where( $queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($row['uid'], Connection::PARAM_INT)) ) ->execute()->fetchAll(); foreach ($mmRows as $mmRow) { $values = [ 'uid_local' => $this->categoryMap[(int) $mmRow['uid_local']], 'uid_foreign' => $this->newsPagesMap[(int) $row['uid']], 'tablenames' => 'pages', 'fieldname' => 'tx_sgnews_tags', 'sorting_foreign' => (int) $mmRow['sorting_foreign'] ]; $queryBuilder->insert('sys_category_record_mm') ->values($values); } } /** * Updates content element of a news translationuid * * @param array $row */ private function updateTranslation(array $row) { // get entry in news map $parentId = $this->newsPagesMap[$row['l10n_parent']]; $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content'); $queryBuilder->getRestrictions()->removeAll(); $originalContentElement = $queryBuilder->select('l18n_parent') ->from('tt_content') ->where( $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($parentId, Connection::PARAM_INT)) ) ->execute()->fetch(); $queryBuilder->update('tt_content'); // if its the new default, there is no l18n_parent if ((int) $this->languageMap[(int) $row['sys_language_uid'] === 0]) { $queryBuilder->where( $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($originalContentElement[0], Connection::PARAM_INT)) ); } else { $queryBuilder->where( $queryBuilder->expr()->eq('l18n_parent', $queryBuilder->createNamedParameter($originalContentElement[0], Connection::PARAM_INT)) ); } // look up the correct language id, if they have changed if (isset($this->languageMap[(int) $row['sys_language_uid']])) { $queryBuilder->andWhere( $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($this->languageMap[(int) $row['sys_language_uid']], Connection::PARAM_INT)) ); } else { $queryBuilder->andWhere( $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($row['sys_language_uid'], Connection::PARAM_INT)) ); } $queryBuilder->set('bodytext', $row['bodytext'], TRUE) ->execute(); // possibly the default language needs to be overwritten and the old default translation needs to be preserved if (isset($this->languageMap[(int) $row['sys_language_uid']]) && $this->languageMap[(int) $row['sys_language_uid']] === 0) { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); $queryBuilder->getRestrictions()->removeAll(); $result = $queryBuilder->select('title', 'subtitle') ->from('pages') ->where( $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($parentId, Connection::PARAM_INT)) ) ->execute()->fetch(); if ($result) { $queryBuilder->where( $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($this->languageMap[0], Connection::PARAM_INT)) ); $queryBuilder->update('pages') ->andWhere( $queryBuilder->expr()->eq('l10n_parent', $queryBuilder->createNamedParameter($this->languageMap[0], Connection::PARAM_INT)) ); $queryBuilder->set('title', $result['title'], TRUE) ->set('subtitle', $result['subtitle'], TRUE) ->set('navtitle', '', TRUE) ->execute(); } $newTitle = date('Y-m-d', $row['datetime']) . ' - ' . $row['title']; $queryBuilder->update('pages') ->where( $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($parentId, Connection::PARAM_INT)) ) ->set('title', $newTitle, TRUE) ->set('subtitle', $row['title'], TRUE) ->set('lastUpdated', $row['datetime'], TRUE) ->set('navtitle', '', TRUE) ->execute(); } else { $queryBuilder->update('pages') ->where( $queryBuilder->expr()->eq('l10n_parent', $queryBuilder->createNamedParameter($parentId, Connection::PARAM_INT)) ); // finally translate the page title if necessary if (isset($this->languageMap[(int) $row['sys_language_uid']]) && $this->languageMap[(int) $row['sys_language_uid']] > 0) { $queryBuilder->andWhere( $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($this->languageMap[(int) $row['sys_language_uid']], Connection::PARAM_INT)) ); } else { $queryBuilder->andWhere( $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($row['sys_language_uid'], Connection::PARAM_INT)) ); } $queryBuilder->set('title', date('Y-m-d', $row['datetime']) . ' - ' . $row['title'], TRUE) ->execute(); } } }