1<?php
2namespace TYPO3\CMS\Extbase\Mvc\Cli;
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
17use TYPO3\CMS\Extbase\Reflection\ClassSchema;
18
19/**
20 * Represents a Command
21 *
22 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Use symfony/console commands instead.
23 */
24class Command
25{
26    /**
27     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
28     */
29    protected $objectManager;
30
31    /**
32     * @var string
33     */
34    protected $controllerClassName;
35
36    /**
37     * @var string
38     */
39    protected $controllerCommandName;
40
41    /**
42     * @var string
43     */
44    protected $commandIdentifier;
45
46    /**
47     * Name of the extension to which this command belongs
48     *
49     * @var string
50     */
51    protected $extensionName;
52
53    /**
54     * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
55     */
56    protected $reflectionService;
57
58    /**
59     * @var ClassSchema
60     */
61    protected $classSchema;
62
63    /**
64     * @var string
65     */
66    protected $controllerCommandMethod;
67
68    /**
69     * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
70     */
71    public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
72    {
73        $this->objectManager = $objectManager;
74    }
75
76    /**
77     * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
78     */
79    public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
80    {
81        $this->reflectionService = $reflectionService;
82    }
83
84    /**
85     * Constructor
86     *
87     * @param string $controllerClassName Class name of the controller providing the command
88     * @param string $controllerCommandName Command name, i.e. the method name of the command, without the "Command" suffix
89     * @throws \InvalidArgumentException
90     */
91    public function __construct($controllerClassName, $controllerCommandName)
92    {
93        $this->controllerClassName = $controllerClassName;
94        $this->controllerCommandName = $controllerCommandName;
95        $this->controllerCommandMethod = $this->controllerCommandName . 'Command';
96        $classNameParts = explode('\\', $controllerClassName);
97        if (isset($classNameParts[0]) && $classNameParts[0] === 'TYPO3' && isset($classNameParts[1]) && $classNameParts[1] === 'CMS') {
98            $classNameParts[0] .= '\\' . $classNameParts[1];
99            unset($classNameParts[1]);
100            $classNameParts = array_values($classNameParts);
101        }
102        $numberOfClassNameParts = count($classNameParts);
103        if ($numberOfClassNameParts < 3) {
104            throw new \InvalidArgumentException(
105                'Controller class names must at least consist of three parts: vendor, extension name and path.',
106                1438782187
107            );
108        }
109        if (strpos($classNameParts[$numberOfClassNameParts - 1], 'CommandController') === false) {
110            throw new \InvalidArgumentException(
111                'Invalid controller class name "' . $controllerClassName . '". Class name must end with "CommandController".',
112                1305100019
113            );
114        }
115
116        $this->extensionName = $classNameParts[1];
117        $extensionKey = \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($this->extensionName);
118        $this->commandIdentifier = strtolower($extensionKey . ':' . substr($classNameParts[$numberOfClassNameParts - 1], 0, -17) . ':' . $controllerCommandName);
119    }
120
121    public function initializeObject()
122    {
123        $this->classSchema = $this->reflectionService->getClassSchema($this->controllerClassName);
124    }
125
126    /**
127     * @return string
128     */
129    public function getControllerClassName()
130    {
131        return $this->controllerClassName;
132    }
133
134    /**
135     * @return string
136     */
137    public function getControllerCommandName()
138    {
139        return $this->controllerCommandName;
140    }
141
142    /**
143     * Returns the command identifier for this command
144     *
145     * @return string The command identifier for this command, following the pattern extensionname:controllername:commandname
146     */
147    public function getCommandIdentifier()
148    {
149        return $this->commandIdentifier;
150    }
151
152    /**
153     * Returns the name of the extension to which this command belongs
154     *
155     * @return string
156     */
157    public function getExtensionName()
158    {
159        return $this->extensionName;
160    }
161
162    /**
163     * Returns a short description of this command
164     *
165     * @return string A short description
166     */
167    public function getShortDescription()
168    {
169        $lines = explode(LF, $this->classSchema->getMethod($this->controllerCommandMethod)['description']);
170        return !empty($lines) ? trim($lines[0]) : '<no description available>';
171    }
172
173    /**
174     * Returns a longer description of this command
175     * This is the complete method description except for the first line which can be retrieved via getShortDescription()
176     * If The command description only consists of one line, an empty string is returned
177     *
178     * @return string A longer description of this command
179     */
180    public function getDescription()
181    {
182        $lines = explode(LF, $this->classSchema->getMethod($this->controllerCommandMethod)['description']);
183        array_shift($lines);
184        $descriptionLines = [];
185        foreach ($lines as $line) {
186            $trimmedLine = trim($line);
187            if ($descriptionLines !== [] || $trimmedLine !== '') {
188                $descriptionLines[] = $trimmedLine;
189            }
190        }
191        return implode(LF, $descriptionLines);
192    }
193
194    /**
195     * Returns TRUE if this command expects required and/or optional arguments, otherwise FALSE
196     *
197     * @return bool
198     */
199    public function hasArguments()
200    {
201        return !empty($this->classSchema->getMethod($this->controllerCommandMethod)['params']);
202    }
203
204    /**
205     * Returns an array of \TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition that contains
206     * information about required/optional arguments of this command.
207     * If the command does not expect any arguments, an empty array is returned
208     *
209     * @return array<\TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition>
210     */
211    public function getArgumentDefinitions()
212    {
213        if (!$this->hasArguments()) {
214            return [];
215        }
216        $commandArgumentDefinitions = [];
217        $commandParameters = $this->classSchema->getMethod($this->controllerCommandMethod)['params'];
218        $commandParameterTags = $this->classSchema->getMethod($this->controllerCommandMethod)['tags']['param'];
219        $i = 0;
220        foreach ($commandParameters as $commandParameterName => $commandParameterDefinition) {
221            $explodedAnnotation = preg_split('/\s+/', $commandParameterTags[$i], 3);
222            $description = !empty($explodedAnnotation[2]) ? $explodedAnnotation[2] : '';
223            $required = $commandParameterDefinition['optional'] !== true;
224            $commandArgumentDefinitions[] = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Cli\CommandArgumentDefinition::class, $commandParameterName, $required, $description);
225            $i++;
226        }
227        return $commandArgumentDefinitions;
228    }
229
230    /**
231     * Tells if this command is internal and thus should not be exposed through help texts, user documentation etc.
232     * Internall commands are still accessible through the regular command line interface, but should not be used
233     * by users.
234     *
235     * @return bool
236     */
237    public function isInternal()
238    {
239        return isset($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['internal']);
240    }
241
242    /**
243     * Tells if this command is meant to be used on CLI only.
244     *
245     * @return bool
246     */
247    public function isCliOnly()
248    {
249        return isset($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['cli']);
250    }
251
252    /**
253     * Tells if this command flushes all caches and thus needs special attention in the interactive shell.
254     *
255     * Note that neither this method nor the @flushesCaches annotation is currently part of the official API.
256     *
257     * @return bool
258     *
259     * @deprecated will be removed in TYPO3 v10.0.
260     */
261    public function isFlushingCaches()
262    {
263        trigger_error(
264            'Method isFlushingCaches() will be removed in TYPO3 v10.0. Do not call from other extension.',
265            E_USER_DEPRECATED
266        );
267        return isset($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['flushesCaches']);
268    }
269
270    /**
271     * Returns an array of command identifiers which were specified in the "@see"
272     * annotation of a command method.
273     *
274     * @return array
275     */
276    public function getRelatedCommandIdentifiers()
277    {
278        if (!isset($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['see'])) {
279            return [];
280        }
281        $relatedCommandIdentifiers = [];
282        foreach ($this->classSchema->getMethod($this->controllerCommandMethod)['tags']['see'] as $tagValue) {
283            if (preg_match('/^[\\w\\d\\.]+:[\\w\\d]+:[\\w\\d]+$/', $tagValue) === 1) {
284                $relatedCommandIdentifiers[] = $tagValue;
285            }
286        }
287        return $relatedCommandIdentifiers;
288    }
289}
290