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