diff --git a/app/config/bootstrap.php b/app/config/bootstrap.php index 924147237..2c9b9f45d 100644 --- a/app/config/bootstrap.php +++ b/app/config/bootstrap.php @@ -20,22 +20,27 @@ * @since CakePHP(tm) v 0.10.8.2117 * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ + /** * The settings below can be used to set additional paths to models, views and controllers. * This is related to Ticket #470 (https://trac.cakephp.org/ticket/470) * - * $modelPaths = array('/full/path/to/models/', '/next/full/path/to/models/'); - * $viewPaths = array('/full/path/to/views/', '/next/full/path/to/views/'); - * $controllerPaths = array(/full/path/to/controllers/', '/next/full/path/to/controllers/'); - * $pluginPaths = array('/full/path/to/plugins/', '/next/full/path/to/plugins/'); - * $behaviorPaths = array('/full/path/to/behaviors/', '/next/full/path/to/behaviors/'); - * $componentPaths = array('/full/path/to/components/', '/next/full/path/to/components/'); - * $helperPaths = array('/full/path/to/helpers/', '/next/full/path/to/helpers/'); - * $vendorPaths = array('/full/path/to/vendors/', '/next/full/path/to/vendors/'); - * $shellPaths = array('/full/path/to/shells/', '/next/full/path/to/shells/'); - * $localePaths = array('/full/path/to/locale/', '/next/full/path/to/locale/'; + * App::build(array( + * 'plugins' => array('/full/path/to/plugins/', '/next/full/path/to/plugins/'), + * 'models' => array('/full/path/to/models/', '/next/full/path/to/models/'), + * 'views' => array('/full/path/to/views/', '/next/full/path/to/views/'), + * 'controllers' => array(/full/path/to/controllers/', '/next/full/path/to/controllers/'), + * 'datasources' => array('/full/path/to/datasources/', '/next/full/path/to/datasources/'), + * 'behaviors' => array('/full/path/to/behaviors/', '/next/full/path/to/behaviors/'), + * 'components' => array('/full/path/to/components/', '/next/full/path/to/components/'), + * 'helpers' => array('/full/path/to/helpers/', '/next/full/path/to/helpers/'), + * 'vendors' => array('/full/path/to/vendors/', '/next/full/path/to/vendors/'), + * 'shells' => array('/full/path/to/shells/', '/next/full/path/to/shells/'), + * 'locales' => array('/full/path/to/locale/', '/next/full/path/to/locale/') + * )); * */ + /** * As of 1.3, additional rules for the inflector are added below * diff --git a/app/webroot/test.php b/app/webroot/test.php index 725469d93..4cdbd8c28 100644 --- a/app/webroot/test.php +++ b/app/webroot/test.php @@ -93,7 +93,7 @@ if (!include(CORE_PATH . 'cake' . DS . 'bootstrap.php')) { trigger_error("CakePHP core could not be found. Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php. It should point to the directory containing your " . DS . "cake core directory and your " . DS . "vendors root directory.", E_USER_ERROR); } -$corePath = Configure::corePaths('cake'); +$corePath = App::core('cake'); if (isset($corePath[0])) { define('TEST_CAKE_CORE_INCLUDE_PATH', rtrim($corePath[0], DS) . DS); } else { diff --git a/cake/basics.php b/cake/basics.php index cae6e65bd..b6b07c761 100644 --- a/cake/basics.php +++ b/cake/basics.php @@ -1,5 +1,6 @@ tags around * the output of given array. Similar to debug(). @@ -314,6 +331,7 @@ if (!function_exists('array_combine')) { echo ''; } } + /** * Display parameters. * @@ -329,6 +347,7 @@ if (!function_exists('array_combine')) { } return $p; } + /** * Merge a group of arrays * @@ -350,6 +369,7 @@ if (!function_exists('array_combine')) { } return $r; } + /** * Gets an environment variable from available sources, and provides emulation * for unsupported or inconsistent environment variables (i.e. DOCUMENT_ROOT on @@ -362,8 +382,8 @@ if (!function_exists('array_combine')) { */ function env($key) { if ($key == 'HTTPS') { - if (isset($_SERVER) && !empty($_SERVER)) { - return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'); + if (isset($_SERVER['HTTPS'])) { + return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'); } return (strpos(env('SCRIPT_URI'), 'https://') === 0); } @@ -426,6 +446,7 @@ if (!function_exists('array_combine')) { return null; } if (!function_exists('file_put_contents')) { + /** * Writes data into file. * @@ -453,6 +474,7 @@ if (!function_exists('file_put_contents')) { return false; } } + /** * Reads/writes temporary data to cache files or session. * @@ -504,6 +526,7 @@ if (!function_exists('file_put_contents')) { } return $data; } + /** * Used to delete files in the cache directories, or clear contents of cache directories * @@ -566,6 +589,7 @@ if (!function_exists('file_put_contents')) { } return false; } + /** * Recursively strips slashes from all values in an array * @@ -583,6 +607,7 @@ if (!function_exists('file_put_contents')) { } return $values; } + /** * Returns a translated string if one is found; Otherwise, the submitted message. * @@ -605,6 +630,7 @@ if (!function_exists('file_put_contents')) { return I18n::translate($singular); } } + /** * Returns correct plural form of message identified by $singular and $plural for count $count. * Some languages have more than one form for plural messages dependent on the count. @@ -624,11 +650,12 @@ if (!function_exists('file_put_contents')) { } if ($return === false) { - echo I18n::translate($singular, $plural, null, 5, $count); + echo I18n::translate($singular, $plural, null, 6, $count); } else { - return I18n::translate($singular, $plural, null, 5, $count); + return I18n::translate($singular, $plural, null, 6, $count); } } + /** * Allows you to override the current domain for a single message lookup. * @@ -651,6 +678,7 @@ if (!function_exists('file_put_contents')) { return I18n::translate($msg, null, $domain); } } + /** * Allows you to override the current domain for a single plural message lookup. * Returns correct plural form of message identified by $singular and $plural for count $count @@ -672,11 +700,12 @@ if (!function_exists('file_put_contents')) { } if ($return === false) { - echo I18n::translate($singular, $plural, $domain, 5, $count); + echo I18n::translate($singular, $plural, $domain, 6, $count); } else { - return I18n::translate($singular, $plural, $domain, 5, $count); + return I18n::translate($singular, $plural, $domain, 6, $count); } } + /** * Allows you to override the current domain for a single message lookup. * It also allows you to specify a category. @@ -713,6 +742,7 @@ if (!function_exists('file_put_contents')) { return I18n::translate($msg, null, $domain, $category); } } + /** * Allows you to override the current domain for a single plural message lookup. * It also allows you to specify a category. @@ -723,13 +753,13 @@ if (!function_exists('file_put_contents')) { * Valid categories are: LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES and LC_ALL. * * Note that the category must be specified with a numeric value, instead of the constant name. The values are: - * LC_CTYPE 0 - * LC_NUMERIC 1 - * LC_TIME 2 - * LC_COLLATE 3 - * LC_MONETARY 4 - * LC_MESSAGES 5 - * LC_ALL 6 + * LC_ALL 0 + * LC_COLLATE 1 + * LC_CTYPE 2 + * LC_MONETARY 3 + * LC_NUMERIC 4 + * LC_TIME 5 + * LC_MESSAGES 6 * * @param string $domain Domain * @param string $singular Singular string to translate @@ -753,6 +783,7 @@ if (!function_exists('file_put_contents')) { return I18n::translate($singular, $plural, $domain, $category, $count); } } + /** * The category argument allows a specific category of the locale settings to be used for fetching a message. * Valid categories are: LC_CTYPE, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES and LC_ALL. @@ -785,6 +816,7 @@ if (!function_exists('file_put_contents')) { return I18n::translate($msg, null, null, $category); } } + /** * Computes the difference of arrays using keys for comparison. * @@ -819,6 +851,7 @@ if (!function_exists('file_put_contents')) { return $valuesDiff; } } + /** * Computes the intersection of arrays using keys for comparison * @@ -837,6 +870,7 @@ if (!function_exists('file_put_contents')) { return $res; } } + /** * Shortcut to Log::write. * @@ -850,6 +884,7 @@ if (!function_exists('file_put_contents')) { $good = ' '; CakeLog::write('error', str_replace($bad, $good, $message)); } + /** * Searches include path for files. * @@ -870,6 +905,7 @@ if (!function_exists('file_put_contents')) { } return false; } + /** * Convert forward slashes to underscores and removes first and last underscores in a string * @@ -883,6 +919,7 @@ if (!function_exists('file_put_contents')) { $string = str_replace('/', '_', $string); return $string; } + /** * Implements http_build_query for PHP4. * @@ -922,6 +959,7 @@ if (!function_exists('file_put_contents')) { return implode($argSep, $out); } } + /** * Wraps ternary operations. If $condition is a non-empty value, $val1 is returned, otherwise $val2. * Don't use for isset() conditions, or wrap your variable with @ operator: diff --git a/cake/config/paths.php b/cake/config/paths.php index 6a6449f0e..4a29fadab 100644 --- a/cake/config/paths.php +++ b/cake/config/paths.php @@ -1,5 +1,6 @@ __construct($args); } + /** * Constructor * - * @param array $args the argv. + * The execution of the script is stopped after dispatching the request with + * a status code of either 0 or 1 according to the result of the dispatch. + * + * @param array $args the argv + * @return void + * @access public */ function __construct($args = array()) { set_time_limit(0); + $this->__initConstants(); $this->parseParams($args); $this->_initEnvironment(); $this->__buildPaths(); - $this->_stop($this->dispatch()); + $this->_stop($this->dispatch() === false ? 1 : 0); } + /** * Defines core configuration. * @@ -154,6 +176,7 @@ class ShellDispatcher { } require_once(CORE_PATH . 'cake' . DS . 'basics.php'); } + /** * Defines current working environment. * @@ -190,6 +213,7 @@ class ShellDispatcher { $this->shiftArgs(); } + /** * Builds the shell paths. * @@ -224,6 +248,7 @@ class ShellDispatcher { $this->shellPaths = array_values(array_unique(array_merge($paths, App::path('shells')))); } + /** * Initializes the environment and loads the Cake core. * @@ -259,129 +284,163 @@ class ShellDispatcher { Configure::getInstance(file_exists(CONFIGS . 'bootstrap.php')); if (!file_exists(APP_PATH . 'config' . DS . 'core.php')) { - include_once CORE_PATH . 'cake' . DS . 'console' . DS . 'templates' . DS . 'skel' . DS . 'config' . DS . 'core.php'; - Configure::buildPaths(array()); + include_once CORE_PATH . 'cake' . DS . 'console' . DS . 'libs' . DS . 'templates' . DS . 'skel' . DS . 'config' . DS . 'core.php'; + App::build(); } Configure::write('debug', 1); return true; } + +/** + * Clear the console + * + * @return void + * @access public + */ + function clear() { + if (empty($this->params['noclear'])) { + if ( DS === '/') { + passthru('clear'); + } else { + passthru('cls'); + } + } + } + /** * Dispatches a CLI request * + * @return boolean * @access public */ function dispatch() { - if (isset($this->args[0])) { - $plugin = null; - $shell = $this->args[0]; - if (strpos($shell, '.') !== false) { - list($plugin, $shell) = explode('.', $this->args[0]); - } + $arg = $this->shiftArgs(); - $this->shell = $shell; - $this->shiftArgs(); - $this->shellName = Inflector::camelize($this->shell); - $this->shellClass = $this->shellName . 'Shell'; - - if ($this->shell === 'help') { - $this->help(); - } else { - $loaded = false; - foreach ($this->shellPaths as $path) { - $this->shellPath = $path . $this->shell . '.php'; - - $isPlugin = ($plugin && strpos($path, DS . $plugin . DS . 'vendors' . DS . 'shells' . DS) !== false); - if (($isPlugin && file_exists($this->shellPath)) || (!$plugin && file_exists($this->shellPath))) { - $loaded = true; - break; - } - } - - if ($loaded) { - if (!class_exists('Shell')) { - require CONSOLE_LIBS . 'shell.php'; - } - require $this->shellPath; - if (class_exists($this->shellClass)) { - $command = null; - if (isset($this->args[0])) { - $command = $this->args[0]; - } - $this->shellCommand = Inflector::variable($command); - $shell = new $this->shellClass($this); - - if (strtolower(get_parent_class($shell)) == 'shell') { - $shell->initialize(); - $shell->loadTasks(); - - foreach ($shell->taskNames as $task) { - if (strtolower(get_parent_class($shell)) == 'shell') { - $shell->{$task}->initialize(); - $shell->{$task}->loadTasks(); - } - } - - $task = Inflector::camelize($command); - if (in_array($task, $shell->taskNames)) { - $this->shiftArgs(); - $shell->{$task}->startup(); - if (isset($this->args[0]) && $this->args[0] == 'help') { - if (method_exists($shell->{$task}, 'help')) { - $shell->{$task}->help(); - $this->_stop(); - } else { - $this->help(); - } - } - return $shell->{$task}->execute(); - } - } - - $classMethods = get_class_methods($shell); - - $privateMethod = $missingCommand = false; - if ((in_array($command, $classMethods) || in_array(strtolower($command), $classMethods)) && strpos($command, '_', 0) === 0) { - $privateMethod = true; - } - - if (!in_array($command, $classMethods) && !in_array(strtolower($command), $classMethods)) { - $missingCommand = true; - } - - $protectedCommands = array( - 'initialize','in','out','err','hr', - 'createfile', 'isdir','copydir','object','tostring', - 'requestaction','log','cakeerror', 'shelldispatcher', - '__initconstants','__initenvironment','__construct', - 'dispatch','__bootstrap','getinput','stdout','stderr','parseparams','shiftargs' - ); - - if (in_array(strtolower($command), $protectedCommands)) { - $missingCommand = true; - } - - if ($missingCommand && method_exists($shell, 'main')) { - $shell->startup(); - return $shell->main(); - } elseif (!$privateMethod && method_exists($shell, $command)) { - $this->shiftArgs(); - $shell->startup(); - return $shell->{$command}(); - } else { - $this->stderr("Unknown {$this->shellName} command '$command'.\nFor usage, try 'cake {$this->shell} help'.\n\n"); - } - } else { - $this->stderr('Class '.$this->shellClass.' could not be loaded'); - } - } else { - $this->help(); - } - } - } else { + if (!$arg) { $this->help(); + return false; } + if ($arg == 'help') { + $this->help(); + return true; + } + + if (strpos($arg, '.') !== false) { + list($plugin, $shell) = explode('.', $arg); + } else { + $plugin = null; + $shell = $arg; + } + $this->shell = $shell; + $this->shellName = Inflector::camelize($shell); + $this->shellClass = $this->shellName . 'Shell'; + + $arg = null; + + if (isset($this->args[0])) { + $arg = $this->args[0]; + $this->shellCommand = Inflector::variable($arg); + } + + $Shell = $this->_getShell($plugin); + + if (!$Shell) { + $title = sprintf(__('Error: Class %s could not be loaded.', true), $this->shellClass); + $this->stderr($title . "\n"); + return false; + } + + $methods = array(); + + if (is_a($Shell, 'Shell')) { + $Shell->initialize(); + $Shell->loadTasks(); + + foreach ($Shell->taskNames as $task) { + if (is_a($Shell->{$task}, 'Shell')) { + $Shell->{$task}->initialize(); + $Shell->{$task}->loadTasks(); + } + } + + $task = Inflector::camelize($arg); + + if (in_array($task, $Shell->taskNames)) { + $this->shiftArgs(); + $Shell->{$task}->startup(); + + if (isset($this->args[0]) && $this->args[0] == 'help') { + if (method_exists($Shell->{$task}, 'help')) { + $Shell->{$task}->help(); + } else { + $this->help(); + } + return true; + } + return $Shell->{$task}->execute(); + } + $methods = array_diff(get_class_methods('Shell'), array('help')); + } + $methods = array_diff(get_class_methods($Shell), $methods); + $added = in_array(strtolower($arg), array_map('strtolower', $methods)); + $private = $arg[0] == '_' && method_exists($Shell, $arg); + + if (!$private) { + if ($added) { + $this->shiftArgs(); + $Shell->startup(); + return $Shell->{$arg}(); + } + if (method_exists($Shell, 'main')) { + $Shell->startup(); + return $Shell->main(); + } + } + + $title = sprintf(__('Error: Unknown %1$s command %2$s.', true), $this->shellName, $arg); + $message = sprintf(__('For usage try `cake %s help`', true), $this->shell); + $this->stderr($title . "\n" . $message . "\n"); + return false; } +/** + * Get shell to use, either plugin shell or application shell + * + * All paths in the shellPaths property are searched. + * shell, shellPath and shellClass properties are taken into account. + * + * @param string $plugin Optionally the name of a plugin + * @return mixed False if no shell could be found or an object on success + * @access protected + */ + function _getShell($plugin = null) { + foreach ($this->shellPaths as $path) { + $this->shellPath = $path . $this->shell . '.php'; + $pluginShellPath = DS . $plugin . DS . 'vendors' . DS . 'shells' . DS; + + if ((strpos($path, $pluginShellPath) !== false || !$plugin) && file_exists($this->shellPath)) { + $loaded = true; + break; + } + } + if (!isset($loaded)) { + return false; + } + + if (!class_exists('Shell')) { + require CONSOLE_LIBS . 'shell.php'; + } + + if (!class_exists($this->shellClass)) { + require $this->shellPath; + } + if (!class_exists($this->shellClass)) { + return false; + } + $Shell = new $this->shellClass($this); + return $Shell; + } + /** * Prompts the user for input, and returns it. * @@ -415,6 +474,7 @@ class ShellDispatcher { } return $result; } + /** * Outputs to the stdout filehandle. * @@ -429,6 +489,7 @@ class ShellDispatcher { fwrite($this->stdout, $string); } } + /** * Outputs to the stderr filehandle. * @@ -436,8 +497,9 @@ class ShellDispatcher { * @access public */ function stderr($string) { - fwrite($this->stderr, 'Error: '. $string); + fwrite($this->stderr, $string); } + /** * Parses command line options * @@ -446,13 +508,15 @@ class ShellDispatcher { */ function parseParams($params) { $this->__parseParams($params); - $defaults = array('app' => 'app', 'root' => dirname(dirname(dirname(__FILE__))), 'working' => null, 'webroot' => 'webroot'); - $params = array_merge($defaults, array_intersect_key($this->params, $defaults)); - - $isWin = array_filter(array_map('strpos', $params, array('\\'))); - + $isWin = false; + foreach ($defaults as $default => $value) { + if (strpos($params[$default], '\\') !== false) { + $isWin = true; + break; + } + } $params = str_replace('\\', '/', $params); if (!empty($params['working']) && (!isset($this->args[0]) || isset($this->args[0]) && $this->args[0]{0} !== '.')) { @@ -464,7 +528,7 @@ class ShellDispatcher { } } - if ($params['app'][0] == '/' || preg_match('/([a-zA-Z])(:)/i', $params['app'], $matches)) { + if ($params['app'][0] == '/' || preg_match('/([a-z])(:)/i', $params['app'], $matches)) { $params['root'] = dirname($params['app']); } elseif (strpos($params['app'], '/')) { $params['root'] .= '/' . dirname($params['app']); @@ -479,8 +543,9 @@ class ShellDispatcher { $this->params = array_merge($this->params, $params); } + /** - * Helper for recursively paraing params + * Helper for recursively parsing params * * @return array params * @access private @@ -510,26 +575,24 @@ class ShellDispatcher { } } } + /** * Removes first argument and shifts other arguments up * - * @return boolean False if there are no arguments + * @return mixed Null if there are no arguments otherwise the shifted argument * @access public */ function shiftArgs() { - if (empty($this->args)) { - return false; - } - unset($this->args[0]); - $this->args = array_values($this->args); - return true; + return array_shift($this->args); } + /** * Shows console help * * @access public */ function help() { + $this->clear(); $this->stdout("\nWelcome to CakePHP v" . Configure::version() . " Console"); $this->stdout("---------------------------------------------------------------"); $this->stdout("Current Paths:"); @@ -559,6 +622,7 @@ class ShellDispatcher { } else { sort($shells); foreach ($shells as $shell) { + if ($shell !== 'shell.php') { $this->stdout("\t " . str_replace('.php', '', $shell)); } @@ -568,8 +632,8 @@ class ShellDispatcher { } $this->stdout("\nTo run a command, type 'cake shell_name [args]'"); $this->stdout("To get help on a specific command, type 'cake shell_name help'"); - $this->_stop(); } + /** * Stop execution of the current script * diff --git a/cake/console/error.php b/cake/console/error.php index 9c88a40b8..4d9a9f7a5 100644 --- a/cake/console/error.php +++ b/cake/console/error.php @@ -1,5 +1,6 @@ stderr($code . $name . $message."\n"); $this->_stop(); } + /** * Convenience method to display a 404 page. * @@ -84,6 +91,7 @@ class ErrorHandler extends Object { 'message' => sprintf(__("The requested address %s was not found on this server.", true), $url, $message))); $this->_stop(); } + /** * Renders the Missing Controller web page. * @@ -96,6 +104,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing Controller '%s'", true), $controllerName)); $this->_stop(); } + /** * Renders the Missing Action web page. * @@ -107,6 +116,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing Method '%s' in '%s'", true), $action, $className)); $this->_stop(); } + /** * Renders the Private Action web page. * @@ -118,6 +128,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Trying to access private method '%s' in '%s'", true), $action, $className)); $this->_stop(); } + /** * Renders the Missing Table web page. * @@ -129,6 +140,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing database table '%s' for model '%s'", true), $table, $className)); $this->_stop(); } + /** * Renders the Missing Database web page. * @@ -139,6 +151,7 @@ class ErrorHandler extends Object { $this->stderr(__("Missing Database", true)); $this->_stop(); } + /** * Renders the Missing View web page. * @@ -150,6 +163,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing View '%s' for '%s' in '%s'", true), $file, $action, $className)); $this->_stop(); } + /** * Renders the Missing Layout web page. * @@ -161,6 +175,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing Layout '%s'", true), $file)); $this->_stop(); } + /** * Renders the Database Connection web page. * @@ -172,6 +187,7 @@ class ErrorHandler extends Object { $this->stderr(__("Missing Database Connection. Try 'cake bake'", true)); $this->_stop(); } + /** * Renders the Missing Helper file web page. * @@ -183,6 +199,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing Helper file '%s' for '%s'", true), $file, Inflector::camelize($helper))); $this->_stop(); } + /** * Renders the Missing Helper class web page. * @@ -194,6 +211,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing Helper class '%s' in '%s'", true), Inflector::camelize($helper), $file)); $this->_stop(); } + /** * Renders the Missing Component file web page. * @@ -205,6 +223,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing Component file '%s' for '%s'", true), $file, Inflector::camelize($component))); $this->_stop(); } + /** * Renders the Missing Component class web page. * @@ -216,6 +235,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing Component class '%s' in '%s'", true), Inflector::camelize($component), $file)); $this->_stop(); } + /** * Renders the Missing Model class web page. * @@ -227,6 +247,7 @@ class ErrorHandler extends Object { $this->stderr(sprintf(__("Missing model '%s'", true), $className)); $this->_stop(); } + /** * Outputs to the stdout filehandle. * @@ -241,6 +262,7 @@ class ErrorHandler extends Object { fwrite($this->stdout, $string); } } + /** * Outputs to the stderr filehandle. * diff --git a/cake/console/libs/acl.php b/cake/console/libs/acl.php index 5a7873da8..c62f91ba3 100644 --- a/cake/console/libs/acl.php +++ b/cake/console/libs/acl.php @@ -1,5 +1,6 @@ '", true); $this->out($out); } + /** * Creates an ARO/ACO node * @@ -179,6 +188,7 @@ class AclShell extends Shell { $this->err(sprintf(__("There was a problem creating a new %s '%s'.", true), $class, $this->args[2])); } } + /** * Delete an ARO/ACO node. * @@ -216,6 +226,7 @@ class AclShell extends Shell { $this->out(sprintf(__("Node parent set to %s", true), $this->args[2]) . "\n", true); } } + /** * Get path to specified ARO/ACO node. * @@ -234,6 +245,7 @@ class AclShell extends Shell { $this->out(str_repeat(' ', $i) . "[" . $nodes[$i][$class]['id'] . "]" . $nodes[$i][$class]['alias'] . "\n"); } } + /** * Check permission for a given ARO to a given ACO. * @@ -249,6 +261,7 @@ class AclShell extends Shell { $this->out(sprintf(__("%s is not allowed.", true), $aro), true); } } + /** * Grant permission for a given ARO to a given ACO. * @@ -264,6 +277,7 @@ class AclShell extends Shell { $this->out(__("Permission was not granted.", true), true); } } + /** * Deny access for an ARO to an ACO. * @@ -279,6 +293,7 @@ class AclShell extends Shell { $this->out(__("Permission was not denied.", true), true); } } + /** * Set an ARO to inhermit permission to an ACO. * @@ -294,6 +309,7 @@ class AclShell extends Shell { $this->out(__("Permission was not inherited.", true), true); } } + /** * Show a specific ARO/ACO node. * @@ -345,6 +361,7 @@ class AclShell extends Shell { } $this->hr(); } + /** * Initialize ACL database. * @@ -354,6 +371,7 @@ class AclShell extends Shell { $this->Dispatch->args = array('schema', 'run', 'create', 'DbAcl'); $this->Dispatch->dispatch(); } + /** * Show help screen. * @@ -431,6 +449,7 @@ class AclShell extends Shell { $this->out(sprintf(__("Command '%s' not found", true), $this->args[0])); } } + /** * Check that first argument specifies a valid Node type (ARO/ACO) * @@ -444,6 +463,7 @@ class AclShell extends Shell { $this->error(sprintf(__("Missing/Unknown node type: '%s'", true), $this->args[1]), __('Please specify which ACL object type you wish to create.', true)); } } + /** * Checks that given node exists * @@ -465,6 +485,7 @@ class AclShell extends Shell { } return $possibility; } + /** * get params for standard Acl methods * diff --git a/cake/console/libs/api.php b/cake/console/libs/api.php index 7ff0d73e0..8da6f9092 100644 --- a/cake/console/libs/api.php +++ b/cake/console/libs/api.php @@ -1,5 +1,6 @@ LIBS )); } + /** * Override main() to handle action * @@ -66,7 +70,7 @@ class ApiShell extends Shell { return $this->help(); } - $type = low($this->args[0]); + $type = strtolower($this->args[0]); if (isset($this->paths[$type])) { $path = $this->paths[$type]; @@ -202,7 +206,7 @@ class ApiShell extends Shell { if (strpos($method, '__') === false && $method[0] != '_') { $parsed[$method] = array( - 'comment' => r(array('/*', '*/', '*'), '', trim($result[1][$key])), + 'comment' => str_replace(array('/*', '*/', '*'), '', trim($result[1][$key])), 'method' => $method, 'parameters' => trim($result[3][$key]) ); diff --git a/cake/console/libs/bake.php b/cake/console/libs/bake.php index f891ca2a4..bc15e6360 100644 --- a/cake/console/libs/bake.php +++ b/cake/console/libs/bake.php @@ -1,5 +1,4 @@ command); if (isset($this->{$task}) && !in_array($task, array('Project', 'DbConfig'))) { - $path = Inflector::underscore(Inflector::pluralize($this->command)); - $this->{$task}->path = $this->params['working'] . DS . $path . DS; + if (empty($this->{$task}->path)) { + $path = Inflector::underscore(Inflector::pluralize($this->command)); + $this->{$task}->path = $this->params['working'] . DS . $path . DS; + } + if (isset($this->params['connection'])) { + $this->{$task}->connection = $this->params['connection']; + } + if (isset($this->params['plugin'])) { + $this->{$task}->plugin = $this->params['plugin']; + } if (!is_dir($this->{$task}->path)) { $this->err(sprintf(__("%s directory could not be found.\nBe sure you have created %s", true), $task, $this->{$task}->path)); $this->_stop(); } } } + /** * Override main() to handle action * @@ -82,6 +90,7 @@ class BakeShell extends Shell { $this->out('[V]iew'); $this->out('[C]ontroller'); $this->out('[P]roject'); + $this->out('[F]ixture'); $this->out('[Q]uit'); $classToBake = strtoupper($this->in(__('What would you like to Bake?', true), array('D', 'M', 'V', 'C', 'P', 'Q'))); @@ -101,6 +110,9 @@ class BakeShell extends Shell { case 'P': $this->Project->execute(); break; + case 'F': + $this->Fixture->execute(); + break; case 'Q': exit(0); break; @@ -110,28 +122,33 @@ class BakeShell extends Shell { $this->hr(); $this->main(); } + /** * Quickly bake the MVC * * @access public */ function all() { - $ds = 'default'; $this->hr(); $this->out('Bake All'); $this->hr(); - if (isset($this->params['connection'])) { - $ds = $this->params['connection']; + if (!isset($this->params['connection']) && empty($this->connection)) { + $this->connection = $this->DbConfig->getConfig(); } if (empty($this->args)) { - $name = $this->Model->getName($ds); + $this->Model->interactive = true; + $name = $this->Model->getName($this->connection); + } + + foreach (array('Model', 'Controller', 'View') as $task) { + $this->{$task}->connection = $this->connection; + $this->{$task}->interactive = false; } if (!empty($this->args[0])) { $name = $this->args[0]; - $this->Model->listAll($ds, false); } $modelExists = false; @@ -140,8 +157,8 @@ class BakeShell extends Shell { $object = new $model(); $modelExists = true; } else { - App::import('Model'); - $object = new Model(array('name' => $name, 'ds' => $ds)); + App::import('Model', 'Model', false); + $object = new Model(array('name' => $name, 'ds' => $this->connection)); } $modelBaked = $this->Model->bake($object, false); @@ -149,6 +166,7 @@ class BakeShell extends Shell { if ($modelBaked && $modelExists === false) { $this->out(sprintf(__('%s Model was baked.', true), $model)); if ($this->_checkUnitTest()) { + $this->Model->bakeFixture($model); $this->Model->bakeTest($model); } $modelExists = true; @@ -165,16 +183,13 @@ class BakeShell extends Shell { if (App::import('Controller', $controller)) { $this->View->args = array($controller); $this->View->execute(); + $this->out(sprintf(__('%s Views were baked.', true), $name)); } - $this->out(__('Bake All complete')); + $this->out(__('Bake All complete', true)); array_shift($this->args); } else { $this->err(__('Bake All could not continue without a valid model', true)); } - - if (empty($this->args)) { - $this->all(); - } $this->_stop(); } diff --git a/cake/console/libs/console.php b/cake/console/libs/console.php index b9f2e6e96..82427d9b4 100644 --- a/cake/console/libs/console.php +++ b/cake/console/libs/console.php @@ -1,5 +1,6 @@ _loadRoutes(); } + /** * Prints the help message * @@ -136,6 +143,7 @@ class ConsoleShell extends Shell { $out .= "\tRoutes show"; $this->out($out); } + /** * Override main() to handle action * @@ -269,8 +277,7 @@ class ConsoleShell extends Shell { if ($this->_isValidModel($modelToSave)) { // Extract the array of data we are trying to build list($foo, $data) = explode("->save", $command); - $badChars = array("(", ")"); - $data = str_replace($badChars, "", $data); + $data = preg_replace('/^\(*(array)?\(*(.+?)\)*$/i', '\\2', $data); $saveCommand = "\$this->{$modelToSave}->save(array('{$modelToSave}' => array({$data})));"; @eval($saveCommand); $this->out('Saved record for ' . $modelToSave); @@ -321,6 +328,7 @@ class ConsoleShell extends Shell { $command = ''; } } + /** * Tells if the specified model is included in the list of available models * @@ -331,6 +339,7 @@ class ConsoleShell extends Shell { function _isValidModel($modelToCheck) { return in_array($modelToCheck, $this->models); } + /** * Reloads the routes configuration from config/routes.php, and compiles * all routes found diff --git a/cake/console/libs/i18n.php b/cake/console/libs/i18n.php index aa5fe8b8a..5e8d547ed 100644 --- a/cake/console/libs/i18n.php +++ b/cake/console/libs/i18n.php @@ -1,5 +1,6 @@ hr(); $this->main(); } + /** * Initialize I18N database. * @@ -105,6 +112,7 @@ class I18nShell extends Shell { $this->Dispatch->args = array('schema', 'run', 'create', 'i18n'); $this->Dispatch->dispatch(); } + /** * Show help screen. * diff --git a/cake/console/libs/schema.php b/cake/console/libs/schema.php index 445ad1579..60d959721 100644 --- a/cake/console/libs/schema.php +++ b/cake/console/libs/schema.php @@ -1,5 +1,6 @@ out('Cake Schema Shell'); $this->hr(); } + /** * Override startup * @@ -85,6 +90,7 @@ class SchemaShell extends Shell { $this->Schema =& new CakeSchema(compact('name', 'path', 'file', 'connection')); } + /** * Override main * @@ -93,6 +99,7 @@ class SchemaShell extends Shell { function main() { $this->help(); } + /** * Read and output contents of schema object * path to read as second arg @@ -109,6 +116,7 @@ class SchemaShell extends Shell { $this->_stop(); } } + /** * Read database and Write schema object * accepts a connection as first arg or path to save as second arg @@ -177,6 +185,7 @@ class SchemaShell extends Shell { $this->_stop(); } } + /** * Dump Schema object to sql file * if first arg == write, file will be written to sql file @@ -199,7 +208,7 @@ class SchemaShell extends Shell { } } $db =& ConnectionManager::getDataSource($this->Schema->connection); - $contents = "#". $Schema->name ." sql generated on: " . date('Y-m-d H:m:s') . " : ". time()."\n\n"; + $contents = "#" . $Schema->name . " sql generated on: " . date('Y-m-d H:i:s') . " : " . time() . "\n\n"; $contents .= $db->dropSchema($Schema) . "\n\n". $db->createSchema($Schema); if ($write) { if (strpos($write, '.sql') === false) { @@ -217,6 +226,7 @@ class SchemaShell extends Shell { $this->out($contents); return $contents; } + /** * Run database commands: create, update * @@ -275,6 +285,7 @@ class SchemaShell extends Shell { $this->_stop(); } } + /** * Create database from Schema object * Should be called via the run method @@ -318,6 +329,7 @@ class SchemaShell extends Shell { $this->out(__('End create.', true)); } + /** * Update database with Schema object * Should be called via the run method @@ -356,6 +368,7 @@ class SchemaShell extends Shell { $this->out(__('End update.', true)); } + /** * Runs sql from __create() or __update() * @@ -397,6 +410,7 @@ class SchemaShell extends Shell { } } } + /** * Displays help contents * @@ -426,4 +440,4 @@ class SchemaShell extends Shell { $this->_stop(); } } -?> +?> \ No newline at end of file diff --git a/cake/console/libs/shell.php b/cake/console/libs/shell.php index bac6c347b..503a63111 100644 --- a/cake/console/libs/shell.php +++ b/cake/console/libs/shell.php @@ -1,5 +1,6 @@ name, $this->alias); if (!PHP5 && isset($this->args[0])) { - if (strpos($this->name, low(Inflector::camelize($this->args[0]))) !== false) { + if (strpos($this->name, strtolower(Inflector::camelize($this->args[0]))) !== false) { $dispatch->shiftArgs(); } - if (low($this->command) == low(Inflector::variable($this->args[0])) && method_exists($this, $this->command)) { + if (strtolower($this->command) == strtolower(Inflector::variable($this->args[0])) && method_exists($this, $this->command)) { $dispatch->shiftArgs(); } } $this->Dispatch =& $dispatch; } + /** * Initializes the Shell * acts as constructor for subclasses @@ -169,6 +186,7 @@ class Shell extends Object { function initialize() { $this->_loadModels(); } + /** * Starts up the the Shell * allows for checking and configuring prior to command or main execution @@ -179,18 +197,21 @@ class Shell extends Object { function startup() { $this->_welcome(); } + /** * Displays a header for the shell * * @access protected */ function _welcome() { + $this->Dispatch->clear(); $this->out("\nWelcome to CakePHP v" . Configure::version() . " Console"); $this->out("---------------------------------------------------------------"); $this->out('App : '. $this->params['app']); $this->out('Path: '. $this->params['working']); $this->hr(); } + /** * Loads database file and constructs DATABASE_CONFIG class * makes $this->DbConfig available to subclasses @@ -207,6 +228,7 @@ class Shell extends Object { $this->out('Run \'bake\' to create the database configuration'); return false; } + /** * if var $uses = true * Loads AppModel file and constructs AppModel class @@ -251,6 +273,7 @@ class Shell extends Object { } return false; } + /** * Loads tasks defined in var $tasks * @@ -297,13 +320,14 @@ class Shell extends Object { } if (!isset($this->{$taskName})) { - $this->err("Task '".$taskName."' could not be loaded"); + $this->err("Task '" . $taskName . "' could not be loaded"); $this->_stop(); } } return true; } + /** * Prompts the user for input, and returns it. * @@ -329,7 +353,7 @@ class Shell extends Object { } } if (is_array($options)) { - while ($in == '' || ($in && (!in_array(low($in), $options) && !in_array(up($in), $options)) && !in_array($in, $options))) { + while ($in == '' || ($in && (!in_array(strtolower($in), $options) && !in_array(strtoupper($in), $options)) && !in_array($in, $options))) { $in = $this->Dispatch->getInput($prompt, $options, $default); } } @@ -337,68 +361,82 @@ class Shell extends Object { return $in; } } + /** - * Outputs to the stdout filehandle. + * Outputs a single or multiple messages to stdout. * - * @param string $string String to output. - * @param boolean $newline If true, the outputs gets an added newline. + * @param mixed $message A string or a an array of strings to output + * @param mixed $after Appended to message, if true a newline is used * @access public */ - function out($string, $newline = true) { - if (is_array($string)) { - $str = ''; - foreach ($string as $message) { - $str .= $message ."\n"; - } - $string = $str; + function out($message, $after = true) { + if (is_array($message)) { + $message = implode($this->nl(), $message); } - return $this->Dispatch->stdout($string, $newline); + $this->Dispatch->stdout($message . $this->nl($after), false); } + /** - * Outputs to the stderr filehandle. + * Outputs a single or multiple error messages to stderr. * - * @param string $string Error text to output. + * @param mixed $message A string or a an array of strings to output + * @param mixed $after Appended to message, if true a newline is used * @access public */ - function err($string) { - if (is_array($string)) { - $str = ''; - foreach ($string as $message) { - $str .= $message ."\n"; - } - $string = $str; + function err($message, $after = true) { + if (is_array($message)) { + $message = implode($this->nl(), $message); } - return $this->Dispatch->stderr($string."\n"); + $this->Dispatch->stderr($message . $this->nl($after)); } + +/** + * Returns a single or multiple linefeeds sequences. + * + * @param mixed $format If true returns a linefeed sequence, if false null, + * if a string is given that is returned, + * if an integer is given it is used as a multiplier to return multiple linefeed sequences + * @access public + * @return string + */ + function nl($format = true) { + if (is_string($format)) { + return $format . "\n"; + } + if (is_int($format)) { + return str_repeat("\n", $format); + } + return $format ? "\n" : null; + } + /** * Outputs a series of minus characters to the standard output, acts as a visual separator. * - * @param boolean $newline If true, the outputs gets an added newline. + * @param mixed $surround If true, the outputs gets surrounded by newlines. * @access public */ - function hr($newline = false) { - if ($newline) { - $this->out("\n"); - } + function hr($surround = false) { + $this->out(null, $surround); $this->out('---------------------------------------------------------------'); - if ($newline) { - $this->out("\n"); - } + $this->out(null, $surround); } /** - * Displays a formatted error message and exits the application + * Displays a formatted error message + * and exits the application with status code 1 * - * @param string $title Title of the error message - * @param string $msg Error message + * @param string $title Title of the error + * @param string $message An optional error message * @access public */ - function error($title, $msg) { - $out = "$title\n"; - $out .= "$msg\n"; - $out .= "\n"; - $this->err($out); - $this->_stop(); + function error($title, $message = null) { + $this->err(sprintf(__('Error: %s', true), $title)); + + if (!empty($message)) { + $this->err($message); + } + $this->_stop(1); } + /** * Will check the number args matches otherwise throw an error * @@ -414,6 +452,7 @@ class Shell extends Object { $this->error("Wrong number of parameters: ".count($this->args), "Expected: {$expectedNum}\nPlease type 'cake {$this->shell} help' for help on usage of the {$this->name} {$command}"); } } + /** * Creates a file at given path * @@ -427,10 +466,10 @@ class Shell extends Object { $this->out("\n" . sprintf(__("Creating file %s", true), $path)); if (is_file($path) && $this->interactive === true) { $key = $this->in(__("File exists, overwrite?", true). " {$path}", array('y', 'n', 'q'), 'n'); - if (low($key) == 'q') { + if (strtolower($key) == 'q') { $this->out(__("Quitting.", true) ."\n"); exit; - } elseif (low($key) != 'y') { + } elseif (strtolower($key) != 'y') { $this->out(__("Skip", true) ." {$path}\n"); return false; } @@ -449,6 +488,7 @@ class Shell extends Object { return false; } } + /** * Outputs usage text on the standard output. Implement it in subclasses. * @@ -461,6 +501,7 @@ class Shell extends Object { $this->Dispatch->help(); } } + /** * Action to create a Unit Test * @@ -472,13 +513,14 @@ class Shell extends Object { return true; } $unitTest = $this->in('SimpleTest is not installed. Do you want to bake unit test files anyway?', array('y','n'), 'y'); - $result = low($unitTest) == 'y' || low($unitTest) == 'yes'; + $result = strtolower($unitTest) == 'y' || strtolower($unitTest) == 'yes'; if ($result) { $this->out("\nYou can download SimpleTest from http://simpletest.org", true); } return $result; } + /** * Makes absolute file path easier to read * @@ -488,38 +530,10 @@ class Shell extends Object { */ function shortPath($file) { $shortPath = str_replace(ROOT, null, $file); - $shortPath = str_replace('..'.DS, '', $shortPath); - return r(DS.DS, DS, $shortPath); - } -/** - * Checks for Configure::read('Routing.admin') and forces user to input it if not enabled - * - * @return string Admin route to use - * @access public - */ - function getAdmin() { - $admin = ''; - $cakeAdmin = null; - $adminRoute = Configure::read('Routing.admin'); - if (!empty($adminRoute)) { - $cakeAdmin = $adminRoute . '_'; - } else { - $this->out('You need to enable Configure::write(\'Routing.admin\',\'admin\') in /app/config/core.php to use admin routing.'); - $this->out('What would you like the admin route to be?'); - $this->out('Example: www.example.com/admin/controller'); - while ($admin == '') { - $admin = $this->in("What would you like the admin route to be?", null, 'admin'); - } - if ($this->Project->cakeAdmin($admin) !== true) { - $this->out('Unable to write to /app/config/core.php.'); - $this->out('You need to enable Configure::write(\'Routing.admin\',\'admin\') in /app/config/core.php to use admin routing.'); - $this->_stop(); - } else { - $cakeAdmin = $admin . '_'; - } - } - return $cakeAdmin; + $shortPath = str_replace('..' . DS, '', $shortPath); + return str_replace(DS . DS, DS, $shortPath); } + /** * Creates the proper controller path for the specified controller class name * @@ -528,8 +542,9 @@ class Shell extends Object { * @access protected */ function _controllerPath($name) { - return low(Inflector::underscore($name)); + return strtolower(Inflector::underscore($name)); } + /** * Creates the proper controller plural name for the specified controller class name * @@ -540,6 +555,7 @@ class Shell extends Object { function _controllerName($name) { return Inflector::pluralize(Inflector::camelize($name)); } + /** * Creates the proper controller camelized name (singularized) for the specified name * @@ -550,6 +566,7 @@ class Shell extends Object { function _modelName($name) { return Inflector::camelize(Inflector::singularize($name)); } + /** * Creates the proper singular model key for associations * @@ -560,6 +577,7 @@ class Shell extends Object { function _modelKey($name) { return Inflector::underscore(Inflector::singularize($name)).'_id'; } + /** * Creates the proper model name from a foreign key * @@ -571,6 +589,7 @@ class Shell extends Object { $name = str_replace('_id', '',$key); return Inflector::camelize($name); } + /** * creates the singular name for use in views. * @@ -581,6 +600,7 @@ class Shell extends Object { function _singularName($name) { return Inflector::variable(Inflector::singularize($name)); } + /** * Creates the plural name for views * @@ -591,6 +611,7 @@ class Shell extends Object { function _pluralName($name) { return Inflector::variable(Inflector::pluralize($name)); } + /** * Creates the singular human name used in views * @@ -601,6 +622,7 @@ class Shell extends Object { function _singularHumanName($name) { return Inflector::humanize(Inflector::underscore(Inflector::singularize($name))); } + /** * Creates the plural human name used in views * @@ -611,5 +633,22 @@ class Shell extends Object { function _pluralHumanName($name) { return Inflector::humanize(Inflector::underscore(Inflector::pluralize($name))); } + +/** + * Find the correct path for a plugin. Scans $pluginPaths for the plugin you want. + * + * @param string $pluginName Name of the plugin you want ie. DebugKit + * @return string $path path to the correct plugin. + **/ + function _pluginPath($pluginName) { + $pluginPaths = App::path('plugins'); + $pluginDirName = Inflector::underscore($pluginName); + foreach ($pluginPaths as $path) { + if (is_dir($path . $pluginDirName)) { + return $path . $pluginDirName . DS ; + } + } + return $pluginPaths[0] . $pluginDirName . DS; + } } ?> \ No newline at end of file diff --git a/cake/console/libs/tasks/controller.php b/cake/console/libs/tasks/controller.php index 81f4fafbe..25e63e404 100644 --- a/cake/console/libs/tasks/controller.php +++ b/cake/console/libs/tasks/controller.php @@ -1,5 +1,4 @@ args[0])) { + if (!isset($this->connection)) { + $this->connection = 'default'; + } + if (strtolower($this->args[0]) == 'all') { + return $this->all(); + } $controller = Inflector::camelize($this->args[0]); $actions = null; if (isset($this->args[1]) && $this->args[1] == 'scaffold') { - $this->out('Baking scaffold for ' . $controller); + $this->out(__('Baking scaffold for ', true) . $controller); $actions = $this->bakeActions($controller); } else { $actions = 'scaffold'; } if ((isset($this->args[1]) && $this->args[1] == 'admin') || (isset($this->args[2]) && $this->args[2] == 'admin')) { - if ($admin = $this->getAdmin()) { + if ($admin = $this->Project->getAdmin()) { $this->out('Adding ' . Configure::read('Routing.admin') .' methods'); if ($actions == 'scaffold') { $actions = $this->bakeActions($controller, $admin); @@ -95,144 +103,169 @@ class ControllerTask extends Shell { } } } + +/** + * Bake All the controllers at once. Will only bake controllers for models that exist. + * + * @access public + * @return void + **/ + function all() { + $this->interactive = false; + $this->listAll($this->connection, false); + ClassRegistry::config('Model', array('ds' => $this->connection)); + $unitTestExists = $this->_checkUnitTest(); + foreach ($this->__tables as $table) { + $model = $this->_modelName($table); + $controller = $this->_controllerName($model); + if (App::import('Model', $model)) { + $actions = $this->bakeActions($controller); + if ($this->bake($controller, $actions) && $unitTestExists) { + $this->bakeTest($controller); + } + } + } + } + /** * Interactive * * @access private */ - function __interactive($controllerName = false) { - if (!$controllerName) { - $this->interactive = true; - $this->hr(); - $this->out(sprintf("Bake Controller\nPath: %s", $this->path)); - $this->hr(); - $actions = ''; - $uses = array(); - $helpers = array(); - $components = array(); - $wannaUseSession = 'y'; - $wannaDoAdmin = 'n'; - $wannaUseScaffold = 'n'; - $wannaDoScaffolding = 'y'; - $controllerName = $this->getName(); - } + function __interactive() { + $this->interactive = true; $this->hr(); - $this->out("Baking {$controllerName}Controller"); + $this->out(sprintf(__("Bake Controller\nPath: %s", true), $this->path)); $this->hr(); - $controllerFile = low(Inflector::underscore($controllerName)); + if (empty($this->connection)) { + $this->connection = $this->DbConfig->getConfig(); + } + + $controllerName = $this->getName(); + $this->hr(); + $this->out(sprintf(__('Baking %sController', true), $controllerName)); + $this->hr(); + + $helpers = $components = array(); + $actions = ''; + $wannaUseSession = 'y'; + $wannaBakeAdminCrud = 'n'; + $useDynamicScaffold = 'n'; + $wannaBakeCrud = 'y'; + + $controllerFile = strtolower(Inflector::underscore($controllerName)); $question[] = __("Would you like to build your controller interactively?", true); if (file_exists($this->path . $controllerFile .'_controller.php')) { $question[] = sprintf(__("Warning: Choosing no will overwrite the %sController.", true), $controllerName); } - $doItInteractive = $this->in(join("\n", $question), array('y','n'), 'y'); + $doItInteractive = $this->in(join("\n", $question), array('y', 'n'), 'y'); - if (low($doItInteractive) == 'y' || low($doItInteractive) == 'yes') { + if (strtolower($doItInteractive) == 'y') { $this->interactive = true; + $useDynamicScaffold = $this->in( + __("Would you like to use dynamic scaffolding?", true), array('y','n'), 'n' + ); - $wannaUseScaffold = $this->in(__("Would you like to use scaffolding?", true), array('y','n'), 'n'); - - if (low($wannaUseScaffold) == 'n' || low($wannaUseScaffold) == 'no') { - - $wannaDoScaffolding = $this->in(__("Would you like to include some basic class methods (index(), add(), view(), edit())?", true), array('y','n'), 'n'); - - if (low($wannaDoScaffolding) == 'y' || low($wannaDoScaffolding) == 'yes') { - $wannaDoAdmin = $this->in(__("Would you like to create the methods for admin routing?", true), array('y','n'), 'n'); - } - - $wannaDoHelpers = $this->in(__("Would you like this controller to use other helpers besides HtmlHelper and FormHelper?", true), array('y','n'), 'n'); - - if (low($wannaDoHelpers) == 'y' || low($wannaDoHelpers) == 'yes') { - $helpersList = $this->in(__("Please provide a comma separated list of the other helper names you'd like to use.\nExample: 'Ajax, Javascript, Time'", true)); - $helpersListTrimmed = str_replace(' ', '', $helpersList); - $helpers = explode(',', $helpersListTrimmed); - } - $wannaDoComponents = $this->in(__("Would you like this controller to use any components?", true), array('y','n'), 'n'); - - if (low($wannaDoComponents) == 'y' || low($wannaDoComponents) == 'yes') { - $componentsList = $this->in(__("Please provide a comma separated list of the component names you'd like to use.\nExample: 'Acl, Security, RequestHandler'", true)); - $componentsListTrimmed = str_replace(' ', '', $componentsList); - $components = explode(',', $componentsListTrimmed); - } - - $wannaUseSession = $this->in(__("Would you like to use Sessions?", true), array('y','n'), 'y'); + if (strtolower($useDynamicScaffold) == 'y') { + $wannaBakeCrud = 'n'; + $actions = 'scaffold'; } else { - $wannaDoScaffolding = 'n'; + list($wannaBakeCrud, $wannaBakeAdminCrud) = $this->_askAboutMethods(); + + $helpers = $this->doHelpers(); + $components = $this->doComponents(); + + $wannaUseSession = $this->in( + __("Would you like to use Session flash messages?", true), array('y','n'), 'y' + ); } } else { - $wannaDoScaffolding = $this->in(__("Would you like to include some basic class methods (index(), add(), view(), edit())?", true), array('y','n'), 'y'); - - if (low($wannaDoScaffolding) == 'y' || low($wannaDoScaffolding) == 'yes') { - $wannaDoAdmin = $this->in(__("Would you like to create the methods for admin routing?", true), array('y','n'), 'y'); - } - } - $admin = false; - - if ((low($wannaDoAdmin) == 'y' || low($wannaDoAdmin) == 'yes')) { - $admin = $this->getAdmin(); + list($wannaBakeCrud, $wannaBakeCrud) = $this->_askAboutMethods(); } - if (low($wannaDoScaffolding) == 'y' || low($wannaDoScaffolding) == 'yes') { - $actions = $this->bakeActions($controllerName, null, in_array(low($wannaUseSession), array('y', 'yes'))); - if ($admin) { - $actions .= $this->bakeActions($controllerName, $admin, in_array(low($wannaUseSession), array('y', 'yes'))); - } + if (strtolower($wannaBakeCrud) == 'y') { + $actions = $this->bakeActions($controllerName, null, strtolower($wannaUseSession) == 'y'); + } + if (strtolower($wannaBakeAdminCrud) == 'y') { + $admin = $this->Project->getAdmin(); + $actions .= $this->bakeActions($controllerName, $admin, strtolower($wannaUseSession) == 'y'); } if ($this->interactive === true) { - $this->out(''); - $this->hr(); - $this->out('The following controller will be created:'); - $this->hr(); - $this->out("Controller Name: $controllerName"); - - if (low($wannaUseScaffold) == 'y' || low($wannaUseScaffold) == 'yes') { - $this->out(" var \$scaffold;"); - $actions = 'scaffold'; - } - - if (count($helpers)) { - $this->out("Helpers: ", false); - - foreach ($helpers as $help) { - if ($help != $helpers[count($helpers) - 1]) { - $this->out(ucfirst($help) . ", ", false); - } else { - $this->out(ucfirst($help)); - } - } - } - - if (count($components)) { - $this->out("Components: ", false); - - foreach ($components as $comp) { - if ($comp != $components[count($components) - 1]) { - $this->out(ucfirst($comp) . ", ", false); - } else { - $this->out(ucfirst($comp)); - } - } - } - $this->hr(); + $this->confirmController($controllerName, $useDynamicScaffold, $helpers, $components); $looksGood = $this->in(__('Look okay?', true), array('y','n'), 'y'); - if (low($looksGood) == 'y' || low($looksGood) == 'yes') { - $baked = $this->bake($controllerName, $actions, $helpers, $components, $uses); + if (strtolower($looksGood) == 'y') { + $baked = $this->bake($controllerName, $actions, $helpers, $components); if ($baked && $this->_checkUnitTest()) { $this->bakeTest($controllerName); } - } else { - $this->__interactive($controllerName); } } else { - $baked = $this->bake($controllerName, $actions, $helpers, $components, $uses); + $baked = $this->bake($controllerName, $actions, $helpers, $components); if ($baked && $this->_checkUnitTest()) { $this->bakeTest($controllerName); } } } + +/** + * Confirm a to be baked controller with the user + * + * @return void + **/ + function confirmController($controllerName, $useDynamicScaffold, $helpers, $components) { + $this->out(''); + $this->hr(); + $this->out(__('The following controller will be created:', true)); + $this->hr(); + $this->out(sprintf(__("Controller Name:\n\t%s", true), $controllerName)); + + if (strtolower($useDynamicScaffold) == 'y') { + $this->out("var \$scaffold;"); + } + + $properties = array( + 'helpers' => __("Helpers:", true), + 'components' => __('Components:', true), + ); + + foreach ($properties as $var => $title) { + if (count($$var)) { + $output = ''; + $length = count($$var); + foreach ($$var as $i => $propElement) { + if ($i != $length -1) { + $output .= ucfirst($propElement) . ', '; + } else { + $output .= ucfirst($propElement); + } + } + $this->out($title . "\n\t" . $output); + } + } + $this->hr(); + } + +/** + * Interact with the user and ask about which methods (admin or regular they want to bake) + * + * @return array Array containing (bakeRegular, bakeAdmin) answers + **/ + function _askAboutMethods() { + $wannaBakeCrud = $this->in( + __("Would you like to create some basic class methods \n(index(), add(), view(), edit())?", true), + array('y','n'), 'n' + ); + $wannaBakeAdminCrud = $this->in( + __("Would you like to create the basic class methods for admin routing?", true), + array('y','n'), 'n' + ); + return array($wannaBakeCrud, $wannaBakeAdminCrud); + } + /** * Bake scaffold actions * @@ -247,151 +280,24 @@ class ControllerTask extends Shell { if ($this->plugin) { $modelImport = $this->plugin . '.' . $modelImport; } - if (!App::import('Model', $modelImport)) { - $this->err(__('You must have a model for this class to build scaffold methods. Please try again.', true)); - exit; + if (!App::import('Model', $currentModelName)) { + $this->err(__('You must have a model for this class to build basic methods. Please try again.', true)); + $this->_stop(); } - $actions = null; - $modelObj =& new $currentModelName(); + + $modelObj =& ClassRegistry::init($currentModelName); $controllerPath = $this->_controllerPath($controllerName); $pluralName = $this->_pluralName($currentModelName); $singularName = Inflector::variable($currentModelName); $singularHumanName = Inflector::humanize($currentModelName); $pluralHumanName = Inflector::humanize($controllerName); - $actions .= "\n"; - $actions .= "\tfunction {$admin}index() {\n"; - $actions .= "\t\t\$this->{$currentModelName}->recursive = 0;\n"; - $actions .= "\t\t\$this->set('{$pluralName}', \$this->paginate());\n"; - $actions .= "\t}\n"; - $actions .= "\n"; - $actions .= "\tfunction {$admin}view(\$id = null) {\n"; - $actions .= "\t\tif (!\$id) {\n"; - if ($wannaUseSession) { - $actions .= "\t\t\t\$this->Session->setFlash(__('Invalid {$singularHumanName}.', true));\n"; - $actions .= "\t\t\t\$this->redirect(array('action'=>'index'));\n"; - } else { - $actions .= "\t\t\t\$this->flash(__('Invalid {$singularHumanName}', true), array('action'=>'index'));\n"; - } - $actions .= "\t\t}\n"; - $actions .= "\t\t\$this->set('".$singularName."', \$this->{$currentModelName}->read(null, \$id));\n"; - $actions .= "\t}\n"; - $actions .= "\n"; - /* ADD ACTION */ - $compact = array(); - $actions .= "\tfunction {$admin}add() {\n"; - $actions .= "\t\tif (!empty(\$this->data)) {\n"; - $actions .= "\t\t\t\$this->{$currentModelName}->create();\n"; - $actions .= "\t\t\tif (\$this->{$currentModelName}->save(\$this->data)) {\n"; - if ($wannaUseSession) { - $actions .= "\t\t\t\t\$this->Session->setFlash(__('The ".$singularHumanName." has been saved', true));\n"; - $actions .= "\t\t\t\t\$this->redirect(array('action'=>'index'));\n"; - } else { - $actions .= "\t\t\t\t\$this->flash(__('{$currentModelName} saved.', true), array('action'=>'index'));\n"; - } - $actions .= "\t\t\t} else {\n"; - if ($wannaUseSession) { - $actions .= "\t\t\t\t\$this->Session->setFlash(__('The {$singularHumanName} could not be saved. Please, try again.', true));\n"; - } - $actions .= "\t\t\t}\n"; - $actions .= "\t\t}\n"; - foreach ($modelObj->hasAndBelongsToMany as $associationName => $relation) { - if (!empty($associationName)) { - $habtmModelName = $this->_modelName($associationName); - $habtmSingularName = $this->_singularName($associationName); - $habtmPluralName = $this->_pluralName($associationName); - $actions .= "\t\t\${$habtmPluralName} = \$this->{$currentModelName}->{$habtmModelName}->find('list');\n"; - $compact[] = "'{$habtmPluralName}'"; - } - } - foreach ($modelObj->belongsTo as $associationName => $relation) { - if (!empty($associationName)) { - $belongsToModelName = $this->_modelName($associationName); - $belongsToPluralName = $this->_pluralName($associationName); - $actions .= "\t\t\${$belongsToPluralName} = \$this->{$currentModelName}->{$belongsToModelName}->find('list');\n"; - $compact[] = "'{$belongsToPluralName}'"; - } - } - if (!empty($compact)) { - $actions .= "\t\t\$this->set(compact(".join(', ', $compact)."));\n"; - } - $actions .= "\t}\n"; - $actions .= "\n"; - - /* EDIT ACTION */ - $compact = array(); - $actions .= "\tfunction {$admin}edit(\$id = null) {\n"; - $actions .= "\t\tif (!\$id && empty(\$this->data)) {\n"; - if ($wannaUseSession) { - $actions .= "\t\t\t\$this->Session->setFlash(__('Invalid {$singularHumanName}', true));\n"; - $actions .= "\t\t\t\$this->redirect(array('action'=>'index'));\n"; - } else { - $actions .= "\t\t\t\$this->flash(__('Invalid {$singularHumanName}', true), array('action'=>'index'));\n"; - } - $actions .= "\t\t}\n"; - $actions .= "\t\tif (!empty(\$this->data)) {\n"; - $actions .= "\t\t\tif (\$this->{$currentModelName}->save(\$this->data)) {\n"; - if ($wannaUseSession) { - $actions .= "\t\t\t\t\$this->Session->setFlash(__('The ".$singularHumanName." has been saved', true));\n"; - $actions .= "\t\t\t\t\$this->redirect(array('action'=>'index'));\n"; - } else { - $actions .= "\t\t\t\t\$this->flash(__('The ".$singularHumanName." has been saved.', true), array('action'=>'index'));\n"; - } - $actions .= "\t\t\t} else {\n"; - if ($wannaUseSession) { - $actions .= "\t\t\t\t\$this->Session->setFlash(__('The {$singularHumanName} could not be saved. Please, try again.', true));\n"; - } - $actions .= "\t\t\t}\n"; - $actions .= "\t\t}\n"; - $actions .= "\t\tif (empty(\$this->data)) {\n"; - $actions .= "\t\t\t\$this->data = \$this->{$currentModelName}->read(null, \$id);\n"; - $actions .= "\t\t}\n"; - - foreach ($modelObj->hasAndBelongsToMany as $associationName => $relation) { - if (!empty($associationName)) { - $habtmModelName = $this->_modelName($associationName); - $habtmSingularName = $this->_singularName($associationName); - $habtmPluralName = $this->_pluralName($associationName); - $actions .= "\t\t\${$habtmPluralName} = \$this->{$currentModelName}->{$habtmModelName}->find('list');\n"; - $compact[] = "'{$habtmPluralName}'"; - } - } - foreach ($modelObj->belongsTo as $associationName => $relation) { - if (!empty($associationName)) { - $belongsToModelName = $this->_modelName($associationName); - $belongsToPluralName = $this->_pluralName($associationName); - $actions .= "\t\t\${$belongsToPluralName} = \$this->{$currentModelName}->{$belongsToModelName}->find('list');\n"; - $compact[] = "'{$belongsToPluralName}'"; - } - } - if (!empty($compact)) { - $actions .= "\t\t\$this->set(compact(".join(',', $compact)."));\n"; - } - $actions .= "\t}\n"; - $actions .= "\n"; - $actions .= "\tfunction {$admin}delete(\$id = null) {\n"; - $actions .= "\t\tif (!\$id) {\n"; - if ($wannaUseSession) { - $actions .= "\t\t\t\$this->Session->setFlash(__('Invalid id for {$singularHumanName}', true));\n"; - $actions .= "\t\t\t\$this->redirect(array('action'=>'index'));\n"; - } else { - $actions .= "\t\t\t\$this->flash(__('Invalid {$singularHumanName}', true), array('action'=>'index'));\n"; - } - $actions .= "\t\t}\n"; - $actions .= "\t\tif (\$this->{$currentModelName}->delete(\$id)) {\n"; - if ($wannaUseSession) { - $actions .= "\t\t\t\$this->Session->setFlash(__('{$singularHumanName} deleted', true));\n"; - $actions .= "\t\t\t\$this->redirect(array('action'=>'index'));\n"; - } else { - $actions .= "\t\t\t\$this->flash(__('{$singularHumanName} deleted', true), array('action'=>'index'));\n"; - } - $actions .= "\t\t}\n"; - $actions .= "\t}\n"; - $actions .= "\n"; + $this->Template->set(compact('admin', 'controllerPath', 'pluralName', 'singularName', 'singularHumanName', + 'pluralHumanName', 'modelObj', 'wannaUseSession', 'currentModelName')); + $actions = $this->Template->generate('actions', 'controller_actions'); return $actions; } - /** * Assembles and writes a Controller file * @@ -403,54 +309,24 @@ class ControllerTask extends Shell { * @return string Baked controller * @access private */ - function bake($controllerName, $actions = '', $helpers = null, $components = null, $uses = null) { - $out = "plugin}AppController {\n\n"; - $out .= "\tvar \$name = '$controllerName';\n"; + function bake($controllerName, $actions = '', $helpers = null, $components = null) { + $isScaffold = ($actions === 'scaffold') ? true : false; - if (low($actions) == 'scaffold') { - $out .= "\tvar \$scaffold;\n"; - } else { - if (count($uses)) { - $out .= "\tvar \$uses = array('" . $this->_modelName($controllerName) . "', "; + $this->Template->set('plugin', Inflector::camelize($this->plugin)); + $this->Template->set(compact('controllerName', 'actions', 'helpers', 'components', 'isScaffold')); + $contents = $this->Template->generate('classes', 'controller'); - foreach ($uses as $use) { - if ($use != $uses[count($uses) - 1]) { - $out .= "'" . $this->_modelName($use) . "', "; - } else { - $out .= "'" . $this->_modelName($use) . "'"; - } - } - $out .= ");\n"; - } - - $out .= "\tvar \$helpers = array('Html', 'Form'"; - if (count($helpers)) { - foreach ($helpers as $help) { - $out .= ", '" . Inflector::camelize($help) . "'"; - } - } - $out .= ");\n"; - - if (count($components)) { - $out .= "\tvar \$components = array("; - - foreach ($components as $comp) { - if ($comp != $components[count($components) - 1]) { - $out .= "'" . Inflector::camelize($comp) . "', "; - } else { - $out .= "'" . Inflector::camelize($comp) . "'"; - } - } - $out .= ");\n"; - } - $out .= $actions; + $path = $this->path; + if (isset($this->plugin)) { + $path = $this->_pluginPath($this->plugin) . 'controllers' . DS; } - $out .= "}\n"; - $out .= "?>"; - $filename = $this->path . $this->_controllerPath($controllerName) . '_controller.php'; - return $this->createFile($filename, $out); + $filename = $path . $this->_controllerPath($controllerName) . '_controller.php'; + if ($this->createFile($filename, $contents)) { + return $contents; + } + return false; } + /** * Assembles and writes a unit test file * @@ -459,84 +335,93 @@ class ControllerTask extends Shell { * @access private */ function bakeTest($className) { - $import = $className; - if ($this->plugin) { - $import = $this->plugin . '.' . $className; - } - $out = "App::import('Controller', '$import');\n\n"; - $out .= "class Test{$className} extends {$className}Controller {\n"; - $out .= "\tvar \$autoRender = false;\n}\n\n"; - $out .= "class {$className}ControllerTest extends CakeTestCase {\n"; - $out .= "\tvar \${$className} = null;\n\n"; - $out .= "\tfunction startTest() {\n\t\t\$this->{$className} = new Test{$className}();"; - $out .= "\n\t\t\$this->{$className}->constructClasses();\n\t}\n\n"; - $out .= "\tfunction test{$className}ControllerInstance() {\n"; - $out .= "\t\t\$this->assertTrue(is_a(\$this->{$className}, '{$className}Controller'));\n\t}\n\n"; - $out .= "\tfunction endTest() {\n\t\tunset(\$this->{$className});\n\t}\n}\n"; - - $path = CONTROLLER_TESTS; - if (isset($this->plugin)) { - $pluginPath = 'plugins' . DS . Inflector::underscore($this->plugin) . DS; - $path = APP . $pluginPath . 'tests' . DS . 'cases' . DS . 'controllers' . DS; - } - - $filename = Inflector::underscore($className).'_controller.test.php'; - $this->out("\nBaking unit test for $className..."); - - $header = '$Id'; - $content = ""; - return $this->createFile($path . $filename, $content); + $this->Test->plugin = $this->plugin; + $this->Test->connection = $this->connection; + return $this->Test->bake('Controller', $className); } + /** - * Outputs and gets the list of possible models or controllers from database + * Interact with the user and get a list of additional helpers + * + * @return array Helpers that the user wants to use. + **/ + function doHelpers() { + return $this->_doPropertyChoices( + __("Would you like this controller to use other helpers\nbesides HtmlHelper and FormHelper?", true), + __("Please provide a comma separated list of the other\nhelper names you'd like to use.\nExample: 'Ajax, Javascript, Time'", true) + ); + } + +/** + * Interact with the user and get a list of additional components + * + * @return array Components the user wants to use. + **/ + function doComponents() { + return $this->_doPropertyChoices( + __("Would you like this controller to use any components?", true), + __("Please provide a comma separated list of the component names you'd like to use.\nExample: 'Acl, Security, RequestHandler'", true) + ); + } + +/** + * Common code for property choice handling. + * + * @param string $prompt A yes/no question to precede the list + * @param sting $example A question for a comma separated list, with examples. + * @return array Array of values for property. + **/ + function _doPropertyChoices($prompt, $example) { + $proceed = $this->in($prompt, array('y','n'), 'n'); + $property = array(); + if (strtolower($proceed) == 'y') { + $propertyList = $this->in($example); + $propertyListTrimmed = str_replace(' ', '', $propertyList); + $property = explode(',', $propertyListTrimmed); + } + return array_filter($property); + } + +/** + * Outputs and gets the list of possible controllers from database * * @param string $useDbConfig Database configuration name + * @param boolean $interactive Whether you are using listAll interactively and want options output. * @return array Set of controllers * @access public */ - function listAll($useDbConfig = 'default') { - $db =& ConnectionManager::getDataSource($useDbConfig); - $usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix']; - if ($usePrefix) { - $tables = array(); - foreach ($db->listSources() as $table) { - if (!strncmp($table, $usePrefix, strlen($usePrefix))) { - $tables[] = substr($table, strlen($usePrefix)); - } + function listAll($useDbConfig = null) { + if (is_null($useDbConfig)) { + $useDbConfig = $this->connection; + } + $this->__tables = $this->Model->getAllTables($useDbConfig); + + if ($this->interactive == true) { + $this->out(__('Possible Controllers based on your current database:', true)); + $this->_controllerNames = array(); + $count = count($this->__tables); + for ($i = 0; $i < $count; $i++) { + $this->_controllerNames[] = $this->_controllerName($this->_modelName($this->__tables[$i])); + $this->out($i + 1 . ". " . $this->_controllerNames[$i]); } - } else { - $tables = $db->listSources(); + return $this->_controllerNames; } - - if (empty($tables)) { - $this->err(__('Your database does not have any tables.', true)); - $this->_stop(); - } - - $this->__tables = $tables; - $this->out('Possible Controllers based on your current database:'); - $this->_controllerNames = array(); - $count = count($tables); - for ($i = 0; $i < $count; $i++) { - $this->_controllerNames[] = $this->_controllerName($this->_modelName($tables[$i])); - $this->out($i + 1 . ". " . $this->_controllerNames[$i]); - } - return $this->_controllerNames; + return $this->__tables; } /** * Forces the user to specify the controller he wants to bake, and returns the selected controller name. * + * @param string $useDbConfig Connection name to get a controller name for. * @return string Controller name * @access public */ - function getName() { - $useDbConfig = 'default'; - $controllers = $this->listAll($useDbConfig, 'Controllers'); + function getName($useDbConfig = null) { + $controllers = $this->listAll($useDbConfig); $enteredController = ''; while ($enteredController == '') { - $enteredController = $this->in(__("Enter a number from the list above, type in the name of another controller, or 'q' to exit", true), null, 'q'); + $enteredController = $this->in(__("Enter a number from the list above,\ntype in the name of another controller, or 'q' to exit", true), null, 'q'); if ($enteredController === 'q') { $this->out(__("Exit", true)); @@ -544,8 +429,7 @@ class ControllerTask extends Shell { } if ($enteredController == '' || intval($enteredController) > count($controllers)) { - $this->out(__('Error:', true)); - $this->out(__("The Controller name you supplied was empty, or the number \nyou selected was not an option. Please try again.", true)); + $this->err(__("The Controller name you supplied was empty,\nor the number you selected was not an option. Please try again.", true)); $enteredController = ''; } } @@ -555,9 +439,9 @@ class ControllerTask extends Shell { } else { $controllerName = Inflector::camelize($enteredController); } - return $controllerName; } + /** * Displays help contents * @@ -568,12 +452,26 @@ class ControllerTask extends Shell { $this->out("Usage: cake bake controller ..."); $this->hr(); $this->out('Commands:'); - $this->out("\n\tcontroller \n\t\tbakes controller with var \$scaffold"); - $this->out("\n\tcontroller scaffold\n\t\tbakes controller with scaffold actions.\n\t\t(index, view, add, edit, delete)"); - $this->out("\n\tcontroller scaffold admin\n\t\tbakes a controller with scaffold actions for both public and Configure::read('Routing.admin')"); - $this->out("\n\tcontroller admin\n\t\tbakes a controller with scaffold actions only for Configure::read('Routing.admin')"); + $this->out(''); + $this->out("controller "); + $this->out("\tbakes controller with var \$scaffold"); + $this->out(''); + $this->out("controller scaffold"); + $this->out("\tbakes controller with scaffold actions."); + $this->out("\t(index, view, add, edit, delete)"); + $this->out(''); + $this->out("controller scaffold admin"); + $this->out("\tbakes a controller with scaffold actions for both public"); + $this->out("\tand Configure::read('Routing.admin')"); + $this->out(''); + $this->out("controller admin"); + $this->out("\tbakes a controller with scaffold actions only for"); + $this->out("\tConfigure::read('Routing.admin')"); + $this->out(''); + $this->out("controller all"); + $this->out("\tbakes all controllers with CRUD methods."); $this->out(""); $this->_stop(); } } -?> +?> \ No newline at end of file diff --git a/cake/console/libs/tasks/db_config.php b/cake/console/libs/tasks/db_config.php index fcd71052e..befedb4c1 100644 --- a/cake/console/libs/tasks/db_config.php +++ b/cake/console/libs/tasks/db_config.php @@ -1,5 +1,4 @@ 'root', 'password'=> 'password', 'database'=> 'project_name', 'schema'=> null, 'prefix'=> null, 'encoding' => null, 'port' => null ); + +/** + * String name of the database config class name. + * Used for testing. + * + * @var string + **/ + var $databaseClassName = 'DATABASE_CONFIG'; + /** * initialization callback * @@ -61,6 +66,7 @@ class DbConfigTask extends Shell { function initialize() { $this->path = $this->params['working'] . DS . 'config' . DS; } + /** * Execution method always used for tasks * @@ -72,6 +78,7 @@ class DbConfigTask extends Shell { $this->_stop(); } } + /** * Interactive interface * @@ -92,35 +99,27 @@ class DbConfigTask extends Shell { if (preg_match('/[^a-z0-9_]/i', $name)) { $name = ''; $this->out('The name may only contain unaccented latin characters, numbers or underscores'); - } - else if (preg_match('/^[^a-z_]/i', $name)) { + } else if (preg_match('/^[^a-z_]/i', $name)) { $name = ''; $this->out('The name must start with an unaccented latin character or an underscore'); } } - $driver = ''; - while ($driver == '') { - $driver = $this->in('Driver:', array('db2', 'firebird', 'mssql', 'mysql', 'mysqli', 'odbc', 'oracle', 'postgres', 'sqlite', 'sybase'), 'mysql'); - } - $persistent = ''; - - while ($persistent == '') { - $persistent = $this->in('Persistent Connection?', array('y', 'n'), 'n'); - } + $driver = $this->in('Driver:', array('db2', 'firebird', 'mssql', 'mysql', 'mysqli', 'odbc', 'oracle', 'postgres', 'sqlite', 'sybase'), 'mysql'); + $persistent = $this->in('Persistent Connection?', array('y', 'n'), 'n'); if (low($persistent) == 'n') { $persistent = 'false'; } else { $persistent = 'true'; } - $host = ''; + $host = ''; while ($host == '') { $host = $this->in('Database Host:', null, 'localhost'); } - $port = ''; + $port = ''; while ($port == '') { $port = $this->in('Port?', null, 'n'); } @@ -128,8 +127,8 @@ class DbConfigTask extends Shell { if (low($port) == 'n') { $port = null; } - $login = ''; + $login = ''; while ($login == '') { $login = $this->in('User:', null, 'root'); } @@ -141,43 +140,39 @@ class DbConfigTask extends Shell { if ($password == '') { $blank = $this->in('The password you supplied was empty. Use an empty password?', array('y', 'n'), 'n'); - if ($blank == 'y') - { + if ($blank == 'y') { $blankPassword = true; } } } - $database = ''; + $database = ''; while ($database == '') { $database = $this->in('Database Name:', null, 'cake'); } - $prefix = ''; + $prefix = ''; while ($prefix == '') { $prefix = $this->in('Table Prefix?', null, 'n'); } - if (low($prefix) == 'n') { $prefix = null; } - $encoding = ''; + $encoding = ''; while ($encoding == '') { $encoding = $this->in('Table encoding?', null, 'n'); } - if (low($encoding) == 'n') { $encoding = null; } - $schema = ''; + $schema = ''; if ($driver == 'postgres') { while ($schema == '') { $schema = $this->in('Table schema?', null, 'n'); } } - if (low($schema) == 'n') { $schema = null; } @@ -199,6 +194,7 @@ class DbConfigTask extends Shell { config('database'); return true; } + /** * Output verification message and bake if it looks good * @@ -240,11 +236,12 @@ class DbConfigTask extends Shell { $this->hr(); $looksGood = $this->in('Look okay?', array('y', 'n'), 'y'); - if (low($looksGood) == 'y' || low($looksGood) == 'yes') { + if (strtolower($looksGood) == 'y') { return $config; } return false; } + /** * Assembles and writes database.php * @@ -262,7 +259,7 @@ class DbConfigTask extends Shell { $oldConfigs = array(); if (file_exists($filename)) { - $db = new DATABASE_CONFIG; + $db = new $this->databaseClassName; $temp = get_class_vars(get_class($db)); foreach ($temp as $configName => $info) { @@ -346,8 +343,28 @@ class DbConfigTask extends Shell { $out .= "}\n"; $out .= "?>"; - $filename = $this->path.'database.php'; + $filename = $this->path . 'database.php'; return $this->createFile($filename, $out); } + +/** + * Get a user specified Connection name + * + * @return void + **/ + function getConfig() { + $useDbConfig = 'default'; + $configs = get_class_vars($this->databaseClassName); + + if (!is_array($configs)) { + return $this->execute(); + } + + $connections = array_keys($configs); + if (count($connections) > 1) { + $useDbConfig = $this->in(__('Use Database Config', true) .':', $connections, 'default'); + } + return $useDbConfig; + } } ?> \ No newline at end of file diff --git a/cake/console/libs/tasks/extract.php b/cake/console/libs/tasks/extract.php index 7918e8cd7..eda49614b 100644 --- a/cake/console/libs/tasks/extract.php +++ b/cake/console/libs/tasks/extract.php @@ -1,5 +1,4 @@ __extract(); } + /** * Extract text * @@ -212,6 +221,7 @@ class ExtractTask extends Shell{ } $this->__extractTokens(); } + /** * Show help options * @@ -237,6 +247,7 @@ class ExtractTask extends Shell{ $this->out(__(' -debug: Perform self test.', true)); $this->out(''); } + /** * Extract tokens out of all files to be processed * @@ -281,6 +292,7 @@ class ExtractTask extends Shell{ $this->__writeFiles(); $this->out('Done.'); } + /** * Will parse __(), __c() functions * @@ -316,6 +328,7 @@ class ExtractTask extends Shell{ $count++; } } + /** * Will parse __d(), __dc(), __n(), __dn(), __dcn() * @@ -402,6 +415,7 @@ class ExtractTask extends Shell{ $count++; } } + /** * Build the translate template file contents out of obtained strings * @@ -464,6 +478,7 @@ class ExtractTask extends Shell{ $this->__store($filename, $output, $fileList); } } + /** * Prepare a file to be stored * @@ -491,6 +506,7 @@ class ExtractTask extends Shell{ return $storage; } } + /** * Write the files that need to be stored * @@ -539,6 +555,7 @@ class ExtractTask extends Shell{ fclose($fp); } } + /** * Merge output files * @@ -565,6 +582,7 @@ class ExtractTask extends Shell{ } return $output; } + /** * Build the translation template header * @@ -590,6 +608,7 @@ class ExtractTask extends Shell{ $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n"; return $output; } + /** * Find the version number of a file looking for SVN commands * @@ -604,6 +623,7 @@ class ExtractTask extends Shell{ $this->__fileVersions[$file] = $version; } } + /** * Format a string to be added as a translateable string * @@ -622,6 +642,7 @@ class ExtractTask extends Shell{ $string = str_replace("\r\n", "\n", $string); return addcslashes($string, "\0..\37\\\""); } + /** * Indicate an invalid marker on a processed file * @@ -654,6 +675,7 @@ class ExtractTask extends Shell{ } $this->out("\n", true); } + /** * Search the specified path for files that may contain translateable strings * diff --git a/cake/console/libs/tasks/fixture.php b/cake/console/libs/tasks/fixture.php new file mode 100644 index 000000000..58a5455d0 --- /dev/null +++ b/cake/console/libs/tasks/fixture.php @@ -0,0 +1,427 @@ +path = $this->params['working'] . DS . 'tests' . DS . 'fixtures' . DS; + if (!class_exists('CakeSchema')) { + App::import('Model', 'CakeSchema', false); + } + } + +/** + * Execution method always used for tasks + * Handles dispatching to interactive, named, or all processess. + * + * @access public + */ + function execute() { + if (empty($this->args)) { + $this->__interactive(); + } + + if (isset($this->args[0])) { + if (!isset($this->connection)) { + $this->connection = 'default'; + } + if (strtolower($this->args[0]) == 'all') { + return $this->all(); + } + $model = Inflector::camelize($this->args[0]); + $this->bake($model); + } + } + +/** + * Bake All the Fixtures at once. Will only bake fixtures for models that exist. + * + * @access public + * @return void + **/ + function all() { + $this->interactive = false; + $tables = $this->Model->listAll($this->connection, false); + foreach ($tables as $table) { + $model = $this->_modelName($table); + $this->bake($model); + } + } + +/** + * Interactive baking function + * + * @access private + */ + function __interactive() { + $this->interactive = true; + $this->hr(); + $this->out(sprintf("Bake Fixture\nPath: %s", $this->path)); + $this->hr(); + + $useDbConfig = $this->connection; + if (!isset($this->connection)) { + $this->connection = $this->DbConfig->getConfig(); + } + $modelName = $this->Model->getName($this->connection); + $useTable = $this->Model->getTable($modelName, $this->connection); + $importOptions = $this->importOptions($modelName); + $this->bake($modelName, $useTable, $importOptions); + } + +/** + * Interacts with the User to setup an array of import options. For a fixture. + * + * @param string $modelName Name of model you are dealing with. + * @return array Array of import options. + **/ + function importOptions($modelName) { + $options = array(); + $doSchema = $this->in(__('Would you like to import schema for this fixture?', true), array('y', 'n'), 'n'); + if ($doSchema == 'y') { + $options['schema'] = $modelName; + } + $doRecords = $this->in(__('Would you like to use record importing for this fixture?', true), array('y', 'n'), 'n'); + if ($doRecords == 'y') { + $options['records'] = true; + } + if ($doRecords == 'n') { + $prompt = sprintf(__("Would you like to build this fixture with data from %s's table?", true), $modelName); + $fromTable = $this->in($prompt, array('y', 'n'), 'n'); + if (strtolower($fromTable) == 'y') { + $options['fromTable'] = true; + } + } + return $options; + } + +/** + * Assembles and writes a Fixture file + * + * @param string $model Name of model to bake. + * @param string $useTable Name of table to use. + * @param array $importOptions Options for var $import + * @return string Baked fixture + * @access private + */ + function bake($model, $useTable = false, $importOptions = array()) { + $table = $schema = $records = $import = $modelImport = $recordImport = null; + if (!$useTable) { + $useTable = Inflector::tableize($model); + } elseif ($useTable != Inflector::tableize($model)) { + $table = $useTable; + } + + if (!empty($importOptions)) { + if (isset($importOptions['schema'])) { + $modelImport = "'model' => '{$importOptions['schema']}'"; + } + if (isset($importOptions['records'])) { + $recordImport = "'records' => true"; + } + if ($modelImport && $recordImport) { + $modelImport .= ', '; + } + if (!empty($modelImport) || !empty($recordImport)) { + $import = sprintf("array(%s%s)", $modelImport, $recordImport); + } + } + + $this->_Schema = new CakeSchema(); + $data = $this->_Schema->read(array('models' => false, 'connection' => $this->connection)); + + if (!isset($data['tables'][$useTable])) { + $this->err('Could not find your selected table ' . $useTable); + return false; + } + + $tableInfo = $data['tables'][$useTable]; + if (is_null($modelImport)) { + $schema = $this->_generateSchema($tableInfo); + } + + if (!isset($importOptions['records']) && !isset($importOptions['fromTable'])) { + $recordCount = 1; + if (isset($this->params['count'])) { + $recordCount = $this->params['count']; + } + $records = $this->_makeRecordString($this->_generateRecords($tableInfo, $recordCount)); + } + if (isset($importOptions['fromTable'])) { + $records = $this->_makeRecordString($this->_getRecordsFromTable($model, $useTable)); + } + $out = $this->generateFixtureFile($model, compact('records', 'table', 'schema', 'import', 'fields')); + return $out; + } + +/** + * Generate the fixture file, and write to disk + * + * @param string $model name of the model being generated + * @param string $fixture Contents of the fixture file. + * @access public + * @return void + **/ + function generateFixtureFile($model, $otherVars) { + $defaults = array('table' => null, 'schema' => null, 'records' => null, 'import' => null, 'fields' => null); + $vars = array_merge($defaults, $otherVars); + + $path = $this->path; + if (isset($this->plugin)) { + $path = $this->_pluginPath($this->plugin) . 'tests' . DS . 'fixtures' . DS; + } + $filename = Inflector::underscore($model) . '_fixture.php'; + + $this->Template->set('model', $model); + $this->Template->set($vars); + $content = $this->Template->generate('classes', 'fixture'); + + $this->out("\nBaking test fixture for $model..."); + $this->createFile($path . $filename, $content); + return $content; + } + +/** + * Generates a string representation of a schema. + * + * @param array $table Table schema array + * @return string fields definitions + **/ + function _generateSchema($tableInfo) { + $cols = array(); + $out = "array(\n"; + foreach ($tableInfo as $field => $fieldInfo) { + if (is_array($fieldInfo)) { + if ($field != 'indexes') { + $col = "\t\t'{$field}' => array('type'=>'" . $fieldInfo['type'] . "', "; + $col .= join(', ', $this->_Schema->__values($fieldInfo)); + } else { + $col = "\t\t'indexes' => array("; + $props = array(); + foreach ((array)$fieldInfo as $key => $index) { + $props[] = "'{$key}' => array(".join(', ', $this->_Schema->__values($index)).")"; + } + $col .= join(', ', $props); + } + $col .= ")"; + $cols[] = $col; + } + } + $out .= join(",\n", $cols); + $out .= "\n\t)"; + return $out; + } + +/** + * Generate String representation of Records + * + * @param array $table Table schema array + * @return array Array of records to use in the fixture. + **/ + function _generateRecords($tableInfo, $recordCount = 1) { + $records = array(); + for ($i = 0; $i < $recordCount; $i++) { + $record = array(); + foreach ($tableInfo as $field => $fieldInfo) { + if (empty($fieldInfo['type'])) { + continue; + } + switch ($fieldInfo['type']) { + case 'integer': + $insert = $i + 1; + break; + case 'string'; + $isPrimaryUuid = ( + isset($fieldInfo['key']) && strtolower($fieldInfo['key']) == 'primary' && + isset($fieldInfo['length']) && $fieldInfo['length'] == 36 + ); + if ($isPrimaryUuid) { + $insert = String::uuid(); + } else { + $insert = "Lorem ipsum dolor sit amet"; + if (!empty($fieldInfo['length'])) { + $insert = substr($insert, 0, (int)$fieldInfo['length'] - 2); + } + } + $insert = "'$insert'"; + break; + case 'timestamp': + $ts = time(); + $insert = "'$ts'"; + break; + case 'datetime': + $ts = date('Y-m-d H:i:s'); + $insert = "'$ts'"; + break; + case 'date': + $ts = date('Y-m-d'); + $insert = "'$ts'"; + break; + case 'time': + $ts = date('H:i:s'); + $insert = "'$ts'"; + break; + case 'boolean': + $insert = 1; + break; + case 'text': + $insert = "'Lorem ipsum dolor sit amet, aliquet feugiat."; + $insert .= " Convallis morbi fringilla gravida,"; + $insert .= " phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin"; + $insert .= " venenatis cum nullam, vivamus ut a sed, mollitia lectus. Nulla"; + $insert .= " vestibulum massa neque ut et, id hendrerit sit,"; + $insert .= " feugiat in taciti enim proin nibh, tempor dignissim, rhoncus"; + $insert .= " duis vestibulum nunc mattis convallis.'"; + break; + } + $record[$field] = $insert; + } + $records[] = $record; + } + return $records; + } + +/** + * Convert a $records array into a a string. + * + * @param array $records Array of records to be converted to string + * @return string A string value of the $records array. + **/ + function _makeRecordString($records) { + $out = "array(\n"; + foreach ($records as $record) { + $values = array(); + foreach ($record as $field => $value) { + $values[] = "\t\t\t'$field' => $value"; + } + $out .= "\t\tarray(\n"; + $out .= implode(",\n", $values); + $out .= "\n\t\t),\n"; + } + $out .= "\t)"; + return $out; + } + +/** + * Interact with the user to get a custom SQL condition and use that to extract data + * to build a fixture. + * + * @param string $modelName name of the model to take records from. + * @param string $useTable Name of table to use. + * @return array Array of records. + **/ + function _getRecordsFromTable($modelName, $useTable = null) { + $condition = null; + $prompt = __("Please provide a SQL fragment to use as conditions\nExample: WHERE 1=1 LIMIT 10", true); + while (!$condition) { + $condition = $this->in($prompt, null, 'WHERE 1=1 LIMIT 10'); + } + App::import('Model', 'Model', false); + $modelObject =& new Model(array('name' => $modelName, 'table' => $useTable, 'ds' => $this->connection)); + $records = $modelObject->find('all', array( + 'conditions' => $condition, + 'recursive' => -1 + )); + $db =& $modelObject->getDataSource(); + $schema = $modelObject->schema(); + $out = array(); + foreach ($records as $record) { + $row = array(); + foreach ($record[$modelObject->alias] as $field => $value) { + $row[$field] = $db->value($value, $schema[$field]['type']); + } + $out[] = $row; + } + return $out; + } + +/** + * Displays help contents + * + * @access public + */ + function help() { + $this->hr(); + $this->out("Usage: cake bake fixture "); + $this->hr(); + $this->out('Commands:'); + $this->out("\nfixture \n\tbakes fixture with specified name."); + $this->out("\nfixture all\n\tbakes all fixtures."); + $this->out(""); + $this->out('Parameters:'); + $this->out("\t-count When using generated data, the number of records to include in the fixture(s)."); + $this->out("\t-connection Which database configuration to use for baking."); + $this->out("\t-plugin lowercased_underscored name of plugin to bake fixtures for."); + $this->out(""); + $this->_stop(); + } +} +?> \ No newline at end of file diff --git a/cake/console/libs/tasks/model.php b/cake/console/libs/tasks/model.php index b4ae6feed..f9d7937b2 100644 --- a/cake/console/libs/tasks/model.php +++ b/cake/console/libs/tasks/model.php @@ -1,5 +1,4 @@ args[0])) { + $this->interactive = false; + if (!isset($this->connection)) { + $this->connection = 'default'; + } + if (strtolower($this->args[0]) == 'all') { + return $this->all(); + } $model = Inflector::camelize($this->args[0]); - if ($this->bake($model)) { + $object = $this->_getModelObject($model); + if ($this->bake($object, false)) { if ($this->_checkUnitTest()) { + $this->bakeFixture($model); $this->bakeTest($model); } } } } + +/** + * Bake all models at once. + * + * @return void + **/ + function all() { + $this->listAll($this->connection, false); + $unitTestExists = $this->_checkUnitTest(); + foreach ($this->__tables as $table) { + $modelClass = Inflector::classify($table); + $this->out(sprintf(__('Baking %s', true), $modelClass)); + $object = $this->_getModelObject($modelClass); + if ($this->bake($object, false) && $unitTestExists) { + $this->bakeFixture($modelClass); + $this->bakeTest($modelClass); + } + } + } + +/** + * Get a model object for a class name. + * + * @param string $className Name of class you want model to be. + * @return object Model instance + **/ + function &_getModelObject($className) { + $object = new Model(array('name' => $className, 'ds' => $this->connection)); + return $object; + } + +/** + * Generate a key value list of options and a prompt. + * + * @param array $options Array of options to use for the selections. indexes must start at 0 + * @param string $prompt Prompt to use for options list. + * @param integer $default The default option for the given prompt. + * @return result of user choice. + **/ + function inOptions($options, $prompt = null, $default = null) { + $valid = false; + $max = count($options); + while (!$valid) { + foreach ($options as $i => $option) { + $this->out($i + 1 .'. ' . $option); + } + if (empty($prompt)) { + $prompt = __('Make a selection from the choices above', true); + } + $choice = $this->in($prompt, null, $default); + if (intval($choice) > 0 && intval($choice) <= $max) { + $valid = true; + } + } + return $choice - 1; + } + /** * Handles interactive baking * @@ -83,73 +181,42 @@ class ModelTask extends Shell { $this->hr(); $this->interactive = true; - $useTable = null; $primaryKey = 'id'; - $validate = array(); - $associations = array('belongsTo'=> array(), 'hasOne'=> array(), 'hasMany' => array(), 'hasAndBelongsToMany'=> array()); + $validate = $associations = array(); - $useDbConfig = 'default'; - $configs = get_class_vars('DATABASE_CONFIG'); - - if (!is_array($configs)) { - return $this->DbConfig->execute(); + if (empty($this->connection)) { + $this->connection = $this->DbConfig->getConfig(); } - - $connections = array_keys($configs); - if (count($connections) > 1) { - $useDbConfig = $this->in(__('Use Database Config', true) .':', $connections, 'default'); - } - - $currentModelName = $this->getName($useDbConfig); - $db =& ConnectionManager::getDataSource($useDbConfig); - $useTable = Inflector::tableize($currentModelName); - $fullTableName = $db->fullTableName($useTable, false); - $tableIsGood = false; - - if (array_search($useTable, $this->__tables) === false) { - $this->out(''); - $this->out(sprintf(__("Given your model named '%s', Cake would expect a database table named %s", true), $currentModelName, $fullTableName)); - $tableIsGood = $this->in(__('Do you want to use this table?', true), array('y','n'), 'y'); - } - - if (low($tableIsGood) == 'n' || low($tableIsGood) == 'no') { - $useTable = $this->in(__('What is the name of the table (enter "null" to use NO table)?', true)); - } - - while ($tableIsGood == false && low($useTable) != 'null') { - if (is_array($this->__tables) && !in_array($useTable, $this->__tables)) { - $fullTableName = $db->fullTableName($useTable, false); - $this->out($fullTableName . ' does not exist.'); - $useTable = $this->in(__('What is the name of the table (enter "null" to use NO table)?', true)); - $tableIsGood = false; - } else { - $tableIsGood = true; - } - } - - $wannaDoValidation = $this->in(__('Would you like to supply validation criteria for the fields in your model?', true), array('y','n'), 'y'); + $currentModelName = $this->getName(); + $useTable = $this->getTable($currentModelName); + $db =& ConnectionManager::getDataSource($this->connection); + $fullTableName = $db->fullTableName($useTable); if (in_array($useTable, $this->__tables)) { - App::import('Model'); - $tempModel = new Model(array('name' => $currentModelName, 'table' => $useTable, 'ds' => $useDbConfig)); - + $tempModel = new Model(array('name' => $currentModelName, 'table' => $useTable, 'ds' => $this->connection)); $fields = $tempModel->schema(); if (!array_key_exists('id', $fields)) { - foreach ($fields as $name => $field) { - if (isset($field['key']) && $field['key'] == 'primary') { - break; - } - } - $primaryKey = $this->in(__('What is the primaryKey?', true), null, $name); + $primaryKey = $this->findPrimaryKey($fields); } + } else { + $this->err(sprintf(__('Table %s does not exist, cannot bake a model without a table.', true), $useTable)); + $this->_stop(); + return false; + } + $displayField = $tempModel->hasField(array('name', 'title')); + if (!$displayField) { + $displayField = $this->findDisplayField($tempModel->schema()); } - if (array_search($useTable, $this->__tables) !== false && (low($wannaDoValidation) == 'y' || low($wannaDoValidation) == 'yes')) { + $prompt = __("Would you like to supply validation criteria \nfor the fields in your model?", true); + $wannaDoValidation = $this->in($prompt, array('y','n'), 'y'); + if (array_search($useTable, $this->__tables) !== false && strtolower($wannaDoValidation) == 'y') { $validate = $this->doValidation($tempModel); } - $wannaDoAssoc = $this->in(__('Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)?', true), array('y','n'), 'y'); - if ((low($wannaDoAssoc) == 'y' || low($wannaDoAssoc) == 'yes')) { + $prompt = __("Would you like to define model associations\n(hasMany, hasOne, belongsTo, etc.)?", true); + $wannaDoAssoc = $this->in($prompt, array('y','n'), 'y'); + if (strtolower($wannaDoAssoc) == 'y') { $associations = $this->doAssociations($tempModel); } @@ -159,51 +226,35 @@ class ModelTask extends Shell { $this->hr(); $this->out("Name: " . $currentModelName); - if ($useDbConfig !== 'default') { - $this->out("DB Config: " . $useDbConfig); + if ($this->connection !== 'default') { + $this->out(sprintf(__("DB Config: %s", true), $this->connection)); } if ($fullTableName !== Inflector::tableize($currentModelName)) { - $this->out("DB Table: " . $fullTableName); + $this->out(sprintf(__("DB Table: %s", true), $fullTableName)); } if ($primaryKey != 'id') { - $this->out("Primary Key: " . $primaryKey); + $this->out(sprintf(__("Primary Key: %s", true), $primaryKey)); } if (!empty($validate)) { - $this->out("Validation: " . print_r($validate, true)); + $this->out(sprintf(__("Validation: %s", true), print_r($validate, true))); } if (!empty($associations)) { - $this->out("Associations:"); - - if (!empty($associations['belongsTo'])) { - for ($i = 0; $i < count($associations['belongsTo']); $i++) { - $this->out(" $currentModelName belongsTo {$associations['belongsTo'][$i]['alias']}"); - } - } - - if (!empty($associations['hasOne'])) { - for ($i = 0; $i < count($associations['hasOne']); $i++) { - $this->out(" $currentModelName hasOne {$associations['hasOne'][$i]['alias']}"); - } - } - - if (!empty($associations['hasMany'])) { - for ($i = 0; $i < count($associations['hasMany']); $i++) { - $this->out(" $currentModelName hasMany {$associations['hasMany'][$i]['alias']}"); - } - } - - if (!empty($associations['hasAndBelongsToMany'])) { - for ($i = 0; $i < count($associations['hasAndBelongsToMany']); $i++) { - $this->out(" $currentModelName hasAndBelongsToMany {$associations['hasAndBelongsToMany'][$i]['alias']}"); - } + $this->out(__("Associations:", true)); + $assocKeys = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'); + foreach ($assocKeys as $assocKey) { + $this->_printAssociation($currentModelName, $assocKey, $associations); } } + $this->hr(); $looksGood = $this->in(__('Look okay?', true), array('y','n'), 'y'); - if (low($looksGood) == 'y' || low($looksGood) == 'yes') { - if ($this->bake($currentModelName, $associations, $validate, $primaryKey, $useTable, $useDbConfig)) { + if (strtolower($looksGood) == 'y') { + $vars = compact('associations', 'validate', 'primaryKey', 'useTable', 'displayField'); + $vars['useDbConfig'] = $this->connection; + if ($this->bake($currentModelName, $vars)) { if ($this->_checkUnitTest()) { + $this->bakeFixture($currentModelName, $useTable); $this->bakeTest($currentModelName, $useTable, $associations); } } @@ -211,15 +262,67 @@ class ModelTask extends Shell { return false; } } + /** - * Handles associations + * Print out all the associations of a particular type * - * @param object $model - * @param boolean $interactive - * @return array $validate + * @param string $modelName Name of the model relations belong to. + * @param string $type Name of association you want to see. i.e. 'belongsTo' + * @param string $associations Collection of associations. + * @access protected + * @return void + **/ + function _printAssociation($modelName, $type, $associations) { + if (!empty($associations[$type])) { + for ($i = 0; $i < count($associations[$type]); $i++) { + $out = "\t" . $modelName . ' ' . $type . ' ' . $associations[$type][$i]['alias']; + $this->out($out); + } + } + } + +/** + * Finds a primary Key in a list of fields. + * + * @param array $fields Array of fields that might have a primary key. + * @return string Name of field that is a primary key. + * @access public + **/ + function findPrimaryKey($fields) { + foreach ($fields as $name => $field) { + if (isset($field['key']) && $field['key'] == 'primary') { + break; + } + } + return $this->in(__('What is the primaryKey?', true), null, $name); + } + +/** + * interact with the user to find the displayField value for a model. + * + * @param array $fields Array of fields to look for and choose as a displayField + * @return mixed Name of field to use for displayField or false if the user declines to choose + **/ + function findDisplayField($fields) { + $fieldNames = array_keys($fields); + $prompt = __("A displayField could not be automatically detected\nwould you like to choose one?", true); + $continue = $this->in($prompt, array('y', 'n')); + if (strtolower($continue) == 'n') { + return false; + } + $prompt = __('Choose a field from the options above:', true); + $choice = $this->inOptions($fieldNames, $prompt); + return $fieldNames[$choice]; + } + +/** + * Handles Generation and user interaction for creating validation. + * + * @param object $model Model to have validations generated for. + * @return array $validate Array of user selected validations. * @access public */ - function doValidation(&$model, $interactive = true) { + function doValidation(&$model) { if (!is_object($model)) { return false; } @@ -228,67 +331,110 @@ class ModelTask extends Shell { if (empty($fields)) { return false; } - $validate = array(); + $this->initValidations(); + foreach ($fields as $fieldName => $field) { + $validation = $this->fieldValidation($fieldName, $field, $model->primaryKey); + if (!empty($validation)) { + $validate[$fieldName] = $validation; + } + } + return $validate; + } - $options = array(); - +/** + * Populate the __validations array + * + * @return void + **/ + function initValidations() { + $options = $choices = array(); if (class_exists('Validation')) { $parent = get_class_methods(get_parent_class('Validation')); $options = array_diff(get_class_methods('Validation'), $parent); } + sort($options); + $default = 1; + foreach ($options as $key => $option) { + if ($option{0} != '_' && strtolower($option) != 'getinstance') { + $choices[$default] = strtolower($option); + $default++; + } + } + $this->__validations = $choices; + return $choices; + } - foreach ($fields as $fieldName => $field) { - $prompt = 'Field: ' . $fieldName . "\n"; - $prompt .= 'Type: ' . $field['type'] . "\n"; - $prompt .= '---------------------------------------------------------------'."\n"; - $prompt .= 'Please select one of the following validation options:'."\n"; - $prompt .= '---------------------------------------------------------------'."\n"; +/** + * Does individual field validation handling. + * + * @param string $fieldName Name of field to be validated. + * @param array $metaData metadata for field + * @return array Array of validation for the field. + **/ + function fieldValidation($fieldName, $metaData, $primaryKey = 'id') { + $defaultChoice = count($this->__validations); + $validate = $alreadyChosen = array(); - sort($options); - - $skip = 1; - foreach ($options as $key => $option) { - if ($option{0} != '_' && strtolower($option) != 'getinstance') { - $prompt .= "{$skip} - {$option}\n"; - $choices[$skip] = strtolower($option); - $skip++; - } + $anotherValidator = 'y'; + while ($anotherValidator == 'y') { + if ($this->interactive) { + $this->out(''); + $this->out(sprintf(__('Field: %s', true), $fieldName)); + $this->out(sprintf(__('Type: %s', true), $metaData['type'])); + $this->hr(); + $this->out(__('Please select one of the following validation options:', true)); + $this->hr(); } - $methods = array_flip($choices); + $prompt = ''; + for ($i = 1; $i < $defaultChoice; $i++) { + $prompt .= $i . ' - ' . $this->__validations[$i] . "\n"; + } + $prompt .= sprintf(__("%s - Do not do any validation on this field.\n", true), $defaultChoice); + $prompt .= __("... or enter in a valid regex validation string.\n", true); - $prompt .= "{$skip} - Do not do any validation on this field.\n"; - $prompt .= "... or enter in a valid regex validation string.\n"; - - $guess = $skip; - if ($field['null'] != 1 && $fieldName != $model->primaryKey && !in_array($fieldName, array('created', 'modified', 'updated'))) { + $methods = array_flip($this->__validations); + $guess = $defaultChoice; + if ($metaData['null'] != 1 && !in_array($fieldName, array($primaryKey, 'created', 'modified', 'updated'))) { if ($fieldName == 'email') { $guess = $methods['email']; - } elseif ($field['type'] == 'string') { + } elseif ($metaData['type'] == 'string') { $guess = $methods['notempty']; - } elseif ($field['type'] == 'integer') { + } elseif ($metaData['type'] == 'integer') { $guess = $methods['numeric']; - } elseif ($field['type'] == 'boolean') { + } elseif ($metaData['type'] == 'boolean') { $guess = $methods['numeric']; - } elseif ($field['type'] == 'datetime') { + } elseif ($metaData['type'] == 'datetime' || $metaData['type'] == 'date') { $guess = $methods['date']; + } elseif ($metaData['type'] == 'time') { + $guess = $methods['time']; } } - if ($interactive === true) { - $this->out(''); + if ($this->interactive === true) { $choice = $this->in($prompt, null, $guess); + if (in_array($choice, $alreadyChosen)) { + $this->out(__("You have already chosen that validation rule,\nplease choose again", true)); + continue; + } + $alreadyChosen[] = $choice; } else { $choice = $guess; } - if ($choice != $skip) { - if (is_numeric($choice) && isset($choices[$choice])) { - $validate[$fieldName] = $choices[$choice]; + $validatorName = $this->__validations[$choice]; + if ($choice != $defaultChoice) { + if (is_numeric($choice) && isset($this->__validations[$choice])) { + $validate[$validatorName] = $this->__validations[$choice]; } else { - $validate[$fieldName] = $choice; + $validate[$validatorName] = $choice; } } + if ($this->interactive == true && $choice != $defaultChoice) { + $anotherValidator = $this->in(__('Would you like to add another validation rule?', true), array('y', 'n'), 'n'); + } else { + $anotherValidator = 'n'; + } } return $validate; } @@ -297,376 +443,312 @@ class ModelTask extends Shell { * Handles associations * * @param object $model - * @param boolean $interactive * @return array $assocaitons * @access public */ - function doAssociations(&$model, $interactive = true) { - + function doAssociations(&$model) { if (!is_object($model)) { return false; } - $this->out(__('One moment while the associations are detected.', true)); + if ($this->interactive === true) { + $this->out(__('One moment while the associations are detected.', true)); + } $fields = $model->schema(); - if (empty($fields)) { return false; } - $primaryKey = $model->primaryKey; - $foreignKey = $this->_modelKey($model->name); - - $associations = array('belongsTo' => array(), 'hasMany' => array(), 'hasOne'=> array(), 'hasAndBelongsToMany' => array()); + $associations = array( + 'belongsTo' => array(), 'hasMany' => array(), 'hasOne'=> array(), 'hasAndBelongsToMany' => array() + ); $possibleKeys = array(); - //Look for belongsTo - $i = 0; - foreach ($fields as $fieldName => $field) { - $offset = strpos($fieldName, '_id'); - if ($fieldName != $model->primaryKey && $offset !== false) { - $tmpModelName = $this->_modelNameFromKey($fieldName); - $associations['belongsTo'][$i]['alias'] = $tmpModelName; - $associations['belongsTo'][$i]['className'] = $tmpModelName; - $associations['belongsTo'][$i]['foreignKey'] = $fieldName; - $i++; - } - } - //Look for hasOne and hasMany and hasAndBelongsToMany - $i = $j = 0; + $associations = $this->findBelongsTo($model, $associations); + $associations = $this->findHasOneAndMany($model, $associations); + $associations = $this->findHasAndBelongsToMany($model, $associations); - foreach ($this->__tables as $otherTable) { - App::import('Model'); - $tmpModelName = $this->_modelName($otherTable); - $tempOtherModel = & new Model(array('name' => $tmpModelName, 'table' => $otherTable, 'ds' => $model->useDbConfig)); - $modelFieldsTemp = $tempOtherModel->schema(); - - $offset = strpos($otherTable, $model->table . '_'); - $otherOffset = strpos($otherTable, '_' . $model->table); - - foreach ($modelFieldsTemp as $fieldName => $field) { - if ($field['type'] == 'integer' || $field['type'] == 'string') { - $possibleKeys[$otherTable][] = $fieldName; - } - if ($fieldName != $model->primaryKey && $fieldName == $foreignKey && $offset === false && $otherOffset === false) { - $associations['hasOne'][$j]['alias'] = $tempOtherModel->name; - $associations['hasOne'][$j]['className'] = $tempOtherModel->name; - $associations['hasOne'][$j]['foreignKey'] = $fieldName; - - $associations['hasMany'][$j]['alias'] = $tempOtherModel->name; - $associations['hasMany'][$j]['className'] = $tempOtherModel->name; - $associations['hasMany'][$j]['foreignKey'] = $fieldName; - $j++; - } - } - - if ($offset !== false) { - $offset = strlen($model->table . '_'); - $tmpModelName = $this->_modelName(substr($otherTable, $offset)); - $associations['hasAndBelongsToMany'][$i]['alias'] = $tmpModelName; - $associations['hasAndBelongsToMany'][$i]['className'] = $tmpModelName; - $associations['hasAndBelongsToMany'][$i]['foreignKey'] = $foreignKey; - $associations['hasAndBelongsToMany'][$i]['associationForeignKey'] = $this->_modelKey($tmpModelName); - $associations['hasAndBelongsToMany'][$i]['joinTable'] = $otherTable; - $i++; - } - - if ($otherOffset !== false) { - $tmpModelName = $this->_modelName(substr($otherTable, 0, $otherOffset)); - $associations['hasAndBelongsToMany'][$i]['alias'] = $tmpModelName; - $associations['hasAndBelongsToMany'][$i]['className'] = $tmpModelName; - $associations['hasAndBelongsToMany'][$i]['foreignKey'] = $foreignKey; - $associations['hasAndBelongsToMany'][$i]['associationForeignKey'] = $this->_modelKey($tmpModelName); - $associations['hasAndBelongsToMany'][$i]['joinTable'] = $otherTable; - $i++; - } - } - - if ($interactive !== true) { + if ($this->interactive !== true) { unset($associations['hasOne']); } - if ($interactive === true) { + if ($this->interactive === true) { $this->hr(); if (empty($associations)) { $this->out(__('None found.', true)); } else { $this->out(__('Please confirm the following associations:', true)); $this->hr(); - foreach ($associations as $type => $settings) { - if (!empty($associations[$type])) { - $count = count($associations[$type]); - $response = 'y'; - for ($i = 0; $i < $count; $i++) { - $prompt = "{$model->name} {$type} {$associations[$type][$i]['alias']}"; - $response = $this->in("{$prompt}?", array('y','n'), 'y'); - - if ('n' == low($response) || 'no' == low($response)) { - unset($associations[$type][$i]); - } else { - if ($model->name === $associations[$type][$i]['alias']) { - if ($type === 'belongsTo') { - $alias = 'Parent' . $associations[$type][$i]['alias']; - } - if ($type === 'hasOne' || $type === 'hasMany') { - $alias = 'Child' . $associations[$type][$i]['alias']; - } - - $alternateAlias = $this->in(sprintf(__('This is a self join. Use %s as the alias', true), $alias), array('y', 'n'), 'y'); - - if ('n' == low($alternateAlias) || 'no' == low($alternateAlias)) { - $associations[$type][$i]['alias'] = $this->in(__('Specify an alternate alias.', true)); - } else { - $associations[$type][$i]['alias'] = $alias; - } - } - } - } - $associations[$type] = array_merge($associations[$type]); - } - } + $associations = $this->confirmAssociations($model, $associations); } + $associations = $this->doMoreAssociations($model, $associations); + } + return $associations; + } - $wannaDoMoreAssoc = $this->in(__('Would you like to define some additional model associations?', true), array('y','n'), 'n'); - - while ((low($wannaDoMoreAssoc) == 'y' || low($wannaDoMoreAssoc) == 'yes')) { - $assocs = array(1 => 'belongsTo', 2 => 'hasOne', 3 => 'hasMany', 4 => 'hasAndBelongsToMany'); - $bad = true; - while ($bad) { - $this->out(__('What is the association type?', true)); - $prompt = "1. belongsTo\n"; - $prompt .= "2. hasOne\n"; - $prompt .= "3. hasMany\n"; - $prompt .= "4. hasAndBelongsToMany\n"; - $assocType = intval($this->in($prompt, null, __("Enter a number", true))); - - if (intval($assocType) < 1 || intval($assocType) > 4) { - $this->out(__('The selection you entered was invalid. Please enter a number between 1 and 4.', true)); - } else { - $bad = false; - } - } - $this->out(__('For the following options be very careful to match your setup exactly. Any spelling mistakes will cause errors.', true)); - $this->hr(); - $alias = $this->in(__('What is the alias for this association?', true)); - $className = $this->in(sprintf(__('What className will %s use?', true), $alias), null, $alias ); - $suggestedForeignKey = null; - if ($assocType == '1') { - $showKeys = $possibleKeys[$model->table]; - $suggestedForeignKey = $this->_modelKey($alias); - } else { - $otherTable = Inflector::tableize($className); - if (in_array($otherTable, $this->__tables)) { - if ($assocType < '4') { - $showKeys = $possibleKeys[$otherTable]; - } else { - $showKeys = null; - } - } else { - $otherTable = $this->in(__('What is the table for this model?', true)); - $showKeys = $possibleKeys[$otherTable]; - } - $suggestedForeignKey = $this->_modelKey($model->name); - } - if (!empty($showKeys)) { - $this->out(__('A helpful List of possible keys', true)); - for ($i = 0; $i < count($showKeys); $i++) { - $this->out($i + 1 . ". " . $showKeys[$i]); - } - $foreignKey = $this->in(__('What is the foreignKey?', true), null, __("Enter a number", true)); - if (intval($foreignKey) > 0 && intval($foreignKey) <= $i ) { - $foreignKey = $showKeys[intval($foreignKey) - 1]; - } - } - if (!isset($foreignKey)) { - $foreignKey = $this->in(__('What is the foreignKey? Specify your own.', true), null, $suggestedForeignKey); - } - if ($assocType == '4') { - $associationForeignKey = $this->in(__('What is the associationForeignKey?', true), null, $this->_modelKey($model->name)); - $joinTable = $this->in(__('What is the joinTable?', true)); - } - $associations[$assocs[$assocType]] = array_values((array)$associations[$assocs[$assocType]]); - $count = count($associations[$assocs[$assocType]]); - $i = ($count > 0) ? $count : 0; - $associations[$assocs[$assocType]][$i]['alias'] = $alias; - $associations[$assocs[$assocType]][$i]['className'] = $className; - $associations[$assocs[$assocType]][$i]['foreignKey'] = $foreignKey; - if ($assocType == '4') { - $associations[$assocs[$assocType]][$i]['associationForeignKey'] = $associationForeignKey; - $associations[$assocs[$assocType]][$i]['joinTable'] = $joinTable; - } - $wannaDoMoreAssoc = $this->in(__('Define another association?', true), array('y','n'), 'y'); +/** + * Find belongsTo relations and add them to the associations list. + * + * @param object $model Model instance of model being generated. + * @param array $associations Array of inprogress associations + * @return array $associations with belongsTo added in. + **/ + function findBelongsTo(&$model, $associations) { + $fields = $model->schema(); + foreach ($fields as $fieldName => $field) { + $offset = strpos($fieldName, '_id'); + if ($fieldName != $model->primaryKey && $fieldName != 'parent_id' && $offset !== false) { + $tmpModelName = $this->_modelNameFromKey($fieldName); + $associations['belongsTo'][] = array( + 'alias' => $tmpModelName, + 'className' => $tmpModelName, + 'foreignKey' => $fieldName, + ); + } elseif ($fieldName == 'parent_id') { + $associations['belongsTo'][] = array( + 'alias' => 'Parent' . $model->name, + 'className' => $model->name, + 'foreignKey' => $fieldName, + ); } } return $associations; } + +/** + * Find the hasOne and HasMany relations and add them to associations list + * + * @param object $model Model instance being generated + * @param array $associations Array of inprogress associations + * @return array $associations with hasOne and hasMany added in. + **/ + function findHasOneAndMany(&$model, $associations) { + $foreignKey = $this->_modelKey($model->name); + foreach ($this->__tables as $otherTable) { + $tempOtherModel = $this->_getModelObject($this->_modelName($otherTable)); + $modelFieldsTemp = $tempOtherModel->schema(); + + $pattern = '/_' . preg_quote($model->table, '/') . '|' . preg_quote($model->table, '/') . '_/'; + $possibleJoinTable = preg_match($pattern , $otherTable); + if ($possibleJoinTable == true) { + continue; + } + foreach ($modelFieldsTemp as $fieldName => $field) { + $assoc = false; + if ($fieldName != $model->primaryKey && $fieldName == $foreignKey) { + $assoc = array( + 'alias' => $tempOtherModel->name, + 'className' => $tempOtherModel->name, + 'foreignKey' => $fieldName + ); + } elseif ($otherTable == $model->table && $fieldName == 'parent_id') { + $assoc = array( + 'alias' => 'Child' . $model->name, + 'className' => $model->name, + 'foreignKey' => $fieldName + ); + } + if ($assoc) { + $associations['hasOne'][] = $assoc; + $associations['hasMany'][] = $assoc; + } + + } + } + return $associations; + } + +/** + * Find the hasAndBelongsToMany relations and add them to associations list + * + * @param object $model Model instance being generated + * @param array $associations Array of inprogress associations + * @return array $associations with hasAndBelongsToMany added in. + **/ + function findHasAndBelongsToMany(&$model, $associations) { + $foreignKey = $this->_modelKey($model->name); + foreach ($this->__tables as $otherTable) { + $tempOtherModel = $this->_getModelObject($this->_modelName($otherTable)); + $modelFieldsTemp = $tempOtherModel->schema(); + + $offset = strpos($otherTable, $model->table . '_'); + $otherOffset = strpos($otherTable, '_' . $model->table); + + if ($offset !== false) { + $offset = strlen($model->table . '_'); + $habtmName = $this->_modelName(substr($otherTable, $offset)); + $associations['hasAndBelongsToMany'][] = array( + 'alias' => $habtmName, + 'className' => $habtmName, + 'foreignKey' => $foreignKey, + 'associationForeignKey' => $this->_modelKey($habtmName), + 'joinTable' => $otherTable + ); + } elseif ($otherOffset !== false) { + $habtmName = $this->_modelName(substr($otherTable, 0, $otherOffset)); + $associations['hasAndBelongsToMany'][] = array( + 'alias' => $habtmName, + 'className' => $habtmName, + 'foreignKey' => $foreignKey, + 'associationForeignKey' => $this->_modelKey($habtmName), + 'joinTable' => $otherTable + ); + } + } + return $associations; + } + +/** + * Interact with the user and confirm associations. + * + * @param array $model Temporary Model instance. + * @param array $associations Array of associations to be confirmed. + * @return array Array of confirmed associations + **/ + function confirmAssociations(&$model, $associations) { + foreach ($associations as $type => $settings) { + if (!empty($associations[$type])) { + $count = count($associations[$type]); + $response = 'y'; + for ($i = 0; $i < $count; $i++) { + $prompt = "{$model->name} {$type} {$associations[$type][$i]['alias']}"; + $response = $this->in("{$prompt}?", array('y','n'), 'y'); + + if ('n' == low($response)) { + unset($associations[$type][$i]); + } elseif ($type == 'hasMany') { + unset($associations['hasOne'][$i]); + } + } + $associations[$type] = array_merge($associations[$type]); + } + } + return $associations; + } + +/** + * Interact with the user and generate additional non-conventional associations + * + * @param object $model Temporary model instance + * @param array $associations Array of associations. + * @return array Array of associations. + **/ + function doMoreAssociations($model, $associations) { + $prompt = __('Would you like to define some additional model associations?', true); + $wannaDoMoreAssoc = $this->in($prompt, array('y','n'), 'n'); + $possibleKeys = $this->_generatePossibleKeys(); + while (low($wannaDoMoreAssoc) == 'y') { + $assocs = array('belongsTo', 'hasOne', 'hasMany', 'hasAndBelongsToMany'); + $this->out(__('What is the association type?', true)); + $assocType = intval($this->inOptions($assocs, __('Enter a number',true))); + + $this->out(__("For the following options be very careful to match your setup exactly.\nAny spelling mistakes will cause errors.", true)); + $this->hr(); + + $alias = $this->in(__('What is the alias for this association?', true)); + $className = $this->in(sprintf(__('What className will %s use?', true), $alias), null, $alias ); + $suggestedForeignKey = null; + + if ($assocType == 0) { + $showKeys = $possibleKeys[$model->table]; + $suggestedForeignKey = $this->_modelKey($alias); + } else { + $otherTable = Inflector::tableize($className); + if (in_array($otherTable, $this->__tables)) { + if ($assocType < 3) { + $showKeys = $possibleKeys[$otherTable]; + } else { + $showKeys = null; + } + } else { + $otherTable = $this->in(__('What is the table for this model?', true)); + $showKeys = $possibleKeys[$otherTable]; + } + $suggestedForeignKey = $this->_modelKey($model->name); + } + if (!empty($showKeys)) { + $this->out(__('A helpful List of possible keys', true)); + $foreignKey = $this->inOptions($showKeys, __('What is the foreignKey?', true)); + $foreignKey = $showKeys[intval($foreignKey)]; + } + if (!isset($foreignKey)) { + $foreignKey = $this->in(__('What is the foreignKey? Specify your own.', true), null, $suggestedForeignKey); + } + if ($assocType == 3) { + $associationForeignKey = $this->in(__('What is the associationForeignKey?', true), null, $this->_modelKey($model->name)); + $joinTable = $this->in(__('What is the joinTable?', true)); + } + $associations[$assocs[$assocType]] = array_values((array)$associations[$assocs[$assocType]]); + $count = count($associations[$assocs[$assocType]]); + $i = ($count > 0) ? $count : 0; + $associations[$assocs[$assocType]][$i]['alias'] = $alias; + $associations[$assocs[$assocType]][$i]['className'] = $className; + $associations[$assocs[$assocType]][$i]['foreignKey'] = $foreignKey; + if ($assocType == 3) { + $associations[$assocs[$assocType]][$i]['associationForeignKey'] = $associationForeignKey; + $associations[$assocs[$assocType]][$i]['joinTable'] = $joinTable; + } + $wannaDoMoreAssoc = $this->in(__('Define another association?', true), array('y','n'), 'y'); + } + return $associations; + } + +/** + * Finds all possible keys to use on custom associations. + * + * @return array array of tables and possible keys + **/ + function _generatePossibleKeys() { + $possible = array(); + foreach ($this->__tables as $otherTable) { + $tempOtherModel = & new Model(array('table' => $otherTable, 'ds' => $this->connection)); + $modelFieldsTemp = $tempOtherModel->schema(); + foreach ($modelFieldsTemp as $fieldName => $field) { + if ($field['type'] == 'integer' || $field['type'] == 'string') { + $possible[$otherTable][] = $fieldName; + } + } + } + return $possible; + } + /** * Assembles and writes a Model file. * * @param mixed $name Model name or object - * @param mixed $associations if array and $name is not an object assume Model associations array otherwise boolean interactive - * @param array $validate Validation rules - * @param string $primaryKey Primary key to use - * @param string $useTable Table to use - * @param string $useDbConfig Database configuration setting to use + * @param mixed $data if array and $name is not an object assume bake data, otherwise boolean. * @access private */ - function bake($name, $associations = array(), $validate = array(), $primaryKey = 'id', $useTable = null, $useDbConfig = 'default') { - + function bake($name, $data = array()) { if (is_object($name)) { - if (!is_array($associations)) { - $associations = $this->doAssociations($name, $associations); - $validate = $this->doValidation($name, $associations); + if ($data == false) { + $data = $associations = array(); + $data['associations'] = $this->doAssociations($name, $associations); + $data['validate'] = $this->doValidation($name); } - $primaryKey = $name->primaryKey; - $useTable = $name->table; - $useDbConfig = $name->useDbConfig; - $name = $name->name; + $data['primaryKey'] = $name->primaryKey; + $data['useTable'] = $name->table; + $data['useDbConfig'] = $name->useDbConfig; + $data['name'] = $name = $name->name; + } else { + $data['name'] = $name; } + $defaults = array('associations' => array(), 'validate' => array(), 'primaryKey' => 'id', + 'useTable' => null, 'useDbConfig' => 'default', 'displayField' => null); + $data = array_merge($defaults, $data); - $out = "plugin}AppModel {\n\n"; - $out .= "\tvar \$name = '{$name}';\n"; + $this->Template->set($data); + $this->Template->set('plugin', Inflector::camelize($this->plugin)); + $out = $this->Template->generate('classes', 'model'); - if ($useDbConfig !== 'default') { - $out .= "\tvar \$useDbConfig = '$useDbConfig';\n"; + $path = $this->path; + if (isset($this->plugin)) { + $path = $this->_pluginPath($this->plugin) . 'models' . DS; } - - if (($useTable && $useTable !== Inflector::tableize($name)) || $useTable === false) { - $table = "'$useTable'"; - if (!$useTable) { - $table = 'false'; - } - $out .= "\tvar \$useTable = $table;\n"; - } - - if ($primaryKey !== 'id') { - $out .= "\tvar \$primaryKey = '$primaryKey';\n"; - } - - $validateCount = count($validate); - if (is_array($validate) && $validateCount > 0) { - $out .= "\tvar \$validate = array(\n"; - $keys = array_keys($validate); - for ($i = 0; $i < $validateCount; $i++) { - $val = "'" . $validate[$keys[$i]] . "'"; - $out .= "\t\t'" . $keys[$i] . "' => array({$val})"; - if ($i + 1 < $validateCount) { - $out .= ","; - } - $out .= "\n"; - } - $out .= "\t);\n"; - } - $out .= "\n"; - - if (!empty($associations)) { - if (!empty($associations['belongsTo']) || !empty($associations['hasOne']) || !empty($associations['hasMany']) || !empty($associations['hasAndBelongsToMany'])) { - $out.= "\t//The Associations below have been created with all possible keys, those that are not needed can be removed\n"; - } - - if (!empty($associations['belongsTo'])) { - $out .= "\tvar \$belongsTo = array(\n"; - $belongsToCount = count($associations['belongsTo']); - - for ($i = 0; $i < $belongsToCount; $i++) { - $out .= "\t\t'{$associations['belongsTo'][$i]['alias']}' => array(\n"; - $out .= "\t\t\t'className' => '{$associations['belongsTo'][$i]['className']}',\n"; - $out .= "\t\t\t'foreignKey' => '{$associations['belongsTo'][$i]['foreignKey']}',\n"; - $out .= "\t\t\t'conditions' => '',\n"; - $out .= "\t\t\t'fields' => '',\n"; - $out .= "\t\t\t'order' => ''\n"; - $out .= "\t\t)"; - if ($i + 1 < $belongsToCount) { - $out .= ","; - } - $out .= "\n"; - - } - $out .= "\t);\n\n"; - } - - if (!empty($associations['hasOne'])) { - $out .= "\tvar \$hasOne = array(\n"; - $hasOneCount = count($associations['hasOne']); - - for ($i = 0; $i < $hasOneCount; $i++) { - $out .= "\t\t'{$associations['hasOne'][$i]['alias']}' => array(\n"; - $out .= "\t\t\t'className' => '{$associations['hasOne'][$i]['className']}',\n"; - $out .= "\t\t\t'foreignKey' => '{$associations['hasOne'][$i]['foreignKey']}',\n"; - $out .= "\t\t\t'dependent' => false,\n"; - $out .= "\t\t\t'conditions' => '',\n"; - $out .= "\t\t\t'fields' => '',\n"; - $out .= "\t\t\t'order' => ''\n"; - $out .= "\t\t)"; - if ($i + 1 < $hasOneCount) { - $out .= ","; - } - $out .= "\n"; - - } - $out .= "\t);\n\n"; - } - - if (!empty($associations['hasMany'])) { - $out .= "\tvar \$hasMany = array(\n"; - $hasManyCount = count($associations['hasMany']); - - for ($i = 0; $i < $hasManyCount; $i++) { - $out .= "\t\t'{$associations['hasMany'][$i]['alias']}' => array(\n"; - $out .= "\t\t\t'className' => '{$associations['hasMany'][$i]['className']}',\n"; - $out .= "\t\t\t'foreignKey' => '{$associations['hasMany'][$i]['foreignKey']}',\n"; - $out .= "\t\t\t'dependent' => false,\n"; - $out .= "\t\t\t'conditions' => '',\n"; - $out .= "\t\t\t'fields' => '',\n"; - $out .= "\t\t\t'order' => '',\n"; - $out .= "\t\t\t'limit' => '',\n"; - $out .= "\t\t\t'offset' => '',\n"; - $out .= "\t\t\t'exclusive' => '',\n"; - $out .= "\t\t\t'finderQuery' => '',\n"; - $out .= "\t\t\t'counterQuery' => ''\n"; - $out .= "\t\t)"; - if ($i + 1 < $hasManyCount) { - $out .= ","; - } - $out .= "\n"; - } - $out .= "\t);\n\n"; - } - - if (!empty($associations['hasAndBelongsToMany'])) { - $out .= "\tvar \$hasAndBelongsToMany = array(\n"; - $hasAndBelongsToManyCount = count($associations['hasAndBelongsToMany']); - - for ($i = 0; $i < $hasAndBelongsToManyCount; $i++) { - $out .= "\t\t'{$associations['hasAndBelongsToMany'][$i]['alias']}' => array(\n"; - $out .= "\t\t\t'className' => '{$associations['hasAndBelongsToMany'][$i]['className']}',\n"; - $out .= "\t\t\t'joinTable' => '{$associations['hasAndBelongsToMany'][$i]['joinTable']}',\n"; - $out .= "\t\t\t'foreignKey' => '{$associations['hasAndBelongsToMany'][$i]['foreignKey']}',\n"; - $out .= "\t\t\t'associationForeignKey' => '{$associations['hasAndBelongsToMany'][$i]['associationForeignKey']}',\n"; - $out .= "\t\t\t'unique' => true,\n"; - $out .= "\t\t\t'conditions' => '',\n"; - $out .= "\t\t\t'fields' => '',\n"; - $out .= "\t\t\t'order' => '',\n"; - $out .= "\t\t\t'limit' => '',\n"; - $out .= "\t\t\t'offset' => '',\n"; - $out .= "\t\t\t'finderQuery' => '',\n"; - $out .= "\t\t\t'deleteQuery' => '',\n"; - $out .= "\t\t\t'insertQuery' => ''\n"; - $out .= "\t\t)"; - if ($i + 1 < $hasAndBelongsToManyCount) { - $out .= ","; - } - $out .= "\n"; - } - $out .= "\t);\n\n"; - } - } - $out .= "}\n"; - $out .= "?>"; - $filename = $this->path . Inflector::underscore($name) . '.php'; + $filename = $path . Inflector::underscore($name) . '.php'; $this->out("\nBaking model class for $name..."); - return $this->createFile($filename, $out); + $this->createFile($filename, $out); + return $out; } /** @@ -675,80 +757,75 @@ class ModelTask extends Shell { * @param string $className Model class name * @access private */ - function bakeTest($className, $useTable = null, $associations = array()) { - $results = $this->fixture($className, $useTable); - - if ($results) { - $fixtureInc = 'app'; - if ($this->plugin) { - $fixtureInc = 'plugin.'.Inflector::underscore($this->plugin); - } - - $fixture[] = "'{$fixtureInc}." . Inflector::underscore($className) ."'"; - - if (!empty($associations)) { - $assoc[] = Set::extract($associations, 'belongsTo.{n}.className'); - $assoc[] = Set::extract($associations, 'hasOne.{n}.className'); - $assoc[] = Set::extract($associations, 'hasMany.{n}.className'); - foreach ($assoc as $key => $value) { - if (is_array($value)) { - foreach ($value as $class) { - $fixture[] = "'{$fixtureInc}." . Inflector::underscore($class) ."'"; - } - } - } - } - $fixture = join(", ", $fixture); - - $import = $className; - if (isset($this->plugin)) { - $import = $this->plugin . '.' . $className; - } - - $out = "App::import('Model', '$import');\n\n"; - $out .= "class {$className}TestCase extends CakeTestCase {\n"; - $out .= "\tvar \${$className} = null;\n"; - $out .= "\tvar \$fixtures = array($fixture);\n\n"; - $out .= "\tfunction startTest() {\n"; - $out .= "\t\t\$this->{$className} =& ClassRegistry::init('{$className}');\n"; - $out .= "\t}\n\n"; - $out .= "\tfunction test{$className}Instance() {\n"; - $out .= "\t\t\$this->assertTrue(is_a(\$this->{$className}, '{$className}'));\n"; - $out .= "\t}\n\n"; - $out .= "\tfunction test{$className}Find() {\n"; - $out .= "\t\t\$this->{$className}->recursive = -1;\n"; - $out .= "\t\t\$results = \$this->{$className}->find('first');\n\t\t\$this->assertTrue(!empty(\$results));\n\n"; - $out .= "\t\t\$expected = array('$className' => array(\n$results\n\t\t));\n"; - $out .= "\t\t\$this->assertEqual(\$results, \$expected);\n"; - $out .= "\t}\n"; - $out .= "}\n"; - - $path = MODEL_TESTS; - if (isset($this->plugin)) { - $pluginPath = 'plugins' . DS . Inflector::underscore($this->plugin) . DS; - $path = APP . $pluginPath . 'tests' . DS . 'cases' . DS . 'models' . DS; - } - - $filename = Inflector::underscore($className).'.test.php'; - $this->out("\nBaking unit test for $className..."); - - $header = '$Id'; - $content = ""; - return $this->createFile($path . $filename, $content); - } - return false; + function bakeTest($className) { + $this->Test->plugin = $this->plugin; + $this->Test->connection = $this->connection; + return $this->Test->bake('Model', $className); } + /** * outputs the a list of possible models or controllers from database * * @param string $useDbConfig Database configuration name * @access public */ - function listAll($useDbConfig = 'default', $interactive = true) { + function listAll($useDbConfig = null) { + $this->__tables = $this->getAllTables($useDbConfig); + + if ($this->interactive === true) { + $this->out(__('Possible Models based on your current database:', true)); + $this->_modelNames = array(); + $count = count($this->__tables); + for ($i = 0; $i < $count; $i++) { + $this->_modelNames[] = $this->_modelName($this->__tables[$i]); + $this->out($i + 1 . ". " . $this->_modelNames[$i]); + } + } + return $this->__tables; + } + +/** + * Interact with the user to determine the table name of a particular model + * + * @param string $modelName Name of the model you want a table for. + * @param string $useDbConfig Name of the database config you want to get tables from. + * @return void + **/ + function getTable($modelName, $useDbConfig = null) { + if (!isset($useDbConfig)) { + $useDbConfig = $this->connection; + } + $db =& ConnectionManager::getDataSource($useDbConfig); + $useTable = Inflector::tableize($modelName); + $fullTableName = $db->fullTableName($useTable, false); + $tableIsGood = false; + + if (array_search($useTable, $this->__tables) === false) { + $this->out(''); + $this->out(sprintf(__("Given your model named '%s',\nCake would expect a database table named '%s'", true), $modelName, $fullTableName)); + $tableIsGood = $this->in(__('Do you want to use this table?', true), array('y','n'), 'y'); + } + if (strtolower($tableIsGood) == 'n') { + $useTable = $this->in(__('What is the name of the table?', true)); + } + return $useTable; + } + +/** + * Get an Array of all the tables in the supplied connection + * will halt the script if no tables are found. + * + * @param string $useDbConfig Connection name to scan. + * @return array Array of tables in the database. + **/ + function getAllTables($useDbConfig = null) { + if (!isset($useDbConfig)) { + $useDbConfig = $this->connection; + } + $tables = array(); $db =& ConnectionManager::getDataSource($useDbConfig); $usePrefix = empty($db->config['prefix']) ? '' : $db->config['prefix']; if ($usePrefix) { - $tables = array(); foreach ($db->listSources() as $table) { if (!strncmp($table, $usePrefix, strlen($usePrefix))) { $tables[] = substr($table, strlen($usePrefix)); @@ -761,32 +838,22 @@ class ModelTask extends Shell { $this->err(__('Your database does not have any tables.', true)); $this->_stop(); } - - $this->__tables = $tables; - - if ($interactive === true) { - $this->out(__('Possible Models based on your current database:', true)); - $this->_modelNames = array(); - $count = count($tables); - for ($i = 0; $i < $count; $i++) { - $this->_modelNames[] = $this->_modelName($tables[$i]); - $this->out($i + 1 . ". " . $this->_modelNames[$i]); - } - } + return $tables; } + /** * Forces the user to specify the model he wants to bake, and returns the selected model name. * * @return string the model name * @access public */ - function getName($useDbConfig) { + function getName($useDbConfig = null) { $this->listAll($useDbConfig); $enteredModel = ''; while ($enteredModel == '') { - $enteredModel = $this->in(__("Enter a number from the list above, type in the name of another model, or 'q' to exit", true), null, 'q'); + $enteredModel = $this->in(__("Enter a number from the list above,\ntype in the name of another model, or 'q' to exit", true), null, 'q'); if ($enteredModel === 'q') { $this->out(__("Exit", true)); @@ -794,19 +861,18 @@ class ModelTask extends Shell { } if ($enteredModel == '' || intval($enteredModel) > count($this->_modelNames)) { - $this->err(__("The model name you supplied was empty, or the number you selected was not an option. Please try again.", true)); + $this->err(__("The model name you supplied was empty,\nor the number you selected was not an option. Please try again.", true)); $enteredModel = ''; } } - if (intval($enteredModel) > 0 && intval($enteredModel) <= count($this->_modelNames)) { $currentModelName = $this->_modelNames[intval($enteredModel) - 1]; } else { $currentModelName = $enteredModel; } - return $currentModelName; } + /** * Displays help contents * @@ -817,122 +883,32 @@ class ModelTask extends Shell { $this->out("Usage: cake bake model "); $this->hr(); $this->out('Commands:'); - $this->out("\n\tmodel\n\t\tbakes model in interactive mode."); - $this->out("\n\tmodel \n\t\tbakes model file with no associations or validation"); + $this->out(''); + $this->out("model"); + $this->out("\tbakes model in interactive mode."); + $this->out(''); + $this->out("model "); + $this->out("\tbakes model file with no associations or validation"); + $this->out(''); + $this->out("model all"); + $this->out("\tbakes all model files with associations and validation"); $this->out(""); $this->_stop(); } + /** - * Builds the tests fixtures for the model and create the file + * Interact with FixtureTask to automatically bake fixtures when baking models. * - * @param string $model the name of the model - * @param string $useTable table name - * @return array $records, used in ModelTask::bakeTest() to create $expected - * @todo move this to a task - */ - function fixture($model, $useTable = null) { - if (!class_exists('CakeSchema')) { - App::import('Model', 'CakeSchema'); - } - $out = "\nclass {$model}Fixture extends CakeTestFixture {\n"; - $out .= "\tvar \$name = '$model';\n"; - - if (!$useTable) { - $useTable = Inflector::tableize($model); - } else { - $out .= "\tvar \$table = '$useTable';\n"; - } - $schema = new CakeSchema(); - $data = $schema->read(array('models' => false)); - - if (!isset($data['tables'][$useTable])) { - return false; - } - $tables[$model] = $data['tables'][$useTable]; - - foreach ($tables as $table => $fields) { - if (!is_numeric($table) && $table !== 'missing') { - $out .= "\tvar \$fields = array(\n"; - $records = array(); - if (is_array($fields)) { - $cols = array(); - foreach ($fields as $field => $value) { - if ($field != 'indexes') { - if (is_string($value)) { - $type = $value; - $value = array('type'=> $type); - } - $col = "\t\t'{$field}' => array('type'=>'" . $value['type'] . "', "; - - switch ($value['type']) { - case 'integer': - $insert = 1; - break; - case 'string'; - $insert = "Lorem ipsum dolor sit amet"; - if (!empty($value['length'])) { - $insert = substr($insert, 0, (int)$value['length'] - 2); - } - $insert = "'$insert'"; - break; - case 'datetime': - $ts = date('Y-m-d H:i:s'); - $insert = "'$ts'"; - break; - case 'date': - $ts = date('Y-m-d'); - $insert = "'$ts'"; - break; - case 'time': - $ts = date('H:i:s'); - $insert = "'$ts'"; - break; - case 'boolean': - $insert = 1; - break; - case 'text': - $insert = - "'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida,"; - $insert .= "phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam,"; - $insert .= "vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit,"; - $insert .= "feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.'"; - break; - } - $records[] = "\t\t'$field' => $insert"; - unset($value['type']); - $col .= join(', ', $schema->__values($value)); - } else { - $col = "\t\t'indexes' => array("; - $props = array(); - foreach ((array)$value as $key => $index) { - $props[] = "'{$key}' => array(".join(', ', $schema->__values($index)).")"; - } - $col .= join(', ', $props); - } - $col .= ")"; - $cols[] = $col; - } - $out .= join(",\n", $cols); - } - $out .= "\n\t);\n"; - } - } - $records = join(",\n", $records); - $out .= "\tvar \$records = array(array(\n$records\n\t));\n"; - $out .= "}\n"; - $path = TESTS . DS . 'fixtures' . DS; - if (isset($this->plugin)) { - $pluginPath = 'plugins' . DS . Inflector::underscore($this->plugin) . DS; - $path = APP . $pluginPath . 'tests' . DS . 'fixtures' . DS; - } - $filename = Inflector::underscore($model).'_fixture.php'; - $header = '$Id'; - $content = ""; - $this->out("\nBaking test fixture for $model..."); - if ($this->createFile($path . $filename, $content)) { - return str_replace("\t\t", "\t\t\t", $records); - } - return false; + * @param string $className Name of class to bake fixture for + * @param string $useTable Optional table name for fixture to use. + * @access public + * @return void + * @see FixtureTask::bake + **/ + function bakeFixture($className, $useTable = null) { + $this->Fixture->connection = $this->connection; + $this->Fixture->plugin = $this->plugin; + $this->Fixture->bake($className, $useTable); } } ?> \ No newline at end of file diff --git a/cake/console/libs/tasks/plugin.php b/cake/console/libs/tasks/plugin.php index 1fc81374b..833f67be6 100644 --- a/cake/console/libs/tasks/plugin.php +++ b/cake/console/libs/tasks/plugin.php @@ -1,5 +1,4 @@ path = APP . 'plugins' . DS; } + /** * Execution method always used for tasks * @@ -62,27 +60,27 @@ class PluginTask extends Shell { function execute() { if (empty($this->params['skel'])) { $this->params['skel'] = ''; - if (is_dir(CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'skel') === true) { - $this->params['skel'] = CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'skel'; + if (is_dir(CAKE_CORE_INCLUDE_PATH . DS . CONSOLE_LIBS . 'templates' . DS . 'skel') === true) { + $this->params['skel'] = CAKE_CORE_INCLUDE_PATH . DS . CONSOLE_LIBS . 'templates' . DS . 'skel'; } } - $plugin = null; if (isset($this->args[0])) { $plugin = Inflector::camelize($this->args[0]); - $pluginPath = Inflector::underscore($plugin) . DS; + $pluginPath = $this->_pluginPath($plugin); $this->Dispatch->shiftArgs(); - if (is_dir($this->path . $pluginPath)) { - $this->out(sprintf('Plugin: %s', $plugin)); - $this->out(sprintf('Path: %s', $this->path . $pluginPath)); - $this->hr(); + if (is_dir($pluginPath)) { + $this->out(sprintf(__('Plugin: %s', true), $plugin)); + $this->out(sprintf(__('Path: %s', true), $pluginPath)); } elseif (isset($this->args[0])) { - $this->err(sprintf('%s in path %s not found.', $plugin, $this->path . $pluginPath)); + $this->err(sprintf(__('%s in path %s not found.', true), $plugin, $pluginPath)); $this->_stop(); } else { $this->__interactive($plugin); } + } else { + return $this->__interactive(); } if (isset($this->args[0])) { @@ -90,13 +88,13 @@ class PluginTask extends Shell { $this->Dispatch->shiftArgs(); if (in_array($task, $this->tasks)) { $this->{$task}->plugin = $plugin; - $this->{$task}->path = $this->path . $pluginPath . Inflector::underscore(Inflector::pluralize($task)) . DS; + $this->{$task}->path = $pluginPath . Inflector::underscore(Inflector::pluralize($task)) . DS; if (!is_dir($this->{$task}->path)) { $this->err(sprintf(__("%s directory could not be found.\nBe sure you have created %s", true), $task, $this->{$task}->path)); } $this->{$task}->loadTasks(); - $this->{$task}->execute(); + return $this->{$task}->execute(); } } } @@ -125,28 +123,49 @@ class PluginTask extends Shell { * @return bool */ function bake($plugin) { - $pluginPath = Inflector::underscore($plugin); + $pathOptions = App::path('plugins'); + if (count($pathOptions) > 1) { + $this->findPath($pathOptions); + } + $this->hr(); - $this->out("Plugin Name: $plugin"); - $this->out("Plugin Directory: {$this->path}{$pluginPath}"); + $this->out(sprintf(__("Plugin Name: %s", true), $plugin)); + $this->out(sprintf(__("Plugin Directory: %s", true), $this->path . $pluginPath)); $this->hr(); + $looksGood = $this->in(__('Look okay?', true), array('y', 'n', 'q'), 'y'); - $looksGood = $this->in('Look okay?', array('y', 'n', 'q'), 'y'); - - if (low($looksGood) == 'y' || low($looksGood) == 'yes') { + if (strtolower($looksGood) == 'y') { $verbose = $this->in(__('Do you want verbose output?', true), array('y', 'n'), 'n'); - $Folder = new Folder($this->path . $pluginPath); - $directories = array('models' . DS . 'behaviors', 'controllers' . DS . 'components', 'views' . DS . 'helpers'); + $Folder =& new Folder($this->path . $pluginPath); + $directories = array( + 'config' . DS . 'sql', + 'models' . DS . 'behaviors', + 'controllers' . DS . 'components', + 'views' . DS . 'helpers', + 'tests' . DS . 'cases' . DS . 'components', + 'tests' . DS . 'cases' . DS . 'helpers', + 'tests' . DS . 'cases' . DS . 'behaviors', + 'tests' . DS . 'cases' . DS . 'controllers', + 'tests' . DS . 'cases' . DS . 'models', + 'tests' . DS . 'groups', + 'tests' . DS . 'fixtures', + 'vendors' . DS . 'img', + 'vendors' . DS . 'js', + 'vendors' . DS . 'css', + 'vendors' . DS . 'shells' + ); foreach ($directories as $directory) { - $Folder->create($this->path . $pluginPath . DS . $directory); + $dirPath = $this->path . $pluginPath . DS . $directory; + $Folder->create($dirPath); + $File =& new File($dirPath . DS . 'empty', true); } - if (low($verbose) == 'y' || low($verbose) == 'yes') { + if (strtolower($verbose) == 'y') { foreach ($Folder->messages() as $message) { $this->out($message); } @@ -180,6 +199,28 @@ class PluginTask extends Shell { return true; } + +/** + * find and change $this->path to the user selection + * + * @return void + **/ + function findPath($pathOptions) { + $valid = false; + $max = count($pathOptions); + while (!$valid) { + foreach ($pathOptions as $i => $option) { + $this->out($i + 1 .'. ' . $option); + } + $prompt = __('Choose a plugin path from the paths above.', true); + $choice = $this->in($prompt); + if (intval($choice) > 0 && intval($choice) <= $max) { + $valid = true; + } + } + $this->path = $pathOptions[$choice - 1]; + } + /** * Help * @@ -191,10 +232,18 @@ class PluginTask extends Shell { $this->out("Usage: cake bake plugin ..."); $this->hr(); $this->out('Commands:'); - $this->out("\n\tplugin \n\t\tbakes plugin directory structure"); - $this->out("\n\tplugin model\n\t\tbakes model. Run 'cake bake model help' for more info."); - $this->out("\n\tplugin controller\n\t\tbakes controller. Run 'cake bake controller help' for more info."); - $this->out("\n\tplugin view\n\t\tbakes view. Run 'cake bake view help' for more info."); + $this->out(''); + $this->out("plugin "); + $this->out("\tbakes plugin directory structure"); + $this->out(''); + $this->out("plugin model"); + $this->out("\tbakes model. Run 'cake bake model help' for more info."); + $this->out(''); + $this->out("plugin controller"); + $this->out("\tbakes controller. Run 'cake bake controller help' for more info."); + $this->out(''); + $this->out("plugin view"); + $this->out("\tbakes view. Run 'cake bake view help' for more info."); $this->out(""); $this->_stop(); } diff --git a/cake/console/libs/tasks/project.php b/cake/console/libs/tasks/project.php index 0502f8429..e14b5af78 100644 --- a/cake/console/libs/tasks/project.php +++ b/cake/console/libs/tasks/project.php @@ -1,32 +1,25 @@ in('A project already exists in this location: '.$project.' Overwrite?', array('y','n'), 'n'); + $response = $this->in('A project already exists in this location: ' . $project . ' Overwrite?', array('y','n'), 'n'); if (strtolower($response) === 'n') { $response = $project = false; } @@ -108,6 +109,7 @@ class ProjectTask extends Shell { return true; } } + /** * Looks for a skeleton template of a Cake application, * and if not found asks the user for a path. When there is a path @@ -144,7 +146,7 @@ class ProjectTask extends Shell { $looksGood = $this->in('Look okay?', array('y', 'n', 'q'), 'y'); - if (low($looksGood) == 'y' || low($looksGood) == 'yes') { + if (strtolower($looksGood) == 'y') { $verbose = $this->in(__('Do you want verbose output?', true), array('y', 'n'), 'n'); $Folder = new Folder($skel); @@ -153,24 +155,25 @@ class ProjectTask extends Shell { $this->out(sprintf(__("Created: %s in %s", true), $app, $path)); $this->hr(); } else { - $this->err(" '".$app."' could not be created properly"); + $this->err(" '" . $app . "' could not be created properly"); return false; } - if (low($verbose) == 'y' || low($verbose) == 'yes') { + if (strtolower($verbose) == 'y') { foreach ($Folder->messages() as $message) { $this->out($message); } } return true; - } elseif (low($looksGood) == 'q' || low($looksGood) == 'quit') { + } elseif (strtolower($looksGood) == 'q') { $this->out('Bake Aborted.'); } else { $this->execute(false); return false; } } + /** * Writes a file with a default home page to the project. * @@ -181,9 +184,10 @@ class ProjectTask extends Shell { function createHome($dir) { $app = basename($dir); $path = $dir . 'views' . DS . 'pages' . DS; - include(CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'views'.DS.'home.ctp'); + include(CAKE_CORE_INCLUDE_PATH.DS.'cake'.DS.'console'.DS.'libs'.DS.'templates'.DS.'default'.DS.'views'.DS.'home.ctp'); return $this->createFile($path.'home.ctp', $output); } + /** * Generates and writes 'Security.salt' * @@ -207,6 +211,7 @@ class ProjectTask extends Shell { } return false; } + /** * Generates and writes CAKE_CORE_INCLUDE_PATH * @@ -219,7 +224,7 @@ class ProjectTask extends Shell { $File =& new File($path . 'webroot' . DS . 'index.php'); $contents = $File->read(); if (preg_match('/([\\t\\x20]*define\\(\\\'CAKE_CORE_INCLUDE_PATH\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) { - $result = str_replace($match[0], "\t\tdefine('CAKE_CORE_INCLUDE_PATH', '".CAKE_CORE_INCLUDE_PATH."');", $contents); + $result = str_replace($match[0], "\t\tdefine('CAKE_CORE_INCLUDE_PATH', '" . CAKE_CORE_INCLUDE_PATH . "');", $contents); if (!$File->write($result)) { return false; } @@ -230,7 +235,7 @@ class ProjectTask extends Shell { $File =& new File($path . 'webroot' . DS . 'test.php'); $contents = $File->read(); if (preg_match('/([\\t\\x20]*define\\(\\\'CAKE_CORE_INCLUDE_PATH\\\',[\\t\\x20\'A-z0-9]*\\);)/', $contents, $match)) { - $result = str_replace($match[0], "\t\tdefine('CAKE_CORE_INCLUDE_PATH', '".CAKE_CORE_INCLUDE_PATH."');", $contents); + $result = str_replace($match[0], "\t\tdefine('CAKE_CORE_INCLUDE_PATH', '" . CAKE_CORE_INCLUDE_PATH . "');", $contents); if (!$File->write($result)) { return false; } @@ -240,6 +245,7 @@ class ProjectTask extends Shell { return true; } } + /** * Enables Configure::read('Routing.admin') in /app/config/core.php * @@ -248,7 +254,8 @@ class ProjectTask extends Shell { * @access public */ function cakeAdmin($name) { - $File =& new File(CONFIGS . 'core.php'); + $path = (empty($this->configPath)) ? CONFIGS : $this->configPath; + $File =& new File($path . 'core.php'); $contents = $File->read(); if (preg_match('%([/\\t\\x20]*Configure::write\(\'Routing.admin\',[\\t\\x20\'a-z]*\\);)%', $contents, $match)) { $result = str_replace($match[0], "\t" . 'Configure::write(\'Routing.admin\', \''.$name.'\');', $contents); @@ -262,6 +269,34 @@ class ProjectTask extends Shell { return false; } } + +/** + * Checks for Configure::read('Routing.admin') and forces user to input it if not enabled + * + * @return string Admin route to use + * @access public + */ + function getAdmin() { + $admin = ''; + $cakeAdmin = null; + $adminRoute = Configure::read('Routing.admin'); + if (!empty($adminRoute)) { + return $adminRoute . '_'; + } + $this->out('You need to enable Configure::write(\'Routing.admin\',\'admin\') in /app/config/core.php to use admin routing.'); + $this->out('What would you like the admin route to be?'); + $this->out('Example: www.example.com/admin/controller'); + while ($admin == '') { + $admin = $this->in("What would you like the admin route to be?", null, 'admin'); + } + if ($this->cakeAdmin($admin) !== true) { + $this->out('Unable to write to /app/config/core.php.'); + $this->out('You need to enable Configure::write(\'Routing.admin\',\'admin\') in /app/config/core.php to use admin routing.'); + $this->_stop(); + } + return $admin . '_'; + } + /** * Help * @@ -273,7 +308,10 @@ class ProjectTask extends Shell { $this->out("Usage: cake bake project "); $this->hr(); $this->out('Commands:'); - $this->out("\n\tproject \n\t\tbakes app directory structure.\n\t\tif begins with '/' path is absolute."); + $this->out(''); + $this->out("project "); + $this->out("\tbakes app directory structure."); + $this->out("\tif begins with '/' path is absolute."); $this->out(""); $this->_stop(); } diff --git a/cake/console/libs/tasks/template.php b/cake/console/libs/tasks/template.php new file mode 100644 index 000000000..2fa426ddb --- /dev/null +++ b/cake/console/libs/tasks/template.php @@ -0,0 +1,191 @@ + $path + * + * @var array + **/ + var $templatePaths = array(); + +/** + * Initialize callback. Setup paths for the template task. + * + * @access public + * @return void + **/ + function initialize() { + $this->templatePaths = $this->_findThemes(); + } + +/** + * Find the paths to all the installed shell themes in the app. + * + * Bake themes are directories not named `skel` inside a `vendors/shells/templates` path. + * + * @return array Array of bake themes that are installed. + **/ + function _findThemes() { + $paths = $this->Dispatch->shellPaths; + $themes = array(); + foreach ($paths as $path) { + $Folder =& new Folder($path . 'templates', false); + $contents = $Folder->read(); + $subDirs = $contents[0]; + foreach ($subDirs as $dir) { + if (empty($dir) || $dir == 'skel') { + continue; + } + $templateDir = $path . 'templates' . DS . $dir . DS; + $themes[$dir] = $templateDir; + } + } + return $themes; + } + +/** + * Set variable values to the template scope + * + * @param mixed $one A string or an array of data. + * @param mixed $two Value in case $one is a string (which then works as the key). + * Unused if $one is an associative array, otherwise serves as the values to $one's keys. + * @return void + */ + function set($one, $two = null) { + $data = null; + if (is_array($one)) { + if (is_array($two)) { + $data = array_combine($one, $two); + } else { + $data = $one; + } + } else { + $data = array($one => $two); + } + + if ($data == null) { + return false; + } + + foreach ($data as $name => $value) { + $this->templateVars[$name] = $value; + } + } + +/** + * Runs the template + * + * @param string $directory directory / type of thing you want + * @param string $filename template name + * @param string $vars Additional vars to set to template scope. + * @access public + * @return contents of generated code template + **/ + function generate($directory, $filename, $vars = null) { + if ($vars !== null) { + $this->set($vars); + } + if (empty($this->templatePaths)) { + $this->initialize(); + } + $themePath = $this->getThemePath(); + $templateFile = $this->_findTemplate($themePath, $directory, $filename); + if ($templateFile) { + extract($this->templateVars); + ob_start(); + ob_implicit_flush(0); + include($templateFile); + $content = ob_get_clean(); + return $content; + } + return ''; + } + +/** + * Find the theme name for the current operation. + * If there is only one theme in $templatePaths it will be used. + * If there is a -theme param in the cli args, it will be used. + * If there is more than one installed theme user interaction will happen + * + * @return string returns the path to the selected theme. + **/ + function getThemePath() { + if (count($this->templatePaths) == 1) { + $paths = array_values($this->templatePaths); + return $paths[0]; + } + if (!empty($this->params['theme']) && isset($this->templatePaths[$this->params['theme']])) { + return $this->templatePaths[$this->params['theme']]; + } + + $this->hr(); + $this->out(__('You have more than one set of templates installed.', true)); + $this->out(__('Please choose the template set you wish to use:', true)); + $this->hr(); + + $i = 1; + $indexedPaths = array(); + foreach ($this->templatePaths as $key => $path) { + $this->out($i . '. ' . $key); + $indexedPaths[$i] = $path; + $i++; + } + $index = $this->in(__('Which bake theme would you like to use?', true), range(1, $i - 1), 1); + $themeNames = array_keys($this->templatePaths); + $this->Dispatch->params['theme'] = $themeNames[$index - 1]; + return $indexedPaths[$index]; + } + +/** + * Find a template inside a directory inside a path. + * Will scan all other theme dirs if the template is not found in the first directory. + * + * @param string $path The initial path to look for the file on. If it is not found fallbacks will be used. + * @param string $directory Subdirectory to look for ie. 'views', 'objects' + * @param string $filename lower_case_underscored filename you want. + * @access public + * @return string filename will exit program if template is not found. + **/ + function _findTemplate($path, $directory, $filename) { + $themeFile = $path . $directory . DS . $filename . '.ctp'; + if (file_exists($themeFile)) { + return $themeFile; + } + foreach ($this->templatePaths as $path) { + $templatePath = $path . $directory . DS . $filename . '.ctp'; + if (file_exists($templatePath)) { + return $templatePath; + } + } + $this->err(sprintf(__('Could not find template for %s', true), $filename)); + $this->_stop(); + return false; + } +} +?> \ No newline at end of file diff --git a/cake/console/libs/tasks/test.php b/cake/console/libs/tasks/test.php index 45b853448..9978c66e4 100644 --- a/cake/console/libs/tasks/test.php +++ b/cake/console/libs/tasks/test.php @@ -1,29 +1,24 @@ args) > 1) { - $class = Inflector::underscore($this->args[0]); - if ($this->bake($class, $this->args[1])) { + $type = Inflector::underscore($this->args[0]); + if ($this->bake($type, $this->args[1])) { $this->out('done'); } } } + /** * Handles interactive baking * * @access private */ - function __interactive($class = null) { + function __interactive($type = null) { + $this->interactive = true; $this->hr(); - $this->out(sprintf("Bake Tests\nPath: %s", $this->path)); + $this->out(__('Bake Tests', true)); + $this->out(sprintf(__("Path: %s", true), $this->path)); $this->hr(); - $key = null; - $options = array('Behavior', 'Helper', 'Component', 'Model', 'Controller'); - - if ($class !== null) { - $class = Inflector::camelize($class); - if (in_array($class, $options)) { - $key = array_search($class); + $selection = null; + if ($type) { + $type = Inflector::camelize($type); + if (!in_array($type, $this->classTypes)) { + unset($type); } } - - while ($class == null) { - $cases = array(); - $this->hr(); - $this->out("Select a class:"); - $this->hr(); - - $keys = array(); - foreach ($options as $key => $option) { - $this->out(++$key . '. ' . $option); - $keys[] = $key; - } - $keys[] = 'q'; - - $key = $this->in(__("Enter the class to test or (q)uit", true), $keys, 'q'); - - if ($key != 'q') { - if (isset($options[--$key])) { - $class = $options[$key]; - } - - if ($class) { - $name = $this->in(__("Enter the name for the test or (q)uit", true), null, 'q'); - if ($name !== 'q') { - $case = null; - while ($case !== 'q') { - $case = $this->in(__("Enter a test case or (q)uit", true), null, 'q'); - if ($case !== 'q') { - $cases[] = $case; - } - } - if ($this->bake($class, $name, $cases)) { - $this->out(__("Test baked\n", true)); - $type = null; - } - $class = null; - } - } - } else { - $this->_stop(); - } + if (!$type) { + $type = $this->getObjectType(); } + $className = $this->getClassName($type); + return $this->bake($type, $className); } + /** - * Writes File + * Completes final steps for generating data to create test case. * + * @param string $type Type of object to bake test case for ie. Model, Controller + * @param string $className the 'cake name' for the class ie. Posts for the PostsController * @access public */ - function bake($class, $name = null, $cases = array()) { - if (!$name) { - return false; + function bake($type, $className) { + if ($this->typeCanDetectFixtures($type) && $this->isLoadableClass($type, $className)) { + $this->out(__('Bake is detecting possible fixtures..', true)); + $testSubject =& $this->buildTestSubject($type, $className); + $this->generateFixtureList($testSubject); + } elseif ($this->interactive) { + $this->getUserFixtures(); + } + $fullClassName = $this->getRealClassName($type, $className); + + $methods = array(); + if (class_exists($fullClassName)) { + $methods = $this->getTestableMethods($fullClassName); + } + $mock = $this->hasMockClass($type, $fullClassName); + $construction = $this->generateConstructor($type, $fullClassName); + + $plugin = null; + if ($this->plugin) { + $plugin = $this->plugin . '.'; } - if (!is_array($cases)) { - $cases = array($cases); - } + $this->Template->set('fixtures', $this->_fixtures); + $this->Template->set('plugin', $plugin); + $this->Template->set(compact('className', 'methods', 'type', 'fullClassName', 'mock', 'construction')); + $out = $this->Template->generate('classes', 'test'); - if (strpos($this->path, $class) === false) { - $this->filePath = $this->path . 'cases' . DS . Inflector::tableize($class) . DS; + $filename = $this->testCaseFileName($type, $className); + $made = $this->createFile($filename, $out); + if ($made) { + return $out; } - - $class = Inflector::classify($class); - $name = Inflector::classify($name); - - $import = $name; - if (isset($this->plugin)) { - $import = $this->plugin . '.' . $name; - } - $extras = $this->__extras($class); - $out = "App::import('$class', '$import');\n"; - if ($class == 'Model') { - $class = null; - } - $out .= "class Test{$name} extends {$name}{$class} {\n"; - $out .= "{$extras}"; - $out .= "}\n\n"; - $out .= "class {$name}{$class}Test extends CakeTestCase {\n"; - $out .= "\n\tfunction startTest() {"; - $out .= "\n\t\t\$this->{$name} = new Test{$name}();"; - $out .= "\n\t}\n"; - $out .= "\n\tfunction test{$name}Instance() {\n"; - $out .= "\t\t\$this->assertTrue(is_a(\$this->{$name}, '{$name}{$class}'));\n\t}\n"; - foreach ($cases as $case) { - $case = Inflector::classify($case); - $out .= "\n\tfunction test{$case}() {\n\n\t}\n"; - } - $out .= "}\n"; - - $this->out("Baking unit test for $name..."); - $this->out($out); - $ok = $this->in(__('Is this correct?', true), array('y', 'n'), 'y'); - if ($ok == 'n') { - return false; - } - - $header = '$Id'; - $content = ""; - return $this->createFile($this->filePath . Inflector::underscore($name) . '.test.php', $content); + return false; } + /** - * Handles the extra stuff needed + * Interact with the user and get their chosen type. Can exit the script. * - * @access private - */ - function __extras($class) { - $extras = null; - switch ($class) { - case 'Model': - $extras = "\n\tvar \$cacheSources = false;"; - $extras .= "\n\tvar \$useDbConfig = 'test_suite';\n"; - break; + * @return string Users chosen type. + **/ + function getObjectType() { + $this->hr(); + $this->out(__("Select an object type:", true)); + $this->hr(); + + $keys = array(); + foreach ($this->classTypes as $key => $option) { + $this->out(++$key . '. ' . $option); + $keys[] = $key; } - return $extras; + $keys[] = 'q'; + $selection = $this->in(__("Enter the type of object to bake a test for or (q)uit", true), $keys, 'q'); + if ($selection == 'q') { + return $this->_stop(); + } + return $this->classTypes[$selection - 1]; + } + +/** + * Get the user chosen Class name for the chosen type + * + * @param string $objectType Type of object to list classes for i.e. Model, Controller. + * @return string Class name the user chose. + **/ + function getClassName($objectType) { + $options = Configure::listObjects(strtolower($objectType)); + $this->out(sprintf(__('Choose a %s class', true), $objectType)); + $keys = array(); + foreach ($options as $key => $option) { + $this->out(++$key . '. ' . $option); + $keys[] = $key; + } + $selection = $this->in(__('Choose an existing class, or enter the name of a class that does not exist', true)); + if (isset($options[$selection - 1])) { + return $options[$selection - 1]; + } + return $selection; + } + +/** + * Checks whether the chosen type can find its own fixtures. + * Currently only model, and controller are supported + * + * @return boolean + **/ + function typeCanDetectFixtures($type) { + $type = strtolower($type); + return ($type == 'controller' || $type == 'model'); + } + +/** + * Check if a class with the given type is loaded or can be loaded. + * + * @return boolean + **/ + function isLoadableClass($type, $class) { + return App::import($type, $class); + } + +/** + * Construct an instance of the class to be tested. + * So that fixtures can be detected + * + * @return object + **/ + function &buildTestSubject($type, $class) { + ClassRegistry::flush(); + App::import($type, $class); + $class = $this->getRealClassName($type, $class); + if (strtolower($type) == 'model') { + $instance =& ClassRegistry::init($class); + } else { + $instance =& new $class(); + } + return $instance; + } + +/** + * Gets the real class name from the cake short form. + * + * @return string Real classname + **/ + function getRealClassName($type, $class) { + if (strtolower($type) == 'model') { + return $class; + } + return $class . $type; + } + +/** + * Get methods declared in the class given. + * No parent methods will be returned + * + * @param string $className Name of class to look at. + * @return array Array of method names. + **/ + function getTestableMethods($className) { + $classMethods = get_class_methods($className); + $parentMethods = get_class_methods(get_parent_class($className)); + $thisMethods = array_diff($classMethods, $parentMethods); + $out = array(); + foreach ($thisMethods as $method) { + if (substr($method, 0, 1) != '_') { + $out[] = $method; + } + } + return $out; + } + +/** + * Generate the list of fixtures that will be required to run this test based on + * loaded models. + * + * @param object The object you want to generate fixtures for. + * @return array Array of fixtures to be included in the test. + **/ + function generateFixtureList(&$subject) { + $this->_fixtures = array(); + if (is_a($subject, 'Model')) { + $this->_processModel($subject); + } elseif (is_a($subject, 'Controller')) { + $this->_processController($subject); + } + return array_values($this->_fixtures); + } + +/** + * Process a model recursively and pull out all the + * model names converting them to fixture names. + * + * @return void + * @access protected + **/ + function _processModel(&$subject) { + $this->_addFixture($subject->name); + $associated = $subject->getAssociated(); + foreach ($associated as $alias => $type) { + $className = $subject->{$alias}->name; + if (!isset($this->_fixtures[$className])) { + $this->_processModel($subject->{$alias}); + } + if ($type == 'hasAndBelongsToMany') { + $joinModel = Inflector::classify($subject->hasAndBelongsToMany[$alias]['joinTable']); + if (!isset($this->_fixtures[$joinModel])) { + $this->_processModel($subject->{$joinModel}); + } + } + } + } + +/** + * Process all the models attached to a controller + * and generate a fixture list. + * + * @return void + * @access protected + **/ + function _processController(&$subject) { + $subject->constructClasses(); + $models = array(Inflector::classify($subject->name)); + if (!empty($subject->uses)) { + $models = $subject->uses; + } + foreach ($models as $model) { + $this->_processModel($subject->{$model}); + } + } + +/** + * Add classname to the fixture list. + * Sets the app. or plugin.plugin_name. prefix. + * + * @return void + * @access protected + **/ + function _addFixture($name) { + $parent = get_parent_class($name); + $prefix = 'app.'; + if (strtolower($parent) != 'appmodel' && strtolower(substr($parent, -8)) == 'appmodel') { + $pluginName = substr($parent, 0, strlen($parent) -8); + $prefix = 'plugin.' . Inflector::underscore($pluginName) . '.'; + } + $fixture = $prefix . Inflector::underscore($name); + $this->_fixtures[$name] = $fixture; + } + +/** + * Interact with the user to get additional fixtures they want to use. + * + * @return void + **/ + function getUserFixtures() { + $proceed = $this->in(__('Bake could not detect fixtures, would you like to add some?', true), array('y','n'), 'n'); + $fixtures = array(); + if (strtolower($proceed) == 'y') { + $fixtureList = $this->in(__("Please provide a comma separated list of the fixtures names you'd like to use.\nExample: 'app.comment, app.post, plugin.forums.post'", true)); + $fixtureListTrimmed = str_replace(' ', '', $fixtureList); + $fixtures = explode(',', $fixtureListTrimmed); + } + $this->_fixtures = array_merge($this->_fixtures, $fixtures); + return $fixtures; + } + +/** + * Is a mock class required for this type of test? + * Controllers require a mock class. + * + * @return boolean + **/ + function hasMockClass($type) { + $type = strtolower($type); + return $type == 'controller'; + } + +/** + * Generate a constructor code snippet for the type and classname + * + * @return string Constructor snippet for the thing you are building. + **/ + function generateConstructor($type, $fullClassName) { + $type = strtolower($type); + if ($type == 'model') { + return "ClassRegistry::init('$fullClassName');\n"; + } + if ($type == 'controller') { + return "new Test$fullClassName();\n\t\t\$this->{$fullClassName}->constructClasses();\n"; + } + return "new $fullClassName()\n"; + } + +/** + * make the filename for the test case. resolve the suffixes for controllers + * and get the plugin path if needed. + * + * @return string filename the test should be created on + **/ + function testCaseFileName($type, $className) { + $path = $this->path; + if (isset($this->plugin)) { + $path = $this->_pluginPath($this->plugin) . 'tests' . DS; + } + $path .= 'cases' . DS . Inflector::tableize($type) . DS; + if (strtolower($type) == 'controller') { + $className = $this->getRealClassName($type, $className); + } + return $path . Inflector::underscore($className) . '.test.php'; } } ?> \ No newline at end of file diff --git a/cake/console/libs/tasks/view.php b/cake/console/libs/tasks/view.php index 4dfdc5089..18e86b995 100644 --- a/cake/console/libs/tasks/view.php +++ b/cake/console/libs/tasks/view.php @@ -1,5 +1,4 @@ args[0])) { + if (!isset($this->connection)) { + $this->connection = 'default'; + } $controller = $action = $alias = null; $this->controllerName = Inflector::camelize($this->args[0]); $this->controllerPath = Inflector::underscore($this->controllerName); @@ -115,37 +124,92 @@ class ViewTask extends Shell { $action = $this->template; } + if (strtolower($this->args[0]) == 'all') { + return $this->all(); + } + if (in_array($action, $this->scaffoldActions)) { $this->bake($action, true); } elseif ($action) { $this->bake($action, true); } else { $vars = $this->__loadController(); - if ($vars) { - - $methods = array_diff( - array_map('strtolower', get_class_methods($this->controllerName . 'Controller')), - array_map('strtolower', get_class_methods('appcontroller')) - ); - if (empty($methods)) { - $methods = $this->scaffoldActions; - } - $adminDelete = null; - - $adminRoute = Configure::read('Routing.admin'); - if (!empty($adminRoute)) { - $adminDelete = $adminRoute.'_delete'; - } - foreach ($methods as $method) { - if ($method{0} != '_' && !in_array($method, array('delete', $adminDelete))) { - $content = $this->getContent($method, $vars); - $this->bake($method, $content); + $methods = $this->_methodsToBake(); + $methods = array_diff( + array_map('strtolower', get_class_methods($this->controllerName . 'Controller')), + array_map('strtolower', get_class_methods('appcontroller')) + ); + if (empty($methods)) { + $methods = $this->scaffoldActions; + } + $adminRoute = Configure::read('Routing.admin'); + if ($adminRoute && isset($this->params['admin'])) { + foreach ($methods as $i => $method) { + if (strpos($method, $adminRoute . '_') === false) { + unset($methods[$i]); } } } + $adminDelete = null; + if (!empty($adminRoute)) { + $adminDelete = $adminRoute . '_delete'; + } + foreach ($methods as $method) { + if ($method{0} != '_' && !in_array($method, array('delete', $adminDelete))) { + $content = $this->getContent($method, $vars); + $this->bake($method, $content); + } + } } } } + +/** + * Get a list of actions that can / should have views baked for them. + * + * @return array Array of action names that should be baked + **/ + function _methodsToBake() { + $methods = array_diff( + array_map('strtolower', get_class_methods($this->controllerName . 'Controller')), + array_map('strtolower', get_class_methods('appcontroller')) + ); + if (empty($methods)) { + $methods = $this->scaffoldActions; + } + $adminRoute = Configure::read('Routing.admin'); + foreach ($methods as $i => $method) { + if ($method == 'delete' || $method = $adminRoute . '_delete' || $method{0} == '_') { + unset($methods[$i]); + } + if ($adminRoute && isset($this->params['admin']) && strpos($method, $adminRoute . '_') === false) { + unset($methods[$i]); + } + } + return $methods; + } + +/** + * Bake All views for All controllers. + * + * @return void + **/ + function all() { + $actions = $this->scaffoldActions; + $this->Controller->interactive = false; + $tables = $this->Controller->listAll($this->connection, false); + $this->interactive = false; + foreach ($tables as $table) { + $model = $this->_modelName($table); + $this->controllerName = $this->_controllerName($model); + $this->controllerPath = Inflector::underscore($this->controllerName); + if (App::import('Model', $model)) { + $vars = $this->__loadController(); + $this->bakeActions($actions, $vars); + } + } + } + /** * Handles interactive baking * @@ -155,72 +219,51 @@ class ViewTask extends Shell { $this->hr(); $this->out(sprintf("Bake View\nPath: %s", $this->path)); $this->hr(); - $wannaDoAdmin = 'n'; - $wannaDoScaffold = 'y'; - $this->interactive = false; + if (empty($this->connection)) { + $this->connection = $this->DbConfig->getConfig(); + } + + $this->Controller->connection = $this->connection; $this->controllerName = $this->Controller->getName(); - $this->controllerPath = low(Inflector::underscore($this->controllerName)); + $this->controllerPath = strtolower(Inflector::underscore($this->controllerName)); - $interactive = $this->in("Would you like bake to build your views interactively?\nWarning: Choosing no will overwrite {$this->controllerName} views if it exist.", array('y','n'), 'y'); + $prompt = sprintf(__("Would you like bake to build your views interactively?\nWarning: Choosing no will overwrite %s views if it exist.", true), $this->controllerName); + $interactive = $this->in($prompt, array('y', 'n'), 'n'); - if (low($interactive) == 'y' || low($interactive) == 'yes') { - $this->interactive = true; - $wannaDoScaffold = $this->in("Would you like to create some scaffolded views (index, add, view, edit) for this controller?\nNOTE: Before doing so, you'll need to create your controller and model classes (including associated models).", array('y','n'), 'n'); + if (strtolower($interactive) == 'n') { + $this->interactive = false; } - if (low($wannaDoScaffold) == 'y' || low($wannaDoScaffold) == 'yes') { - $wannaDoAdmin = $this->in("Would you like to create the views for admin routing?", array('y','n'), 'y'); - } - $admin = false; + $prompt = __("Would you like to create some CRUD views\n(index, add, view, edit) for this controller?\nNOTE: Before doing so, you'll need to create your controller\nand model classes (including associated models).", true); + $wannaDoScaffold = $this->in($prompt, array('y','n'), 'y'); - if ((low($wannaDoAdmin) == 'y' || low($wannaDoAdmin) == 'yes')) { - $admin = $this->getAdmin(); - } + $wannaDoAdmin = $this->in(__("Would you like to create the views for admin routing?", true), array('y','n'), 'n'); - if (low($wannaDoScaffold) == 'y' || low($wannaDoScaffold) == 'yes') { - $actions = $this->scaffoldActions; - if ($admin) { - foreach ($actions as $action) { - $actions[] = $admin . $action; - } - } + if (strtolower($wannaDoScaffold) == 'y' || strtolower($wannaDoAdmin) == 'y') { $vars = $this->__loadController(); - if ($vars) { - foreach ($actions as $action) { - $content = $this->getContent($action, $vars); - $this->bake($action, $content); + if (strtolower($wannaDoScaffold) == 'y') { + $actions = $this->scaffoldActions; + $this->bakeActions($actions, $vars); + } + if (strtolower($wannaDoAdmin) == 'y') { + $admin = $this->Project->getAdmin(); + $regularActions = $this->scaffoldActions; + $adminActions = array(); + foreach ($regularActions as $action) { + $adminActions[] = $admin . $action; } + $this->bakeActions($adminActions, $vars); } $this->hr(); $this->out(''); - $this->out('View Scaffolding Complete.'."\n"); + $this->out(__("View Scaffolding Complete.\n", true)); } else { - $action = ''; - while ($action == '') { - $action = $this->in('Action Name? (use camelCased function name)'); - if ($action == '') { - $this->out('The action name you supplied was empty. Please try again.'); - } - } - $this->out(''); - $this->hr(); - $this->out('The following view will be created:'); - $this->hr(); - $this->out("Controller Name: {$this->controllerName}"); - $this->out("Action Name: {$action}"); - $this->out("Path: ".$this->params['app'] . DS . $this->controllerPath . DS . Inflector::underscore($action) . ".ctp"); - $this->hr(); - $looksGood = $this->in('Look okay?', array('y','n'), 'y'); - if (low($looksGood) == 'y' || low($looksGood) == 'yes') { - $this->bake($action); - $this->_stop(); - } else { - $this->out('Bake Aborted.'); - } + $this->customAction(); } } + /** * Loads Controller and sets variables for the template * Available template variables @@ -247,7 +290,7 @@ class ViewTask extends Shell { $this->_stop(); } $controllerClassName = $this->controllerName . 'Controller'; - $controllerObj = & new $controllerClassName(); + $controllerObj =& new $controllerClassName(); $controllerObj->constructClasses(); $modelClass = $controllerObj->modelClass; $modelObj =& ClassRegistry::getObject($controllerObj->modelKey); @@ -277,6 +320,50 @@ class ViewTask extends Shell { return compact('modelClass', 'schema', 'primaryKey', 'displayField', 'singularVar', 'pluralVar', 'singularHumanName', 'pluralHumanName', 'fields','associations'); } + +/** + * Bake a view file for each of the supplied actions + * + * @param array $actions Array of actions to make files for. + * @return void + **/ + function bakeActions($actions, $vars) { + foreach ($actions as $action) { + $content = $this->getContent($action, $vars); + $this->bake($action, $content); + } + } + +/** + * handle creation of baking a custom action view file + * + * @return void + **/ + function customAction() { + $action = ''; + while ($action == '') { + $action = $this->in(__('Action Name? (use lowercase_underscored function name)', true)); + if ($action == '') { + $this->out(__('The action name you supplied was empty. Please try again.', true)); + } + } + $this->out(''); + $this->hr(); + $this->out(__('The following view will be created:', true)); + $this->hr(); + $this->out(sprintf(__('Controller Name: %s', true), $this->controllerName)); + $this->out(sprintf(__('Action Name: %s', true), $action)); + $this->out(sprintf(__('Path: %s', true), $this->params['app'] . DS . $this->controllerPath . DS . Inflector::underscore($action) . ".ctp")); + $this->hr(); + $looksGood = $this->in(__('Look okay?', true), array('y','n'), 'y'); + if (strtolower($looksGood) == 'y') { + $this->bake($action); + $this->_stop(); + } else { + $this->out(__('Bake Aborted.', true)); + } + } + /** * Assembles and writes bakes the view file. * @@ -287,21 +374,16 @@ class ViewTask extends Shell { */ function bake($action, $content = '') { if ($content === true) { - $content = $this->getContent(); + $content = $this->getContent($action); } - $filename = $this->path . $this->controllerPath . DS . Inflector::underscore($action) . '.ctp'; - $Folder =& new Folder($this->path . $this->controllerPath, true); - $errors = $Folder->errors(); - if (empty($errors)) { - $path = $Folder->slashTerm($Folder->pwd()); - return $this->createFile($filename, $content); - } else { - foreach ($errors as $error) { - $this->err($error); - } + $path = $this->path; + if (isset($this->plugin)) { + $path = $this->_pluginPath($this->plugin) . 'views' . DS; } - return false; + $filename = $path . $this->controllerPath . DS . Inflector::underscore($action) . '.ctp'; + return $this->createFile($filename, $content); } + /** * Builds content from template and variables * @@ -318,35 +400,29 @@ class ViewTask extends Shell { $adminRoute = Configure::read('Routing.admin'); if (!empty($adminRoute) && strpos($template, $adminRoute) !== false) { - $template = str_replace($adminRoute.'_', '', $template); + $template = str_replace($adminRoute . '_', '', $template); } if (in_array($template, array('add', 'edit'))) { $action = $template; $template = 'form'; } - $loaded = false; - foreach ($this->Dispatch->shellPaths as $path) { - $templatePath = $path . 'templates' . DS . 'views' . DS .Inflector::underscore($template).'.ctp'; - if (file_exists($templatePath) && is_file($templatePath)) { - $loaded = true; - break; - } - } if (!$vars) { $vars = $this->__loadController(); } - if ($loaded) { - extract($vars); - ob_start(); - ob_implicit_flush(0); - include($templatePath); - $content = ob_get_clean(); - return $content; + + $this->Template->set('action', $action); + $this->Template->set('plugin', $this->plugin); + $this->Template->set($vars); + $output = $this->Template->generate('views', $template); + + if (!empty($output)) { + return $output; } $this->hr(); $this->err(sprintf(__('Template for %s could not be found', true), $template)); return false; } + /** * Displays help contents * @@ -357,12 +433,28 @@ class ViewTask extends Shell { $this->out("Usage: cake bake view ..."); $this->hr(); $this->out('Commands:'); - $this->out("\n\tview \n\t\twill read the given controller for methods\n\t\tand bake corresponding views.\n\t\tIf var scaffold is found it will bake the scaffolded actions\n\t\t(index,view,add,edit)"); - $this->out("\n\tview \n\t\twill bake a template. core templates: (index, add, edit, view)"); - $this->out("\n\tview