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