PageRenderTime 27ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/cakephp/bake/src/Shell/Task/PluginTask.php

https://gitlab.com/alexandresgv/siteentec
PHP | 380 lines | 241 code | 35 blank | 104 comment | 18 complexity | 2807a57e9d6bd3071601d70ef9d5d96e MD5 | raw file
  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 0.1.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Bake\Shell\Task;
  16. use Cake\Console\Shell;
  17. use Cake\Core\App;
  18. use Cake\Core\Configure;
  19. use Cake\Core\Plugin;
  20. use Cake\Filesystem\File;
  21. use Cake\Filesystem\Folder;
  22. use Cake\Utility\Inflector;
  23. /**
  24. * The Plugin Task handles creating an empty plugin, ready to be used
  25. *
  26. */
  27. class PluginTask extends BakeTask
  28. {
  29. /**
  30. * Path to the bootstrap file. Changed in tests.
  31. *
  32. * @var string
  33. */
  34. public $bootstrap = null;
  35. /**
  36. * Tasks this task uses.
  37. *
  38. * @var array
  39. */
  40. public $tasks = [
  41. 'Bake.BakeTemplate'
  42. ];
  43. /**
  44. * initialize
  45. *
  46. * @return void
  47. */
  48. public function initialize()
  49. {
  50. $this->path = current(App::path('Plugin'));
  51. $this->bootstrap = ROOT . DS . 'config' . DS . 'bootstrap.php';
  52. }
  53. /**
  54. * Execution method always used for tasks
  55. *
  56. * @param string|null $name The name of the plugin to bake.
  57. * @return void
  58. */
  59. public function main($name = null)
  60. {
  61. if (empty($name)) {
  62. $this->err('<error>You must provide a plugin name in CamelCase format.</error>');
  63. $this->err('To make an "Example" plugin, run <info>`cake bake plugin Example`</info>.');
  64. return false;
  65. }
  66. $plugin = $this->_camelize($name);
  67. $pluginPath = $this->_pluginPath($plugin);
  68. if (is_dir($pluginPath)) {
  69. $this->out(sprintf('Plugin: %s already exists, no action taken', $plugin));
  70. $this->out(sprintf('Path: %s', $pluginPath));
  71. return false;
  72. }
  73. if (!$this->bake($plugin)) {
  74. $this->error(sprintf("An error occurred trying to bake: %s in %s", $plugin, $this->path . $plugin));
  75. }
  76. }
  77. /**
  78. * Bake the plugin's contents
  79. *
  80. * Also update the autoloader and the root composer.json file if it can be found
  81. *
  82. * @param string $plugin Name of the plugin in CamelCased format
  83. * @return bool
  84. */
  85. public function bake($plugin)
  86. {
  87. $pathOptions = App::path('Plugin');
  88. if (count($pathOptions) > 1) {
  89. $this->findPath($pathOptions);
  90. }
  91. $this->out(sprintf("<info>Plugin Name:</info> %s", $plugin));
  92. $this->out(sprintf("<info>Plugin Directory:</info> %s", $this->path . $plugin));
  93. $this->hr();
  94. $looksGood = $this->in('Look okay?', ['y', 'n', 'q'], 'y');
  95. if (strtolower($looksGood) !== 'y') {
  96. return;
  97. }
  98. $this->_generateFiles($plugin, $this->path);
  99. $hasAutoloader = $this->_modifyAutoloader($plugin, $this->path);
  100. $this->_modifyBootstrap($plugin, $hasAutoloader);
  101. $this->hr();
  102. $this->out(sprintf('<success>Created:</success> %s in %s', $plugin, $this->path . $plugin), 2);
  103. return true;
  104. }
  105. /**
  106. * Update the app's bootstrap.php file.
  107. *
  108. * @param string $plugin Name of plugin
  109. * @param bool $hasAutoloader Whether or not there is an autoloader configured for
  110. * the plugin
  111. * @return void
  112. */
  113. protected function _modifyBootstrap($plugin, $hasAutoloader)
  114. {
  115. $bootstrap = new File($this->bootstrap, false);
  116. $contents = $bootstrap->read();
  117. if (!preg_match("@\n\s*Plugin::loadAll@", $contents)) {
  118. $autoload = $hasAutoloader ? null : "'autoload' => true, ";
  119. $bootstrap->append(sprintf(
  120. "\nPlugin::load('%s', [%s'bootstrap' => false, 'routes' => true]);\n",
  121. $plugin,
  122. $autoload
  123. ));
  124. $this->out('');
  125. $this->out(sprintf('%s modified', $this->bootstrap));
  126. }
  127. }
  128. /**
  129. * Generate all files for a plugin
  130. *
  131. * Find the first path which contains `src/Template/Bake/Plugin` that contains
  132. * something, and use that as the template to recursively render a plugin's
  133. * contents. Allows the creation of a bake them containing a `Plugin` folder
  134. * to provide customized bake output for plugins.
  135. *
  136. * @param string $pluginName the CamelCase name of the plugin
  137. * @param string $path the path to the plugins dir (the containing folder)
  138. * @return void
  139. */
  140. protected function _generateFiles($pluginName, $path)
  141. {
  142. $namespace = str_replace('/', '\\', $pluginName);
  143. $name = $pluginName;
  144. $vendor = 'your-name-here';
  145. if (strpos($pluginName, '/') !== false) {
  146. list($vendor, $name) = explode('/', $pluginName);
  147. }
  148. $package = $vendor . '/' . $name;
  149. $this->BakeTemplate->set([
  150. 'package' => $package,
  151. 'namespace' => $namespace,
  152. 'plugin' => $pluginName,
  153. 'routePath' => Inflector::dasherize($pluginName),
  154. 'path' => $path,
  155. 'root' => ROOT,
  156. ]);
  157. $root = $path . $pluginName . DS;
  158. $paths = [];
  159. if (!empty($this->params['theme'])) {
  160. $paths[] = Plugin::path($this->params['theme']) . 'src/Template/';
  161. }
  162. $paths = array_merge($paths, Configure::read('App.paths.templates'));
  163. $paths[] = Plugin::path('Bake') . 'src/Template/';
  164. do {
  165. $templatesPath = array_shift($paths) . 'Bake/Plugin';
  166. $templatesDir = new Folder($templatesPath);
  167. $templates = $templatesDir->findRecursive('.*\.ctp');
  168. } while (!$templates);
  169. sort($templates);
  170. foreach ($templates as $template) {
  171. $template = substr($template, strrpos($template, 'Plugin') + 7, -4);
  172. $this->_generateFile($template, $root);
  173. }
  174. }
  175. /**
  176. * Generate a file
  177. *
  178. * @param string $template The template to render
  179. * @param string $root The path to the plugin's root
  180. * @return void
  181. */
  182. protected function _generateFile($template, $root)
  183. {
  184. $this->out(sprintf('Generating %s file...', $template));
  185. $out = $this->BakeTemplate->generate('Plugin/' . $template);
  186. $this->createFile($root . $template, $out);
  187. }
  188. /**
  189. * Modifies App's composer.json to include the plugin and tries to call
  190. * composer dump-autoload to refresh the autoloader cache
  191. *
  192. * @param string $plugin Name of plugin
  193. * @param string $path The path to save the phpunit.xml file to.
  194. * @return bool True if composer could be modified correctly
  195. */
  196. protected function _modifyAutoloader($plugin, $path)
  197. {
  198. $file = $this->_rootComposerFilePath();
  199. if (!file_exists($file)) {
  200. $this->out(sprintf('<info>Main composer file %s not found</info>', $file));
  201. return false;
  202. }
  203. $autoloadPath = str_replace(ROOT, '.', $this->path);
  204. $autoloadPath = str_replace('\\', '/', $autoloadPath);
  205. $namespace = str_replace('/', '\\', $plugin);
  206. $config = json_decode(file_get_contents($file), true);
  207. $config['autoload']['psr-4'][$namespace . '\\'] = $autoloadPath . $plugin . "/src";
  208. $config['autoload-dev']['psr-4'][$namespace . '\\Test\\'] = $autoloadPath . $plugin . "/tests";
  209. $this->out('<info>Modifying composer autoloader</info>');
  210. $out = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
  211. $this->createFile($file, $out);
  212. $composer = $this->findComposer();
  213. if (!$composer) {
  214. $this->error('Could not locate composer. Add composer to your PATH, or use the --composer option.');
  215. return false;
  216. }
  217. try {
  218. $cwd = getcwd();
  219. // Windows makes running multiple commands at once hard.
  220. chdir(dirname($this->_rootComposerFilePath()));
  221. $command = 'php ' . escapeshellarg($composer) . ' dump-autoload';
  222. $this->callProcess($command);
  223. chdir($cwd);
  224. } catch (\RuntimeException $e) {
  225. $error = $e->getMessage();
  226. $this->error(sprintf('Could not run `composer dump-autoload`: %s', $error));
  227. return false;
  228. }
  229. return true;
  230. }
  231. /**
  232. * The path to the main application's composer file
  233. *
  234. * This is a test isolation wrapper
  235. *
  236. * @return string the abs file path
  237. */
  238. protected function _rootComposerFilePath()
  239. {
  240. return ROOT . DS . 'composer.json';
  241. }
  242. /**
  243. * find and change $this->path to the user selection
  244. *
  245. * @param array $pathOptions The list of paths to look in.
  246. * @return void
  247. */
  248. public function findPath(array $pathOptions)
  249. {
  250. $valid = false;
  251. foreach ($pathOptions as $i => $path) {
  252. if (!is_dir($path)) {
  253. unset($pathOptions[$i]);
  254. }
  255. }
  256. $pathOptions = array_values($pathOptions);
  257. $max = count($pathOptions);
  258. if ($max === 0) {
  259. $this->err('No valid plugin paths found! Please configure a plugin path that exists.');
  260. throw new \RuntimeException();
  261. }
  262. if ($max === 1) {
  263. $this->path = $pathOptions[0];
  264. return;
  265. }
  266. while (!$valid) {
  267. foreach ($pathOptions as $i => $option) {
  268. $this->out($i + 1 . '. ' . $option);
  269. }
  270. $prompt = 'Choose a plugin path from the paths above.';
  271. $choice = $this->in($prompt, null, 1);
  272. if ((int)$choice > 0 && (int)$choice <= $max) {
  273. $valid = true;
  274. }
  275. }
  276. $this->path = $pathOptions[$choice - 1];
  277. }
  278. /**
  279. * Gets the option parser instance and configures it.
  280. *
  281. * @return \Cake\Console\ConsoleOptionParser
  282. */
  283. public function getOptionParser()
  284. {
  285. $parser = parent::getOptionParser();
  286. $parser->description(
  287. 'Create the directory structure, AppController class and testing setup for a new plugin. ' .
  288. 'Can create plugins in any of your bootstrapped plugin paths.'
  289. )->addArgument('name', [
  290. 'help' => 'CamelCased name of the plugin to create.'
  291. ])->addOption('composer', [
  292. 'default' => ROOT . DS . 'composer.phar',
  293. 'help' => 'The path to the composer executable.'
  294. ])->removeOption('plugin');
  295. return $parser;
  296. }
  297. /**
  298. * Uses either the CLI option or looks in $PATH and cwd for composer.
  299. *
  300. * @return string|false Either the path to composer or false if it cannot be found.
  301. */
  302. public function findComposer()
  303. {
  304. if (!empty($this->params['composer'])) {
  305. $path = $this->params['composer'];
  306. if (file_exists($path)) {
  307. return $path;
  308. }
  309. }
  310. $composer = false;
  311. $path = env('PATH');
  312. if (!empty($path)) {
  313. $paths = explode(PATH_SEPARATOR, $path);
  314. $composer = $this->_searchPath($paths);
  315. }
  316. return $composer;
  317. }
  318. /**
  319. * Search the $PATH for composer.
  320. *
  321. * @param array $path The paths to search.
  322. * @return string|bool
  323. */
  324. protected function _searchPath($path)
  325. {
  326. $composer = ['composer.phar', 'composer'];
  327. foreach ($path as $dir) {
  328. foreach ($composer as $cmd) {
  329. if (is_file($dir . DS . $cmd)) {
  330. $this->_io->verbose('Found composer executable in ' . $dir);
  331. return $dir . DS . $cmd;
  332. }
  333. }
  334. }
  335. return false;
  336. }
  337. }