Files
cms-core/Classes/Service/SilentConfigurationUpgradeService.php

1209 lines
48 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Core\Service;
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
use TYPO3\CMS\Core\Configuration\Exception\SettingsWriteException;
use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2idPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2iPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\BcryptPasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashInterface;
use TYPO3\CMS\Core\Crypto\PasswordHashing\Pbkdf2PasswordHash;
use TYPO3\CMS\Core\Crypto\PasswordHashing\PhpassPasswordHash;
use TYPO3\CMS\Core\Crypto\Random;
use TYPO3\CMS\Core\Service\Exception\ConfigurationChangedException;
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Execute "silent" LocalConfiguration upgrades if needed.
*
* Some LocalConfiguration settings are obsolete or changed over time.
* This class handles upgrades of these settings. It is called by
* the step controller at an early point.
*
* Every change is encapsulated in one method and must throw a ConfigurationChangedException
* if new data is written to LocalConfiguration. This is caught by above
* step controller to initiate a redirect and start again with adapted configuration.
*
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
*/
class SilentConfigurationUpgradeService
{
/**
* List of obsolete configuration options in LocalConfiguration to be removed
* Example:
* // #forge-ticket
* 'BE/somesetting',
*/
protected array $obsoleteLocalConfigurationSettings = [
// #72400
'BE/spriteIconGenerator_handler',
// #72417
'SYS/lockingMode',
// #72473
'FE/secureFormmail',
'FE/strictFormmail',
'FE/formmailMaxAttachmentSize',
// #72337
'SYS/t3lib_cs_utils',
'SYS/t3lib_cs_convMethod',
// #72604
'SYS/maxFileNameLength',
// #72602
'BE/unzip_path',
// #72615
'BE/notificationPrefix',
// #72616
'BE/XCLASS',
'FE/XCLASS',
// #43085
'GFX/image_processing',
// #70056
'SYS/curlUse',
'SYS/curlProxyNTLM',
'SYS/curlProxyServer',
'SYS/curlProxyTunnel',
'SYS/curlProxyUserPass',
'SYS/curlTimeout',
// #75355
'BE/niceFlexFormXMLtags',
'BE/compactFlexFormXML',
// #75625
'SYS/clearCacheSystem',
// #77411
'SYS/caching/cacheConfigurations/extbase_typo3dbbackend_tablecolumns',
// #77460
'SYS/caching/cacheConfigurations/extbase_typo3dbbackend_queries',
// #79513
'FE/lockHashKeyWords',
'BE/lockHashKeyWords',
// #78835
'SYS/cookieHttpOnly',
// #71095
'BE/lang',
// #80050
'FE/cHashIncludePageId',
// #80711
'FE/noPHPscriptInclude',
'FE/maxSessionDataSize',
// #82162
'SYS/enable_errorDLOG',
'SYS/enable_exceptionDLOG',
// #82377
'EXT/allowSystemInstall',
// #82421
'SYS/sqlDebug',
'SYS/no_pconnect',
'SYS/setDBinit',
'SYS/dbClientCompress',
// #82430
'SYS/syslogErrorReporting',
// #82639
'SYS/enable_DLOG',
'SC_OPTIONS/t3lib/class.t3lib_userauth.php/writeDevLog',
'SC_OPTIONS/t3lib/class.t3lib_userauth.php/writeDevLogBE',
'SC_OPTIONS/t3lib/class.t3lib_userauth.php/writeDevLogFE',
// #82438
'SYS/enableDeprecationLog',
// #82680
'GFX/png_truecolor',
// #82803
'FE/content_doktypes',
// #83081
'BE/fileExtensions',
// #83768
'SYS/doNotCheckReferer',
// #83878
'SYS/isInitialInstallationInProgress',
'SYS/isInitialDatabaseImportDone',
// #84810
'BE/explicitConfirmationOfTranslation',
// #87482
'EXT/extConf',
// #87767
'SYS/recursiveDomainSearch',
// #88376
'FE/pageNotFound_handling',
'FE/pageNotFound_handling_statheader',
'FE/pageNotFound_handling_accessdeniedheader',
'FE/pageUnavailable_handling',
'FE/pageUnavailable_handling_statheader',
// #88458
'FE/get_url_id_token',
// #88500
'BE/RTE_imageStorageDir',
// #89645
'SYS/systemLog',
'SYS/systemLogLevel',
// #91974
'FE/IPmaskMountGroups',
// #87301
'SYS/cookieSecure',
// #92940
'BE/lockBeUserToDBmounts',
// #92941
'BE/enabledBeUserIPLock',
// #94312
'BE/loginSecurityLevel',
'FE/loginSecurityLevel',
// #94871
'SYS/features/form.legacyUploadMimeTypes',
// #96550
'SYS/USdateFormat',
// #96982
'EXT/allowGlobalInstall',
// #96988
'EXT/allowLocalInstall',
// #97265
'BE/explicitADmode',
// #98179
'BE/interfaces',
// Please note that further migrations in this file are kept in order to remove the setting at the very end
// #97797
'GFX/processor_path_lzw',
// #98503
'SYS/caching/cacheConfigurations/pagesection',
// #99075
'FE/defaultUserTSconfig',
// #101037
'BE/languageDebug',
'BE/lang/debug',
// #101793
'BE/checkStoredRecords',
'BE/checkStoredRecordsLoose',
// #101941
'GFX/thumbnails_png',
'GFX/gif_compress',
// #101950
'GFX/processor_allowTemporaryMasksAsPng',
// #102020
'GFX/gdlib_png',
// #102023
'SYS/features/security.usePasswordPolicyForFrontendUsers',
// #102113
'GFX/gdlib',
// #102146
'BE/flexformForceCDATA',
// #103752
'FE/addRootLineFields',
// #104104
'EXTENSIONS/indexed_search/debugMode',
// #101807
'BE/defaultUserTSconfig',
// #101799
'BE/defaultPageTSconfig',
// #107436
// symfony/translation loaders used now
'SYS/lang/parser',
// #107943
'BE/compressionLevel',
// #108055
'FE/compressionLevel',
// #108114
'FE/additionalAbsRefPrefixDirectories',
// #109959
'FE/cacheHash/fallbackToLegacyHash',
// #109986
'FE/addAllowedPaths',
];
public function __construct(private readonly ConfigurationManager $configurationManager) {}
/**
* Executed configuration upgrades. Single upgrade methods must throw a
* ConfigurationChangedException if something was written to LocalConfiguration.
*
* @throws ConfigurationChangedException
* @throws SettingsWriteException
*/
public function execute(): void
{
$this->generateEncryptionKeyIfNeeded();
$this->migrateImageProcessorSetting();
$this->transferHttpSettings();
$this->disableImageMagickDetailSettingsIfImageMagickIsDisabled();
$this->setImageMagickDetailSettings();
$this->removeDefaultColorspaceSettings();
$this->migrateLockSslSetting();
$this->migrateDatabaseConnectionSettings();
$this->migrateDatabaseConnectionCharset();
$this->migrateDatabaseDriverOptions();
$this->migrateCacheHashOptions();
$this->migrateExceptionErrors();
$this->migrateDisplayErrorsSetting();
$this->migrateSaltedPasswordsSettings();
$this->migrateCachingFrameworkCaches();
$this->migrateMailSettingsToSendmail();
$this->migrateMailSmtpEncryptSetting();
$this->migrateVersionNumberInFileNameSetting();
$this->migrateLanguageSettings();
// Should run at the end to prevent obsolete settings are removed before migration
$this->removeObsoleteLocalConfigurationSettings();
}
/**
* Throw exception after configuration change to trigger a redirect.
*
* @throws ConfigurationChangedException
*/
protected function throwConfigurationChangedException(): void
{
throw new ConfigurationChangedException(
'Configuration updated, reload needed',
1379024938
);
}
/**
* Some settings in LocalConfiguration vanished in DefaultConfiguration
* and have no impact on the core anymore.
* To keep the configuration clean, those old settings are just silently
* removed from LocalConfiguration if set.
*
* @throws ConfigurationChangedException
*/
protected function removeObsoleteLocalConfigurationSettings(): void
{
$removed = $this->configurationManager->removeLocalConfigurationKeysByPath($this->obsoleteLocalConfigurationSettings);
// If something was changed: Trigger reload to have new values in next request
if ($removed) {
$this->throwConfigurationChangedException();
}
}
/**
* The encryption key is crucial for securing form tokens
* and the whole TYPO3 link rendering later on. A random key is set here in
* LocalConfiguration if it does not exist yet. This might happen
* during upgrading and will happen during first install.
*
* @throws ConfigurationChangedException
*/
protected function generateEncryptionKeyIfNeeded(): void
{
try {
$currentValue = $this->configurationManager->getLocalConfigurationValueByPath('SYS/encryptionKey');
} catch (MissingArrayPathException) {
// If an exception is thrown, the value is not set in LocalConfiguration
$currentValue = '';
}
if (empty($currentValue)) {
$randomKey = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(96);
$this->configurationManager->setLocalConfigurationValueByPath('SYS/encryptionKey', $randomKey);
$this->throwConfigurationChangedException();
}
}
/**
* Parse old curl and HTTP options and set new HTTP options, related to Guzzle
*
* @throws ConfigurationChangedException
*/
protected function transferHttpSettings(): void
{
$changed = false;
$newParameters = [];
$obsoleteParameters = [];
// Remove / migrate options to new options
try {
// Check if the adapter option is set, if so, set it to the parameters that are obsolete
$this->configurationManager->getLocalConfigurationValueByPath('HTTP/adapter');
$obsoleteParameters[] = 'HTTP/adapter';
} catch (MissingArrayPathException) {
// Migration done already
}
try {
$newParameters['HTTP/version'] = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/protocol_version');
$obsoleteParameters[] = 'HTTP/protocol_version';
} catch (MissingArrayPathException) {
// Migration done already
}
try {
$this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_verify_host');
$obsoleteParameters[] = 'HTTP/ssl_verify_host';
} catch (MissingArrayPathException) {
// Migration done already
}
try {
$legacyUserAgent = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/userAgent');
$newParameters['HTTP/headers/User-Agent'] = $legacyUserAgent;
$obsoleteParameters[] = 'HTTP/userAgent';
} catch (MissingArrayPathException) {
// Migration done already
}
// Redirects
try {
$legacyFollowRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/follow_redirects');
$obsoleteParameters[] = 'HTTP/follow_redirects';
} catch (MissingArrayPathException) {
$legacyFollowRedirects = '';
}
try {
$legacyMaximumRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/max_redirects');
$obsoleteParameters[] = 'HTTP/max_redirects';
} catch (MissingArrayPathException) {
$legacyMaximumRedirects = '';
}
try {
$legacyStrictRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/strict_redirects');
$obsoleteParameters[] = 'HTTP/strict_redirects';
} catch (MissingArrayPathException) {
$legacyStrictRedirects = '';
}
// Check if redirects have been disabled
if ($legacyFollowRedirects !== '' && (bool)$legacyFollowRedirects === false) {
$newParameters['HTTP/allow_redirects'] = false;
} elseif ($legacyMaximumRedirects !== '' || $legacyStrictRedirects !== '') {
$newParameters['HTTP/allow_redirects'] = [];
if ($legacyMaximumRedirects !== '' && (int)$legacyMaximumRedirects !== 5) {
$newParameters['HTTP/allow_redirects']['max'] = (int)$legacyMaximumRedirects;
}
if ($legacyStrictRedirects !== '' && (bool)$legacyStrictRedirects === true) {
$newParameters['HTTP/allow_redirects']['strict'] = true;
}
// defaults are used, no need to set the option in system/settings.php
if (empty($newParameters['HTTP/allow_redirects'])) {
unset($newParameters['HTTP/allow_redirects']);
}
}
// Migrate Proxy settings
try {
// Currently without protocol or port
$legacyProxyHost = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_host');
$obsoleteParameters[] = 'HTTP/proxy_host';
} catch (MissingArrayPathException) {
$legacyProxyHost = '';
}
try {
$legacyProxyPort = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_port');
$obsoleteParameters[] = 'HTTP/proxy_port';
} catch (MissingArrayPathException) {
$legacyProxyPort = '';
}
try {
$legacyProxyUser = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_user');
$obsoleteParameters[] = 'HTTP/proxy_user';
} catch (MissingArrayPathException) {
$legacyProxyUser = '';
}
try {
$legacyProxyPassword = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_password');
$obsoleteParameters[] = 'HTTP/proxy_password';
} catch (MissingArrayPathException) {
$legacyProxyPassword = '';
}
// Auth Scheme: Basic, digest etc.
try {
$legacyProxyAuthScheme = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_auth_scheme');
$obsoleteParameters[] = 'HTTP/proxy_auth_scheme';
} catch (MissingArrayPathException) {
$legacyProxyAuthScheme = '';
}
if ($legacyProxyHost !== '') {
$proxy = 'http://';
if ($legacyProxyAuthScheme !== '' && $legacyProxyUser !== '' && $legacyProxyPassword !== '') {
$proxy .= $legacyProxyUser . ':' . $legacyProxyPassword . '@';
}
$proxy .= $legacyProxyHost;
if ($legacyProxyPort !== '') {
$proxy .= ':' . $legacyProxyPort;
}
$newParameters['HTTP/proxy'] = $proxy;
}
// Verify peers
// see http://docs.guzzlephp.org/en/latest/request-options.html#verify
try {
$legacySslVerifyPeer = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_verify_peer');
$obsoleteParameters[] = 'HTTP/ssl_verify_peer';
} catch (MissingArrayPathException) {
$legacySslVerifyPeer = '';
}
// Directory holding multiple Certificate Authority files
try {
$legacySslCaPath = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_capath');
$obsoleteParameters[] = 'HTTP/ssl_capath';
} catch (MissingArrayPathException) {
$legacySslCaPath = '';
}
// Certificate Authority file to verify the peer with (use when ssl_verify_peer is TRUE)
try {
$legacySslCaFile = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_cafile');
$obsoleteParameters[] = 'HTTP/ssl_cafile';
} catch (MissingArrayPathException) {
$legacySslCaFile = '';
}
if ($legacySslVerifyPeer !== '') {
if ($legacySslCaFile !== '' && $legacySslCaPath !== '') {
$newParameters['HTTP/verify'] = $legacySslCaPath . $legacySslCaFile;
} elseif ((bool)$legacySslVerifyPeer === false) {
$newParameters['HTTP/verify'] = false;
}
}
// SSL Key + Passphrase
// Name of a file containing local certificate
try {
$legacySslLocalCert = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_local_cert');
$obsoleteParameters[] = 'HTTP/ssl_local_cert';
} catch (MissingArrayPathException) {
$legacySslLocalCert = '';
}
// Passphrase with which local certificate was encoded
try {
$legacySslPassphrase = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_passphrase');
$obsoleteParameters[] = 'HTTP/ssl_passphrase';
} catch (MissingArrayPathException) {
$legacySslPassphrase = '';
}
if ($legacySslLocalCert !== '') {
if ($legacySslPassphrase !== '') {
$newParameters['HTTP/ssl_key'] = [
$legacySslLocalCert,
$legacySslPassphrase,
];
} else {
$newParameters['HTTP/ssl_key'] = $legacySslLocalCert;
}
}
// Update the LocalConfiguration file if obsolete parameters or new parameters are set
if (!empty($obsoleteParameters)) {
$this->configurationManager->removeLocalConfigurationKeysByPath($obsoleteParameters);
$changed = true;
}
if (!empty($newParameters)) {
$this->configurationManager->setLocalConfigurationValuesByPathValuePairs($newParameters);
$changed = true;
}
if ($changed) {
$this->throwConfigurationChangedException();
}
}
/**
* Detail configuration of Image Magick settings must be cleared
* if Image Magick handling is disabled.
*
* "Configuration presets" in install tool is not type safe, so value
* comparisons here are not type safe too, to not trigger changes to
* LocalConfiguration again.
*
* @throws ConfigurationChangedException
*/
protected function disableImageMagickDetailSettingsIfImageMagickIsDisabled(): void
{
$changedValues = [];
try {
$currentEnabledValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_enabled');
} catch (MissingArrayPathException) {
$currentEnabledValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_enabled');
}
try {
$currentPathValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_path');
} catch (MissingArrayPathException) {
$currentPathValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_path');
}
try {
$currentImageFileExtValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/imagefile_ext');
} catch (MissingArrayPathException) {
$currentImageFileExtValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/imagefile_ext');
}
try {
$currentThumbnailsValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/thumbnails');
} catch (MissingArrayPathException) {
$currentThumbnailsValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/thumbnails');
}
if (!$currentEnabledValue) {
if ($currentPathValue != '') {
$changedValues['GFX/processor_path'] = '';
}
if ($currentImageFileExtValue !== 'gif,jpg,jpeg,png') {
$changedValues['GFX/imagefile_ext'] = 'gif,jpg,jpeg,png';
}
if ($currentThumbnailsValue != 0) {
$changedValues['GFX/thumbnails'] = 0;
}
}
if (!empty($changedValues)) {
$this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
$this->throwConfigurationChangedException();
}
}
/**
* Detail configuration of Image Magick and Graphics Magick settings
* depending on main values.
*
* "Configuration presets" in install tool is not type safe, so value
* comparisons here are not type safe too, to not trigger changes to
* LocalConfiguration again.
*
* @throws ConfigurationChangedException
*/
protected function setImageMagickDetailSettings(): void
{
$changedValues = [];
try {
$currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor');
} catch (MissingArrayPathException) {
$currentProcessorValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor');
}
try {
$currentProcessorEffectsValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_effects');
} catch (MissingArrayPathException) {
$currentProcessorEffectsValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_effects');
}
if ((string)$currentProcessorValue !== '') {
if (!is_bool($currentProcessorEffectsValue)) {
$changedValues['GFX/processor_effects'] = (int)$currentProcessorEffectsValue > 0;
}
}
if (!empty($changedValues)) {
$this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
$this->throwConfigurationChangedException();
}
}
/**
* Migrate the definition of the image processor from the configuration value
* im_version_5 to the setting processor.
*
* @throws ConfigurationChangedException
*/
protected function migrateImageProcessorSetting(): void
{
$changedSettings = [];
$settingsToRename = [
'GFX/im' => 'GFX/processor_enabled',
'GFX/im_version_5' => 'GFX/processor',
'GFX/im_v5effects' => 'GFX/processor_effects',
'GFX/im_path' => 'GFX/processor_path',
'GFX/im_path_lzw' => 'GFX/processor_path_lzw',
'GFX/im_mask_temp_ext_gif' => 'GFX/processor_allowTemporaryMasksAsPng',
'GFX/im_noScaleUp' => 'GFX/processor_allowUpscaling',
'GFX/im_noFramePrepended' => 'GFX/processor_allowFrameSelection',
'GFX/im_stripProfileCommand' => 'GFX/processor_stripColorProfileCommand',
'GFX/im_useStripProfileByDefault' => 'GFX/processor_stripColorProfileByDefault',
'GFX/colorspace' => 'GFX/processor_colorspace',
];
foreach ($settingsToRename as $oldPath => $newPath) {
try {
$value = $this->configurationManager->getLocalConfigurationValueByPath($oldPath);
$this->configurationManager->setLocalConfigurationValueByPath($newPath, $value);
$changedSettings[$oldPath] = true;
} catch (MissingArrayPathException) {
// If an exception is thrown, the value is not set in LocalConfiguration
$changedSettings[$oldPath] = false;
}
}
if (!empty($changedSettings['GFX/im_version_5'])) {
$currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_version_5');
$newProcessorValue = $currentProcessorValue === 'gm' ? 'GraphicsMagick' : 'ImageMagick';
$this->configurationManager->setLocalConfigurationValueByPath('GFX/processor', $newProcessorValue);
}
if (!empty($changedSettings['GFX/im_noScaleUp'])) {
$currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_noScaleUp');
$newProcessorValue = !$currentProcessorValue;
$this->configurationManager->setLocalConfigurationValueByPath(
'GFX/processor_allowUpscaling',
$newProcessorValue
);
}
if (!empty($changedSettings['GFX/im_noFramePrepended'])) {
$currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_noFramePrepended');
$newProcessorValue = !$currentProcessorValue;
$this->configurationManager->setLocalConfigurationValueByPath(
'GFX/processor_allowFrameSelection',
$newProcessorValue
);
}
if (!empty($changedSettings['GFX/im_mask_temp_ext_gif'])) {
$currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_mask_temp_ext_gif');
$newProcessorValue = !$currentProcessorValue;
$this->configurationManager->setLocalConfigurationValueByPath(
'GFX/processor_allowTemporaryMasksAsPng',
$newProcessorValue
);
}
if (!empty(array_filter($changedSettings))) {
$this->configurationManager->removeLocalConfigurationKeysByPath(array_keys($changedSettings));
$this->throwConfigurationChangedException();
}
}
/**
* Migrate the configuration setting BE/lockSSL to boolean if set in the system/settings.php file
*
* @throws ConfigurationChangedException
*/
protected function migrateLockSslSetting(): void
{
try {
$currentOption = $this->configurationManager->getLocalConfigurationValueByPath('BE/lockSSL');
// check if the current option is an integer/string and if it is active
if (!is_bool($currentOption) && (int)$currentOption > 0) {
$this->configurationManager->setLocalConfigurationValueByPath('BE/lockSSL', true);
$this->throwConfigurationChangedException();
}
} catch (MissingArrayPathException) {
// no change inside the system/settings.php found, so nothing needs to be modified
}
}
/**
* Move the database connection settings to a "Default" connection
*
* @throws ConfigurationChangedException
*/
protected function migrateDatabaseConnectionSettings(): void
{
$confManager = $this->configurationManager;
$newSettings = [];
$removeSettings = [];
try {
$value = $confManager->getLocalConfigurationValueByPath('DB/username');
$removeSettings[] = 'DB/username';
$newSettings['DB/Connections/Default/user'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$value = $confManager->getLocalConfigurationValueByPath('DB/password');
$removeSettings[] = 'DB/password';
$newSettings['DB/Connections/Default/password'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$value = $confManager->getLocalConfigurationValueByPath('DB/host');
$removeSettings[] = 'DB/host';
$newSettings['DB/Connections/Default/host'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$value = $confManager->getLocalConfigurationValueByPath('DB/port');
$removeSettings[] = 'DB/port';
$newSettings['DB/Connections/Default/port'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$value = $confManager->getLocalConfigurationValueByPath('DB/socket');
$removeSettings[] = 'DB/socket';
// Remove empty socket connects
if (!empty($value)) {
$newSettings['DB/Connections/Default/unix_socket'] = $value;
}
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$value = $confManager->getLocalConfigurationValueByPath('DB/database');
$removeSettings[] = 'DB/database';
$newSettings['DB/Connections/Default/dbname'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$value = (bool)$confManager->getLocalConfigurationValueByPath('SYS/dbClientCompress');
$removeSettings[] = 'SYS/dbClientCompress';
if ($value) {
$newSettings['DB/Connections/Default/driverOptions'] = [
'flags' => MYSQLI_CLIENT_COMPRESS,
];
}
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$value = (bool)$confManager->getLocalConfigurationValueByPath('SYS/no_pconnect');
$removeSettings[] = 'SYS/no_pconnect';
if (!$value) {
$newSettings['DB/Connections/Default/persistentConnection'] = true;
}
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$value = $confManager->getLocalConfigurationValueByPath('SYS/setDBinit');
$removeSettings[] = 'SYS/setDBinit';
$newSettings['DB/Connections/Default/initCommands'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
try {
$confManager->getLocalConfigurationValueByPath('DB/Connections/Default/charset');
} catch (MissingArrayPathException) {
// If there is no charset option yet, add it.
$newSettings['DB/Connections/Default/charset'] = 'utf8';
}
try {
$confManager->getLocalConfigurationValueByPath('DB/Connections/Default/driver');
} catch (MissingArrayPathException) {
// Use the mysqli driver by default if no value has been provided yet
$newSettings['DB/Connections/Default/driver'] = 'mysqli';
}
// Add new settings and remove old ones
if (!empty($newSettings)) {
$confManager->setLocalConfigurationValuesByPathValuePairs($newSettings);
}
if (!empty($removeSettings)) {
$confManager->removeLocalConfigurationKeysByPath($removeSettings);
}
// Throw redirect if something was changed
if (!empty($newSettings) || !empty($removeSettings)) {
$this->throwConfigurationChangedException();
}
}
/**
* Migrate the configuration setting DB/Connections/Default/charset to 'utf8' as
* 'utf-8' is not supported by all MySQL versions.
*
* @throws ConfigurationChangedException
*/
protected function migrateDatabaseConnectionCharset(): void
{
$confManager = $this->configurationManager;
try {
$driver = $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/driver');
$charset = $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/charset');
if (in_array($driver, ['mysqli', 'pdo_mysql', 'drizzle_pdo_mysql'], true) && $charset === 'utf-8') {
$confManager->setLocalConfigurationValueByPath('DB/Connections/Default/charset', 'utf8');
$this->throwConfigurationChangedException();
}
} catch (MissingArrayPathException) {
// no incompatible charset configuration found, so nothing needs to be modified
}
}
/**
* Migrate the configuration setting DB/Connections/Default/driverOptions to array type.
*
* @throws ConfigurationChangedException
*/
protected function migrateDatabaseDriverOptions(): void
{
$confManager = $this->configurationManager;
try {
$options = $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/driverOptions');
if (!is_array($options)) {
$confManager->setLocalConfigurationValueByPath(
'DB/Connections/Default/driverOptions',
['flags' => (int)$options]
);
$this->throwConfigurationChangedException();
}
} catch (MissingArrayPathException) {
// no driver options found, nothing needs to be modified
}
}
/**
* Migrate single cache hash related options under "FE" into "FE/cacheHash"
*
* @throws ConfigurationChangedException
*/
protected function migrateCacheHashOptions(): void
{
$confManager = $this->configurationManager;
$removeSettings = [];
$newSettings = [];
try {
$value = $confManager->getLocalConfigurationValueByPath('FE/cHashOnlyForParameters');
$removeSettings[] = 'FE/cHashOnlyForParameters';
$newSettings['FE/cacheHash/cachedParametersWhiteList'] = GeneralUtility::trimExplode(',', $value, true);
} catch (MissingArrayPathException) {
// Migration done already
}
try {
$value = $confManager->getLocalConfigurationValueByPath('FE/cHashExcludedParameters');
$removeSettings[] = 'FE/cHashExcludedParameters';
$newSettings['FE/cacheHash/excludedParameters'] = GeneralUtility::trimExplode(',', $value, true);
} catch (MissingArrayPathException) {
// Migration done already
}
try {
$value = $confManager->getLocalConfigurationValueByPath('FE/cHashRequiredParameters');
$removeSettings[] = 'FE/cHashRequiredParameters';
$newSettings['FE/cacheHash/requireCacheHashPresenceParameters'] = GeneralUtility::trimExplode(',', $value, true);
} catch (MissingArrayPathException) {
// Migration done already
}
try {
$value = $confManager->getLocalConfigurationValueByPath('FE/cHashExcludedParametersIfEmpty');
$removeSettings[] = 'FE/cHashExcludedParametersIfEmpty';
if (trim($value) === '*') {
$newSettings['FE/cacheHash/excludeAllEmptyParameters'] = true;
} else {
$newSettings['FE/cacheHash/excludedParametersIfEmpty'] = GeneralUtility::trimExplode(',', $value, true);
}
} catch (MissingArrayPathException) {
// Migration done already
}
// Add new settings and remove old ones
if (!empty($newSettings)) {
$confManager->setLocalConfigurationValuesByPathValuePairs($newSettings);
}
if (!empty($removeSettings)) {
$confManager->removeLocalConfigurationKeysByPath($removeSettings);
}
// Throw redirect if something was changed
if (!empty($newSettings) || !empty($removeSettings)) {
$this->throwConfigurationChangedException();
}
}
/**
* Migrate SYS/exceptionalErrors to not contain E_USER_DEPRECATED
*
* @throws ConfigurationChangedException
*/
protected function migrateExceptionErrors(): void
{
$confManager = $this->configurationManager;
try {
$currentOption = (int)$confManager->getLocalConfigurationValueByPath('SYS/exceptionalErrors');
// make sure E_USER_DEPRECATED is not part of the exceptionalErrors
if ($currentOption & E_USER_DEPRECATED) {
$confManager->setLocalConfigurationValueByPath('SYS/exceptionalErrors', $currentOption & ~E_USER_DEPRECATED);
$this->throwConfigurationChangedException();
}
} catch (MissingArrayPathException) {
// no change inside the system/settings.php found, so nothing needs to be modified
}
}
/**
* Migrate SYS/displayErrors to not contain 2
*
* @throws ConfigurationChangedException
*/
protected function migrateDisplayErrorsSetting(): void
{
$confManager = $this->configurationManager;
try {
$currentOption = (int)$confManager->getLocalConfigurationValueByPath('SYS/displayErrors');
// make sure displayErrors is set to 2
if ($currentOption === 2) {
$confManager->setLocalConfigurationValueByPath('SYS/displayErrors', -1);
$this->throwConfigurationChangedException();
}
} catch (MissingArrayPathException) {
// no change inside the system/settings.php found, so nothing needs to be modified
}
}
/**
* Migrate salted passwords extension configuration settings to BE/passwordHashing and FE/passwordHashing
*
* @throws ConfigurationChangedException
*/
protected function migrateSaltedPasswordsSettings(): void
{
$confManager = $this->configurationManager;
$configsToRemove = [];
try {
$extensionConfiguration = (array)$confManager->getLocalConfigurationValueByPath('EXTENSIONS/saltedpasswords');
$configsToRemove[] = 'EXTENSIONS/saltedpasswords';
} catch (MissingArrayPathException) {
$extensionConfiguration = [];
}
// Migration already done
if (empty($extensionConfiguration)) {
return;
}
// Upgrade to the best available hash method. This is only done once since that code will no longer be reached
// after first migration because extConf and EXTENSIONS array entries are gone then. Thus, a manual selection
// to some different hash mechanism will not be touched again after first upgrade.
// Phpass is always available, so we have some last fallback if the others don't kick in
$okHashMethods = [
Argon2iPasswordHash::class,
Argon2idPasswordHash::class,
BcryptPasswordHash::class,
Pbkdf2PasswordHash::class,
PhpassPasswordHash::class,
];
$newMethods = [];
foreach (['BE', 'FE'] as $mode) {
foreach ($okHashMethods as $className) {
/** @var PasswordHashInterface $instance */
$instance = GeneralUtility::makeInstance($className);
if ($instance->isAvailable()) {
$newMethods[$mode] = $className;
break;
}
}
}
// We only need to write to LocalConfiguration if method is different from Argon2i in DefaultConfiguration
$newConfig = [];
if ($newMethods['BE'] !== Argon2iPasswordHash::class) {
$newConfig['BE/passwordHashing/className'] = $newMethods['BE'];
}
if ($newMethods['FE'] !== Argon2iPasswordHash::class) {
$newConfig['FE/passwordHashing/className'] = $newMethods['FE'];
}
if (!empty($newConfig)) {
$confManager->setLocalConfigurationValuesByPathValuePairs($newConfig);
}
$confManager->removeLocalConfigurationKeysByPath($configsToRemove);
$this->throwConfigurationChangedException();
}
/**
* Renames all SYS[caching][cache] configuration names to names without the prefix "cache_".
* see #88366
*
* @throws ConfigurationChangedException
*/
protected function migrateCachingFrameworkCaches(): void
{
$confManager = $this->configurationManager;
try {
$cacheConfigurations = (array)$confManager->getLocalConfigurationValueByPath('SYS/caching/cacheConfigurations');
$newConfig = [];
$hasBeenModified = false;
foreach ($cacheConfigurations as $identifier => $cacheConfiguration) {
if (str_starts_with($identifier, 'cache_')) {
$identifier = substr($identifier, 6);
$hasBeenModified = true;
}
$newConfig[$identifier] = $cacheConfiguration;
}
if ($hasBeenModified) {
$confManager->setLocalConfigurationValueByPath('SYS/caching/cacheConfigurations', $newConfig);
$this->throwConfigurationChangedException();
}
} catch (MissingArrayPathException) {
// no change inside the system/settings.php found, so nothing needs to be modified
}
}
/**
* Migrates "mail" to "sendmail" as "mail" (PHP's built-in mail() method) is not supported anymore
* with Symfony components.
* See #88643
*
* @throws ConfigurationChangedException
*/
protected function migrateMailSettingsToSendmail(): void
{
$confManager = $this->configurationManager;
try {
$transport = $confManager->getLocalConfigurationValueByPath('MAIL/transport');
if ($transport === 'mail') {
$confManager->setLocalConfigurationValueByPath('MAIL/transport', 'sendmail');
$confManager->setLocalConfigurationValueByPath('MAIL/transport_sendmail_command', (string)@ini_get('sendmail_path'));
$this->throwConfigurationChangedException();
}
} catch (MissingArrayPathException) {
// no change inside the system/settings.php found, so nothing needs to be modified
}
}
/**
* Migrates MAIL/transport_smtp_encrypt to a boolean value
* See #91070, #90295, #88643 and https://github.com/symfony/symfony/commit/5b8c4676d059
*
* @throws ConfigurationChangedException
*/
protected function migrateMailSmtpEncryptSetting(): void
{
$confManager = $this->configurationManager;
try {
$transport = $confManager->getLocalConfigurationValueByPath('MAIL/transport');
if ($transport === 'smtp') {
$encrypt = $confManager->getLocalConfigurationValueByPath('MAIL/transport_smtp_encrypt');
if (is_string($encrypt)) {
// SwiftMailer used 'tls' as identifier to connect with STARTTLS via SMTP (as usually used with port 587).
// See https://github.com/swiftmailer/swiftmailer/blob/v5.4.10/lib/classes/Swift/Transport/EsmtpTransport.php#L144
if ($encrypt === 'tls') {
// With TYPO3 v10 the MAIL/transport_smtp_encrypt option is passed as constructor parameter $tls to
// Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport
// $tls = true instructs to start a SMTPS connection that means SSL/TLS via SMTPS, not STARTTLS via SMTP.
// That means symfony/mailer will use STARTTLS when $tls = false or ($tls = null with port != 465) is passed.
// Actually symfony/mailer will use STARTTLS by default now.
// Due to the misleading name (transport_smtp_encrypt) we avoid to set the option to false, but rather remove it.
// Note: symfony/mailer provides no way to enforce STARTTLS usage, see https://github.com/symfony/symfony/commit/5b8c4676d059
$confManager->removeLocalConfigurationKeysByPath(['MAIL/transport_smtp_encrypt']);
} elseif ($encrypt === '') {
$confManager->setLocalConfigurationValueByPath('MAIL/transport_smtp_encrypt', false);
} else {
$confManager->setLocalConfigurationValueByPath('MAIL/transport_smtp_encrypt', true);
}
$this->throwConfigurationChangedException();
}
}
} catch (MissingArrayPathException) {
// no change inside the system/settings.php found, so nothing needs to be modified
}
}
/**
* Migrate [FE][versionNumberInFilename] to become a boolean flag
*
* @throws ConfigurationChangedException
*/
protected function migrateVersionNumberInFileNameSetting(): void
{
try {
$confManager = $this->configurationManager;
$currentOption = $confManager->getLocalConfigurationValueByPath('FE/versionNumberInFilename');
if ($currentOption === true) {
return;
}
if ($currentOption === 'embed') {
$confManager->setLocalConfigurationValueByPath('FE/versionNumberInFilename', true);
} else {
$confManager->removeLocalConfigurationKeysByPath(['FE/versionNumberInFilename']);
}
$this->throwConfigurationChangedException();
} catch (MissingArrayPathException) {
// no flag set, so nothing to be configured
}
}
/**
* Remove the colorspace setting if it's already the recommended default for a given processor
*
* @throws ConfigurationChangedException
*/
protected function removeDefaultColorspaceSettings(): void
{
try {
$confManager = $this->configurationManager;
$currentProcessor = $confManager->getLocalConfigurationValueByPath('GFX/processor');
$currentColorspace = $confManager->getLocalConfigurationValueByPath('GFX/processor_colorspace');
if ($currentProcessor === 'ImageMagick' && $currentColorspace === 'sRGB'
|| $currentProcessor === 'GraphicsMagick' && $currentColorspace === 'RGB') {
$confManager->removeLocalConfigurationKeysByPath(['GFX/processor_colorspace']);
$this->throwConfigurationChangedException();
}
} catch (MissingArrayPathException) {
// no change inside the system/settings.php found, so nothing needs to be modified
}
}
/**
* Migrate 'SYS'/'lang' related configuration settings to 'LANG'
*
* @throws ConfigurationChangedException
*/
protected function migrateLanguageSettings(): void
{
$confManager = $this->configurationManager;
$newSettings = [];
$removeSettings = [];
// Migrate SYS/lang/requireApprovedLocalizations => LANG/requireApprovedLocalizations
try {
$value = $confManager->getLocalConfigurationValueByPath('SYS/lang/requireApprovedLocalizations');
$removeSettings[] = 'SYS/lang/requireApprovedLocalizations';
$newSettings['LANG/requireApprovedLocalizations'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
// Migrate SYS/lang/format => LANG/format
try {
$value = $confManager->getLocalConfigurationValueByPath('SYS/lang/format');
$removeSettings[] = 'SYS/lang/format';
$newSettings['LANG/format'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
// Migrate EXTCONF/lang/availableLanguages => LANG/availableLocales
try {
$value = $confManager->getLocalConfigurationValueByPath('EXTCONF/lang/availableLanguages');
$removeSettings[] = 'EXTCONF/lang/availableLanguages';
$newSettings['LANG/availableLocales'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
// Migrate SYS/locallangXMLOverride => LANG/resourceOverrides
try {
$value = $confManager->getLocalConfigurationValueByPath('SYS/locallangXMLOverride');
$removeSettings[] = 'SYS/locallangXMLOverride';
$newSettings['LANG/resourceOverrides'] = $value;
} catch (MissingArrayPathException) {
// Old setting does not exist, do nothing
}
// Add new settings and remove old ones
if (!empty($newSettings)) {
$confManager->setLocalConfigurationValuesByPathValuePairs($newSettings);
}
if (!empty($removeSettings)) {
$confManager->removeLocalConfigurationKeysByPath($removeSettings);
}
// Throw redirect if something was changed
if (!empty($newSettings) || !empty($removeSettings)) {
$this->throwConfigurationChangedException();
}
}
}