[!!!][TASK] Remove [SYS][recursiveDomainSearch] option
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Service / SilentConfigurationUpgradeService.php
1 <?php
2 namespace TYPO3\CMS\Install\Service;
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 TYPO3\CMS\Core\Configuration\ConfigurationManager;
18 use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
19 use TYPO3\CMS\Core\Crypto\PasswordHashing\BcryptPasswordHash;
20 use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface;
21 use TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash;
22 use TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash;
23 use TYPO3\CMS\Core\Crypto\Random;
24 use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
25 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use TYPO3\CMS\Install\Service\Exception\ConfigurationChangedException;
28
29 /**
30 * Execute "silent" LocalConfiguration upgrades if needed.
31 *
32 * Some LocalConfiguration settings are obsolete or changed over time.
33 * This class handles upgrades of these settings. It is called by
34 * the step controller at an early point.
35 *
36 * Every change is encapsulated in one method an must throw a ConfigurationChangedException
37 * if new data is written to LocalConfiguration. This is caught by above
38 * step controller to initiate a redirect and start again with adapted configuration.
39 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
40 */
41 class SilentConfigurationUpgradeService
42 {
43 /**
44 * @var \TYPO3\CMS\Core\Configuration\ConfigurationManager
45 */
46 protected $configurationManager;
47
48 /**
49 * List of obsolete configuration options in LocalConfiguration to be removed
50 * Example:
51 * // #forge-ticket
52 * 'BE/somesetting',
53 *
54 * @var array
55 */
56 protected $obsoleteLocalConfigurationSettings = [
57 // #72400
58 'BE/spriteIconGenerator_handler',
59 // #72417
60 'SYS/lockingMode',
61 // #72473
62 'FE/secureFormmail',
63 'FE/strictFormmail',
64 'FE/formmailMaxAttachmentSize',
65 // #72337
66 'SYS/t3lib_cs_utils',
67 'SYS/t3lib_cs_convMethod',
68 // #72604
69 'SYS/maxFileNameLength',
70 // #72602
71 'BE/unzip_path',
72 // #72615
73 'BE/notificationPrefix',
74 // #72616
75 'BE/XCLASS',
76 'FE/XCLASS',
77 // #43085
78 'GFX/image_processing',
79 // #70056
80 'SYS/curlUse',
81 'SYS/curlProxyNTLM',
82 'SYS/curlProxyServer',
83 'SYS/curlProxyTunnel',
84 'SYS/curlProxyUserPass',
85 'SYS/curlTimeout',
86 // #75355
87 'BE/niceFlexFormXMLtags',
88 'BE/compactFlexFormXML',
89 // #75625
90 'SYS/clearCacheSystem',
91 // #77411
92 'SYS/caching/cacheConfigurations/extbase_typo3dbbackend_tablecolumns',
93 // #77460
94 'SYS/caching/cacheConfigurations/extbase_typo3dbbackend_queries',
95 // #79513
96 'FE/lockHashKeyWords',
97 'BE/lockHashKeyWords',
98 // #78835
99 'SYS/cookieHttpOnly',
100 // #71095
101 'BE/lang',
102 // #80050
103 'FE/cHashIncludePageId',
104 // #80711
105 'FE/noPHPscriptInclude',
106 'FE/maxSessionDataSize',
107 // #82162
108 'SYS/enable_errorDLOG',
109 'SYS/enable_exceptionDLOG',
110 // #82377
111 'EXT/allowSystemInstall',
112 // #82421
113 'SYS/sqlDebug',
114 'SYS/no_pconnect',
115 'SYS/setDBinit',
116 'SYS/dbClientCompress',
117 // #82430
118 'SYS/syslogErrorReporting',
119 // #82639
120 'SYS/enable_DLOG',
121 'SC_OPTIONS/t3lib/class.t3lib_userauth.php/writeDevLog',
122 'SC_OPTIONS/t3lib/class.t3lib_userauth.php/writeDevLogBE',
123 'SC_OPTIONS/t3lib/class.t3lib_userauth.php/writeDevLogFE',
124 // #82438
125 'SYS/enableDeprecationLog',
126 // #82680
127 'GFX/png_truecolor',
128 // #82803
129 'FE/content_doktypes',
130 // #83081
131 'BE/fileExtensions',
132 // #83768
133 'SYS/doNotCheckReferer',
134 // #83878
135 'SYS/isInitialInstallationInProgress',
136 'SYS/isInitialDatabaseImportDone',
137 // #84810
138 'BE/explicitConfirmationOfTranslation',
139 // #87482
140 'EXT/extConf',
141 // #87767
142 'SYS/recursiveDomainSearch',
143 ];
144
145 public function __construct(ConfigurationManager $configurationManager = null)
146 {
147 $this->configurationManager = $configurationManager ?: GeneralUtility::makeInstance(ConfigurationManager::class);
148 }
149
150 /**
151 * Executed configuration upgrades. Single upgrade methods must throw a
152 * ConfigurationChangedException if something was written to LocalConfiguration.
153 *
154 * @throws ConfigurationChangedException
155 */
156 public function execute()
157 {
158 $this->generateEncryptionKeyIfNeeded();
159 $this->configureBackendLoginSecurity();
160 $this->configureFrontendLoginSecurity();
161 $this->migrateImageProcessorSetting();
162 $this->transferHttpSettings();
163 $this->disableImageMagickDetailSettingsIfImageMagickIsDisabled();
164 $this->setImageMagickDetailSettings();
165 $this->migrateThumbnailsPngSetting();
166 $this->migrateLockSslSetting();
167 $this->migrateDatabaseConnectionSettings();
168 $this->migrateDatabaseConnectionCharset();
169 $this->migrateDatabaseDriverOptions();
170 $this->migrateLangDebug();
171 $this->migrateCacheHashOptions();
172 $this->migrateExceptionErrors();
173 $this->migrateDisplayErrorsSetting();
174 $this->migrateSaltedPasswordsSettings();
175
176 // Should run at the end to prevent obsolete settings are removed before migration
177 $this->removeObsoleteLocalConfigurationSettings();
178 }
179
180 /**
181 * Some settings in LocalConfiguration vanished in DefaultConfiguration
182 * and have no impact on the core anymore.
183 * To keep the configuration clean, those old settings are just silently
184 * removed from LocalConfiguration if set.
185 *
186 * @throws ConfigurationChangedException
187 */
188 protected function removeObsoleteLocalConfigurationSettings()
189 {
190 $removed = $this->configurationManager->removeLocalConfigurationKeysByPath($this->obsoleteLocalConfigurationSettings);
191
192 // If something was changed: Trigger a reload to have new values in next request
193 if ($removed) {
194 $this->throwConfigurationChangedException();
195 }
196 }
197
198 /**
199 * Backend login security is set to rsa if rsaauth
200 * is installed (but not used) otherwise the default value "normal" has to be used.
201 * This forces either 'normal' or 'rsa' to be set in LocalConfiguration.
202 *
203 * @throws ConfigurationChangedException
204 */
205 protected function configureBackendLoginSecurity()
206 {
207 $rsaauthLoaded = ExtensionManagementUtility::isLoaded('rsaauth');
208 try {
209 $currentLoginSecurityLevelValue = $this->configurationManager->getLocalConfigurationValueByPath('BE/loginSecurityLevel');
210 if ($rsaauthLoaded && $currentLoginSecurityLevelValue !== 'rsa') {
211 $this->configurationManager->setLocalConfigurationValueByPath('BE/loginSecurityLevel', 'rsa');
212 $this->throwConfigurationChangedException();
213 } elseif (!$rsaauthLoaded && $currentLoginSecurityLevelValue !== 'normal') {
214 $this->configurationManager->setLocalConfigurationValueByPath('BE/loginSecurityLevel', 'normal');
215 $this->throwConfigurationChangedException();
216 }
217 } catch (MissingArrayPathException $e) {
218 // If an exception is thrown, the value is not set in LocalConfiguration
219 $this->configurationManager->setLocalConfigurationValueByPath(
220 'BE/loginSecurityLevel',
221 $rsaauthLoaded ? 'rsa' : 'normal'
222 );
223 $this->throwConfigurationChangedException();
224 }
225 }
226
227 /**
228 * Frontend login security is set to normal in case
229 * any other value is set while ext:rsaauth is not loaded.
230 *
231 * @throws ConfigurationChangedException
232 */
233 protected function configureFrontendLoginSecurity()
234 {
235 $rsaauthLoaded = ExtensionManagementUtility::isLoaded('rsaauth');
236 try {
237 $currentLoginSecurityLevelValue = $this->configurationManager->getLocalConfigurationValueByPath('FE/loginSecurityLevel');
238 if (!$rsaauthLoaded && $currentLoginSecurityLevelValue !== 'normal') {
239 $this->configurationManager->setLocalConfigurationValueByPath('FE/loginSecurityLevel', 'normal');
240 $this->throwConfigurationChangedException();
241 }
242 } catch (MissingArrayPathException $e) {
243 // no value set, just ignore
244 }
245 }
246
247 /**
248 * The encryption key is crucial for securing form tokens
249 * and the whole TYPO3 link rendering later on. A random key is set here in
250 * LocalConfiguration if it does not exist yet. This might possible happen
251 * during upgrading and will happen during first install.
252 *
253 * @throws ConfigurationChangedException
254 */
255 protected function generateEncryptionKeyIfNeeded()
256 {
257 try {
258 $currentValue = $this->configurationManager->getLocalConfigurationValueByPath('SYS/encryptionKey');
259 } catch (MissingArrayPathException $e) {
260 // If an exception is thrown, the value is not set in LocalConfiguration
261 $currentValue = '';
262 }
263
264 if (empty($currentValue)) {
265 $randomKey = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(96);
266 $this->configurationManager->setLocalConfigurationValueByPath('SYS/encryptionKey', $randomKey);
267 $this->throwConfigurationChangedException();
268 }
269 }
270
271 /**
272 * Parse old curl and HTTP options and set new HTTP options, related to Guzzle
273 *
274 * @throws ConfigurationChangedException
275 */
276 protected function transferHttpSettings()
277 {
278 $changed = false;
279 $newParameters = [];
280 $obsoleteParameters = [];
281
282 // Remove / migrate options to new options
283 try {
284 // Check if the adapter option is set, if so, set it to the parameters that are obsolete
285 $this->configurationManager->getLocalConfigurationValueByPath('HTTP/adapter');
286 $obsoleteParameters[] = 'HTTP/adapter';
287 } catch (MissingArrayPathException $e) {
288 // Migration done already
289 }
290 try {
291 $newParameters['HTTP/version'] = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/protocol_version');
292 $obsoleteParameters[] = 'HTTP/protocol_version';
293 } catch (MissingArrayPathException $e) {
294 // Migration done already
295 }
296 try {
297 $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_verify_host');
298 $obsoleteParameters[] = 'HTTP/ssl_verify_host';
299 } catch (MissingArrayPathException $e) {
300 // Migration done already
301 }
302 try {
303 $legacyUserAgent = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/userAgent');
304 $newParameters['HTTP/headers/User-Agent'] = $legacyUserAgent;
305 $obsoleteParameters[] = 'HTTP/userAgent';
306 } catch (MissingArrayPathException $e) {
307 // Migration done already
308 }
309
310 // Redirects
311 try {
312 $legacyFollowRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/follow_redirects');
313 $obsoleteParameters[] = 'HTTP/follow_redirects';
314 } catch (MissingArrayPathException $e) {
315 $legacyFollowRedirects = '';
316 }
317 try {
318 $legacyMaximumRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/max_redirects');
319 $obsoleteParameters[] = 'HTTP/max_redirects';
320 } catch (MissingArrayPathException $e) {
321 $legacyMaximumRedirects = '';
322 }
323 try {
324 $legacyStrictRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/strict_redirects');
325 $obsoleteParameters[] = 'HTTP/strict_redirects';
326 } catch (MissingArrayPathException $e) {
327 $legacyStrictRedirects = '';
328 }
329
330 // Check if redirects have been disabled
331 if ($legacyFollowRedirects !== '' && (bool)$legacyFollowRedirects === false) {
332 $newParameters['HTTP/allow_redirects'] = false;
333 } elseif ($legacyMaximumRedirects !== '' || $legacyStrictRedirects !== '') {
334 $newParameters['HTTP/allow_redirects'] = [];
335 if ($legacyMaximumRedirects !== '' && (int)$legacyMaximumRedirects !== 5) {
336 $newParameters['HTTP/allow_redirects']['max'] = (int)$legacyMaximumRedirects;
337 }
338 if ($legacyStrictRedirects !== '' && (bool)$legacyStrictRedirects === true) {
339 $newParameters['HTTP/allow_redirects']['strict'] = true;
340 }
341 // defaults are used, no need to set the option in LocalConfiguration.php
342 if (empty($newParameters['HTTP/allow_redirects'])) {
343 unset($newParameters['HTTP/allow_redirects']);
344 }
345 }
346
347 // Migrate Proxy settings
348 try {
349 // Currently without protocol or port
350 $legacyProxyHost = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_host');
351 $obsoleteParameters[] = 'HTTP/proxy_host';
352 } catch (MissingArrayPathException $e) {
353 $legacyProxyHost = '';
354 }
355 try {
356 $legacyProxyPort = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_port');
357 $obsoleteParameters[] = 'HTTP/proxy_port';
358 } catch (MissingArrayPathException $e) {
359 $legacyProxyPort = '';
360 }
361 try {
362 $legacyProxyUser = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_user');
363 $obsoleteParameters[] = 'HTTP/proxy_user';
364 } catch (MissingArrayPathException $e) {
365 $legacyProxyUser = '';
366 }
367 try {
368 $legacyProxyPassword = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_password');
369 $obsoleteParameters[] = 'HTTP/proxy_password';
370 } catch (MissingArrayPathException $e) {
371 $legacyProxyPassword = '';
372 }
373 // Auth Scheme: Basic, digest etc.
374 try {
375 $legacyProxyAuthScheme = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_auth_scheme');
376 $obsoleteParameters[] = 'HTTP/proxy_auth_scheme';
377 } catch (MissingArrayPathException $e) {
378 $legacyProxyAuthScheme = '';
379 }
380
381 if ($legacyProxyHost !== '') {
382 $proxy = 'http://';
383 if ($legacyProxyAuthScheme !== '' && $legacyProxyUser !== '' && $legacyProxyPassword !== '') {
384 $proxy .= $legacyProxyUser . ':' . $legacyProxyPassword . '@';
385 }
386 $proxy .= $legacyProxyHost;
387 if ($legacyProxyPort !== '') {
388 $proxy .= ':' . $legacyProxyPort;
389 }
390 $newParameters['HTTP/proxy'] = $proxy;
391 }
392
393 // Verify peers
394 // see http://docs.guzzlephp.org/en/latest/request-options.html#verify
395 try {
396 $legacySslVerifyPeer = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_verify_peer');
397 $obsoleteParameters[] = 'HTTP/ssl_verify_peer';
398 } catch (MissingArrayPathException $e) {
399 $legacySslVerifyPeer = '';
400 }
401
402 // Directory holding multiple Certificate Authority files
403 try {
404 $legacySslCaPath = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_capath');
405 $obsoleteParameters[] = 'HTTP/ssl_capath';
406 } catch (MissingArrayPathException $e) {
407 $legacySslCaPath = '';
408 }
409 // Certificate Authority file to verify the peer with (use when ssl_verify_peer is TRUE)
410 try {
411 $legacySslCaFile = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_cafile');
412 $obsoleteParameters[] = 'HTTP/ssl_cafile';
413 } catch (MissingArrayPathException $e) {
414 $legacySslCaFile = '';
415 }
416 if ($legacySslVerifyPeer !== '') {
417 if ($legacySslCaFile !== '' && $legacySslCaPath !== '') {
418 $newParameters['HTTP/verify'] = $legacySslCaPath . $legacySslCaFile;
419 } elseif ((bool)$legacySslVerifyPeer === false) {
420 $newParameters['HTTP/verify'] = false;
421 }
422 }
423
424 // SSL Key + Passphrase
425 // Name of a file containing local certificate
426 try {
427 $legacySslLocalCert = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_local_cert');
428 $obsoleteParameters[] = 'HTTP/ssl_local_cert';
429 } catch (MissingArrayPathException $e) {
430 $legacySslLocalCert = '';
431 }
432
433 // Passphrase with which local certificate was encoded
434 try {
435 $legacySslPassphrase = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_passphrase');
436 $obsoleteParameters[] = 'HTTP/ssl_passphrase';
437 } catch (MissingArrayPathException $e) {
438 $legacySslPassphrase = '';
439 }
440
441 if ($legacySslLocalCert !== '') {
442 if ($legacySslPassphrase !== '') {
443 $newParameters['HTTP/ssl_key'] = [
444 $legacySslLocalCert,
445 $legacySslPassphrase
446 ];
447 } else {
448 $newParameters['HTTP/ssl_key'] = $legacySslLocalCert;
449 }
450 }
451
452 // Update the LocalConfiguration file if obsolete parameters or new parameters are set
453 if (!empty($obsoleteParameters)) {
454 $this->configurationManager->removeLocalConfigurationKeysByPath($obsoleteParameters);
455 $changed = true;
456 }
457 if (!empty($newParameters)) {
458 $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($newParameters);
459 $changed = true;
460 }
461 if ($changed) {
462 $this->throwConfigurationChangedException();
463 }
464 }
465
466 /**
467 * Detail configuration of Image Magick settings must be cleared
468 * if Image Magick handling is disabled.
469 *
470 * "Configuration presets" in install tool is not type safe, so value
471 * comparisons here are not type safe too, to not trigger changes to
472 * LocalConfiguration again.
473 *
474 * @throws ConfigurationChangedException
475 */
476 protected function disableImageMagickDetailSettingsIfImageMagickIsDisabled()
477 {
478 $changedValues = [];
479 try {
480 $currentEnabledValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_enabled');
481 } catch (MissingArrayPathException $e) {
482 $currentEnabledValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_enabled');
483 }
484
485 try {
486 $currentPathValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_path');
487 } catch (MissingArrayPathException $e) {
488 $currentPathValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_path');
489 }
490
491 try {
492 $currentPathLzwValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_path_lzw');
493 } catch (MissingArrayPathException $e) {
494 $currentPathLzwValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_path_lzw');
495 }
496
497 try {
498 $currentImageFileExtValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/imagefile_ext');
499 } catch (MissingArrayPathException $e) {
500 $currentImageFileExtValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/imagefile_ext');
501 }
502
503 try {
504 $currentThumbnailsValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/thumbnails');
505 } catch (MissingArrayPathException $e) {
506 $currentThumbnailsValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/thumbnails');
507 }
508
509 if (!$currentEnabledValue) {
510 if ($currentPathValue != '') {
511 $changedValues['GFX/processor_path'] = '';
512 }
513 if ($currentPathLzwValue != '') {
514 $changedValues['GFX/processor_path_lzw'] = '';
515 }
516 if ($currentImageFileExtValue !== 'gif,jpg,jpeg,png') {
517 $changedValues['GFX/imagefile_ext'] = 'gif,jpg,jpeg,png';
518 }
519 if ($currentThumbnailsValue != 0) {
520 $changedValues['GFX/thumbnails'] = 0;
521 }
522 }
523 if (!empty($changedValues)) {
524 $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
525 $this->throwConfigurationChangedException();
526 }
527 }
528
529 /**
530 * Detail configuration of Image Magick and Graphics Magick settings
531 * depending on main values.
532 *
533 * "Configuration presets" in install tool is not type safe, so value
534 * comparisons here are not type safe too, to not trigger changes to
535 * LocalConfiguration again.
536 *
537 * @throws ConfigurationChangedException
538 */
539 protected function setImageMagickDetailSettings()
540 {
541 $changedValues = [];
542 try {
543 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor');
544 } catch (MissingArrayPathException $e) {
545 $currentProcessorValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor');
546 }
547
548 try {
549 $currentProcessorMaskValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_allowTemporaryMasksAsPng');
550 } catch (MissingArrayPathException $e) {
551 $currentProcessorMaskValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_allowTemporaryMasksAsPng');
552 }
553
554 try {
555 $currentProcessorEffectsValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_effects');
556 } catch (MissingArrayPathException $e) {
557 $currentProcessorEffectsValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_effects');
558 }
559
560 if ((string)$currentProcessorValue !== '') {
561 if (!is_bool($currentProcessorEffectsValue)) {
562 $changedValues['GFX/processor_effects'] = (int)$currentProcessorEffectsValue > 0;
563 }
564
565 if ($currentProcessorMaskValue != 0) {
566 $changedValues['GFX/processor_allowTemporaryMasksAsPng'] = 0;
567 }
568 }
569 if (!empty($changedValues)) {
570 $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
571 $this->throwConfigurationChangedException();
572 }
573 }
574
575 /**
576 * Migrate the definition of the image processor from the configuration value
577 * im_version_5 to the setting processor.
578 *
579 * @throws ConfigurationChangedException
580 */
581 protected function migrateImageProcessorSetting()
582 {
583 $changedSettings = [];
584 $settingsToRename = [
585 'GFX/im' => 'GFX/processor_enabled',
586 'GFX/im_version_5' => 'GFX/processor',
587 'GFX/im_v5effects' => 'GFX/processor_effects',
588 'GFX/im_path' => 'GFX/processor_path',
589 'GFX/im_path_lzw' => 'GFX/processor_path_lzw',
590 'GFX/im_mask_temp_ext_gif' => 'GFX/processor_allowTemporaryMasksAsPng',
591 'GFX/im_noScaleUp' => 'GFX/processor_allowUpscaling',
592 'GFX/im_noFramePrepended' => 'GFX/processor_allowFrameSelection',
593 'GFX/im_stripProfileCommand' => 'GFX/processor_stripColorProfileCommand',
594 'GFX/im_useStripProfileByDefault' => 'GFX/processor_stripColorProfileByDefault',
595 'GFX/colorspace' => 'GFX/processor_colorspace',
596 ];
597
598 foreach ($settingsToRename as $oldPath => $newPath) {
599 try {
600 $value = $this->configurationManager->getLocalConfigurationValueByPath($oldPath);
601 $this->configurationManager->setLocalConfigurationValueByPath($newPath, $value);
602 $changedSettings[$oldPath] = true;
603 } catch (MissingArrayPathException $e) {
604 // If an exception is thrown, the value is not set in LocalConfiguration
605 $changedSettings[$oldPath] = false;
606 }
607 }
608
609 if (!empty($changedSettings['GFX/im_version_5'])) {
610 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_version_5');
611 $newProcessorValue = $currentProcessorValue === 'gm' ? 'GraphicsMagick' : 'ImageMagick';
612 $this->configurationManager->setLocalConfigurationValueByPath('GFX/processor', $newProcessorValue);
613 }
614
615 if (!empty($changedSettings['GFX/im_noScaleUp'])) {
616 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_noScaleUp');
617 $newProcessorValue = !$currentProcessorValue;
618 $this->configurationManager->setLocalConfigurationValueByPath(
619 'GFX/processor_allowUpscaling',
620 $newProcessorValue
621 );
622 }
623
624 if (!empty($changedSettings['GFX/im_noFramePrepended'])) {
625 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_noFramePrepended');
626 $newProcessorValue = !$currentProcessorValue;
627 $this->configurationManager->setLocalConfigurationValueByPath(
628 'GFX/processor_allowFrameSelection',
629 $newProcessorValue
630 );
631 }
632
633 if (!empty($changedSettings['GFX/im_mask_temp_ext_gif'])) {
634 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_mask_temp_ext_gif');
635 $newProcessorValue = !$currentProcessorValue;
636 $this->configurationManager->setLocalConfigurationValueByPath(
637 'GFX/processor_allowTemporaryMasksAsPng',
638 $newProcessorValue
639 );
640 }
641
642 if (!empty(array_filter($changedSettings))) {
643 $this->configurationManager->removeLocalConfigurationKeysByPath(array_keys($changedSettings));
644 $this->throwConfigurationChangedException();
645 }
646 }
647
648 /**
649 * Throw exception after configuration change to trigger a redirect.
650 *
651 * @throws ConfigurationChangedException
652 */
653 protected function throwConfigurationChangedException()
654 {
655 throw new ConfigurationChangedException(
656 'Configuration updated, reload needed',
657 1379024938
658 );
659 }
660
661 /**
662 * Migrate the configuration value thumbnails_png to a boolean value.
663 *
664 * @throws ConfigurationChangedException
665 */
666 protected function migrateThumbnailsPngSetting()
667 {
668 $changedValues = [];
669 try {
670 $currentThumbnailsPngValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/thumbnails_png');
671 } catch (MissingArrayPathException $e) {
672 $currentThumbnailsPngValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/thumbnails_png');
673 }
674
675 if (is_int($currentThumbnailsPngValue) && $currentThumbnailsPngValue > 0) {
676 $changedValues['GFX/thumbnails_png'] = true;
677 }
678 if (!empty($changedValues)) {
679 $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
680 $this->throwConfigurationChangedException();
681 }
682 }
683
684 /**
685 * Migrate the configuration setting BE/lockSSL to boolean if set in the LocalConfiguration.php file
686 *
687 * @throws ConfigurationChangedException
688 */
689 protected function migrateLockSslSetting()
690 {
691 try {
692 $currentOption = $this->configurationManager->getLocalConfigurationValueByPath('BE/lockSSL');
693 // check if the current option is an integer/string and if it is active
694 if (!is_bool($currentOption) && (int)$currentOption > 0) {
695 $this->configurationManager->setLocalConfigurationValueByPath('BE/lockSSL', true);
696 $this->throwConfigurationChangedException();
697 }
698 } catch (MissingArrayPathException $e) {
699 // no change inside the LocalConfiguration.php found, so nothing needs to be modified
700 }
701 }
702
703 /**
704 * Move the database connection settings to a "Default" connection
705 *
706 * @throws ConfigurationChangedException
707 */
708 protected function migrateDatabaseConnectionSettings()
709 {
710 $confManager = $this->configurationManager;
711
712 $newSettings = [];
713 $removeSettings = [];
714
715 try {
716 $value = $confManager->getLocalConfigurationValueByPath('DB/username');
717 $removeSettings[] = 'DB/username';
718 $newSettings['DB/Connections/Default/user'] = $value;
719 } catch (MissingArrayPathException $e) {
720 // Old setting does not exist, do nothing
721 }
722
723 try {
724 $value = $confManager->getLocalConfigurationValueByPath('DB/password');
725 $removeSettings[] = 'DB/password';
726 $newSettings['DB/Connections/Default/password'] = $value;
727 } catch (MissingArrayPathException $e) {
728 // Old setting does not exist, do nothing
729 }
730
731 try {
732 $value = $confManager->getLocalConfigurationValueByPath('DB/host');
733 $removeSettings[] = 'DB/host';
734 $newSettings['DB/Connections/Default/host'] = $value;
735 } catch (MissingArrayPathException $e) {
736 // Old setting does not exist, do nothing
737 }
738
739 try {
740 $value = $confManager->getLocalConfigurationValueByPath('DB/port');
741 $removeSettings[] = 'DB/port';
742 $newSettings['DB/Connections/Default/port'] = $value;
743 } catch (MissingArrayPathException $e) {
744 // Old setting does not exist, do nothing
745 }
746
747 try {
748 $value = $confManager->getLocalConfigurationValueByPath('DB/socket');
749 $removeSettings[] = 'DB/socket';
750 // Remove empty socket connects
751 if (!empty($value)) {
752 $newSettings['DB/Connections/Default/unix_socket'] = $value;
753 }
754 } catch (MissingArrayPathException $e) {
755 // Old setting does not exist, do nothing
756 }
757
758 try {
759 $value = $confManager->getLocalConfigurationValueByPath('DB/database');
760 $removeSettings[] = 'DB/database';
761 $newSettings['DB/Connections/Default/dbname'] = $value;
762 } catch (MissingArrayPathException $e) {
763 // Old setting does not exist, do nothing
764 }
765
766 try {
767 $value = (bool)$confManager->getLocalConfigurationValueByPath('SYS/dbClientCompress');
768 $removeSettings[] = 'SYS/dbClientCompress';
769 if ($value) {
770 $newSettings['DB/Connections/Default/driverOptions'] = [
771 'flags' => MYSQLI_CLIENT_COMPRESS,
772 ];
773 }
774 } catch (MissingArrayPathException $e) {
775 // Old setting does not exist, do nothing
776 }
777
778 try {
779 $value = (bool)$confManager->getLocalConfigurationValueByPath('SYS/no_pconnect');
780 $removeSettings[] = 'SYS/no_pconnect';
781 if (!$value) {
782 $newSettings['DB/Connections/Default/persistentConnection'] = true;
783 }
784 } catch (MissingArrayPathException $e) {
785 // Old setting does not exist, do nothing
786 }
787
788 try {
789 $value = $confManager->getLocalConfigurationValueByPath('SYS/setDBinit');
790 $removeSettings[] = 'SYS/setDBinit';
791 $newSettings['DB/Connections/Default/initCommands'] = $value;
792 } catch (MissingArrayPathException $e) {
793 // Old setting does not exist, do nothing
794 }
795
796 try {
797 $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/charset');
798 } catch (MissingArrayPathException $e) {
799 // If there is no charset option yet, add it.
800 $newSettings['DB/Connections/Default/charset'] = 'utf8';
801 }
802
803 try {
804 $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/driver');
805 } catch (MissingArrayPathException $e) {
806 // Use the mysqli driver by default if no value has been provided yet
807 $newSettings['DB/Connections/Default/driver'] = 'mysqli';
808 }
809
810 // Add new settings and remove old ones
811 if (!empty($newSettings)) {
812 $confManager->setLocalConfigurationValuesByPathValuePairs($newSettings);
813 }
814 if (!empty($removeSettings)) {
815 $confManager->removeLocalConfigurationKeysByPath($removeSettings);
816 }
817
818 // Throw redirect if something was changed
819 if (!empty($newSettings) || !empty($removeSettings)) {
820 $this->throwConfigurationChangedException();
821 }
822 }
823
824 /**
825 * Migrate the configuration setting DB/Connections/Default/charset to 'utf8' as
826 * 'utf-8' is not supported by all MySQL versions.
827 *
828 * @throws ConfigurationChangedException
829 */
830 protected function migrateDatabaseConnectionCharset()
831 {
832 $confManager = $this->configurationManager;
833 try {
834 $driver = $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/driver');
835 $charset = $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/charset');
836 if (in_array($driver, ['mysqli', 'pdo_mysql', 'drizzle_pdo_mysql'], true) && $charset === 'utf-8') {
837 $confManager->setLocalConfigurationValueByPath('DB/Connections/Default/charset', 'utf8');
838 $this->throwConfigurationChangedException();
839 }
840 } catch (MissingArrayPathException $e) {
841 // no incompatible charset configuration found, so nothing needs to be modified
842 }
843 }
844
845 /**
846 * Migrate the configuration setting DB/Connections/Default/driverOptions to array type.
847 *
848 * @throws ConfigurationChangedException
849 */
850 protected function migrateDatabaseDriverOptions()
851 {
852 $confManager = $this->configurationManager;
853 try {
854 $options = $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/driverOptions');
855 if (!is_array($options)) {
856 $confManager->setLocalConfigurationValueByPath(
857 'DB/Connections/Default/driverOptions',
858 ['flags' => (int)$options]
859 );
860 $this->throwConfigurationChangedException();
861 }
862 } catch (MissingArrayPathException $e) {
863 // no driver options found, nothing needs to be modified
864 }
865 }
866
867 /**
868 * Migrate the configuration setting BE/lang/debug if set in the LocalConfiguration.php file
869 *
870 * @throws ConfigurationChangedException
871 */
872 protected function migrateLangDebug()
873 {
874 $confManager = $this->configurationManager;
875 try {
876 $currentOption = $confManager->getLocalConfigurationValueByPath('BE/lang/debug');
877 // check if the current option is set and boolean
878 if (isset($currentOption) && is_bool($currentOption)) {
879 $confManager->setLocalConfigurationValueByPath('BE/languageDebug', $currentOption);
880 $this->throwConfigurationChangedException();
881 }
882 } catch (MissingArrayPathException $e) {
883 // no change inside the LocalConfiguration.php found, so nothing needs to be modified
884 }
885 }
886
887 /**
888 * Migrate single cache hash related options under "FE" into "FE/cacheHash"
889 *
890 * @throws ConfigurationChangedException
891 */
892 protected function migrateCacheHashOptions()
893 {
894 $confManager = $this->configurationManager;
895 $removeSettings = [];
896 $newSettings = [];
897
898 try {
899 $value = $confManager->getLocalConfigurationValueByPath('FE/cHashOnlyForParameters');
900 $removeSettings[] = 'FE/cHashOnlyForParameters';
901 $newSettings['FE/cacheHash/cachedParametersWhiteList'] = GeneralUtility::trimExplode(',', $value, true);
902 } catch (MissingArrayPathException $e) {
903 // Migration done already
904 }
905
906 try {
907 $value = $confManager->getLocalConfigurationValueByPath('FE/cHashExcludedParameters');
908 $removeSettings[] = 'FE/cHashExcludedParameters';
909 $newSettings['FE/cacheHash/excludedParameters'] = GeneralUtility::trimExplode(',', $value, true);
910 } catch (MissingArrayPathException $e) {
911 // Migration done already
912 }
913
914 try {
915 $value = $confManager->getLocalConfigurationValueByPath('FE/cHashRequiredParameters');
916 $removeSettings[] = 'FE/cHashRequiredParameters';
917 $newSettings['FE/cacheHash/requireCacheHashPresenceParameters'] = GeneralUtility::trimExplode(',', $value, true);
918 } catch (MissingArrayPathException $e) {
919 // Migration done already
920 }
921
922 try {
923 $value = $confManager->getLocalConfigurationValueByPath('FE/cHashExcludedParametersIfEmpty');
924 $removeSettings[] = 'FE/cHashExcludedParametersIfEmpty';
925 if (trim($value) === '*') {
926 $newSettings['FE/cacheHash/excludeAllEmptyParameters'] = true;
927 } else {
928 $newSettings['FE/cacheHash/excludedParametersIfEmpty'] = GeneralUtility::trimExplode(',', $value, true);
929 }
930 } catch (MissingArrayPathException $e) {
931 // Migration done already
932 }
933
934 // Add new settings and remove old ones
935 if (!empty($newSettings)) {
936 $confManager->setLocalConfigurationValuesByPathValuePairs($newSettings);
937 }
938 if (!empty($removeSettings)) {
939 $confManager->removeLocalConfigurationKeysByPath($removeSettings);
940 }
941
942 // Throw redirect if something was changed
943 if (!empty($newSettings) || !empty($removeSettings)) {
944 $this->throwConfigurationChangedException();
945 }
946 }
947
948 /**
949 * Migrate SYS/exceptionalErrors to not contain E_USER_DEPRECATED
950 *
951 * @throws ConfigurationChangedException
952 */
953 protected function migrateExceptionErrors()
954 {
955 $confManager = $this->configurationManager;
956 try {
957 $currentOption = (int)$confManager->getLocalConfigurationValueByPath('SYS/exceptionalErrors');
958 // make sure E_USER_DEPRECATED is not part of the exceptionalErrors
959 if ($currentOption & E_USER_DEPRECATED) {
960 $confManager->setLocalConfigurationValueByPath('SYS/exceptionalErrors', $currentOption & ~E_USER_DEPRECATED);
961 $this->throwConfigurationChangedException();
962 }
963 } catch (MissingArrayPathException $e) {
964 // no change inside the LocalConfiguration.php found, so nothing needs to be modified
965 }
966 }
967
968 /**
969 * Migrate SYS/displayErrors to not contain 2
970 *
971 * @throws ConfigurationChangedException
972 */
973 protected function migrateDisplayErrorsSetting()
974 {
975 $confManager = $this->configurationManager;
976 try {
977 $currentOption = (int)$confManager->getLocalConfigurationValueByPath('SYS/displayErrors');
978 // make sure displayErrors is set to 2
979 if ($currentOption === 2) {
980 $confManager->setLocalConfigurationValueByPath('SYS/displayErrors', -1);
981 $this->throwConfigurationChangedException();
982 }
983 } catch (MissingArrayPathException $e) {
984 // no change inside the LocalConfiguration.php found, so nothing needs to be modified
985 }
986 }
987
988 /**
989 * Migrate salted passwords extension configuration settings to BE/passwordHashing and FE/passwordHashing
990 *
991 * @throws ConfigurationChangedException
992 */
993 protected function migrateSaltedPasswordsSettings()
994 {
995 $confManager = $this->configurationManager;
996 $configsToRemove = [];
997 try {
998 $extensionConfiguration = (array)$confManager->getLocalConfigurationValueByPath('EXTENSIONS/saltedpasswords');
999 $configsToRemove[] = 'EXTENSIONS/saltedpasswords';
1000 } catch (MissingArrayPathException $e) {
1001 $extensionConfiguration = [];
1002 }
1003 // Migration already done
1004 if (empty($extensionConfiguration)) {
1005 return;
1006 }
1007 // Upgrade to best available hash method. This is only done once since that code will no longer be reached
1008 // after first migration because extConf and EXTENSIONS array entries are gone then. Thus, a manual selection
1009 // to some different hash mechanism will not be touched again after first upgrade.
1010 // Phpass is always available, so we have some last fallback if the others don't kick in
1011 $okHashMethods = [
1012 Argon2iPasswordHash::class,
1013 BcryptPasswordHash::class,
1014 Pbkdf2PasswordHash::class,
1015 PhpassPasswordHash::class,
1016 ];
1017 $newMethods = [];
1018 foreach (['BE', 'FE'] as $mode) {
1019 foreach ($okHashMethods as $className) {
1020 /** @var PasswordHashInterface $instance */
1021 $instance = GeneralUtility::makeInstance($className);
1022 if ($instance->isAvailable()) {
1023 $newMethods[$mode] = $className;
1024 break;
1025 }
1026 }
1027 }
1028 // We only need to write to LocalConfiguration if method is different than Argon2i from DefaultConfiguration
1029 $newConfig = [];
1030 if ($newMethods['BE'] !== Argon2iPasswordHash::class) {
1031 $newConfig['BE/passwordHashing/className'] = $newMethods['BE'];
1032 }
1033 if ($newMethods['FE'] !== Argon2iPasswordHash::class) {
1034 $newConfig['FE/passwordHashing/className'] = $newMethods['FE'];
1035 }
1036 if (!empty($newConfig)) {
1037 $confManager->setLocalConfigurationValuesByPathValuePairs($newConfig);
1038 }
1039 $confManager->removeLocalConfigurationKeysByPath($configsToRemove);
1040 $this->throwConfigurationChangedException();
1041 }
1042 }