1
0

Added new (clean) yii boilerplate

This commit is contained in:
2014-05-13 12:40:42 +02:00
parent 1d6d975a16
commit 99d29b432b
1983 changed files with 653465 additions and 17 deletions

View File

@@ -0,0 +1,228 @@
<?php
/**
* MessageCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 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>
* @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. Accepts both string for single function name or array for
multiple function names.
- overwrite: if message file must be overwritten with the merged messages.
- removeOld: if message no longer needs translation it will be removed,
instead of being enclosed between a pair of '@@' marks.
- sort: sort messages by key when merging, regardless of their translation
state (new, obsolete, translated.)
EOD;
}
/**
* Execute the action.
* @param array $args 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($args[0]);
$translator='Yii::t';
extract($config);
if(!isset($sourcePath,$messagePath,$languages))
$this->usageError('The configuration file must specify "sourcePath", "messagePath" and "languages".');
if(!is_dir($sourcePath))
$this->usageError("The source path $sourcePath is not a valid directory.");
if(!is_dir($messagePath))
$this->usageError("The message path $messagePath is not a valid directory.");
if(empty($languages))
$this->usageError("Languages cannot be empty.");
if(!isset($overwrite))
$overwrite = false;
if(!isset($removeOld))
$removeOld = false;
if(!isset($sort))
$sort = false;
$options=array();
if(isset($fileTypes))
$options['fileTypes']=$fileTypes;
if(isset($exclude))
$options['exclude']=$exclude;
$files=CFileHelper::findFiles(realpath($sourcePath),$options);
$messages=array();
foreach($files as $file)
$messages=array_merge_recursive($messages,$this->extractMessages($file,$translator));
foreach($languages as $language)
{
$dir=$messagePath.DIRECTORY_SEPARATOR.$language;
if(!is_dir($dir))
@mkdir($dir);
foreach($messages as $category=>$msgs)
{
$msgs=array_values(array_unique($msgs));
$this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort);
}
}
}
protected function extractMessages($fileName,$translator)
{
echo "Extracting messages from $fileName...\n";
$subject=file_get_contents($fileName);
$messages=array();
if(!is_array($translator))
$translator=array($translator);
foreach ($translator as $currentTranslator)
{
$n=preg_match_all('/\b'.$currentTranslator.'\s*\(\s*(\'[\w.\/]*?(?<!\.)\'|"[\w.]*?(?<!\.)")\s*,\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*[,\)]/s',$subject,$matches,PREG_SET_ORDER);
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,$sort)
{
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(array_key_exists($message,$translated) && strlen($translated[$message])>0)
$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)
{
if(substr($translation,0,2)==='@@' && substr($translation,-2)==='@@')
$todo[$message]=$translation;
else
$todo[$message]='@@'.$translation.'@@';
}
}
$merged=array_merge($todo,$merged);
if($sort)
ksort($merged);
if($overwrite === false)
$fileName.='.merged';
echo "translation merged.\n";
}
else
{
$merged=array();
foreach($messages as $message)
$merged[$message]='';
ksort($merged);
echo "saved.\n";
}
$array=str_replace("\r",'',var_export($merged,true));
$content=<<<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.
*/
return $array;
EOD;
file_put_contents($fileName, $content);
}
}

View File

@@ -0,0 +1,585 @@
<?php
/**
* MigrateCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 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.
*
* Since version 1.1.11 this command will exit with the following exit codes:
* <ul>
* <li>0 on success</li>
* <li>1 on general error</li>
* <li>2 on failed migration.</li>
* </ul>
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @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))
{
echo 'Error: The migration directory does not exist: '.$this->migrationPath."\n";
exit(1);
}
$this->migrationPath=$path;
$yiiVersion=Yii::getVersion();
echo "\nYii Migration Tool v1.0 (based on Yii v{$yiiVersion})\n\n";
return parent::beforeAction($action,$params);
}
public function actionUp($args)
{
if(($migrations=$this->getNewMigrations())===array())
{
echo "No new migration found. Your system is up-to-date.\n";
return 0;
}
$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 2;
}
}
echo "\nMigrated up successfully.\n";
}
}
public function actionDown($args)
{
$step=isset($args[0]) ? (int)$args[0] : 1;
if($step<1)
{
echo "Error: The step parameter must be greater than 0.\n";
return 1;
}
if(($migrations=$this->getMigrationHistory($step))===array())
{
echo "No migration has been done before.\n";
return 0;
}
$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 2;
}
}
echo "\nMigrated down successfully.\n";
}
}
public function actionRedo($args)
{
$step=isset($args[0]) ? (int)$args[0] : 1;
if($step<1)
{
echo "Error: The step parameter must be greater than 0.\n";
return 1;
}
if(($migrations=$this->getMigrationHistory($step))===array())
{
echo "No migration has been done before.\n";
return 0;
}
$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 2;
}
}
foreach(array_reverse($migrations) as $migration)
{
if($this->migrateUp($migration)===false)
{
echo "\nMigration failed. All later migrations are canceled.\n";
return 2;
}
}
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
{
echo "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";
return 1;
}
// try migrate up
$migrations=$this->getNewMigrations();
foreach($migrations as $i=>$migration)
{
if(strpos($migration,$version.'_')===0)
return $this->actionUp(array($i+1));
}
// 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";
return 0;
}
else
return $this->actionDown(array($i));
}
}
echo "Error: Unable to find the version '$originalVersion'.\n";
return 1;
}
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 {
echo "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";
return 1;
}
$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 0;
}
}
// 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 0;
}
}
echo "Error: Unable to find the version '$originalVersion'.\n";
return 1;
}
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)) {
echo "Error: The name of the migration must contain letters, digits and/or underscore characters only.\n";
return 1;
}
$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,$default=false)
{
if(!$this->interactive)
return true;
return parent::confirm($message,$default);
}
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;
elseif(($this->_db=Yii::app()->getComponent($this->connectionID)) instanceof CDbConnection)
return $this->_db;
echo "Error: CMigrationCommand.connectionID '{$this->connectionID}' is invalid. Please make sure it refers to the ID of a CDbConnection application component.\n";
exit(1);
}
protected function getMigrationHistory($limit)
{
$db=$this->getDbConnection();
if($db->schema->getTable($this->migrationTable,true)===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;
}
}

View File

@@ -0,0 +1,146 @@
<?php
/**
* ShellCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* 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>
* @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
{
}

View File

@@ -0,0 +1,213 @@
<?php
/**
* WebAppCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* WebAppCommand creates an Yii Web application at the specified location.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.cli.commands
* @since 1.0
*/
class WebAppCommand extends CConsoleCommand
{
private $_rootPath;
public function getHelp()
{
return <<<EOD
USAGE
yiic webapp <app-path> [<vcs>]
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.
* vcs: optional, version control system you're going to use in the new project.
Application generator will create all needed files to the specified VCS
(such as .gitignore, .gitkeep, etc.). Possible values: git, hg. Do not
use this argument if you're going to create VCS files yourself.
EOD;
}
/**
* Execute the action.
* @param array $args command line parameters specific for this command
*/
public function run($args)
{
$vcs=false;
if(isset($args[1]))
{
if($args[1]!='git' && $args[1]!='hg')
$this->usageError('Unsupported VCS specified. Currently only git and hg supported.');
$vcs=$args[1];
}
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;
if(basename($path)=='..')
$path.=DIRECTORY_SEPARATOR.'.';
$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=$this->getSourceDir();
if($sourceDir===false)
die("\nUnable to locate the source directory.\n");
$ignoreFiles=array();
$renameMap=array();
switch($vcs)
{
case 'git':
$renameMap=array('git-gitignore'=>'.gitignore','git-gitkeep'=>'.gitkeep'); // move with rename git files
$ignoreFiles=array('hg-hgignore','hg-hgkeep'); // ignore only hg files
break;
case 'hg':
$renameMap=array('hg-hgignore'=>'.hgignore','hg-hgkeep'=>'.hgkeep'); // move with rename hg files
$ignoreFiles=array('git-gitignore','git-gitkeep'); // ignore only git files
break;
default:
// no files for renaming
$ignoreFiles=array('git-gitignore','git-gitkeep','hg-hgignore','hg-hgkeep'); // ignore both git and hg files
break;
}
$list=$this->buildFileList($sourceDir,$path,'',$ignoreFiles,$renameMap);
$this->addFileModificationCallbacks($list);
$this->copyFiles($list);
$this->setPermissions($path);
echo "\nYour application has been created successfully under {$path}.\n";
}
}
/**
* Adjusts created application file and directory permissions
*
* @param string $targetDir path to created application
*/
protected function setPermissions($targetDir)
{
@chmod($targetDir.'/assets',0777);
@chmod($targetDir.'/protected/runtime',0777);
@chmod($targetDir.'/protected/data',0777);
@chmod($targetDir.'/protected/data/testdrive.db',0777);
@chmod($targetDir.'/protected/yiic',0755);
}
/**
* @return string path to application bootstrap source files
*/
protected function getSourceDir()
{
return realpath(dirname(__FILE__).'/../views/webapp');
}
/**
* Adds callbacks that will modify source files
*
* @param array $fileList
*/
protected function addFileModificationCallbacks(&$fileList)
{
$fileList['index.php']['callback']=array($this,'generateIndex');
$fileList['index-test.php']['callback']=array($this,'generateIndex');
$fileList['protected/tests/bootstrap.php']['callback']=array($this,'generateTestBoostrap');
$fileList['protected/yiic.php']['callback']=array($this,'generateYiic');
}
/**
* Inserts path to framework's yii.php into application's index.php
*
* @param string $source source file path
* @param array $params
* @return string modified source file content
*/
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);
}
/**
* Inserts path to framework's yiit.php into application's index-test.php
*
* @param string $source source file path
* @param array $params
* @return string modified source file 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);
}
/**
* Inserts path to framework's yiic.php into application's yiic.php
*
* @param string $source source file path
* @param array $params
* @return string modified source file 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);
}
/**
* Returns variant of $path1 relative to $path2
*
* @param string $path1
* @param string $path2
* @return string $path1 relative to $path2
*/
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).'\'';
}
}

View File

@@ -0,0 +1,175 @@
<?php
/**
* ControllerCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* ControllerCommand generates a controller class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @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 $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0]))
{
echo "Error: controller name is required.\n";
echo $this->getHelp();
return 1;
}
$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);
}
}

View File

@@ -0,0 +1,326 @@
<?php
/**
* CrudCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CrudCommand generates code implementing CRUD operations.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @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 $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0]))
{
echo "Error: data model class is required.\n";
echo $this->getHelp();
return 1;
}
$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())));
elseif(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}')";
elseif(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}')";
elseif(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)))));
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* FormCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* FormCommand generates a form view based on a specified model.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @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 $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
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 1;
}
$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))),'-');
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* HelpCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* HelpCommand displays help information for commands under yiic shell.
*
* @property string $help The command description.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.cli.commands.shell
* @since 1.0
*/
class HelpCommand extends CConsoleCommand
{
/**
* Execute the action.
* @param array $args command line parameters specific for this command
* @return integer non zero application exit code for help
*/
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();
return 1;
}
/**
* 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;
}
}

View File

@@ -0,0 +1,488 @@
<?php
/**
* ModelCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* ModelCommand generates a model class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @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 table to inspect
* @return boolean true if table matches description of helper 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 foreign 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 $tableName 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 $schema the database schema
* @param string $pattern 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));
elseif(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 $tableName the name of the table to hold the relation
* @param string $fkName the foreign key name
* @param boolean $multiple whether the relation would contain multiple objects
* @return string the generated relation name
*/
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 $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0]))
{
echo "Error: model class name is required.\n";
echo $this->getHelp();
return 1;
}
$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 1;
}
$db->active=true;
$this->_schema=$db->schema;
if(!preg_match('/^[\w\.\-\*]*(.*?)$/',$className,$matches))
{
echo "Error: model class name is invalid.\n";
return 1;
}
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;
elseif($column->type==='double')
$numerical[]=$column->name;
elseif($column->type==='string' && $column->size>0)
$length[$column->size][]=$column->name;
elseif(!$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;
}
}

View File

@@ -0,0 +1,93 @@
<?php
/**
* ModuleCommand class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 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 $args command line parameters specific for this command
* @return integer|null non zero application exit code for help or null on success
*/
public function run($args)
{
if(!isset($args[0]))
{
echo "Error: module ID is required.\n";
echo $this->getHelp();
return 1;
}
$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);
}
}

View File

@@ -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',
),
);
}
*/
}

View File

@@ -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>

View File

@@ -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 -->

View File

@@ -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 -->

View File

@@ -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>

View File

@@ -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(){
$('#<?php echo $this->class2id($modelClass); ?>-grid').yiiGridView('update', {
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',
),
),
)); ?>

View File

@@ -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();
}
}
}

View File

@@ -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)); ?>"; ?>

View File

@@ -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',
)); ?>

View File

@@ -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');
}
}

View File

@@ -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)); ?>"; ?>

View File

@@ -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";
?>
),
)); ?>

View File

@@ -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));
}

View File

@@ -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 -->

View File

@@ -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";
} ?>
),
*/
);

View File

@@ -0,0 +1,120 @@
<?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
{
/**
* @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.
*
* Typical usecase:
* - Initialize the model fields with values from filter form.
* - Execute this method to get CActiveDataProvider instance which will filter
* models according to data in model fields.
* - Pass data provider to CGridView, CListView or any similar widget.
*
* @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,
));
}
/**
* 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);
}
}

View File

@@ -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()
{
}
}

View File

@@ -0,0 +1,9 @@
<?php
class DefaultController extends Controller
{
public function actionIndex()
{
$this->render('index');
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -0,0 +1,2 @@
*
!.gitignore

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

View File

@@ -0,0 +1,164 @@
/**
* 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 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 div.success label
{
color: inherit;
}
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;
}

View File

@@ -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;}

View File

@@ -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;
}

View File

@@ -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%;}

View File

@@ -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;}

View File

@@ -0,0 +1,7 @@
syntax: glob
syntax: regexp
# ignore all except .hgkeep
^assets/(?!.*\.hgkeep$).+
^protected/runtime/(?!.*\.hgkeep$).+
^protected/tests/report/(?!.*\.hgkeep$).+

View File

@@ -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();

View File

@@ -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();

View File

@@ -0,0 +1 @@
deny from all

View File

@@ -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();
}

View File

@@ -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;
elseif($users[$this->username]!==$this->password)
$this->errorCode=self::ERROR_PASSWORD_INVALID;
else
$this->errorCode=self::ERROR_NONE;
return !$this->errorCode;
}
}

View File

@@ -0,0 +1,37 @@
<?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',
// preloading 'log' component
'preload'=>array('log'),
// 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',
),
*/
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
array(
'class'=>'CFileLogRoute',
'levels'=>'error, warning',
),
),
),
),
);

View File

@@ -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',
),
);

View File

@@ -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',
),
*/
),
)
);

View File

@@ -0,0 +1,109 @@
<?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())
{
$name='=?UTF-8?B?'.base64_encode($model->name).'?=';
$subject='=?UTF-8?B?'.base64_encode($model->subject).'?=';
$headers="From: $name <{$model->email}>\r\n".
"Reply-To: {$model->email}\r\n".
"MIME-Version: 1.0\r\n".
"Content-Type: text/plain; charset=UTF-8";
mail(Yii::app()->params['adminEmail'],$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);
}
}

View File

@@ -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');

View File

@@ -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');

Binary file not shown.

View File

@@ -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',
);
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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');
}
}

View File

@@ -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>

View File

@@ -0,0 +1,2 @@
*
!.gitignore

View File

View File

@@ -0,0 +1,6 @@
<?php /* @var $this Controller */ ?>
<?php $this->beginContent('//layouts/main'); ?>
<div id="content">
<?php echo $content; ?>
</div><!-- content -->
<?php $this->endContent(); ?>

View File

@@ -0,0 +1,22 @@
<?php /* @var $this Controller */ ?>
<?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(); ?>

View File

@@ -0,0 +1,59 @@
<?php /* @var $this Controller */ ?>
<!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>

View File

@@ -0,0 +1,85 @@
<?php
/* @var $this SiteController */
/* @var $model ContactForm */
/* @var $form CActiveForm */
$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; ?>

View File

@@ -0,0 +1,15 @@
<?php
/* @var $this SiteController */
/* @var $error array */
$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>

View File

@@ -0,0 +1,20 @@
<?php
/* @var $this SiteController */
$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: <code><?php echo __FILE__; ?></code></li>
<li>Layout file: <code><?php echo $this->getLayoutFile('main'); ?></code></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>

View File

@@ -0,0 +1,53 @@
<?php
/* @var $this SiteController */
/* @var $model LoginForm */
/* @var $form CActiveForm */
$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 <kbd>demo</kbd>/<kbd>demo</kbd> or <kbd>admin</kbd>/<kbd>admin</kbd>.
</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 -->

View File

@@ -0,0 +1,12 @@
<?php
/* @var $this SiteController */
$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 <code><?php echo __FILE__; ?></code>.</p>

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env php
<?php
require_once(dirname(__FILE__).'/yiic.php');

View File

@@ -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

View File

@@ -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);

View File

@@ -0,0 +1 @@
deny from all