[!!!][FEATURE] Introduce single _cli_ user for all CLI calls
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Console / CliRequestHandler.php
1 <?php
2 namespace TYPO3\CMS\Backend\Console;
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 Symfony\Component\Console\Input\InputInterface;
18 use Symfony\Component\Console\Output\ConsoleOutput;
19 use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication;
20 use TYPO3\CMS\Core\Console\RequestHandlerInterface;
21 use TYPO3\CMS\Core\Core\Bootstrap;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23
24 /**
25 * Command Line Interface Request Handler dealing with "cliKey"-based Commands from the cli_dispatch.phpsh script.
26 * Picks up requests only when coming from the CLI mode.
27 * Resolves the "cliKey" which is registered inside $TYPO3_CONF_VARS[SC_OPTIONS][GLOBAL][cliKeys]
28 * and includes the CLI-based script or exits if no valid "cliKey" is found.
29 * Also logs into the system as a backend user which needs to be added to the database called _CLI_mymodule
30 */
31 class CliRequestHandler implements RequestHandlerInterface
32 {
33 /**
34 * Instance of the current TYPO3 bootstrap
35 * @var Bootstrap
36 */
37 protected $bootstrap;
38
39 /**
40 * Constructor handing over the bootstrap
41 *
42 * @param Bootstrap $bootstrap
43 */
44 public function __construct(Bootstrap $bootstrap)
45 {
46 $this->bootstrap = $bootstrap;
47 }
48
49 /**
50 * Handles any commandline request
51 *
52 * @param InputInterface $input
53 * @return void
54 */
55 public function handleRequest(InputInterface $input)
56 {
57 $output = GeneralUtility::makeInstance(ConsoleOutput::class);
58 $exitCode = 0;
59
60 try {
61 $command = $this->validateCommandLineKeyFromInput($input);
62
63 // try and look up if the CLI command exists
64 $commandLineScript = $this->getIncludeScriptByCommandLineKey($command);
65 $this->boot();
66
67 if (is_callable($commandLineScript)) {
68 call_user_func($commandLineScript);
69 } else {
70 // include the CLI script
71 include($commandLineScript);
72 }
73 } catch (\InvalidArgumentException $e) {
74 $output->writeln('<error>Oops, an error occurred: ' . $e->getMessage() . '</error>');
75 $output->writeln('');
76 $output->writeln('Valid keys are:');
77 $output->writeln('');
78 $cliKeys = array_keys($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys']);
79 asort($cliKeys);
80 foreach ($cliKeys as $key => $value) {
81 $output->writeln(' ' . $value);
82 }
83 $exitCode = $e->getCode();
84 } catch (\RuntimeException $e) {
85 $output->writeln('<error>Oops, an error occurred: ' . $e->getMessage() . '</error>');
86 $exitCode = $e->getCode();
87 } catch (\Throwable $e) {
88 $output->writeln('<error>Oops, an error occurred: ' . $e->getMessage() . '</error>');
89 $exitCode = $e->getCode();
90 }
91
92 exit($exitCode);
93 }
94
95 /**
96 * Execute TYPO3 bootstrap
97 *
98 * @throws \RuntimeException when the _CLI_ user cannot be authenticated properly
99 */
100 protected function boot()
101 {
102 $this->bootstrap
103 ->loadExtensionTables()
104 ->initializeBackendUser(CommandLineUserAuthentication::class);
105
106 // Checks for a user _CLI_, if non exists, will create one
107 $this->loadCommandLineBackendUser();
108
109 $this->bootstrap
110 ->initializeLanguageObject()
111 // Make sure output is not buffered, so command-line output and interaction can take place
112 ->endOutputBufferingAndCleanPreviousOutput();
113 }
114
115 /**
116 * Check CLI parameters.
117 * First argument is a key that points to the script configuration.
118 * If it is not set or not valid, the script exits with an error message.
119 *
120 * @param InputInterface $input an instance of the input given to the CLI call
121 * @return string the CLI key in use
122 * @throws \InvalidArgumentException
123 */
124 protected function validateCommandLineKeyFromInput(InputInterface $input)
125 {
126 $cliKey = $input->getFirstArgument();
127 if (empty($cliKey)) {
128 throw new \InvalidArgumentException('This script must have a command as first argument.', 1476107418);
129 } elseif (!is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys'][$cliKey])) {
130 throw new \InvalidArgumentException('This supplied command is not valid.', 1476107480);
131 }
132 return $cliKey;
133 }
134
135 /**
136 * Define cli-related parameters and return the include script.
137 *
138 * @param string $cliKey the CLI key
139 * @return string the absolute path to the include script
140 */
141 protected function getIncludeScriptByCommandLineKey($cliKey)
142 {
143 $commandLineScript = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys'][$cliKey][0];
144 if (!is_callable($commandLineScript)) {
145 $commandLineScript = GeneralUtility::getFileAbsFileName($commandLineScript);
146 }
147
148 // the CLI key (e.g. "extbase" or "lowlevel"), is removed from the argv, but the script path is kept
149 $cliScriptPath = array_shift($_SERVER['argv']);
150 array_shift($_SERVER['argv']);
151 array_unshift($_SERVER['argv'], $cliScriptPath);
152 return $commandLineScript;
153 }
154
155 /**
156 * If the backend script is in CLI mode, it will try to load a backend user named _cli_
157 *
158 * @throws \RuntimeException if a non-admin Backend user could not be loaded
159 */
160 protected function loadCommandLineBackendUser()
161 {
162 if ($GLOBALS['BE_USER']->user['uid']) {
163 throw new \RuntimeException('Another user was already loaded which is impossible in CLI mode!', 1476107444);
164 }
165 $GLOBALS['BE_USER']->authenticate();
166 }
167
168 /**
169 * This request handler can handle any CLI request.
170 *
171 * @param InputInterface $input
172 * @return bool Always TRUE
173 */
174 public function canHandleRequest(InputInterface $input)
175 {
176 return true;
177 }
178
179 /**
180 * Returns the priority - how eager the handler is to actually handle the request.
181 *
182 * @return int The priority of the request handler.
183 */
184 public function getPriority()
185 {
186 return 20;
187 }
188 }