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