26058af198e266edff79ddfad91301f56b877988
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Mvc / Controller / CommandController.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\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\Authentication\BackendUserAuthentication;
18 use TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition;
19 use TYPO3\CMS\Extbase\Mvc\Cli\ConsoleOutput;
20 use TYPO3\CMS\Extbase\Mvc\Cli\Request;
21 use TYPO3\CMS\Extbase\Mvc\Cli\Response;
22 use TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException;
23 use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException;
24 use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
25 use TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException;
26 use TYPO3\CMS\Extbase\Mvc\RequestInterface;
27 use TYPO3\CMS\Extbase\Mvc\ResponseInterface;
28 use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
29 use TYPO3\CMS\Extbase\Reflection\ReflectionService;
30
31 /**
32 * A controller which processes requests from the command line
33 *
34 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
35 */
36 class CommandController implements CommandControllerInterface
37 {
38 /**
39 * @var Request
40 */
41 protected $request;
42
43 /**
44 * @var Response
45 */
46 protected $response;
47
48 /**
49 * @var Arguments
50 */
51 protected $arguments;
52
53 /**
54 * Name of the command method
55 *
56 * @var string
57 */
58 protected $commandMethodName = '';
59
60 /**
61 * Whether the command needs admin access to perform its job
62 *
63 * @var bool
64 * @api
65 */
66 protected $requestAdminPermissions = false;
67
68 /**
69 * @var ReflectionService
70 */
71 protected $reflectionService;
72
73 /**
74 * @var ObjectManagerInterface
75 */
76 protected $objectManager;
77
78 /**
79 * @var ConsoleOutput
80 */
81 protected $output;
82
83 /**
84 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
85 */
86 public function injectObjectManager(ObjectManagerInterface $objectManager)
87 {
88 $this->objectManager = $objectManager;
89 }
90
91 /**
92 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
93 */
94 public function injectReflectionService(ReflectionService $reflectionService)
95 {
96 $this->reflectionService = $reflectionService;
97 }
98
99 /**
100 * Checks if the current request type is supported by the controller.
101 *
102 * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The current request
103 * @return bool TRUE if this request type is supported, otherwise FALSE
104 */
105 public function canProcessRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request)
106 {
107 return $request instanceof Request;
108 }
109
110 /**
111 * Processes a command line request.
112 *
113 * @param RequestInterface $request The request object
114 * @param ResponseInterface $response The response, modified by this handler
115 * @throws UnsupportedRequestTypeException if the controller doesn't support the current request type
116 * @api
117 */
118 public function processRequest(RequestInterface $request, ResponseInterface $response)
119 {
120 if (!$this->canProcessRequest($request)) {
121 throw new UnsupportedRequestTypeException(sprintf('%s only supports command line requests – requests of type "%s" given.', static::class, get_class($request)), 1300787096);
122 }
123
124 $this->request = $request;
125 $this->request->setDispatched(true);
126 $this->response = $response;
127
128 $this->commandMethodName = $this->resolveCommandMethodName();
129 $this->output = $this->objectManager->get(ConsoleOutput::class);
130 $this->arguments = $this->objectManager->get(Arguments::class);
131 $this->initializeCommandMethodArguments();
132 $this->mapRequestArgumentsToControllerArguments();
133 $this->initializeBackendAuthentication();
134 $this->callCommandMethod();
135 }
136
137 /**
138 * Resolves and checks the current command method name
139 *
140 * Note: The resulting command method name might not have the correct case, which isn't a problem because PHP is
141 * case insensitive regarding method names.
142 *
143 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchCommandException
144 * @return string Method name of the current command
145 * @throws NoSuchCommandException
146 */
147 protected function resolveCommandMethodName()
148 {
149 $commandMethodName = $this->request->getControllerCommandName() . 'Command';
150 if (!is_callable([$this, $commandMethodName])) {
151 throw new NoSuchCommandException(sprintf('A command method "%s()" does not exist in controller "%s".', $commandMethodName, static::class), 1300902143);
152 }
153 return $commandMethodName;
154 }
155
156 /**
157 * Initializes the arguments array of this controller by creating an empty argument object for each of the
158 * method arguments found in the designated command method.
159 *
160 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
161 * @throws InvalidArgumentTypeException
162 */
163 protected function initializeCommandMethodArguments()
164 {
165 $methodParameters = $this->reflectionService->getMethodParameters(static::class, $this->commandMethodName);
166
167 foreach ($methodParameters as $parameterName => $parameterInfo) {
168 $dataType = null;
169 if (isset($parameterInfo['type'])) {
170 $dataType = $parameterInfo['type'];
171 } elseif ($parameterInfo['array']) {
172 $dataType = 'array';
173 }
174 if ($dataType === null) {
175 throw new InvalidArgumentTypeException(sprintf('The argument type for parameter $%s of method %s->%s() could not be detected.', $parameterName, static::class, $this->commandMethodName), 1306755296);
176 }
177 $defaultValue = (isset($parameterInfo['defaultValue']) ? $parameterInfo['defaultValue'] : null);
178 $this->arguments->addNewArgument($parameterName, $dataType, ($parameterInfo['optional'] === false), $defaultValue);
179 }
180 }
181
182 /**
183 * Maps arguments delivered by the request object to the local controller arguments.
184 */
185 protected function mapRequestArgumentsToControllerArguments()
186 {
187 /** @var Argument $argument */
188 foreach ($this->arguments as $argument) {
189 $argumentName = $argument->getName();
190 if ($this->request->hasArgument($argumentName)) {
191 $argument->setValue($this->request->getArgument($argumentName));
192 continue;
193 }
194 if (!$argument->isRequired()) {
195 continue;
196 }
197 $argumentValue = null;
198 $commandArgumentDefinition = $this->objectManager->get(CommandArgumentDefinition::class, $argumentName, true, null);
199 while ($argumentValue === null) {
200 $argumentValue = $this->output->ask(sprintf('<comment>Please specify the required argument "%s":</comment> ', $commandArgumentDefinition->getDashedName()));
201 }
202 $argument->setValue($argumentValue);
203 }
204 }
205
206 /**
207 * Initializes and ensures authenticated backend access
208 */
209 protected function initializeBackendAuthentication()
210 {
211 $backendUserAuthentication = $this->getBackendUserAuthentication();
212 if ($backendUserAuthentication !== null) {
213 $backendUserAuthentication->backendCheckLogin();
214 }
215 }
216
217 /**
218 * Forwards the request to another command and / or CommandController.
219 *
220 * Request is directly transferred to the other command / controller
221 * without the need for a new request.
222 *
223 * @param string $commandName
224 * @param string $controllerObjectName
225 * @param array $arguments
226 * @throws StopActionException
227 */
228 protected function forward($commandName, $controllerObjectName = null, array $arguments = [])
229 {
230 $this->request->setDispatched(false);
231 $this->request->setControllerCommandName($commandName);
232 if ($controllerObjectName !== null) {
233 $this->request->setControllerObjectName($controllerObjectName);
234 }
235 $this->request->setArguments($arguments);
236
237 $this->arguments->removeAll();
238 throw new StopActionException('forward', 1476107661);
239 }
240
241 /**
242 * Calls the specified command method and passes the arguments.
243 *
244 * If the command returns a string, it is appended to the content in the
245 * response object. If the command doesn't return anything and a valid
246 * view exists, the view is rendered automatically.
247 */
248 protected function callCommandMethod()
249 {
250 $preparedArguments = [];
251 /** @var Argument $argument */
252 foreach ($this->arguments as $argument) {
253 $preparedArguments[] = $argument->getValue();
254 }
255 $commandResult = call_user_func_array([$this, $this->commandMethodName], $preparedArguments);
256 if (is_string($commandResult) && $commandResult !== '') {
257 $this->response->appendContent($commandResult);
258 } elseif (is_object($commandResult) && method_exists($commandResult, '__toString')) {
259 $this->response->appendContent((string)$commandResult);
260 }
261 }
262
263 /**
264 * Outputs specified text to the console window
265 * You can specify arguments that will be passed to the text via sprintf
266 *
267 * @see http://www.php.net/sprintf
268 * @param string $text Text to output
269 * @param array $arguments Optional arguments to use for sprintf
270 */
271 protected function output($text, array $arguments = [])
272 {
273 $this->output->output($text, $arguments);
274 }
275
276 /**
277 * Outputs specified text to the console window and appends a line break
278 *
279 * @param string $text Text to output
280 * @param array $arguments Optional arguments to use for sprintf
281 * @see output()
282 */
283 protected function outputLine($text = '', array $arguments = [])
284 {
285 $this->output->outputLine($text, $arguments);
286 }
287
288 /**
289 * Formats the given text to fit into MAXIMUM_LINE_LENGTH and outputs it to the
290 * console window
291 *
292 * @param string $text Text to output
293 * @param array $arguments Optional arguments to use for sprintf
294 * @param int $leftPadding The number of spaces to use for indentation
295 * @see outputLine()
296 */
297 protected function outputFormatted($text = '', array $arguments = [], $leftPadding = 0)
298 {
299 $this->output->outputFormatted($text, $arguments, $leftPadding);
300 }
301
302 /**
303 * Exits the CLI through the dispatcher
304 * An exit status code can be specified @see http://www.php.net/exit
305 *
306 * @param int $exitCode Exit code to return on exit
307 * @throws StopActionException
308 */
309 protected function quit($exitCode = 0)
310 {
311 $this->response->setExitCode($exitCode);
312 throw new StopActionException('quit', 1476107681);
313 }
314
315 /**
316 * Sends the response and exits the CLI without any further code execution
317 * Should be used for commands that flush code caches.
318 *
319 * @param int $exitCode Exit code to return on exit
320 */
321 protected function sendAndExit($exitCode = 0)
322 {
323 $this->response->send();
324 exit($exitCode);
325 }
326
327 /**
328 * Returns the global BackendUserAuthentication object.
329 *
330 * @return BackendUserAuthentication|null
331 */
332 protected function getBackendUserAuthentication()
333 {
334 return isset($GLOBALS['BE_USER']) ? $GLOBALS['BE_USER'] : null;
335 }
336 }