ProjectTask.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since CakePHP(tm) v 1.2
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Console\Command\Task;
  16. use Cake\Console\Shell;
  17. use Cake\Core\App;
  18. use Cake\Core\Configure;
  19. use Cake\Utility\File;
  20. use Cake\Utility\Folder;
  21. use Cake\Utility\Security;
  22. use Cake\Utility\String;
  23. /**
  24. * Task class for creating new project apps and plugins
  25. *
  26. */
  27. class ProjectTask extends Shell {
  28. /**
  29. * App path (used in testing).
  30. *
  31. * @var string
  32. */
  33. public $appPath = null;
  34. /**
  35. * Checks that given project path does not already exist, and
  36. * finds the app directory in it. Then it calls bake() with that information.
  37. *
  38. * @return mixed
  39. */
  40. public function execute() {
  41. $project = null;
  42. if (isset($this->args[0])) {
  43. $project = $this->args[0];
  44. } else {
  45. $appContents = array_diff(scandir(APP), array('.', '..'));
  46. if (empty($appContents)) {
  47. $suggestedPath = rtrim(APP, DS);
  48. } else {
  49. $suggestedPath = APP . 'MyApp';
  50. }
  51. }
  52. while (!$project) {
  53. $prompt = __d('cake_console', 'What is the path to the project you want to bake?');
  54. $project = $this->in($prompt, null, $suggestedPath);
  55. }
  56. $namespace = basename($project);
  57. if (!preg_match('/^\w[\w\d_]+$/', $namespace)) {
  58. $this->err(__d('cake_console', 'Project Name/Namespace needs to start with a letter and can only contain letters, digits and underscore'));
  59. $this->args = array();
  60. return $this->execute();
  61. }
  62. if ($project && !Folder::isAbsolute($project) && isset($_SERVER['PWD'])) {
  63. $project = $_SERVER['PWD'] . DS . $project;
  64. }
  65. $response = false;
  66. while (!$response && is_dir($project) === true && file_exists($project . 'Config' . 'boostrap.php')) {
  67. $prompt = __d('cake_console', '<warning>A project already exists in this location:</warning> %s Overwrite?', $project);
  68. $response = $this->in($prompt, array('y', 'n'), 'n');
  69. if (strtolower($response) === 'n') {
  70. $response = $project = false;
  71. }
  72. }
  73. if ($project === false) {
  74. $this->out(__d('cake_console', 'Aborting project creation.'));
  75. return;
  76. }
  77. if ($this->bake($project)) {
  78. $this->out(__d('cake_console', '<success>Project baked successfully!</success>'));
  79. return $path;
  80. }
  81. }
  82. /**
  83. * Uses composer to generate a new package using the cakephp/app project.
  84. *
  85. * @param string $path Project path
  86. * @return mixed
  87. */
  88. public function bake($path) {
  89. $composer = $this->params['composer'];
  90. if (!file_exists($composer)) {
  91. $this->error(__d('cake_console', 'Cannot bake project. Could not find composer at "%s".', $composer));
  92. return false;
  93. }
  94. $this->out(__d('cake_console', '<info>Downloading a new cakephp app from packagist.org</info>'));
  95. $command = 'php ' . escapeshellarg($composer) . ' create-project --dev cakephp/app ' . escapeshellarg($path);
  96. $descriptorSpec = array(
  97. 0 => array('pipe', 'r'),
  98. 1 => array('pipe', 'w'),
  99. 2 => array('pipe', 'w')
  100. );
  101. $process = proc_open(
  102. $command,
  103. $descriptorSpec,
  104. $pipes
  105. );
  106. if (!is_resource($process)) {
  107. $this->error(__d('cake_console', 'Could not start subprocess.'));
  108. return false;
  109. }
  110. $output = $error = '';
  111. fclose($pipes[0]);
  112. $output = stream_get_contents($pipes[1]);
  113. fclose($pipes[1]);
  114. $error = stream_get_contents($pipes[2]);
  115. fclose($pipes[2]);
  116. proc_close($process);
  117. if ($error) {
  118. $this->error(__d('cake_console', 'Installation from packagist.org failed with: %s', $error));
  119. return false;
  120. }
  121. $this->out($output);
  122. return true;
  123. }
  124. /**
  125. * Enables Configure::read('Routing.prefixes') in /app/Config/routes.php
  126. *
  127. * @param string $name Name to use as admin routing
  128. * @return boolean Success
  129. */
  130. public function cakeAdmin($name) {
  131. $path = $this->appPath ?: APP;
  132. $path .= 'Config/';
  133. $File = new File($path . 'routes.php');
  134. $contents = $File->read();
  135. if (preg_match('%(\s*[/]*Configure::write\(\'Routing.prefixes\',[\s\'a-z,\)\(]*\);)%', $contents, $match)) {
  136. $result = str_replace($match[0], "\n" . 'Configure::write(\'Routing.prefixes\', array(\'' . $name . '\'));', $contents);
  137. if ($File->write($result)) {
  138. Configure::write('Routing.prefixes', array($name));
  139. return true;
  140. }
  141. }
  142. return false;
  143. }
  144. /**
  145. * Checks for Configure::read('Routing.prefixes') and forces user to input it if not enabled
  146. *
  147. * @return string Admin route to use
  148. */
  149. public function getPrefix() {
  150. $admin = '';
  151. $prefixes = Configure::read('Routing.prefixes');
  152. if (!empty($prefixes)) {
  153. if (count($prefixes) === 1) {
  154. return $prefixes[0] . '_';
  155. }
  156. if ($this->interactive) {
  157. $this->out();
  158. $this->out(__d('cake_console', 'You have more than one routing prefix configured'));
  159. }
  160. $options = array();
  161. foreach ($prefixes as $i => $prefix) {
  162. $options[] = $i + 1;
  163. if ($this->interactive) {
  164. $this->out($i + 1 . '. ' . $prefix);
  165. }
  166. }
  167. $selection = $this->in(__d('cake_console', 'Please choose a prefix to bake with.'), $options, 1);
  168. return $prefixes[$selection - 1] . '_';
  169. }
  170. if ($this->interactive) {
  171. $this->hr();
  172. $this->out(__d('cake_console', 'You need to enable %s in %s to use prefix routing.',
  173. 'Configure::write(\'Routing.prefixes\', array(\'admin\'))',
  174. '/app/Config/routes.php'));
  175. $this->out(__d('cake_console', 'What would you like the prefix route to be?'));
  176. $this->out(__d('cake_console', 'Example: %s', 'www.example.com/admin/controller'));
  177. while (!$admin) {
  178. $admin = $this->in(__d('cake_console', 'Enter a routing prefix:'), null, 'admin');
  179. }
  180. if ($this->cakeAdmin($admin) !== true) {
  181. $this->out(__d('cake_console', '<error>Unable to write to</error> %s.', '/app/Config/routes.php'));
  182. $this->out(__d('cake_console', 'You need to enable %s in %s to use prefix routing.',
  183. 'Configure::write(\'Routing.prefixes\', array(\'admin\'))',
  184. '/app/Config/routes.php'));
  185. return $this->_stop();
  186. }
  187. return $admin . '_';
  188. }
  189. return '';
  190. }
  191. /**
  192. * get the option parser.
  193. *
  194. * @return ConsoleOptionParser
  195. */
  196. public function getOptionParser() {
  197. $parser = parent::getOptionParser();
  198. return $parser->description(
  199. __d('cake_console', 'Generate a new CakePHP project skeleton.')
  200. )->addArgument('name', array(
  201. 'help' => __d('cake_console', 'Application directory to make, if it starts with "/" the path is absolute.')
  202. ))->addOption('empty', array(
  203. 'boolean' => true,
  204. 'help' => __d('cake_console', 'Create empty files in each of the directories. Good if you are using git')
  205. ))->addOption('theme', array(
  206. 'short' => 't',
  207. 'help' => __d('cake_console', 'Theme to use when baking code.')
  208. ))->addOption('composer', array(
  209. 'default' => ROOT . '/composer.phar',
  210. 'help' => __d('cake_console', 'The path to the composer executable.')
  211. ));
  212. }
  213. }