SilentConfigurationUpgradeService.php 45.1 KB
Newer Older
1
<?php
2

3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
7
8
 * 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.
9
 *
10
11
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
14
 * The TYPO3 project - inspiring people to share!
 */
15

16
17
namespace TYPO3\CMS\Install\Service;

18
use TYPO3\CMS\Core\Configuration\ConfigurationManager;
19
use TYPO3\CMS\Core\Crypto\PasswordHashing\Argon2idPasswordHash;
20
21
22
23
24
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;
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
32
33
34
35
36

/**
 * 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.
 *
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
43
44
class SilentConfigurationUpgradeService
{
    /**
45
     * @var ConfigurationManager
46
     */
47
    protected $configurationManager;
48

49
50
51
52
53
54
55
56
    /**
     * List of obsolete configuration options in LocalConfiguration to be removed
     * Example:
     *    // #forge-ticket
     *    'BE/somesetting',
     *
     * @var array
     */
57
    protected $obsoleteLocalConfigurationSettings = [
58
59
        // #72400
        'BE/spriteIconGenerator_handler',
60
61
        // #72417
        'SYS/lockingMode',
62
63
64
65
        // #72473
        'FE/secureFormmail',
        'FE/strictFormmail',
        'FE/formmailMaxAttachmentSize',
66
67
68
        // #72337
        'SYS/t3lib_cs_utils',
        'SYS/t3lib_cs_convMethod',
69
70
        // #72604
        'SYS/maxFileNameLength',
71
72
        // #72602
        'BE/unzip_path',
73
74
        // #72615
        'BE/notificationPrefix',
75
76
77
        // #72616
        'BE/XCLASS',
        'FE/XCLASS',
78
79
        // #43085
        'GFX/image_processing',
80
81
82
83
84
85
86
        // #70056
        'SYS/curlUse',
        'SYS/curlProxyNTLM',
        'SYS/curlProxyServer',
        'SYS/curlProxyTunnel',
        'SYS/curlProxyUserPass',
        'SYS/curlTimeout',
87
88
        // #75355
        'BE/niceFlexFormXMLtags',
Christian Kuhn's avatar
Christian Kuhn committed
89
90
91
        'BE/compactFlexFormXML',
        // #75625
        'SYS/clearCacheSystem',
92
        // #77411
93
94
        'SYS/caching/cacheConfigurations/extbase_typo3dbbackend_tablecolumns',
        // #77460
95
96
97
        'SYS/caching/cacheConfigurations/extbase_typo3dbbackend_queries',
        // #79513
        'FE/lockHashKeyWords',
98
99
        'BE/lockHashKeyWords',
        // #78835
100
101
        'SYS/cookieHttpOnly',
        // #71095
102
103
104
        'BE/lang',
        // #80050
        'FE/cHashIncludePageId',
105
106
107
        // #80711
        'FE/noPHPscriptInclude',
        'FE/maxSessionDataSize',
108
109
110
        // #82162
        'SYS/enable_errorDLOG',
        'SYS/enable_exceptionDLOG',
111
112
        // #82377
        'EXT/allowSystemInstall',
113
114
115
        // #82421
        'SYS/sqlDebug',
        'SYS/no_pconnect',
116
        'SYS/setDBinit',
117
        'SYS/dbClientCompress',
118
119
        // #82430
        'SYS/syslogErrorReporting',
120
121
122
123
124
        // #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',
125
126
        // #82438
        'SYS/enableDeprecationLog',
127
128
        // #82680
        'GFX/png_truecolor',
129
130
        // #82803
        'FE/content_doktypes',
131
        // #83081
132
133
134
        'BE/fileExtensions',
        // #83768
        'SYS/doNotCheckReferer',
135
136
137
        // #83878
        'SYS/isInitialInstallationInProgress',
        'SYS/isInitialDatabaseImportDone',
138
139
        // #84810
        'BE/explicitConfirmationOfTranslation',
140
        // #87482
141
142
143
        'EXT/extConf',
        // #87767
        'SYS/recursiveDomainSearch',
144
145
146
147
148
149
        // #88376
        'FE/pageNotFound_handling',
        'FE/pageNotFound_handling_statheader',
        'FE/pageNotFound_handling_accessdeniedheader',
        'FE/pageUnavailable_handling',
        'FE/pageUnavailable_handling_statheader',
150
        // #88458
151
152
153
        'FE/get_url_id_token',
        // #88500
        'BE/RTE_imageStorageDir',
154
155
156
        // #89645
        'SYS/systemLog',
        'SYS/systemLogLevel',
157
158
        // #91974
        'FE/IPmaskMountGroups',
159
160
        // #87301
        'SYS/cookieSecure',
161
162
        // #92940
        'BE/lockBeUserToDBmounts',
163
164
        // #92941
        'BE/enabledBeUserIPLock',
165
166
        // #94312
        'BE/loginSecurityLevel',
167
168
169
        'FE/loginSecurityLevel',
        // #94871
        'SYS/features/form.legacyUploadMimeTypes',
170
171
        // #96550
        'SYS/USdateFormat',
172
173
        // #96982
        'EXT/allowGlobalInstall',
174
175
        // #96988
        'EXT/allowLocalInstall',
176
177
        // #97265
        'BE/explicitADmode',
178
179
180
        // 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',
181
    ];
182

183
    public function __construct(ConfigurationManager $configurationManager)
184
    {
185
        $this->configurationManager = $configurationManager;
186
    }
187

188
189
    /**
     * Executed configuration upgrades. Single upgrade methods must throw a
190
     * ConfigurationChangedException if something was written to LocalConfiguration.
191
192
     *
     * @throws ConfigurationChangedException
193
194
195
196
     */
    public function execute()
    {
        $this->generateEncryptionKeyIfNeeded();
197
        $this->migrateImageProcessorSetting();
198
        $this->transferHttpSettings();
199
200
        $this->disableImageMagickDetailSettingsIfImageMagickIsDisabled();
        $this->setImageMagickDetailSettings();
201
        $this->migrateThumbnailsPngSetting();
202
        $this->migrateLockSslSetting();
203
        $this->migrateDatabaseConnectionSettings();
204
        $this->migrateDatabaseConnectionCharset();
205
        $this->migrateDatabaseDriverOptions();
206
        $this->migrateLangDebug();
207
        $this->migrateCacheHashOptions();
208
        $this->migrateExceptionErrors();
209
        $this->migrateDisplayErrorsSetting();
210
        $this->migrateSaltedPasswordsSettings();
211
        $this->migrateCachingFrameworkCaches();
212
        $this->migrateMailSettingsToSendmail();
213
        $this->migrateMailSmtpEncryptSetting();
214

215
        // Should run at the end to prevent obsolete settings are removed before migration
216
        $this->removeObsoleteLocalConfigurationSettings();
217
    }
218

219
220
221
222
223
    /**
     * 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.
224
225
     *
     * @throws ConfigurationChangedException
226
227
228
229
     */
    protected function removeObsoleteLocalConfigurationSettings()
    {
        $removed = $this->configurationManager->removeLocalConfigurationKeysByPath($this->obsoleteLocalConfigurationSettings);
230

231
232
        // If something was changed: Trigger a reload to have new values in next request
        if ($removed) {
233
            $this->throwConfigurationChangedException();
234
235
        }
    }
236

237
238
239
240
241
    /**
     * 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 possible happen
     * during upgrading and will happen during first install.
242
243
     *
     * @throws ConfigurationChangedException
244
245
246
247
248
     */
    protected function generateEncryptionKeyIfNeeded()
    {
        try {
            $currentValue = $this->configurationManager->getLocalConfigurationValueByPath('SYS/encryptionKey');
249
        } catch (MissingArrayPathException $e) {
250
251
252
            // If an exception is thrown, the value is not set in LocalConfiguration
            $currentValue = '';
        }
253

254
        if (empty($currentValue)) {
Christian Futterlieb's avatar
Christian Futterlieb committed
255
            $randomKey = GeneralUtility::makeInstance(Random::class)->generateRandomHexString(96);
256
            $this->configurationManager->setLocalConfigurationValueByPath('SYS/encryptionKey', $randomKey);
257
            $this->throwConfigurationChangedException();
258
259
        }
    }
260

261
    /**
262
     * Parse old curl and HTTP options and set new HTTP options, related to Guzzle
263
264
     *
     * @throws ConfigurationChangedException
265
     */
266
    protected function transferHttpSettings()
267
    {
268
269
270
271
272
        $changed = false;
        $newParameters = [];
        $obsoleteParameters = [];

        // Remove / migrate options to new options
273
        try {
274
275
276
            // 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';
277
278
        } catch (MissingArrayPathException $e) {
            // Migration done already
279
        }
280
281
282
        try {
            $newParameters['HTTP/version'] = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/protocol_version');
            $obsoleteParameters[] = 'HTTP/protocol_version';
283
284
        } catch (MissingArrayPathException $e) {
            // Migration done already
285
286
287
288
        }
        try {
            $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_verify_host');
            $obsoleteParameters[] = 'HTTP/ssl_verify_host';
289
290
        } catch (MissingArrayPathException $e) {
            // Migration done already
291
292
293
294
295
        }
        try {
            $legacyUserAgent = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/userAgent');
            $newParameters['HTTP/headers/User-Agent'] = $legacyUserAgent;
            $obsoleteParameters[] = 'HTTP/userAgent';
296
297
        } catch (MissingArrayPathException $e) {
            // Migration done already
298
        }
299

300
        // Redirects
301
        try {
302
303
            $legacyFollowRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/follow_redirects');
            $obsoleteParameters[] = 'HTTP/follow_redirects';
304
        } catch (MissingArrayPathException $e) {
305
            $legacyFollowRedirects = '';
306
307
        }
        try {
308
309
            $legacyMaximumRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/max_redirects');
            $obsoleteParameters[] = 'HTTP/max_redirects';
310
        } catch (MissingArrayPathException $e) {
311
            $legacyMaximumRedirects = '';
312
        }
313
314
315
        try {
            $legacyStrictRedirects = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/strict_redirects');
            $obsoleteParameters[] = 'HTTP/strict_redirects';
316
        } catch (MissingArrayPathException $e) {
317
            $legacyStrictRedirects = '';
318
        }
319

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
        // 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 LocalConfiguration.php
            if (empty($newParameters['HTTP/allow_redirects'])) {
                unset($newParameters['HTTP/allow_redirects']);
            }
        }

        // Migrate Proxy settings
338
        try {
339
340
341
            // Currently without protocol or port
            $legacyProxyHost = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_host');
            $obsoleteParameters[] = 'HTTP/proxy_host';
342
        } catch (MissingArrayPathException $e) {
343
            $legacyProxyHost = '';
344
345
        }
        try {
346
347
            $legacyProxyPort = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_port');
            $obsoleteParameters[] = 'HTTP/proxy_port';
348
        } catch (MissingArrayPathException $e) {
349
            $legacyProxyPort = '';
350
        }
351
352
353
        try {
            $legacyProxyUser = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_user');
            $obsoleteParameters[] = 'HTTP/proxy_user';
354
        } catch (MissingArrayPathException $e) {
355
356
357
358
359
            $legacyProxyUser = '';
        }
        try {
            $legacyProxyPassword = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_password');
            $obsoleteParameters[] = 'HTTP/proxy_password';
360
        } catch (MissingArrayPathException $e) {
361
362
363
364
365
366
            $legacyProxyPassword = '';
        }
        // Auth Scheme: Basic, digest etc.
        try {
            $legacyProxyAuthScheme = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_auth_scheme');
            $obsoleteParameters[] = 'HTTP/proxy_auth_scheme';
367
        } catch (MissingArrayPathException $e) {
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
            $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';
388
        } catch (MissingArrayPathException $e) {
389
            $legacySslVerifyPeer = '';
390
        }
391

392
        // Directory holding multiple Certificate Authority files
393
        try {
394
395
            $legacySslCaPath = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_capath');
            $obsoleteParameters[] = 'HTTP/ssl_capath';
396
        } catch (MissingArrayPathException $e) {
397
            $legacySslCaPath = '';
398
        }
399
        // Certificate Authority file to verify the peer with (use when ssl_verify_peer is TRUE)
400
        try {
401
402
            $legacySslCaFile = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_cafile');
            $obsoleteParameters[] = 'HTTP/ssl_cafile';
403
        } catch (MissingArrayPathException $e) {
404
405
406
407
408
409
410
411
            $legacySslCaFile = '';
        }
        if ($legacySslVerifyPeer !== '') {
            if ($legacySslCaFile !== '' && $legacySslCaPath !== '') {
                $newParameters['HTTP/verify'] = $legacySslCaPath . $legacySslCaFile;
            } elseif ((bool)$legacySslVerifyPeer === false) {
                $newParameters['HTTP/verify'] = false;
            }
412
        }
413
414
415
416
417
418

        // SSL Key + Passphrase
        // Name of a file containing local certificate
        try {
            $legacySslLocalCert = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_local_cert');
            $obsoleteParameters[] = 'HTTP/ssl_local_cert';
419
        } catch (MissingArrayPathException $e) {
420
421
422
423
424
425
426
            $legacySslLocalCert = '';
        }

        // Passphrase with which local certificate was encoded
        try {
            $legacySslPassphrase = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/ssl_passphrase');
            $obsoleteParameters[] = 'HTTP/ssl_passphrase';
427
        } catch (MissingArrayPathException $e) {
428
429
430
431
432
433
434
            $legacySslPassphrase = '';
        }

        if ($legacySslLocalCert !== '') {
            if ($legacySslPassphrase !== '') {
                $newParameters['HTTP/ssl_key'] = [
                    $legacySslLocalCert,
435
                    $legacySslPassphrase,
436
437
438
439
440
441
442
443
444
445
446
447
448
                ];
            } 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);
449
450
451
            $changed = true;
        }
        if ($changed) {
452
            $this->throwConfigurationChangedException();
453
454
        }
    }
455

456
457
458
459
460
461
462
    /**
     * 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.
463
464
     *
     * @throws ConfigurationChangedException
465
466
467
     */
    protected function disableImageMagickDetailSettingsIfImageMagickIsDisabled()
    {
468
        $changedValues = [];
469
        try {
470
            $currentEnabledValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_enabled');
471
        } catch (MissingArrayPathException $e) {
472
            $currentEnabledValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_enabled');
473
        }
474

475
        try {
476
            $currentPathValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_path');
477
        } catch (MissingArrayPathException $e) {
478
            $currentPathValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_path');
479
        }
480

481
482
        try {
            $currentImageFileExtValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/imagefile_ext');
483
        } catch (MissingArrayPathException $e) {
484
485
            $currentImageFileExtValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/imagefile_ext');
        }
486

487
488
        try {
            $currentThumbnailsValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/thumbnails');
489
        } catch (MissingArrayPathException $e) {
490
491
            $currentThumbnailsValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/thumbnails');
        }
492

493
494
        if (!$currentEnabledValue) {
            if ($currentPathValue != '') {
495
                $changedValues['GFX/processor_path'] = '';
496
497
498
499
500
501
502
503
504
505
            }
            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);
506
            $this->throwConfigurationChangedException();
507
508
        }
    }
509

510
511
512
513
514
515
516
    /**
     * 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.
517
518
     *
     * @throws ConfigurationChangedException
519
520
521
     */
    protected function setImageMagickDetailSettings()
    {
522
        $changedValues = [];
523
        try {
524
            $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor');
525
        } catch (MissingArrayPathException $e) {
526
            $currentProcessorValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor');
527
        }
528

529
        try {
530
            $currentProcessorMaskValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_allowTemporaryMasksAsPng');
531
        } catch (MissingArrayPathException $e) {
532
            $currentProcessorMaskValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_allowTemporaryMasksAsPng');
533
        }
534

535
        try {
536
            $currentProcessorEffectsValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_effects');
537
        } catch (MissingArrayPathException $e) {
538
            $currentProcessorEffectsValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_effects');
539
        }
540
541

        if ((string)$currentProcessorValue !== '') {
542
543
544
545
            if (!is_bool($currentProcessorEffectsValue)) {
                $changedValues['GFX/processor_effects'] = (int)$currentProcessorEffectsValue > 0;
            }

546
547
            if ($currentProcessorMaskValue != 0) {
                $changedValues['GFX/processor_allowTemporaryMasksAsPng'] = 0;
548
549
550
551
            }
        }
        if (!empty($changedValues)) {
            $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
552
            $this->throwConfigurationChangedException();
553
554
        }
    }
555

556
557
558
    /**
     * Migrate the definition of the image processor from the configuration value
     * im_version_5 to the setting processor.
559
560
     *
     * @throws ConfigurationChangedException
561
562
563
     */
    protected function migrateImageProcessorSetting()
    {
564
565
        $changedSettings = [];
        $settingsToRename = [
566
567
568
569
570
571
572
573
574
575
576
            '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',
577
        ];
578
579
580
581
582
583

        foreach ($settingsToRename as $oldPath => $newPath) {
            try {
                $value = $this->configurationManager->getLocalConfigurationValueByPath($oldPath);
                $this->configurationManager->setLocalConfigurationValueByPath($newPath, $value);
                $changedSettings[$oldPath] = true;
584
            } catch (MissingArrayPathException $e) {
585
586
587
588
589
590
591
592
593
594
595
596
597
598
                // 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;
599
600
601
602
            $this->configurationManager->setLocalConfigurationValueByPath(
                'GFX/processor_allowUpscaling',
                $newProcessorValue
            );
603
604
605
606
607
        }

        if (!empty($changedSettings['GFX/im_noFramePrepended'])) {
            $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_noFramePrepended');
            $newProcessorValue = !$currentProcessorValue;
608
609
610
611
            $this->configurationManager->setLocalConfigurationValueByPath(
                'GFX/processor_allowFrameSelection',
                $newProcessorValue
            );
612
613
614
615
616
        }

        if (!empty($changedSettings['GFX/im_mask_temp_ext_gif'])) {
            $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_mask_temp_ext_gif');
            $newProcessorValue = !$currentProcessorValue;
617
618
619
620
            $this->configurationManager->setLocalConfigurationValueByPath(
                'GFX/processor_allowTemporaryMasksAsPng',
                $newProcessorValue
            );
621
622
623
624
        }

        if (!empty(array_filter($changedSettings))) {
            $this->configurationManager->removeLocalConfigurationKeysByPath(array_keys($changedSettings));
625
            $this->throwConfigurationChangedException();
626
627
628
        }
    }

629
630
631
    /**
     * Throw exception after configuration change to trigger a redirect.
     *
632
     * @throws ConfigurationChangedException
633
     */
634
    protected function throwConfigurationChangedException()
635
    {
636
        throw new ConfigurationChangedException(
637
638
639
640
            'Configuration updated, reload needed',
            1379024938
        );
    }
641
642
643

    /**
     * Migrate the configuration value thumbnails_png to a boolean value.
644
645
     *
     * @throws ConfigurationChangedException
646
     */
647
648
    protected function migrateThumbnailsPngSetting()
    {
649
        $changedValues = [];
650
651
        try {
            $currentThumbnailsPngValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/thumbnails_png');
652
        } catch (MissingArrayPathException $e) {
653
654
655
656
657
658
659
660
            $currentThumbnailsPngValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/thumbnails_png');
        }

        if (is_int($currentThumbnailsPngValue) && $currentThumbnailsPngValue > 0) {
            $changedValues['GFX/thumbnails_png'] = true;
        }
        if (!empty($changedValues)) {
            $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
661
            $this->throwConfigurationChangedException();
662
663
        }
    }
664
665
666

    /**
     * Migrate the configuration setting BE/lockSSL to boolean if set in the LocalConfiguration.php file
667
668
     *
     * @throws ConfigurationChangedException
669
670
671
672
673
674
675
676
     */
    protected function migrateLockSslSetting()
    {
        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);
677
                $this->throwConfigurationChangedException();
678
            }
679
        } catch (MissingArrayPathException $e) {
680
681
682
            // no change inside the LocalConfiguration.php found, so nothing needs to be modified
        }
    }
683
684
685

    /**
     * Move the database connection settings to a "Default" connection
686
687
     *
     * @throws ConfigurationChangedException
688
689
690
     */
    protected function migrateDatabaseConnectionSettings()
    {
691
        $confManager = $this->configurationManager;
692

693
694
        $newSettings = [];
        $removeSettings = [];
695

696
697
698
699
        try {
            $value = $confManager->getLocalConfigurationValueByPath('DB/username');
            $removeSettings[] = 'DB/username';
            $newSettings['DB/Connections/Default/user'] = $value;
700
        } catch (MissingArrayPathException $e) {
701
702
            // Old setting does not exist, do nothing
        }
703

704
        try {
Christian Kuhn's avatar
Christian Kuhn committed
705
            $value = $confManager->getLocalConfigurationValueByPath('DB/password');
706
707
            $removeSettings[] = 'DB/password';
            $newSettings['DB/Connections/Default/password'] = $value;
708
        } catch (MissingArrayPathException $e) {
709
710
711
712
713
714
715
            // Old setting does not exist, do nothing
        }

        try {
            $value = $confManager->getLocalConfigurationValueByPath('DB/host');
            $removeSettings[] = 'DB/host';
            $newSettings['DB/Connections/Default/host'] = $value;
716
        } catch (MissingArrayPathException $e) {
717
718
719
720
721
722
723
            // Old setting does not exist, do nothing
        }

        try {
            $value = $confManager->getLocalConfigurationValueByPath('DB/port');
            $removeSettings[] = 'DB/port';
            $newSettings['DB/Connections/Default/port'] = $value;
724
        } catch (MissingArrayPathException $e) {
725
726
727
728
729
730
731
732
733
            // 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;
734
            }
735
        } catch (MissingArrayPathException $e) {
736
            // Old setting does not exist, do nothing
737
738
        }

739
740
741
742
        try {
            $value = $confManager->getLocalConfigurationValueByPath('DB/database');
            $removeSettings[] = 'DB/database';
            $newSettings['DB/Connections/Default/dbname'] = $value;
743
        } catch (MissingArrayPathException $e) {
744
745
746
747
748
749
750
751
752
753
            // 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,
                ];
754
            }
755
        } catch (MissingArrayPathException $e) {
756
            // Old setting does not exist, do nothing
757
758
        }

759
760
761
762
763
764
        try {
            $value = (bool)$confManager->getLocalConfigurationValueByPath('SYS/no_pconnect');
            $removeSettings[] = 'SYS/no_pconnect';
            if (!$value) {
                $newSettings['DB/Connections/Default/persistentConnection'] = true;
            }
765
        } catch (MissingArrayPathException $e) {
766
            // Old setting does not exist, do nothing
767
768
        }

769
770
771
772
        try {
            $value = $confManager->getLocalConfigurationValueByPath('SYS/setDBinit');
            $removeSettings[] = 'SYS/setDBinit';
            $newSettings['DB/Connections/Default/initCommands'] = $value;
773
        } catch (MissingArrayPathException $e) {
774
            // Old setting does not exist, do nothing
775
776
        }

777
778
        try {
            $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/charset');
779
        } catch (MissingArrayPathException $e) {
780
781
            // If there is no charset option yet, add it.
            $newSettings['DB/Connections/Default/charset'] = 'utf8';
782
        }
783

784
785
        try {
            $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/driver');
786
        } catch (MissingArrayPathException $e) {
787
788
            // Use the mysqli driver by default if no value has been provided yet
            $newSettings['DB/Connections/Default/driver'] = 'mysqli';
789
        }
790

791
792
793
794
795
796
797
798
799
800
        // 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)) {
801
            $this->throwConfigurationChangedException();
802
803
        }
    }
804
805
806
807

    /**
     * Migrate the configuration setting DB/Connections/Default/charset to 'utf8' as
     * 'utf-8' is not supported by all MySQL versions.
808
809
     *
     * @throws ConfigurationChangedException
810
811
812
813
814
815
816
817
818
     */
    protected function migrateDatabaseConnectionCharset()
    {
        $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');
819
                $this->throwConfigurationChangedException();
820
            }
821
        } catch (MissingArrayPathException $e) {
822
823
824
            // no incompatible charset configuration found, so nothing needs to be modified
        }
    }
825
826
827

    /**
     * Migrate the configuration setting DB/Connections/Default/driverOptions to array type.
828
829
     *
     * @throws ConfigurationChangedException
830
831
832
833
834
835
836
837
838
839
840
     */
    protected function migrateDatabaseDriverOptions()
    {
        $confManager = $this->configurationManager;
        try {
            $options = $confManager->getLocalConfigurationValueByPath('DB/Connections/Default/driverOptions');
            if (!is_array($options)) {
                $confManager->setLocalConfigurationValueByPath(
                    'DB/Connections/Default/driverOptions',
                    ['flags' => (int)$options]
                );
841
                $this->throwConfigurationChangedException();
842
            }
843
        } catch (MissingArrayPathException $e) {
844
845
846
            // no driver options found, nothing needs to be modified
        }
    }
847
848
849

    /**
     * Migrate the configuration setting BE/lang/debug if set in the LocalConfiguration.php file
850
851
     *
     * @throws ConfigurationChangedException
852
853
854
855
856
857
858
859
860
     */
    protected function migrateLangDebug()
    {
        $confManager = $this->configurationManager;
        try {
            $currentOption = $confManager->getLocalConfigurationValueByPath('BE/lang/debug');
            // check if the current option is set and boolean
            if (isset($currentOption) && is_bool($currentOption)) {
                $confManager->setLocalConfigurationValueByPath('BE/languageDebug', $currentOption);
861
                $this->throwConfigurationChangedException();
862
            }
863
        } catch (MissingArrayPathException $e) {
864
865
866
            // no change inside the LocalConfiguration.php found, so nothing needs to be modified
        }
    }
867
868
869

    /**
     * Migrate single cache hash related options under "FE" into "FE/cacheHash"
870
871
     *
     * @throws ConfigurationChangedException
872
873
874
875
876
877
878
879
880
881
882
     */
    protected function migrateCacheHashOptions()
    {
        $confManager = $this->configurationManager;
        $removeSettings = [];
        $newSettings = [];

        try {
            $value = $confManager->getLocalConfigurationValueByPath('FE/cHashOnlyForParameters');
            $removeSettings[] = 'FE/cHashOnlyForParameters';
            $newSettings['FE/cacheHash/cachedParametersWhiteList'] = GeneralUtility::trimExplode(',', $value, true);
883
884
        } catch (MissingArrayPathException $e) {
            // Migration done already
885
886
887
888
889
890
        }

        try {
            $value = $confManager->getLocalConfigurationValueByPath('FE/cHashExcludedParameters');
            $removeSettings[] = 'FE/cHashExcludedParameters';
            $newSettings['FE/cacheHash/excludedParameters'] = GeneralUtility::trimExplode(',', $value, true);
891
892
        } catch (MissingArrayPathException $e) {
            // Migration done already
893
894
895
896
897
898
        }

        try {
            $value = $confManager->getLocalConfigurationValueByPath('FE/cHashRequiredParameters');
            $removeSettings[] = 'FE/cHashRequiredParameters';
            $newSettings['FE/cacheHash/requireCacheHashPresenceParameters'] = GeneralUtility::trimExplode(',', $value, true);
899
900
        } catch (MissingArrayPathException $e) {
            // Migration done already
901
902
903
904
905
906
907
908
909
910
        }

        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);
            }
911
912
        } catch (MissingArrayPathException $e) {
            // Migration done already
913
914
915
916
917
918
919
920
921
922
923
924
        }

        // 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)) {
925
            $this->throwConfigurationChangedException();
926
927
        }
    }
928
929
930

    /**
     * Migrate SYS/exceptionalErrors to not contain E_USER_DEPRECATED
931
932
     *
     * @throws ConfigurationChangedException
933
934
935
936
937
938
939
940
941
     */
    protected function migrateExceptionErrors()
    {
        $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);
942
                $this->throwConfigurationChangedException();
943
            }
944
        } catch (MissingArrayPathException $e) {
945
946
947
            // no change inside the LocalConfiguration.php found, so nothing needs to be modified
        }
    }
948
949
950

    /**
     * Migrate SYS/displayErrors to not contain 2
951
952
     *
     * @throws ConfigurationChangedException
953
954
955
956
957
958
959
960
961
     */
    protected function migrateDisplayErrorsSetting()
    {
        $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);
962
                $this->throwConfigurationChangedException();
963
964
965
966
967
            }
        } catch (MissingArrayPathException $e) {
            // no change inside the LocalConfiguration.php found, so nothing needs to be modified
        }
    }
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984

    /**
     * Migrate salted passwords extension configuration settings to BE/passwordHashing and FE/passwordHashing
     *
     * @throws ConfigurationChangedException
     */
    protected function migrateSaltedPasswordsSettings()
    {
        $confManager = $this->configurationManager;
        $configsToRemove = [];
        try {
            $extensionConfiguration = (array)$confManager->getLocalConfigurationValueByPath('EXTENSIONS/saltedpasswords');
            $configsToRemove[] = 'EXTENSIONS/saltedpasswords';
        } catch (MissingArrayPathException $e) {
            $extensionConfiguration = [];
        }
        // Migration already done
985
        if (empty($extensionConfiguration)) {
986
987
988
989
990
991
992
            return;
        }
        // Upgrade to 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 = [
993
            Argon2iPasswordHash::class,
994
            Argon2idPasswordHash::class,
995
996
997
            BcryptPasswordHash::class,
            Pbkdf2PasswordHash::class,
            PhpassPasswordHash::class,
998
999
1000
        ];
        $newMethods = [];
        foreach (['BE', 'FE'] as $mode) {