1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
17:
18: App::uses('TaskCollection', 'Console');
19: App::uses('ConsoleOutput', 'Console');
20: App::uses('ConsoleInput', 'Console');
21: App::uses('ConsoleInputSubcommand', 'Console');
22: App::uses('ConsoleOptionParser', 'Console');
23: App::uses('ClassRegistry', 'Utility');
24: App::uses('File', 'Utility');
25: App::uses('ClassRegistry', 'Utility');
26:
27: 28: 29: 30: 31:
32: class Shell extends Object {
33:
34: 35: 36: 37: 38:
39: const VERBOSE = 2;
40:
41: 42: 43: 44: 45:
46: const NORMAL = 1;
47:
48: 49: 50: 51: 52:
53: const QUIET = 0;
54:
55: 56: 57: 58: 59:
60: public $OptionParser;
61:
62: 63: 64: 65: 66:
67: public $interactive = true;
68:
69: 70: 71: 72: 73:
74: public $params = array();
75:
76: 77: 78: 79: 80:
81: public $command;
82:
83: 84: 85: 86: 87:
88: public $args = array();
89:
90: 91: 92: 93: 94:
95: public $name = null;
96:
97: 98: 99: 100: 101: 102:
103: public $plugin = null;
104:
105: 106: 107: 108: 109: 110:
111: public $tasks = array();
112:
113: 114: 115: 116: 117:
118: public $taskNames = array();
119:
120: 121: 122: 123: 124: 125:
126: public $uses = array();
127:
128: 129: 130: 131: 132:
133: public $modelClass = null;
134:
135: 136: 137: 138: 139:
140: public $Tasks;
141:
142: 143: 144: 145: 146:
147: protected $_taskMap = array();
148:
149: 150: 151: 152: 153:
154: public $stdout;
155:
156: 157: 158: 159: 160:
161: public $stderr;
162:
163: 164: 165: 166: 167:
168: public $stdin;
169:
170: 171: 172: 173: 174: 175: 176: 177:
178: public function __construct($stdout = null, $stderr = null, $stdin = null) {
179: if (!$this->name) {
180: $this->name = Inflector::camelize(str_replace(array('Shell', 'Task'), '', get_class($this)));
181: }
182: $this->Tasks = new TaskCollection($this);
183:
184: $this->stdout = $stdout ? $stdout : new ConsoleOutput('php://stdout');
185: $this->stderr = $stderr ? $stderr : new ConsoleOutput('php://stderr');
186: $this->stdin = $stdin ? $stdin : new ConsoleInput('php://stdin');
187:
188: $this->_useLogger();
189: $parent = get_parent_class($this);
190: if ($this->tasks !== null && $this->tasks !== false) {
191: $this->_mergeVars(array('tasks'), $parent, true);
192: }
193: if (!empty($this->uses)) {
194: $this->_mergeVars(array('uses'), $parent, false);
195: }
196: }
197:
198: 199: 200: 201: 202: 203: 204: 205:
206: public function initialize() {
207: $this->_loadModels();
208: $this->loadTasks();
209: }
210:
211: 212: 213: 214: 215: 216: 217: 218: 219: 220:
221: public function startup() {
222: $this->_welcome();
223: }
224:
225: 226: 227: 228: 229:
230: protected function _welcome() {
231: $this->out();
232: $this->out(__d('cake_console', '<info>Welcome to CakePHP %s Console</info>', 'v' . Configure::version()));
233: $this->hr();
234: $this->out(__d('cake_console', 'App : %s', APP_DIR));
235: $this->out(__d('cake_console', 'Path: %s', APP));
236: $this->hr();
237: }
238:
239: 240: 241: 242: 243:
244: protected function _loadModels() {
245: if (is_array($this->uses)) {
246: list(, $this->modelClass) = pluginSplit(current($this->uses));
247: foreach ($this->uses as $modelClass) {
248: $this->loadModel($modelClass);
249: }
250: }
251: return true;
252: }
253:
254: 255: 256: 257: 258: 259:
260: public function __isset($name) {
261: if (is_array($this->uses)) {
262: foreach ($this->uses as $modelClass) {
263: list(, $class) = pluginSplit($modelClass);
264: if ($name === $class) {
265: return $this->loadModel($modelClass);
266: }
267: }
268: }
269: }
270:
271: 272: 273: 274: 275: 276: 277: 278:
279: public function loadModel($modelClass = null, $id = null) {
280: if ($modelClass === null) {
281: $modelClass = $this->modelClass;
282: }
283:
284: $this->uses = ($this->uses) ? (array)$this->uses : array();
285: if (!in_array($modelClass, $this->uses)) {
286: $this->uses[] = $modelClass;
287: }
288:
289: list($plugin, $modelClass) = pluginSplit($modelClass, true);
290: if (!isset($this->modelClass)) {
291: $this->modelClass = $modelClass;
292: }
293:
294: $this->{$modelClass} = ClassRegistry::init(array(
295: 'class' => $plugin . $modelClass, 'alias' => $modelClass, 'id' => $id
296: ));
297: if (!$this->{$modelClass}) {
298: throw new MissingModelException($modelClass);
299: }
300: return true;
301: }
302:
303: 304: 305: 306: 307:
308: public function loadTasks() {
309: if ($this->tasks === true || empty($this->tasks) || empty($this->Tasks)) {
310: return true;
311: }
312: $this->_taskMap = TaskCollection::normalizeObjectArray((array)$this->tasks);
313: $this->taskNames = array_merge($this->taskNames, array_keys($this->_taskMap));
314: return true;
315: }
316:
317: 318: 319: 320: 321: 322: 323:
324: public function hasTask($task) {
325: return isset($this->_taskMap[Inflector::camelize($task)]);
326: }
327:
328: 329: 330: 331: 332: 333: 334:
335: public function hasMethod($name) {
336: try {
337: $method = new ReflectionMethod($this, $name);
338: if (!$method->isPublic() || substr($name, 0, 1) === '_') {
339: return false;
340: }
341: if ($method->getDeclaringClass()->name === 'Shell') {
342: return false;
343: }
344: return true;
345: } catch (ReflectionException $e) {
346: return false;
347: }
348: }
349:
350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370:
371: public function dispatchShell() {
372: $args = func_get_args();
373: if (is_string($args[0]) && count($args) === 1) {
374: $args = explode(' ', $args[0]);
375: }
376:
377: $Dispatcher = new ShellDispatcher($args, false);
378: return $Dispatcher->dispatch();
379: }
380:
381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399:
400: public function runCommand($command, $argv) {
401: $isTask = $this->hasTask($command);
402: $isMethod = $this->hasMethod($command);
403: $isMain = $this->hasMethod('main');
404:
405: if ($isTask || $isMethod && $command !== 'execute') {
406: array_shift($argv);
407: }
408:
409: $this->OptionParser = $this->getOptionParser();
410: try {
411: list($this->params, $this->args) = $this->OptionParser->parse($argv, $command);
412: } catch (ConsoleException $e) {
413: $this->out($this->OptionParser->help($command));
414: return false;
415: }
416:
417: if (!empty($this->params['quiet'])) {
418: $this->_useLogger(false);
419: }
420: if (!empty($this->params['plugin'])) {
421: CakePlugin::load($this->params['plugin']);
422: }
423: $this->command = $command;
424: if (!empty($this->params['help'])) {
425: return $this->_displayHelp($command);
426: }
427:
428: if (($isTask || $isMethod || $isMain) && $command !== 'execute') {
429: $this->startup();
430: }
431:
432: if ($isTask) {
433: $command = Inflector::camelize($command);
434: return $this->{$command}->runCommand('execute', $argv);
435: }
436: if ($isMethod) {
437: return $this->{$command}();
438: }
439: if ($isMain) {
440: return $this->main();
441: }
442: $this->out($this->OptionParser->help($command));
443: return false;
444: }
445:
446: 447: 448: 449: 450: 451:
452: protected function _displayHelp($command) {
453: $format = 'text';
454: if (!empty($this->args[0]) && $this->args[0] === 'xml') {
455: $format = 'xml';
456: $this->stdout->outputAs(ConsoleOutput::RAW);
457: } else {
458: $this->_welcome();
459: }
460: return $this->out($this->OptionParser->help($command, $format));
461: }
462:
463: 464: 465: 466: 467: 468: 469: 470:
471: public function getOptionParser() {
472: $name = ($this->plugin ? $this->plugin . '.' : '') . $this->name;
473: $parser = new ConsoleOptionParser($name);
474: return $parser;
475: }
476:
477: 478: 479: 480: 481: 482:
483: public function __get($name) {
484: if (empty($this->{$name}) && in_array($name, $this->taskNames)) {
485: $properties = $this->_taskMap[$name];
486: $this->{$name} = $this->Tasks->load($properties['class'], $properties['settings']);
487: $this->{$name}->args =& $this->args;
488: $this->{$name}->params =& $this->params;
489: $this->{$name}->initialize();
490: $this->{$name}->loadTasks();
491: }
492: return $this->{$name};
493: }
494:
495: 496: 497: 498: 499: 500: 501: 502: 503:
504: public function in($prompt, $options = null, $default = null) {
505: if (!$this->interactive) {
506: return $default;
507: }
508: $originalOptions = $options;
509: $in = $this->_getInput($prompt, $originalOptions, $default);
510:
511: if ($options && is_string($options)) {
512: if (strpos($options, ',')) {
513: $options = explode(',', $options);
514: } elseif (strpos($options, '/')) {
515: $options = explode('/', $options);
516: } else {
517: $options = array($options);
518: }
519: }
520: if (is_array($options)) {
521: $options = array_merge(
522: array_map('strtolower', $options),
523: array_map('strtoupper', $options),
524: $options
525: );
526: while ($in === '' || !in_array($in, $options)) {
527: $in = $this->_getInput($prompt, $originalOptions, $default);
528: }
529: }
530: return $in;
531: }
532:
533: 534: 535: 536: 537: 538: 539: 540:
541: protected function _getInput($prompt, $options, $default) {
542: if (!is_array($options)) {
543: $printOptions = '';
544: } else {
545: $printOptions = '(' . implode('/', $options) . ')';
546: }
547:
548: if ($default === null) {
549: $this->stdout->write('<question>' . $prompt . '</question>' . " $printOptions \n" . '> ', 0);
550: } else {
551: $this->stdout->write('<question>' . $prompt . '</question>' . " $printOptions \n" . "[$default] > ", 0);
552: }
553: $result = $this->stdin->read();
554:
555: if ($result === false) {
556: return $this->_stop(1);
557: }
558: $result = trim($result);
559:
560: if ($default !== null && ($result === '' || $result === null)) {
561: return $default;
562: }
563: return $result;
564: }
565:
566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581:
582: public function wrapText($text, $options = array()) {
583: return String::wrap($text, $options);
584: }
585:
586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602:
603: public function out($message = null, $newlines = 1, $level = Shell::NORMAL) {
604: $currentLevel = Shell::NORMAL;
605: if (!empty($this->params['verbose'])) {
606: $currentLevel = Shell::VERBOSE;
607: }
608: if (!empty($this->params['quiet'])) {
609: $currentLevel = Shell::QUIET;
610: }
611: if ($level <= $currentLevel) {
612: return $this->stdout->write($message, $newlines);
613: }
614: return true;
615: }
616:
617: 618: 619: 620: 621: 622: 623: 624: 625:
626: public function err($message = null, $newlines = 1) {
627: $this->stderr->write($message, $newlines);
628: }
629:
630: 631: 632: 633: 634: 635: 636:
637: public function nl($multiplier = 1) {
638: return str_repeat(ConsoleOutput::LF, $multiplier);
639: }
640:
641: 642: 643: 644: 645: 646: 647: 648:
649: public function hr($newlines = 0, $width = 63) {
650: $this->out(null, $newlines);
651: $this->out(str_repeat('-', $width));
652: $this->out(null, $newlines);
653: }
654:
655: 656: 657: 658: 659: 660: 661: 662: 663:
664: public function error($title, $message = null) {
665: $this->err(__d('cake_console', '<error>Error:</error> %s', $title));
666:
667: if (!empty($message)) {
668: $this->err($message);
669: }
670: return $this->_stop(1);
671: }
672:
673: 674: 675: 676: 677: 678:
679: public function clear() {
680: if (empty($this->params['noclear'])) {
681: if (DS === '/') {
682: passthru('clear');
683: } else {
684: passthru('cls');
685: }
686: }
687: }
688:
689: 690: 691: 692: 693: 694: 695: 696:
697: public function createFile($path, $contents) {
698: $path = str_replace(DS . DS, DS, $path);
699:
700: $this->out();
701:
702: if (is_file($path) && empty($this->params['force']) && $this->interactive === true) {
703: $this->out(__d('cake_console', '<warning>File `%s` exists</warning>', $path));
704: $key = $this->in(__d('cake_console', 'Do you want to overwrite?'), array('y', 'n', 'q'), 'n');
705:
706: if (strtolower($key) === 'q') {
707: $this->out(__d('cake_console', '<error>Quitting</error>.'), 2);
708: return $this->_stop();
709: } elseif (strtolower($key) !== 'y') {
710: $this->out(__d('cake_console', 'Skip `%s`', $path), 2);
711: return false;
712: }
713: } else {
714: $this->out(__d('cake_console', 'Creating file %s', $path));
715: }
716:
717: $File = new File($path, true);
718: if ($File->exists() && $File->writable()) {
719: $data = $File->prepare($contents);
720: $File->write($data);
721: $this->out(__d('cake_console', '<success>Wrote</success> `%s`', $path));
722: return true;
723: }
724:
725: $this->err(__d('cake_console', '<error>Could not write to `%s`</error>.', $path), 2);
726: return false;
727: }
728:
729: 730: 731: 732: 733:
734: protected function _checkUnitTest() {
735: if (class_exists('PHPUnit_Framework_TestCase')) {
736: return true;
737:
738: } elseif (@include 'PHPUnit' . DS . 'Autoload.php') {
739:
740: return true;
741: } elseif (App::import('Vendor', 'phpunit', array('file' => 'PHPUnit' . DS . 'Autoload.php'))) {
742: return true;
743: }
744:
745: $prompt = __d('cake_console', 'PHPUnit is not installed. Do you want to bake unit test files anyway?');
746: $unitTest = $this->in($prompt, array('y', 'n'), 'y');
747: $result = strtolower($unitTest) === 'y' || strtolower($unitTest) === 'yes';
748:
749: if ($result) {
750: $this->out();
751: $this->out(__d('cake_console', 'You can download PHPUnit from %s', 'http://phpunit.de'));
752: }
753: return $result;
754: }
755:
756: 757: 758: 759: 760: 761: 762:
763: public function shortPath($file) {
764: $shortPath = str_replace(ROOT, null, $file);
765: $shortPath = str_replace('..' . DS, '', $shortPath);
766: return str_replace(DS . DS, DS, $shortPath);
767: }
768:
769: 770: 771: 772: 773: 774:
775: protected function _controllerPath($name) {
776: return Inflector::underscore($name);
777: }
778:
779: 780: 781: 782: 783: 784:
785: protected function _controllerName($name) {
786: return Inflector::pluralize(Inflector::camelize($name));
787: }
788:
789: 790: 791: 792: 793: 794:
795: protected function _modelName($name) {
796: return Inflector::camelize(Inflector::singularize($name));
797: }
798:
799: 800: 801: 802: 803: 804:
805: protected function _modelKey($name) {
806: return Inflector::underscore($name) . '_id';
807: }
808:
809: 810: 811: 812: 813: 814:
815: protected function _modelNameFromKey($key) {
816: return Inflector::camelize(str_replace('_id', '', $key));
817: }
818:
819: 820: 821: 822: 823: 824:
825: protected function _singularName($name) {
826: return Inflector::variable(Inflector::singularize($name));
827: }
828:
829: 830: 831: 832: 833: 834:
835: protected function _pluralName($name) {
836: return Inflector::variable(Inflector::pluralize($name));
837: }
838:
839: 840: 841: 842: 843: 844:
845: protected function _singularHumanName($name) {
846: return Inflector::humanize(Inflector::underscore(Inflector::singularize($name)));
847: }
848:
849: 850: 851: 852: 853: 854:
855: protected function _pluralHumanName($name) {
856: return Inflector::humanize(Inflector::underscore($name));
857: }
858:
859: 860: 861: 862: 863: 864:
865: protected function _pluginPath($pluginName) {
866: if (CakePlugin::loaded($pluginName)) {
867: return CakePlugin::path($pluginName);
868: }
869: return current(App::path('plugins')) . $pluginName . DS;
870: }
871:
872: 873: 874: 875: 876: 877: 878: 879:
880: protected function _useLogger($enable = true) {
881: if (!$enable) {
882: CakeLog::drop('stdout');
883: CakeLog::drop('stderr');
884: return;
885: }
886: CakeLog::config('stdout', array(
887: 'engine' => 'Console',
888: 'types' => array('notice', 'info'),
889: 'stream' => $this->stdout,
890: ));
891: CakeLog::config('stderr', array(
892: 'engine' => 'Console',
893: 'types' => array('emergency', 'alert', 'critical', 'error', 'warning', 'debug'),
894: 'stream' => $this->stderr,
895: ));
896: }
897: }
898: