[FEATURE] Integrate preliminary PackageManager API
[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 * @throws Exception
49 * @return void
50 */
51 public function execute() {
52 $this->loadBaseExtensions();
53 $this->initializeObjectManager();
54
55 // Warning: Order of these methods is security relevant and interferes with different access
56 // conditions (new/existing installation). See the single method comments for details.
57 $this->outputInstallToolNotEnabledMessageIfNeeded();
58 $this->migrateLocalconfToLocalConfigurationIfNeeded();
59 // @TODO: Move method to silest upgrader?!
60 $this->migrateExtensionListToPackageStatesFile();
61 $this->outputInstallToolPasswordNotSetMessageIfNeeded();
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 try {
117 // needsExecution() may throw a RedirectException to communicate that it changed
118 // configuration parameters and need an application reload.
119 $needsExecution = $stepAction->needsExecution();
120 } catch (Exception\RedirectException $e) {
121 $this->redirect();
122 }
123
124 if ($needsExecution) {
125 $stepAction->setMessages($this->session->getMessagesAndFlush());
126 $this->output($stepAction->handle());
127 } else {
128 // Redirect to next step if there are any
129 $currentPosition = array_keys($this->authenticationActions, $action, TRUE);
130 $nextAction = array_slice($this->authenticationActions, $currentPosition[0] + 1, 1);
131 if (!empty($nextAction)) {
132 $this->redirect('', $nextAction[0]);
133 }
134 }
135 }
136
137 /**
138 * Instantiate a specific action class
139 *
140 * @param string $action Action to instantiate
141 * @throws Exception
142 * @return \TYPO3\CMS\Install\Controller\Action\Step\StepInterface
143 */
144 protected function getActionInstance($action) {
145 $this->validateAuthenticationAction($action);
146 $actionClass = ucfirst($action);
147 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $stepAction */
148 $stepAction = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\' . $actionClass);
149 if (!($stepAction instanceof Action\Step\StepInterface)) {
150 throw new Exception(
151 $action . ' does non implement StepInterface',
152 1371303903
153 );
154 }
155 return $stepAction;
156 }
157
158 /**
159 * If the last step was reached and none needs execution, a redirect
160 * to call the tool controller is initiated.
161 *
162 * @return void
163 */
164 protected function redirectToTool() {
165 $this->redirect('tool');
166 }
167
168 /**
169 * "Silent" upgrade very early in step installer, before rendering step 1:
170 * If typo3conf and typo3conf/localconf.php exist, but no typo3conf/LocalConfiguration,
171 * create LocalConfiguration.php / AdditionalConfiguration.php from localconf.php
172 * Might throw exception if typo3conf directory is not writable.
173 *
174 * @return void
175 */
176 protected function migrateLocalconfToLocalConfigurationIfNeeded() {
177 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
178 $configurationManager = $this->objectManager->get('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager');
179
180 $localConfigurationFileLocation = $configurationManager->getLocalConfigurationFileLocation();
181 $localConfigurationFileExists = is_file($localConfigurationFileLocation);
182 $localConfFileLocation = PATH_typo3conf . 'localconf.php';
183 $localConfFileExists = is_file($localConfFileLocation);
184
185 if (is_dir(PATH_typo3conf) && $localConfFileExists && !$localConfigurationFileExists) {
186 $localConfContent = file($localConfFileLocation);
187
188 // Line array for the three categories: localConfiguration, db settings, additionalConfiguration
189 $typo3ConfigurationVariables = array();
190 $typo3DatabaseVariables = array();
191 $additionalConfiguration = array();
192 foreach ($localConfContent as $line) {
193 $line = trim($line);
194 $matches = array();
195 // Convert extList to array
196 if (
197 preg_match('/^\\$TYPO3_CONF_VARS\\[\'EXT\'\\]\\[\'extList\'\\] *={1} *\'(.+)\';{1}/', $line, $matches) === 1
198 || preg_match('/^\\$GLOBALS\\[\'TYPO3_CONF_VARS\'\\]\\[\'EXT\'\\]\\[\'extList\'\\] *={1} *\'(.+)\';{1}/', $line, $matches) === 1
199 ) {
200 $extListAsArray = GeneralUtility::trimExplode(',', $matches[1], TRUE);
201 $typo3ConfigurationVariables[] = '$TYPO3_CONF_VARS[\'EXT\'][\'extListArray\'] = ' . var_export($extListAsArray, TRUE) . ';';
202 } elseif (
203 preg_match('/^\\$TYPO3_CONF_VARS.+;{1}/', $line, $matches) === 1
204 ) {
205 $typo3ConfigurationVariables[] = $matches[0];
206 } elseif (
207 preg_match('/^\\$GLOBALS\\[\'TYPO3_CONF_VARS\'\\].+;{1}/', $line, $matches) === 1
208 ) {
209 $lineWithoutGlobals = str_replace('$GLOBALS[\'TYPO3_CONF_VARS\']', '$TYPO3_CONF_VARS', $matches[0]);
210 $typo3ConfigurationVariables[] = $lineWithoutGlobals;
211 } elseif (
212 preg_match('/^\\$typo_db.+;{1}/', $line, $matches) === 1
213 ) {
214 eval($matches[0]);
215 if (isset($typo_db_host)) {
216 $typo3DatabaseVariables['host'] = $typo_db_host;
217 } elseif (isset($typo_db)) {
218 $typo3DatabaseVariables['database'] = $typo_db;
219 } elseif (isset($typo_db_username)) {
220 $typo3DatabaseVariables['username'] = $typo_db_username;
221 } elseif (isset($typo_db_password)) {
222 $typo3DatabaseVariables['password'] = $typo_db_password;
223 } elseif (isset($typo_db_extTableDef_script)) {
224 $typo3DatabaseVariables['extTablesDefinitionScript'] = $typo_db_extTableDef_script;
225 }
226 unset($typo_db_host, $typo_db, $typo_db_username, $typo_db_password, $typo_db_extTableDef_script);
227 } elseif (
228 strlen($line) > 0 && preg_match('/^\\/\\/.+|^#.+|^<\\?php$|^<\\?$|^\\?>$/', $line, $matches) === 0
229 ) {
230 $additionalConfiguration[] = $line;
231 }
232 }
233
234 // Build new TYPO3_CONF_VARS array
235 $TYPO3_CONF_VARS = NULL;
236 // Issue #39434: Combining next two lines into one triggers a weird issue in some PHP versions
237 $evalData = implode(LF, $typo3ConfigurationVariables);
238 eval($evalData);
239
240 // Add db settings to array
241 $TYPO3_CONF_VARS['DB'] = $typo3DatabaseVariables;
242 $TYPO3_CONF_VARS = \TYPO3\CMS\Core\Utility\ArrayUtility::sortByKeyRecursive($TYPO3_CONF_VARS);
243
244 // Write out new LocalConfiguration file
245 $configurationManager->writeLocalConfiguration($TYPO3_CONF_VARS);
246
247 // Write out new AdditionalConfiguration file
248 if (sizeof($additionalConfiguration) > 0) {
249 $configurationManager->writeAdditionalConfiguration($additionalConfiguration);
250 } else {
251 @unlink($configurationManager->getAdditionalConfigurationFileLocation());
252 }
253
254 // Move localconf.php to localconf.obsolete.php
255 rename($localConfFileLocation, PATH_site . 'typo3conf/localconf.obsolete.php');
256
257 // Perform a reload to self, so bootstrap now uses new LocalConfiguration.php
258 $this->redirect();
259 }
260 }
261
262 /**
263 * "Silent" upgrade very early in step installer, before rendering step 1
264 *
265 * @throws \Exception
266 * @return void
267 */
268 protected function migrateExtensionListToPackageStatesFile() {
269 try {
270 if (file_exists(PATH_typo3conf . 'PackageStates.php')) {
271 return;
272 }
273 $bootstrap = \TYPO3\CMS\Core\Core\Bootstrap::getInstance();
274 $packageManager = new \TYPO3\CMS\Install\Updates\UpdatePackageManager($bootstrap->getEarlyInstance('TYPO3\\CMS\\Core\\Configuration\\ConfigurationManager'));
275 $packageManager->createPackageStatesFile($bootstrap, PATH_site, PATH_typo3conf . 'PackageStates.php');
276
277 // Perform a reload to self, so bootstrap now uses new PackageStates.php
278 $this->redirect();
279 } catch (\Exception $exception) {
280 if (file_exists(PATH_typo3conf . 'PackageStates.php')) {
281 unlink(PATH_typo3conf . 'PackageStates.php');
282 }
283 throw $exception;
284 }
285 }
286
287 /**
288 * The first install step has a special standing and needs separate handling:
289 * At this point no directory exists (no typo3conf, no typo3temp), so we can
290 * not start the session handling (that stores the install tool session within typo3temp).
291 * This also means, we can not start the token handling for CSRF protection. This
292 * is no real problem, since no local configuration or other security relevant
293 * information was created yet.
294 *
295 * So, if no typo3conf directory exists yet, the first step is just rendered, or
296 * executed if called so. After that, a redirect is initiated to proceed with
297 * other tasks.
298 *
299 * @return void
300 */
301 protected function executeOrOutputFirstInstallStepIfNeeded() {
302 $postValues = $this->getPostValues();
303
304 $wasExecuted= FALSE;
305 $errorMessagesFromExecute = array();
306 if (isset($postValues['action'])
307 && $postValues['action'] === 'environmentAndFolders'
308 ) {
309 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
310 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
311 $errorMessagesFromExecute = $action->execute();
312 $wasExecuted = TRUE;
313 }
314
315 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
316 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
317
318 try {
319 // needsExecution() may throw a RedirectException to communicate that it changed
320 // configuration parameters and need an application reload.
321 $needsExecution = $action->needsExecution();
322 } catch (Exception\RedirectException $e) {
323 $this->redirect();
324 }
325
326 if (!@is_dir(PATH_typo3conf)
327 || $needsExecution
328 ) {
329 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
330 $action = $this->objectManager->get('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\EnvironmentAndFolders');
331 $action->setController('step');
332 $action->setAction('environmentAndFolders');
333 if (count($errorMessagesFromExecute) > 0) {
334 $action->setMessages($errorMessagesFromExecute);
335 }
336 $this->output($action->handle());
337 }
338
339 if ($wasExecuted) {
340 $this->redirect();
341 }
342 }
343
344 /**
345 * Call silent upgrade class, redirect to self if configuration was changed.
346 *
347 * @return void
348 */
349 protected function executeSilentConfigurationUpgradesIfNeeded() {
350 /** @var \TYPO3\CMS\Install\Service\SilentConfigurationUpgradeService $upgradeService */
351 $upgradeService = $this->objectManager->get(
352 'TYPO3\\CMS\\Install\\Service\\SilentConfigurationUpgradeService'
353 );
354 try {
355 $upgradeService->execute();
356 } catch (Exception\RedirectException $e) {
357 $this->redirect();
358 }
359 }
360 }