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