From b074ecebe0a5b2933af708041afaacb0a17e6ad2 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 6 Aug 2012 21:58:39 -0400 Subject: [PATCH] renamed console controller folder name. --- framework/console/commands/AppController.php | 129 ----- framework/console/commands/HelpController.php | 356 ------------- framework/console/commands/MessageController.php | 223 -------- framework/console/commands/MigrateController.php | 561 --------------------- framework/console/commands/ShellController.php | 148 ------ framework/console/controllers/AppController.php | 129 +++++ framework/console/controllers/HelpController.php | 356 +++++++++++++ .../console/controllers/MessageController.php | 223 ++++++++ .../console/controllers/MigrateController.php | 561 +++++++++++++++++++++ framework/console/controllers/ShellController.php | 148 ++++++ framework/yiic.php | 2 +- 11 files changed, 1418 insertions(+), 1418 deletions(-) delete mode 100644 framework/console/commands/AppController.php delete mode 100644 framework/console/commands/HelpController.php delete mode 100644 framework/console/commands/MessageController.php delete mode 100644 framework/console/commands/MigrateController.php delete mode 100644 framework/console/commands/ShellController.php create mode 100644 framework/console/controllers/AppController.php create mode 100644 framework/console/controllers/HelpController.php create mode 100644 framework/console/controllers/MessageController.php create mode 100644 framework/console/controllers/MigrateController.php create mode 100644 framework/console/controllers/ShellController.php diff --git a/framework/console/commands/AppController.php b/framework/console/commands/AppController.php deleted file mode 100644 index 0f3f846..0000000 --- a/framework/console/commands/AppController.php +++ /dev/null @@ -1,129 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - * @version $Id$ - */ - -/** - * WebAppCommand creates an Yii Web application at the specified location. - * - * @author Qiang Xue - * @version $Id$ - * @package system.cli.commands - * @since 1.0 - */ -class WebAppCommand extends CConsoleCommand -{ - private $_rootPath; - - public function getHelp() - { - return << - -DESCRIPTION - This command generates an Yii Web Application at the specified location. - -PARAMETERS - * app-path: required, the directory where the new application will be created. - If the directory does not exist, it will be created. After the application - is created, please make sure the directory can be accessed by Web users. - -EOD; - } - - /** - * Execute the action. - * @param array command line parameters specific for this command - */ - public function run($args) - { - if(!isset($args[0])) - $this->usageError('the Web application location is not specified.'); - $path=strtr($args[0],'/\\',DIRECTORY_SEPARATOR); - if(strpos($path,DIRECTORY_SEPARATOR)===false) - $path='.'.DIRECTORY_SEPARATOR.$path; - $dir=rtrim(realpath(dirname($path)),'\\/'); - if($dir===false || !is_dir($dir)) - $this->usageError("The directory '$path' is not valid. Please make sure the parent directory exists."); - if(basename($path)==='.') - $this->_rootPath=$path=$dir; - else - $this->_rootPath=$path=$dir.DIRECTORY_SEPARATOR.basename($path); - if($this->confirm("Create a Web application under '$path'?")) - { - $sourceDir=realpath(dirname(__FILE__).'/../views/webapp'); - if($sourceDir===false) - die("\nUnable to locate the source directory.\n"); - $list=$this->buildFileList($sourceDir,$path); - $list['index.php']['callback']=array($this,'generateIndex'); - $list['index-test.php']['callback']=array($this,'generateIndex'); - $list['protected/tests/bootstrap.php']['callback']=array($this,'generateTestBoostrap'); - $list['protected/yiic.php']['callback']=array($this,'generateYiic'); - $this->copyFiles($list); - @chmod($path.'/assets',0777); - @chmod($path.'/protected/runtime',0777); - @chmod($path.'/protected/data',0777); - @chmod($path.'/protected/data/testdrive.db',0777); - @chmod($path.'/protected/yiic',0755); - echo "\nYour application has been created successfully under {$path}.\n"; - } - } - - public function generateIndex($source,$params) - { - $content=file_get_contents($source); - $yii=realpath(dirname(__FILE__).'/../../yii.php'); - $yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'index.php'); - $yii=str_replace('\\','\\\\',$yii); - return preg_replace('/\$yii\s*=(.*?);/',"\$yii=$yii;",$content); - } - - public function generateTestBoostrap($source,$params) - { - $content=file_get_contents($source); - $yii=realpath(dirname(__FILE__).'/../../yiit.php'); - $yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'bootstrap.php'); - $yii=str_replace('\\','\\\\',$yii); - return preg_replace('/\$yiit\s*=(.*?);/',"\$yiit=$yii;",$content); - } - - public function generateYiic($source,$params) - { - $content=file_get_contents($source); - $yiic=realpath(dirname(__FILE__).'/../../yiic.php'); - $yiic=$this->getRelativePath($yiic,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'yiic.php'); - $yiic=str_replace('\\','\\\\',$yiic); - return preg_replace('/\$yiic\s*=(.*?);/',"\$yiic=$yiic;",$content); - } - - protected function getRelativePath($path1,$path2) - { - $segs1=explode(DIRECTORY_SEPARATOR,$path1); - $segs2=explode(DIRECTORY_SEPARATOR,$path2); - $n1=count($segs1); - $n2=count($segs2); - - for($i=0;$i<$n1 && $i<$n2;++$i) - { - if($segs1[$i]!==$segs2[$i]) - break; - } - - if($i===0) - return "'".$path1."'"; - $up=''; - for($j=$i;$j<$n2-1;++$j) - $up.='/..'; - for(;$i<$n1-1;++$i) - $up.='/'.$segs1[$i]; - - return 'dirname(__FILE__).\''.$up.'/'.basename($path1).'\''; - } -} \ No newline at end of file diff --git a/framework/console/commands/HelpController.php b/framework/console/commands/HelpController.php deleted file mode 100644 index d986363..0000000 --- a/framework/console/commands/HelpController.php +++ /dev/null @@ -1,356 +0,0 @@ - - * @since 2.0 - */ -class HelpController extends Controller -{ - /** - * Displays available commands or the detailed information - * about a particular command. For example, - * - * ~~~ - * yiic help # list available commands - * yiic help message # display help info about "message" - * ~~~ - * - * @param array $args additional anonymous command line arguments. - * You may provide a command name to display its detailed information. - * @return integer the exit status - */ - public function actionIndex($args = array()) - { - if (empty($args)) { - $status = $this->getHelp(); - } else { - $result = \Yii::$application->createController($args[0]); - if ($result === false) { - echo "Unknown command: " . $args[0] . "\n"; - return 1; - } - - list($controller, $action) = $result; - - if ($action === '') { - $status = $this->getControllerHelp($controller); - } else { - $status = $this->getActionHelp($controller, $action); - } - } - return $status; - } - - /** - * Returns all available command names. - * @return array all available command names - */ - public function getCommands() - { - $commands = $this->getModuleCommands(\Yii::$application); - sort($commands); - return array_unique($commands); - } - - /** - * Returns all available actions of the specified controller. - * @param Controller $controller the controller instance - * @return array all available action IDs. - */ - public function getActions($controller) - { - $actions = array_keys($controller->actions); - $class = new \ReflectionClass($controller); - foreach ($class->getMethods() as $method) { - /** @var $method \ReflectionMethod */ - $name = $method->getName(); - if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0) { - $actions[] = lcfirst(substr($name, 6)); - } - } - sort($actions); - return array_unique($actions); - } - - /** - * Returns available commands of a specified module. - * @param \yii\base\Module $module the module instance - * @return array the available command names - */ - protected function getModuleCommands($module) - { - if ($module instanceof Application) { - $prefix = ''; - } else { - $prefix = $module->getUniqueId() . '/'; - } - - $commands = array(); - foreach (array_keys($module->controllers) as $id) { - $commands[] = $prefix . $id; - } - - foreach ($module->getModules() as $id => $child) { - if (($child = $module->getModule($id)) === null) { - continue; - } - foreach ($this->getModuleCommands($child) as $command) { - $commands[] = $prefix . $id . '/' . $command; - } - } - - $files = scandir($module->getControllerPath()); - foreach ($files as $file) { - if(strcmp(substr($file,-14),'Controller.php') === 0 && is_file($file)) { - $commands[] = $prefix . lcfirst(substr(basename($file), 0, -14)); - } - } - - return $commands; - } - - /** - * Displays all available commands. - * @return integer the exit status - */ - protected function getHelp() - { - $commands = $this->getCommands(); - if ($commands !== array()) { - echo "\n Usage: yiic [...options...]\n\n"; - echo "The following commands are available:\n"; - foreach ($commands as $command) { - echo " - $command\n"; - } - echo "\nTo see individual command help, enter:\n"; - echo "\n yiic help \n"; - } else { - echo "\nNo commands are found.\n"; - } - return 0; - } - - /** - * Displays the overall information of the command. - * @param Controller $controller the controller instance - * @return integer the exit status - */ - protected function getControllerHelp($controller) - { - $class = new \ReflectionClass($controller); - $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($class->getDocComment(), '/'))), "\r", ''); - if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { - $comment = trim(substr($comment, 0, $matches[0][1])); - } - - if ($comment !== '') { - echo "\nDESCRIPTION"; - echo "\n-----------\n\n"; - echo $comment . "\n"; - } - - $options = $this->getGlobalOptions($class, $controller); - if ($options !== array()) { - echo "\nGLOBAL OPTIONS"; - echo "\n--------------\n\n"; - foreach ($options as $name => $description) { - echo " --$name"; - if ($description != '') { - echo ": $description\n"; - } - } - echo "\n"; - } - - $actions = $this->getActions($controller); - if ($actions !== array()) { - echo "\nSUB-COMMANDS"; - echo "\n------------\n\n"; - $prefix = $controller->getUniqueId(); - foreach ($actions as $action) { - if ($controller->defaultAction === $action) { - echo " * $prefix/$action (default)\n"; - } else { - echo " * $prefix/$action\n"; - } - } - echo "\n"; - } - - return 0; - } - - /** - * Displays the detailed information of a command action. - * @param Controller $controller the controller instance - * @param string $actionID action ID - * @return integer the exit status - */ - protected function getActionHelp($controller, $actionID) - { - $action = $controller->createAction($actionID); - if ($action === null) { - echo "Unknown sub-command: " . $controller->getUniqueId() . "/$actionID\n"; - return 1; - } - if ($action instanceof InlineAction) { - $method = new \ReflectionMethod($controller, 'action' . $action->id); - } else { - $method = new \ReflectionMethod($action, 'run'); - } - $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($method->getDocComment(), '/'))), "\r", ''); - if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { - $meta = substr($comment, $matches[0][1]); - $comment = trim(substr($comment, 0, $matches[0][1])); - } else { - $meta = ''; - } - - if ($comment !== '') { - echo "\nDESCRIPTION"; - echo "\n-----------\n\n"; - echo $comment . "\n"; - } - - $options = $this->getOptions($method, $meta); - if ($options !== array()) { - echo "\nOPTIONS"; - echo "\n-------\n\n"; - foreach ($options as $name => $description) { - echo " --$name"; - if ($description != '') { - echo ": $description\n"; - } - } - echo "\n"; - } - - return 0; - } - - /** - * @param \ReflectionMethod $method - * @param string $meta - * @return array - */ - protected function getOptions($method, $meta) - { - $params = $method->getParameters(); - $tags = preg_split('/^\s*@/m', $meta, -1, PREG_SPLIT_NO_EMPTY); - $options = array(); - $count = 0; - foreach ($tags as $tag) { - $parts = preg_split('/\s+/', trim($tag), 2); - if ($parts[0] === 'param' && isset($params[$count])) { - $param = $params[$count]; - $comment = isset($parts[1]) ? $parts[1] : ''; - if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $comment, $matches)) { - $type = $matches[1]; - $doc = $matches[3]; - } else { - $type = $comment; - $doc = ''; - } - $comment = $type === '' ? '' : ($type . ', '); - if ($param->isDefaultValueAvailable()) { - $value = $param->getDefaultValue(); - if (!is_array($value)) { - $comment .= 'optional (defaults to ' . var_export($value, true) . ').'; - } else { - $comment .= 'optional.'; - } - } else { - $comment .= 'required.'; - } - if (trim($doc) !== '') { - $comment .= "\n" . preg_replace("/^/m", " ", $doc); - } - $options[$param->getName()] = $comment; - $count++; - } - } - if ($count < count($params)) { - for ($i = $count; $i < count($params); ++$i) { - $options[$params[$i]->getName()] = ''; - } - } - - ksort($options); - return $options; - } - - /** - * @param \ReflectionClass $class - * @param Controller $controller - * @return array - */ - protected function getGlobalOptions($class, $controller) - { - $options = array(); - foreach ($class->getProperties() as $property) { - if (!$property->isPublic() || $property->isStatic() || $property->getDeclaringClass()->getName() === 'yii\base\Controller') { - continue; - } - $name = $property->getName(); - $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($property->getDocComment(), '/'))), "\r", ''); - if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { - $meta = substr($comment, $matches[0][1]); - } else { - $meta = ''; - } - $tags = preg_split('/^\s*@/m', $meta, -1, PREG_SPLIT_NO_EMPTY); - foreach ($tags as $tag) { - $parts = preg_split('/\s+/', trim($tag), 2); - $comment = isset($parts[1]) ? $parts[1] : ''; - if ($parts[0] === 'var' || $parts[0] === 'property') { - if (preg_match('/^([^\s]+)(\s+.*)?/s', $comment, $matches)) { - $type = $matches[1]; - $doc = trim($matches[2]); - } else { - $type = $comment; - $doc = ''; - } - $comment = $type === '' ? '' : ($type . '.'); - if (trim($doc) !== '') { - $comment .= "\n" . preg_replace("/^/m", " ", $doc); - } - $options[$name] = $comment; - break; - } - } - if (!isset($options[$name])) { - $options[$name] = ''; - } - } - ksort($options); - return $options; - } -} \ No newline at end of file diff --git a/framework/console/commands/MessageController.php b/framework/console/commands/MessageController.php deleted file mode 100644 index 8b8595b..0000000 --- a/framework/console/commands/MessageController.php +++ /dev/null @@ -1,223 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * MessageCommand extracts messages to be translated from source files. - * The extracted messages are saved as PHP message source files - * under the specified directory. - * - * @author Qiang Xue - * @version $Id$ - * @package system.cli.commands - * @since 1.0 - */ -class MessageCommand extends CConsoleCommand -{ - public function getHelp() - { - return << - -DESCRIPTION - This command searches for messages to be translated in the specified - source files and compiles them into PHP arrays as message source. - -PARAMETERS - * config-file: required, the path of the configuration file. You can find - an example in framework/messages/config.php. - - The file can be placed anywhere and must be a valid PHP script which - returns an array of name-value pairs. Each name-value pair represents - a configuration option. - - The following options are available: - - - sourcePath: string, root directory of all source files. - - messagePath: string, root directory containing message translations. - - languages: array, list of language codes that the extracted messages - should be translated to. For example, array('zh_cn','en_au'). - - fileTypes: array, a list of file extensions (e.g. 'php', 'xml'). - Only the files whose extension name can be found in this list - will be processed. If empty, all files will be processed. - - exclude: array, a list of directory and file exclusions. Each - exclusion can be either a name or a path. If a file or directory name - or path matches the exclusion, it will not be copied. For example, - an exclusion of '.svn' will exclude all files and directories whose - name is '.svn'. And an exclusion of '/a/b' will exclude file or - directory 'sourcePath/a/b'. - - translator: the name of the function for translating messages. - Defaults to 'Yii::t'. This is used as a mark to find messages to be - translated. - - overwrite: if message file must be overwritten with the merged messages. - - removeOld: if message no longer needs translation it will be removed, - instead of being enclosed between a pair of '@@' marks. - - sort: sort messages by key when merging, regardless of their translation - state (new, obsolete, translated.) - -EOD; - } - - /** - * Execute the action. - * @param array command line parameters specific for this command - */ - public function run($args) - { - if(!isset($args[0])) - $this->usageError('the configuration file is not specified.'); - if(!is_file($args[0])) - $this->usageError("the configuration file {$args[0]} does not exist."); - - $config=require_once($args[0]); - $translator='Yii::t'; - extract($config); - - if(!isset($sourcePath,$messagePath,$languages)) - $this->usageError('The configuration file must specify "sourcePath", "messagePath" and "languages".'); - if(!is_dir($sourcePath)) - $this->usageError("The source path $sourcePath is not a valid directory."); - if(!is_dir($messagePath)) - $this->usageError("The message path $messagePath is not a valid directory."); - if(empty($languages)) - $this->usageError("Languages cannot be empty."); - - if(!isset($overwrite)) - $overwrite = false; - - if(!isset($removeOld)) - $removeOld = false; - - if(!isset($sort)) - $sort = false; - - $options=array(); - if(isset($fileTypes)) - $options['fileTypes']=$fileTypes; - if(isset($exclude)) - $options['exclude']=$exclude; - $files=CFileHelper::findFiles(realpath($sourcePath),$options); - - $messages=array(); - foreach($files as $file) - $messages=array_merge_recursive($messages,$this->extractMessages($file,$translator)); - - foreach($languages as $language) - { - $dir=$messagePath.DIRECTORY_SEPARATOR.$language; - if(!is_dir($dir)) - @mkdir($dir); - foreach($messages as $category=>$msgs) - { - $msgs=array_values(array_unique($msgs)); - $this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort); - } - } - } - - protected function extractMessages($fileName,$translator) - { - echo "Extracting messages from $fileName...\n"; - $subject=file_get_contents($fileName); - $n=preg_match_all('/\b'.$translator.'\s*\(\s*(\'.*?(?$translation) - { - if(!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld) - { - if(substr($translation,0,2)==='@@' && substr($translation,-2)==='@@') - $todo[$message]=$translation; - else - $todo[$message]='@@'.$translation.'@@'; - } - } - $merged=array_merge($todo,$merged); - if($sort) - ksort($merged); - if($overwrite === false) - $fileName.='.merged'; - echo "translation merged.\n"; - } - else - { - $merged=array(); - foreach($messages as $message) - $merged[$message]=''; - ksort($merged); - echo "saved.\n"; - } - $array=str_replace("\r",'',var_export($merged,true)); - $content=<< - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -/** - * MigrateCommand manages the database migrations. - * - * The implementation of this command and other supporting classes referenced - * the yii-dbmigrations extension ((https://github.com/pieterclaerhout/yii-dbmigrations), - * authored by Pieter Claerhout. - * - * @author Qiang Xue - * @version $Id$ - * @package system.cli.commands - * @since 1.1.6 - */ -class MigrateCommand extends CConsoleCommand -{ - const BASE_MIGRATION='m000000_000000_base'; - - /** - * @var string the directory that stores the migrations. This must be specified - * in terms of a path alias, and the corresponding directory must exist. - * Defaults to 'application.migrations' (meaning 'protected/migrations'). - */ - public $migrationPath='application.migrations'; - /** - * @var string the name of the table for keeping applied migration information. - * This table will be automatically created if not exists. Defaults to 'tbl_migration'. - * The table structure is: (version varchar(255) primary key, apply_time integer) - */ - public $migrationTable='tbl_migration'; - /** - * @var string the application component ID that specifies the database connection for - * storing migration information. Defaults to 'db'. - */ - public $connectionID='db'; - /** - * @var string the path of the template file for generating new migrations. This - * must be specified in terms of a path alias (e.g. application.migrations.template). - * If not set, an internal template will be used. - */ - public $templateFile; - /** - * @var string the default command action. It defaults to 'up'. - */ - public $defaultAction='up'; - /** - * @var boolean whether to execute the migration in an interactive mode. Defaults to true. - * Set this to false when performing migration in a cron job or background process. - */ - public $interactive=true; - - public function beforeAction($action,$params) - { - $path=Yii::getPathOfAlias($this->migrationPath); - if($path===false || !is_dir($path)) - die('Error: The migration directory does not exist: '.$this->migrationPath."\n"); - $this->migrationPath=$path; - - $yiiVersion=Yii::getVersion(); - echo "\nYii Migration Tool v1.0 (based on Yii v{$yiiVersion})\n\n"; - - return true; - } - - public function actionUp($args) - { - if(($migrations=$this->getNewMigrations())===array()) - { - echo "No new migration found. Your system is up-to-date.\n"; - return; - } - - $total=count($migrations); - $step=isset($args[0]) ? (int)$args[0] : 0; - if($step>0) - $migrations=array_slice($migrations,0,$step); - - $n=count($migrations); - if($n===$total) - echo "Total $n new ".($n===1 ? 'migration':'migrations')." to be applied:\n"; - else - echo "Total $n out of $total new ".($total===1 ? 'migration':'migrations')." to be applied:\n"; - - foreach($migrations as $migration) - echo " $migration\n"; - echo "\n"; - - if($this->confirm('Apply the above '.($n===1 ? 'migration':'migrations')."?")) - { - foreach($migrations as $migration) - { - if($this->migrateUp($migration)===false) - { - echo "\nMigration failed. All later migrations are canceled.\n"; - return; - } - } - echo "\nMigrated up successfully.\n"; - } - } - - public function actionDown($args) - { - $step=isset($args[0]) ? (int)$args[0] : 1; - if($step<1) - die("Error: The step parameter must be greater than 0.\n"); - - if(($migrations=$this->getMigrationHistory($step))===array()) - { - echo "No migration has been done before.\n"; - return; - } - $migrations=array_keys($migrations); - - $n=count($migrations); - echo "Total $n ".($n===1 ? 'migration':'migrations')." to be reverted:\n"; - foreach($migrations as $migration) - echo " $migration\n"; - echo "\n"; - - if($this->confirm('Revert the above '.($n===1 ? 'migration':'migrations')."?")) - { - foreach($migrations as $migration) - { - if($this->migrateDown($migration)===false) - { - echo "\nMigration failed. All later migrations are canceled.\n"; - return; - } - } - echo "\nMigrated down successfully.\n"; - } - } - - public function actionRedo($args) - { - $step=isset($args[0]) ? (int)$args[0] : 1; - if($step<1) - die("Error: The step parameter must be greater than 0.\n"); - - if(($migrations=$this->getMigrationHistory($step))===array()) - { - echo "No migration has been done before.\n"; - return; - } - $migrations=array_keys($migrations); - - $n=count($migrations); - echo "Total $n ".($n===1 ? 'migration':'migrations')." to be redone:\n"; - foreach($migrations as $migration) - echo " $migration\n"; - echo "\n"; - - if($this->confirm('Redo the above '.($n===1 ? 'migration':'migrations')."?")) - { - foreach($migrations as $migration) - { - if($this->migrateDown($migration)===false) - { - echo "\nMigration failed. All later migrations are canceled.\n"; - return; - } - } - foreach(array_reverse($migrations) as $migration) - { - if($this->migrateUp($migration)===false) - { - echo "\nMigration failed. All later migrations are canceled.\n"; - return; - } - } - echo "\nMigration redone successfully.\n"; - } - } - - public function actionTo($args) - { - if(isset($args[0])) - $version=$args[0]; - else - $this->usageError('Please specify which version to migrate to.'); - - $originalVersion=$version; - if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches)) - $version='m'.$matches[1]; - else - die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n"); - - // try migrate up - $migrations=$this->getNewMigrations(); - foreach($migrations as $i=>$migration) - { - if(strpos($migration,$version.'_')===0) - { - $this->actionUp(array($i+1)); - return; - } - } - - // try migrate down - $migrations=array_keys($this->getMigrationHistory(-1)); - foreach($migrations as $i=>$migration) - { - if(strpos($migration,$version.'_')===0) - { - if($i===0) - echo "Already at '$originalVersion'. Nothing needs to be done.\n"; - else - $this->actionDown(array($i)); - return; - } - } - - die("Error: Unable to find the version '$originalVersion'.\n"); - } - - public function actionMark($args) - { - if(isset($args[0])) - $version=$args[0]; - else - $this->usageError('Please specify which version to mark to.'); - $originalVersion=$version; - if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches)) - $version='m'.$matches[1]; - else - die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n"); - - $db=$this->getDbConnection(); - - // try mark up - $migrations=$this->getNewMigrations(); - foreach($migrations as $i=>$migration) - { - if(strpos($migration,$version.'_')===0) - { - if($this->confirm("Set migration history at $originalVersion?")) - { - $command=$db->createCommand(); - for($j=0;$j<=$i;++$j) - { - $command->insert($this->migrationTable, array( - 'version'=>$migrations[$j], - 'apply_time'=>time(), - )); - } - echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; - } - return; - } - } - - // try mark down - $migrations=array_keys($this->getMigrationHistory(-1)); - foreach($migrations as $i=>$migration) - { - if(strpos($migration,$version.'_')===0) - { - if($i===0) - echo "Already at '$originalVersion'. Nothing needs to be done.\n"; - else - { - if($this->confirm("Set migration history at $originalVersion?")) - { - $command=$db->createCommand(); - for($j=0;$j<$i;++$j) - $command->delete($this->migrationTable, $db->quoteColumnName('version').'=:version', array(':version'=>$migrations[$j])); - echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; - } - } - return; - } - } - - die("Error: Unable to find the version '$originalVersion'.\n"); - } - - public function actionHistory($args) - { - $limit=isset($args[0]) ? (int)$args[0] : -1; - $migrations=$this->getMigrationHistory($limit); - if($migrations===array()) - echo "No migration has been done before.\n"; - else - { - $n=count($migrations); - if($limit>0) - echo "Showing the last $n applied ".($n===1 ? 'migration' : 'migrations').":\n"; - else - echo "Total $n ".($n===1 ? 'migration has' : 'migrations have')." been applied before:\n"; - foreach($migrations as $version=>$time) - echo " (".date('Y-m-d H:i:s',$time).') '.$version."\n"; - } - } - - public function actionNew($args) - { - $limit=isset($args[0]) ? (int)$args[0] : -1; - $migrations=$this->getNewMigrations(); - if($migrations===array()) - echo "No new migrations found. Your system is up-to-date.\n"; - else - { - $n=count($migrations); - if($limit>0 && $n>$limit) - { - $migrations=array_slice($migrations,0,$limit); - echo "Showing $limit out of $n new ".($n===1 ? 'migration' : 'migrations').":\n"; - } - else - echo "Found $n new ".($n===1 ? 'migration' : 'migrations').":\n"; - - foreach($migrations as $migration) - echo " ".$migration."\n"; - } - } - - public function actionCreate($args) - { - if(isset($args[0])) - $name=$args[0]; - else - $this->usageError('Please provide the name of the new migration.'); - - if(!preg_match('/^\w+$/',$name)) - die("Error: The name of the migration must contain letters, digits and/or underscore characters only.\n"); - - $name='m'.gmdate('ymd_His').'_'.$name; - $content=strtr($this->getTemplate(), array('{ClassName}'=>$name)); - $file=$this->migrationPath.DIRECTORY_SEPARATOR.$name.'.php'; - - if($this->confirm("Create new migration '$file'?")) - { - file_put_contents($file, $content); - echo "New migration created successfully.\n"; - } - } - - public function confirm($message) - { - if(!$this->interactive) - return true; - return parent::confirm($message); - } - - protected function migrateUp($class) - { - if($class===self::BASE_MIGRATION) - return; - - echo "*** applying $class\n"; - $start=microtime(true); - $migration=$this->instantiateMigration($class); - if($migration->up()!==false) - { - $this->getDbConnection()->createCommand()->insert($this->migrationTable, array( - 'version'=>$class, - 'apply_time'=>time(), - )); - $time=microtime(true)-$start; - echo "*** applied $class (time: ".sprintf("%.3f",$time)."s)\n\n"; - } - else - { - $time=microtime(true)-$start; - echo "*** failed to apply $class (time: ".sprintf("%.3f",$time)."s)\n\n"; - return false; - } - } - - protected function migrateDown($class) - { - if($class===self::BASE_MIGRATION) - return; - - echo "*** reverting $class\n"; - $start=microtime(true); - $migration=$this->instantiateMigration($class); - if($migration->down()!==false) - { - $db=$this->getDbConnection(); - $db->createCommand()->delete($this->migrationTable, $db->quoteColumnName('version').'=:version', array(':version'=>$class)); - $time=microtime(true)-$start; - echo "*** reverted $class (time: ".sprintf("%.3f",$time)."s)\n\n"; - } - else - { - $time=microtime(true)-$start; - echo "*** failed to revert $class (time: ".sprintf("%.3f",$time)."s)\n\n"; - return false; - } - } - - protected function instantiateMigration($class) - { - $file=$this->migrationPath.DIRECTORY_SEPARATOR.$class.'.php'; - require_once($file); - $migration=new $class; - $migration->setDbConnection($this->getDbConnection()); - return $migration; - } - - /** - * @var CDbConnection - */ - private $_db; - protected function getDbConnection() - { - if($this->_db!==null) - return $this->_db; - else if(($this->_db=\Yii::$application->getComponent($this->connectionID)) instanceof CDbConnection) - return $this->_db; - else - die("Error: CMigrationCommand.connectionID '{$this->connectionID}' is invalid. Please make sure it refers to the ID of a CDbConnection application component.\n"); - } - - protected function getMigrationHistory($limit) - { - $db=$this->getDbConnection(); - if($db->schema->getTable($this->migrationTable)===null) - { - $this->createMigrationHistoryTable(); - } - return CHtml::listData($db->createCommand() - ->select('version, apply_time') - ->from($this->migrationTable) - ->order('version DESC') - ->limit($limit) - ->queryAll(), 'version', 'apply_time'); - } - - protected function createMigrationHistoryTable() - { - $db=$this->getDbConnection(); - echo 'Creating migration history table "'.$this->migrationTable.'"...'; - $db->createCommand()->createTable($this->migrationTable,array( - 'version'=>'string NOT NULL PRIMARY KEY', - 'apply_time'=>'integer', - )); - $db->createCommand()->insert($this->migrationTable,array( - 'version'=>self::BASE_MIGRATION, - 'apply_time'=>time(), - )); - echo "done.\n"; - } - - protected function getNewMigrations() - { - $applied=array(); - foreach($this->getMigrationHistory(-1) as $version=>$time) - $applied[substr($version,1,13)]=true; - - $migrations=array(); - $handle=opendir($this->migrationPath); - while(($file=readdir($handle))!==false) - { - if($file==='.' || $file==='..') - continue; - $path=$this->migrationPath.DIRECTORY_SEPARATOR.$file; - if(preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/',$file,$matches) && is_file($path) && !isset($applied[$matches[2]])) - $migrations[]=$matches[1]; - } - closedir($handle); - sort($migrations); - return $migrations; - } - - public function getHelp() - { - return <<templateFile!==null) - return file_get_contents(Yii::getPathOfAlias($this->templateFile).'.php'); - else - return << - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - * @version $Id$ - */ - -/** - * ShellCommand executes the specified Web application and provides a shell for interaction. - * - * @property string $help The help information for the shell command. - * - * @author Qiang Xue - * @version $Id$ - * @package system.cli.commands - * @since 1.0 - */ -class ShellCommand extends CConsoleCommand -{ - /** - * @return string the help information for the shell command - */ - public function getHelp() - { - return <<usageError("{$args[0]} does not exist or is not an entry script file."); - - // fake the web server setting - $cwd=getcwd(); - chdir(dirname($entryScript)); - $_SERVER['SCRIPT_NAME']='/'.basename($entryScript); - $_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME']; - $_SERVER['SCRIPT_FILENAME']=$entryScript; - $_SERVER['HTTP_HOST']='localhost'; - $_SERVER['SERVER_NAME']='localhost'; - $_SERVER['SERVER_PORT']=80; - - // reset context to run the web application - restore_error_handler(); - restore_exception_handler(); - Yii::setApplication(null); - Yii::setPathOfAlias('application',null); - - ob_start(); - $config=require($entryScript); - ob_end_clean(); - - // oops, the entry script turns out to be a config file - if(is_array($config)) - { - chdir($cwd); - $_SERVER['SCRIPT_NAME']='/index.php'; - $_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME']; - $_SERVER['SCRIPT_FILENAME']=$cwd.DIRECTORY_SEPARATOR.'index.php'; - Yii::createWebApplication($config); - } - - restore_error_handler(); - restore_exception_handler(); - - $yiiVersion=Yii::getVersion(); - echo <<runShell(); - } - - protected function runShell() - { - // disable E_NOTICE so that the shell is more friendly - error_reporting(E_ALL ^ E_NOTICE); - - $_runner_=new CConsoleCommandRunner; - $_runner_->addCommands(dirname(__FILE__).'/shell'); - $_runner_->addCommands(Yii::getPathOfAlias('application.commands.shell')); - if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false) - $_runner_->addCommands($_path_); - $_commands_=$_runner_->commands; - $log=\Yii::$application->log; - - while(($_line_=$this->prompt("\n>>"))!==false) - { - $_line_=trim($_line_); - if($_line_==='exit') - return; - try - { - $_args_=preg_split('/[\s,]+/',rtrim($_line_,';'),-1,PREG_SPLIT_NO_EMPTY); - if(isset($_args_[0]) && isset($_commands_[$_args_[0]])) - { - $_command_=$_runner_->createCommand($_args_[0]); - array_shift($_args_); - $_command_->init(); - $_command_->run($_args_); - } - else - echo eval($_line_.';'); - } - catch(Exception $e) - { - if($e instanceof ShellException) - echo $e->getMessage(); - else - echo $e; - } - } - } -} - -class ShellException extends CException -{ -} \ No newline at end of file diff --git a/framework/console/controllers/AppController.php b/framework/console/controllers/AppController.php new file mode 100644 index 0000000..0f3f846 --- /dev/null +++ b/framework/console/controllers/AppController.php @@ -0,0 +1,129 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id$ + */ + +/** + * WebAppCommand creates an Yii Web application at the specified location. + * + * @author Qiang Xue + * @version $Id$ + * @package system.cli.commands + * @since 1.0 + */ +class WebAppCommand extends CConsoleCommand +{ + private $_rootPath; + + public function getHelp() + { + return << + +DESCRIPTION + This command generates an Yii Web Application at the specified location. + +PARAMETERS + * app-path: required, the directory where the new application will be created. + If the directory does not exist, it will be created. After the application + is created, please make sure the directory can be accessed by Web users. + +EOD; + } + + /** + * Execute the action. + * @param array command line parameters specific for this command + */ + public function run($args) + { + if(!isset($args[0])) + $this->usageError('the Web application location is not specified.'); + $path=strtr($args[0],'/\\',DIRECTORY_SEPARATOR); + if(strpos($path,DIRECTORY_SEPARATOR)===false) + $path='.'.DIRECTORY_SEPARATOR.$path; + $dir=rtrim(realpath(dirname($path)),'\\/'); + if($dir===false || !is_dir($dir)) + $this->usageError("The directory '$path' is not valid. Please make sure the parent directory exists."); + if(basename($path)==='.') + $this->_rootPath=$path=$dir; + else + $this->_rootPath=$path=$dir.DIRECTORY_SEPARATOR.basename($path); + if($this->confirm("Create a Web application under '$path'?")) + { + $sourceDir=realpath(dirname(__FILE__).'/../views/webapp'); + if($sourceDir===false) + die("\nUnable to locate the source directory.\n"); + $list=$this->buildFileList($sourceDir,$path); + $list['index.php']['callback']=array($this,'generateIndex'); + $list['index-test.php']['callback']=array($this,'generateIndex'); + $list['protected/tests/bootstrap.php']['callback']=array($this,'generateTestBoostrap'); + $list['protected/yiic.php']['callback']=array($this,'generateYiic'); + $this->copyFiles($list); + @chmod($path.'/assets',0777); + @chmod($path.'/protected/runtime',0777); + @chmod($path.'/protected/data',0777); + @chmod($path.'/protected/data/testdrive.db',0777); + @chmod($path.'/protected/yiic',0755); + echo "\nYour application has been created successfully under {$path}.\n"; + } + } + + public function generateIndex($source,$params) + { + $content=file_get_contents($source); + $yii=realpath(dirname(__FILE__).'/../../yii.php'); + $yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'index.php'); + $yii=str_replace('\\','\\\\',$yii); + return preg_replace('/\$yii\s*=(.*?);/',"\$yii=$yii;",$content); + } + + public function generateTestBoostrap($source,$params) + { + $content=file_get_contents($source); + $yii=realpath(dirname(__FILE__).'/../../yiit.php'); + $yii=$this->getRelativePath($yii,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'tests'.DIRECTORY_SEPARATOR.'bootstrap.php'); + $yii=str_replace('\\','\\\\',$yii); + return preg_replace('/\$yiit\s*=(.*?);/',"\$yiit=$yii;",$content); + } + + public function generateYiic($source,$params) + { + $content=file_get_contents($source); + $yiic=realpath(dirname(__FILE__).'/../../yiic.php'); + $yiic=$this->getRelativePath($yiic,$this->_rootPath.DIRECTORY_SEPARATOR.'protected'.DIRECTORY_SEPARATOR.'yiic.php'); + $yiic=str_replace('\\','\\\\',$yiic); + return preg_replace('/\$yiic\s*=(.*?);/',"\$yiic=$yiic;",$content); + } + + protected function getRelativePath($path1,$path2) + { + $segs1=explode(DIRECTORY_SEPARATOR,$path1); + $segs2=explode(DIRECTORY_SEPARATOR,$path2); + $n1=count($segs1); + $n2=count($segs2); + + for($i=0;$i<$n1 && $i<$n2;++$i) + { + if($segs1[$i]!==$segs2[$i]) + break; + } + + if($i===0) + return "'".$path1."'"; + $up=''; + for($j=$i;$j<$n2-1;++$j) + $up.='/..'; + for(;$i<$n1-1;++$i) + $up.='/'.$segs1[$i]; + + return 'dirname(__FILE__).\''.$up.'/'.basename($path1).'\''; + } +} \ No newline at end of file diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php new file mode 100644 index 0000000..8806fe0 --- /dev/null +++ b/framework/console/controllers/HelpController.php @@ -0,0 +1,356 @@ + + * @since 2.0 + */ +class HelpController extends Controller +{ + /** + * Displays available commands or the detailed information + * about a particular command. For example, + * + * ~~~ + * yiic help # list available commands + * yiic help message # display help info about "message" + * ~~~ + * + * @param array $args additional anonymous command line arguments. + * You may provide a command name to display its detailed information. + * @return integer the exit status + */ + public function actionIndex($args = array()) + { + if (empty($args)) { + $status = $this->getHelp(); + } else { + $result = \Yii::$application->createController($args[0]); + if ($result === false) { + echo "Unknown command: " . $args[0] . "\n"; + return 1; + } + + list($controller, $action) = $result; + + if ($action === '') { + $status = $this->getControllerHelp($controller); + } else { + $status = $this->getActionHelp($controller, $action); + } + } + return $status; + } + + /** + * Returns all available command names. + * @return array all available command names + */ + public function getCommands() + { + $commands = $this->getModuleCommands(\Yii::$application); + sort($commands); + return array_unique($commands); + } + + /** + * Returns all available actions of the specified controller. + * @param Controller $controller the controller instance + * @return array all available action IDs. + */ + public function getActions($controller) + { + $actions = array_keys($controller->actions); + $class = new \ReflectionClass($controller); + foreach ($class->getMethods() as $method) { + /** @var $method \ReflectionMethod */ + $name = $method->getName(); + if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0) { + $actions[] = lcfirst(substr($name, 6)); + } + } + sort($actions); + return array_unique($actions); + } + + /** + * Returns available commands of a specified module. + * @param \yii\base\Module $module the module instance + * @return array the available command names + */ + protected function getModuleCommands($module) + { + if ($module instanceof Application) { + $prefix = ''; + } else { + $prefix = $module->getUniqueId() . '/'; + } + + $commands = array(); + foreach (array_keys($module->controllers) as $id) { + $commands[] = $prefix . $id; + } + + foreach ($module->getModules() as $id => $child) { + if (($child = $module->getModule($id)) === null) { + continue; + } + foreach ($this->getModuleCommands($child) as $command) { + $commands[] = $prefix . $id . '/' . $command; + } + } + + $files = scandir($module->getControllerPath()); + foreach ($files as $file) { + if(strcmp(substr($file,-14),'Controller.php') === 0 && is_file($file)) { + $commands[] = $prefix . lcfirst(substr(basename($file), 0, -14)); + } + } + + return $commands; + } + + /** + * Displays all available commands. + * @return integer the exit status + */ + protected function getHelp() + { + $commands = $this->getCommands(); + if ($commands !== array()) { + echo "\n Usage: yiic [...options...]\n\n"; + echo "The following commands are available:\n"; + foreach ($commands as $command) { + echo " - $command\n"; + } + echo "\nTo see individual command help, enter:\n"; + echo "\n yiic help \n"; + } else { + echo "\nNo commands are found.\n"; + } + return 0; + } + + /** + * Displays the overall information of the command. + * @param Controller $controller the controller instance + * @return integer the exit status + */ + protected function getControllerHelp($controller) + { + $class = new \ReflectionClass($controller); + $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($class->getDocComment(), '/'))), "\r", ''); + if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { + $comment = trim(substr($comment, 0, $matches[0][1])); + } + + if ($comment !== '') { + echo "\nDESCRIPTION"; + echo "\n-----------\n\n"; + echo $comment . "\n"; + } + + $options = $this->getGlobalOptions($class, $controller); + if ($options !== array()) { + echo "\nGLOBAL OPTIONS"; + echo "\n--------------\n\n"; + foreach ($options as $name => $description) { + echo " --$name"; + if ($description != '') { + echo ": $description\n"; + } + } + echo "\n"; + } + + $actions = $this->getActions($controller); + if ($actions !== array()) { + echo "\nSUB-COMMANDS"; + echo "\n------------\n\n"; + $prefix = $controller->getUniqueId(); + foreach ($actions as $action) { + if ($controller->defaultAction === $action) { + echo " * $prefix/$action (default)\n"; + } else { + echo " * $prefix/$action\n"; + } + } + echo "\n"; + } + + return 0; + } + + /** + * Displays the detailed information of a command action. + * @param Controller $controller the controller instance + * @param string $actionID action ID + * @return integer the exit status + */ + protected function getActionHelp($controller, $actionID) + { + $action = $controller->createAction($actionID); + if ($action === null) { + echo "Unknown sub-command: " . $controller->getUniqueId() . "/$actionID\n"; + return 1; + } + if ($action instanceof InlineAction) { + $method = new \ReflectionMethod($controller, 'action' . $action->id); + } else { + $method = new \ReflectionMethod($action, 'run'); + } + $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($method->getDocComment(), '/'))), "\r", ''); + if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { + $meta = substr($comment, $matches[0][1]); + $comment = trim(substr($comment, 0, $matches[0][1])); + } else { + $meta = ''; + } + + if ($comment !== '') { + echo "\nDESCRIPTION"; + echo "\n-----------\n\n"; + echo $comment . "\n"; + } + + $options = $this->getOptions($method, $meta); + if ($options !== array()) { + echo "\nOPTIONS"; + echo "\n-------\n\n"; + foreach ($options as $name => $description) { + echo " --$name"; + if ($description != '') { + echo ": $description\n"; + } + } + echo "\n"; + } + + return 0; + } + + /** + * @param \ReflectionMethod $method + * @param string $meta + * @return array + */ + protected function getOptions($method, $meta) + { + $params = $method->getParameters(); + $tags = preg_split('/^\s*@/m', $meta, -1, PREG_SPLIT_NO_EMPTY); + $options = array(); + $count = 0; + foreach ($tags as $tag) { + $parts = preg_split('/\s+/', trim($tag), 2); + if ($parts[0] === 'param' && isset($params[$count])) { + $param = $params[$count]; + $comment = isset($parts[1]) ? $parts[1] : ''; + if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $comment, $matches)) { + $type = $matches[1]; + $doc = $matches[3]; + } else { + $type = $comment; + $doc = ''; + } + $comment = $type === '' ? '' : ($type . ', '); + if ($param->isDefaultValueAvailable()) { + $value = $param->getDefaultValue(); + if (!is_array($value)) { + $comment .= 'optional (defaults to ' . var_export($value, true) . ').'; + } else { + $comment .= 'optional.'; + } + } else { + $comment .= 'required.'; + } + if (trim($doc) !== '') { + $comment .= "\n" . preg_replace("/^/m", " ", $doc); + } + $options[$param->getName()] = $comment; + $count++; + } + } + if ($count < count($params)) { + for ($i = $count; $i < count($params); ++$i) { + $options[$params[$i]->getName()] = ''; + } + } + + ksort($options); + return $options; + } + + /** + * @param \ReflectionClass $class + * @param Controller $controller + * @return array + */ + protected function getGlobalOptions($class, $controller) + { + $options = array(); + foreach ($class->getProperties() as $property) { + if (!$property->isPublic() || $property->isStatic() || $property->getDeclaringClass()->getName() === 'yii\base\Controller') { + continue; + } + $name = $property->getName(); + $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($property->getDocComment(), '/'))), "\r", ''); + if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { + $meta = substr($comment, $matches[0][1]); + } else { + $meta = ''; + } + $tags = preg_split('/^\s*@/m', $meta, -1, PREG_SPLIT_NO_EMPTY); + foreach ($tags as $tag) { + $parts = preg_split('/\s+/', trim($tag), 2); + $comment = isset($parts[1]) ? $parts[1] : ''; + if ($parts[0] === 'var' || $parts[0] === 'property') { + if (preg_match('/^([^\s]+)(\s+.*)?/s', $comment, $matches)) { + $type = $matches[1]; + $doc = trim($matches[2]); + } else { + $type = $comment; + $doc = ''; + } + $comment = $type === '' ? '' : ($type . '.'); + if (trim($doc) !== '') { + $comment .= "\n" . preg_replace("/^/m", " ", $doc); + } + $options[$name] = $comment; + break; + } + } + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + ksort($options); + return $options; + } +} \ No newline at end of file diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php new file mode 100644 index 0000000..8b8595b --- /dev/null +++ b/framework/console/controllers/MessageController.php @@ -0,0 +1,223 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * MessageCommand extracts messages to be translated from source files. + * The extracted messages are saved as PHP message source files + * under the specified directory. + * + * @author Qiang Xue + * @version $Id$ + * @package system.cli.commands + * @since 1.0 + */ +class MessageCommand extends CConsoleCommand +{ + public function getHelp() + { + return << + +DESCRIPTION + This command searches for messages to be translated in the specified + source files and compiles them into PHP arrays as message source. + +PARAMETERS + * config-file: required, the path of the configuration file. You can find + an example in framework/messages/config.php. + + The file can be placed anywhere and must be a valid PHP script which + returns an array of name-value pairs. Each name-value pair represents + a configuration option. + + The following options are available: + + - sourcePath: string, root directory of all source files. + - messagePath: string, root directory containing message translations. + - languages: array, list of language codes that the extracted messages + should be translated to. For example, array('zh_cn','en_au'). + - fileTypes: array, a list of file extensions (e.g. 'php', 'xml'). + Only the files whose extension name can be found in this list + will be processed. If empty, all files will be processed. + - exclude: array, a list of directory and file exclusions. Each + exclusion can be either a name or a path. If a file or directory name + or path matches the exclusion, it will not be copied. For example, + an exclusion of '.svn' will exclude all files and directories whose + name is '.svn'. And an exclusion of '/a/b' will exclude file or + directory 'sourcePath/a/b'. + - translator: the name of the function for translating messages. + Defaults to 'Yii::t'. This is used as a mark to find messages to be + translated. + - overwrite: if message file must be overwritten with the merged messages. + - removeOld: if message no longer needs translation it will be removed, + instead of being enclosed between a pair of '@@' marks. + - sort: sort messages by key when merging, regardless of their translation + state (new, obsolete, translated.) + +EOD; + } + + /** + * Execute the action. + * @param array command line parameters specific for this command + */ + public function run($args) + { + if(!isset($args[0])) + $this->usageError('the configuration file is not specified.'); + if(!is_file($args[0])) + $this->usageError("the configuration file {$args[0]} does not exist."); + + $config=require_once($args[0]); + $translator='Yii::t'; + extract($config); + + if(!isset($sourcePath,$messagePath,$languages)) + $this->usageError('The configuration file must specify "sourcePath", "messagePath" and "languages".'); + if(!is_dir($sourcePath)) + $this->usageError("The source path $sourcePath is not a valid directory."); + if(!is_dir($messagePath)) + $this->usageError("The message path $messagePath is not a valid directory."); + if(empty($languages)) + $this->usageError("Languages cannot be empty."); + + if(!isset($overwrite)) + $overwrite = false; + + if(!isset($removeOld)) + $removeOld = false; + + if(!isset($sort)) + $sort = false; + + $options=array(); + if(isset($fileTypes)) + $options['fileTypes']=$fileTypes; + if(isset($exclude)) + $options['exclude']=$exclude; + $files=CFileHelper::findFiles(realpath($sourcePath),$options); + + $messages=array(); + foreach($files as $file) + $messages=array_merge_recursive($messages,$this->extractMessages($file,$translator)); + + foreach($languages as $language) + { + $dir=$messagePath.DIRECTORY_SEPARATOR.$language; + if(!is_dir($dir)) + @mkdir($dir); + foreach($messages as $category=>$msgs) + { + $msgs=array_values(array_unique($msgs)); + $this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort); + } + } + } + + protected function extractMessages($fileName,$translator) + { + echo "Extracting messages from $fileName...\n"; + $subject=file_get_contents($fileName); + $n=preg_match_all('/\b'.$translator.'\s*\(\s*(\'.*?(?$translation) + { + if(!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld) + { + if(substr($translation,0,2)==='@@' && substr($translation,-2)==='@@') + $todo[$message]=$translation; + else + $todo[$message]='@@'.$translation.'@@'; + } + } + $merged=array_merge($todo,$merged); + if($sort) + ksort($merged); + if($overwrite === false) + $fileName.='.merged'; + echo "translation merged.\n"; + } + else + { + $merged=array(); + foreach($messages as $message) + $merged[$message]=''; + ksort($merged); + echo "saved.\n"; + } + $array=str_replace("\r",'',var_export($merged,true)); + $content=<< + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * MigrateCommand manages the database migrations. + * + * The implementation of this command and other supporting classes referenced + * the yii-dbmigrations extension ((https://github.com/pieterclaerhout/yii-dbmigrations), + * authored by Pieter Claerhout. + * + * @author Qiang Xue + * @version $Id$ + * @package system.cli.commands + * @since 1.1.6 + */ +class MigrateCommand extends CConsoleCommand +{ + const BASE_MIGRATION='m000000_000000_base'; + + /** + * @var string the directory that stores the migrations. This must be specified + * in terms of a path alias, and the corresponding directory must exist. + * Defaults to 'application.migrations' (meaning 'protected/migrations'). + */ + public $migrationPath='application.migrations'; + /** + * @var string the name of the table for keeping applied migration information. + * This table will be automatically created if not exists. Defaults to 'tbl_migration'. + * The table structure is: (version varchar(255) primary key, apply_time integer) + */ + public $migrationTable='tbl_migration'; + /** + * @var string the application component ID that specifies the database connection for + * storing migration information. Defaults to 'db'. + */ + public $connectionID='db'; + /** + * @var string the path of the template file for generating new migrations. This + * must be specified in terms of a path alias (e.g. application.migrations.template). + * If not set, an internal template will be used. + */ + public $templateFile; + /** + * @var string the default command action. It defaults to 'up'. + */ + public $defaultAction='up'; + /** + * @var boolean whether to execute the migration in an interactive mode. Defaults to true. + * Set this to false when performing migration in a cron job or background process. + */ + public $interactive=true; + + public function beforeAction($action,$params) + { + $path=Yii::getPathOfAlias($this->migrationPath); + if($path===false || !is_dir($path)) + die('Error: The migration directory does not exist: '.$this->migrationPath."\n"); + $this->migrationPath=$path; + + $yiiVersion=Yii::getVersion(); + echo "\nYii Migration Tool v1.0 (based on Yii v{$yiiVersion})\n\n"; + + return true; + } + + public function actionUp($args) + { + if(($migrations=$this->getNewMigrations())===array()) + { + echo "No new migration found. Your system is up-to-date.\n"; + return; + } + + $total=count($migrations); + $step=isset($args[0]) ? (int)$args[0] : 0; + if($step>0) + $migrations=array_slice($migrations,0,$step); + + $n=count($migrations); + if($n===$total) + echo "Total $n new ".($n===1 ? 'migration':'migrations')." to be applied:\n"; + else + echo "Total $n out of $total new ".($total===1 ? 'migration':'migrations')." to be applied:\n"; + + foreach($migrations as $migration) + echo " $migration\n"; + echo "\n"; + + if($this->confirm('Apply the above '.($n===1 ? 'migration':'migrations')."?")) + { + foreach($migrations as $migration) + { + if($this->migrateUp($migration)===false) + { + echo "\nMigration failed. All later migrations are canceled.\n"; + return; + } + } + echo "\nMigrated up successfully.\n"; + } + } + + public function actionDown($args) + { + $step=isset($args[0]) ? (int)$args[0] : 1; + if($step<1) + die("Error: The step parameter must be greater than 0.\n"); + + if(($migrations=$this->getMigrationHistory($step))===array()) + { + echo "No migration has been done before.\n"; + return; + } + $migrations=array_keys($migrations); + + $n=count($migrations); + echo "Total $n ".($n===1 ? 'migration':'migrations')." to be reverted:\n"; + foreach($migrations as $migration) + echo " $migration\n"; + echo "\n"; + + if($this->confirm('Revert the above '.($n===1 ? 'migration':'migrations')."?")) + { + foreach($migrations as $migration) + { + if($this->migrateDown($migration)===false) + { + echo "\nMigration failed. All later migrations are canceled.\n"; + return; + } + } + echo "\nMigrated down successfully.\n"; + } + } + + public function actionRedo($args) + { + $step=isset($args[0]) ? (int)$args[0] : 1; + if($step<1) + die("Error: The step parameter must be greater than 0.\n"); + + if(($migrations=$this->getMigrationHistory($step))===array()) + { + echo "No migration has been done before.\n"; + return; + } + $migrations=array_keys($migrations); + + $n=count($migrations); + echo "Total $n ".($n===1 ? 'migration':'migrations')." to be redone:\n"; + foreach($migrations as $migration) + echo " $migration\n"; + echo "\n"; + + if($this->confirm('Redo the above '.($n===1 ? 'migration':'migrations')."?")) + { + foreach($migrations as $migration) + { + if($this->migrateDown($migration)===false) + { + echo "\nMigration failed. All later migrations are canceled.\n"; + return; + } + } + foreach(array_reverse($migrations) as $migration) + { + if($this->migrateUp($migration)===false) + { + echo "\nMigration failed. All later migrations are canceled.\n"; + return; + } + } + echo "\nMigration redone successfully.\n"; + } + } + + public function actionTo($args) + { + if(isset($args[0])) + $version=$args[0]; + else + $this->usageError('Please specify which version to migrate to.'); + + $originalVersion=$version; + if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches)) + $version='m'.$matches[1]; + else + die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n"); + + // try migrate up + $migrations=$this->getNewMigrations(); + foreach($migrations as $i=>$migration) + { + if(strpos($migration,$version.'_')===0) + { + $this->actionUp(array($i+1)); + return; + } + } + + // try migrate down + $migrations=array_keys($this->getMigrationHistory(-1)); + foreach($migrations as $i=>$migration) + { + if(strpos($migration,$version.'_')===0) + { + if($i===0) + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + else + $this->actionDown(array($i)); + return; + } + } + + die("Error: Unable to find the version '$originalVersion'.\n"); + } + + public function actionMark($args) + { + if(isset($args[0])) + $version=$args[0]; + else + $this->usageError('Please specify which version to mark to.'); + $originalVersion=$version; + if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches)) + $version='m'.$matches[1]; + else + die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n"); + + $db=$this->getDbConnection(); + + // try mark up + $migrations=$this->getNewMigrations(); + foreach($migrations as $i=>$migration) + { + if(strpos($migration,$version.'_')===0) + { + if($this->confirm("Set migration history at $originalVersion?")) + { + $command=$db->createCommand(); + for($j=0;$j<=$i;++$j) + { + $command->insert($this->migrationTable, array( + 'version'=>$migrations[$j], + 'apply_time'=>time(), + )); + } + echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; + } + return; + } + } + + // try mark down + $migrations=array_keys($this->getMigrationHistory(-1)); + foreach($migrations as $i=>$migration) + { + if(strpos($migration,$version.'_')===0) + { + if($i===0) + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + else + { + if($this->confirm("Set migration history at $originalVersion?")) + { + $command=$db->createCommand(); + for($j=0;$j<$i;++$j) + $command->delete($this->migrationTable, $db->quoteColumnName('version').'=:version', array(':version'=>$migrations[$j])); + echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; + } + } + return; + } + } + + die("Error: Unable to find the version '$originalVersion'.\n"); + } + + public function actionHistory($args) + { + $limit=isset($args[0]) ? (int)$args[0] : -1; + $migrations=$this->getMigrationHistory($limit); + if($migrations===array()) + echo "No migration has been done before.\n"; + else + { + $n=count($migrations); + if($limit>0) + echo "Showing the last $n applied ".($n===1 ? 'migration' : 'migrations').":\n"; + else + echo "Total $n ".($n===1 ? 'migration has' : 'migrations have')." been applied before:\n"; + foreach($migrations as $version=>$time) + echo " (".date('Y-m-d H:i:s',$time).') '.$version."\n"; + } + } + + public function actionNew($args) + { + $limit=isset($args[0]) ? (int)$args[0] : -1; + $migrations=$this->getNewMigrations(); + if($migrations===array()) + echo "No new migrations found. Your system is up-to-date.\n"; + else + { + $n=count($migrations); + if($limit>0 && $n>$limit) + { + $migrations=array_slice($migrations,0,$limit); + echo "Showing $limit out of $n new ".($n===1 ? 'migration' : 'migrations').":\n"; + } + else + echo "Found $n new ".($n===1 ? 'migration' : 'migrations').":\n"; + + foreach($migrations as $migration) + echo " ".$migration."\n"; + } + } + + public function actionCreate($args) + { + if(isset($args[0])) + $name=$args[0]; + else + $this->usageError('Please provide the name of the new migration.'); + + if(!preg_match('/^\w+$/',$name)) + die("Error: The name of the migration must contain letters, digits and/or underscore characters only.\n"); + + $name='m'.gmdate('ymd_His').'_'.$name; + $content=strtr($this->getTemplate(), array('{ClassName}'=>$name)); + $file=$this->migrationPath.DIRECTORY_SEPARATOR.$name.'.php'; + + if($this->confirm("Create new migration '$file'?")) + { + file_put_contents($file, $content); + echo "New migration created successfully.\n"; + } + } + + public function confirm($message) + { + if(!$this->interactive) + return true; + return parent::confirm($message); + } + + protected function migrateUp($class) + { + if($class===self::BASE_MIGRATION) + return; + + echo "*** applying $class\n"; + $start=microtime(true); + $migration=$this->instantiateMigration($class); + if($migration->up()!==false) + { + $this->getDbConnection()->createCommand()->insert($this->migrationTable, array( + 'version'=>$class, + 'apply_time'=>time(), + )); + $time=microtime(true)-$start; + echo "*** applied $class (time: ".sprintf("%.3f",$time)."s)\n\n"; + } + else + { + $time=microtime(true)-$start; + echo "*** failed to apply $class (time: ".sprintf("%.3f",$time)."s)\n\n"; + return false; + } + } + + protected function migrateDown($class) + { + if($class===self::BASE_MIGRATION) + return; + + echo "*** reverting $class\n"; + $start=microtime(true); + $migration=$this->instantiateMigration($class); + if($migration->down()!==false) + { + $db=$this->getDbConnection(); + $db->createCommand()->delete($this->migrationTable, $db->quoteColumnName('version').'=:version', array(':version'=>$class)); + $time=microtime(true)-$start; + echo "*** reverted $class (time: ".sprintf("%.3f",$time)."s)\n\n"; + } + else + { + $time=microtime(true)-$start; + echo "*** failed to revert $class (time: ".sprintf("%.3f",$time)."s)\n\n"; + return false; + } + } + + protected function instantiateMigration($class) + { + $file=$this->migrationPath.DIRECTORY_SEPARATOR.$class.'.php'; + require_once($file); + $migration=new $class; + $migration->setDbConnection($this->getDbConnection()); + return $migration; + } + + /** + * @var CDbConnection + */ + private $_db; + protected function getDbConnection() + { + if($this->_db!==null) + return $this->_db; + else if(($this->_db=\Yii::$application->getComponent($this->connectionID)) instanceof CDbConnection) + return $this->_db; + else + die("Error: CMigrationCommand.connectionID '{$this->connectionID}' is invalid. Please make sure it refers to the ID of a CDbConnection application component.\n"); + } + + protected function getMigrationHistory($limit) + { + $db=$this->getDbConnection(); + if($db->schema->getTable($this->migrationTable)===null) + { + $this->createMigrationHistoryTable(); + } + return CHtml::listData($db->createCommand() + ->select('version, apply_time') + ->from($this->migrationTable) + ->order('version DESC') + ->limit($limit) + ->queryAll(), 'version', 'apply_time'); + } + + protected function createMigrationHistoryTable() + { + $db=$this->getDbConnection(); + echo 'Creating migration history table "'.$this->migrationTable.'"...'; + $db->createCommand()->createTable($this->migrationTable,array( + 'version'=>'string NOT NULL PRIMARY KEY', + 'apply_time'=>'integer', + )); + $db->createCommand()->insert($this->migrationTable,array( + 'version'=>self::BASE_MIGRATION, + 'apply_time'=>time(), + )); + echo "done.\n"; + } + + protected function getNewMigrations() + { + $applied=array(); + foreach($this->getMigrationHistory(-1) as $version=>$time) + $applied[substr($version,1,13)]=true; + + $migrations=array(); + $handle=opendir($this->migrationPath); + while(($file=readdir($handle))!==false) + { + if($file==='.' || $file==='..') + continue; + $path=$this->migrationPath.DIRECTORY_SEPARATOR.$file; + if(preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/',$file,$matches) && is_file($path) && !isset($applied[$matches[2]])) + $migrations[]=$matches[1]; + } + closedir($handle); + sort($migrations); + return $migrations; + } + + public function getHelp() + { + return <<templateFile!==null) + return file_get_contents(Yii::getPathOfAlias($this->templateFile).'.php'); + else + return << + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id$ + */ + +/** + * ShellCommand executes the specified Web application and provides a shell for interaction. + * + * @property string $help The help information for the shell command. + * + * @author Qiang Xue + * @version $Id$ + * @package system.cli.commands + * @since 1.0 + */ +class ShellCommand extends CConsoleCommand +{ + /** + * @return string the help information for the shell command + */ + public function getHelp() + { + return <<usageError("{$args[0]} does not exist or is not an entry script file."); + + // fake the web server setting + $cwd=getcwd(); + chdir(dirname($entryScript)); + $_SERVER['SCRIPT_NAME']='/'.basename($entryScript); + $_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME']; + $_SERVER['SCRIPT_FILENAME']=$entryScript; + $_SERVER['HTTP_HOST']='localhost'; + $_SERVER['SERVER_NAME']='localhost'; + $_SERVER['SERVER_PORT']=80; + + // reset context to run the web application + restore_error_handler(); + restore_exception_handler(); + Yii::setApplication(null); + Yii::setPathOfAlias('application',null); + + ob_start(); + $config=require($entryScript); + ob_end_clean(); + + // oops, the entry script turns out to be a config file + if(is_array($config)) + { + chdir($cwd); + $_SERVER['SCRIPT_NAME']='/index.php'; + $_SERVER['REQUEST_URI']=$_SERVER['SCRIPT_NAME']; + $_SERVER['SCRIPT_FILENAME']=$cwd.DIRECTORY_SEPARATOR.'index.php'; + Yii::createWebApplication($config); + } + + restore_error_handler(); + restore_exception_handler(); + + $yiiVersion=Yii::getVersion(); + echo <<runShell(); + } + + protected function runShell() + { + // disable E_NOTICE so that the shell is more friendly + error_reporting(E_ALL ^ E_NOTICE); + + $_runner_=new CConsoleCommandRunner; + $_runner_->addCommands(dirname(__FILE__).'/shell'); + $_runner_->addCommands(Yii::getPathOfAlias('application.commands.shell')); + if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false) + $_runner_->addCommands($_path_); + $_commands_=$_runner_->commands; + $log=\Yii::$application->log; + + while(($_line_=$this->prompt("\n>>"))!==false) + { + $_line_=trim($_line_); + if($_line_==='exit') + return; + try + { + $_args_=preg_split('/[\s,]+/',rtrim($_line_,';'),-1,PREG_SPLIT_NO_EMPTY); + if(isset($_args_[0]) && isset($_commands_[$_args_[0]])) + { + $_command_=$_runner_->createCommand($_args_[0]); + array_shift($_args_); + $_command_->init(); + $_command_->run($_args_); + } + else + echo eval($_line_.';'); + } + catch(Exception $e) + { + if($e instanceof ShellException) + echo $e->getMessage(); + else + echo $e; + } + } + } +} + +class ShellException extends CException +{ +} \ No newline at end of file diff --git a/framework/yiic.php b/framework/yiic.php index a6b205d..9b38690 100644 --- a/framework/yiic.php +++ b/framework/yiic.php @@ -13,7 +13,7 @@ defined('STDIN') or define('STDIN', fopen('php://stdin', 'r')); require(__DIR__ . '/yii.php'); $config = array( - 'controllerPath' => '@yii/console/commands', + 'controllerPath' => '@yii/console/controllers', ); $id = 'yiic'; $basePath = __DIR__ . '/console';