97c08b0ada87ae9b9d01699a8d7257003d97eb83
[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
23 /**
24 * Installs EXT:redirect if sys_domain.redirectTo is filled, and migrates the values from redirectTo
25 * to a proper sys_redirect entry.
26 */
27 class RedirectsExtensionUpdate extends AbstractDownloadExtensionUpdate implements LoggerAwareInterface
28 {
29 use LoggerAwareTrait;
30
31 /**
32 * @var string
33 */
34 protected $title = 'Install system extension "redirects" if a sys_domain entry with redirectTo is necessary';
35
36 /**
37 * @var array
38 */
39 protected $extensionDetails = [
40 'redirects' => [
41 'title' => 'Redirects',
42 'description' => 'Manage redirects for your TYPO3-based website',
43 'versionString' => '9.2',
44 'composerName' => 'typo3/cms-redirects',
45 ],
46 ];
47
48 /**
49 * Checks if an update is needed
50 *
51 * @param string $description The description for the update
52 * @return bool Whether an update is needed (true) or not (false)
53 */
54 public function checkForUpdate(&$description): bool
55 {
56 $description = 'The extension "redirects" includes functionality to handle any kind of redirects. '
57 . 'The functionality superseds sys_domain entries with the only purpose of redirecting to a different domain or entry. '
58 . 'This upgrade wizard installs the redirect extension if necessary and migrates the sys_domain entries to standard redirects.';
59
60 $updateNeeded = false;
61
62 // Check if table exists and table is not empty, and the wizard has not been run already
63 if ($this->checkIfWizardIsRequired() && !$this->isWizardDone()) {
64 $updateNeeded = true;
65 }
66
67 return $updateNeeded;
68 }
69
70 /**
71 * Performs the update:
72 * - Install EXT:redirect
73 * - Migrate DB records
74 *
75 * @param array $databaseQueries Queries done in this update
76 * @param string $customMessage Custom message
77 * @return bool
78 */
79 public function performUpdate(array &$databaseQueries, &$customMessage): bool
80 {
81 // Install the EXT:redirects extension if not happened yet
82 $installationSuccessful = $this->installExtension('redirects', $customMessage);
83 if ($installationSuccessful) {
84 // Migrate the database entries
85 $this->migrateRedirectDomainsToSysRedirect();
86 $this->markWizardAsDone();
87 }
88 return $installationSuccessful;
89 }
90
91 /**
92 * Check if the database field "sys_domain.redirectTo" exists and if so, if there are entries in the DB table with the field filled.
93 *
94 * @return bool
95 * @throws \InvalidArgumentException
96 */
97 protected function checkIfWizardIsRequired(): bool
98 {
99 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
100 $connection = $connectionPool->getConnectionByName('Default');
101 $columns = $connection->getSchemaManager()->listTableColumns('sys_domain');
102 if (isset($columns['redirectto'])) {
103 // table is available, now check if there are entries in it
104 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
105 ->getQueryBuilderForTable('sys_domain');
106 $queryBuilder->getRestrictions()->removeAll();
107 $numberOfEntries = $queryBuilder->count('*')
108 ->from('sys_domain')
109 ->where(
110 $queryBuilder->expr()->neq('redirectTo', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR))
111 )
112 ->execute()
113 ->fetchColumn();
114 return (bool)$numberOfEntries;
115 }
116
117 return false;
118 }
119
120 /**
121 * Move all sys_domain records with a "redirectTo" value filled (also deleted) to "sys_redirect" record
122 */
123 protected function migrateRedirectDomainsToSysRedirect()
124 {
125 $connDomains = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_domain');
126 $connRedirects = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_redirect');
127
128 $queryBuilder = $connDomains->createQueryBuilder();
129 $queryBuilder->getRestrictions()->removeAll();
130 $domainEntries = $queryBuilder->select('*')
131 ->from('sys_domain')
132 ->where(
133 $queryBuilder->expr()->neq('redirectTo', $queryBuilder->createNamedParameter('', \PDO::PARAM_STR))
134 )
135 ->execute()
136 ->fetchAll();
137
138 foreach ($domainEntries as $domainEntry) {
139 $domainName = $domainEntry['domainName'];
140 $target = $domainEntry['redirectTo'];
141 $sourceDetails = parse_url($domainName);
142 $targetDetails = parse_url($target);
143 $redirectRecord = [
144 'deleted' => (int)$domainEntry['deleted'],
145 'disabled' => (int)$domainEntry['hidden'],
146 'createdon' => (int)$domainEntry['crdate'],
147 'createdby' => (int)$domainEntry['cruser_id'],
148 'updatedon' => (int)$domainEntry['tstamp'],
149 'source_host' => $sourceDetails['host'] . ($sourceDetails['port'] ? ':' . $sourceDetails['port'] : ''),
150 'keep_query_parameters' => (int)$domainEntry['prepend_params'],
151 'target_statuscode' => (int)$domainEntry['redirectHttpStatusCode'],
152 'target' => $target
153 ];
154
155 if (isset($targetDetails['scheme']) && $targetDetails['scheme'] === 'https') {
156 $redirectRecord['force_https'] = 1;
157 }
158
159 if (empty($sourceDetails['path']) || $sourceDetails['path'] === '/') {
160 $redirectRecord['source_path'] = '.*';
161 $redirectRecord['is_regexp'] = 1;
162 } else {
163 // Remove the / and add a "/" always before, and at the very end, if path is not empty
164 $sourceDetails['path'] = trim($sourceDetails['path'], '/');
165 $redirectRecord['source_path'] = '/' . ($sourceDetails['path'] ? $sourceDetails['path'] . '/' : '');
166 }
167
168 // Add the redirect record
169 $connRedirects->insert('sys_redirect', $redirectRecord);
170
171 // Remove the sys_domain record (hard)
172 $connDomains->delete('sys_domain', ['uid' => (int)$domainEntry['uid']]);
173 }
174 }
175 }