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