[TASK] Get rid of ObjectManager 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 * 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 as 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 {
47 $this->loadBaseExtensions();
48 $this->outputInstallToolNotEnabledMessageIfNeeded();
49 $this->outputInstallToolPasswordNotSetMessageIfNeeded();
50 $this->recreatePackageStatesFileIfNotExisting();
51 $this->executeOrOutputFirstInstallStepIfNeeded();
52 $this->adjustTrustedHostsPatternIfNeeded();
53 $this->executeSilentConfigurationUpgradesIfNeeded();
54 $this->initializeSession();
55 $this->checkSessionToken();
56 $this->checkSessionLifetime();
57 $this->loginIfRequested();
58 $this->outputLoginFormIfNotAuthorized();
59 $this->executeSpecificStep();
60 $this->outputSpecificStep();
61 $this->redirectToTool();
62 }
63
64 /**
65 * Execute a step action if requested. If executed, a redirect is done, so
66 * the next request will render step one again if needed or initiate a
67 * request to test the next step.
68 *
69 * @throws Exception
70 * @return void
71 */
72 protected function executeSpecificStep()
73 {
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 {
97 $action = $this->getAction();
98 if ($action === '') {
99 // First step action
100 list($action) = $this->authenticationActions;
101 }
102 $stepAction = $this->getActionInstance($action);
103 $stepAction->setAction($action);
104 $stepAction->setController('step');
105 $stepAction->setToken($this->generateTokenForAction($action));
106 $stepAction->setPostValues($this->getPostValues());
107
108 $needsExecution = true;
109 try {
110 // needsExecution() may throw a RedirectException to communicate that it changed
111 // configuration parameters and need an application reload.
112 $needsExecution = $stepAction->needsExecution();
113 } catch (Exception\RedirectException $e) {
114 $this->redirect();
115 }
116
117 if ($needsExecution) {
118 if ($this->isInitialInstallationInProgress()) {
119 $currentStep = (array_search($action, $this->authenticationActions) + 1);
120 $totalSteps = count($this->authenticationActions);
121 $stepAction->setStepsCounter($currentStep, $totalSteps);
122 }
123 $stepAction->setMessages($this->session->getMessagesAndFlush());
124 $this->output($stepAction->handle());
125 } else {
126 // Redirect to next step if there are any
127 $currentPosition = array_keys($this->authenticationActions, $action, true);
128 $nextAction = array_slice($this->authenticationActions, $currentPosition[0] + 1, 1);
129 if (!empty($nextAction)) {
130 $this->redirect('', $nextAction[0]);
131 }
132 }
133 }
134
135 /**
136 * Instantiate a specific action class
137 *
138 * @param string $action Action to instantiate
139 * @throws Exception
140 * @return \TYPO3\CMS\Install\Controller\Action\Step\StepInterface
141 */
142 protected function getActionInstance($action)
143 {
144 $this->validateAuthenticationAction($action);
145 $actionClass = ucfirst($action);
146 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $stepAction */
147 $stepAction = GeneralUtility::makeInstance('TYPO3\\CMS\\Install\\Controller\\Action\\Step\\' . $actionClass);
148 if (!($stepAction instanceof Action\Step\StepInterface)) {
149 throw new Exception(
150 $action . ' does non implement StepInterface',
151 1371303903
152 );
153 }
154 return $stepAction;
155 }
156
157 /**
158 * If the last step was reached and none needs execution, a redirect
159 * to call the tool controller is initiated.
160 *
161 * @return void
162 */
163 protected function redirectToTool()
164 {
165 $this->redirect('tool');
166 }
167
168 /**
169 * Create PackageStates.php if missing and LocalConfiguration exists.
170 *
171 * It is fired if PackageStates.php is deleted on a running instance,
172 * all packages marked as "part of minimal system" are activated in this case.
173 *
174 * The step installer creates typo3conf/, LocalConfiguration and PackageStates in
175 * one call, so an "installation in progress" does not trigger creation of
176 * PackageStates here.
177 *
178 * @throws \Exception
179 * @return void
180 */
181 protected function recreatePackageStatesFileIfNotExisting()
182 {
183 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
184 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
185 $localConfigurationFileLocation = $configurationManager->getLocalConfigurationFileLocation();
186 $localConfigurationFileExists = is_file($localConfigurationFileLocation);
187 $packageStatesFilePath = PATH_typo3conf . 'PackageStates.php';
188 $localConfigurationBackupFilePath = preg_replace(
189 '/\\.php$/',
190 '.beforePackageStatesMigration.php',
191 $configurationManager->getLocalConfigurationFileLocation()
192 );
193
194 if (file_exists($packageStatesFilePath)
195 || (is_dir(PATH_typo3conf) && !$localConfigurationFileExists)
196 || !is_dir(PATH_typo3conf)
197 ) {
198 return;
199 }
200
201 try {
202 /** @var \TYPO3\CMS\Core\Package\FailsafePackageManager $packageManager */
203 $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->getEarlyInstance(\TYPO3\CMS\Core\Package\PackageManager::class);
204
205 // Activate all packages required for a minimal usable system
206 $packages = $packageManager->getAvailablePackages();
207 foreach ($packages as $package) {
208 /** @var $package \TYPO3\CMS\Core\Package\PackageInterface */
209 if ($package instanceof \TYPO3\CMS\Core\Package\PackageInterface
210 && $package->isPartOfMinimalUsableSystem()
211 ) {
212 $packageManager->activatePackage($package->getPackageKey());
213 }
214 }
215
216 // Backup LocalConfiguration.php
217 copy(
218 $configurationManager->getLocalConfigurationFileLocation(),
219 $localConfigurationBackupFilePath
220 );
221
222 $packageManager->forceSortAndSavePackageStates();
223
224 // Perform a reload to self, so bootstrap now uses new PackageStates.php
225 $this->redirect();
226 } catch (\Exception $exception) {
227 if (file_exists($packageStatesFilePath)) {
228 unlink($packageStatesFilePath);
229 }
230 if (file_exists($localConfigurationBackupFilePath)) {
231 unlink($localConfigurationBackupFilePath);
232 }
233 throw $exception;
234 }
235 }
236
237 /**
238 * The first install step has a special standing and needs separate handling:
239 * At this point no directory exists (no typo3conf, no typo3temp), so we can
240 * not start the session handling (that stores the install tool session within typo3temp).
241 * This also means, we can not start the token handling for CSRF protection. This
242 * is no real problem, since no local configuration or other security relevant
243 * information was created yet.
244 *
245 * So, if no typo3conf directory exists yet, the first step is just rendered, or
246 * executed if called so. After that, a redirect is initiated to proceed with
247 * other tasks.
248 *
249 * @return void
250 */
251 protected function executeOrOutputFirstInstallStepIfNeeded()
252 {
253 $postValues = $this->getPostValues();
254
255 $wasExecuted = false;
256 $errorMessagesFromExecute = array();
257 if (isset($postValues['action'])
258 && $postValues['action'] === 'environmentAndFolders'
259 ) {
260 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
261 $action = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Controller\Action\Step\EnvironmentAndFolders::class);
262 $errorMessagesFromExecute = $action->execute();
263 $wasExecuted = true;
264 }
265
266 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
267 $action = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Controller\Action\Step\EnvironmentAndFolders::class);
268
269 $needsExecution = true;
270 try {
271 // needsExecution() may throw a RedirectException to communicate that it changed
272 // configuration parameters and need an application reload.
273 $needsExecution = $action->needsExecution();
274 } catch (Exception\RedirectException $e) {
275 $this->redirect();
276 }
277
278 if (!@is_dir(PATH_typo3conf) || $needsExecution) {
279 /** @var \TYPO3\CMS\Install\Controller\Action\Step\StepInterface $action */
280 $action = GeneralUtility::makeInstance(\TYPO3\CMS\Install\Controller\Action\Step\EnvironmentAndFolders::class);
281 if ($this->isInitialInstallationInProgress()) {
282 $currentStep = (array_search('environmentAndFolders', $this->authenticationActions) + 1);
283 $totalSteps = count($this->authenticationActions);
284 $action->setStepsCounter($currentStep, $totalSteps);
285 }
286 $action->setController('step');
287 $action->setAction('environmentAndFolders');
288 if (!empty($errorMessagesFromExecute)) {
289 $action->setMessages($errorMessagesFromExecute);
290 }
291 $this->output($action->handle());
292 }
293
294 if ($wasExecuted) {
295 $this->redirect();
296 }
297 }
298
299 /**
300 * Checks the trusted hosts pattern setting
301 */
302 protected function adjustTrustedHostsPatternIfNeeded()
303 {
304 if (GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST'])) {
305 return;
306 }
307
308 /** @var \TYPO3\CMS\Core\Configuration\ConfigurationManager $configurationManager */
309 $configurationManager = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Configuration\ConfigurationManager::class);
310 $configurationManager->setLocalConfigurationValueByPath('SYS/trustedHostsPattern', '.*');
311 $this->redirect();
312 }
313
314 /**
315 * Call silent upgrade class, redirect to self if configuration was changed.
316 *
317 * @return void
318 */
319 protected function executeSilentConfigurationUpgradesIfNeeded()
320 {
321 /** @var SilentConfigurationUpgradeService $upgradeService */
322 $upgradeService = GeneralUtility::makeInstance(SilentConfigurationUpgradeService::class);
323 try {
324 $upgradeService->execute();
325 } catch (Exception\RedirectException $e) {
326 $this->redirect();
327 }
328 }
329 }