From 4e21a5d36c09e75dc9150b591cd559dba7beb2a6 Mon Sep 17 00:00:00 2001
From: Kevin Ditscheid <kevin.ditscheid@sgalinski.de>
Date: Wed, 15 Jul 2020 15:23:40 +0200
Subject: [PATCH] [TASK] Remove legacy code and switch to symfony command

---
 .../Command/MigrateNewsCommandController.php  | 214 ++++++------------
 Classes/Controller/AbstractController.php     |   2 +-
 Classes/Controller/BackendController.php      |  18 +-
 Classes/Hooks/PageLayoutController.php        |  16 --
 Classes/Service/LicensingService.php          |   4 +-
 Classes/Updates/MigrateSchedulerTasks.php     | 170 ++++++++++++++
 Classes/Utility/BackendNewsUtility.php        |  25 +-
 Classes/Utility/ExtensionUtility.php          |  55 -----
 .../ViewHelpers/Backend/ControlViewHelper.php |  19 +-
 .../Backend/TranslationLinksViewHelper.php    |  27 +--
 Configuration/Commands.php                    |   6 +
 Configuration/TCA/Overrides/pages.php         | 108 ++++-----
 .../TCA/Overrides/pages_language_overlay.php  | 200 ----------------
 .../TCA/tx_sgnews_domain_model_author.php     |  11 +-
 ext_localconf.php                             |  12 +-
 ext_tables.php                                |   4 -
 16 files changed, 309 insertions(+), 582 deletions(-)
 create mode 100644 Classes/Updates/MigrateSchedulerTasks.php
 delete mode 100644 Classes/Utility/ExtensionUtility.php
 create mode 100644 Configuration/Commands.php
 delete mode 100644 Configuration/TCA/Overrides/pages_language_overlay.php

diff --git a/Classes/Command/MigrateNewsCommandController.php b/Classes/Command/MigrateNewsCommandController.php
index 4aec4af..a2883ae 100644
--- a/Classes/Command/MigrateNewsCommandController.php
+++ b/Classes/Command/MigrateNewsCommandController.php
@@ -29,65 +29,28 @@ namespace SGalinski\SgNews\Command;
 use SGalinski\SgNews\Domain\Model\News;
 use SGalinski\SgNews\Domain\Repository\FileReferenceRepository;
 use SGalinski\SgNews\Domain\Repository\NewsRepository;
-use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
+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 TYPO3\CMS\Core\Utility\VersionNumberUtility;
-use TYPO3\CMS\Extbase\Mvc\Controller\CommandController;
+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;
-use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
  * Command controller, that migrates data from tx_news to sg_news
  */
-class MigrateNewsCommandController extends CommandController {
-	/**
-	 * @var bool
-	 */
-	protected $requestAdminPermissions = TRUE;
-
-	/**
-	 * @var PersistenceManager
-	 */
-	protected $persistenceManager;
-
-	/**
-	 * @param PersistenceManager $persistenceManager
-	 */
-	public function injectPersistenceManager(PersistenceManager $persistenceManager) {
-		$this->persistenceManager = $persistenceManager;
-	}
-
-	/**
-	 * @var NewsRepository
-	 */
-	protected $newsRepository;
-
-	/**
-	 * @param NewsRepository $newsRepository
-	 */
-	public function injectNewsRepository(NewsRepository $newsRepository) {
-		$this->newsRepository = $newsRepository;
-	}
-
-	/**
-	 * @var FileReferenceRepository
-	 */
-	private $fileReferenceRepository;
-
-	/**
-	 * @param FileReferenceRepository $fileReferenceRepository
-	 */
-	public function injectFileReferenceRepository(FileReferenceRepository $fileReferenceRepository) {
-		$this->fileReferenceRepository = $fileReferenceRepository;
-	}
-
+class MigrateNewsCommandController extends Command {
 	/**
 	 * this array maps new pages to their original entry in the tx_news table
 	 *
@@ -95,13 +58,6 @@ class MigrateNewsCommandController extends CommandController {
 	 */
 	protected $newsPagesMap = [];
 
-	/**
-	 * this array contains all pages that should be migrated to sg_news
-	 *
-	 * @var array $pagesToMigrate
-	 */
-	protected $pagesToMigrate = [];
-
 	/**
 	 * @var array $languageMap
 	 */
@@ -113,43 +69,55 @@ class MigrateNewsCommandController extends CommandController {
 	protected $categoryMap = [];
 
 	/**
-	 * @param string $copyPageId the page id of the template that should be copied
-	 * @param int $categoryPid the page id of the category page
-	 * @param int $year only news from that year will be migrated
-	 * @param string $languageMapAsJson a json, mapping language ids (old => new). this is needed if the sys_language_uids have changed
-	 * @param string $categoryMapAsJson a json, mapping sys_category ids (old => new). t
-	 * @param int $pId only news from that pid will be migrated
+	 * 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 \Exception
+	 * @throws \JsonException
+	 * @throws \TYPO3\CMS\Extbase\Object\Exception
 	 */
-	public function runMigrateNewsCommand(
-		$copyPageId, $categoryPid, $year = 2015,
-		$languageMapAsJson = '{"3":1,"1":0,"2":2,"0":3}',
-		$categoryMapAsJson = '{"2":17,"3":16,"4":15,"5":14,"6":14,"7":15,"8":16,"9":17}',
-		$pId = 52
-	) {
-		// fix repair translation bug where tsfe is missing from command controller, can be removed when v1.5 is released
-		if (!$GLOBALS['TSFE']) {
-			$GLOBALS['TSFE'] = $this->objectManager->get(
-				TypoScriptFrontendController::class, $GLOBALS['TYPO3_CONF_VARS'], 0, 0
-			);
-		}
-
-		$this->languageMap = json_decode($languageMapAsJson, TRUE);
-		$this->categoryMap = json_decode($categoryMapAsJson, TRUE);
+	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, \PDO::PARAM_INT))
+				$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pId, Connection::PARAM_INT))
 			)
 			->execute()->fetchAll();
 		$localDataHandler = GeneralUtility::makeInstance(DataHandler::class);
-		$beUser = $this->simulateBackendUser();
+		Bootstrap::initializeBackendAuthentication();
 		$localCommandMap = [
 			'pages' => [
 				$copyPageId => [
@@ -166,7 +134,7 @@ class MigrateNewsCommandController extends CommandController {
 
 			// if no l10n_parent exists, create a copy of the page
 			if ((int) $row['l10n_parent'] === 0) {
-				$localDataHandler->start([], $localCommandMap, $beUser);
+				$localDataHandler->start([], $localCommandMap);
 				$localDataHandler->bypassAccessCheckForRecords = TRUE;
 				$localDataHandler->checkModifyAccessList('pages');
 				$localDataHandler->process_cmdmap();
@@ -178,7 +146,7 @@ class MigrateNewsCommandController extends CommandController {
 				$this->newsPagesMap[$row['uid']] = $newPageId;
 
 				/** @var News $newsPage */
-				$newsPage = $this->newsRepository->findByUidIgnoreEnableFields($newPageId);
+				$newsPage = $newsRepository->findByUidIgnoreEnableFields($newPageId);
 
 				if ($newsPage !== NULL) {
 					$title = date('Y-m-d', $row['datetime']) . ' - ' . $row['title'];
@@ -192,7 +160,7 @@ class MigrateNewsCommandController extends CommandController {
 					/** @var File $image */
 					$file = $this->getMatchingFile($row);
 					if ($file instanceof File) {
-						$teaserImage1 = $this->fileReferenceRepository->addFileReferenceFromFile(
+						$teaserImage1 = $fileReferenceRepository->addFileReferenceFromFile(
 							$file, $this->newsPagesMap[$row['uid']],
 							$this->newsPagesMap[$row['uid']], 'pages', 'tx_sgnews_teaser1_image'
 						);
@@ -200,7 +168,7 @@ class MigrateNewsCommandController extends CommandController {
 						if ($teaserImage1) {
 							$newsPage->addTeaser1Image($teaserImage1);
 
-							$teaserImage2 = $this->fileReferenceRepository->addFileReferenceFromFile(
+							$teaserImage2 = $fileReferenceRepository->addFileReferenceFromFile(
 								$file, $this->newsPagesMap[$row['uid']],
 								$this->newsPagesMap[$row['uid']], 'pages', 'tx_sgnews_teaser2_image'
 							);
@@ -208,8 +176,8 @@ class MigrateNewsCommandController extends CommandController {
 						}
 					}
 
-					$this->newsRepository->update($newsPage);
-					$this->persistenceManager->persistAll();
+					$newsRepository->update($newsPage);
+					$persistenceManager->persistAll();
 
 					// update content element from the new page
 					$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
@@ -217,7 +185,7 @@ class MigrateNewsCommandController extends CommandController {
 					$queryBuilder->update('tt_content')
 						->where(
 							$queryBuilder->expr()->andX(
-								$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($newPageId, \PDO::PARAM_INT)),
+								$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))
 							)
 						)
@@ -247,7 +215,7 @@ class MigrateNewsCommandController extends CommandController {
 				$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'], \PDO::PARAM_INT))
+					$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($row['uid'], Connection::PARAM_INT))
 				)
 			)
 			->execute()->fetch();
@@ -260,7 +228,7 @@ class MigrateNewsCommandController extends CommandController {
 		$fileResult = $queryBuilder->select('identifier')
 			->from('sys_file_news_migration')
 			->where(
-				$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($fileReferenceResult['uid_local'], \PDO::PARAM_INT))
+				$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($fileReferenceResult['uid_local'], Connection::PARAM_INT))
 			)
 			->execute()->fetch();
 		if (!$fileResult) {
@@ -268,7 +236,7 @@ class MigrateNewsCommandController extends CommandController {
 		}
 
 		$oldIdentifier = $fileResult['identifier'];
-		$resourceFactory = \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance();
+		$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
 		$storage = $resourceFactory->getStorageObject(1);
 		if (!$storage->hasFile($oldIdentifier)) {
 			return NULL;
@@ -288,7 +256,7 @@ class MigrateNewsCommandController extends CommandController {
 		$mmRows = $queryBuilder->select('uid_local', 'sorting_foreign')
 			->from('sys_category_record_mm_news_migration')
 			->where(
-				$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($row['uid'], \PDO::PARAM_INT))
+				$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($row['uid'], Connection::PARAM_INT))
 			)
 			->execute()->fetchAll();
 		foreach ($mmRows as $mmRow) {
@@ -312,13 +280,12 @@ class MigrateNewsCommandController extends CommandController {
 	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, \PDO::PARAM_INT))
+				$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($parentId, Connection::PARAM_INT))
 			)
 			->execute()->fetch();
 
@@ -326,22 +293,22 @@ class MigrateNewsCommandController extends CommandController {
 		// 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], \PDO::PARAM_INT))
+				$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($originalContentElement[0], Connection::PARAM_INT))
 			);
 		} else {
 			$queryBuilder->where(
-				$queryBuilder->expr()->eq('l18n_parent', $queryBuilder->createNamedParameter($originalContentElement[0], \PDO::PARAM_INT))
+				$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']], \PDO::PARAM_INT))
+				$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'], \PDO::PARAM_INT))
+				$queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($row['sys_language_uid'], Connection::PARAM_INT))
 			);
 		}
 		$queryBuilder->set('bodytext', $row['bodytext'], TRUE)
@@ -349,30 +316,22 @@ class MigrateNewsCommandController extends CommandController {
 
 		// 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, \PDO::PARAM_INT))
+					$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], \PDO::PARAM_INT))
+					$queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter($this->languageMap[0], Connection::PARAM_INT))
 				);
-				if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-					$queryBuilder->update('pages_language_overlay')
-						->andWhere(
-							$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($parentId, \PDO::PARAM_INT))
-						);
-				} else {
-					$queryBuilder->update('pages')
-						->andWhere(
-							$queryBuilder->expr()->eq('l10n_parent', $queryBuilder->createNamedParameter($this->languageMap[0], \PDO::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)
@@ -382,7 +341,7 @@ class MigrateNewsCommandController extends CommandController {
 			$newTitle = date('Y-m-d', $row['datetime']) . ' - ' . $row['title'];
 			$queryBuilder->update('pages')
 				->where(
-					$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($parentId, \PDO::PARAM_INT))
+					$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($parentId, Connection::PARAM_INT))
 				)
 				->set('title', $newTitle, TRUE)
 				->set('subtitle', $row['title'], TRUE)
@@ -390,48 +349,23 @@ class MigrateNewsCommandController extends CommandController {
 				->set('navtitle', '', TRUE)
 				->execute();
 		} else {
-			if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-				$queryBuilder->update('pages_language_overlay')
-					->where(
-						$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($parentId, \PDO::PARAM_INT))
-					);
-			} else {
-				$queryBuilder->update('pages')
-					->where(
-						$queryBuilder->expr()->eq('l10n_parent', $queryBuilder->createNamedParameter($parentId, \PDO::PARAM_INT))
-					);
-			}
+			$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']], \PDO::PARAM_INT))
+					$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'], \PDO::PARAM_INT))
+					$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();
 		}
 	}
-
-	/**
-	 * Simulate Backend User for DataHandler
-	 *
-	 * @return FrontendBackendUserAuthentication
-	 */
-	private function simulateBackendUser() {
-		/** @var \TYPO3\CMS\Backend\FrontendBackendUserAuthentication $BE_USER */
-		$BE_USER = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class);
-		$BE_USER->setBeUserByName('admin');
-		if ($BE_USER->user['uid']) {
-			$BE_USER->fetchGroupData();
-		}
-		$BE_USER->uc_default['copyLevels'] = '9999';
-		$BE_USER->uc = $BE_USER->uc_default;
-		$GLOBALS['PAGES_TYPES'][254]['allowedTables'] = '*';
-		return $BE_USER;
-	}
 }
diff --git a/Classes/Controller/AbstractController.php b/Classes/Controller/AbstractController.php
index 95dbdf5..526287e 100644
--- a/Classes/Controller/AbstractController.php
+++ b/Classes/Controller/AbstractController.php
@@ -60,7 +60,7 @@ abstract class AbstractController extends ActionController {
 	 */
 	public function initializeAction() {
 		$extensionKey = $this->request->getControllerExtensionKey();
-		$this->extensionConfiguration = ExtensionUtility::getExtensionConfiguration($extensionKey);
+		$this->extensionConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extensionKey] ?? [];
 
 		parent::initializeAction();
 	}
diff --git a/Classes/Controller/BackendController.php b/Classes/Controller/BackendController.php
index 35a4d04..059f328 100644
--- a/Classes/Controller/BackendController.php
+++ b/Classes/Controller/BackendController.php
@@ -35,9 +35,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
-use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\VersionNumberUtility;
 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
@@ -154,14 +152,6 @@ class BackendController extends ActionController {
 				$currentLanguageInfo = $languageOptions[$this->language] ?? NULL;
 			}
 
-			$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
-			$pageRenderer->addJsInlineCode('typo3_version', 'TYPO3.version='
-				. VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version())
-				. ';');
-			if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-				$pageRenderer->loadExtJS();
-			}
-
 			$this->docHeaderComponent->setMetaInformation($this->pageInfo);
 			$this->makeButtons();
 			$this->makeLanguageMenu();
@@ -170,7 +160,6 @@ class BackendController extends ActionController {
 			$this->view->assign('language', $this->language);
 			$this->view->assign('languageInfo', $currentLanguageInfo);
 			$this->view->assign('docHeader', $this->docHeaderComponent->docHeaderContent());
-			$this->view->assign('typo3Version', VersionNumberUtility::convertVersionNumberToInteger(TYPO3_version));
 		}
 	}
 
@@ -227,12 +216,7 @@ class BackendController extends ActionController {
 
 		/** @var IconFactory $iconFactory */
 		$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-
-		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-			$locallangPath = 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:';
-		} else {
-			$locallangPath = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:';
-		}
+		$locallangPath = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:';
 
 		// Refresh
 		$refreshButton = $buttonBar->makeLinkButton()
diff --git a/Classes/Hooks/PageLayoutController.php b/Classes/Hooks/PageLayoutController.php
index 0daa0ec..cb59462 100644
--- a/Classes/Hooks/PageLayoutController.php
+++ b/Classes/Hooks/PageLayoutController.php
@@ -32,7 +32,6 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\VersionNumberUtility;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
 
 /**
@@ -80,22 +79,7 @@ class PageLayoutController {
 		$icon = '<span style="vertical-align: middle; display:inline-block;">' . $icon . '</span>';
 		$buttonLabel = LocalizationUtility::translate('backend.button.goToNewsModule', 'SgNews');
 		$buttonLabel = '<span style="vertical-align: middle;">' . $buttonLabel . '</span>';
-
 		$path = '';
-		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-			$pageRenderer->addJsInlineCode('typo3_version', 'TYPO3.version='
-				. VersionNumberUtility::convertVersionNumberToInteger(VersionNumberUtility::getCurrentTypo3Version())
-				. ';');
-			$rootline = BackendUtility::BEgetRootLine($categoryRow['uid'], '', TRUE);
-			ksort($rootline);
-			$path = '/root';
-			foreach ($rootline as $page) {
-				$path .= '/p' . dechex($page['uid']);
-			}
-
-			$path = ', \'' . $path . '\'';
-		}
-
 		$onclick = 'TYPO3.SgNewsModule.sgNewsGoToNewsModule(' . $categoryRow['uid'] . $path . '); return false;';
 		$wrap = ' <div class="btn-group" role="group">%s</div>';
 		$link = '<a href="#" onclick="' . $onclick . '" class="btn btn-primary">%s</a>';
diff --git a/Classes/Service/LicensingService.php b/Classes/Service/LicensingService.php
index 73a0f34..a29290a 100644
--- a/Classes/Service/LicensingService.php
+++ b/Classes/Service/LicensingService.php
@@ -56,7 +56,7 @@ class LicensingService {
 	public static function checkKey(): bool {
 		if (static::$isLicenseKeyValid === NULL) {
 			static::$isLicenseKeyValid = FALSE;
-			$configuration = ExtensionUtility::getExtensionConfiguration(self::EXTENSION_KEY);
+			$configuration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][self::EXTENSION_KEY] ?? [];
 			if (isset($configuration['key']) && $key = trim($configuration['key'])) {
 				static::$isLicenseKeyValid = (bool) preg_match('/^([A-Z\d]{6}-?){4}$/', $key);
 			}
@@ -73,7 +73,7 @@ class LicensingService {
 	 */
 	public static function ping($returnUrl = FALSE): string {
 		try {
-			$configuration = ExtensionUtility::getExtensionConfiguration(self::EXTENSION_KEY);
+			$configuration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][self::EXTENSION_KEY] ?? [];
 			$key = '';
 			if (isset($configuration['key'])) {
 				$key = trim($configuration['key']);
diff --git a/Classes/Updates/MigrateSchedulerTasks.php b/Classes/Updates/MigrateSchedulerTasks.php
new file mode 100644
index 0000000..b7af223
--- /dev/null
+++ b/Classes/Updates/MigrateSchedulerTasks.php
@@ -0,0 +1,170 @@
+<?php
+/***************************************************************
+ *  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!
+ ***************************************************************/
+
+namespace SGalinski\SgNews\Updates;
+
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\Updates\DatabaseUpdatedPrerequisite;
+use TYPO3\CMS\Install\Updates\UpgradeWizardInterface;
+use TYPO3\CMS\Scheduler\Scheduler;
+use TYPO3\CMS\Scheduler\Task\ExecuteSchedulableCommandTask;
+
+/**
+ * Class MigrateSchedulerTasks
+ *
+ * @package SGalinski\SgNews\Updates
+ */
+class MigrateSchedulerTasks implements UpgradeWizardInterface {
+	/**
+	 * Identifier of the upgrade
+	 */
+	const IDENTIFIER = 'tx_sgnews_migrateschedulertasks';
+
+	/**
+	 * @inheritDoc
+	 */
+	public function getIdentifier(): string {
+		return self::IDENTIFIER;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function getTitle(): string {
+		return 'Migrate sg_news scheduler tasks to symfony commands API';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function getDescription(): string {
+		return 'This upgrade migrates all sg_news scheduler tasks, created with the old CommandController API to the new Symfony Commands API';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function executeUpdate(): bool {
+		$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(
+			'tx_scheduler_task'
+		);
+		$tasks = $queryBuilder->select('serialized_task_object')
+			->from('tx_scheduler_task')
+			->where(
+				$queryBuilder->expr()->eq('disable', 0)
+			)->execute()->fetchAll();
+		foreach ($tasks as $_task) {
+			$task = unserialize($_task['serialized_task_object'], [\__PHP_Incomplete_Class::class]);
+			$taskVars = $this->cleanArrayKeys((array)$task);
+			$identifier = $taskVars['*commandIdentifier'];
+			if (
+				$identifier === 'sg_news:migratenews:runmigratenews'
+			) {
+				$this->replaceTask($taskVars);
+			}
+		}
+		return TRUE;
+	}
+
+	/**
+	 * Replace the given extbase task implementation with the new Symfony Commands API one
+	 *
+	 * @param array $task
+	 */
+	protected function replaceTask(array $task) {
+		$commandTask = GeneralUtility::makeInstance(ExecuteSchedulableCommandTask::class);
+		switch ($task['*commandIdentifier']) {
+			case 'sg_news:migratenews:runmigratenews':
+				$commandTask->setCommandIdentifier('sg_news:generateHttpCodeSites');
+				break;
+		}
+
+		if (is_array($task['*arguments'])) {
+			$commandTask->setArguments($task['*arguments']);
+		}
+
+		if (is_array($task['*defaults'])) {
+			foreach ($task['*defaults'] as $key => $default) {
+				$commandTask->addDefaultValue($key, $default);
+			}
+		}
+
+		$commandTask->setTaskGroup($task['*taskGroup']);
+		$commandTask->setExecution($task['*execution']);
+		$commandTask->setExecutionTime($task['*executionTime']);
+		$commandTask->setRunOnNextCronJob($task['*runOnNextCronJob']);
+		$commandTask->setTaskUid($task['*taskUid']);
+		GeneralUtility::makeInstance(Scheduler::class)->saveTask($commandTask);
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function updateNecessary(): bool {
+		$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(
+			'tx_scheduler_task'
+		);
+		$tasks = $queryBuilder->select('serialized_task_object')
+			->from('tx_scheduler_task')
+			->where(
+				$queryBuilder->expr()->eq('disable', 0)
+			)->execute()->fetchAll();
+		foreach ($tasks as $_task) {
+			$task = unserialize($_task['serialized_task_object'], [\__PHP_Incomplete_Class::class]);
+			$taskVars = $this->cleanArrayKeys((array)$task);
+			$identifier = $taskVars['*commandIdentifier'];
+			if (
+				$identifier === 'sg_news:migratenews:runmigratenews'
+			) {
+				return TRUE;
+			}
+		}
+		return FALSE;
+	}
+
+	/**
+	 * Cleans array keys from their hidden \0
+	 *
+	 * @param array $array
+	 * @return array
+	 */
+	protected function cleanArrayKeys(array $array) {
+		$newArray = [];
+		foreach($array as $key => $value) {
+			$newArray[str_replace("\0", '', $key)] = $value;
+		}
+		return $newArray;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function getPrerequisites(): array {
+		return [
+			DatabaseUpdatedPrerequisite::class
+		];
+	}
+}
diff --git a/Classes/Utility/BackendNewsUtility.php b/Classes/Utility/BackendNewsUtility.php
index 97ae987..4cb2354 100644
--- a/Classes/Utility/BackendNewsUtility.php
+++ b/Classes/Utility/BackendNewsUtility.php
@@ -42,7 +42,6 @@ use TYPO3\CMS\Core\Imaging\IconProvider\BitmapIconProvider;
 use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
 use TYPO3\CMS\Core\Imaging\IconRegistry;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\VersionNumberUtility;
 use TYPO3\CMS\Extbase\Object\ObjectManager;
 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
@@ -91,9 +90,6 @@ class BackendNewsUtility {
 	public static function getAlternativePageOptions(): array {
 		$options = [];
 		$andWhere = ' AND sys_language_uid IN (0,-1)';
-		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-			$andWhere = '';
-		}
 		/** @var array $rootOptionRows */
 		$rootOptionRows = self::getRecordsByField(
 			'pages', 'is_siteroot', 1, $andWhere, '', 'sorting'
@@ -363,21 +359,12 @@ class BackendNewsUtility {
 			->groupBy('p.uid')
 			->orderBy('p.sorting');
 		if ($languageUid) {
-			if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-				$queryBuilder->leftJoin('p', 'pages_language_overlay', 'translation',
-					$queryBuilder->expr()->andX(
-						$queryBuilder->expr()->eq('translation.pid', 'p.uid'),
-						$queryBuilder->expr()->eq('translation.sys_language_uid', $queryBuilder->createNamedParameter($languageUid, \PDO::PARAM_INT))
-					)
-				);
-			} else {
-				$queryBuilder->leftJoin('p', 'pages', 'translation',
-					$queryBuilder->expr()->andX(
-						$queryBuilder->expr()->eq('translation.l10n_parent', 'p.uid'),
-						$queryBuilder->expr()->eq('translation.sys_language_uid', $queryBuilder->createNamedParameter($languageUid, \PDO::PARAM_INT))
-					)
-				);
-			}
+			$queryBuilder->leftJoin('p', 'pages', 'translation',
+				$queryBuilder->expr()->andX(
+					$queryBuilder->expr()->eq('translation.l10n_parent', 'p.uid'),
+					$queryBuilder->expr()->eq('translation.sys_language_uid', $queryBuilder->createNamedParameter($languageUid, \PDO::PARAM_INT))
+				)
+			);
 		}
 
 		if (isset($filters['tags']) && is_array($filters['tags']) && count($filters['tags'])) {
diff --git a/Classes/Utility/ExtensionUtility.php b/Classes/Utility/ExtensionUtility.php
deleted file mode 100644
index 65cf527..0000000
--- a/Classes/Utility/ExtensionUtility.php
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-
-namespace SGalinski\SgNews\Utility;
-
-/**
- *
- * 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 TYPO3\CMS\Core\Utility\VersionNumberUtility;
-
-/**
- * Class ExtensionUtility
- *
- * @package SGalinski\SgMail\Utility
- * @author Kevin Ditscheid <kevin.ditscheid@sgalinski.de>
- */
-class ExtensionUtility {
-	/**
-	 * Get the extension configuration
-	 *
-	 * @param string $extKey
-	 * @return array
-	 */
-	public static function getExtensionConfiguration(string $extKey = 'sg_news'): array {
-		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-			$extConf = \unserialize(
-				$GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][$extKey], ['allowed_classes' => FALSE]
-			);
-			return is_array($extConf) ? $extConf : [];
-		}
-
-		return $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS'][$extKey] ?? [];
-	}
-}
diff --git a/Classes/ViewHelpers/Backend/ControlViewHelper.php b/Classes/ViewHelpers/Backend/ControlViewHelper.php
index f1aedda..b8a5c8a 100644
--- a/Classes/ViewHelpers/Backend/ControlViewHelper.php
+++ b/Classes/ViewHelpers/Backend/ControlViewHelper.php
@@ -34,7 +34,6 @@ use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\VersionNumberUtility;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
 use TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList;
 
@@ -77,12 +76,7 @@ class ControlViewHelper extends AbstractViewHelper {
 		$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
 		$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/AjaxDataHandler');
 		$pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf');
-		if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-			$languageService = GeneralUtility::makeInstance(\TYPO3\CMS\Lang\LanguageService::class);
-		} else {
-			$languageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
-		}
-
+		$languageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
 		$languageService->includeLLFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf');
 
 		$databaseRecordList->currentTable = $sortingData;
@@ -103,17 +97,6 @@ class ControlViewHelper extends AbstractViewHelper {
 			$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
 			$buttonLabel = LocalizationUtility::translate('backend.button.editPageContent', 'SgNews');
 			$path = '';
-			if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-				$rootline = BackendUtility::BEgetRootLine($row['uid'], '', TRUE);
-				ksort($rootline);
-				$path = '/root';
-				foreach ($rootline as $page) {
-					$path .= '/p' . dechex($page['uid']);
-				}
-
-				$path = ', \'' . $path . '\'';
-			}
-
 			$onclick = 'return TYPO3.SgNewsModule.sgNewsGoToPageModule(' . $row['uid'] . $path . ');';
 			$icon = $iconFactory->getIcon('actions-document-open-white', Icon::SIZE_SMALL)->render();
 			$wrap = ' <div class="btn-group" role="group">%s</div>';
diff --git a/Classes/ViewHelpers/Backend/TranslationLinksViewHelper.php b/Classes/ViewHelpers/Backend/TranslationLinksViewHelper.php
index 249f482..ff0645a 100644
--- a/Classes/ViewHelpers/Backend/TranslationLinksViewHelper.php
+++ b/Classes/ViewHelpers/Backend/TranslationLinksViewHelper.php
@@ -35,7 +35,6 @@ use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Type\Icon\IconState;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\VersionNumberUtility;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
 
 /**
@@ -94,13 +93,8 @@ class TranslationLinksViewHelper extends AbstractViewHelper {
 			$editLabel = LocalizationUtility::translate('backend.action.edit', 'SgNews');
 			$newLabel = LocalizationUtility::translate('backend.action.new', 'SgNews');
 			$translationParameters = '&cmd[' . $table . '][' . $row['uid'] . '][localize]=%s';
-			if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-				$returnUrl = BackendUtility::getModuleUrl('web_SgNewsNews', ['id' => GeneralUtility::_GP('id')]);
-			} else {
-				$returnUrl = GeneralUtility::makeInstance(UriBuilder::class)
-					->buildUriFromRoute('web_SgNewsNews', ['id' => GeneralUtility::_GP('id')]);
-			}
-
+			$returnUrl = GeneralUtility::makeInstance(UriBuilder::class)
+				->buildUriFromRoute('web_SgNewsNews', ['id' => GeneralUtility::_GP('id')]);
 			foreach ($languages as $languageUid => $language) {
 				$translatedUid = 0;
 				if ((int) $languageUid <= 0) {
@@ -115,26 +109,15 @@ class TranslationLinksViewHelper extends AbstractViewHelper {
 				}
 
 				if ($translatedUid) {
-					if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-						$link = BackendUtility::getModuleUrl('record_edit', [
+					$link = GeneralUtility::makeInstance(UriBuilder::class)
+						->buildUriFromRoute('record_edit', [
 							'edit' => [
 								$translationTable => [
 									$translatedUid => 'edit'
 								]
 							],
-							'returnUrl' => $returnUrl
+							'returnUrl' => (string) $returnUrl
 						]);
-					} else {
-						$link = GeneralUtility::makeInstance(UriBuilder::class)
-							->buildUriFromRoute('record_edit', [
-								'edit' => [
-									$translationTable => [
-										$translatedUid => 'edit'
-									]
-								],
-								'returnUrl' => (string) $returnUrl
-							]);
-					}
 
 					$out .= ' <a href="' . $link . '" title="'. $language['title'] . ' [' . $editLabel . ']" >'
 						. $iconFactory->getIcon($language['flag'], Icon::SIZE_SMALL)->render()
diff --git a/Configuration/Commands.php b/Configuration/Commands.php
new file mode 100644
index 0000000..813f2ee
--- /dev/null
+++ b/Configuration/Commands.php
@@ -0,0 +1,6 @@
+<?php
+return [
+	'sg_news:migrateNews' => [
+		'class' => \SGalinski\SgNews\Command\MigrateNewsCommandController::class
+	]
+];
diff --git a/Configuration/TCA/Overrides/pages.php b/Configuration/TCA/Overrides/pages.php
index 0122d1d..441a445 100644
--- a/Configuration/TCA/Overrides/pages.php
+++ b/Configuration/TCA/Overrides/pages.php
@@ -225,11 +225,6 @@ call_user_func(
 						'items' => [
 							['', ''],
 						],
-						'wizards' => [
-							'suggest' => [
-								'type' => 'suggest',
-							],
-						],
 						'fieldControl' => [
 							'addRecord' => [
 								'disabled' => FALSE,
@@ -250,12 +245,7 @@ call_user_func(
 						'maxitems' => 1,
 						'items' => [
 							['', ''],
-						],
-						'wizards' => [
-							'suggest' => [
-								'type' => 'suggest',
-							],
-						],
+						]
 					],
 				],
 				'tx_sgnews_related_news' => [
@@ -268,12 +258,7 @@ call_user_func(
 						'allowed' => $table,
 						'size' => 5,
 						'minitems' => 0,
-						'maxitems' => 99,
-						'wizards' => [
-							'suggest' => [
-								'type' => 'suggest',
-							],
-						],
+						'maxitems' => 99
 					],
 				],
 				'tx_sgnews_highlighted' => [
@@ -398,60 +383,45 @@ call_user_func(
 			'canNotCollapse' => 1,
 		];
 
-		// Removal of the realurl fields, if the extension isn't installed.
-		if (!\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('realurl')) {
-			$GLOBALS['TCA'][$table]['palettes']['titleDescriptionAndHighlightFlag'] = str_replace(
-				'--linebreak--, tx_realurl_pathsegment, tx_realurl_exclude,', '',
-				$GLOBALS['TCA'][$table]['palettes']['titleDescriptionAndHighlightFlag']
-			);
-			$GLOBALS['TCA'][$table]['types'][117] = str_replace(
-				'tx_realurl_pathsegment, tx_realurl_exclude,', '',
-				$GLOBALS['TCA'][$table]['types'][117]
-			);
-		}
-
-		if (\version_compare(\TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '>')) {
-			// if TYPO3 9.5, exclude a lot of fields from the news doktype in localizations
-			foreach (
-				$GLOBALS['TCA'][$table]['columns'] as $languageExcludeField => $_
+		foreach (
+			$GLOBALS['TCA'][$table]['columns'] as $languageExcludeField => $_
+		) {
+			if (
+			!\in_array(
+				$languageExcludeField, [
+				'doktype',
+				'title',
+				'subtitle',
+				'description',
+				'slug',
+				'tx_projectbase_path_segment',
+				'tx_projectbase_excludefromsluggeneration',
+				'tx_sgnews_location',
+				'tx_sgnews_teaser1_image',
+				'tx_sgnews_teaser2_image',
+				'tx_sgnews_tags',
+				'abstract',
+				'tx_projectbase_seo_titletag',
+				'tx_projectbase_seo_canonicaltag',
+				'hidden',
+				'sys_language_uid',
+				'tx_languagevisibility_visibility',
+				'lastUpdated',
+				'tx_sgnews_date_end',
+				'tx_sgnews_highlighted',
+				'tx_sgnews_never_highlighted',
+			]
+			)
 			) {
-				if (
-				!\in_array(
-					$languageExcludeField, [
-					'doktype',
-					'title',
-					'subtitle',
-					'description',
-					'slug',
-					'tx_projectbase_path_segment',
-					'tx_projectbase_excludefromsluggeneration',
-					'tx_sgnews_location',
-					'tx_sgnews_teaser1_image',
-					'tx_sgnews_teaser2_image',
-					'tx_sgnews_tags',
-					'abstract',
-					'tx_projectbase_seo_titletag',
-					'tx_projectbase_seo_canonicaltag',
-					'hidden',
-					'sys_language_uid',
-					'tx_languagevisibility_visibility',
-					'lastUpdated',
-					'tx_sgnews_date_end',
-					'tx_sgnews_highlighted',
-					'tx_sgnews_never_highlighted',
-				]
-				)
-				) {
-					$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE]['columnsOverrides'][$languageExcludeField]['l10n_mode'] = 'exclude';
-				}
+				$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE]['columnsOverrides'][$languageExcludeField]['l10n_mode'] = 'exclude';
 			}
-
-			$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE]['columnsOverrides']['title']['label'] =
-				'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_db.xlf:' . $table . '.title';
-			$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE]['columnsOverrides']['subtitle']['label'] =
-				'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_db.xlf:' . $table . '.subtitle';
-			$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE]['columnsOverrides']['slug']['label'] =
-				'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_db.xlf:' . $table . '.slug';
 		}
+
+		$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE]['columnsOverrides']['title']['label'] =
+			'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_db.xlf:' . $table . '.title';
+		$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE]['columnsOverrides']['subtitle']['label'] =
+			'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_db.xlf:' . $table . '.subtitle';
+		$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE]['columnsOverrides']['slug']['label'] =
+			'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_db.xlf:' . $table . '.slug';
 	}, 'sg_news', 'pages'
 );
diff --git a/Configuration/TCA/Overrides/pages_language_overlay.php b/Configuration/TCA/Overrides/pages_language_overlay.php
deleted file mode 100644
index 0c0151b..0000000
--- a/Configuration/TCA/Overrides/pages_language_overlay.php
+++ /dev/null
@@ -1,200 +0,0 @@
-<?php
-/**
- *
- * 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!
- */
-
-if (\version_compare(\TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-	call_user_func(
-		function ($extKey, $table) {
-			$pagesTable = 'pages';
-			$localLangDbPath = 'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_db.xlf:';
-			$localLangBackendPath = 'LLL:EXT:' . $extKey . '/Resources/Private/Language/locallang_backend.xlf:';
-
-			$GLOBALS['TYPO3_CONF_VARS']['FE']['pageOverlayFields'] .= ',tx_sgnews_teaser1_image,tx_sgnews_teaser2_image';
-
-			foreach (
-				[
-					\SGalinski\SgNews\Utility\BackendNewsUtility::CATEGORY_DOKTYPE => [
-						'icon' => 'EXT:sg_news/Resources/Public/Images/Category.png',
-						'locallangIndex' => 'pageType.category'
-					],
-					\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE => [
-						'icon' => 'EXT:sg_news/Resources/Public/Images/News.png',
-						'locallangIndex' => 'pageType.news'
-					]
-				] as $doktype => $configuration) {
-				// also add the new doktype to the page language overlays type selector (so that translations can inherit the same type)
-				$GLOBALS['TCA'][$table]['columns']['doktype']['config']['items'][] = [
-					$localLangBackendPath . $configuration['locallangIndex'],
-					$doktype,
-					$configuration['icon']
-				];
-			}
-
-			$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE] = [
-				'showitem' => '--palette--;LLL:EXT:cms/locallang_tca.xlf:' . $pagesTable . '.palettes.standard;standard,
-					subtitle;' . $localLangDbPath . $pagesTable . '.subtitle.inPalette,
-					description, tx_realurl_pathsegment, author, tx_sgnews_location,
-				--div--;' . $localLangDbPath . $pagesTable . '.tabs.images,
-					tx_sgnews_teaser2_image, tx_sgnews_teaser1_image,
-				--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:' . $pagesTable . '.tabs.metadata,
-					--palette--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:' . $pagesTable . '.palettes.abstract;abstract,
-					tx_projectbase_seo_titletag,tx_projectbase_seo_canonicaltag,
-				--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
-					sys_language_uid,
-				--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
-					--palette--;LLL:EXT:cms/locallang_tca.xlf:' . $pagesTable . '.palettes.visibility;hiddenonly,
-					--palette--;LLL:EXT:cms/locallang_tca.xlf:' . $pagesTable . '.palettes.access;access'
-			];
-
-			$GLOBALS['TCA'][$table]['types'][\SGalinski\SgNews\Utility\BackendNewsUtility::CATEGORY_DOKTYPE] = [
-				'showitem' => '--palette--;LLL:EXT:cms/locallang_tca.xlf:' . $pagesTable . '.palettes.standard;standard,
-					title, tx_realurl_pathsegment,
-				--div--;' . $localLangDbPath . $table . '.tabs.images,
-					tx_sgnews_teaser2_image, tx_sgnews_teaser1_image,
-				--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:' . $pagesTable . '.tabs.metadata,
-					--palette--;LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:' . $pagesTable . '.palettes.abstract;abstract,
-					tx_projectbase_seo_titletag,tx_projectbase_seo_canonicaltag, description,
-				--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
-					sys_language_uid,
-				--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
-					--palette--;LLL:EXT:cms/locallang_tca.xlf:' . $pagesTable . '.palettes.visibility;hiddenonly,
-					--palette--;LLL:EXT:cms/locallang_tca.xlf:' . $pagesTable . '.palettes.access;access'
-			];
-
-			\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns(
-				$table, [
-					'tx_sgnews_teaser1_image' => [
-						'exclude' => TRUE,
-						'label' => $localLangDbPath . $pagesTable . '.tx_sgnews_teaser1_image',
-						'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
-							'tx_sgnews_teaser1_image',
-							[
-								'maxitems' => 9999,
-								'foreign_types' => [
-									'0' => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									]
-								],
-								'appearance' => [
-									'showPossibleLocalizationRecords' => TRUE,
-									'showRemovedLocalizationRecords' => TRUE,
-									'showSynchronizationLink' => TRUE,
-									'showAllLocalizationLink' => TRUE,
-								],
-							],
-							$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
-						),
-					],
-					'tx_sgnews_teaser2_image' => [
-						'exclude' => TRUE,
-						'label' => $localLangDbPath . $pagesTable . '.tx_sgnews_teaser2_image',
-						'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig(
-							'tx_sgnews_teaser2_image',
-							[
-								'maxitems' => 9999,
-								'foreign_types' => [
-									'0' => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_TEXT => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_IMAGE => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_AUDIO => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_VIDEO => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									],
-									\TYPO3\CMS\Core\Resource\File::FILETYPE_APPLICATION => [
-										'showitem' => '
-										--palette--;LLL:EXT:lang/locallang_tca.xlf:sys_file_reference.imageoverlayPalette;imageoverlayPalette,
-										--palette--;;filePalette'
-									]
-								],
-								'appearance' => [
-									'showPossibleLocalizationRecords' => TRUE,
-									'showRemovedLocalizationRecords' => TRUE,
-									'showSynchronizationLink' => TRUE,
-									'showAllLocalizationLink' => TRUE,
-								],
-							],
-							$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
-						),
-					],
-					'tx_sgnews_location' => [
-						'exclude' => TRUE,
-						'label' => $localLangDbPath . $pagesTable . '.tx_sgnews_location',
-						'config' => [
-							'type' => 'input',
-							'size' => 20,
-							'eval' => 'trim'
-						]
-					]
-				]
-			);
-
-		}, 'sg_news', 'pages_language_overlay'
-	);
-}
diff --git a/Configuration/TCA/tx_sgnews_domain_model_author.php b/Configuration/TCA/tx_sgnews_domain_model_author.php
index 4d5cddf..3ea41bc 100644
--- a/Configuration/TCA/tx_sgnews_domain_model_author.php
+++ b/Configuration/TCA/tx_sgnews_domain_model_author.php
@@ -46,7 +46,7 @@ $configuration = [
 		'iconfile' => 'EXT:sg_news/Resources/Public/Icons/module-sgnews.svg'
 	],
 	'interface' => [
-		'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, crdate, name, email, 
+		'showRecordFieldList' => 'sys_language_uid, l10n_parent, l10n_diffsource, hidden, crdate, name, email,
 			description, website, image, path_segment',
 	],
 	'types' => [
@@ -73,7 +73,7 @@ $configuration = [
 			'label' => 'LLL:EXT:sg_news/Resources/Private/Language/locallang_db.xlf:tx_sgnews_domain_model_author.crdate',
 			'config' => [
 				'type' => 'input',
-				'max' => '20',
+				'max' => 20,
 				'eval' => 'datetime',
 				'default' => $GLOBALS['EXEC_TIME'],
 			]
@@ -198,11 +198,4 @@ $configuration = [
 	]
 ];
 
-
-// The slug field isn't available for TYPO3 8 and below.
-if (TYPO3\CMS\Core\Utility\VersionNumberUtility::convertVersionNumberToInteger(TYPO3_version) < 9000000) {
-	unset($configuration['columns']['path_segment']);
-	$configuration['types'] = str_replace('path_segment', '', $configuration['types']);
-}
-
 return $configuration;
diff --git a/ext_localconf.php b/ext_localconf.php
index eb2b01c..25a665d 100644
--- a/ext_localconf.php
+++ b/ext_localconf.php
@@ -104,12 +104,6 @@ call_user_func(
 		$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects']['TYPO3\CMS\Core\Page\PageRenderer'] =
 			['className' => 'SGalinski\SgNews\Xclass\PageRenderer'];
 
-		// add realurl configuration
-		if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('realurl')) {
-			$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/realurl/class.tx_realurl_autoconfgen.php']['extensionConfiguration']['sgnews'] =
-				\SGalinski\SgNews\Hooks\RealUrlAutoConfiguration::class . '->addNewsConfig';
-		}
-
 		/** @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher */
 		$signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
 			\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class
@@ -128,10 +122,6 @@ call_user_func(
 		$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'][] =
 			\SGalinski\SgNews\Hooks\PageLayoutController::class . '->addNewsModuleLink';
 
-		// register command controllers
-		$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['extbase']['commandControllers'][] =
-			'SGalinski\SgNews\Command\MigrateNewsCommandController';
-
 		// add the new doktype to the list of types available from the new page menu at the top of the page tree
 		\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addUserTSConfig(
 			'options.pageTree.doktypesToShowInNewPageDragArea := addToList(' . \SGalinski\SgNews\Utility\BackendNewsUtility::NEWS_DOKTYPE . ')' . PHP_EOL
@@ -147,5 +137,7 @@ call_user_func(
 		// Add upgrade wizards
 		$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][\SGalinski\SgNews\Updates\UpdateAuthors::IDENTIFIER] =
 			\SGalinski\SgNews\Updates\UpdateAuthors::class;
+		$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][\SGalinski\SgNews\Updates\MigrateSchedulerTasks::IDENTIFIER] =
+			\SGalinski\SgNews\Updates\MigrateSchedulerTasks::class;
 	}, 'sg_news'
 );
diff --git a/ext_tables.php b/ext_tables.php
index 0d7d12f..4834b61 100644
--- a/ext_tables.php
+++ b/ext_tables.php
@@ -32,10 +32,6 @@ call_user_func(
 
 		if (TYPO3_MODE === 'BE') {
 			$navigationComponentId = 'TYPO3/CMS/Backend/PageTree/PageTreeElement';
-			if (version_compare(\TYPO3\CMS\Core\Utility\VersionNumberUtility::getCurrentTypo3Version(), '9.0.0', '<')) {
-				$navigationComponentId = 'typo3-pagetree';
-			}
-
 			\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerModule(
 				'SGalinski.' . $extKey,
 				'web',
-- 
GitLab