1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: namespace Cake\Shell\Task;
16:
17: use Cake\Console\Shell;
18: use Cake\Core\App;
19: use Cake\Core\Plugin;
20: use Cake\Filesystem\Folder;
21: use Cake\Utility\Hash;
22: use Cake\Utility\Inflector;
23: use ReflectionClass;
24: use ReflectionMethod;
25:
26: 27: 28:
29: class CommandTask extends Shell
30: {
31:
32: 33: 34: 35: 36:
37: public function getShellList()
38: {
39: $skipFiles = ['app'];
40: $hiddenCommands = ['command_list', 'completion'];
41: $plugins = Plugin::loaded();
42: $shellList = array_fill_keys($plugins, null) + ['CORE' => null, 'app' => null];
43:
44: $appPath = App::path('Shell');
45: $shellList = $this->_findShells($shellList, $appPath[0], 'app', $skipFiles);
46:
47: $appPath = App::path('Command');
48: $shellList = $this->_findShells($shellList, $appPath[0], 'app', $skipFiles);
49:
50: $skipCore = array_merge($skipFiles, $hiddenCommands, $shellList['app']);
51: $corePath = dirname(__DIR__);
52: $shellList = $this->_findShells($shellList, $corePath, 'CORE', $skipCore);
53:
54: $corePath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'Command';
55: $shellList = $this->_findShells($shellList, $corePath, 'CORE', $skipCore);
56:
57: foreach ($plugins as $plugin) {
58: $pluginPath = Plugin::classPath($plugin) . 'Shell';
59: $shellList = $this->_findShells($shellList, $pluginPath, $plugin, []);
60: }
61:
62: return array_filter($shellList);
63: }
64:
65: 66: 67: 68: 69: 70: 71: 72: 73:
74: protected function _findShells($shellList, $path, $key, $skip)
75: {
76: $shells = $this->_scanDir($path);
77:
78: return $this->_appendShells($key, $shells, $shellList, $skip);
79: }
80:
81: 82: 83: 84: 85: 86: 87: 88: 89:
90: protected function _appendShells($type, $shells, $shellList, $skip)
91: {
92: if (!isset($shellList[$type])) {
93: $shellList[$type] = [];
94: }
95:
96: foreach ($shells as $shell) {
97: $name = Inflector::underscore(preg_replace('/(Shell|Command)$/', '', $shell));
98: if (!in_array($name, $skip, true)) {
99: $shellList[$type][] = $name;
100: }
101: }
102: sort($shellList[$type]);
103:
104: return $shellList;
105: }
106:
107: 108: 109: 110: 111: 112: 113:
114: protected function _scanDir($dir)
115: {
116: $dir = new Folder($dir);
117: $contents = $dir->read(true, true);
118: if (empty($contents[1])) {
119: return [];
120: }
121: $shells = [];
122: foreach ($contents[1] as $file) {
123: if (substr($file, -4) !== '.php') {
124: continue;
125: }
126: $shells[] = substr($file, 0, -4);
127: }
128:
129: return $shells;
130: }
131:
132: 133: 134: 135: 136:
137: public function commands()
138: {
139: $shellList = $this->getShellList();
140: $flatten = Hash::flatten($shellList);
141: $duplicates = array_intersect($flatten, array_unique(array_diff_key($flatten, array_unique($flatten))));
142: $duplicates = Hash::expand($duplicates);
143:
144: $options = [];
145: foreach ($shellList as $type => $commands) {
146: foreach ($commands as $shell) {
147: $prefix = '';
148: if (!in_array(strtolower($type), ['app', 'core']) &&
149: isset($duplicates[$type]) &&
150: in_array($shell, $duplicates[$type])
151: ) {
152: $prefix = $type . '.';
153: }
154:
155: $options[] = $prefix . $shell;
156: }
157: }
158:
159: return $options;
160: }
161:
162: 163: 164: 165: 166: 167: 168:
169: public function subCommands($commandName)
170: {
171: $Shell = $this->getShell($commandName);
172:
173: if (!$Shell) {
174: return [];
175: }
176:
177: $taskMap = $this->Tasks->normalizeArray((array)$Shell->tasks);
178: $return = array_keys($taskMap);
179: $return = array_map('Cake\Utility\Inflector::underscore', $return);
180:
181: $shellMethodNames = ['main', 'help', 'getOptionParser', 'initialize', 'runCommand'];
182:
183: $baseClasses = ['Object', 'Shell', 'AppShell'];
184:
185: $Reflection = new ReflectionClass($Shell);
186: $methods = $Reflection->getMethods(ReflectionMethod::IS_PUBLIC);
187: $methodNames = [];
188: foreach ($methods as $method) {
189: $declaringClass = $method->getDeclaringClass()->getShortName();
190: if (!in_array($declaringClass, $baseClasses)) {
191: $methodNames[] = $method->getName();
192: }
193: }
194:
195: $return = array_merge($return, array_diff($methodNames, $shellMethodNames));
196: sort($return);
197:
198: return $return;
199: }
200:
201: 202: 203: 204: 205: 206:
207: public function getShell($commandName)
208: {
209: list($pluginDot, $name) = pluginSplit($commandName, true);
210:
211: if (in_array(strtolower($pluginDot), ['app.', 'core.'])) {
212: $commandName = $name;
213: $pluginDot = '';
214: }
215:
216: if (!in_array($commandName, $this->commands()) && (empty($pluginDot) && !in_array($name, $this->commands()))) {
217: return false;
218: }
219:
220: if (empty($pluginDot)) {
221: $shellList = $this->getShellList();
222:
223: if (!in_array($commandName, $shellList['app']) && !in_array($commandName, $shellList['CORE'])) {
224: unset($shellList['CORE'], $shellList['app']);
225: foreach ($shellList as $plugin => $commands) {
226: if (in_array($commandName, $commands)) {
227: $pluginDot = $plugin . '.';
228: break;
229: }
230: }
231: }
232: }
233:
234: $name = Inflector::camelize($name);
235: $pluginDot = Inflector::camelize($pluginDot);
236: $class = App::className($pluginDot . $name, 'Shell', 'Shell');
237: if (!$class) {
238: return false;
239: }
240:
241:
242: $Shell = new $class();
243: $Shell->plugin = trim($pluginDot, '.');
244: $Shell->initialize();
245:
246: return $Shell;
247: }
248:
249: 250: 251: 252: 253: 254: 255: 256:
257: public function options($commandName, $subCommandName = '')
258: {
259: $Shell = $this->getShell($commandName);
260:
261: if (!$Shell) {
262: return [];
263: }
264:
265: $parser = $Shell->getOptionParser();
266:
267: if (!empty($subCommandName)) {
268: $subCommandName = Inflector::camelize($subCommandName);
269: if ($Shell->hasTask($subCommandName)) {
270: $parser = $Shell->{$subCommandName}->getOptionParser();
271: } else {
272: return [];
273: }
274: }
275:
276: $options = [];
277: $array = $parser->options();
278:
279: foreach ($array as $name => $obj) {
280: $options[] = "--$name";
281: $short = $obj->short();
282: if ($short) {
283: $options[] = "-$short";
284: }
285: }
286:
287: return $options;
288: }
289: }
290: