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