summaryrefslogtreecommitdiff
path: root/framework/cli
diff options
context:
space:
mode:
Diffstat (limited to 'framework/cli')
-rw-r--r--framework/cli/commands/MessageCommand.php211
-rw-r--r--framework/cli/commands/MigrateCommand.php561
-rw-r--r--framework/cli/commands/ShellCommand.php148
-rw-r--r--framework/cli/commands/WebAppCommand.php129
-rw-r--r--framework/cli/commands/shell/ControllerCommand.php176
-rw-r--r--framework/cli/commands/shell/CrudCommand.php327
-rw-r--r--framework/cli/commands/shell/FormCommand.php123
-rw-r--r--framework/cli/commands/shell/HelpCommand.php78
-rw-r--r--framework/cli/commands/shell/ModelCommand.php488
-rw-r--r--framework/cli/commands/shell/ModuleCommand.php92
-rw-r--r--framework/cli/runtime/.yii0
-rw-r--r--framework/cli/views/shell/controller/controller.php47
-rw-r--r--framework/cli/views/shell/controller/view.php26
-rw-r--r--framework/cli/views/shell/crud/_form.php42
-rw-r--r--framework/cli/views/shell/crud/_search.php35
-rw-r--r--framework/cli/views/shell/crud/_view.php29
-rw-r--r--framework/cli/views/shell/crud/admin.php73
-rw-r--r--framework/cli/views/shell/crud/controller.php190
-rw-r--r--framework/cli/views/shell/crud/create.php27
-rw-r--r--framework/cli/views/shell/crud/index.php31
-rw-r--r--framework/cli/views/shell/crud/test.php47
-rw-r--r--framework/cli/views/shell/crud/update.php31
-rw-r--r--framework/cli/views/shell/crud/view.php39
-rw-r--r--framework/cli/views/shell/form/action.php37
-rw-r--r--framework/cli/views/shell/form/form.php39
-rw-r--r--framework/cli/views/shell/model/fixture.php25
-rw-r--r--framework/cli/views/shell/model/model.php113
-rw-r--r--framework/cli/views/shell/model/test.php21
-rw-r--r--framework/cli/views/shell/module/components/.yii0
-rw-r--r--framework/cli/views/shell/module/controllers/DefaultController.php9
-rw-r--r--framework/cli/views/shell/module/messages/.yii0
-rw-r--r--framework/cli/views/shell/module/models/.yii0
-rw-r--r--framework/cli/views/shell/module/module.php28
-rw-r--r--framework/cli/views/shell/module/views/default/index.php14
-rw-r--r--framework/cli/views/shell/module/views/layouts/.yii0
-rw-r--r--framework/cli/views/webapp/assets/.yii0
-rw-r--r--framework/cli/views/webapp/css/bg.gifbin0 -> 243 bytes
-rw-r--r--framework/cli/views/webapp/css/form.css160
-rw-r--r--framework/cli/views/webapp/css/ie.css36
-rw-r--r--framework/cli/views/webapp/css/main.css229
-rw-r--r--framework/cli/views/webapp/css/print.css29
-rw-r--r--framework/cli/views/webapp/css/screen.css238
-rw-r--r--framework/cli/views/webapp/images/.yii0
-rw-r--r--framework/cli/views/webapp/index-test.php15
-rw-r--r--framework/cli/views/webapp/index.php13
-rw-r--r--framework/cli/views/webapp/protected/.htaccess1
-rw-r--r--framework/cli/views/webapp/protected/commands/shell/.yii0
-rw-r--r--framework/cli/views/webapp/protected/components/Controller.php23
-rw-r--r--framework/cli/views/webapp/protected/components/UserIdentity.php33
-rw-r--r--framework/cli/views/webapp/protected/config/console.php24
-rw-r--r--framework/cli/views/webapp/protected/config/main.php90
-rw-r--r--framework/cli/views/webapp/protected/config/test.php17
-rw-r--r--framework/cli/views/webapp/protected/controllers/SiteController.php103
-rw-r--r--framework/cli/views/webapp/protected/data/schema.mysql.sql28
-rw-r--r--framework/cli/views/webapp/protected/data/schema.sqlite.sql28
-rw-r--r--framework/cli/views/webapp/protected/data/testdrive.dbbin0 -> 3072 bytes
-rw-r--r--framework/cli/views/webapp/protected/extensions/.yii0
-rw-r--r--framework/cli/views/webapp/protected/messages/.yii0
-rw-r--r--framework/cli/views/webapp/protected/migrations/.yii0
-rw-r--r--framework/cli/views/webapp/protected/models/ContactForm.php42
-rw-r--r--framework/cli/views/webapp/protected/models/LoginForm.php77
-rw-r--r--framework/cli/views/webapp/protected/runtime/.yii0
-rw-r--r--framework/cli/views/webapp/protected/tests/WebTestCase.php25
-rw-r--r--framework/cli/views/webapp/protected/tests/bootstrap.php10
-rw-r--r--framework/cli/views/webapp/protected/tests/fixtures/.yii0
-rw-r--r--framework/cli/views/webapp/protected/tests/functional/SiteTest.php47
-rw-r--r--framework/cli/views/webapp/protected/tests/phpunit.xml13
-rw-r--r--framework/cli/views/webapp/protected/tests/report/.yii0
-rw-r--r--framework/cli/views/webapp/protected/tests/unit/.yii0
-rw-r--r--framework/cli/views/webapp/protected/views/layouts/column1.php5
-rw-r--r--framework/cli/views/webapp/protected/views/layouts/column2.php21
-rw-r--r--framework/cli/views/webapp/protected/views/layouts/main.php58
-rw-r--r--framework/cli/views/webapp/protected/views/site/contact.php81
-rw-r--r--framework/cli/views/webapp/protected/views/site/error.php12
-rw-r--r--framework/cli/views/webapp/protected/views/site/index.php16
-rw-r--r--framework/cli/views/webapp/protected/views/site/login.php49
-rw-r--r--framework/cli/views/webapp/protected/views/site/pages/about.php10
-rw-r--r--framework/cli/views/webapp/protected/yiic4
-rw-r--r--framework/cli/views/webapp/protected/yiic.bat16
-rw-r--r--framework/cli/views/webapp/protected/yiic.php7
-rw-r--r--framework/cli/views/webapp/themes/classic/views/.htaccess1
-rw-r--r--framework/cli/views/webapp/themes/classic/views/layouts/.yii0
-rw-r--r--framework/cli/views/webapp/themes/classic/views/site/.yii0
-rw-r--r--framework/cli/views/webapp/themes/classic/views/system/.yii0
84 files changed, 4797 insertions, 0 deletions
diff --git a/framework/cli/commands/MessageCommand.php b/framework/cli/commands/MessageCommand.php
new file mode 100644
index 0000000..410e275
--- /dev/null
+++ b/framework/cli/commands/MessageCommand.php
@@ -0,0 +1,211 @@
+<?php
+/**
+ * MessageCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 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 <qiang.xue@gmail.com>
+ * @version $Id: MessageCommand.php 3394 2011-09-14 21:31:30Z alexander.makarow $
+ * @package system.cli.commands
+ * @since 1.0
+ */
+class MessageCommand extends CConsoleCommand
+{
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ yiic message <config-file>
+
+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.
+
+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;
+
+ $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);
+ }
+ }
+ }
+
+ protected function extractMessages($fileName,$translator)
+ {
+ echo "Extracting messages from $fileName...\n";
+ $subject=file_get_contents($fileName);
+ $n=preg_match_all('/\b'.$translator.'\s*\(\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*,\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*[,\)]/s',$subject,$matches,PREG_SET_ORDER);
+ $messages=array();
+ for($i=0;$i<$n;++$i)
+ {
+ if(($pos=strpos($matches[$i][1],'.'))!==false)
+ $category=substr($matches[$i][1],$pos+1,-1);
+ else
+ $category=substr($matches[$i][1],1,-1);
+ $message=$matches[$i][2];
+ $messages[$category][]=eval("return $message;"); // use eval to eliminate quote escape
+ }
+ return $messages;
+ }
+
+ protected function generateMessageFile($messages,$fileName,$overwrite,$removeOld)
+ {
+ echo "Saving messages to $fileName...";
+ if(is_file($fileName))
+ {
+ $translated=require($fileName);
+ sort($messages);
+ ksort($translated);
+ if(array_keys($translated)==$messages)
+ {
+ echo "nothing new...skipped.\n";
+ return;
+ }
+ $merged=array();
+ $untranslated=array();
+ foreach($messages as $message)
+ {
+ if(!empty($translated[$message]))
+ $merged[$message]=$translated[$message];
+ else
+ $untranslated[]=$message;
+ }
+ ksort($merged);
+ sort($untranslated);
+ $todo=array();
+ foreach($untranslated as $message)
+ $todo[$message]='';
+ ksort($translated);
+ foreach($translated as $message=>$translation)
+ {
+ if(!isset($merged[$message]) && !isset($todo[$message]) && !$removeOld)
+ $todo[$message]='@@'.$translation.'@@';
+ }
+ $merged=array_merge($todo,$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=<<<EOD
+<?php
+/**
+ * Message translations.
+ *
+ * This file is automatically generated by 'yiic message' command.
+ * It contains the localizable messages extracted from source code.
+ * You may modify this file by translating the extracted messages.
+ *
+ * Each array element represents the translation (value) of a message (key).
+ * If the value is empty, the message is considered as not translated.
+ * Messages that no longer need translation will have their translations
+ * enclosed between a pair of '@@' marks.
+ *
+ * Message string can be used with plural forms format. Check i18n section
+ * of the guide for details.
+ *
+ * NOTE, this file must be saved in UTF-8 encoding.
+ *
+ * @version \$Id: \$
+ */
+return $array;
+
+EOD;
+ file_put_contents($fileName, $content);
+ }
+} \ No newline at end of file
diff --git a/framework/cli/commands/MigrateCommand.php b/framework/cli/commands/MigrateCommand.php
new file mode 100644
index 0000000..5111b9a
--- /dev/null
+++ b/framework/cli/commands/MigrateCommand.php
@@ -0,0 +1,561 @@
+<?php
+/**
+ * MigrateCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 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 <qiang.xue@gmail.com>
+ * @version $Id: MigrateCommand.php 3514 2011-12-27 20:28:26Z alexander.makarow $
+ * @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::app()->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 <<<EOD
+USAGE
+ yiic migrate [action] [parameter]
+
+DESCRIPTION
+ This command provides support for database migrations. The optional
+ 'action' parameter specifies which specific migration task to perform.
+ It can take these values: up, down, to, create, history, new, mark.
+ If the 'action' parameter is not given, it defaults to 'up'.
+ Each action takes different parameters. Their usage can be found in
+ the following examples.
+
+EXAMPLES
+ * yiic migrate
+ Applies ALL new migrations. This is equivalent to 'yiic migrate up'.
+
+ * yiic migrate create create_user_table
+ Creates a new migration named 'create_user_table'.
+
+ * yiic migrate up 3
+ Applies the next 3 new migrations.
+
+ * yiic migrate down
+ Reverts the last applied migration.
+
+ * yiic migrate down 3
+ Reverts the last 3 applied migrations.
+
+ * yiic migrate to 101129_185401
+ Migrates up or down to version 101129_185401.
+
+ * yiic migrate mark 101129_185401
+ Modifies the migration history up or down to version 101129_185401.
+ No actual migration will be performed.
+
+ * yiic migrate history
+ Shows all previously applied migration information.
+
+ * yiic migrate history 10
+ Shows the last 10 applied migrations.
+
+ * yiic migrate new
+ Shows all new migrations.
+
+ * yiic migrate new 10
+ Shows the next 10 migrations that have not been applied.
+
+EOD;
+ }
+
+ protected function getTemplate()
+ {
+ if($this->templateFile!==null)
+ return file_get_contents(Yii::getPathOfAlias($this->templateFile).'.php');
+ else
+ return <<<EOD
+<?php
+
+class {ClassName} extends CDbMigration
+{
+ public function up()
+ {
+ }
+
+ public function down()
+ {
+ echo "{ClassName} does not support migration down.\\n";
+ return false;
+ }
+
+ /*
+ // Use safeUp/safeDown to do migration with transaction
+ public function safeUp()
+ {
+ }
+
+ public function safeDown()
+ {
+ }
+ */
+}
+EOD;
+ }
+}
diff --git a/framework/cli/commands/ShellCommand.php b/framework/cli/commands/ShellCommand.php
new file mode 100644
index 0000000..e90d9bf
--- /dev/null
+++ b/framework/cli/commands/ShellCommand.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * ShellCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @version $Id: ShellCommand.php 3477 2011-12-06 22:33:37Z alexander.makarow $
+ */
+
+/**
+ * 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 <qiang.xue@gmail.com>
+ * @version $Id: ShellCommand.php 3477 2011-12-06 22:33:37Z alexander.makarow $
+ * @package system.cli.commands
+ * @since 1.0
+ */
+class ShellCommand extends CConsoleCommand
+{
+ /**
+ * @return string the help information for the shell command
+ */
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ yiic shell [entry-script | config-file]
+
+DESCRIPTION
+ This command allows you to interact with a Web application
+ on the command line. It also provides tools to automatically
+ generate new controllers, views and data models.
+
+ It is recommended that you execute this command under
+ the directory that contains the entry script file of
+ the Web application.
+
+PARAMETERS
+ * entry-script | config-file: optional, the path to
+ the entry script file or the configuration file for
+ the Web application. If not given, it is assumed to be
+ the 'index.php' file under the current directory.
+
+EOD;
+ }
+
+ /**
+ * Execute the action.
+ * @param array $args command line parameters specific for this command
+ */
+ public function run($args)
+ {
+ if(!isset($args[0]))
+ $args[0]='index.php';
+ $entryScript=isset($args[0]) ? $args[0] : 'index.php';
+ if(($entryScript=realpath($args[0]))===false || !is_file($entryScript))
+ $this->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 <<<EOD
+Yii Interactive Tool v1.1 (based on Yii v{$yiiVersion})
+Please type 'help' for help. Type 'exit' to quit.
+EOD;
+ $this->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::app()->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/cli/commands/WebAppCommand.php b/framework/cli/commands/WebAppCommand.php
new file mode 100644
index 0000000..f5a94f1
--- /dev/null
+++ b/framework/cli/commands/WebAppCommand.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * WebAppCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @version $Id: WebAppCommand.php 3477 2011-12-06 22:33:37Z alexander.makarow $
+ */
+
+/**
+ * WebAppCommand creates an Yii Web application at the specified location.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: WebAppCommand.php 3477 2011-12-06 22:33:37Z alexander.makarow $
+ * @package system.cli.commands
+ * @since 1.0
+ */
+class WebAppCommand extends CConsoleCommand
+{
+ private $_rootPath;
+
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ yiic webapp <app-path>
+
+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/cli/commands/shell/ControllerCommand.php b/framework/cli/commands/shell/ControllerCommand.php
new file mode 100644
index 0000000..f7447b6
--- /dev/null
+++ b/framework/cli/commands/shell/ControllerCommand.php
@@ -0,0 +1,176 @@
+<?php
+/**
+ * ControllerCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @version $Id: ControllerCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $
+ */
+
+/**
+ * ControllerCommand generates a controller class.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: ControllerCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $
+ * @package system.cli.commands.shell
+ * @since 1.0
+ */
+class ControllerCommand extends CConsoleCommand
+{
+ /**
+ * @var string the directory that contains templates for the model command.
+ * Defaults to null, meaning using 'framework/cli/views/shell/controller'.
+ * If you set this path and some views are missing in the directory,
+ * the default views will be used.
+ */
+ public $templatePath;
+
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ controller <controller-ID> [action-ID] ...
+
+DESCRIPTION
+ This command generates a controller and views associated with
+ the specified actions.
+
+PARAMETERS
+ * controller-ID: required, controller ID, e.g., 'post'.
+ If the controller should be located under a subdirectory,
+ please specify the controller ID as 'path/to/ControllerID',
+ e.g., 'admin/user'.
+
+ If the controller belongs to a module, please specify
+ the controller ID as 'ModuleID/ControllerID' or
+ 'ModuleID/path/to/Controller' (assuming the controller is
+ under a subdirectory of that module).
+
+ * action-ID: optional, action ID. You may supply one or several
+ action IDs. A default 'index' action will always be generated.
+
+EXAMPLES
+ * Generates the 'post' controller:
+ controller post
+
+ * Generates the 'post' controller with additional actions 'contact'
+ and 'about':
+ controller post contact about
+
+ * Generates the 'post' controller which should be located under
+ the 'admin' subdirectory of the base controller path:
+ controller admin/post
+
+ * Generates the 'post' controller which should belong to
+ the 'admin' module:
+ controller admin/post
+
+NOTE: in the last two examples, the commands are the same, but
+the generated controller file is located under different directories.
+Yii is able to detect whether 'admin' refers to a module or a subdirectory.
+
+EOD;
+ }
+
+ /**
+ * Execute the action.
+ * @param array command line parameters specific for this command
+ */
+ public function run($args)
+ {
+ if(!isset($args[0]))
+ {
+ echo "Error: controller name is required.\n";
+ echo $this->getHelp();
+ return;
+ }
+
+ $module=Yii::app();
+ $controllerID=$args[0];
+ if(($pos=strrpos($controllerID,'/'))===false)
+ {
+ $controllerClass=ucfirst($controllerID).'Controller';
+ $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php';
+ $controllerID[0]=strtolower($controllerID[0]);
+ }
+ else
+ {
+ $last=substr($controllerID,$pos+1);
+ $last[0]=strtolower($last[0]);
+ $pos2=strpos($controllerID,'/');
+ $first=substr($controllerID,0,$pos2);
+ $middle=$pos===$pos2?'':substr($controllerID,$pos2+1,$pos-$pos2);
+
+ $controllerClass=ucfirst($last).'Controller';
+ $controllerFile=($middle===''?'':$middle.'/').$controllerClass.'.php';
+ $controllerID=$middle===''?$last:$middle.'/'.$last;
+ if(($m=Yii::app()->getModule($first))!==null)
+ $module=$m;
+ else
+ {
+ $controllerFile=$first.'/'.$controllerClass.'.php';
+ $controllerID=$first.'/'.$controllerID;
+ }
+
+ $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$controllerFile);
+ }
+
+ $args[]='index';
+ $actions=array_unique(array_splice($args,1));
+
+ $templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/controller':$this->templatePath;
+
+ $list=array(
+ basename($controllerFile)=>array(
+ 'source'=>$templatePath.DIRECTORY_SEPARATOR.'controller.php',
+ 'target'=>$controllerFile,
+ 'callback'=>array($this,'generateController'),
+ 'params'=>array($controllerClass, $actions),
+ ),
+ );
+
+ $viewPath=$module->viewPath.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$controllerID);
+ foreach($actions as $name)
+ {
+ $list[$name.'.php']=array(
+ 'source'=>$templatePath.DIRECTORY_SEPARATOR.'view.php',
+ 'target'=>$viewPath.DIRECTORY_SEPARATOR.$name.'.php',
+ 'callback'=>array($this,'generateAction'),
+ 'params'=>array('controller'=>$controllerClass, 'action'=>$name),
+ );
+ }
+
+ $this->copyFiles($list);
+
+ if($module instanceof CWebModule)
+ $moduleID=$module->id.'/';
+ else
+ $moduleID='';
+
+ echo <<<EOD
+
+Controller '{$controllerID}' has been created in the following file:
+ $controllerFile
+
+You may access it in the browser using the following URL:
+ http://hostname/path/to/index.php?r={$moduleID}{$controllerID}
+
+EOD;
+ }
+
+ public function generateController($source,$params)
+ {
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/controller/'.basename($source);
+ return $this->renderFile($source,array('className'=>$params[0],'actions'=>$params[1]),true);
+ }
+
+ public function generateAction($source,$params)
+ {
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/controller/'.basename($source);
+ return $this->renderFile($source,$params,true);
+ }
+} \ No newline at end of file
diff --git a/framework/cli/commands/shell/CrudCommand.php b/framework/cli/commands/shell/CrudCommand.php
new file mode 100644
index 0000000..5932dea
--- /dev/null
+++ b/framework/cli/commands/shell/CrudCommand.php
@@ -0,0 +1,327 @@
+<?php
+/**
+ * CrudCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @version $Id: CrudCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $
+ */
+
+/**
+ * CrudCommand generates code implementing CRUD operations.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: CrudCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $
+ * @package system.cli.commands.shell
+ * @since 1.0
+ */
+class CrudCommand extends CConsoleCommand
+{
+ /**
+ * @var string the directory that contains templates for crud commands.
+ * Defaults to null, meaning using 'framework/cli/views/shell/crud'.
+ * If you set this path and some views are missing in the directory,
+ * the default views will be used.
+ */
+ public $templatePath;
+ /**
+ * @var string the directory that contains functional test classes.
+ * Defaults to null, meaning using 'protected/tests/functional'.
+ * If this is false, it means functional test file should NOT be generated.
+ */
+ public $functionalTestPath;
+ /**
+ * @var array list of actions to be created. Each action must be associated with a template file with the same name.
+ */
+ public $actions=array('create','update','index','view','admin','_form','_view','_search');
+
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ crud <model-class> [controller-ID] ...
+
+DESCRIPTION
+ This command generates a controller and views that accomplish
+ CRUD operations for the specified data model.
+
+PARAMETERS
+ * model-class: required, the name of the data model class. This can
+ also be specified as a path alias (e.g. application.models.Post).
+ If the model class belongs to a module, it should be specified
+ as 'ModuleID.models.ClassName'.
+
+ * controller-ID: optional, the controller ID (e.g. 'post').
+ If this is not specified, the model class name will be used
+ as the controller ID. In this case, if the model belongs to
+ a module, the controller will also be created under the same
+ module.
+
+ If the controller should be located under a subdirectory,
+ please specify the controller ID as 'path/to/ControllerID'
+ (e.g. 'admin/user').
+
+ If the controller belongs to a module (different from the module
+ that the model belongs to), please specify the controller ID
+ as 'ModuleID/ControllerID' or 'ModuleID/path/to/Controller'.
+
+EXAMPLES
+ * Generates CRUD for the Post model:
+ crud Post
+
+ * Generates CRUD for the Post model which belongs to module 'admin':
+ crud admin.models.Post
+
+ * Generates CRUD for the Post model. The generated controller should
+ belong to module 'admin', but not the model class:
+ crud Post admin/post
+
+EOD;
+ }
+
+ /**
+ * Execute the action.
+ * @param array command line parameters specific for this command
+ */
+ public function run($args)
+ {
+ if(!isset($args[0]))
+ {
+ echo "Error: data model class is required.\n";
+ echo $this->getHelp();
+ return;
+ }
+ $module=Yii::app();
+ $modelClass=$args[0];
+ if(($pos=strpos($modelClass,'.'))===false)
+ $modelClass='application.models.'.$modelClass;
+ else
+ {
+ $id=substr($modelClass,0,$pos);
+ if(($m=Yii::app()->getModule($id))!==null)
+ $module=$m;
+ }
+ $modelClass=Yii::import($modelClass);
+
+ if(isset($args[1]))
+ {
+ $controllerID=$args[1];
+ if(($pos=strrpos($controllerID,'/'))===false)
+ {
+ $controllerClass=ucfirst($controllerID).'Controller';
+ $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php';
+ $controllerID[0]=strtolower($controllerID[0]);
+ }
+ else
+ {
+ $last=substr($controllerID,$pos+1);
+ $last[0]=strtolower($last);
+ $pos2=strpos($controllerID,'/');
+ $first=substr($controllerID,0,$pos2);
+ $middle=$pos===$pos2?'':substr($controllerID,$pos2+1,$pos-$pos2);
+
+ $controllerClass=ucfirst($last).'Controller';
+ $controllerFile=($middle===''?'':$middle.'/').$controllerClass.'.php';
+ $controllerID=$middle===''?$last:$middle.'/'.$last;
+ if(($m=Yii::app()->getModule($first))!==null)
+ $module=$m;
+ else
+ {
+ $controllerFile=$first.'/'.$controllerFile;
+ $controllerID=$first.'/'.$controllerID;
+ }
+
+ $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.str_replace('/',DIRECTORY_SEPARATOR,$controllerFile);
+ }
+ }
+ else
+ {
+ $controllerID=$modelClass;
+ $controllerClass=ucfirst($controllerID).'Controller';
+ $controllerFile=$module->controllerPath.DIRECTORY_SEPARATOR.$controllerClass.'.php';
+ $controllerID[0]=strtolower($controllerID[0]);
+ }
+
+ $templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/crud':$this->templatePath;
+ $functionalTestPath=$this->functionalTestPath===null?Yii::getPathOfAlias('application.tests.functional'):$this->functionalTestPath;
+
+ $viewPath=$module->viewPath.DIRECTORY_SEPARATOR.str_replace('.',DIRECTORY_SEPARATOR,$controllerID);
+ $fixtureName=$this->pluralize($modelClass);
+ $fixtureName[0]=strtolower($fixtureName);
+ $list=array(
+ basename($controllerFile)=>array(
+ 'source'=>$templatePath.'/controller.php',
+ 'target'=>$controllerFile,
+ 'callback'=>array($this,'generateController'),
+ 'params'=>array($controllerClass,$modelClass),
+ ),
+ );
+
+ if($functionalTestPath!==false)
+ {
+ $list[$modelClass.'Test.php']=array(
+ 'source'=>$templatePath.'/test.php',
+ 'target'=>$functionalTestPath.DIRECTORY_SEPARATOR.$modelClass.'Test.php',
+ 'callback'=>array($this,'generateTest'),
+ 'params'=>array($controllerID,$fixtureName,$modelClass),
+ );
+ }
+
+ foreach($this->actions as $action)
+ {
+ $list[$action.'.php']=array(
+ 'source'=>$templatePath.'/'.$action.'.php',
+ 'target'=>$viewPath.'/'.$action.'.php',
+ 'callback'=>array($this,'generateView'),
+ 'params'=>$modelClass,
+ );
+ }
+
+ $this->copyFiles($list);
+
+ if($module instanceof CWebModule)
+ $moduleID=$module->id.'/';
+ else
+ $moduleID='';
+
+ echo "\nCrud '{$controllerID}' has been successfully created. You may access it via:\n";
+ echo "http://hostname/path/to/index.php?r={$moduleID}{$controllerID}\n";
+ }
+
+ public function generateController($source,$params)
+ {
+ list($controllerClass,$modelClass)=$params;
+ $model=CActiveRecord::model($modelClass);
+ $id=$model->tableSchema->primaryKey;
+ if($id===null)
+ throw new ShellException(Yii::t('yii','Error: Table "{table}" does not have a primary key.',array('{table}'=>$model->tableName())));
+ else if(is_array($id))
+ throw new ShellException(Yii::t('yii','Error: Table "{table}" has a composite primary key which is not supported by crud command.',array('{table}'=>$model->tableName())));
+
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
+
+ return $this->renderFile($source,array(
+ 'ID'=>$id,
+ 'controllerClass'=>$controllerClass,
+ 'modelClass'=>$modelClass,
+ ),true);
+ }
+
+ public function generateView($source,$modelClass)
+ {
+ $model=CActiveRecord::model($modelClass);
+ $table=$model->getTableSchema();
+ $columns=$table->columns;
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
+ return $this->renderFile($source,array(
+ 'ID'=>$table->primaryKey,
+ 'modelClass'=>$modelClass,
+ 'columns'=>$columns),true);
+ }
+
+ public function generateTest($source,$params)
+ {
+ list($controllerID,$fixtureName,$modelClass)=$params;
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/crud/'.basename($source);
+ return $this->renderFile($source, array(
+ 'controllerID'=>$controllerID,
+ 'fixtureName'=>$fixtureName,
+ 'modelClass'=>$modelClass,
+ ),true);
+ }
+
+ public function generateInputLabel($modelClass,$column)
+ {
+ return "CHtml::activeLabelEx(\$model,'{$column->name}')";
+ }
+
+ public function generateInputField($modelClass,$column)
+ {
+ if($column->type==='boolean')
+ return "CHtml::activeCheckBox(\$model,'{$column->name}')";
+ else if(stripos($column->dbType,'text')!==false)
+ return "CHtml::activeTextArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))";
+ else
+ {
+ if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name))
+ $inputField='activePasswordField';
+ else
+ $inputField='activeTextField';
+
+ if($column->type!=='string' || $column->size===null)
+ return "CHtml::{$inputField}(\$model,'{$column->name}')";
+ else
+ {
+ if(($size=$maxLength=$column->size)>60)
+ $size=60;
+ return "CHtml::{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))";
+ }
+ }
+ }
+
+ public function generateActiveLabel($modelClass,$column)
+ {
+ return "\$form->labelEx(\$model,'{$column->name}')";
+ }
+
+ public function generateActiveField($modelClass,$column)
+ {
+ if($column->type==='boolean')
+ return "\$form->checkBox(\$model,'{$column->name}')";
+ else if(stripos($column->dbType,'text')!==false)
+ return "\$form->textArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))";
+ else
+ {
+ if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name))
+ $inputField='passwordField';
+ else
+ $inputField='textField';
+
+ if($column->type!=='string' || $column->size===null)
+ return "\$form->{$inputField}(\$model,'{$column->name}')";
+ else
+ {
+ if(($size=$maxLength=$column->size)>60)
+ $size=60;
+ return "\$form->{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))";
+ }
+ }
+ }
+
+ public function guessNameColumn($columns)
+ {
+ foreach($columns as $column)
+ {
+ if(!strcasecmp($column->name,'name'))
+ return $column->name;
+ }
+ foreach($columns as $column)
+ {
+ if(!strcasecmp($column->name,'title'))
+ return $column->name;
+ }
+ foreach($columns as $column)
+ {
+ if($column->isPrimaryKey)
+ return $column->name;
+ }
+ return 'id';
+ }
+
+ public function class2id($className)
+ {
+ return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $className))),'-');
+ }
+
+ public function class2name($className,$pluralize=false)
+ {
+ if($pluralize)
+ $className=$this->pluralize($className);
+ return ucwords(trim(strtolower(str_replace(array('-','_'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $className)))));
+ }
+}
diff --git a/framework/cli/commands/shell/FormCommand.php b/framework/cli/commands/shell/FormCommand.php
new file mode 100644
index 0000000..c3f064f
--- /dev/null
+++ b/framework/cli/commands/shell/FormCommand.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * FormCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @version $Id: FormCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $
+ */
+
+/**
+ * FormCommand generates a form view based on a specified model.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: FormCommand.php 2799 2011-01-01 19:31:13Z qiang.xue $
+ * @package system.cli.commands.shell
+ * @since 1.0
+ */
+class FormCommand extends CConsoleCommand
+{
+ /**
+ * @var string the directory that contains templates for the form command.
+ * Defaults to null, meaning using 'framework/cli/views/shell/form'.
+ * If you set this path and some views are missing in the directory,
+ * the default views will be used.
+ */
+ public $templatePath;
+
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ form <model-class> <view-name> [scenario]
+
+DESCRIPTION
+ This command generates a form view that can be used to collect inputs
+ for the specified model.
+
+PARAMETERS
+ * model-class: required, model class. This can be either the name of
+ the model class (e.g. 'ContactForm') or the path alias of the model
+ class file (e.g. 'application.models.ContactForm'). The former can
+ be used only if the class can be autoloaded.
+
+ * view-name: required, the name of the view to be generated. This should
+ be the path alias of the view script (e.g. 'application.views.site.contact').
+
+ * scenario: optional, the name of the scenario in which the model is used
+ (e.g. 'update', 'login'). This determines which model attributes the
+ generated form view will be used to collect user inputs for. If this
+ is not provided, the scenario will be assumed to be '' (empty string).
+
+EXAMPLES
+ * Generates the view script for the 'ContactForm' model:
+ form ContactForm application.views.site.contact
+
+EOD;
+ }
+
+ /**
+ * Execute the action.
+ * @param array command line parameters specific for this command
+ */
+ public function run($args)
+ {
+ if(!isset($args[0],$args[1]))
+ {
+ echo "Error: both model class and view name are required.\n";
+ echo $this->getHelp();
+ return;
+ }
+ $scenario=isset($args[2]) ? $args[2] : '';
+ $modelClass=Yii::import($args[0],true);
+ $model=new $modelClass($scenario);
+ $attributes=$model->getSafeAttributeNames();
+
+ $templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/form':$this->templatePath;
+ $viewPath=Yii::getPathOfAlias($args[1]);
+ $viewName=basename($viewPath);
+ $viewPath.='.php';
+ $params=array(
+ 'modelClass'=>$modelClass,
+ 'viewName'=>$viewName,
+ 'attributes'=>$attributes,
+ );
+ $list=array(
+ basename($viewPath)=>array(
+ 'source'=>$templatePath.'/form.php',
+ 'target'=>$viewPath,
+ 'callback'=>array($this,'generateForm'),
+ 'params'=>$params,
+ ),
+ );
+
+ $this->copyFiles($list);
+
+ $actionFile=$templatePath.'/action.php';
+ if(!is_file($actionFile)) // fall back to default ones
+ $actionFile=YII_PATH.'/cli/views/shell/form/action.php';
+
+ echo "The following form view has been successfully created:\n";
+ echo "\t$viewPath\n\n";
+ echo "You may use the following code in your controller action:\n\n";
+ echo $this->renderFile($actionFile,$params,true);
+ echo "\n";
+ }
+
+ public function generateForm($source,$params)
+ {
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/form/'.basename($source);
+
+ return $this->renderFile($source,$params,true);
+ }
+
+ public function class2id($className)
+ {
+ if(strrpos($className,'Form')===strlen($className)-4)
+ $className=substr($className,0,strlen($className)-4);
+ return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $className))),'-');
+ }
+} \ No newline at end of file
diff --git a/framework/cli/commands/shell/HelpCommand.php b/framework/cli/commands/shell/HelpCommand.php
new file mode 100644
index 0000000..8007373
--- /dev/null
+++ b/framework/cli/commands/shell/HelpCommand.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * HelpCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @version $Id: HelpCommand.php 3426 2011-10-25 00:01:09Z alexander.makarow $
+ */
+
+/**
+ * HelpCommand displays help information for commands under yiic shell.
+ *
+ * @property string $help The command description.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: HelpCommand.php 3426 2011-10-25 00:01:09Z alexander.makarow $
+ * @package system.cli.commands.shell
+ * @since 1.0
+ */
+class HelpCommand extends CConsoleCommand
+{
+ /**
+ * Execute the action.
+ * @param array command line parameters specific for this command
+ */
+ public function run($args)
+ {
+ $runner=$this->getCommandRunner();
+ $commands=$runner->commands;
+ if(isset($args[0]))
+ $name=strtolower($args[0]);
+ if(!isset($args[0]) || !isset($commands[$name]))
+ {
+ echo <<<EOD
+At the prompt, you may enter a PHP statement or one of the following commands:
+
+EOD;
+ $commandNames=array_keys($commands);
+ sort($commandNames);
+ echo ' - '.implode("\n - ",$commandNames);
+ echo <<<EOD
+
+
+Type 'help <command-name>' for details about a command.
+
+To expand the above command list, place your command class files
+under 'protected/commands/shell', or a directory specified
+by the 'YIIC_SHELL_COMMAND_PATH' environment variable. The command class
+must extend from CConsoleCommand.
+
+EOD;
+ }
+ else
+ echo $runner->createCommand($name)->getHelp();
+ }
+
+ /**
+ * Provides the command description.
+ * @return string the command description.
+ */
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ help [command-name]
+
+DESCRIPTION
+ Display the help information for the specified command.
+ If the command name is not given, all commands will be listed.
+
+PARAMETERS
+ * command-name: optional, the name of the command to show help information.
+
+EOD;
+ }
+} \ No newline at end of file
diff --git a/framework/cli/commands/shell/ModelCommand.php b/framework/cli/commands/shell/ModelCommand.php
new file mode 100644
index 0000000..cfd8c21
--- /dev/null
+++ b/framework/cli/commands/shell/ModelCommand.php
@@ -0,0 +1,488 @@
+<?php
+/**
+ * ModelCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @version $Id: ModelCommand.php 3477 2011-12-06 22:33:37Z alexander.makarow $
+ */
+
+/**
+ * ModelCommand generates a model class.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: ModelCommand.php 3477 2011-12-06 22:33:37Z alexander.makarow $
+ * @package system.cli.commands.shell
+ * @since 1.0
+ */
+class ModelCommand extends CConsoleCommand
+{
+ /**
+ * @var string the directory that contains templates for the model command.
+ * Defaults to null, meaning using 'framework/cli/views/shell/model'.
+ * If you set this path and some views are missing in the directory,
+ * the default views will be used.
+ */
+ public $templatePath;
+ /**
+ * @var string the directory that contains test fixtures.
+ * Defaults to null, meaning using 'protected/tests/fixtures'.
+ * If this is false, it means fixture file should NOT be generated.
+ */
+ public $fixturePath;
+ /**
+ * @var string the directory that contains unit test classes.
+ * Defaults to null, meaning using 'protected/tests/unit'.
+ * If this is false, it means unit test file should NOT be generated.
+ */
+ public $unitTestPath;
+
+ private $_schema;
+ private $_relations; // where we keep table relations
+ private $_tables;
+ private $_classes;
+
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ model <class-name> [table-name]
+
+DESCRIPTION
+ This command generates a model class with the specified class name.
+
+PARAMETERS
+ * class-name: required, model class name. By default, the generated
+ model class file will be placed under the directory aliased as
+ 'application.models'. To override this default, specify the class
+ name in terms of a path alias, e.g., 'application.somewhere.ClassName'.
+
+ If the model class belongs to a module, it should be specified
+ as 'ModuleID.models.ClassName'.
+
+ If the class name ends with '*', then a model class will be generated
+ for EVERY table in the database.
+
+ If the class name contains a regular expression deliminated by slashes,
+ then a model class will be generated for those tables whose name
+ matches the regular expression. If the regular expression contains
+ sub-patterns, the first sub-pattern will be used to generate the model
+ class name.
+
+ * table-name: optional, the associated database table name. If not given,
+ it is assumed to be the model class name.
+
+ Note, when the class name ends with '*', this parameter will be
+ ignored.
+
+EXAMPLES
+ * Generates the Post model:
+ model Post
+
+ * Generates the Post model which is associated with table 'posts':
+ model Post posts
+
+ * Generates the Post model which should belong to module 'admin':
+ model admin.models.Post
+
+ * Generates a model class for every table in the current database:
+ model *
+
+ * Same as above, but the model class files should be generated
+ under 'protected/models2':
+ model application.models2.*
+
+ * Generates a model class for every table whose name is prefixed
+ with 'tbl_' in the current database. The model class will not
+ contain the table prefix.
+ model /^tbl_(.*)$/
+
+ * Same as above, but the model class files should be generated
+ under 'protected/models2':
+ model application.models2./^tbl_(.*)$/
+
+EOD;
+ }
+
+ /**
+ * Checks if the given table is a "many to many" helper table.
+ * Their PK has 2 fields, and both of those fields are also FK to other separate tables.
+ * @param CDbTableSchema table to inspect
+ * @return boolean true if table matches description of helpter table.
+ */
+ protected function isRelationTable($table)
+ {
+ $pk=$table->primaryKey;
+ return (count($pk) === 2 // we want 2 columns
+ && isset($table->foreignKeys[$pk[0]]) // pk column 1 is also a foreign key
+ && isset($table->foreignKeys[$pk[1]]) // pk column 2 is also a foriegn key
+ && $table->foreignKeys[$pk[0]][0] !== $table->foreignKeys[$pk[1]][0]); // and the foreign keys point different tables
+ }
+
+ /**
+ * Generate code to put in ActiveRecord class's relations() function.
+ * @return array indexed by table names, each entry contains array of php code to go in appropriate ActiveRecord class.
+ * Empty array is returned if database couldn't be connected.
+ */
+ protected function generateRelations()
+ {
+ $this->_relations=array();
+ $this->_classes=array();
+ foreach($this->_schema->getTables() as $table)
+ {
+ $tableName=$table->name;
+
+ if ($this->isRelationTable($table))
+ {
+ $pks=$table->primaryKey;
+ $fks=$table->foreignKeys;
+
+ $table0=$fks[$pks[1]][0];
+ $table1=$fks[$pks[0]][0];
+ $className0=$this->getClassName($table0);
+ $className1=$this->getClassName($table1);
+
+ $unprefixedTableName=$this->removePrefix($tableName,true);
+
+ $relationName=$this->generateRelationName($table0, $table1, true);
+ $this->_relations[$className0][$relationName]="array(self::MANY_MANY, '$className1', '$unprefixedTableName($pks[0], $pks[1])')";
+
+ $relationName=$this->generateRelationName($table1, $table0, true);
+ $this->_relations[$className1][$relationName]="array(self::MANY_MANY, '$className0', '$unprefixedTableName($pks[0], $pks[1])')";
+ }
+ else
+ {
+ $this->_classes[$tableName]=$className=$this->getClassName($tableName);
+ foreach ($table->foreignKeys as $fkName => $fkEntry)
+ {
+ // Put table and key name in variables for easier reading
+ $refTable=$fkEntry[0]; // Table name that current fk references to
+ $refKey=$fkEntry[1]; // Key in that table being referenced
+ $refClassName=$this->getClassName($refTable);
+
+ // Add relation for this table
+ $relationName=$this->generateRelationName($tableName, $fkName, false);
+ $this->_relations[$className][$relationName]="array(self::BELONGS_TO, '$refClassName', '$fkName')";
+
+ // Add relation for the referenced table
+ $relationType=$table->primaryKey === $fkName ? 'HAS_ONE' : 'HAS_MANY';
+ $relationName=$this->generateRelationName($refTable, $this->removePrefix($tableName), $relationType==='HAS_MANY');
+ $this->_relations[$refClassName][$relationName]="array(self::$relationType, '$className', '$fkName')";
+ }
+ }
+ }
+ }
+
+ protected function getClassName($tableName)
+ {
+ return isset($this->_tables[$tableName]) ? $this->_tables[$tableName] : $this->generateClassName($tableName);
+ }
+
+ /**
+ * Generates model class name based on a table name
+ * @param string the table name
+ * @return string the generated model class name
+ */
+ protected function generateClassName($tableName)
+ {
+ return str_replace(' ','',
+ ucwords(
+ trim(
+ strtolower(
+ str_replace(array('-','_'),' ',
+ preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $tableName))))));
+ }
+
+ /**
+ * Generates the mapping table between table names and class names.
+ * @param CDbSchema the database schema
+ * @param string a regular expression that may be used to filter table names
+ */
+ protected function generateClassNames($schema,$pattern=null)
+ {
+ $this->_tables=array();
+ foreach($schema->getTableNames() as $name)
+ {
+ if($pattern===null)
+ $this->_tables[$name]=$this->generateClassName($this->removePrefix($name));
+ else if(preg_match($pattern,$name,$matches))
+ {
+ if(count($matches)>1 && !empty($matches[1]))
+ $className=$this->generateClassName($matches[1]);
+ else
+ $className=$this->generateClassName($matches[0]);
+ $this->_tables[$name]=empty($className) ? $name : $className;
+ }
+ }
+ }
+
+ /**
+ * Generate a name for use as a relation name (inside relations() function in a model).
+ * @param string the name of the table to hold the relation
+ * @param string the foreign key name
+ * @param boolean whether the relation would contain multiple objects
+ */
+ protected function generateRelationName($tableName, $fkName, $multiple)
+ {
+ if(strcasecmp(substr($fkName,-2),'id')===0 && strcasecmp($fkName,'id'))
+ $relationName=rtrim(substr($fkName, 0, -2),'_');
+ else
+ $relationName=$fkName;
+ $relationName[0]=strtolower($relationName);
+
+ $rawName=$relationName;
+ if($multiple)
+ $relationName=$this->pluralize($relationName);
+
+ $table=$this->_schema->getTable($tableName);
+ $i=0;
+ while(isset($table->columns[$relationName]))
+ $relationName=$rawName.($i++);
+ return $relationName;
+ }
+
+ /**
+ * Execute the action.
+ * @param array command line parameters specific for this command
+ */
+ public function run($args)
+ {
+ if(!isset($args[0]))
+ {
+ echo "Error: model class name is required.\n";
+ echo $this->getHelp();
+ return;
+ }
+ $className=$args[0];
+
+ if(($db=Yii::app()->getDb())===null)
+ {
+ echo "Error: an active 'db' connection is required.\n";
+ echo "If you already added 'db' component in application configuration,\n";
+ echo "please quit and re-enter the yiic shell.\n";
+ return;
+ }
+
+ $db->active=true;
+ $this->_schema=$db->schema;
+
+ if(!preg_match('/^[\w\.\-\*]*(.*?)$/',$className,$matches))
+ {
+ echo "Error: model class name is invalid.\n";
+ return;
+ }
+
+ if(empty($matches[1])) // without regular expression
+ {
+ $this->generateClassNames($this->_schema);
+ if(($pos=strrpos($className,'.'))===false)
+ $basePath=Yii::getPathOfAlias('application.models');
+ else
+ {
+ $basePath=Yii::getPathOfAlias(substr($className,0,$pos));
+ $className=substr($className,$pos+1);
+ }
+ if($className==='*') // generate all models
+ $this->generateRelations();
+ else
+ {
+ $tableName=isset($args[1])?$args[1]:$className;
+ $tableName=$this->addPrefix($tableName);
+ $this->_tables[$tableName]=$className;
+ $this->generateRelations();
+ $this->_classes=array($tableName=>$className);
+ }
+ }
+ else // with regular expression
+ {
+ $pattern=$matches[1];
+ $pos=strrpos($className,$pattern);
+ if($pos>0) // only regexp is given
+ $basePath=Yii::getPathOfAlias(rtrim(substr($className,0,$pos),'.'));
+ else
+ $basePath=Yii::getPathOfAlias('application.models');
+ $this->generateClassNames($this->_schema,$pattern);
+ $classes=$this->_tables;
+ $this->generateRelations();
+ $this->_classes=$classes;
+ }
+
+ if(count($this->_classes)>1)
+ {
+ $entries=array();
+ $count=0;
+ foreach($this->_classes as $tableName=>$className)
+ $entries[]=++$count.". $className ($tableName)";
+ echo "The following model classes (tables) match your criteria:\n";
+ echo implode("\n",$entries)."\n\n";
+ if(!$this->confirm("Do you want to generate the above classes?"))
+ return;
+ }
+
+ $templatePath=$this->templatePath===null?YII_PATH.'/cli/views/shell/model':$this->templatePath;
+ $fixturePath=$this->fixturePath===null?Yii::getPathOfAlias('application.tests.fixtures'):$this->fixturePath;
+ $unitTestPath=$this->unitTestPath===null?Yii::getPathOfAlias('application.tests.unit'):$this->unitTestPath;
+
+ $list=array();
+ $files=array();
+ foreach ($this->_classes as $tableName=>$className)
+ {
+ $files[$className]=$classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
+ $list['models/'.$className.'.php']=array(
+ 'source'=>$templatePath.DIRECTORY_SEPARATOR.'model.php',
+ 'target'=>$classFile,
+ 'callback'=>array($this,'generateModel'),
+ 'params'=>array($className,$tableName),
+ );
+ if($fixturePath!==false)
+ {
+ $list['fixtures/'.$tableName.'.php']=array(
+ 'source'=>$templatePath.DIRECTORY_SEPARATOR.'fixture.php',
+ 'target'=>$fixturePath.DIRECTORY_SEPARATOR.$tableName.'.php',
+ 'callback'=>array($this,'generateFixture'),
+ 'params'=>$this->_schema->getTable($tableName),
+ );
+ }
+ if($unitTestPath!==false)
+ {
+ $fixtureName=$this->pluralize($className);
+ $fixtureName[0]=strtolower($fixtureName);
+ $list['unit/'.$className.'Test.php']=array(
+ 'source'=>$templatePath.DIRECTORY_SEPARATOR.'test.php',
+ 'target'=>$unitTestPath.DIRECTORY_SEPARATOR.$className.'Test.php',
+ 'callback'=>array($this,'generateTest'),
+ 'params'=>array($className,$fixtureName),
+ );
+ }
+ }
+
+ $this->copyFiles($list);
+
+ foreach($files as $className=>$file)
+ {
+ if(!class_exists($className,false))
+ include_once($file);
+ }
+
+ $classes=implode(", ", $this->_classes);
+
+ echo <<<EOD
+
+The following model classes are successfully generated:
+ $classes
+
+If you have a 'db' database connection, you can test these models now with:
+ \$model={$className}::model()->find();
+ print_r(\$model);
+
+EOD;
+ }
+
+ public function generateModel($source,$params)
+ {
+ list($className,$tableName)=$params;
+ $rules=array();
+ $labels=array();
+ $relations=array();
+ if(($table=$this->_schema->getTable($tableName))!==null)
+ {
+ $required=array();
+ $integers=array();
+ $numerical=array();
+ $length=array();
+ $safe=array();
+ foreach($table->columns as $column)
+ {
+ $label=ucwords(trim(strtolower(str_replace(array('-','_'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $column->name)))));
+ $label=preg_replace('/\s+/',' ',$label);
+ if(strcasecmp(substr($label,-3),' id')===0)
+ $label=substr($label,0,-3);
+ $labels[$column->name]=$label;
+ if($column->isPrimaryKey && $table->sequenceName!==null)
+ continue;
+ $r=!$column->allowNull && $column->defaultValue===null;
+ if($r)
+ $required[]=$column->name;
+ if($column->type==='integer')
+ $integers[]=$column->name;
+ else if($column->type==='double')
+ $numerical[]=$column->name;
+ else if($column->type==='string' && $column->size>0)
+ $length[$column->size][]=$column->name;
+ else if(!$column->isPrimaryKey && !$r)
+ $safe[]=$column->name;
+ }
+ if($required!==array())
+ $rules[]="array('".implode(', ',$required)."', 'required')";
+ if($integers!==array())
+ $rules[]="array('".implode(', ',$integers)."', 'numerical', 'integerOnly'=>true)";
+ if($numerical!==array())
+ $rules[]="array('".implode(', ',$numerical)."', 'numerical')";
+ if($length!==array())
+ {
+ foreach($length as $len=>$cols)
+ $rules[]="array('".implode(', ',$cols)."', 'length', 'max'=>$len)";
+ }
+ if($safe!==array())
+ $rules[]="array('".implode(', ',$safe)."', 'safe')";
+
+ if(isset($this->_relations[$className]) && is_array($this->_relations[$className]))
+ $relations=$this->_relations[$className];
+ }
+ else
+ echo "Warning: the table '$tableName' does not exist in the database.\n";
+
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/model/'.basename($source);
+ return $this->renderFile($source,array(
+ 'className'=>$className,
+ 'tableName'=>$this->removePrefix($tableName,true),
+ 'columns'=>isset($table) ? $table->columns : array(),
+ 'rules'=>$rules,
+ 'labels'=>$labels,
+ 'relations'=>$relations,
+ ),true);
+ }
+
+ public function generateFixture($source,$table)
+ {
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/model/'.basename($source);
+ return $this->renderFile($source, array(
+ 'table'=>$table,
+ ),true);
+ }
+
+ public function generateTest($source,$params)
+ {
+ list($className,$fixtureName)=$params;
+ if(!is_file($source)) // fall back to default ones
+ $source=YII_PATH.'/cli/views/shell/model/'.basename($source);
+ return $this->renderFile($source, array(
+ 'className'=>$className,
+ 'fixtureName'=>$fixtureName,
+ ),true);
+ }
+
+ protected function removePrefix($tableName,$addBrackets=false)
+ {
+ $tablePrefix=Yii::app()->getDb()->tablePrefix;
+ if($tablePrefix!='' && !strncmp($tableName,$tablePrefix,strlen($tablePrefix)))
+ {
+ $tableName=substr($tableName,strlen($tablePrefix));
+ if($addBrackets)
+ $tableName='{{'.$tableName.'}}';
+ }
+ return $tableName;
+ }
+
+ protected function addPrefix($tableName)
+ {
+ $tablePrefix=Yii::app()->getDb()->tablePrefix;
+ if($tablePrefix!='' && strncmp($tableName,$tablePrefix,strlen($tablePrefix)))
+ $tableName=$tablePrefix.$tableName;
+ return $tableName;
+ }
+} \ No newline at end of file
diff --git a/framework/cli/commands/shell/ModuleCommand.php b/framework/cli/commands/shell/ModuleCommand.php
new file mode 100644
index 0000000..51a2158
--- /dev/null
+++ b/framework/cli/commands/shell/ModuleCommand.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * ModuleCommand class file.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @version $Id: ModuleCommand.php 433 2008-12-30 22:59:17Z qiang.xue $
+ */
+
+/**
+ * ModuleCommand generates a controller class.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Id: ModuleCommand.php 433 2008-12-30 22:59:17Z qiang.xue $
+ * @package system.cli.commands.shell
+ */
+class ModuleCommand extends CConsoleCommand
+{
+ /**
+ * @var string the directory that contains templates for the module command.
+ * Defaults to null, meaning using 'framework/cli/views/shell/module'.
+ * If you set this path and some views are missing in the directory,
+ * the default views will be used.
+ */
+ public $templatePath;
+
+ public function getHelp()
+ {
+ return <<<EOD
+USAGE
+ module <module-ID>
+
+DESCRIPTION
+ This command generates an application module.
+
+PARAMETERS
+ * module-ID: required, module ID. It is case-sensitive.
+
+EOD;
+ }
+
+ /**
+ * Execute the action.
+ * @param array command line parameters specific for this command
+ */
+ public function run($args)
+ {
+ if(!isset($args[0]))
+ {
+ echo "Error: module ID is required.\n";
+ echo $this->getHelp();
+ return;
+ }
+
+ $moduleID=$args[0];
+ $moduleClass=ucfirst($moduleID).'Module';
+ $modulePath=Yii::app()->getModulePath().DIRECTORY_SEPARATOR.$moduleID;
+
+ $sourceDir=$this->templatePath===null?YII_PATH.'/cli/views/shell/module':$this->templatePath;
+ $list=$this->buildFileList($sourceDir,$modulePath);
+ $list['module.php']['target']=$modulePath.DIRECTORY_SEPARATOR.$moduleClass.'.php';
+ $list['module.php']['callback']=array($this,'generateModuleClass');
+ $list['module.php']['params']=array(
+ 'moduleClass'=>$moduleClass,
+ 'moduleID'=>$moduleID,
+ );
+ $list[$moduleClass.'.php']=$list['module.php'];
+ unset($list['module.php']);
+
+ $this->copyFiles($list);
+
+ echo <<<EOD
+
+Module '{$moduleID}' has been created under the following folder:
+ $modulePath
+
+You may access it in the browser using the following URL:
+ http://hostname/path/to/index.php?r=$moduleID
+
+Note, the module needs to be installed first by adding '{$moduleID}'
+to the 'modules' property in the application configuration.
+
+EOD;
+ }
+
+ public function generateModuleClass($source,$params)
+ {
+ return $this->renderFile($source,$params,true);
+ }
+} \ No newline at end of file
diff --git a/framework/cli/runtime/.yii b/framework/cli/runtime/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/runtime/.yii
diff --git a/framework/cli/views/shell/controller/controller.php b/framework/cli/views/shell/controller/controller.php
new file mode 100644
index 0000000..295fc5f
--- /dev/null
+++ b/framework/cli/views/shell/controller/controller.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * This is the template for generating a controller class file.
+ * The following variables are available in this template:
+ * - $className: the class name of the controller
+ * - $actions: a list of action names for the controller
+ */
+?>
+<?php echo "<?php\n"; ?>
+
+class <?php echo $className; ?> extends Controller
+{
+<?php foreach($actions as $action): ?>
+ public function action<?php echo ucfirst($action); ?>()
+ {
+ $this->render('<?php echo $action; ?>');
+ }
+
+<?php endforeach; ?>
+ // -----------------------------------------------------------
+ // Uncomment the following methods and override them if needed
+ /*
+ public function filters()
+ {
+ // return the filter configuration for this controller, e.g.:
+ return array(
+ 'inlineFilterName',
+ array(
+ 'class'=>'path.to.FilterClass',
+ 'propertyName'=>'propertyValue',
+ ),
+ );
+ }
+
+ public function actions()
+ {
+ // return external action classes, e.g.:
+ return array(
+ 'action1'=>'path.to.ActionClass',
+ 'action2'=>array(
+ 'class'=>'path.to.AnotherActionClass',
+ 'propertyName'=>'propertyValue',
+ ),
+ );
+ }
+ */
+} \ No newline at end of file
diff --git a/framework/cli/views/shell/controller/view.php b/framework/cli/views/shell/controller/view.php
new file mode 100644
index 0000000..0bcc212
--- /dev/null
+++ b/framework/cli/views/shell/controller/view.php
@@ -0,0 +1,26 @@
+<?php
+echo "<?php\n";
+$controller=substr($controller,0,strlen($controller)-10);
+$label=ucwords(trim(strtolower(str_replace(array('-','_','.'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $controller)))));
+
+if($action==='index')
+{
+ echo "\$this->breadcrumbs=array(
+ '$label',
+);";
+}
+else
+{
+ $route=$controller.'/index';
+ $route[0]=strtolower($route[0]);
+ $action=ucfirst($action);
+ echo "\$this->breadcrumbs=array(
+ '$label'=>array('$route'),
+ '$action',
+);";
+}
+?>
+?>
+<h1><?php echo '<?php'; ?> echo $this->id . '/' . $this->action->id; ?></h1>
+
+<p>You may change the content of this page by modifying the file <tt><?php echo '<?php'; ?> echo __FILE__; ?></tt>.</p>
diff --git a/framework/cli/views/shell/crud/_form.php b/framework/cli/views/shell/crud/_form.php
new file mode 100644
index 0000000..e0de820
--- /dev/null
+++ b/framework/cli/views/shell/crud/_form.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * This is the template for generating the form view for crud.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $modelClass: the model class name
+ * - $columns: a list of column schema objects
+ */
+?>
+<div class="form">
+
+<?php echo "<?php \$form=\$this->beginWidget('CActiveForm', array(
+ 'id'=>'".$this->class2id($modelClass)."-form',
+ 'enableAjaxValidation'=>false,
+)); ?>\n"; ?>
+
+ <p class="note">Fields with <span class="required">*</span> are required.</p>
+
+ <?php echo "<?php echo \$form->errorSummary(\$model); ?>\n"; ?>
+
+<?php
+foreach($columns as $column)
+{
+ if($column->isPrimaryKey)
+ continue;
+?>
+ <div class="row">
+ <?php echo "<?php echo ".$this->generateActiveLabel($modelClass,$column)."; ?>\n"; ?>
+ <?php echo "<?php echo ".$this->generateActiveField($modelClass,$column)."; ?>\n"; ?>
+ <?php echo "<?php echo \$form->error(\$model,'{$column->name}'); ?>\n"; ?>
+ </div>
+
+<?php
+}
+?>
+ <div class="row buttons">
+ <?php echo "<?php echo CHtml::submitButton(\$model->isNewRecord ? 'Create' : 'Save'); ?>\n"; ?>
+ </div>
+
+<?php echo "<?php \$this->endWidget(); ?>\n"; ?>
+
+</div><!-- form --> \ No newline at end of file
diff --git a/framework/cli/views/shell/crud/_search.php b/framework/cli/views/shell/crud/_search.php
new file mode 100644
index 0000000..fd81c35
--- /dev/null
+++ b/framework/cli/views/shell/crud/_search.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * This is the template for generating the form view for crud.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $modelClass: the model class name
+ * - $columns: a list of column schema objects
+ */
+?>
+<div class="wide form">
+
+<?php echo "<?php \$form=\$this->beginWidget('CActiveForm', array(
+ 'action'=>Yii::app()->createUrl(\$this->route),
+ 'method'=>'get',
+)); ?>\n"; ?>
+
+<?php foreach($columns as $column): ?>
+<?php
+ $field=$this->generateInputField($modelClass,$column);
+ if(strpos($field,'password')!==false)
+ continue;
+?>
+ <div class="row">
+ <?php echo "<?php echo \$form->label(\$model,'{$column->name}'); ?>\n"; ?>
+ <?php echo "<?php echo ".$this->generateActiveField($modelClass,$column)."; ?>\n"; ?>
+ </div>
+
+<?php endforeach; ?>
+ <div class="row buttons">
+ <?php echo "<?php echo CHtml::submitButton('Search'); ?>\n"; ?>
+ </div>
+
+<?php echo "<?php \$this->endWidget(); ?>\n"; ?>
+
+</div><!-- search-form --> \ No newline at end of file
diff --git a/framework/cli/views/shell/crud/_view.php b/framework/cli/views/shell/crud/_view.php
new file mode 100644
index 0000000..02f383d
--- /dev/null
+++ b/framework/cli/views/shell/crud/_view.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * This is the template for generating the partial view for rendering a single model.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $modelClass: the model class name
+ * - $columns: a list of column schema objects
+ */
+?>
+<div class="view">
+
+<?php
+echo "\t<b><?php echo CHtml::encode(\$data->getAttributeLabel('{$ID}')); ?>:</b>\n";
+echo "\t<?php echo CHtml::link(CHtml::encode(\$data->{$ID}), array('view', 'id'=>\$data->{$ID})); ?>\n\t<br />\n\n";
+$count=0;
+foreach($columns as $column)
+{
+ if($column->isPrimaryKey)
+ continue;
+ if(++$count==7)
+ echo "\t<?php /*\n";
+ echo "\t<b><?php echo CHtml::encode(\$data->getAttributeLabel('{$column->name}')); ?>:</b>\n";
+ echo "\t<?php echo CHtml::encode(\$data->{$column->name}); ?>\n\t<br />\n\n";
+}
+if($count>=7)
+ echo "\t*/ ?>\n";
+?>
+
+</div> \ No newline at end of file
diff --git a/framework/cli/views/shell/crud/admin.php b/framework/cli/views/shell/crud/admin.php
new file mode 100644
index 0000000..ae0bc0f
--- /dev/null
+++ b/framework/cli/views/shell/crud/admin.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * This is the template for generating the admin view for crud.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $modelClass: the model class name
+ * - $columns: a list of column schema objects
+ */
+?>
+<?php
+echo "<?php\n";
+$label=$this->class2name($modelClass,true);
+echo "\$this->breadcrumbs=array(
+ '$label'=>array('index'),
+ 'Manage',
+);\n";
+?>
+
+$this->menu=array(
+ array('label'=>'List <?php echo $modelClass; ?>', 'url'=>array('index')),
+ array('label'=>'Create <?php echo $modelClass; ?>', 'url'=>array('create')),
+);
+
+Yii::app()->clientScript->registerScript('search', "
+$('.search-button').click(function(){
+ $('.search-form').toggle();
+ return false;
+});
+$('.search-form form').submit(function(){
+ $.fn.yiiGridView.update('<?php echo $this->class2id($modelClass); ?>-grid', {
+ data: $(this).serialize()
+ });
+ return false;
+});
+");
+?>
+
+<h1>Manage <?php echo $this->class2name($modelClass,true); ?></h1>
+
+<p>
+You may optionally enter a comparison operator (<b>&lt;</b>, <b>&lt;=</b>, <b>&gt;</b>, <b>&gt;=</b>, <b>&lt;&gt;</b>
+or <b>=</b>) at the beginning of each of your search values to specify how the comparison should be done.
+</p>
+
+<?php echo "<?php echo CHtml::link('Advanced Search','#',array('class'=>'search-button')); ?>"; ?>
+
+<div class="search-form" style="display:none">
+<?php echo "<?php \$this->renderPartial('_search',array(
+ 'model'=>\$model,
+)); ?>\n"; ?>
+</div><!-- search-form -->
+
+<?php echo "<?php"; ?> $this->widget('zii.widgets.grid.CGridView', array(
+ 'id'=>'<?php echo $this->class2id($modelClass); ?>-grid',
+ 'dataProvider'=>$model->search(),
+ 'filter'=>$model,
+ 'columns'=>array(
+<?php
+$count=0;
+foreach($columns as $column)
+{
+ if(++$count==7)
+ echo "\t\t/*\n";
+ echo "\t\t'".$column->name."',\n";
+}
+if($count>=7)
+ echo "\t\t*/\n";
+?>
+ array(
+ 'class'=>'CButtonColumn',
+ ),
+ ),
+)); ?>
diff --git a/framework/cli/views/shell/crud/controller.php b/framework/cli/views/shell/crud/controller.php
new file mode 100644
index 0000000..cd7f79f
--- /dev/null
+++ b/framework/cli/views/shell/crud/controller.php
@@ -0,0 +1,190 @@
+<?php
+/**
+ * This is the template for generating the controller class file for crud.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $controllerClass: the controller class name
+ * - $modelClass: the model class name
+ */
+?>
+<?php echo "<?php\n"; ?>
+
+class <?php echo $controllerClass; ?> extends Controller
+{
+ /**
+ * @var string the default layout for the views. Defaults to '//layouts/column2', meaning
+ * using two-column layout. See 'protected/views/layouts/column2.php'.
+ */
+ public $layout='//layouts/column2';
+
+ /**
+ * @var CActiveRecord the currently loaded data model instance.
+ */
+ private $_model;
+
+ /**
+ * @return array action filters
+ */
+ public function filters()
+ {
+ return array(
+ 'accessControl', // perform access control for CRUD operations
+ );
+ }
+
+ /**
+ * Specifies the access control rules.
+ * This method is used by the 'accessControl' filter.
+ * @return array access control rules
+ */
+ public function accessRules()
+ {
+ return array(
+ array('allow', // allow all users to perform 'index' and 'view' actions
+ 'actions'=>array('index','view'),
+ 'users'=>array('*'),
+ ),
+ array('allow', // allow authenticated user to perform 'create' and 'update' actions
+ 'actions'=>array('create','update'),
+ 'users'=>array('@'),
+ ),
+ array('allow', // allow admin user to perform 'admin' and 'delete' actions
+ 'actions'=>array('admin','delete'),
+ 'users'=>array('admin'),
+ ),
+ array('deny', // deny all users
+ 'users'=>array('*'),
+ ),
+ );
+ }
+
+ /**
+ * Displays a particular model.
+ */
+ public function actionView()
+ {
+ $this->render('view',array(
+ 'model'=>$this->loadModel(),
+ ));
+ }
+
+ /**
+ * Creates a new model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ */
+ public function actionCreate()
+ {
+ $model=new <?php echo $modelClass; ?>;
+
+ // Uncomment the following line if AJAX validation is needed
+ // $this->performAjaxValidation($model);
+
+ if(isset($_POST['<?php echo $modelClass; ?>']))
+ {
+ $model->attributes=$_POST['<?php echo $modelClass; ?>'];
+ if($model->save())
+ $this->redirect(array('view','id'=>$model-><?php echo $ID; ?>));
+ }
+
+ $this->render('create',array(
+ 'model'=>$model,
+ ));
+ }
+
+ /**
+ * Updates a particular model.
+ * If update is successful, the browser will be redirected to the 'view' page.
+ */
+ public function actionUpdate()
+ {
+ $model=$this->loadModel();
+
+ // Uncomment the following line if AJAX validation is needed
+ // $this->performAjaxValidation($model);
+
+ if(isset($_POST['<?php echo $modelClass; ?>']))
+ {
+ $model->attributes=$_POST['<?php echo $modelClass; ?>'];
+ if($model->save())
+ $this->redirect(array('view','id'=>$model-><?php echo $ID; ?>));
+ }
+
+ $this->render('update',array(
+ 'model'=>$model,
+ ));
+ }
+
+ /**
+ * Deletes a particular model.
+ * If deletion is successful, the browser will be redirected to the 'index' page.
+ */
+ public function actionDelete()
+ {
+ if(Yii::app()->request->isPostRequest)
+ {
+ // we only allow deletion via POST request
+ $this->loadModel()->delete();
+
+ // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser
+ if(!isset($_GET['ajax']))
+ $this->redirect(array('index'));
+ }
+ else
+ throw new CHttpException(400,'Invalid request. Please do not repeat this request again.');
+ }
+
+ /**
+ * Lists all models.
+ */
+ public function actionIndex()
+ {
+ $dataProvider=new CActiveDataProvider('<?php echo $modelClass; ?>');
+ $this->render('index',array(
+ 'dataProvider'=>$dataProvider,
+ ));
+ }
+
+ /**
+ * Manages all models.
+ */
+ public function actionAdmin()
+ {
+ $model=new <?php echo $modelClass; ?>('search');
+ $model->unsetAttributes(); // clear any default values
+ if(isset($_GET['<?php echo $modelClass; ?>']))
+ $model->attributes=$_GET['<?php echo $modelClass; ?>'];
+
+ $this->render('admin',array(
+ 'model'=>$model,
+ ));
+ }
+
+ /**
+ * Returns the data model based on the primary key given in the GET variable.
+ * If the data model is not found, an HTTP exception will be raised.
+ */
+ public function loadModel()
+ {
+ if($this->_model===null)
+ {
+ if(isset($_GET['id']))
+ $this->_model=<?php echo $modelClass; ?>::model()->findbyPk($_GET['id']);
+ if($this->_model===null)
+ throw new CHttpException(404,'The requested page does not exist.');
+ }
+ return $this->_model;
+ }
+
+ /**
+ * Performs the AJAX validation.
+ * @param CModel the model to be validated
+ */
+ protected function performAjaxValidation($model)
+ {
+ if(isset($_POST['ajax']) && $_POST['ajax']==='<?php echo $this->class2id($modelClass); ?>-form')
+ {
+ echo CActiveForm::validate($model);
+ Yii::app()->end();
+ }
+ }
+}
diff --git a/framework/cli/views/shell/crud/create.php b/framework/cli/views/shell/crud/create.php
new file mode 100644
index 0000000..64e3cac
--- /dev/null
+++ b/framework/cli/views/shell/crud/create.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * This is the template for generating the create view for crud.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $modelClass: the model class name
+ * - $columns: a list of column schema objects
+ */
+?>
+<?php
+echo "<?php\n";
+$label=$this->class2name($modelClass,true);
+echo "\$this->breadcrumbs=array(
+ '$label'=>array('index'),
+ 'Create',
+);\n";
+?>
+
+$this->menu=array(
+ array('label'=>'List <?php echo $modelClass; ?>', 'url'=>array('index')),
+ array('label'=>'Manage <?php echo $modelClass; ?>', 'url'=>array('admin')),
+);
+?>
+
+<h1>Create <?php echo $modelClass; ?></h1>
+
+<?php echo "<?php echo \$this->renderPartial('_form', array('model'=>\$model)); ?>"; ?>
diff --git a/framework/cli/views/shell/crud/index.php b/framework/cli/views/shell/crud/index.php
new file mode 100644
index 0000000..658896e
--- /dev/null
+++ b/framework/cli/views/shell/crud/index.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * This is the template for generating the index view for crud.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $modelClass: the model class name
+ * - $columns: a list of column schema objects
+ */
+?>
+<?php
+echo "<?php\n";
+$label=$this->class2name($modelClass,true);
+$route=$modelClass.'/index';
+$route[0]=strtolower($route[0]);
+echo "\$this->breadcrumbs=array(
+ '$label',
+);\n";
+?>
+
+$this->menu=array(
+ array('label'=>'Create <?php echo $modelClass; ?>', 'url'=>array('create')),
+ array('label'=>'Manage <?php echo $modelClass; ?>', 'url'=>array('admin')),
+);
+?>
+
+<h1><?php echo $label; ?></h1>
+
+<?php echo "<?php"; ?> $this->widget('zii.widgets.CListView', array(
+ 'dataProvider'=>$dataProvider,
+ 'itemView'=>'_view',
+)); ?>
diff --git a/framework/cli/views/shell/crud/test.php b/framework/cli/views/shell/crud/test.php
new file mode 100644
index 0000000..fbb1fd9
--- /dev/null
+++ b/framework/cli/views/shell/crud/test.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * This is the template for generating the functional test for controller.
+ * The following variables are available in this template:
+ * - $controllerID: the controller ID
+ * - $fixtureName: the fixture name
+ * - $modelClass: the model class name
+ */
+?>
+<?php echo "<?php\n"; ?>
+
+class <?php echo $modelClass; ?>Test extends WebTestCase
+{
+ public $fixtures=array(
+ '<?php echo $fixtureName; ?>'=>'<?php echo $modelClass; ?>',
+ );
+
+ public function testShow()
+ {
+ $this->open('?r=<?php echo $controllerID; ?>/view&id=1');
+ }
+
+ public function testCreate()
+ {
+ $this->open('?r=<?php echo $controllerID; ?>/create');
+ }
+
+ public function testUpdate()
+ {
+ $this->open('?r=<?php echo $controllerID; ?>/update&id=1');
+ }
+
+ public function testDelete()
+ {
+ $this->open('?r=<?php echo $controllerID; ?>/view&id=1');
+ }
+
+ public function testList()
+ {
+ $this->open('?r=<?php echo $controllerID; ?>/index');
+ }
+
+ public function testAdmin()
+ {
+ $this->open('?r=<?php echo $controllerID; ?>/admin');
+ }
+}
diff --git a/framework/cli/views/shell/crud/update.php b/framework/cli/views/shell/crud/update.php
new file mode 100644
index 0000000..88aee25
--- /dev/null
+++ b/framework/cli/views/shell/crud/update.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * This is the template for generating the update view for crud.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $modelClass: the model class name
+ * - $columns: a list of column schema objects
+ */
+?>
+<?php
+echo "<?php\n";
+$nameColumn=$this->guessNameColumn($columns);
+$label=$this->class2name($modelClass,true);
+echo "\$this->breadcrumbs=array(
+ '$label'=>array('index'),
+ \$model->{$nameColumn}=>array('view','id'=>\$model->{$ID}),
+ 'Update',
+);\n";
+?>
+
+$this->menu=array(
+ array('label'=>'List <?php echo $modelClass; ?>', 'url'=>array('index')),
+ array('label'=>'Create <?php echo $modelClass; ?>', 'url'=>array('create')),
+ array('label'=>'View <?php echo $modelClass; ?>', 'url'=>array('view', 'id'=>$model-><?php echo $ID; ?>)),
+ array('label'=>'Manage <?php echo $modelClass; ?>', 'url'=>array('admin')),
+);
+?>
+
+<h1>Update <?php echo $modelClass." <?php echo \$model->{$ID}; ?>"; ?></h1>
+
+<?php echo "<?php echo \$this->renderPartial('_form', array('model'=>\$model)); ?>"; ?> \ No newline at end of file
diff --git a/framework/cli/views/shell/crud/view.php b/framework/cli/views/shell/crud/view.php
new file mode 100644
index 0000000..692b328
--- /dev/null
+++ b/framework/cli/views/shell/crud/view.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * This is the template for generating the 'view' view for crud.
+ * The following variables are available in this template:
+ * - $ID: the primary key name
+ * - $modelClass: the model class name
+ * - $columns: a list of column schema objects
+ */
+?>
+<?php
+echo "<?php\n";
+$nameColumn=$this->guessNameColumn($columns);
+$label=$this->class2name($modelClass,true);
+echo "\$this->breadcrumbs=array(
+ '$label'=>array('index'),
+ \$model->{$nameColumn},
+);\n";
+?>
+
+$this->menu=array(
+ array('label'=>'List <?php echo $modelClass; ?>', 'url'=>array('index')),
+ array('label'=>'Create <?php echo $modelClass; ?>', 'url'=>array('create')),
+ array('label'=>'Update <?php echo $modelClass; ?>', 'url'=>array('update', 'id'=>$model-><?php echo $ID; ?>)),
+ array('label'=>'Delete <?php echo $modelClass; ?>', 'url'=>'#', 'linkOptions'=>array('submit'=>array('delete','id'=>$model-><?php echo $ID; ?>),'confirm'=>'Are you sure you want to delete this item?')),
+ array('label'=>'Manage <?php echo $modelClass; ?>', 'url'=>array('admin')),
+);
+?>
+
+<h1>View <?php echo $modelClass." #<?php echo \$model->{$ID}; ?>"; ?></h1>
+
+<?php echo "<?php"; ?> $this->widget('zii.widgets.CDetailView', array(
+ 'data'=>$model,
+ 'attributes'=>array(
+<?php
+foreach($columns as $column)
+ echo "\t\t'".$column->name."',\n";
+?>
+ ),
+)); ?>
diff --git a/framework/cli/views/shell/form/action.php b/framework/cli/views/shell/form/action.php
new file mode 100644
index 0000000..367c8ba
--- /dev/null
+++ b/framework/cli/views/shell/form/action.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * This is the template for generating the action script for the form.
+ * The following variables are available in this template:
+ * - $modelClass: the model class name
+ * - $viewName: the name of the view
+ */
+?>
+<?php
+$actionName=$modelClass;
+if(strrpos($modelClass,'Form')===strlen($modelClass)-4)
+ $actionName=substr($modelClass,0,strlen($modelClass)-4);
+?>
+public function action<?php echo $actionName; ?>()
+{
+ $model=new <?php echo $modelClass; ?>;
+
+ // uncomment the following code to enable ajax-based validation
+ /*
+ if(isset($_POST['ajax']) && $_POST['ajax']==='<?php echo $this->class2id($modelClass); ?>-form')
+ {
+ echo CActiveForm::validate($model);
+ Yii::app()->end();
+ }
+ */
+
+ if(isset($_POST['<?php echo $modelClass; ?>']))
+ {
+ $model->attributes=$_POST['<?php echo $modelClass; ?>'];
+ if($model->validate())
+ {
+ // form inputs are valid, do something here
+ return;
+ }
+ }
+ $this->render('<?php echo $viewName; ?>',array('model'=>$model));
+} \ No newline at end of file
diff --git a/framework/cli/views/shell/form/form.php b/framework/cli/views/shell/form/form.php
new file mode 100644
index 0000000..cbf63bb
--- /dev/null
+++ b/framework/cli/views/shell/form/form.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * This is the template for generating the form view for the specified model.
+ * The following variables are available in this template:
+ * - $modelClass: the model class name
+ * - $attributes: a list of attribute names to receive form inputs
+ */
+?>
+<div class="form">
+
+<?php echo "<?php \$form=\$this->beginWidget('CActiveForm', array(
+ 'id'=>'".$this->class2id($modelClass)."-form',
+ 'enableAjaxValidation'=>false,
+)); ?>\n"; ?>
+
+ <p class="note">Fields with <span class="required">*</span> are required.</p>
+
+ <?php echo "<?php echo \$form->errorSummary(\$model); ?>\n"; ?>
+
+<?php
+foreach($attributes as $attribute)
+{
+?>
+ <div class="row">
+ <?php echo "<?php echo \$form->labelEx(\$model,'$attribute'); ?>\n"; ?>
+ <?php echo "<?php echo \$form->textField(\$model,'$attribute'); ?>\n"; ?>
+ <?php echo "<?php echo \$form->error(\$model,'$attribute'); ?>\n"; ?>
+ </div>
+
+<?php
+}
+?>
+ <div class="row buttons">
+ <?php echo "<?php echo CHtml::submitButton('Submit'); ?>\n"; ?>
+ </div>
+
+<?php echo "<?php \$this->endWidget(); ?>\n"; ?>
+
+</div><!-- form --> \ No newline at end of file
diff --git a/framework/cli/views/shell/model/fixture.php b/framework/cli/views/shell/model/fixture.php
new file mode 100644
index 0000000..a159585
--- /dev/null
+++ b/framework/cli/views/shell/model/fixture.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * This is the template for generating the fixture file for a model class.
+ * The following variables are available in this template:
+ * - $table: the table schema
+ */
+?>
+<?php echo "<?php\n"; ?>
+
+return array(
+ /*
+ 'sample1'=>array(
+<?php foreach($table->columns as $name=>$column) {
+ if($table->sequenceName===null || $table->primaryKey!==$column->name)
+ echo "\t\t'$name' => '',\n";
+} ?>
+ ),
+ 'sample2'=>array(
+<?php foreach($table->columns as $name=>$column) {
+ if($table->sequenceName===null || $table->primaryKey!==$column->name)
+ echo "\t\t'$name' => '',\n";
+} ?>
+ ),
+ */
+);
diff --git a/framework/cli/views/shell/model/model.php b/framework/cli/views/shell/model/model.php
new file mode 100644
index 0000000..b1137db
--- /dev/null
+++ b/framework/cli/views/shell/model/model.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * This is the template for generating a model class file.
+ * The following variables are available in this template:
+ * - $className: the class name
+ * - $tableName: the table name
+ * - $columns: a list of table column schema objects
+ * - $rules: a list of validation rules (string)
+ * - $labels: a list of labels (column name => label)
+ * - $relations: a list of relations (string)
+ */
+?>
+<?php echo "<?php\n"; ?>
+
+/**
+ * This is the model class for table "<?php echo $tableName; ?>".
+ *
+ * The followings are the available columns in table '<?php echo $tableName; ?>':
+<?php foreach($columns as $column): ?>
+ * @property <?php echo $column->type.' $'.$column->name."\n"; ?>
+<?php endforeach; ?>
+ */
+class <?php echo $className; ?> extends CActiveRecord
+{
+ /**
+ * Returns the static model of the specified AR class.
+ * @return <?php echo $className; ?> the static model class
+ */
+ public static function model($className=__CLASS__)
+ {
+ return parent::model($className);
+ }
+
+ /**
+ * @return string the associated database table name
+ */
+ public function tableName()
+ {
+ return '<?php echo $tableName; ?>';
+ }
+
+ /**
+ * @return array validation rules for model attributes.
+ */
+ public function rules()
+ {
+ // NOTE: you should only define rules for those attributes that
+ // will receive user inputs.
+ return array(
+<?php foreach($rules as $rule): ?>
+ <?php echo $rule.",\n"; ?>
+<?php endforeach; ?>
+ // The following rule is used by search().
+ // Please remove those attributes that should not be searched.
+ array('<?php echo implode(', ', array_keys($columns)); ?>', 'safe', 'on'=>'search'),
+ );
+ }
+
+ /**
+ * @return array relational rules.
+ */
+ public function relations()
+ {
+ // NOTE: you may need to adjust the relation name and the related
+ // class name for the relations automatically generated below.
+ return array(
+<?php foreach($relations as $name=>$relation): ?>
+ <?php echo "'$name' => $relation,\n"; ?>
+<?php endforeach; ?>
+ );
+ }
+
+ /**
+ * @return array customized attribute labels (name=>label)
+ */
+ public function attributeLabels()
+ {
+ return array(
+<?php foreach($labels as $column=>$label): ?>
+ <?php echo "'$column' => '$label',\n"; ?>
+<?php endforeach; ?>
+ );
+ }
+
+ /**
+ * Retrieves a list of models based on the current search/filter conditions.
+ * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
+ */
+ public function search()
+ {
+ // Warning: Please modify the following code to remove attributes that
+ // should not be searched.
+
+ $criteria=new CDbCriteria;
+
+<?php
+foreach($columns as $name=>$column)
+{
+ if($column->type==='string')
+ {
+ echo "\t\t\$criteria->compare('$name',\$this->$name,true);\n\n";
+ }
+ else
+ {
+ echo "\t\t\$criteria->compare('$name',\$this->$name);\n\n";
+ }
+}
+?>
+ return new CActiveDataProvider('<?php echo $className; ?>', array(
+ 'criteria'=>$criteria,
+ ));
+ }
+} \ No newline at end of file
diff --git a/framework/cli/views/shell/model/test.php b/framework/cli/views/shell/model/test.php
new file mode 100644
index 0000000..93aa17e
--- /dev/null
+++ b/framework/cli/views/shell/model/test.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * This is the template for generating the unit test for a model class.
+ * The following variables are available in this template:
+ * - $className: the class name
+ * - $fixtureName: the fixture name
+ */
+?>
+<?php echo "<?php\n"; ?>
+
+class <?php echo $className; ?>Test extends CDbTestCase
+{
+ public $fixtures=array(
+ '<?php echo $fixtureName; ?>'=>'<?php echo $className; ?>',
+ );
+
+ public function testCreate()
+ {
+
+ }
+} \ No newline at end of file
diff --git a/framework/cli/views/shell/module/components/.yii b/framework/cli/views/shell/module/components/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/shell/module/components/.yii
diff --git a/framework/cli/views/shell/module/controllers/DefaultController.php b/framework/cli/views/shell/module/controllers/DefaultController.php
new file mode 100644
index 0000000..957551e
--- /dev/null
+++ b/framework/cli/views/shell/module/controllers/DefaultController.php
@@ -0,0 +1,9 @@
+<?php
+
+class DefaultController extends Controller
+{
+ public function actionIndex()
+ {
+ $this->render('index');
+ }
+} \ No newline at end of file
diff --git a/framework/cli/views/shell/module/messages/.yii b/framework/cli/views/shell/module/messages/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/shell/module/messages/.yii
diff --git a/framework/cli/views/shell/module/models/.yii b/framework/cli/views/shell/module/models/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/shell/module/models/.yii
diff --git a/framework/cli/views/shell/module/module.php b/framework/cli/views/shell/module/module.php
new file mode 100644
index 0000000..c8f6e02
--- /dev/null
+++ b/framework/cli/views/shell/module/module.php
@@ -0,0 +1,28 @@
+<?php echo "<?php\n"; ?>
+
+class <?php echo $moduleClass; ?> extends CWebModule
+{
+ public function init()
+ {
+ // this method is called when the module is being created
+ // you may place code here to customize the module or the application
+
+ // import the module-level models and components
+ $this->setImport(array(
+ '<?php echo $moduleID; ?>.models.*',
+ '<?php echo $moduleID; ?>.components.*',
+ ));
+ }
+
+ public function beforeControllerAction($controller, $action)
+ {
+ if(parent::beforeControllerAction($controller, $action))
+ {
+ // this method is called before any module controller action is performed
+ // you may place customized code here
+ return true;
+ }
+ else
+ return false;
+ }
+}
diff --git a/framework/cli/views/shell/module/views/default/index.php b/framework/cli/views/shell/module/views/default/index.php
new file mode 100644
index 0000000..b726985
--- /dev/null
+++ b/framework/cli/views/shell/module/views/default/index.php
@@ -0,0 +1,14 @@
+<?php
+$this->breadcrumbs=array(
+ $this->module->id,
+);
+?>
+<h1><?php echo $this->uniqueId . '/' . $this->action->id; ?></h1>
+
+<p>
+This is the view content for action "<?php echo $this->action->id; ?>".
+The action belongs to the controller "<?php echo get_class($this); ?>" in the "<?php echo $this->module->id; ?>" module.
+</p>
+<p>
+You may customize this page by editing <tt><?php echo __FILE__; ?></tt>
+</p> \ No newline at end of file
diff --git a/framework/cli/views/shell/module/views/layouts/.yii b/framework/cli/views/shell/module/views/layouts/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/shell/module/views/layouts/.yii
diff --git a/framework/cli/views/webapp/assets/.yii b/framework/cli/views/webapp/assets/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/assets/.yii
diff --git a/framework/cli/views/webapp/css/bg.gif b/framework/cli/views/webapp/css/bg.gif
new file mode 100644
index 0000000..4283989
--- /dev/null
+++ b/framework/cli/views/webapp/css/bg.gif
Binary files differ
diff --git a/framework/cli/views/webapp/css/form.css b/framework/cli/views/webapp/css/form.css
new file mode 100644
index 0000000..b194603
--- /dev/null
+++ b/framework/cli/views/webapp/css/form.css
@@ -0,0 +1,160 @@
+/**
+ * CSS styles for forms generated by yiic.
+ *
+ * The styles can be applied to the following form structure:
+ *
+ * <div class="form">
+ * <div class="row">
+ * <label for="inputid">xyz</label>
+ * <input name="inputid" id="inputid" type="text" />
+ * <p class="hint">hint text</p>
+ * </div>
+ * <div class="row">
+ * <label for="inputid">xyz</label>
+ * <input name="inputid" id="inputid" type="text" />
+ * <p class="hint">hint text</p>
+ * </div>
+ * <div class="row buttons">
+ * <label for="inputid">xyz</label>
+ * <input name="inputid" id="inputid" type="text" />
+ * <p class="hint">hint text</p>
+ * </div>
+ * </div>
+ *
+ * The above code will render the labels and input fields in separate lines.
+ * In order to render them in the same line, please use the "wide" form as follows,
+ *
+ * <div class="wide form">
+ * ......
+ * </div>
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright &copy; 2008-2010 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+div.form
+{
+}
+
+div.form input,
+div.form textarea,
+div.form select
+{
+ margin: 0.2em 0 0.5em 0;
+}
+
+div.form fieldset
+{
+ border: 1px solid #DDD;
+ padding: 10px;
+ margin: 0 0 10px 0;
+ -moz-border-radius:7px;
+}
+
+div.form label
+{
+ font-weight: bold;
+ font-size: 0.9em;
+ display: block;
+}
+
+div.form .row
+{
+ margin: 5px 0;
+}
+
+div.form .hint
+{
+ margin: 0;
+ padding: 0;
+ color: #999;
+}
+
+div.form .note
+{
+ font-style: italic;
+}
+
+div.form span.required
+{
+ color: red;
+}
+
+div.form div.error label:first-child,
+div.form label.error,
+div.form span.error
+{
+ color: #C00;
+}
+
+div.form div.error input,
+div.form div.error textarea,
+div.form div.error select,
+div.form input.error,
+div.form textarea.error,
+div.form select.error
+{
+ background: #FEE;
+ border-color: #C00;
+}
+
+div.form div.success input,
+div.form div.success textarea,
+div.form div.success select,
+div.form input.success,
+div.form textarea.success,
+div.form select.success
+{
+ background: #E6EFC2;
+ border-color: #C6D880;
+}
+
+
+div.form .errorSummary
+{
+ border: 2px solid #C00;
+ padding: 7px 7px 12px 7px;
+ margin: 0 0 20px 0;
+ background: #FEE;
+ font-size: 0.9em;
+}
+
+div.form .errorMessage
+{
+ color: red;
+ font-size: 0.9em;
+}
+
+div.form .errorSummary p
+{
+ margin: 0;
+ padding: 5px;
+}
+
+div.form .errorSummary ul
+{
+ margin: 0;
+ padding: 0 0 0 20px;
+}
+
+div.wide.form label
+{
+ float: left;
+ margin-right: 10px;
+ position: relative;
+ text-align: right;
+ width: 100px;
+}
+
+div.wide.form .row
+{
+ clear: left;
+}
+
+div.wide.form .buttons, div.wide.form .hint, div.wide.form .errorMessage
+{
+ clear: left;
+ padding-left: 110px;
+}
diff --git a/framework/cli/views/webapp/css/ie.css b/framework/cli/views/webapp/css/ie.css
new file mode 100644
index 0000000..f015399
--- /dev/null
+++ b/framework/cli/views/webapp/css/ie.css
@@ -0,0 +1,36 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* ie.css */
+body {text-align:center;}
+.container {text-align:left;}
+* html .column, * html .span-1, * html .span-2, * html .span-3, * html .span-4, * html .span-5, * html .span-6, * html .span-7, * html .span-8, * html .span-9, * html .span-10, * html .span-11, * html .span-12, * html .span-13, * html .span-14, * html .span-15, * html .span-16, * html .span-17, * html .span-18, * html .span-19, * html .span-20, * html .span-21, * html .span-22, * html .span-23, * html .span-24 {display:inline;overflow-x:hidden;}
+* html legend {margin:0px -8px 16px 0;padding:0;}
+sup {vertical-align:text-top;}
+sub {vertical-align:text-bottom;}
+html>body p code {*white-space:normal;}
+hr {margin:-8px auto 11px;}
+img {-ms-interpolation-mode:bicubic;}
+.clearfix, .container {display:inline-block;}
+* html .clearfix, * html .container {height:1%;}
+fieldset {padding-top:0;}
+legend {margin-top:-0.2em;margin-bottom:1em;margin-left:-0.5em;}
+textarea {overflow:auto;}
+label {vertical-align:middle;position:relative;top:-0.25em;}
+input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;}
+input.text:focus, input.title:focus {border-color:#666;}
+input.text, input.title, textarea, select {margin:0.5em 0;}
+input.checkbox, input.radio {position:relative;top:.25em;}
+form.inline div, form.inline p {vertical-align:middle;}
+form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;}
+button, input.button {position:relative;top:0.25em;} \ No newline at end of file
diff --git a/framework/cli/views/webapp/css/main.css b/framework/cli/views/webapp/css/main.css
new file mode 100644
index 0000000..13327b8
--- /dev/null
+++ b/framework/cli/views/webapp/css/main.css
@@ -0,0 +1,229 @@
+body
+{
+ margin: 0;
+ padding: 0;
+ color: #555;
+ font: normal 10pt Arial,Helvetica,sans-serif;
+ background: #EFEFEF;
+}
+
+#page
+{
+ margin-top: 5px;
+ margin-bottom: 5px;
+ background: white;
+ border: 1px solid #C9E0ED;
+}
+
+#header
+{
+ margin: 0;
+ padding: 0;
+ border-top: 3px solid #C9E0ED;
+}
+
+#content
+{
+ padding: 20px;
+}
+
+#sidebar
+{
+ padding: 20px 20px 20px 0;
+}
+
+#footer
+{
+ padding: 10px;
+ margin: 10px 20px;
+ font-size: 0.8em;
+ text-align: center;
+ border-top: 1px solid #C9E0ED;
+}
+
+#logo
+{
+ padding: 10px 20px;
+ font-size: 200%;
+}
+
+#mainmenu
+{
+ background:white url(bg.gif) repeat-x left top;
+}
+
+#mainmenu ul
+{
+ padding:6px 20px 5px 20px;
+ margin:0px;
+}
+
+#mainmenu ul li
+{
+ display: inline;
+}
+
+#mainmenu ul li a
+{
+ color:#ffffff;
+ background-color:transparent;
+ font-size:12px;
+ font-weight:bold;
+ text-decoration:none;
+ padding:5px 8px;
+}
+
+#mainmenu ul li a:hover, #mainmenu ul li.active a
+{
+ color: #6399cd;
+ background-color:#EFF4FA;
+ text-decoration:none;
+}
+
+div.flash-error, div.flash-notice, div.flash-success
+{
+ padding:.8em;
+ margin-bottom:1em;
+ border:2px solid #ddd;
+}
+
+div.flash-error
+{
+ background:#FBE3E4;
+ color:#8a1f11;
+ border-color:#FBC2C4;
+}
+
+div.flash-notice
+{
+ background:#FFF6BF;
+ color:#514721;
+ border-color:#FFD324;
+}
+
+div.flash-success
+{
+ background:#E6EFC2;
+ color:#264409;
+ border-color:#C6D880;
+}
+
+div.flash-error a
+{
+ color:#8a1f11;
+}
+
+div.flash-notice a
+{
+ color:#514721;
+}
+
+div.flash-success a
+{
+ color:#264409;
+}
+
+div.form .rememberMe label
+{
+ display: inline;
+}
+
+div.view
+{
+ padding: 10px;
+ margin: 10px 0;
+ border: 1px solid #C9E0ED;
+}
+
+div.breadcrumbs
+{
+ font-size: 0.9em;
+ padding: 5px 20px;
+}
+
+div.breadcrumbs span
+{
+ font-weight: bold;
+}
+
+div.search-form
+{
+ padding: 10px;
+ margin: 10px 0;
+ background: #eee;
+}
+
+.portlet
+{
+
+}
+
+.portlet-decoration
+{
+ padding: 3px 8px;
+ background: #B7D6E7;
+ border-left: 5px solid #6FACCF;
+}
+
+.portlet-title
+{
+ font-size: 12px;
+ font-weight: bold;
+ padding: 0;
+ margin: 0;
+ color: #298dcd;
+}
+
+.portlet-content
+{
+ font-size:0.9em;
+ margin: 0 0 15px 0;
+ padding: 5px 8px;
+ background:#EFFDFF;
+}
+
+.portlet-content ul
+{
+ list-style-image:none;
+ list-style-position:outside;
+ list-style-type:none;
+ margin: 0;
+ padding: 0;
+}
+
+.portlet-content li
+{
+ padding: 2px 0 4px 0px;
+}
+
+.operations
+{
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+}
+
+.operations li
+{
+ padding-bottom: 2px;
+}
+
+.operations li a
+{
+ font: bold 12px Arial;
+ color: #0066A4;
+ display: block;
+ padding: 2px 0 2px 8px;
+ line-height: 15px;
+ text-decoration: none;
+}
+
+.operations li a:visited
+{
+ color: #0066A4;
+}
+
+.operations li a:hover
+{
+ background: #80CFFF;
+} \ No newline at end of file
diff --git a/framework/cli/views/webapp/css/print.css b/framework/cli/views/webapp/css/print.css
new file mode 100644
index 0000000..bd79afd
--- /dev/null
+++ b/framework/cli/views/webapp/css/print.css
@@ -0,0 +1,29 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* print.css */
+body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;}
+.container {background:none;}
+hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;}
+hr.space {background:#fff;color:#fff;visibility:hidden;}
+h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;}
+code {font:.9em "Courier New", Monaco, Courier, monospace;}
+a img {border:none;}
+p img.top {margin-top:0;}
+blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;}
+.small {font-size:.9em;}
+.large {font-size:1.1em;}
+.quiet {color:#999;}
+.hide {display:none;}
+a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;}
+a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;} \ No newline at end of file
diff --git a/framework/cli/views/webapp/css/screen.css b/framework/cli/views/webapp/css/screen.css
new file mode 100644
index 0000000..7824d07
--- /dev/null
+++ b/framework/cli/views/webapp/css/screen.css
@@ -0,0 +1,238 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 1.0.1
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* reset.css */
+html {margin:0;padding:0;border:0;}
+body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;}
+article, aside, details, figcaption, figure, dialog, footer, header, hgroup, menu, nav, section {display:block;}
+body {line-height:1.5;background:white;}
+table {border-collapse:separate;border-spacing:0;}
+caption, th, td {text-align:left;font-weight:normal;float:none !important;}
+table, th, td {vertical-align:middle;}
+blockquote:before, blockquote:after, q:before, q:after {content:'';}
+blockquote, q {quotes:"" "";}
+a img {border:none;}
+:focus {outline:0;}
+
+/* typography.css */
+html {font-size:100.01%;}
+body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
+h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;}
+h1 {font-size:2em;line-height:1;margin-bottom:0.5em;}
+h2 {font-size:1.6em;margin-bottom:0.75em;}
+h3 {font-size:1.4em;line-height:1;margin-bottom:1em;}
+h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;}
+h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
+h6 {font-size:1em;font-weight:bold;}
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
+p {margin:0 0 1.5em;}
+.left {float:left !important;}
+p .left {margin:1.5em 1.5em 1.5em 0;padding:0;}
+.right {float:right !important;}
+p .right {margin:1.5em 0 1.5em 1.5em;padding:0;}
+a:focus, a:hover {color:#09f;}
+a {color:#06c;text-decoration:underline;}
+blockquote {margin:1.5em;color:#666;font-style:italic;}
+strong, dfn {font-weight:bold;}
+em, dfn {font-style:italic;}
+sup, sub {line-height:0;}
+abbr, acronym {border-bottom:1px dotted #666;}
+address {margin:0 0 1.5em;font-style:italic;}
+del {color:#666;}
+pre {margin:1.5em 0;white-space:pre;}
+pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
+li ul, li ol {margin:0;}
+ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;}
+ul {list-style-type:disc;}
+ol {list-style-type:decimal;}
+dl {margin:0 0 1.5em 0;}
+dl dt {font-weight:bold;}
+dd {margin-left:1.5em;}
+table {margin-bottom:1.4em;width:100%;}
+th {font-weight:bold;}
+thead th {background:#c3d9ff;}
+th, td, caption {padding:4px 10px 4px 5px;}
+tfoot {font-style:italic;}
+caption {background:#eee;}
+.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
+.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
+.hide {display:none;}
+.quiet {color:#666;}
+.loud {color:#000;}
+.highlight {background:#ff0;}
+.added {background:#060;color:#fff;}
+.removed {background:#900;color:#fff;}
+.first {margin-left:0;padding-left:0;}
+.last {margin-right:0;padding-right:0;}
+.top {margin-top:0;padding-top:0;}
+.bottom {margin-bottom:0;padding-bottom:0;}
+
+/* grid.css */
+.container {width:950px;margin:0 auto;}
+.column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {float:left;margin-right:10px;}
+.last {margin-right:0;}
+.span-1 {width:30px;}
+.span-2 {width:70px;}
+.span-3 {width:110px;}
+.span-4 {width:150px;}
+.span-5 {width:190px;}
+.span-6 {width:230px;}
+.span-7 {width:270px;}
+.span-8 {width:310px;}
+.span-9 {width:350px;}
+.span-10 {width:390px;}
+.span-11 {width:430px;}
+.span-12 {width:470px;}
+.span-13 {width:510px;}
+.span-14 {width:550px;}
+.span-15 {width:590px;}
+.span-16 {width:630px;}
+.span-17 {width:670px;}
+.span-18 {width:710px;}
+.span-19 {width:750px;}
+.span-20 {width:790px;}
+.span-21 {width:830px;}
+.span-22 {width:870px;}
+.span-23 {width:910px;}
+.span-24 {width:950px;margin-right:0;}
+input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px;}
+input.span-1, textarea.span-1 {width:18px;}
+input.span-2, textarea.span-2 {width:58px;}
+input.span-3, textarea.span-3 {width:98px;}
+input.span-4, textarea.span-4 {width:138px;}
+input.span-5, textarea.span-5 {width:178px;}
+input.span-6, textarea.span-6 {width:218px;}
+input.span-7, textarea.span-7 {width:258px;}
+input.span-8, textarea.span-8 {width:298px;}
+input.span-9, textarea.span-9 {width:338px;}
+input.span-10, textarea.span-10 {width:378px;}
+input.span-11, textarea.span-11 {width:418px;}
+input.span-12, textarea.span-12 {width:458px;}
+input.span-13, textarea.span-13 {width:498px;}
+input.span-14, textarea.span-14 {width:538px;}
+input.span-15, textarea.span-15 {width:578px;}
+input.span-16, textarea.span-16 {width:618px;}
+input.span-17, textarea.span-17 {width:658px;}
+input.span-18, textarea.span-18 {width:698px;}
+input.span-19, textarea.span-19 {width:738px;}
+input.span-20, textarea.span-20 {width:778px;}
+input.span-21, textarea.span-21 {width:818px;}
+input.span-22, textarea.span-22 {width:858px;}
+input.span-23, textarea.span-23 {width:898px;}
+input.span-24, textarea.span-24 {width:938px;}
+.append-1 {padding-right:40px;}
+.append-2 {padding-right:80px;}
+.append-3 {padding-right:120px;}
+.append-4 {padding-right:160px;}
+.append-5 {padding-right:200px;}
+.append-6 {padding-right:240px;}
+.append-7 {padding-right:280px;}
+.append-8 {padding-right:320px;}
+.append-9 {padding-right:360px;}
+.append-10 {padding-right:400px;}
+.append-11 {padding-right:440px;}
+.append-12 {padding-right:480px;}
+.append-13 {padding-right:520px;}
+.append-14 {padding-right:560px;}
+.append-15 {padding-right:600px;}
+.append-16 {padding-right:640px;}
+.append-17 {padding-right:680px;}
+.append-18 {padding-right:720px;}
+.append-19 {padding-right:760px;}
+.append-20 {padding-right:800px;}
+.append-21 {padding-right:840px;}
+.append-22 {padding-right:880px;}
+.append-23 {padding-right:920px;}
+.prepend-1 {padding-left:40px;}
+.prepend-2 {padding-left:80px;}
+.prepend-3 {padding-left:120px;}
+.prepend-4 {padding-left:160px;}
+.prepend-5 {padding-left:200px;}
+.prepend-6 {padding-left:240px;}
+.prepend-7 {padding-left:280px;}
+.prepend-8 {padding-left:320px;}
+.prepend-9 {padding-left:360px;}
+.prepend-10 {padding-left:400px;}
+.prepend-11 {padding-left:440px;}
+.prepend-12 {padding-left:480px;}
+.prepend-13 {padding-left:520px;}
+.prepend-14 {padding-left:560px;}
+.prepend-15 {padding-left:600px;}
+.prepend-16 {padding-left:640px;}
+.prepend-17 {padding-left:680px;}
+.prepend-18 {padding-left:720px;}
+.prepend-19 {padding-left:760px;}
+.prepend-20 {padding-left:800px;}
+.prepend-21 {padding-left:840px;}
+.prepend-22 {padding-left:880px;}
+.prepend-23 {padding-left:920px;}
+.border {padding-right:4px;margin-right:5px;border-right:1px solid #ddd;}
+.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #ddd;}
+.pull-1 {margin-left:-40px;}
+.pull-2 {margin-left:-80px;}
+.pull-3 {margin-left:-120px;}
+.pull-4 {margin-left:-160px;}
+.pull-5 {margin-left:-200px;}
+.pull-6 {margin-left:-240px;}
+.pull-7 {margin-left:-280px;}
+.pull-8 {margin-left:-320px;}
+.pull-9 {margin-left:-360px;}
+.pull-10 {margin-left:-400px;}
+.pull-11 {margin-left:-440px;}
+.pull-12 {margin-left:-480px;}
+.pull-13 {margin-left:-520px;}
+.pull-14 {margin-left:-560px;}
+.pull-15 {margin-left:-600px;}
+.pull-16 {margin-left:-640px;}
+.pull-17 {margin-left:-680px;}
+.pull-18 {margin-left:-720px;}
+.pull-19 {margin-left:-760px;}
+.pull-20 {margin-left:-800px;}
+.pull-21 {margin-left:-840px;}
+.pull-22 {margin-left:-880px;}
+.pull-23 {margin-left:-920px;}
+.pull-24 {margin-left:-960px;}
+.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;}
+.push-1 {margin:0 -40px 1.5em 40px;}
+.push-2 {margin:0 -80px 1.5em 80px;}
+.push-3 {margin:0 -120px 1.5em 120px;}
+.push-4 {margin:0 -160px 1.5em 160px;}
+.push-5 {margin:0 -200px 1.5em 200px;}
+.push-6 {margin:0 -240px 1.5em 240px;}
+.push-7 {margin:0 -280px 1.5em 280px;}
+.push-8 {margin:0 -320px 1.5em 320px;}
+.push-9 {margin:0 -360px 1.5em 360px;}
+.push-10 {margin:0 -400px 1.5em 400px;}
+.push-11 {margin:0 -440px 1.5em 440px;}
+.push-12 {margin:0 -480px 1.5em 480px;}
+.push-13 {margin:0 -520px 1.5em 520px;}
+.push-14 {margin:0 -560px 1.5em 560px;}
+.push-15 {margin:0 -600px 1.5em 600px;}
+.push-16 {margin:0 -640px 1.5em 640px;}
+.push-17 {margin:0 -680px 1.5em 680px;}
+.push-18 {margin:0 -720px 1.5em 720px;}
+.push-19 {margin:0 -760px 1.5em 760px;}
+.push-20 {margin:0 -800px 1.5em 800px;}
+.push-21 {margin:0 -840px 1.5em 840px;}
+.push-22 {margin:0 -880px 1.5em 880px;}
+.push-23 {margin:0 -920px 1.5em 920px;}
+.push-24 {margin:0 -960px 1.5em 960px;}
+.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:left;position:relative;}
+div.prepend-top, .prepend-top {margin-top:1.5em;}
+div.append-bottom, .append-bottom {margin-bottom:1.5em;}
+.box {padding:1.5em;margin-bottom:1.5em;background:#e5eCf9;}
+hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 17px;border:none;}
+hr.space {background:#fff;color:#fff;visibility:hidden;}
+.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
+.clearfix, .container {display:block;}
+.clear {clear:both;}
diff --git a/framework/cli/views/webapp/images/.yii b/framework/cli/views/webapp/images/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/images/.yii
diff --git a/framework/cli/views/webapp/index-test.php b/framework/cli/views/webapp/index-test.php
new file mode 100644
index 0000000..c6d5a04
--- /dev/null
+++ b/framework/cli/views/webapp/index-test.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * This is the bootstrap file for test application.
+ * This file should be removed when the application is deployed for production.
+ */
+
+// change the following paths if necessary
+$yii=dirname(__FILE__).'/../framework/yii.php';
+$config=dirname(__FILE__).'/protected/config/test.php';
+
+// remove the following line when in production mode
+defined('YII_DEBUG') or define('YII_DEBUG',true);
+
+require_once($yii);
+Yii::createWebApplication($config)->run();
diff --git a/framework/cli/views/webapp/index.php b/framework/cli/views/webapp/index.php
new file mode 100644
index 0000000..11332f3
--- /dev/null
+++ b/framework/cli/views/webapp/index.php
@@ -0,0 +1,13 @@
+<?php
+
+// change the following paths if necessary
+$yii=dirname(__FILE__).'/../framework/yii.php';
+$config=dirname(__FILE__).'/protected/config/main.php';
+
+// remove the following lines when in production mode
+defined('YII_DEBUG') or define('YII_DEBUG',true);
+// specify how many levels of call stack should be shown in each log message
+defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);
+
+require_once($yii);
+Yii::createWebApplication($config)->run();
diff --git a/framework/cli/views/webapp/protected/.htaccess b/framework/cli/views/webapp/protected/.htaccess
new file mode 100644
index 0000000..8d2f256
--- /dev/null
+++ b/framework/cli/views/webapp/protected/.htaccess
@@ -0,0 +1 @@
+deny from all
diff --git a/framework/cli/views/webapp/protected/commands/shell/.yii b/framework/cli/views/webapp/protected/commands/shell/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/protected/commands/shell/.yii
diff --git a/framework/cli/views/webapp/protected/components/Controller.php b/framework/cli/views/webapp/protected/components/Controller.php
new file mode 100644
index 0000000..4d27862
--- /dev/null
+++ b/framework/cli/views/webapp/protected/components/Controller.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Controller is the customized base controller class.
+ * All controller classes for this application should extend from this base class.
+ */
+class Controller extends CController
+{
+ /**
+ * @var string the default layout for the controller view. Defaults to '//layouts/column1',
+ * meaning using a single column layout. See 'protected/views/layouts/column1.php'.
+ */
+ public $layout='//layouts/column1';
+ /**
+ * @var array context menu items. This property will be assigned to {@link CMenu::items}.
+ */
+ public $menu=array();
+ /**
+ * @var array the breadcrumbs of the current page. The value of this property will
+ * be assigned to {@link CBreadcrumbs::links}. Please refer to {@link CBreadcrumbs::links}
+ * for more details on how to specify this property.
+ */
+ public $breadcrumbs=array();
+} \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/components/UserIdentity.php b/framework/cli/views/webapp/protected/components/UserIdentity.php
new file mode 100644
index 0000000..a9704e5
--- /dev/null
+++ b/framework/cli/views/webapp/protected/components/UserIdentity.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * UserIdentity represents the data needed to identity a user.
+ * It contains the authentication method that checks if the provided
+ * data can identity the user.
+ */
+class UserIdentity extends CUserIdentity
+{
+ /**
+ * Authenticates a user.
+ * The example implementation makes sure if the username and password
+ * are both 'demo'.
+ * In practical applications, this should be changed to authenticate
+ * against some persistent user identity storage (e.g. database).
+ * @return boolean whether authentication succeeds.
+ */
+ public function authenticate()
+ {
+ $users=array(
+ // username => password
+ 'demo'=>'demo',
+ 'admin'=>'admin',
+ );
+ if(!isset($users[$this->username]))
+ $this->errorCode=self::ERROR_USERNAME_INVALID;
+ else if($users[$this->username]!==$this->password)
+ $this->errorCode=self::ERROR_PASSWORD_INVALID;
+ else
+ $this->errorCode=self::ERROR_NONE;
+ return !$this->errorCode;
+ }
+} \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/config/console.php b/framework/cli/views/webapp/protected/config/console.php
new file mode 100644
index 0000000..15c9353
--- /dev/null
+++ b/framework/cli/views/webapp/protected/config/console.php
@@ -0,0 +1,24 @@
+<?php
+
+// This is the configuration for yiic console application.
+// Any writable CConsoleApplication properties can be configured here.
+return array(
+ 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
+ 'name'=>'My Console Application',
+ // application components
+ 'components'=>array(
+ 'db'=>array(
+ 'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
+ ),
+ // uncomment the following to use a MySQL database
+ /*
+ 'db'=>array(
+ 'connectionString' => 'mysql:host=localhost;dbname=testdrive',
+ 'emulatePrepare' => true,
+ 'username' => 'root',
+ 'password' => '',
+ 'charset' => 'utf8',
+ ),
+ */
+ ),
+); \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/config/main.php b/framework/cli/views/webapp/protected/config/main.php
new file mode 100644
index 0000000..9e840e4
--- /dev/null
+++ b/framework/cli/views/webapp/protected/config/main.php
@@ -0,0 +1,90 @@
+<?php
+
+// uncomment the following to define a path alias
+// Yii::setPathOfAlias('local','path/to/local-folder');
+
+// This is the main Web application configuration. Any writable
+// CWebApplication properties can be configured here.
+return array(
+ 'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
+ 'name'=>'My Web Application',
+
+ // preloading 'log' component
+ 'preload'=>array('log'),
+
+ // autoloading model and component classes
+ 'import'=>array(
+ 'application.models.*',
+ 'application.components.*',
+ ),
+
+ 'modules'=>array(
+ // uncomment the following to enable the Gii tool
+ /*
+ 'gii'=>array(
+ 'class'=>'system.gii.GiiModule',
+ 'password'=>'Enter Your Password Here',
+ // If removed, Gii defaults to localhost only. Edit carefully to taste.
+ 'ipFilters'=>array('127.0.0.1','::1'),
+ ),
+ */
+ ),
+
+ // application components
+ 'components'=>array(
+ 'user'=>array(
+ // enable cookie-based authentication
+ 'allowAutoLogin'=>true,
+ ),
+ // uncomment the following to enable URLs in path-format
+ /*
+ 'urlManager'=>array(
+ 'urlFormat'=>'path',
+ 'rules'=>array(
+ '<controller:\w+>/<id:\d+>'=>'<controller>/view',
+ '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
+ '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
+ ),
+ ),
+ */
+ 'db'=>array(
+ 'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
+ ),
+ // uncomment the following to use a MySQL database
+ /*
+ 'db'=>array(
+ 'connectionString' => 'mysql:host=localhost;dbname=testdrive',
+ 'emulatePrepare' => true,
+ 'username' => 'root',
+ 'password' => '',
+ 'charset' => 'utf8',
+ ),
+ */
+ 'errorHandler'=>array(
+ // use 'site/error' action to display errors
+ 'errorAction'=>'site/error',
+ ),
+ 'log'=>array(
+ 'class'=>'CLogRouter',
+ 'routes'=>array(
+ array(
+ 'class'=>'CFileLogRoute',
+ 'levels'=>'error, warning',
+ ),
+ // uncomment the following to show log messages on web pages
+ /*
+ array(
+ 'class'=>'CWebLogRoute',
+ ),
+ */
+ ),
+ ),
+ ),
+
+ // application-level parameters that can be accessed
+ // using Yii::app()->params['paramName']
+ 'params'=>array(
+ // this is used in contact page
+ 'adminEmail'=>'webmaster@example.com',
+ ),
+); \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/config/test.php b/framework/cli/views/webapp/protected/config/test.php
new file mode 100644
index 0000000..fd7085a
--- /dev/null
+++ b/framework/cli/views/webapp/protected/config/test.php
@@ -0,0 +1,17 @@
+<?php
+
+return CMap::mergeArray(
+ require(dirname(__FILE__).'/main.php'),
+ array(
+ 'components'=>array(
+ 'fixture'=>array(
+ 'class'=>'system.test.CDbFixtureManager',
+ ),
+ /* uncomment the following to provide test database connection
+ 'db'=>array(
+ 'connectionString'=>'DSN for test database',
+ ),
+ */
+ ),
+ )
+);
diff --git a/framework/cli/views/webapp/protected/controllers/SiteController.php b/framework/cli/views/webapp/protected/controllers/SiteController.php
new file mode 100644
index 0000000..8d3084c
--- /dev/null
+++ b/framework/cli/views/webapp/protected/controllers/SiteController.php
@@ -0,0 +1,103 @@
+<?php
+
+class SiteController extends Controller
+{
+ /**
+ * Declares class-based actions.
+ */
+ public function actions()
+ {
+ return array(
+ // captcha action renders the CAPTCHA image displayed on the contact page
+ 'captcha'=>array(
+ 'class'=>'CCaptchaAction',
+ 'backColor'=>0xFFFFFF,
+ ),
+ // page action renders "static" pages stored under 'protected/views/site/pages'
+ // They can be accessed via: index.php?r=site/page&view=FileName
+ 'page'=>array(
+ 'class'=>'CViewAction',
+ ),
+ );
+ }
+
+ /**
+ * This is the default 'index' action that is invoked
+ * when an action is not explicitly requested by users.
+ */
+ public function actionIndex()
+ {
+ // renders the view file 'protected/views/site/index.php'
+ // using the default layout 'protected/views/layouts/main.php'
+ $this->render('index');
+ }
+
+ /**
+ * This is the action to handle external exceptions.
+ */
+ public function actionError()
+ {
+ if($error=Yii::app()->errorHandler->error)
+ {
+ if(Yii::app()->request->isAjaxRequest)
+ echo $error['message'];
+ else
+ $this->render('error', $error);
+ }
+ }
+
+ /**
+ * Displays the contact page
+ */
+ public function actionContact()
+ {
+ $model=new ContactForm;
+ if(isset($_POST['ContactForm']))
+ {
+ $model->attributes=$_POST['ContactForm'];
+ if($model->validate())
+ {
+ $headers="From: {$model->email}\r\nReply-To: {$model->email}";
+ mail(Yii::app()->params['adminEmail'],$model->subject,$model->body,$headers);
+ Yii::app()->user->setFlash('contact','Thank you for contacting us. We will respond to you as soon as possible.');
+ $this->refresh();
+ }
+ }
+ $this->render('contact',array('model'=>$model));
+ }
+
+ /**
+ * Displays the login page
+ */
+ public function actionLogin()
+ {
+ $model=new LoginForm;
+
+ // if it is ajax validation request
+ if(isset($_POST['ajax']) && $_POST['ajax']==='login-form')
+ {
+ echo CActiveForm::validate($model);
+ Yii::app()->end();
+ }
+
+ // collect user input data
+ if(isset($_POST['LoginForm']))
+ {
+ $model->attributes=$_POST['LoginForm'];
+ // validate user input and redirect to the previous page if valid
+ if($model->validate() && $model->login())
+ $this->redirect(Yii::app()->user->returnUrl);
+ }
+ // display the login form
+ $this->render('login',array('model'=>$model));
+ }
+
+ /**
+ * Logs out the current user and redirect to homepage.
+ */
+ public function actionLogout()
+ {
+ Yii::app()->user->logout();
+ $this->redirect(Yii::app()->homeUrl);
+ }
+} \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/data/schema.mysql.sql b/framework/cli/views/webapp/protected/data/schema.mysql.sql
new file mode 100644
index 0000000..32788bd
--- /dev/null
+++ b/framework/cli/views/webapp/protected/data/schema.mysql.sql
@@ -0,0 +1,28 @@
+CREATE TABLE tbl_user (
+ id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ username VARCHAR(128) NOT NULL,
+ password VARCHAR(128) NOT NULL,
+ email VARCHAR(128) NOT NULL
+);
+
+INSERT INTO tbl_user (username, password, email) VALUES ('test1', 'pass1', 'test1@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test2', 'pass2', 'test2@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test3', 'pass3', 'test3@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test4', 'pass4', 'test4@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test5', 'pass5', 'test5@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test6', 'pass6', 'test6@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test7', 'pass7', 'test7@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test8', 'pass8', 'test8@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test9', 'pass9', 'test9@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test10', 'pass10', 'test10@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test11', 'pass11', 'test11@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test12', 'pass12', 'test12@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test13', 'pass13', 'test13@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test14', 'pass14', 'test14@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test15', 'pass15', 'test15@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test16', 'pass16', 'test16@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test17', 'pass17', 'test17@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test18', 'pass18', 'test18@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test19', 'pass19', 'test19@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test20', 'pass20', 'test20@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test21', 'pass21', 'test21@example.com');
diff --git a/framework/cli/views/webapp/protected/data/schema.sqlite.sql b/framework/cli/views/webapp/protected/data/schema.sqlite.sql
new file mode 100644
index 0000000..e5e0830
--- /dev/null
+++ b/framework/cli/views/webapp/protected/data/schema.sqlite.sql
@@ -0,0 +1,28 @@
+CREATE TABLE tbl_user (
+ id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+ username VARCHAR(128) NOT NULL,
+ password VARCHAR(128) NOT NULL,
+ email VARCHAR(128) NOT NULL
+);
+
+INSERT INTO tbl_user (username, password, email) VALUES ('test1', 'pass1', 'test1@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test2', 'pass2', 'test2@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test3', 'pass3', 'test3@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test4', 'pass4', 'test4@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test5', 'pass5', 'test5@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test6', 'pass6', 'test6@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test7', 'pass7', 'test7@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test8', 'pass8', 'test8@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test9', 'pass9', 'test9@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test10', 'pass10', 'test10@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test11', 'pass11', 'test11@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test12', 'pass12', 'test12@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test13', 'pass13', 'test13@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test14', 'pass14', 'test14@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test15', 'pass15', 'test15@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test16', 'pass16', 'test16@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test17', 'pass17', 'test17@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test18', 'pass18', 'test18@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test19', 'pass19', 'test19@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test20', 'pass20', 'test20@example.com');
+INSERT INTO tbl_user (username, password, email) VALUES ('test21', 'pass21', 'test21@example.com');
diff --git a/framework/cli/views/webapp/protected/data/testdrive.db b/framework/cli/views/webapp/protected/data/testdrive.db
new file mode 100644
index 0000000..0672b21
--- /dev/null
+++ b/framework/cli/views/webapp/protected/data/testdrive.db
Binary files differ
diff --git a/framework/cli/views/webapp/protected/extensions/.yii b/framework/cli/views/webapp/protected/extensions/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/protected/extensions/.yii
diff --git a/framework/cli/views/webapp/protected/messages/.yii b/framework/cli/views/webapp/protected/messages/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/protected/messages/.yii
diff --git a/framework/cli/views/webapp/protected/migrations/.yii b/framework/cli/views/webapp/protected/migrations/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/protected/migrations/.yii
diff --git a/framework/cli/views/webapp/protected/models/ContactForm.php b/framework/cli/views/webapp/protected/models/ContactForm.php
new file mode 100644
index 0000000..86541cb
--- /dev/null
+++ b/framework/cli/views/webapp/protected/models/ContactForm.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * ContactForm class.
+ * ContactForm is the data structure for keeping
+ * contact form data. It is used by the 'contact' action of 'SiteController'.
+ */
+class ContactForm extends CFormModel
+{
+ public $name;
+ public $email;
+ public $subject;
+ public $body;
+ public $verifyCode;
+
+ /**
+ * Declares the validation rules.
+ */
+ public function rules()
+ {
+ return array(
+ // name, email, subject and body are required
+ array('name, email, subject, body', 'required'),
+ // email has to be a valid email address
+ array('email', 'email'),
+ // verifyCode needs to be entered correctly
+ array('verifyCode', 'captcha', 'allowEmpty'=>!CCaptcha::checkRequirements()),
+ );
+ }
+
+ /**
+ * Declares customized attribute labels.
+ * If not declared here, an attribute would have a label that is
+ * the same as its name with the first letter in upper case.
+ */
+ public function attributeLabels()
+ {
+ return array(
+ 'verifyCode'=>'Verification Code',
+ );
+ }
+} \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/models/LoginForm.php b/framework/cli/views/webapp/protected/models/LoginForm.php
new file mode 100644
index 0000000..eb36e4a
--- /dev/null
+++ b/framework/cli/views/webapp/protected/models/LoginForm.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * LoginForm class.
+ * LoginForm is the data structure for keeping
+ * user login form data. It is used by the 'login' action of 'SiteController'.
+ */
+class LoginForm extends CFormModel
+{
+ public $username;
+ public $password;
+ public $rememberMe;
+
+ private $_identity;
+
+ /**
+ * Declares the validation rules.
+ * The rules state that username and password are required,
+ * and password needs to be authenticated.
+ */
+ public function rules()
+ {
+ return array(
+ // username and password are required
+ array('username, password', 'required'),
+ // rememberMe needs to be a boolean
+ array('rememberMe', 'boolean'),
+ // password needs to be authenticated
+ array('password', 'authenticate'),
+ );
+ }
+
+ /**
+ * Declares attribute labels.
+ */
+ public function attributeLabels()
+ {
+ return array(
+ 'rememberMe'=>'Remember me next time',
+ );
+ }
+
+ /**
+ * Authenticates the password.
+ * This is the 'authenticate' validator as declared in rules().
+ */
+ public function authenticate($attribute,$params)
+ {
+ if(!$this->hasErrors())
+ {
+ $this->_identity=new UserIdentity($this->username,$this->password);
+ if(!$this->_identity->authenticate())
+ $this->addError('password','Incorrect username or password.');
+ }
+ }
+
+ /**
+ * Logs in the user using the given username and password in the model.
+ * @return boolean whether login is successful
+ */
+ public function login()
+ {
+ if($this->_identity===null)
+ {
+ $this->_identity=new UserIdentity($this->username,$this->password);
+ $this->_identity->authenticate();
+ }
+ if($this->_identity->errorCode===UserIdentity::ERROR_NONE)
+ {
+ $duration=$this->rememberMe ? 3600*24*30 : 0; // 30 days
+ Yii::app()->user->login($this->_identity,$duration);
+ return true;
+ }
+ else
+ return false;
+ }
+}
diff --git a/framework/cli/views/webapp/protected/runtime/.yii b/framework/cli/views/webapp/protected/runtime/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/protected/runtime/.yii
diff --git a/framework/cli/views/webapp/protected/tests/WebTestCase.php b/framework/cli/views/webapp/protected/tests/WebTestCase.php
new file mode 100644
index 0000000..d252bba
--- /dev/null
+++ b/framework/cli/views/webapp/protected/tests/WebTestCase.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Change the following URL based on your server configuration
+ * Make sure the URL ends with a slash so that we can use relative URLs in test cases
+ */
+define('TEST_BASE_URL','http://localhost/testdrive/index-test.php/');
+
+/**
+ * The base class for functional test cases.
+ * In this class, we set the base URL for the test application.
+ * We also provide some common methods to be used by concrete test classes.
+ */
+class WebTestCase extends CWebTestCase
+{
+ /**
+ * Sets up before each test method runs.
+ * This mainly sets the base URL for the test application.
+ */
+ protected function setUp()
+ {
+ parent::setUp();
+ $this->setBrowserUrl(TEST_BASE_URL);
+ }
+}
diff --git a/framework/cli/views/webapp/protected/tests/bootstrap.php b/framework/cli/views/webapp/protected/tests/bootstrap.php
new file mode 100644
index 0000000..4404cac
--- /dev/null
+++ b/framework/cli/views/webapp/protected/tests/bootstrap.php
@@ -0,0 +1,10 @@
+<?php
+
+// change the following paths if necessary
+$yiit=dirname(__FILE__).'/../../../framework/yiit.php';
+$config=dirname(__FILE__).'/../config/test.php';
+
+require_once($yiit);
+require_once(dirname(__FILE__).'/WebTestCase.php');
+
+Yii::createWebApplication($config);
diff --git a/framework/cli/views/webapp/protected/tests/fixtures/.yii b/framework/cli/views/webapp/protected/tests/fixtures/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/protected/tests/fixtures/.yii
diff --git a/framework/cli/views/webapp/protected/tests/functional/SiteTest.php b/framework/cli/views/webapp/protected/tests/functional/SiteTest.php
new file mode 100644
index 0000000..cb9727c
--- /dev/null
+++ b/framework/cli/views/webapp/protected/tests/functional/SiteTest.php
@@ -0,0 +1,47 @@
+<?php
+
+class SiteTest extends WebTestCase
+{
+ public function testIndex()
+ {
+ $this->open('');
+ $this->assertTextPresent('Welcome');
+ }
+
+ public function testContact()
+ {
+ $this->open('?r=site/contact');
+ $this->assertTextPresent('Contact Us');
+ $this->assertElementPresent('name=ContactForm[name]');
+
+ $this->type('name=ContactForm[name]','tester');
+ $this->type('name=ContactForm[email]','tester@example.com');
+ $this->type('name=ContactForm[subject]','test subject');
+ $this->click("//input[@value='Submit']");
+ $this->waitForTextPresent('Body cannot be blank.');
+ }
+
+ public function testLoginLogout()
+ {
+ $this->open('');
+ // ensure the user is logged out
+ if($this->isTextPresent('Logout'))
+ $this->clickAndWait('link=Logout (demo)');
+
+ // test login process, including validation
+ $this->clickAndWait('link=Login');
+ $this->assertElementPresent('name=LoginForm[username]');
+ $this->type('name=LoginForm[username]','demo');
+ $this->click("//input[@value='Login']");
+ $this->waitForTextPresent('Password cannot be blank.');
+ $this->type('name=LoginForm[password]','demo');
+ $this->clickAndWait("//input[@value='Login']");
+ $this->assertTextNotPresent('Password cannot be blank.');
+ $this->assertTextPresent('Logout');
+
+ // test logout process
+ $this->assertTextNotPresent('Login');
+ $this->clickAndWait('link=Logout (demo)');
+ $this->assertTextPresent('Login');
+ }
+}
diff --git a/framework/cli/views/webapp/protected/tests/phpunit.xml b/framework/cli/views/webapp/protected/tests/phpunit.xml
new file mode 100644
index 0000000..22c96ff
--- /dev/null
+++ b/framework/cli/views/webapp/protected/tests/phpunit.xml
@@ -0,0 +1,13 @@
+<phpunit bootstrap="bootstrap.php"
+ colors="false"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnFailure="false">
+
+ <selenium>
+ <browser name="Internet Explorer" browser="*iexplore" />
+ <browser name="Firefox" browser="*firefox" />
+ </selenium>
+
+</phpunit> \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/tests/report/.yii b/framework/cli/views/webapp/protected/tests/report/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/protected/tests/report/.yii
diff --git a/framework/cli/views/webapp/protected/tests/unit/.yii b/framework/cli/views/webapp/protected/tests/unit/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/protected/tests/unit/.yii
diff --git a/framework/cli/views/webapp/protected/views/layouts/column1.php b/framework/cli/views/webapp/protected/views/layouts/column1.php
new file mode 100644
index 0000000..f70b154
--- /dev/null
+++ b/framework/cli/views/webapp/protected/views/layouts/column1.php
@@ -0,0 +1,5 @@
+<?php $this->beginContent('//layouts/main'); ?>
+<div id="content">
+ <?php echo $content; ?>
+</div><!-- content -->
+<?php $this->endContent(); ?> \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/views/layouts/column2.php b/framework/cli/views/webapp/protected/views/layouts/column2.php
new file mode 100644
index 0000000..e435a69
--- /dev/null
+++ b/framework/cli/views/webapp/protected/views/layouts/column2.php
@@ -0,0 +1,21 @@
+<?php $this->beginContent('//layouts/main'); ?>
+<div class="span-19">
+ <div id="content">
+ <?php echo $content; ?>
+ </div><!-- content -->
+</div>
+<div class="span-5 last">
+ <div id="sidebar">
+ <?php
+ $this->beginWidget('zii.widgets.CPortlet', array(
+ 'title'=>'Operations',
+ ));
+ $this->widget('zii.widgets.CMenu', array(
+ 'items'=>$this->menu,
+ 'htmlOptions'=>array('class'=>'operations'),
+ ));
+ $this->endWidget();
+ ?>
+ </div><!-- sidebar -->
+</div>
+<?php $this->endContent(); ?> \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/views/layouts/main.php b/framework/cli/views/webapp/protected/views/layouts/main.php
new file mode 100644
index 0000000..f2ff75a
--- /dev/null
+++ b/framework/cli/views/webapp/protected/views/layouts/main.php
@@ -0,0 +1,58 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="language" content="en" />
+
+ <!-- blueprint CSS framework -->
+ <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/screen.css" media="screen, projection" />
+ <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/print.css" media="print" />
+ <!--[if lt IE 8]>
+ <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/ie.css" media="screen, projection" />
+ <![endif]-->
+
+ <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/main.css" />
+ <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/form.css" />
+
+ <title><?php echo CHtml::encode($this->pageTitle); ?></title>
+</head>
+
+<body>
+
+<div class="container" id="page">
+
+ <div id="header">
+ <div id="logo"><?php echo CHtml::encode(Yii::app()->name); ?></div>
+ </div><!-- header -->
+
+ <div id="mainmenu">
+ <?php $this->widget('zii.widgets.CMenu',array(
+ 'items'=>array(
+ array('label'=>'Home', 'url'=>array('/site/index')),
+ array('label'=>'About', 'url'=>array('/site/page', 'view'=>'about')),
+ array('label'=>'Contact', 'url'=>array('/site/contact')),
+ array('label'=>'Login', 'url'=>array('/site/login'), 'visible'=>Yii::app()->user->isGuest),
+ array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest)
+ ),
+ )); ?>
+ </div><!-- mainmenu -->
+ <?php if(isset($this->breadcrumbs)):?>
+ <?php $this->widget('zii.widgets.CBreadcrumbs', array(
+ 'links'=>$this->breadcrumbs,
+ )); ?><!-- breadcrumbs -->
+ <?php endif?>
+
+ <?php echo $content; ?>
+
+ <div class="clear"></div>
+
+ <div id="footer">
+ Copyright &copy; <?php echo date('Y'); ?> by My Company.<br/>
+ All Rights Reserved.<br/>
+ <?php echo Yii::powered(); ?>
+ </div><!-- footer -->
+
+</div><!-- page -->
+
+</body>
+</html>
diff --git a/framework/cli/views/webapp/protected/views/site/contact.php b/framework/cli/views/webapp/protected/views/site/contact.php
new file mode 100644
index 0000000..51f90a9
--- /dev/null
+++ b/framework/cli/views/webapp/protected/views/site/contact.php
@@ -0,0 +1,81 @@
+<?php
+$this->pageTitle=Yii::app()->name . ' - Contact Us';
+$this->breadcrumbs=array(
+ 'Contact',
+);
+?>
+
+<h1>Contact Us</h1>
+
+<?php if(Yii::app()->user->hasFlash('contact')): ?>
+
+<div class="flash-success">
+ <?php echo Yii::app()->user->getFlash('contact'); ?>
+</div>
+
+<?php else: ?>
+
+<p>
+If you have business inquiries or other questions, please fill out the following form to contact us. Thank you.
+</p>
+
+<div class="form">
+
+<?php $form=$this->beginWidget('CActiveForm', array(
+ 'id'=>'contact-form',
+ 'enableClientValidation'=>true,
+ 'clientOptions'=>array(
+ 'validateOnSubmit'=>true,
+ ),
+)); ?>
+
+ <p class="note">Fields with <span class="required">*</span> are required.</p>
+
+ <?php echo $form->errorSummary($model); ?>
+
+ <div class="row">
+ <?php echo $form->labelEx($model,'name'); ?>
+ <?php echo $form->textField($model,'name'); ?>
+ <?php echo $form->error($model,'name'); ?>
+ </div>
+
+ <div class="row">
+ <?php echo $form->labelEx($model,'email'); ?>
+ <?php echo $form->textField($model,'email'); ?>
+ <?php echo $form->error($model,'email'); ?>
+ </div>
+
+ <div class="row">
+ <?php echo $form->labelEx($model,'subject'); ?>
+ <?php echo $form->textField($model,'subject',array('size'=>60,'maxlength'=>128)); ?>
+ <?php echo $form->error($model,'subject'); ?>
+ </div>
+
+ <div class="row">
+ <?php echo $form->labelEx($model,'body'); ?>
+ <?php echo $form->textArea($model,'body',array('rows'=>6, 'cols'=>50)); ?>
+ <?php echo $form->error($model,'body'); ?>
+ </div>
+
+ <?php if(CCaptcha::checkRequirements()): ?>
+ <div class="row">
+ <?php echo $form->labelEx($model,'verifyCode'); ?>
+ <div>
+ <?php $this->widget('CCaptcha'); ?>
+ <?php echo $form->textField($model,'verifyCode'); ?>
+ </div>
+ <div class="hint">Please enter the letters as they are shown in the image above.
+ <br/>Letters are not case-sensitive.</div>
+ <?php echo $form->error($model,'verifyCode'); ?>
+ </div>
+ <?php endif; ?>
+
+ <div class="row buttons">
+ <?php echo CHtml::submitButton('Submit'); ?>
+ </div>
+
+<?php $this->endWidget(); ?>
+
+</div><!-- form -->
+
+<?php endif; ?> \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/views/site/error.php b/framework/cli/views/webapp/protected/views/site/error.php
new file mode 100644
index 0000000..4607ff3
--- /dev/null
+++ b/framework/cli/views/webapp/protected/views/site/error.php
@@ -0,0 +1,12 @@
+<?php
+$this->pageTitle=Yii::app()->name . ' - Error';
+$this->breadcrumbs=array(
+ 'Error',
+);
+?>
+
+<h2>Error <?php echo $code; ?></h2>
+
+<div class="error">
+<?php echo CHtml::encode($message); ?>
+</div> \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/views/site/index.php b/framework/cli/views/webapp/protected/views/site/index.php
new file mode 100644
index 0000000..b44b2e6
--- /dev/null
+++ b/framework/cli/views/webapp/protected/views/site/index.php
@@ -0,0 +1,16 @@
+<?php $this->pageTitle=Yii::app()->name; ?>
+
+<h1>Welcome to <i><?php echo CHtml::encode(Yii::app()->name); ?></i></h1>
+
+<p>Congratulations! You have successfully created your Yii application.</p>
+
+<p>You may change the content of this page by modifying the following two files:</p>
+<ul>
+ <li>View file: <tt><?php echo __FILE__; ?></tt></li>
+ <li>Layout file: <tt><?php echo $this->getLayoutFile('main'); ?></tt></li>
+</ul>
+
+<p>For more details on how to further develop this application, please read
+the <a href="http://www.yiiframework.com/doc/">documentation</a>.
+Feel free to ask in the <a href="http://www.yiiframework.com/forum/">forum</a>,
+should you have any questions.</p> \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/views/site/login.php b/framework/cli/views/webapp/protected/views/site/login.php
new file mode 100644
index 0000000..c53bf62
--- /dev/null
+++ b/framework/cli/views/webapp/protected/views/site/login.php
@@ -0,0 +1,49 @@
+<?php
+$this->pageTitle=Yii::app()->name . ' - Login';
+$this->breadcrumbs=array(
+ 'Login',
+);
+?>
+
+<h1>Login</h1>
+
+<p>Please fill out the following form with your login credentials:</p>
+
+<div class="form">
+<?php $form=$this->beginWidget('CActiveForm', array(
+ 'id'=>'login-form',
+ 'enableClientValidation'=>true,
+ 'clientOptions'=>array(
+ 'validateOnSubmit'=>true,
+ ),
+)); ?>
+
+ <p class="note">Fields with <span class="required">*</span> are required.</p>
+
+ <div class="row">
+ <?php echo $form->labelEx($model,'username'); ?>
+ <?php echo $form->textField($model,'username'); ?>
+ <?php echo $form->error($model,'username'); ?>
+ </div>
+
+ <div class="row">
+ <?php echo $form->labelEx($model,'password'); ?>
+ <?php echo $form->passwordField($model,'password'); ?>
+ <?php echo $form->error($model,'password'); ?>
+ <p class="hint">
+ Hint: You may login with <tt>demo/demo</tt> or <tt>admin/admin</tt>.
+ </p>
+ </div>
+
+ <div class="row rememberMe">
+ <?php echo $form->checkBox($model,'rememberMe'); ?>
+ <?php echo $form->label($model,'rememberMe'); ?>
+ <?php echo $form->error($model,'rememberMe'); ?>
+ </div>
+
+ <div class="row buttons">
+ <?php echo CHtml::submitButton('Login'); ?>
+ </div>
+
+<?php $this->endWidget(); ?>
+</div><!-- form -->
diff --git a/framework/cli/views/webapp/protected/views/site/pages/about.php b/framework/cli/views/webapp/protected/views/site/pages/about.php
new file mode 100644
index 0000000..c6c05dc
--- /dev/null
+++ b/framework/cli/views/webapp/protected/views/site/pages/about.php
@@ -0,0 +1,10 @@
+<?php
+$this->pageTitle=Yii::app()->name . ' - About';
+$this->breadcrumbs=array(
+ 'About',
+);
+?>
+<h1>About</h1>
+
+<p>This is a "static" page. You may change the content of this page
+by updating the file <tt><?php echo __FILE__; ?></tt>.</p> \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/yiic b/framework/cli/views/webapp/protected/yiic
new file mode 100644
index 0000000..7f56f54
--- /dev/null
+++ b/framework/cli/views/webapp/protected/yiic
@@ -0,0 +1,4 @@
+#!/usr/bin/env php
+<?php
+
+require_once(dirname(__FILE__).'/yiic.php');
diff --git a/framework/cli/views/webapp/protected/yiic.bat b/framework/cli/views/webapp/protected/yiic.bat
new file mode 100644
index 0000000..e8e4eda
--- /dev/null
+++ b/framework/cli/views/webapp/protected/yiic.bat
@@ -0,0 +1,16 @@
+@echo off
+
+rem -------------------------------------------------------------
+rem Yii command line script for Windows.
+rem This is the bootstrap script for running yiic on Windows.
+rem -------------------------------------------------------------
+
+@setlocal
+
+set BIN_PATH=%~dp0
+
+if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
+
+"%PHP_COMMAND%" "%BIN_PATH%yiic.php" %*
+
+@endlocal \ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/yiic.php b/framework/cli/views/webapp/protected/yiic.php
new file mode 100644
index 0000000..564d2f6
--- /dev/null
+++ b/framework/cli/views/webapp/protected/yiic.php
@@ -0,0 +1,7 @@
+<?php
+
+// change the following paths if necessary
+$yiic=dirname(__FILE__).'/../../framework/yiic.php';
+$config=dirname(__FILE__).'/config/console.php';
+
+require_once($yiic);
diff --git a/framework/cli/views/webapp/themes/classic/views/.htaccess b/framework/cli/views/webapp/themes/classic/views/.htaccess
new file mode 100644
index 0000000..8d2f256
--- /dev/null
+++ b/framework/cli/views/webapp/themes/classic/views/.htaccess
@@ -0,0 +1 @@
+deny from all
diff --git a/framework/cli/views/webapp/themes/classic/views/layouts/.yii b/framework/cli/views/webapp/themes/classic/views/layouts/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/themes/classic/views/layouts/.yii
diff --git a/framework/cli/views/webapp/themes/classic/views/site/.yii b/framework/cli/views/webapp/themes/classic/views/site/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/themes/classic/views/site/.yii
diff --git a/framework/cli/views/webapp/themes/classic/views/system/.yii b/framework/cli/views/webapp/themes/classic/views/system/.yii
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/framework/cli/views/webapp/themes/classic/views/system/.yii