[BUGFIX] Introduce failsafe view in install tool
[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 * Index action acts a a dispatcher to different steps
47 *
48 * Warning: Order of these methods is security relevant and interferes with different access
49 * conditions (new/existing installation). See the single method comments for details.
50 *
51 * @throws Exception
52 * @return void
53 */
54 public function execute() {
55 $this->loadBaseExtensions();
56 $this->initializeObjectManager();
57
58 $this->outputInstallToolNotEnabledMessageIfNeeded();
59 $this->migrateLocalconfToLocalConfigurationIfNeeded();
60 $this->outputInstallToolPasswordNotSetMessageIfNeeded();
61 $this->migrateExtensionListToPackageStatesFile();
62 $this->executeOrOutputFirstInstallStepIfNeeded();
63 $this->executeSilentConfigurationUpgradesIfNeeded();
64 $this->initializeSession();
65 $this->checkSessionToken();
66 $this->checkSessionLifetime();
67 $this->loginIfRequested();
68 $this->outputLoginFormIfNotAuthorized();
69 $this->executeSpecificStep();
70 $this->outputSpecificStep();
71 $this->redirectToTool();
72 }
73
74 /**
75 * Execute a step action if requested. If executed, a redirect is done, so
76 * the next request will render step one again if needed or initiate a
77 * request to test the next step.
78 *
79 * @throws Exception
80 * @return void
81 */
82 protected function executeSpecificStep() {
83 $action = $this->getAction();
84 $postValues = $this->getPostValues();
85 if ($action && isset($postValues['set']) && $postValues['set'] === 'execute') {
86 $stepAction = $this->getActionInstance($action);
87 $stepAction->setAction($action);
88 $stepAction->setToken($this->generateTokenForAction($action));
89 $stepAction->setPostValues($this->getPostValues());
90 $messages = $stepAction->execute();
91 $this->addSessionMessages($messages);
92 $this->redirect();
93 }
94 }
95
96 /**
97 * Render a specific step. Fallback to first step if none is given.
98 * The according step is instantiated and 'needsExecution' is called. If
99 * it needs execution, the step will be rendered, otherwise a redirect
100 * to test the next step is initiated.
101 *
102 * @return void
103 */
104 protected function outputSpecificStep() {
105 $action = $this->getAction();
106 if ($action === '') {
107 // First step action
108 list($action) = $this->authenticationActions;
109 }
110 $stepAction = $this->getActionInstance($action);
111 $stepAction->setAction($action);
112 $stepAction->setController('step');
113 $stepAction->setToken($this->generateTokenForAction($action));
114 $stepAction->setPostValues($this->getPostValues());
115
116 $needsExecution = TRUE;
117 try {
118 // needsExecution() may throw a RedirectException to communicate that it changed
119 // configuration parameters and need an application reload.
120 $needsExecution = $stepAction->needsExecution();
121 } catch (Exception\RedirectException $e) {
122 $this->redirect();
123 }
124
125 if ($needsExecution) {
126 $stepAction->setMessages($this->session->getMessagesAndFlush());
127 $this->output($stepAction->handle());
128 } else {
129 // Redirect to next step if there are any
130 $currentPosition = array_keys($this->authenticationActions, $action, TRUE);
131 $nextAction = array_slice($this->authenticationActions, $currentPosition[0] + 1, 1);
132 if (!empty($nextAction)) {
133 $this->redirect('', $nextAction[0]);
134 }
135 }
136 }
137
138 /**
139 * Instantiate a specific action class
140 *
141 * @param string $action Action to instantiate
142 * @throws Exception
143 * @return \TYPO3\CMS\Install\Controller\Action\Step\StepInterface
144 */
145 protected function getActionInstance($action) {
146 $this->validateAuthenticationAction($action);
147 $actionClass = ucfirst($action);
148 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $stepAction */
149 $stepAction = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\' . $actionClass);
150 if (!($stepAction instanceof Action\Step\StepInterface)) {
151 throw new Exception(
152 $action . ' does non implement StepInterface',
153 1371303903
154 );
155 }
156 return $stepAction;
157 }
158
159 /**
160 * If the last step was reached and none needs execution, a redirect
161 * to call the tool controller is initiated.
162 *
163 * @return void
164 */
165 protected function redirectToTool() {
166 $this->redirect('tool');
167 }
168
169 /**
170 * Migrate localconf.php to LocalConfiguration if needed. This is done early in
171 * install tool to ease further handling.
172 *
173 * If typo3conf and typo3conf/localconf.php exist, but no typo3conf/LocalConfiguration,
174 * create LocalConfiguration.php / AdditionalConfiguration.php from localconf.php
175 * Might throw exception if typo3conf directory is not writable.
176 *
177 * @return void
178 */
179 protected function migrateLocalconfToLocalConfigurationIfNeeded() {
180 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
181 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
182
183 $localConfigurationFileLocation = $configurationManager->getLocalConfigurationFileLocation();
184 $localConfigurationFileExists = is_file($localConfigurationFileLocation);
185 $localConfFileLocation = PATH_typo3conf . 'localconf.php';
186 $localConfFileExists = is_file($localConfFileLocation);
187
188 if (is_dir(PATH_typo3conf) && $localConfFileExists && !$localConfigurationFileExists) {
189 $localConfContent = file($localConfFileLocation);
190
191 // Line array for the three categories: localConfiguration, db settings, additionalConfiguration
192 $typo3ConfigurationVariables = array();
193 $typo3DatabaseVariables = array();
194 $additionalConfiguration = array();
195 foreach ($localConfContent as $line) {
196 $line = trim($line);
197 $matches = array();
198 // Convert extList to array
199 if (
200 preg_match('/^\\$TYPO3_CONF_VARS\\[\'EXT\'\\]\\[\'extList\'\\] *={1} *\'(.+)\';{1}/', $line, $matches) === 1
201 || preg_match('/^\\$GLOBALS\\[\'TYPO3_CONF_VARS\'\\]\\[\'EXT\'\\]\\[\'extList\'\\] *={1} *\'(.+)\';{1}/', $line, $matches) === 1
202 ) {
203 $extListAsArray = GeneralUtility::trimExplode(',', $matches[1], TRUE);
204 $typo3ConfigurationVariables[] = '$TYPO3_CONF_VARS[\'EXT\'][\'extListArray\'] = ' . var_export($extListAsArray, TRUE) . ';';
205 } elseif (
206 preg_match('/^\\$TYPO3_CONF_VARS.+;{1}/', $line, $matches) === 1
207 ) {
208 $typo3ConfigurationVariables[] = $matches[0];
209 } elseif (
210 preg_match('/^\\$GLOBALS\\[\'TYPO3_CONF_VARS\'\\].+;{1}/', $line, $matches) === 1
211 ) {
212 $lineWithoutGlobals = str_replace('$GLOBALS[\'TYPO3_CONF_VARS\']', '$TYPO3_CONF_VARS', $matches[0]);
213 $typo3ConfigurationVariables[] = $lineWithoutGlobals;
214 } elseif (
215 preg_match('/^\\$typo_db.+;{1}/', $line, $matches) === 1
216 ) {
217 eval($matches[0]);
218 if (isset($typo_db_host)) {
219 $typo3DatabaseVariables['host'] = $typo_db_host;
220 } elseif (isset($typo_db)) {
221 $typo3DatabaseVariables['database'] = $typo_db;
222 } elseif (isset($typo_db_username)) {
223 $typo3DatabaseVariables['username'] = $typo_db_username;
224 } elseif (isset($typo_db_password)) {
225 $typo3DatabaseVariables['password'] = $typo_db_password;
226 } elseif (isset($typo_db_extTableDef_script)) {
227 $typo3DatabaseVariables['extTablesDefinitionScript'] = $typo_db_extTableDef_script;
228 }
229 unset($typo_db_host, $typo_db, $typo_db_username, $typo_db_password, $typo_db_extTableDef_script);
230 } elseif (
231 strlen($line) > 0 && preg_match('/^\\/\\/.+|^#.+|^<\\?php$|^<\\?$|^\\?>$/', $line, $matches) === 0
232 ) {
233 $additionalConfiguration[] = $line;
234 }
235 }
236
237 // Build new TYPO3_CONF_VARS array
238 $TYPO3_CONF_VARS = NULL;
239 // Issue #39434: Combining next two lines into one triggers a weird issue in some PHP versions
240 $evalData = implode(LF, $typo3ConfigurationVariables);
241 eval($evalData);
242
243 // Add db settings to array
244 $TYPO3_CONF_VARS['DB'] = $typo3DatabaseVariables;
245 $TYPO3_CONF_VARS = \TYPO3\CMS\Core\Utility\ArrayUtility::sortByKeyRecursive($TYPO3_CONF_VARS);
246
247 // Write out new LocalConfiguration file
248 $configurationManager->writeLocalConfiguration($TYPO3_CONF_VARS);
249
250 // Write out new AdditionalConfiguration file
251 if (sizeof($additionalConfiguration) > 0) {
252 $configurationManager->writeAdditionalConfiguration($additionalConfiguration);
253 } else {
254 @unlink($configurationManager->getAdditionalConfigurationFileLocation());
255 }
256
257 // Move localconf.php to localconf.obsolete.php
258 rename($localConfFileLocation, PATH_site . 'typo3conf/localconf.obsolete.php');
259
260 // Perform a reload to self, so bootstrap now uses new LocalConfiguration.php
261 $this->redirect();
262 }
263 }
264
265 /**
266 * Create PackageStates.php if missing and LocalConfiguration exists.
267 *
268 * This typically happens during upgrading from 6.1 or lower, all valid packages
269 * from old EXT/extListArray will be marked active.
270 *
271 * It is also fired if PackageStates.php is deleted on a running 6.2 instance,
272 * all packages marked as "part of minimal system" are activated in this case.
273 *
274 * The step installer creates typo3conf/, LocalConfiguration and PackageStates in
275 * one call, so an "installation in progress" does not trigger creation of
276 * PackageStates here.
277 *
278 * @throws \Exception
279 * @return void
280 */
281 protected function migrateExtensionListToPackageStatesFile() {
282 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
283 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
284 $localConfigurationFileLocation = $configurationManager->getLocalConfigurationFileLocation();
285 $localConfigurationFileExists = is_file($localConfigurationFileLocation);
286
287 if (file_exists(PATH_typo3conf . 'PackageStates.php')
288 || (is_dir(PATH_typo3conf) && !$localConfigurationFileExists)
289 || !is_dir(PATH_typo3conf)
290 ) {
291 return;
292 }
293
294 try {
295 /** @var \TYPO3\CMS\Core\Package\FailsafePackageManager $packageManager */
296 $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->getEarlyInstance('TYPO3\\Flow\\Package\\PackageManager');
297
298 // Activate all packages required for a minimal usable system
299 $packages = $packageManager->getAvailablePackages();
300 foreach ($packages as $package) {
301 /** @var $package \TYPO3\CMS\Core\Package\PackageInterface */
302 if ($package instanceof \TYPO3\CMS\Core\Package\PackageInterface
303 && $package->isPartOfMinimalUsableSystem()
304 ) {
305 $packageManager->activatePackage($package->getPackageKey());
306 }
307 }
308
309 // Activate all packages from LocalConfiguration EXT/extListArray if there is such an entry during upgrading.
310 $extensionsFromExtListArray = array();
311 try {
312 $extensionsFromExtListArray = $configurationManager->getLocalConfigurationValueByPath('EXT/extListArray');
313 } catch (\RuntimeException $exception) {
314 }
315 foreach ($extensionsFromExtListArray as $loadedExtension) {
316 try {
317 $packageManager->activatePackage($loadedExtension);
318 } catch (\TYPO3\Flow\Package\Exception\UnknownPackageException $exception) {
319 // Skip unavailable packages silently
320 }
321 }
322
323 // Backup LocalConfiguration.php
324 if (file_exists(PATH_typo3conf . 'PackageStates.php')) {
325 copy(
326 $configurationManager->getLocalConfigurationFileLocation(),
327 preg_replace('/\.php$/', '.beforePackageStatesMigration.php', $configurationManager->getLocalConfigurationFileLocation())
328 );
329 }
330
331 $packageManager->forceSortAndSavePackageStates();
332
333 // Perform a reload to self, so bootstrap now uses new PackageStates.php
334 $this->redirect();
335 } catch (\Exception $exception) {
336 if (file_exists(PATH_typo3conf . 'PackageStates.php')) {
337 unlink(PATH_typo3conf . 'PackageStates.php');
338 }
339 throw $exception;
340 }
341 }
342
343 /**
344 * The first install step has a special standing and needs separate handling:
345 * At this point no directory exists (no typo3conf, no typo3temp), so we can
346 * not start the session handling (that stores the install tool session within typo3temp).
347 * This also means, we can not start the token handling for CSRF protection. This
348 * is no real problem, since no local configuration or other security relevant
349 * information was created yet.
350 *
351 * So, if no typo3conf directory exists yet, the first step is just rendered, or
352 * executed if called so. After that, a redirect is initiated to proceed with
353 * other tasks.
354 *
355 * @return void
356 */
357 protected function executeOrOutputFirstInstallStepIfNeeded() {
358 $postValues = $this->getPostValues();
359
360 $wasExecuted= FALSE;
361 $errorMessagesFromExecute = array();
362 if (isset($postValues['action'])
363 && $postValues['action'] === 'environmentAndFolders'
364 ) {
365 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
366 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
367 $errorMessagesFromExecute = $action->execute();
368 $wasExecuted = TRUE;
369 }
370
371 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
372 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
373
374 $needsExecution = TRUE;
375 try {
376 // needsExecution() may throw a RedirectException to communicate that it changed
377 // configuration parameters and need an application reload.
378 $needsExecution = $action->needsExecution();
379 } catch (Exception\RedirectException $e) {
380 $this->redirect();
381 }
382
383 $testReflection = new \ReflectionMethod(get_class($this), __FUNCTION__);
384 if (!@is_dir(PATH_typo3conf)
385 || $needsExecution
386 || $testReflection->getDocComment() === FALSE
387 ) {
388 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
389 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
390 $action->setController('step');
391 $action->setAction('environmentAndFolders');
392 if (count($errorMessagesFromExecute) > 0) {
393 $action->setMessages($errorMessagesFromExecute);
394 }
395 $this->output($action->handle());
396 }
397
398 if ($wasExecuted) {
399 $this->redirect();
400 }
401 }
402
403 /**
404 * Call silent upgrade class, redirect to self if configuration was changed.
405 *
406 * @return void
407 */
408 protected function executeSilentConfigurationUpgradesIfNeeded() {
409 /** @var \TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService $upgradeService */
410 $upgradeService = $this->objectManager->get(
411 'TYPO3\\CMS\\Install\\Service\\SilentConfigurationUpgradeService'
412 );
413 try {
414 $upgradeService->execute();
415 } catch (Exception\RedirectException $e) {
416 $this->redirect();
417 }
418 }
419 }