[!!!][TASK] Remove sys_domain redirect functionality
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Updates / RedirectsExtensionUpdate.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Install\Updates;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Psr\Log\LoggerAwareInterface;
19 use Psr\Log\LoggerAwareTrait;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Extbase\Object\ObjectManager;
23 use TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException;
24 use TYPO3\CMS\Extensionmanager\Utility\InstallUtility;
25 use TYPO3\CMS\Extensionmanager\Utility\ListUtility;
26
27 /**
28 * Installs EXT:redirect if sys_domain.redirectTo is filled, and migrates the values from redirectTo
29 * to a proper sys_redirect entry.
30 */
31 class RedirectsExtensionUpdate extends AbstractUpdate implements LoggerAwareInterface
32 {
33 use LoggerAwareTrait;
34
35 /**
36 * @var string
37 */
38 protected $title = 'Install system extension "redirects" if a sys_domain entry with redirectTo is necessary';
39
40 /**
41 * @var string
42 */
43 protected $extensionKey = 'redirects';
44
45 /**
46 * Checks if an update is needed
47 *
48 * @param string $description The description for the update
49 * @return bool Whether an update is needed (true) or not (false)
50 */
51 public function checkForUpdate(&$description): bool
52 {
53 $description = 'The extension "redirects" includes functionality to handle any kind of redirects. '
54 . 'The functionality superseds sys_domain entries with the only purpose of redirecting to a different domain or entry. '
55 . 'This upgrade wizard installs the redirect extension if necessary and migrates the sys_domain entries to standard redirects.';
56
57 $updateNeeded = false;
58
59 // Check if table exists and table is not empty, and the wizard has not been run already
60 if ($this->checkIfWizardIsRequired() && !$this->isWizardDone()) {
61 $updateNeeded = true;
62 }
63
64 return $updateNeeded;
65 }
66
67 /**
68 * Performs the update:
69 * - Install EXT:redirect
70 * - Migrate DB records
71 *
72 * @param array $databaseQueries Queries done in this update
73 * @param string $customMessage Custom message
74 * @return bool
75 */
76 public function performUpdate(array &$databaseQueries, &$customMessage): bool
77 {
78 // Install the EXT:redirects extension if not happened yet
79 $installationSuccessful = $this->installExtension($this->extensionKey);
80 if ($installationSuccessful) {
81 // Migrate the database entries
82 $this->migrateRedirectDomainsToSysRedirect();
83 $this->markWizardAsDone();
84 }
85 return $installationSuccessful;
86 }
87
88 /**
89 * Check if the database field "sys_domain.redirectTo" exists and if so, if there are entries in the DB table with the field filled.
90 *
91 * @return bool
92 * @throws \InvalidArgumentException
93 */
94 protected function checkIfWizardIsRequired(): bool
95 {
96 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
97 $connection = $connectionPool->getConnectionByName('Default');
98 $columns = $connection->getSchemaManager()->listTableColumns('sys_domain');
99 if (isset($columns['redirectto'])) {
100 // table is available, now check if there are entries in it
101 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
102 ->getQueryBuilderForTable('sys_domain');
103 $queryBuilder->getRestrictions()->removeAll();
104 $numberOfEntries = $queryBuilder->count('*')
105 ->from('sys_domain')
106 ->where(
107 $queryBuilder->expr()->neq('redirectTo', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR))
108 )
109 ->execute()
110 ->fetchColumn();
111 return (bool)$numberOfEntries;
112 }
113
114 return false;
115 }
116
117 /**
118 * This method can be called to install an extension following all proper processes
119 * (e.g. installing in extList, respecting priority, etc.)
120 *
121 * @param string $extensionKey
122 * @return bool
123 */
124 protected function installExtension(string $extensionKey): bool
125 {
126 $extensionInstalled = false;
127 /** @var $objectManager ObjectManager */
128 $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
129
130 /** @var $extensionListUtility ListUtility */
131 $extensionListUtility = $objectManager->get(ListUtility::class);
132
133 $availableExtensions = $extensionListUtility->getAvailableExtensions();
134 $availableAndInstalledExtensions = $extensionListUtility->getAvailableAndInstalledExtensions($availableExtensions);
135
136 // Extension is not installed yet, install it
137 if (!isset($availableAndInstalledExtensions[$extensionKey]['installed']) || $availableAndInstalledExtensions[$extensionKey]['installed'] !== true) {
138 /** @var $extensionInstallUtility InstallUtility */
139 $extensionInstallUtility = $objectManager->get(InstallUtility::class);
140 try {
141 $extensionInstallUtility->install($extensionKey);
142 $extensionInstalled = true;
143 } catch (ExtensionManagerException $e) {
144 $this->logger->warning($e->getMessage());
145 }
146 } else {
147 $extensionInstalled = true;
148 }
149 return $extensionInstalled;
150 }
151
152 /**
153 * Move all sys_domain records with a "redirectTo" value filled (also deleted) to "sys_redirect" record
154 */
155 protected function migrateRedirectDomainsToSysRedirect()
156 {
157 $connDomains = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_domain');
158 $connRedirects = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_redirect');
159
160 $queryBuilder = $connDomains->createQueryBuilder();
161 $queryBuilder->getRestrictions()->removeAll();
162 $domainEntries = $queryBuilder->select('*')
163 ->from('sys_domain')
164 ->where(
165 $queryBuilder->expr()->neq('redirectTo', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR))
166 )
167 ->execute()
168 ->fetchAll();
169
170 foreach ($domainEntries as $domainEntry) {
171 $domainName = $domainEntry['domainName'];
172 $target = $domainEntry['redirectTo'];
173 $sourceDetails = parse_url($domainName);
174 $targetDetails = parse_url($target);
175 $redirectRecord = [
176 'deleted' => (int)$domainEntry['deleted'],
177 'disabled' => (int)$domainEntry['hidden'],
178 'createdon' => (int)$domainEntry['crdate'],
179 'createdby' => (int)$domainEntry['cruser_id'],
180 'updatedon' => (int)$domainEntry['tstamp'],
181 'source_host' => $sourceDetails['host'] . ($sourceDetails['port'] ? ':' . $sourceDetails['port'] : ''),
182 'keep_query_parameters' => (int)$domainEntry['prepend_params'],
183 'target_statuscode' => (int)$domainEntry['redirectHttpStatusCode'],
184 'target' => $target
185 ];
186
187 if (isset($targetDetails['scheme']) && $targetDetails['scheme'] === 'https') {
188 $redirectRecord['force_https'] = 1;
189 }
190
191 if (empty($sourceDetails['path']) || $sourceDetails['path'] === '/') {
192 $redirectRecord['source_path'] = '.*';
193 $redirectRecord['is_regexp'] = 1;
194 } else {
195 // Remove the / and add a "/" always before, and at the very end, if path is not empty
196 $sourceDetails['path'] = trim($sourceDetails['path'], '/');
197 $redirectRecord['source_path'] = '/' . ($sourceDetails['path'] ? $sourceDetails['path'] . '/' : '');
198 }
199
200 // Add the redirect record
201 $connRedirects->insert('sys_redirect', $redirectRecord);
202
203 // Remove the sys_domain record (hard)
204 $connDomains->delete('sys_domain', ['uid' => (int)$domainEntry['uid']]);
205 }
206 }
207 }