[BUGFIX] Check for string before using strlen
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / Controller / StepController.php
1 <?php
2 namespace TYPO3\CMS\Install\Controller;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2013 Christian Kuhn <lolli@schwarzbu.ch>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28
29 /**
30 * Install step controller, dispatcher class of step actions.
31 */
32 class StepController extends AbstractController {
33
34 /**
35 * @var array List of valid action names that need authentication. Order is important!
36 */
37 protected $authenticationActions = array(
38 'environmentAndFolders',
39 'databaseConnect',
40 'databaseSelect',
41 'databaseData',
42 'defaultConfiguration',
43 );
44
45 /**
46 * @var array List of obsolete configuration options in LocalConfiguration to be removed
47 */
48 protected $obsoleteLocalConfigurationSettings = array(
49 // #34092
50 'BE/forceCharset',
51 // #26519
52 'BE/loginLabels',
53 // #44506
54 'BE/loginNews',
55 // #52013
56 'BE/TSconfigConditions',
57 // #30613
58 'BE/useOnContextMenuHandler',
59 // #48179
60 'EXT/em_mirrorListURL',
61 'EXT/em_wsdlURL',
62 // #43094
63 'EXT/extList',
64 // #35877
65 'EXT/extList_FE',
66 // #41813
67 'EXT/noEdit',
68 // #26090
69 'FE/defaultTypoScript_editorcfg',
70 'FE/defaultTypoScript_editorcfg.',
71 // #25099
72 'FE/simulateStaticDocuments',
73 // #52011
74 'GFX/im_combine_filename',
75 // #52088
76 'GFX/im_imvMaskState',
77 // #22687
78 'GFX/gdlib_2',
79 // #52012
80 'GFX/im_mask_temp_ext_noloss',
81 // #52088
82 'GFX/im_negate_mask',
83 // #52010
84 'GFX/im_no_effects',
85 // #18431
86 'GFX/noIconProc',
87 // #17606
88 'GFX/TTFLocaleConv',
89 // #39164
90 'SYS/additionalAllowedClassPrefixes',
91 // #27689
92 'SYS/caching/cacheBackends',
93 'SYS/caching/cacheFrontends',
94 // #38414
95 'SYS/extCache',
96 // #35923
97 'SYS/multiplyDBfieldSize',
98 // #46993
99 'SYS/T3instID',
100 );
101
102 /**
103 * Index action acts a a dispatcher to different steps
104 *
105 * @throws Exception
106 * @return void
107 */
108 public function execute() {
109 $this->loadBaseExtensions();
110 $this->initializeObjectManager();
111
112 // Warning: Order of these methods is security relevant and interferes with different access
113 // conditions (new/existing installation). See the single method comments for details.
114 $this->outputInstallToolNotEnabledMessageIfNeeded();
115 $this->migrateLocalconfToLocalConfigurationIfNeeded();
116 $this->outputInstallToolPasswordNotSetMessageIfNeeded();
117 $this->executeOrOutputFirstInstallStepIfNeeded();
118 $this->removeObsoleteLocalConfigurationSettings();
119 $this->generateEncryptionKeyIfNeeded();
120 $this->configureBackendLoginSecurity();
121 $this->configureSaltedpasswords();
122 $this->initializeSession();
123 $this->checkSessionToken();
124 $this->checkSessionLifetime();
125 $this->loginIfRequested();
126 $this->outputLoginFormIfNotAuthorized();
127 $this->executeSpecificStep();
128 $this->outputSpecificStep();
129 $this->redirectToTool();
130 }
131
132 /**
133 * Execute a step action if requested. If executed, a redirect is done, so
134 * the next request will render step one again if needed or initiate a
135 * request to test the next step.
136 *
137 * @throws Exception
138 * @return void
139 */
140 protected function executeSpecificStep() {
141 $action = $this->getAction();
142 $postValues = $this->getPostValues();
143 if ($action && isset($postValues['set']) && $postValues['set'] === 'execute') {
144 $stepAction = $this->getActionInstance($action);
145 $stepAction->setAction($action);
146 $stepAction->setToken($this->generateTokenForAction($action));
147 $stepAction->setPostValues($this->getPostValues());
148 $messages = $stepAction->execute();
149 $this->addSessionMessages($messages);
150 $this->redirect();
151 }
152 }
153
154 /**
155 * Render a specific step. Fallback to first step if none is given.
156 * The according step is instantiated and 'needsExecution' is called. If
157 * it needs execution, the step will be rendered, otherwise a redirect
158 * to test the next step is initiated.
159 *
160 * @return void
161 */
162 protected function outputSpecificStep() {
163 $action = $this->getAction();
164 if ($action === '') {
165 // First step action
166 list($action) = $this->authenticationActions;
167 }
168 $stepAction = $this->getActionInstance($action);
169 $stepAction->setAction($action);
170 $stepAction->setController('step');
171 $stepAction->setToken($this->generateTokenForAction($action));
172 $stepAction->setPostValues($this->getPostValues());
173
174 try {
175 // needsExecution() may throw a RedirectException to communicate that it changed
176 // configuration parameters and need an application reload.
177 $needsExecution = $stepAction->needsExecution();
178 } catch (Exception\RedirectException $e) {
179 $this->redirect();
180 }
181
182 if ($needsExecution) {
183 $stepAction->setMessages($this->session->getMessagesAndFlush());
184 $this->output($stepAction->handle());
185 } else {
186 // Redirect to next step if there are any
187 $currentPosition = array_keys($this->authenticationActions, $action, TRUE);
188 $nextAction = array_slice($this->authenticationActions, $currentPosition[0] + 1, 1);
189 if (!empty($nextAction)) {
190 $this->redirect('', $nextAction[0]);
191 }
192 }
193 }
194
195 /**
196 * Instantiate a specific action class
197 *
198 * @param string $action Action to instantiate
199 * @throws Exception
200 * @return \TYPO3\CMS\Install\Controller\Action\Step\StepInterface
201 */
202 protected function getActionInstance($action) {
203 $this->validateAuthenticationAction($action);
204 $actionClass = ucfirst($action);
205 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $stepAction */
206 $stepAction = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\' . $actionClass);
207 if (!($stepAction instanceof Action\Step\StepInterface)) {
208 throw new Exception(
209 $action . ' does non implement StepInterface',
210 1371303903
211 );
212 }
213 return $stepAction;
214 }
215
216 /**
217 * If the last step was reached and none needs execution, a redirect
218 * to call the tool controller is initiated.
219 *
220 * @return void
221 */
222 protected function redirectToTool() {
223 $this->redirect('tool');
224 }
225
226 /**
227 * "Silent" upgrade very early in step installer, before rendering step 1:
228 * If typo3conf and typo3conf/localconf.php exist, but no typo3conf/LocalConfiguration,
229 * create LocalConfiguration.php / AdditionalConfiguration.php from localconf.php
230 * Might throw exception if typo3conf directory is not writable.
231 *
232 * @return void
233 */
234 protected function migrateLocalconfToLocalConfigurationIfNeeded() {
235 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
236 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
237
238 $localConfigurationFileLocation = $configurationManager->getLocalConfigurationFileLocation();
239 $localConfigurationFileExists = is_file($localConfigurationFileLocation);
240 $localConfFileLocation = PATH_typo3conf . 'localconf.php';
241 $localConfFileExists = is_file($localConfFileLocation);
242
243 if (is_dir(PATH_typo3conf) && $localConfFileExists && !$localConfigurationFileExists) {
244 $localConfContent = file($localConfFileLocation);
245
246 // Line array for the three categories: localConfiguration, db settings, additionalConfiguration
247 $typo3ConfigurationVariables = array();
248 $typo3DatabaseVariables = array();
249 $additionalConfiguration = array();
250 foreach ($localConfContent as $line) {
251 $line = trim($line);
252 $matches = array();
253 // Convert extList to array
254 if (
255 preg_match('/^\\$TYPO3_CONF_VARS\\[\'EXT\'\\]\\[\'extList\'\\] *={1} *\'(.+)\';{1}/', $line, $matches) === 1
256 || preg_match('/^\\$GLOBALS\\[\'TYPO3_CONF_VARS\'\\]\\[\'EXT\'\\]\\[\'extList\'\\] *={1} *\'(.+)\';{1}/', $line, $matches) === 1
257 ) {
258 $extListAsArray = GeneralUtility::trimExplode(',', $matches[1], TRUE);
259 $typo3ConfigurationVariables[] = '$TYPO3_CONF_VARS[\'EXT\'][\'extListArray\'] = ' . var_export($extListAsArray, TRUE) . ';';
260 } elseif (
261 preg_match('/^\\$TYPO3_CONF_VARS.+;{1}/', $line, $matches) === 1
262 ) {
263 $typo3ConfigurationVariables[] = $matches[0];
264 } elseif (
265 preg_match('/^\\$GLOBALS\\[\'TYPO3_CONF_VARS\'\\].+;{1}/', $line, $matches) === 1
266 ) {
267 $lineWithoutGlobals = str_replace('$GLOBALS[\'TYPO3_CONF_VARS\']', '$TYPO3_CONF_VARS', $matches[0]);
268 $typo3ConfigurationVariables[] = $lineWithoutGlobals;
269 } elseif (
270 preg_match('/^\\$typo_db.+;{1}/', $line, $matches) === 1
271 ) {
272 eval($matches[0]);
273 if (isset($typo_db_host)) {
274 $typo3DatabaseVariables['host'] = $typo_db_host;
275 } elseif (isset($typo_db)) {
276 $typo3DatabaseVariables['database'] = $typo_db;
277 } elseif (isset($typo_db_username)) {
278 $typo3DatabaseVariables['username'] = $typo_db_username;
279 } elseif (isset($typo_db_password)) {
280 $typo3DatabaseVariables['password'] = $typo_db_password;
281 } elseif (isset($typo_db_extTableDef_script)) {
282 $typo3DatabaseVariables['extTablesDefinitionScript'] = $typo_db_extTableDef_script;
283 }
284 unset($typo_db_host, $typo_db, $typo_db_username, $typo_db_password, $typo_db_extTableDef_script);
285 } elseif (
286 strlen($line) > 0 && preg_match('/^\\/\\/.+|^#.+|^<\\?php$|^<\\?$|^\\?>$/', $line, $matches) === 0
287 ) {
288 $additionalConfiguration[] = $line;
289 }
290 }
291
292 // Build new TYPO3_CONF_VARS array
293 $TYPO3_CONF_VARS = NULL;
294 // Issue #39434: Combining next two lines into one triggers a weird issue in some PHP versions
295 $evalData = implode(LF, $typo3ConfigurationVariables);
296 eval($evalData);
297
298 // Add db settings to array
299 $TYPO3_CONF_VARS['DB'] = $typo3DatabaseVariables;
300 $TYPO3_CONF_VARS = \TYPO3\CMS\Core\Utility\ArrayUtility::sortByKeyRecursive($TYPO3_CONF_VARS);
301
302 // Write out new LocalConfiguration file
303 $configurationManager->writeLocalConfiguration($TYPO3_CONF_VARS);
304
305 // Write out new AdditionalConfiguration file
306 if (sizeof($additionalConfiguration) > 0) {
307 $configurationManager->writeAdditionalConfiguration($additionalConfiguration);
308 } else {
309 @unlink($configurationManager->getAdditionalConfigurationFileLocation());
310 }
311
312 // Move localconf.php to localconf.obsolete.php
313 rename($localConfFileLocation, PATH_site . 'typo3conf/localconf.obsolete.php');
314
315 // Perform a reload to self, so bootstrap now uses new LocalConfiguration.php
316 $this->redirect();
317 }
318 }
319
320 /**
321 * Some settings in LocalConfiguration vanished in DefaultConfiguration
322 * and have no impact on the core anymore.
323 * To keep the configuration clean, those old settings are just silently
324 * removed from LocalConfiguration if set.
325 *
326 * @return void
327 */
328 protected function removeObsoleteLocalConfigurationSettings() {
329 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
330 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
331 $removed = $configurationManager->removeLocalConfigurationKeysByPath($this->obsoleteLocalConfigurationSettings);
332 // If something was changed: Trigger a reload to have new values in next request
333 if ($removed) {
334 $this->redirect();
335 }
336 }
337
338 /**
339 * The first install step has a special standing and needs separate handling:
340 * At this point no directory exists (no typo3conf, no typo3temp), so we can
341 * not start the session handling (that stores the install tool session within typo3temp).
342 * This also means, we can not start the token handling for CSRF protection. This
343 * is no real problem, since no local configuration or other security relevant
344 * information was created yet.
345 *
346 * So, if no typo3conf directory exists yet, the first step is just rendered, or
347 * executed if called so. After that, a redirect is initiated to proceed with
348 * other tasks.
349 *
350 * @return void
351 */
352 protected function executeOrOutputFirstInstallStepIfNeeded() {
353 $postValues = $this->getPostValues();
354
355 $wasExecuted= FALSE;
356 $errorMessagesFromExecute = array();
357 if (isset($postValues['action'])
358 && $postValues['action'] === 'environmentAndFolders'
359 ) {
360 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
361 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
362 $errorMessagesFromExecute = $action->execute();
363 $wasExecuted = TRUE;
364 }
365
366 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
367 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
368
369 try {
370 // needsExecution() may throw a RedirectException to communicate that it changed
371 // configuration parameters and need an application reload.
372 $needsExecution = $action->needsExecution();
373 } catch (Exception\RedirectException $e) {
374 $this->redirect();
375 }
376
377 if (!is_dir(PATH_typo3conf)
378 || count($errorMessagesFromExecute) > 0
379 || $needsExecution
380 ) {
381 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
382 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
383 $action->setController('step');
384 $action->setAction('environmentAndFolders');
385 if (count($errorMessagesFromExecute) > 0) {
386 $action->setMessages($errorMessagesFromExecute);
387 }
388 $this->output($action->handle());
389 }
390
391 if ($wasExecuted) {
392 $this->redirect();
393 }
394 }
395
396 /**
397 * "Silent" upgrade: The encryption key is crucial for securing form tokens
398 * and the whole TYPO3 link rendering later on. A random key is set here in
399 * LocalConfiguration if it does not exist yet. This might possible happen
400 * during upgrading and will happen during first install.
401 *
402 * @return void
403 */
404 protected function generateEncryptionKeyIfNeeded() {
405 if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'])) {
406 $randomKey = GeneralUtility::getRandomHexString(96);
407 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
408 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
409 $configurationManager->setLocalConfigurationValueByPath('SYS/encryptionKey', $randomKey);
410 $this->redirect();
411 }
412 }
413
414 /**
415 * "Silent" upgrade: Backend login security is set to rsa if rsaauth
416 * is installed (but not used) otherwise the default value "normal" has to be used.
417 *
418 * @return void
419 */
420 protected function configureBackendLoginSecurity() {
421 if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('rsaauth')
422 && $GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel'] !== 'rsa')
423 {
424 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
425 $configurationManager->setLocalConfigurationValueByPath('BE/loginSecurityLevel', 'rsa');
426 $this->redirect();
427 } elseif (!\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('rsaauth')
428 && $GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel'] !== 'normal'
429 ) {
430 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
431 $configurationManager->setLocalConfigurationValueByPath('BE/loginSecurityLevel', 'normal');
432 $this->redirect();
433 }
434 }
435
436 /**
437 * "Silent" upgrade: Check the settings for saltedpasswords extension to
438 * load it as a required extension.
439 *
440 * @return void
441 */
442 protected function configureSaltedpasswords() {
443 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
444 $defaultConfiguration = $configurationManager->getDefaultConfiguration();
445 $defaultExtensionConfiguration = unserialize($defaultConfiguration['EXT']['extConf']['saltedpasswords']);
446 $extensionConfiguration = @unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['saltedpasswords']);
447 if (is_array($extensionConfiguration) && !empty($extensionConfiguration)) {
448 if (isset($extensionConfiguration['BE.']['enabled'])) {
449 if ($extensionConfiguration['BE.']['enabled']) {
450 unset($extensionConfiguration['BE.']['enabled']);
451 } else {
452 $extensionConfiguration['BE.'] = $defaultExtensionConfiguration['BE.'];
453 }
454 $configurationManager->setLocalConfigurationValueByPath(
455 'EXT/extConf/saltedpasswords',
456 serialize($extensionConfiguration)
457 );
458 $this->redirect();
459 }
460 } else {
461 $configurationManager->setLocalConfigurationValueByPath(
462 'EXT/extConf/saltedpasswords',
463 serialize($defaultExtensionConfiguration)
464 );
465 $this->redirect();
466 }
467 }
468 }
469 ?>