[TASK] Streamline CLI Request Handling
[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\Core\Bootstrap;
20 use TYPO3\CMS\Core\Console\RequestHandlerInterface;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Utility\StringUtility;
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 $this->bootstrap = $bootstrap;
46 }
47
48 /**
49 * Handles any commandline request
50 *
51 * @param InputInterface $input
52 * @return void
53 */
54 public function handleRequest(InputInterface $input) {
55 $output = GeneralUtility::makeInstance(ConsoleOutput::class);
56 $exitCode = 0;
57
58 try {
59 $command = $this->validateCommandLineKeyFromInput($input);
60
61 // try and look up if the CLI command user exists, throws an exception if the CLI
62 // user cannot be found
63 list($commandLineScript, $commandLineName) = $this->getIncludeScriptByCommandLineKey($command);
64 $this->boot($commandLineName);
65
66 if (is_callable($commandLineScript)) {
67 if ($commandLineScript instanceof \Closure) {
68 $commandLineScript->bindTo($this);
69 }
70 call_user_func($commandLineScript);
71 } else {
72 // include the CLI script
73 include($commandLineScript);
74 }
75
76 } catch (\InvalidArgumentException $e) {
77 $output->writeln('<error>Oops, an error occurred: ' . $e->getMessage() . '</error>');
78 $output->writeln('');
79 $output->writeln('Valid keys are:');
80 $output->writeln('');
81 $cliKeys = array_keys($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys']);
82 asort($cliKeys);
83 foreach ($cliKeys as $key => $value) {
84 $output->writeln(' ' . $value);
85 }
86 $exitCode = $e->getCode();
87
88 } catch (\RuntimeException $e) {
89 $output->writeln('<error>Oops, an error occurred: ' . $e->getMessage() . '</error>');
90 $exitCode = $e->getCode();
91
92 } catch (\Exception $e) {
93 $output->writeln('<error>Oops, an error occurred: ' . $e->getMessage() . '</error>');
94 $exitCode = $e->getCode();
95 }
96
97 exit($exitCode);
98 }
99
100 /**
101 * Execute TYPO3 bootstrap
102 *
103 * @throws \RuntimeException when the _CLI_ user cannot be authenticated properly
104 */
105 protected function boot($commandLineName) {
106 $this->bootstrap
107 ->loadExtensionTables(TRUE)
108 ->initializeBackendUser();
109
110 // Checks for a user called starting with _CLI_ e.g. "_CLI_lowlevel"
111 $this->loadCommandLineBackendUser($commandLineName);
112
113 $this->bootstrap
114 ->initializeBackendAuthentication()
115 ->initializeLanguageObject();
116
117 // Make sure output is not buffered, so command-line output and interaction can take place
118 GeneralUtility::flushOutputBuffers();
119 }
120
121 /**
122 * Check CLI parameters.
123 * First argument is a key that points to the script configuration.
124 * If it is not set or not valid, the script exits with an error message.
125 *
126 * @param InputInterface $input an instance of the input given to the CLI call
127 * @return string the CLI key in use
128 * @throws \InvalidArgumentException
129 */
130 protected function validateCommandLineKeyFromInput(InputInterface $input) {
131 $cliKey = $input->getFirstArgument();
132 if (empty($cliKey)) {
133 throw new \InvalidArgumentException('This script must have a command as first argument.', 1);
134 } elseif (!is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys'][$cliKey])) {
135 throw new \InvalidArgumentException('This supplied command is not valid.', 1);
136 }
137 return $cliKey;
138 }
139
140 /**
141 * Define cli-related parameters and return the include script as well as the command line name. Used for
142 * authentication against the backend user in the "laodCommandLineBackendUser()" action.
143 *
144 * @param string $cliKey the CLI key
145 * @return string the absolute path to the include script
146 */
147 protected function getIncludeScriptByCommandLineKey($cliKey) {
148 list($commandLineScript, $commandLineName) = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys'][$cliKey];
149 if (!is_callable($commandLineScript)) {
150 $commandLineScript = GeneralUtility::getFileAbsFileName($commandLineScript);
151 // Note: These constants are not in use anymore, and marked for deprecation and will be removed in TYPO3 CMS 8
152 define('TYPO3_cliKey', $cliKey);
153 define('TYPO3_cliInclude', $commandLineScript);
154 }
155 // This is a compatibility layer: Some cli scripts rely on this, like ext:phpunit cli
156 // This layer will be removed in TYPO3 CMS 8
157 $GLOBALS['temp_cliScriptPath'] = array_shift($_SERVER['argv']);
158 $GLOBALS['temp_cliKey'] = array_shift($_SERVER['argv']);
159 array_unshift($_SERVER['argv'], $GLOBALS['temp_cliScriptPath']);
160 return array($commandLineScript, $commandLineName);
161 }
162
163 /**
164 * If the backend script is in CLI mode, it will try to load a backend user named by the CLI module name (in lowercase)
165 *
166 * @param string $commandLineName the name of the module registered inside $TYPO3_CONF_VARS[SC_OPTIONS][GLOBAL][cliKeys] as second parameter
167 * @throws \RuntimeException if a non-admin Backend user could not be loaded
168 */
169 protected function loadCommandLineBackendUser($commandLineName) {
170 if ($GLOBALS['BE_USER']->user['uid']) {
171 throw new \RuntimeException('Another user was already loaded which is impossible in CLI mode!', 3);
172 }
173 if (!StringUtility::beginsWith($commandLineName, '_CLI_')) {
174 throw new \RuntimeException('Module name, "' . $commandLineName . '", was not prefixed with "_CLI_"', 3);
175 }
176 $userName = strtolower($commandLineName);
177 $GLOBALS['BE_USER']->setBeUserByName($userName);
178 if (!$GLOBALS['BE_USER']->user['uid']) {
179 throw new \RuntimeException('No backend user named "' . $userName . '" was found!', 3);
180 }
181 if ($GLOBALS['BE_USER']->isAdmin()) {
182 throw new \RuntimeException('CLI backend user "' . $userName . '" was ADMIN which is not allowed!', 3);
183 }
184 }
185
186 /**
187 * This request handler can handle any CLI request.
188 *
189 * @param InputInterface $input
190 * @return bool Always TRUE
191 */
192 public function canHandleRequest(InputInterface $input) {
193 return TRUE;
194 }
195
196 /**
197 * Returns the priority - how eager the handler is to actually handle the request.
198 *
199 * @return int The priority of the request handler.
200 */
201 public function getPriority() {
202 return 20;
203 }
204 }