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