[TASK] Get rid of ObjectManager in install tool
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Service / SilentConfigurationUpgradeService.php
1 <?php
2 namespace TYPO3\CMS\Install\Service;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
18 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Install\Controller\Exception\RedirectException;
21
22 /**
23 * Execute "silent" LocalConfiguration upgrades if needed.
24 *
25 * Some LocalConfiguration settings are obsolete or changed over time.
26 * This class handles upgrades of these settings. It is called by
27 * the step controller at an early point.
28 *
29 * Every change is encapsulated in one method an must throw a RedirectException
30 * if new data is written to LocalConfiguration. This is caught by above
31 * step controller to initiate a redirect and start again with adapted configuration.
32 */
33 class SilentConfigurationUpgradeService
34 {
35 /**
36 * @var \TYPO3\CMS\Core\Configuration\ConfigurationManager
37 */
38 protected $configurationManager;
39
40 /**
41 * List of obsolete configuration options in LocalConfiguration to be removed
42 * Example:
43 * // #forge-ticket
44 * 'BE/somesetting',
45 *
46 * @var array
47 */
48 protected $obsoleteLocalConfigurationSettings = array(
49 // #72367
50 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\AccessRightParametersUpdate',
51 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\BackendUserStartModuleUpdate',
52 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\Compatibility6ExtractionUpdate',
53 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\ContentTypesToTextMediaUpdate',
54 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\FileListIsStartModuleUpdate',
55 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\FilesReplacePermissionUpdate',
56 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\LanguageIsoCodeUpdate',
57 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\MediaceExtractionUpdate',
58 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\MigrateMediaToAssetsForTextMediaCe',
59 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\MigrateShortcutUrlsAgainUpdate',
60 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\OpenidExtractionUpdate',
61 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\PageShortcutParentUpdate',
62 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\ProcessedFileChecksumUpdate',
63 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\TableFlexFormToTtContentFieldsUpdate',
64 'INSTALL/wizardDone/TYPO3\\CMS\\Install\\Updates\\WorkspacesNotificationSettingsUpdate',
65 'INSTALL/wizardDone/TYPO3\\CMS\\Rtehtmlarea\\Hook\\Install\\DeprecatedRteProperties',
66 'INSTALL/wizardDone/TYPO3\\CMS\\Rtehtmlarea\\Hook\\Install\\RteAcronymButtonRenamedToAbbreviation',
67 // #72400
68 'BE/spriteIconGenerator_handler',
69 // #72417
70 'SYS/lockingMode',
71 // #72473
72 'FE/secureFormmail',
73 'FE/strictFormmail',
74 'FE/formmailMaxAttachmentSize',
75 // #72337
76 'SYS/t3lib_cs_utils',
77 'SYS/t3lib_cs_convMethod',
78 // #72604
79 'SYS/maxFileNameLength',
80 // #72602
81 'BE/unzip_path',
82 // #72615
83 'BE/notificationPrefix',
84 // #72616
85 'BE/XCLASS',
86 'FE/XCLASS',
87 // #43085
88 'GFX/image_processing',
89 );
90
91 public function __construct(ConfigurationManager $configurationManager = null)
92 {
93 $this->configurationManager = $configurationManager ?: GeneralUtility::makeInstance(ConfigurationManager::class);
94 }
95
96 /**
97 * Executed configuration upgrades. Single upgrade methods must throw a
98 * RedirectException if something was written to LocalConfiguration.
99 *
100 * @return void
101 */
102 public function execute()
103 {
104 $this->generateEncryptionKeyIfNeeded();
105 $this->configureBackendLoginSecurity();
106 $this->configureSaltedPasswords();
107 $this->setProxyAuthScheme();
108 $this->migrateImageProcessorSetting();
109 $this->transferDeprecatedCurlSettings();
110 $this->disableImageMagickDetailSettingsIfImageMagickIsDisabled();
111 $this->setImageMagickDetailSettings();
112 $this->removeObsoleteLocalConfigurationSettings();
113 }
114
115 /**
116 * Some settings in LocalConfiguration vanished in DefaultConfiguration
117 * and have no impact on the core anymore.
118 * To keep the configuration clean, those old settings are just silently
119 * removed from LocalConfiguration if set.
120 *
121 * @return void
122 */
123 protected function removeObsoleteLocalConfigurationSettings()
124 {
125 $removed = $this->configurationManager->removeLocalConfigurationKeysByPath($this->obsoleteLocalConfigurationSettings);
126
127 // If something was changed: Trigger a reload to have new values in next request
128 if ($removed) {
129 $this->throwRedirectException();
130 }
131 }
132
133 /**
134 * Backend login security is set to rsa if rsaauth
135 * is installed (but not used) otherwise the default value "normal" has to be used.
136 * This forces either 'normal' or 'rsa' to be set in LocalConfiguration.
137 *
138 * @return void
139 */
140 protected function configureBackendLoginSecurity()
141 {
142 $rsaauthLoaded = ExtensionManagementUtility::isLoaded('rsaauth');
143 try {
144 $currentLoginSecurityLevelValue = $this->configurationManager->getLocalConfigurationValueByPath('BE/loginSecurityLevel');
145 if ($rsaauthLoaded && $currentLoginSecurityLevelValue !== 'rsa') {
146 $this->configurationManager->setLocalConfigurationValueByPath('BE/loginSecurityLevel', 'rsa');
147 $this->throwRedirectException();
148 } elseif (!$rsaauthLoaded && $currentLoginSecurityLevelValue !== 'normal') {
149 $this->configurationManager->setLocalConfigurationValueByPath('BE/loginSecurityLevel', 'normal');
150 $this->throwRedirectException();
151 }
152 } catch (\RuntimeException $e) {
153 // If an exception is thrown, the value is not set in LocalConfiguration
154 $this->configurationManager->setLocalConfigurationValueByPath('BE/loginSecurityLevel', $rsaauthLoaded ? 'rsa' : 'normal');
155 $this->throwRedirectException();
156 }
157 }
158
159 /**
160 * Check the settings for salted passwords extension to load it as a required extension.
161 * Unset obsolete configuration options if given.
162 *
163 * @return void
164 */
165 protected function configureSaltedPasswords()
166 {
167 $defaultConfiguration = $this->configurationManager->getDefaultConfiguration();
168 $defaultExtensionConfiguration = unserialize($defaultConfiguration['EXT']['extConf']['saltedpasswords']);
169 try {
170 $extensionConfiguration = @unserialize($this->configurationManager->getLocalConfigurationValueByPath('EXT/extConf/saltedpasswords'));
171 } catch (\RuntimeException $e) {
172 $extensionConfiguration = array();
173 }
174 if (is_array($extensionConfiguration) && !empty($extensionConfiguration)) {
175 if (isset($extensionConfiguration['BE.']['enabled'])) {
176 if ($extensionConfiguration['BE.']['enabled']) {
177 unset($extensionConfiguration['BE.']['enabled']);
178 } else {
179 $extensionConfiguration['BE.'] = $defaultExtensionConfiguration['BE.'];
180 }
181 $this->configurationManager->setLocalConfigurationValueByPath(
182 'EXT/extConf/saltedpasswords',
183 serialize($extensionConfiguration)
184 );
185 $this->throwRedirectException();
186 }
187 } else {
188 $this->configurationManager->setLocalConfigurationValueByPath(
189 'EXT/extConf/saltedpasswords',
190 serialize($defaultExtensionConfiguration)
191 );
192 $this->throwRedirectException();
193 }
194 }
195
196 /**
197 * The encryption key is crucial for securing form tokens
198 * and the whole TYPO3 link rendering later on. A random key is set here in
199 * LocalConfiguration if it does not exist yet. This might possible happen
200 * during upgrading and will happen during first install.
201 *
202 * @return void
203 */
204 protected function generateEncryptionKeyIfNeeded()
205 {
206 try {
207 $currentValue = $this->configurationManager->getLocalConfigurationValueByPath('SYS/encryptionKey');
208 } catch (\RuntimeException $e) {
209 // If an exception is thrown, the value is not set in LocalConfiguration
210 $currentValue = '';
211 }
212
213 if (empty($currentValue)) {
214 $randomKey = GeneralUtility::getRandomHexString(96);
215 $this->configurationManager->setLocalConfigurationValueByPath('SYS/encryptionKey', $randomKey);
216 $this->throwRedirectException();
217 }
218 }
219
220 /**
221 * $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_auth_scheme'] must be either
222 * 'digest' or 'basic'. 'basic' is default in DefaultConfiguration, so the
223 * setting can be removed from LocalConfiguration if it is not set to 'digest'.
224 *
225 * @return void
226 */
227 protected function setProxyAuthScheme()
228 {
229 // Get current value from LocalConfiguration
230 try {
231 $currentValueInLocalConfiguration = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_auth_scheme');
232 } catch (\RuntimeException $e) {
233 // If an exception is thrown, the value is not set in LocalConfiguration, so we don't need to do anything
234 return;
235 }
236 if ($currentValueInLocalConfiguration !== 'digest') {
237 $this->configurationManager->removeLocalConfigurationKeysByPath(array('HTTP/proxy_auth_scheme'));
238 $this->throwRedirectException();
239 }
240 }
241
242 /**
243 * Parse old curl options and set new http ones instead
244 *
245 * @return void
246 */
247 protected function transferDeprecatedCurlSettings()
248 {
249 $changed = false;
250 try {
251 $curlProxyServer = $this->configurationManager->getLocalConfigurationValueByPath('SYS/curlProxyServer');
252 } catch (\RuntimeException $e) {
253 $curlProxyServer = '';
254 }
255 try {
256 $proxyHost = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_host');
257 } catch (\RuntimeException $e) {
258 $proxyHost = '';
259 }
260 if (!empty($curlProxyServer) && empty($proxyHost)) {
261 $curlProxy = rtrim(preg_replace('#^https?://#', '', $curlProxyServer), '/');
262 $proxyParts = GeneralUtility::revExplode(':', $curlProxy, 2);
263 $this->configurationManager->setLocalConfigurationValueByPath('HTTP/proxy_host', $proxyParts[0]);
264 $this->configurationManager->setLocalConfigurationValueByPath('HTTP/proxy_port', $proxyParts[1]);
265 $changed = true;
266 }
267
268 try {
269 $curlProxyUserPass = $this->configurationManager->getLocalConfigurationValueByPath('SYS/curlProxyUserPass');
270 } catch (\RuntimeException $e) {
271 $curlProxyUserPass = '';
272 }
273 try {
274 $proxyUser = $this->configurationManager->getLocalConfigurationValueByPath('HTTP/proxy_user');
275 } catch (\RuntimeException $e) {
276 $proxyUser = '';
277 }
278 if (!empty($curlProxyUserPass) && empty($proxyUser)) {
279 $userPassParts = explode(':', $curlProxyUserPass, 2);
280 $this->configurationManager->setLocalConfigurationValueByPath('HTTP/proxy_user', $userPassParts[0]);
281 $this->configurationManager->setLocalConfigurationValueByPath('HTTP/proxy_password', $userPassParts[1]);
282 $changed = true;
283 }
284
285 try {
286 $curlUse = $this->configurationManager->getLocalConfigurationValueByPath('SYS/curlUse');
287 } catch (\RuntimeException $e) {
288 $curlUse = '';
289 }
290 try {
291 $adapter = $this->configurationManager->getConfigurationValueByPath('HTTP/adapter');
292 } catch (\RuntimeException $e) {
293 $adapter = '';
294 }
295 if (!empty($curlUse) && $adapter !== 'curl') {
296 $GLOBALS['TYPO3_CONF_VARS']['HTTP']['adapter'] = 'curl';
297 $this->configurationManager->setLocalConfigurationValueByPath('HTTP/adapter', 'curl');
298 $changed = true;
299 }
300 if ($changed) {
301 $this->throwRedirectException();
302 }
303 }
304
305 /**
306 * Detail configuration of Image Magick settings must be cleared
307 * if Image Magick handling is disabled.
308 *
309 * "Configuration presets" in install tool is not type safe, so value
310 * comparisons here are not type safe too, to not trigger changes to
311 * LocalConfiguration again.
312 *
313 * @return void
314 */
315 protected function disableImageMagickDetailSettingsIfImageMagickIsDisabled()
316 {
317 $changedValues = array();
318 try {
319 $currentImValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_enabled');
320 } catch (\RuntimeException $e) {
321 $currentImValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_enabled');
322 }
323
324 try {
325 $currentImPathValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_path');
326 } catch (\RuntimeException $e) {
327 $currentImPathValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_path');
328 }
329
330 try {
331 $currentImPathLzwValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_path_lzw');
332 } catch (\RuntimeException $e) {
333 $currentImPathLzwValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_path_lzw');
334 }
335
336 try {
337 $currentImageFileExtValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/imagefile_ext');
338 } catch (\RuntimeException $e) {
339 $currentImageFileExtValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/imagefile_ext');
340 }
341
342 try {
343 $currentThumbnailsValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/thumbnails');
344 } catch (\RuntimeException $e) {
345 $currentThumbnailsValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/thumbnails');
346 }
347
348 if (!$currentImValue) {
349 if ($currentImPathValue != '') {
350 $changedValues['GFX/processor_path'] = '';
351 }
352 if ($currentImPathLzwValue != '') {
353 $changedValues['GFX/processor_path_lzw'] = '';
354 }
355 if ($currentImageFileExtValue !== 'gif,jpg,jpeg,png') {
356 $changedValues['GFX/imagefile_ext'] = 'gif,jpg,jpeg,png';
357 }
358 if ($currentThumbnailsValue != 0) {
359 $changedValues['GFX/thumbnails'] = 0;
360 }
361 }
362 if (!empty($changedValues)) {
363 $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
364 $this->throwRedirectException();
365 }
366 }
367
368 /**
369 * Detail configuration of Image Magick and Graphics Magick settings
370 * depending on main values.
371 *
372 * "Configuration presets" in install tool is not type safe, so value
373 * comparisons here are not type safe too, to not trigger changes to
374 * LocalConfiguration again.
375 *
376 * @return void
377 */
378 protected function setImageMagickDetailSettings()
379 {
380 $changedValues = array();
381 try {
382 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor');
383 } catch (\RuntimeException $e) {
384 $currentProcessorValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor');
385 }
386
387 try {
388 $currentProcessorMaskValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_allowTemporaryMasksAsPng');
389 } catch (\RuntimeException $e) {
390 $currentProcessorMaskValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_allowTemporaryMasksAsPng');
391 }
392
393 try {
394 $currentProcessorEffectsValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/processor_effects');
395 } catch (\RuntimeException $e) {
396 $currentProcessorEffectsValue = $this->configurationManager->getDefaultConfigurationValueByPath('GFX/processor_effects');
397 }
398
399 if ((string)$currentProcessorValue !== '') {
400 if ($currentProcessorMaskValue != 0) {
401 $changedValues['GFX/processor_allowTemporaryMasksAsPng'] = 0;
402 }
403 if ($currentProcessorValue === 'GraphicsMagick') {
404 if ($currentProcessorEffectsValue != -1) {
405 $changedValues['GFX/processor_effects'] = -1;
406 }
407 }
408 }
409 if (!empty($changedValues)) {
410 $this->configurationManager->setLocalConfigurationValuesByPathValuePairs($changedValues);
411 $this->throwRedirectException();
412 }
413 }
414
415 /**
416 * Migrate the definition of the image processor from the configuration value
417 * im_version_5 to the setting processor.
418 *
419 * @return void
420 */
421 protected function migrateImageProcessorSetting()
422 {
423 $changedSettings = array();
424 $settingsToRename = array(
425 'GFX/im' => 'GFX/processor_enabled',
426 'GFX/im_version_5' => 'GFX/processor',
427 'GFX/im_v5effects' => 'GFX/processor_effects',
428 'GFX/im_path' => 'GFX/processor_path',
429 'GFX/im_path_lzw' => 'GFX/processor_path_lzw',
430 'GFX/im_mask_temp_ext_gif' => 'GFX/processor_allowTemporaryMasksAsPng',
431 'GFX/im_noScaleUp' => 'GFX/processor_allowUpscaling',
432 'GFX/im_noFramePrepended' => 'GFX/processor_allowFrameSelection',
433 'GFX/im_stripProfileCommand' => 'GFX/processor_stripColorProfileCommand',
434 'GFX/im_useStripProfileByDefault' => 'GFX/processor_stripColorProfileByDefault',
435 'GFX/colorspace' => 'GFX/processor_colorspace',
436 );
437
438 foreach ($settingsToRename as $oldPath => $newPath) {
439 try {
440 $value = $this->configurationManager->getLocalConfigurationValueByPath($oldPath);
441 $this->configurationManager->setLocalConfigurationValueByPath($newPath, $value);
442 $changedSettings[$oldPath] = true;
443 } catch (\RuntimeException $e) {
444 // If an exception is thrown, the value is not set in LocalConfiguration
445 $changedSettings[$oldPath] = false;
446 }
447 }
448
449 if (!empty($changedSettings['GFX/im_version_5'])) {
450 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_version_5');
451 $newProcessorValue = $currentProcessorValue === 'gm' ? 'GraphicsMagick' : 'ImageMagick';
452 $this->configurationManager->setLocalConfigurationValueByPath('GFX/processor', $newProcessorValue);
453 }
454
455 if (!empty($changedSettings['GFX/im_noScaleUp'])) {
456 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_noScaleUp');
457 $newProcessorValue = !$currentProcessorValue;
458 $this->configurationManager->setLocalConfigurationValueByPath('GFX/processor_allowUpscaling', $newProcessorValue);
459 }
460
461 if (!empty($changedSettings['GFX/im_noFramePrepended'])) {
462 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_noFramePrepended');
463 $newProcessorValue = !$currentProcessorValue;
464 $this->configurationManager->setLocalConfigurationValueByPath('GFX/processor_allowFrameSelection', $newProcessorValue);
465 }
466
467 if (!empty($changedSettings['GFX/im_mask_temp_ext_gif'])) {
468 $currentProcessorValue = $this->configurationManager->getLocalConfigurationValueByPath('GFX/im_mask_temp_ext_gif');
469 $newProcessorValue = !$currentProcessorValue;
470 $this->configurationManager->setLocalConfigurationValueByPath('GFX/processor_allowTemporaryMasksAsPng', $newProcessorValue);
471 }
472
473 if (!empty(array_filter($changedSettings))) {
474 $this->configurationManager->removeLocalConfigurationKeysByPath(array_keys($changedSettings));
475 $this->throwRedirectException();
476 }
477 }
478
479 /**
480 * Throw exception after configuration change to trigger a redirect.
481 *
482 * @throws RedirectException
483 */
484 protected function throwRedirectException()
485 {
486 throw new RedirectException(
487 'Configuration updated, reload needed',
488 1379024938
489 );
490 }
491 }