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