path = current(App::path('Plugin'));
$this->bootstrap = ROOT . DS . 'config' . DS . 'bootstrap.php';
}
/**
* Execution method always used for tasks
*
* @param string $name The name of the plugin to bake.
* @return void
*/
public function main($name = null) {
if (empty($name)) {
$this->err('You must provide a plugin name in CamelCase format.');
$this->err('To make an "Example" plugin, run `cake bake plugin Example`.');
return false;
}
$plugin = $this->_camelize($name);
$pluginPath = $this->_pluginPath($plugin);
if (is_dir($pluginPath)) {
$this->out(sprintf('Plugin: %s already exists, no action taken', $plugin));
$this->out(sprintf('Path: %s', $pluginPath));
return false;
}
if (!$this->bake($plugin)) {
$this->error(sprintf("An error occurred trying to bake: %s in %s", $plugin, $this->path . $plugin));
}
}
/**
* Bake the plugin, create directories and files
*
* @param string $plugin Name of the plugin in CamelCased format
* @return bool
*/
public function bake($plugin) {
$pathOptions = App::path('Plugin');
if (count($pathOptions) > 1) {
$this->findPath($pathOptions);
}
$this->hr();
$this->out(sprintf("Plugin Name: %s", $plugin));
$this->out(sprintf("Plugin Directory: %s", $this->path . $plugin));
$this->hr();
$classBase = 'src';
$looksGood = $this->in('Look okay?', ['y', 'n', 'q'], 'y');
if (strtolower($looksGood) === 'y') {
$Folder = new Folder($this->path . $plugin);
$directories = [
'config',
$classBase . DS . 'Model' . DS . 'Behavior',
$classBase . DS . 'Model' . DS . 'Table',
$classBase . DS . 'Model' . DS . 'Entity',
$classBase . DS . 'Shell' . DS . 'Task',
$classBase . DS . 'Controller' . DS . 'Component',
$classBase . DS . 'View' . DS . 'Helper',
$classBase . DS . 'Template',
'tests' . DS . 'TestCase' . DS . 'Controller' . DS . 'Component',
'tests' . DS . 'TestCase' . DS . 'View' . DS . 'Helper',
'tests' . DS . 'TestCase' . DS . 'Model' . DS . 'Behavior',
'tests' . DS . 'Fixture',
'webroot'
];
foreach ($directories as $directory) {
$dirPath = $this->path . $plugin . DS . $directory;
$Folder->create($dirPath);
new File($dirPath . DS . 'empty', true);
}
foreach ($Folder->messages() as $message) {
$this->out($message, 1, Shell::VERBOSE);
}
$errors = $Folder->errors();
if (!empty($errors)) {
foreach ($errors as $message) {
$this->error($message);
}
return false;
}
$controllerFileName = 'AppController.php';
$out = "createFile($this->path . $plugin . DS . $classBase . DS . 'Controller' . DS . $controllerFileName, $out);
$emptyFile = $this->path . 'empty';
$this->_deleteEmptyFile($emptyFile);
$hasAutoloader = $this->_modifyAutoloader($plugin, $this->path);
$this->_generateRoutes($plugin, $this->path);
$this->_modifyBootstrap($plugin, $hasAutoloader);
$this->_generatePhpunitXml($plugin, $this->path);
$this->_generateTestBootstrap($plugin, $this->path);
$this->hr();
$this->out(sprintf('Created: %s in %s', $plugin, $this->path . $plugin), 2);
}
return true;
}
/**
* Update the app's bootstrap.php file.
*
* @param string $plugin Name of plugin
* @param bool $hasAutoloader Whether or not there is an autoloader configured for
* the plugin
* @return void
*/
protected function _modifyBootstrap($plugin, $hasAutoloader) {
$bootstrap = new File($this->bootstrap, false);
$contents = $bootstrap->read();
if (!preg_match("@\n\s*Plugin::loadAll@", $contents)) {
$autoload = $hasAutoloader ? null : "'autoload' => true, ";
$bootstrap->append(sprintf(
"\nPlugin::load('%s', [%s'bootstrap' => false, 'routes' => true]);\n",
$plugin,
$autoload
));
$this->out('');
$this->out(sprintf('%s modified', $this->bootstrap));
}
}
/**
* Generate a routes file for the plugin being baked.
*
* @param string $plugin The plugin to generate routes for.
* @param string $path The path to save the routes.php file in.
* @return void
*/
protected function _generateRoutes($plugin, $path) {
$this->Template->set([
'plugin' => $plugin,
]);
$this->out('Generating routes.php file...');
$out = $this->Template->generate('config', 'routes');
$file = $path . $plugin . DS . 'config' . DS . 'routes.php';
$this->createFile($file, $out);
}
/**
* Generate a phpunit.xml stub for the plugin.
*
* @param string $plugin Name of plugin
* @param string $path The path to save the phpunit.xml file to.
* @return void
*/
protected function _generatePhpunitXml($plugin, $path) {
$this->Template->set([
'plugin' => $plugin,
'path' => $path
]);
$this->out('Generating phpunit.xml file...');
$out = $this->Template->generate('test', 'phpunit.xml');
$file = $path . $plugin . DS . 'phpunit.xml';
$this->createFile($file, $out);
}
/**
* Generate a Test/bootstrap.php stub for the plugin.
*
* @param string $plugin Name of plugin
* @param string $path The path to save the phpunit.xml file to.
* @return void
*/
protected function _generateTestBootstrap($plugin, $path) {
$this->Template->set([
'plugin' => $plugin,
'path' => $path,
'root' => ROOT
]);
$this->out('Generating tests/bootstrap.php file...');
$out = $this->Template->generate('test', 'bootstrap');
$file = $path . $plugin . DS . 'tests' . DS . 'bootstrap.php';
$this->createFile($file, $out);
}
/**
* Modifies App's composer.json to include the plugin and tries to call
* composer dump-autoload to refresh the autoloader cache
*
* @param string $plugin Name of plugin
* @param string $path The path to save the phpunit.xml file to.
* @return bool True if composer could be modified correctly
*/
protected function _modifyAutoloader($plugin, $path) {
$path = dirname($path);
$file = $path . DS . 'composer.json';
if (!file_exists($file)) {
return false;
}
$config = json_decode(file_get_contents($file), true);
$config['autoload']['psr-4'][$plugin . '\\'] = "./plugins/$plugin/src";
$config['autoload-dev']['psr-4'][$plugin . '\\Test\\'] = "./plugins/$plugin/tests";
$this->out('Modifying composer autoloader');
$out = json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$this->createFile($file, $out);
$composer = $this->Project->findComposer();
if (!$composer) {
$this->error('Could not locate composer, Add composer to your PATH, or use the -composer option.');
return false;
}
try {
chdir($path);
$command = 'php ' . escapeshellarg($composer) . ' dump-autoload';
$this->callProcess($command);
} catch (\RuntimeException $e) {
$error = $e->getMessage();
$this->error(sprintf('Could not run `composer dump-autoload`: %s', $error));
return false;
}
return true;
}
/**
* find and change $this->path to the user selection
*
* @param array $pathOptions The list of paths to look in.
* @return void
*/
public function findPath(array $pathOptions) {
$valid = false;
foreach ($pathOptions as $i => $path) {
if (!is_dir($path)) {
unset($pathOptions[$i]);
}
}
$pathOptions = array_values($pathOptions);
$max = count($pathOptions);
while (!$valid) {
foreach ($pathOptions as $i => $option) {
$this->out($i + 1 . '. ' . $option);
}
$prompt = 'Choose a plugin path from the paths above.';
$choice = $this->in($prompt, null, 1);
if ((int)$choice > 0 && (int)$choice <= $max) {
$valid = true;
}
}
$this->path = $pathOptions[$choice - 1];
}
/**
* Gets the option parser instance and configures it.
*
* @return \Cake\Console\ConsoleOptionParser
*/
public function getOptionParser() {
$parser = parent::getOptionParser();
$parser->description(
'Create the directory structure, AppController class and testing setup for a new plugin. ' .
'Can create plugins in any of your bootstrapped plugin paths.'
)->addArgument('name', [
'help' => 'CamelCased name of the plugin to create.'
])->addOption('composer', [
'default' => ROOT . '/composer.phar',
'help' => 'The path to the composer executable.'
])->removeOption('plugin');
return $parser;
}
}