[BUGFIX] Show correct extension name in redirects update
[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 TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * Installs EXT:redirect if sys_domain.redirectTo is filled, and migrates the values from redirectTo
23 * to a proper sys_redirect entry.
24 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
25 */
26 class RedirectsExtensionUpdate extends AbstractDownloadExtensionUpdate
27 {
28 /**
29 * @var \TYPO3\CMS\Install\Updates\Confirmation
30 */
31 protected $confirmation;
32
33 public function __construct()
34 {
35 $this->extension = new ExtensionModel(
36 'redirects',
37 'Redirects',
38 '9.2',
39 'typo3/cms-redirects',
40 'Manage redirects for your TYPO3-based website'
41 );
42
43 $this->confirmation = new Confirmation(
44 'Are you sure?',
45 'You should install the "redirects" extension only if needed. ' . $this->extension->getDescription(),
46 true
47 );
48 }
49
50 /**
51 * Return a confirmation message instance
52 *
53 * @return \TYPO3\CMS\Install\Updates\Confirmation
54 */
55 public function getConfirmation(): Confirmation
56 {
57 return $this->confirmation;
58 }
59
60 /**
61 * Return the identifier for this wizard
62 * This should be the same string as used in the ext_localconf class registration
63 *
64 * @return string
65 */
66 public function getIdentifier(): string
67 {
68 return 'redirects';
69 }
70
71 /**
72 * Return the speaking name of this wizard
73 *
74 * @return string
75 */
76 public function getTitle(): string
77 {
78 return 'Install system extension "redirects" if a sys_domain entry with redirectTo is necessary';
79 }
80
81 /**
82 * Return the description for this wizard
83 *
84 * @return string
85 */
86 public function getDescription(): string
87 {
88 return 'The extension "redirects" includes functionality to handle any kind of redirects. '
89 . 'The functionality superseds sys_domain entries with the only purpose of redirecting to a different domain or entry. '
90 . 'This upgrade wizard installs the redirect extension if necessary and migrates the sys_domain entries to standard redirects.';
91 }
92
93 /**
94 * Is an update necessary?
95 * Is used to determine whether a wizard needs to be run.
96 *
97 * @return bool
98 */
99 public function updateNecessary(): bool
100 {
101 return $this->checkIfWizardIsRequired();
102 }
103
104 /**
105 * Performs the update:
106 * - Install EXT:redirect
107 * - Migrate DB records
108 *
109 * @return bool
110 */
111 public function executeUpdate(): bool
112 {
113 // Install the EXT:redirects extension if not happened yet
114 $installationSuccessful = $this->installExtension($this->extension);
115 if ($installationSuccessful) {
116 // Migrate the database entries
117 $this->migrateRedirectDomainsToSysRedirect();
118 }
119 return $installationSuccessful;
120 }
121
122 /**
123 * Check if the database field "sys_domain.redirectTo" exists and if so, if there are entries in the DB table with the field filled.
124 *
125 * @return bool
126 * @throws \InvalidArgumentException
127 */
128 protected function checkIfWizardIsRequired(): bool
129 {
130 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
131 $connection = $connectionPool->getConnectionByName('Default');
132 $tables = $connection->getSchemaManager()->listTables();
133 $tableExists = false;
134 foreach ($tables as $table) {
135 if (strtolower($table->getName()) === 'sys_domain') {
136 $tableExists = true;
137 }
138 }
139 if (!$tableExists) {
140 return false;
141 }
142 $columns = $connection->getSchemaManager()->listTableColumns('sys_domain');
143 if (isset($columns['redirectto'])) {
144 // table is available, now check if there are entries in it
145 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
146 ->getQueryBuilderForTable('sys_domain');
147 $queryBuilder->getRestrictions()->removeAll();
148 $numberOfEntries = $queryBuilder->count('*')
149 ->from('sys_domain')
150 ->where(
151 $queryBuilder->expr()->neq('redirectTo', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR))
152 )
153 ->execute()
154 ->fetchColumn();
155 return (bool)$numberOfEntries;
156 }
157
158 return false;
159 }
160
161 /**
162 * Move all sys_domain records with a "redirectTo" value filled (also deleted) to "sys_redirect" record
163 */
164 protected function migrateRedirectDomainsToSysRedirect()
165 {
166 $connDomains = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_domain');
167 $connRedirects = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_redirect');
168
169 $queryBuilder = $connDomains->createQueryBuilder();
170 $queryBuilder->getRestrictions()->removeAll();
171 $domainEntries = $queryBuilder->select('*')
172 ->from('sys_domain')
173 ->where(
174 $queryBuilder->expr()->neq('redirectTo', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR))
175 )
176 ->execute()
177 ->fetchAll();
178
179 foreach ($domainEntries as $domainEntry) {
180 $domainName = $domainEntry['domainName'];
181 $target = $domainEntry['redirectTo'];
182 $sourceDetails = parse_url($domainName);
183 $targetDetails = parse_url($target);
184 $redirectRecord = [
185 'deleted' => (int)$domainEntry['deleted'],
186 'disabled' => (int)$domainEntry['hidden'],
187 'createdon' => (int)$domainEntry['crdate'],
188 'createdby' => (int)$domainEntry['cruser_id'],
189 'updatedon' => (int)$domainEntry['tstamp'],
190 'source_host' => $sourceDetails['host'] . ($sourceDetails['port'] ? ':' . $sourceDetails['port'] : ''),
191 'keep_query_parameters' => (int)$domainEntry['prepend_params'],
192 'target_statuscode' => (int)$domainEntry['redirectHttpStatusCode'],
193 'target' => $target
194 ];
195
196 if (isset($targetDetails['scheme']) && $targetDetails['scheme'] === 'https') {
197 $redirectRecord['force_https'] = 1;
198 }
199
200 if (empty($sourceDetails['path']) || $sourceDetails['path'] === '/') {
201 $redirectRecord['source_path'] = '.*';
202 $redirectRecord['is_regexp'] = 1;
203 } else {
204 // Remove the / and add a "/" always before, and at the very end, if path is not empty
205 $sourceDetails['path'] = trim($sourceDetails['path'], '/');
206 $redirectRecord['source_path'] = '/' . ($sourceDetails['path'] ? $sourceDetails['path'] . '/' : '');
207 }
208
209 // Add the redirect record
210 $connRedirects->insert('sys_redirect', $redirectRecord);
211
212 // Remove the sys_domain record (hard)
213 $connDomains->delete('sys_domain', ['uid' => (int)$domainEntry['uid']]);
214 }
215 }
216
217 /**
218 * Returns an array of class names of Prerequisite classes
219 * This way a wizard can define dependencies like "database up-to-date" or
220 * "reference index updated"
221 *
222 * @return string[]
223 */
224 public function getPrerequisites(): array
225 {
226 return [
227 DatabaseUpdatedPrerequisite::class
228 ];
229 }
230 }