Passed
Push — master ( b15de1...c90927 )
by
unknown
29:44 queued 13:01
created

migrateMailSettingsToSendmail()   A

Complexity

Conditions 3
Paths 6

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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