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