[TASK] Doctrine: Migrate RTE-related Upgrade Wizards
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Classes / Hook / Install / DeprecatedRteProperties.php
1 <?php
2 namespace TYPO3\CMS\Rtehtmlarea\Hook\Install;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Doctrine\DBAL\DBALException;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Core\Utility\StringUtility;
21 use TYPO3\CMS\Install\Updates\AbstractUpdate;
22
23 /**
24 * Contains the update class for the replacement of deprecated RTE properties in Page TSconfig.
25 * Used by the upgrade wizard in the install tool.
26 */
27 class DeprecatedRteProperties extends AbstractUpdate
28 {
29 /**
30 * @var string
31 */
32 protected $title = 'Deprecated RTE properties in Page TSconfig';
33
34 /**
35 * Properties that may be replaced automatically in Page TSconfig (except inludes from external files)
36 * Syntax: 'oldProperty' => 'newProperty'
37 *
38 * @var array
39 */
40 protected $replacementRteProperties = array(
41 'disableRightClick' => 'contextMenu.disable',
42 'disableContextMenu' => 'contextMenu.disable',
43 'hidePStyleItems' => 'buttons.formatblock.removeItems',
44 'hideFontFaces' => 'buttons.fontstyle.removeItems',
45 'fontFace' => 'buttons.fontstyle.addItems',
46 'hideFontSizes' => 'buttons.fontsize.removeItems',
47 'classesCharacter' => 'buttons.textstyle.tags.span.allowedClasses',
48 'classesParagraph' => 'buttons.blockstyle.tags.div.allowedClasses',
49 'classesTable' => 'buttons.blockstyle.tags.table.allowedClasses',
50 'classesTD' => 'buttons.blockstyle.tags.td.allowedClasses',
51 'classesImage' => 'buttons.image.properties.class.allowedClasses',
52 'classesLinks' => 'buttons.link.properties.class.allowedClasses',
53 'blindImageOptions' => 'buttons.image.options.removeItems',
54 'blindLinkOptions' => 'buttons.link.options.removeItems',
55 'defaultLinkTarget' => 'buttons.link.properties.target.default'
56 );
57
58 /**
59 * Properties that may be replaced automatically in Page TSconfig (except inludes from external files)
60 * Syntax: 'oldProperty' => [ 'newProperty', 'newProperty' ]
61 *
62 * @var array
63 */
64 protected $doubleReplacementRteProperties = array(
65 'disableTYPO3Browsers' => array(
66 'buttons.image.TYPO3Browser.disabled',
67 'buttons.link.TYPO3Browser.disabled'
68 ),
69 'showTagFreeClasses' => array(
70 'buttons.blockstyle.showTagFreeClasses',
71 'buttons.textstyle.showTagFreeClasses'
72 ),
73 'disablePCexamples' => array(
74 'buttons.blockstyle.disableStyleOnOptionLabel',
75 'buttons.textstyle.disableStyleOnOptionLabel'
76 )
77 );
78
79 /**
80 * Properties that may not be replaced automatically in Page TSconfig
81 * Syntax: 'oldProperty' => 'newProperty'
82 *
83 * @var array
84 */
85 protected $useInsteadRteProperties = array(
86 'fontSize' => 'buttons.fontsize.addItems',
87 'RTE.default.classesAnchor' => 'RTE.default.buttons.link.properties.class.allowedClasses',
88 'RTE.default.classesAnchor.default.[link-type]' => 'RTE.default.buttons.link.[link-type].properties.class.default',
89 'mainStyleOverride' => 'contentCSS',
90 'mainStyleOverride_add.[key]' => 'contentCSS',
91 'mainStyle_font' => 'contentCSS',
92 'mainStyle_size' => 'contentCSS',
93 'mainStyle_color' => 'contentCSS',
94 'mainStyle_bgcolor' => 'contentCSS',
95 'inlineStyle.[any-keystring]' => 'contentCSS',
96 'ignoreMainStyleOverride' => 'n.a.'
97 );
98
99 /**
100 * Function which checks if update is needed. Called in the beginning of an update process.
101 *
102 * @param string $description Pointer to description for the update
103 * @return bool TRUE if update is needs to be performed, FALSE otherwise.
104 */
105 public function checkForUpdate(&$description)
106 {
107 $result = false;
108
109 $pages = $this->getPagesWithDeprecatedRteProperties($customMessages);
110 $pagesCount = count($pages);
111 $deprecatedProperties = '';
112 $deprecatedRteProperties = array_merge($this->replacementRteProperties, $this->useInsteadRteProperties);
113 foreach ($deprecatedRteProperties as $deprecatedProperty => $replacementProperty) {
114 $deprecatedProperties .= '<tr><td>' . $deprecatedProperty . '</td><td>' . $replacementProperty . '</td></tr>' . LF;
115 }
116 foreach ($this->doubleReplacementRteProperties as $deprecatedProperty => $replacementProperties) {
117 $deprecatedProperties .= '<tr><td>' . $deprecatedProperty . '</td><td>' . implode(' and ', $replacementProperties) . '</td></tr>' . LF;
118 }
119 $description = '<p>The following Page TSconfig RTE properties are deprecated since TYPO3 4.6 and have been removed in TYPO3 6.0.</p>' . LF . '<table><thead><tr><th>Deprecated property</th><th>Use instead</th></tr></thead>' . LF . '<tbody>' . $deprecatedProperties . '</tboby></table>' . LF . '<p>You are currently using some of these properties on <strong>' . strval($pagesCount) . '&nbsp;pages</strong> (including deleted and hidden pages).</p>' . LF;
120 if ($pagesCount) {
121 $pagesUids = array();
122 foreach ($pages as $page) {
123 $pagesUids[] = $page['uid'];
124 }
125 $description .= '<p>Pages id\'s: ' . implode(', ', $pagesUids) . '</p>';
126 }
127 $replacementProperties = '';
128 foreach ($this->useInsteadRteProperties as $deprecatedProperty => $replacementProperty) {
129 $replacementProperties .= '<tr><td>' . $deprecatedProperty . '</td><td>' . $replacementProperty . '</td></tr>' . LF;
130 }
131 if ($pagesCount) {
132 $updateablePages = $this->findUpdateablePagesWithDeprecatedRteProperties($pages);
133 if (!empty($updateablePages)) {
134 $replacementProperties = '';
135 foreach ($this->replacementRteProperties as $deprecatedProperty => $replacementProperty) {
136 $replacementProperties .= '<tr><td>' . $deprecatedProperty . '</td><td>' . $replacementProperty . '</td></tr>' . LF;
137 }
138 $description .= '<p>This wizard will perform automatic replacement of the following properties on <strong>' . strval(count($updateablePages)) . '&nbsp;pages</strong> (including deleted and hidden):</p>' . LF . '<table><thead><tr><th>Deprecated property</th><th>Will be replaced by</th></tr></thead><tbody>' . $replacementProperties . '</tboby></table>' . LF . '<p>The Page TSconfig column of the remaining pages will need to be updated manually.</p>' . LF;
139 } else {
140 $replacementProperties = '';
141 foreach ($this->useInsteadRteProperties as $deprecatedProperty => $_) {
142 $replacementProperties .= '<tr><td>' . $deprecatedProperty . '</td></tr>' . LF;
143 }
144 foreach ($this->doubleReplacementRteProperties as $deprecatedProperty => $_) {
145 $replacementProperties .= '<tr><td>' . $deprecatedProperty . '</td></tr>' . LF;
146 }
147 $description .= '<p>This wizard cannot update the following properties, some of which are present on those pages:</p>' . LF . '<table><thead><tr><th>Deprecated property</th></tr></thead><tbody>' . $replacementProperties . '</tboby></table>' . LF . '<p>Therefore, the Page TSconfig column of those pages will need to be updated manually.</p>' . LF;
148 }
149 $result = true;
150 } else {
151 // if we found no occurrence of deprecated settings and wizard was already executed, then
152 // we do not show up anymore
153 if ($this->isWizardDone()) {
154 $result = false;
155 }
156 }
157 $description .= '<p>Only page records are searched for deprecated properties. However, such properties can also be used in BE group and BE user records (prepended with page.). These are not searched nor updated by this wizard.</p>' . LF . '<p>Page TSconfig may also be included from external files. These are not updated by this wizard. If required, the update will need to be done manually.</p>' . LF . '<p>Note also that deprecated properties have been replaced in default configurations provided by htmlArea RTE';
158
159 return $result;
160 }
161
162 /**
163 * Performs the update itself
164 *
165 * @param array $dbQueries Pointer where to insert all DB queries made, so they can be shown to the user if wanted
166 * @param string $customMessages Pointer to output custom messages
167 * @return bool TRUE if update succeeded, FALSE otherwise
168 */
169 public function performUpdate(array &$dbQueries, &$customMessages)
170 {
171 $customMessages = '';
172 $pages = $this->getPagesWithDeprecatedRteProperties($customMessages);
173 if (empty($customMessages)) {
174 $pagesCount = count($pages);
175 if ($pagesCount) {
176 $updateablePages = $this->findUpdateablePagesWithDeprecatedRteProperties($pages);
177 if (!empty($updateablePages)) {
178 $this->updatePages($updateablePages, $dbQueries, $customMessages);
179 // If the update was successful
180 if (empty($customMessages)) {
181 // If all pages were updated, we query again to check if any deprecated properties are still present.
182 if (count($updateablePages) === $pagesCount) {
183 $pagesAfter = $this->getPagesWithDeprecatedRteProperties($customMessages);
184 if (empty($customMessages)) {
185 if (!empty($pagesAfter)) {
186 $customMessages = 'Some deprecated Page TSconfig properties were found. However, the wizard was unable to automatically replace all the deprecated properties found. Some properties will have to be replaced manually.';
187 }
188 }
189 } else {
190 $customMessages = 'Some deprecated Page TSconfig properties were found. However, the wizard was unable to automatically replace all the deprecated properties found. Some properties will have to be replaced manually.';
191 }
192 }
193 } else {
194 $customMessages = 'Some deprecated Page TSconfig properties were found. However, the wizard was unable to automatically replace any of the deprecated properties found. These properties will have to be replaced manually.';
195 }
196 }
197 }
198 $this->markWizardAsDone();
199 return empty($customMessages);
200 }
201
202 /**
203 * Gets the pages with deprecated RTE properties in TSconfig column
204 *
205 * @param string $customMessages Pointer to output custom messages
206 * @return array uid and inclusion string for the pages with deprecated RTE properties in TSconfig column
207 */
208 protected function getPagesWithDeprecatedRteProperties(&$customMessages)
209 {
210 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
211 $queryBuilder->getRestrictions()->removeAll();
212
213 $isMySQL = StringUtility::beginsWith($queryBuilder->getConnection()->getServerVersion(), 'MySQL');
214
215 $constraints = [];
216 foreach (array_merge($this->replacementRteProperties, $this->useInsteadRteProperties, $this->doubleReplacementRteProperties) as $deprecatedRteProperty => $_) {
217 // MySQL needs the non-standard BINARY modifier to ensure case sensitive comparisons with LIKE
218 if ($isMySQL) {
219 $constraints[] = $queryBuilder->expr()->andX(
220 $queryBuilder->expr()->comparison(
221 $queryBuilder->quoteIdentifier('TSconfig'),
222 'LIKE BINARY',
223 $queryBuilder->createNamedParameter(
224 '%RTE.%' . $queryBuilder->escapeLikeWildcards($deprecatedRteProperty) . '%'
225 )
226 ),
227 $queryBuilder->expr()->comparison(
228 $queryBuilder->quoteIdentifier('TSconfig'),
229 'NOT LIKE BINARY',
230 $queryBuilder->createNamedParameter(
231 '%RTE.%' . $queryBuilder->escapeLikeWildcards($deprecatedRteProperty) . 's%'
232 )
233 )
234 );
235 } else {
236 $constraints[] = $queryBuilder->expr()->andX(
237 $queryBuilder->expr()->like(
238 'TSconfig',
239 $queryBuilder->createNamedParameter(
240 '%RTE.%' . $queryBuilder->escapeLikeWildcards($deprecatedRteProperty) . '%'
241 )
242 ),
243 $queryBuilder->expr()->notLike(
244 'TSconfig',
245 $queryBuilder->createNamedParameter(
246 '%RTE.%' . $queryBuilder->escapeLikeWildcards($deprecatedRteProperty) . 's%'
247 )
248 )
249 );
250 }
251 }
252
253 try {
254 return $queryBuilder
255 ->select('uid', 'TSconfig')
256 ->from('pages')
257 ->orWhere(...$constraints)
258 ->execute()
259 ->fetchAll();
260 } catch (DBALException $e) {
261 $customMessages = 'SQL-ERROR: ' . htmlspecialchars($e->getPrevious()->getMessage());
262 }
263
264 return [];
265 }
266
267 /**
268 * Gets the pages with updateable deprecated RTE properties in TSconfig column
269 *
270 * @param array $pages reference to pages with deprecated property
271 * @return array uid and inclusion string for the pages with deprecated RTE properties in TSconfig column
272 */
273 protected function findUpdateablePagesWithDeprecatedRteProperties(&$pages)
274 {
275 foreach ($pages as $index => $page) {
276 $deprecatedProperties = explode(',', '/' . implode('/,/((RTE\\.(default\\.|config\\.[a-zA-Z0-9_\\-]*\\.[a-zA-Z0-9_\\-]*\\.))|\\s)', array_keys($this->replacementRteProperties)) . '/');
277 $replacementProperties = explode(',', '$1' . implode(',$1', array_values($this->replacementRteProperties)));
278 $updatedPageTSConfig = preg_replace($deprecatedProperties, $replacementProperties, $page['TSconfig']);
279 if ($updatedPageTSConfig === $page['TSconfig']) {
280 unset($pages[$index]);
281 } else {
282 $pages[$index]['TSconfig'] = $updatedPageTSConfig;
283 }
284 }
285 return $pages;
286 }
287
288 /**
289 * updates the pages records with updateable Page TSconfig properties
290 *
291 * @param array $pages Page records to update, fetched by getTemplates() and filtered by
292 * @param array $dbQueries Pointer where to insert all DB queries made, so they can be shown to the user if wanted
293 * @param string $customMessages Pointer to output custom messages
294 */
295 protected function updatePages($pages, &$dbQueries, &$customMessages)
296 {
297 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
298 foreach ($pages as $page) {
299 try {
300 $queryBuilder->update('pages')
301 ->where($queryBuilder->expr()->eq('uid', (int)$page['uid']))
302 ->set('TSconfig', $queryBuilder->quote($page['TSconfig']), false)
303 ->execute();
304 } catch (DBALException $e) {
305 $customMessages .= 'SQL-ERROR: ' . htmlspecialchars($e->getPrevious()->getMessage()) . LF . LF;
306 }
307 $dbQueries[] = str_replace(LF, ' ', $queryBuilder->getSQL());
308 }
309 }
310 }