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