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