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