diff options
Diffstat (limited to 'framework/gii')
95 files changed, 7369 insertions, 0 deletions
diff --git a/framework/gii/CCodeFile.php b/framework/gii/CCodeFile.php new file mode 100644 index 0000000..c5989b1 --- /dev/null +++ b/framework/gii/CCodeFile.php @@ -0,0 +1,135 @@ +<?php +/** + * CCodeFile class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CCodeFile represents a code file being generated. + * + * @property string $relativePath The code file path relative to the application base path. + * @property string $type The code file extension (e.g. php, txt). + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CCodeFile.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.gii + * @since 1.1.2 + */ +class CCodeFile extends CComponent +{ + const OP_NEW='new'; + const OP_OVERWRITE='overwrite'; + const OP_SKIP='skip'; + + /** + * @var string the file path that the new code should be saved to. + */ + public $path; + /** + * @var mixed the newly generated code. If this is null, it means {@link path} + * should be treated as a directory. + */ + public $content; + /** + * @var string the operation to be performed + */ + public $operation; + /** + * @var string the error occurred when saving the code into a file + */ + public $error; + + /** + * Constructor. + * @param string $path the file path that the new code should be saved to. + * @param string $content the newly generated code + */ + public function __construct($path,$content) + { + $this->path=strtr($path,array('/'=>DIRECTORY_SEPARATOR,'\\'=>DIRECTORY_SEPARATOR)); + $this->content=$content; + if(is_file($path)) + $this->operation=file_get_contents($path)===$content ? self::OP_SKIP : self::OP_OVERWRITE; + else if($content===null) // is dir + $this->operation=is_dir($path) ? self::OP_SKIP : self::OP_NEW; + else + $this->operation=self::OP_NEW; + } + + /** + * Saves the code into the file {@link path}. + */ + public function save() + { + $module=Yii::app()->controller->module; + if($this->content===null) // a directory + { + if(!is_dir($this->path)) + { + $oldmask=@umask(0); + $result=@mkdir($this->path,$module->newDirMode,true); + @umask($oldmask); + if(!$result) + { + $this->error="Unable to create the directory '{$this->path}'."; + return false; + } + } + return true; + } + + if($this->operation===self::OP_NEW) + { + $dir=dirname($this->path); + if(!is_dir($dir)) + { + $oldmask=@umask(0); + $result=@mkdir($dir,$module->newDirMode,true); + @umask($oldmask); + if(!$result) + { + $this->error="Unable to create the directory '$dir'."; + return false; + } + } + } + if(@file_put_contents($this->path,$this->content)===false) + { + $this->error="Unable to write the file '{$this->path}'."; + return false; + } + else + { + $oldmask=@umask(0); + @chmod($this->path,$module->newFileMode); + @umask($oldmask); + } + return true; + } + + /** + * @return string the code file path relative to the application base path. + */ + public function getRelativePath() + { + if(strpos($this->path,Yii::app()->basePath)===0) + return substr($this->path,strlen(Yii::app()->basePath)+1); + else + return $this->path; + } + + /** + * @return string the code file extension (e.g. php, txt) + */ + public function getType() + { + if(($pos=strrpos($this->path,'.'))!==false) + return substr($this->path,$pos+1); + else + return 'unknown'; + } +}
\ No newline at end of file diff --git a/framework/gii/CCodeForm.php b/framework/gii/CCodeForm.php new file mode 100644 index 0000000..3989172 --- /dev/null +++ b/framework/gii/CCodeForm.php @@ -0,0 +1,60 @@ +<?php +/** + * CCodeForm class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CCodeForm represents the form for collecting code generation parameters. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CCodeForm.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @package system.gii + * @since 1.1.2 + */ +class CCodeForm extends CActiveForm +{ + /** + * @var CCodeModel the code model associated with the form + */ + public $model; + + /** + * Initializes the widget. + * This renders the form open tag. + */ + public function init() + { + echo <<<EOD +<div class="form gii"> + <p class="note"> + Fields with <span class="required">*</span> are required. + Click on the <span class="sticky">highlighted fields</span> to edit them. + </p> +EOD; + parent::init(); + } + + /** + * Runs the widget. + */ + public function run() + { + $templates=array(); + foreach($this->model->getTemplates() as $i=>$template) + $templates[$i]=basename($template).' ('.$template.')'; + + $this->renderFile(Yii::getPathOfAlias('gii.views.common.generator').'.php',array( + 'model'=>$this->model, + 'templates'=>$templates, + )); + + parent::run(); + + echo "</div>"; + } +}
\ No newline at end of file diff --git a/framework/gii/CCodeGenerator.php b/framework/gii/CCodeGenerator.php new file mode 100644 index 0000000..5930251 --- /dev/null +++ b/framework/gii/CCodeGenerator.php @@ -0,0 +1,166 @@ +<?php +/** + * CCodeGenerator class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CCodeGenerator is the base class for code generator classes. + * + * CCodeGenerator is a controller that predefines several actions for code generation purpose. + * Derived classes mainly need to configure the {@link codeModel} property + * override the {@link getSuccessMessage} method. The former specifies which + * code model (extending {@link CCodeModel}) that this generator should use, + * while the latter should return a success message to be displayed when + * code files are successfully generated. + * + * @property string $pageTitle The page title. + * @property string $viewPath The view path of the generator. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CCodeGenerator.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.gii + * @since 1.1.2 + */ +class CCodeGenerator extends CController +{ + /** + * @var string the layout to be used by the generator. Defaults to 'generator'. + */ + public $layout='generator'; + /** + * @var array a list of available code templates (name=>path) + */ + public $templates=array(); + /** + * @var string the code model class. This can be either a class name (if it can be autoloaded) + * or a path alias referring to the class file. + * Child classes must configure this property with a concrete value. + */ + public $codeModel; + + private $_viewPath; + + /** + * @return string the page title + */ + public function getPageTitle() + { + return 'Gii - '.ucfirst($this->id).' Generator'; + } + + /** + * The code generation action. + * This is the action that displays the code generation interface. + * Child classes mainly need to provide the 'index' view for collecting user parameters + * for code generation. + */ + public function actionIndex() + { + $model=$this->prepare(); + if($model->files!=array() && isset($_POST['generate'], $_POST['answers'])) + { + $model->answers=$_POST['answers']; + $model->status=$model->save() ? CCodeModel::STATUS_SUCCESS : CCodeModel::STATUS_ERROR; + } + + $this->render('index',array( + 'model'=>$model, + )); + } + + /** + * The code preview action. + * This action shows up the specified generated code. + */ + public function actionCode() + { + $model=$this->prepare(); + if(isset($_GET['id']) && isset($model->files[$_GET['id']])) + { + $this->renderPartial('/common/code', array( + 'file'=>$model->files[$_GET['id']], + )); + } + else + throw new CHttpException(404,'Unable to find the code you requested.'); + } + + /** + * The code diff action. + * This action shows up the difference between the newly generated code and the corresponding existing code. + */ + public function actionDiff() + { + Yii::import('gii.components.TextDiff'); + + $model=$this->prepare(); + if(isset($_GET['id']) && isset($model->files[$_GET['id']])) + { + $file=$model->files[$_GET['id']]; + if(!in_array($file->type,array('php', 'txt','js','css'))) + $diff=false; + else if($file->operation===CCodeFile::OP_OVERWRITE) + $diff=TextDiff::compare(file_get_contents($file->path), $file->content); + else + $diff=''; + + $this->renderPartial('/common/diff',array( + 'file'=>$file, + 'diff'=>$diff, + )); + } + else + throw new CHttpException(404,'Unable to find the code you requested.'); + } + + /** + * Returns the view path of the generator. + * The "views" directory under the directory containing the generator class file will be returned. + * @return string the view path of the generator + */ + public function getViewPath() + { + if($this->_viewPath===null) + { + $class=new ReflectionClass(get_class($this)); + $this->_viewPath=dirname($class->getFileName()).DIRECTORY_SEPARATOR.'views'; + } + return $this->_viewPath; + } + + /** + * @param string $value the view path of the generator. + */ + public function setViewPath($value) + { + $this->_viewPath=$value; + } + + /** + * Prepares the code model. + */ + protected function prepare() + { + if($this->codeModel===null) + throw new CException(get_class($this).'.codeModel property must be specified.'); + $modelClass=Yii::import($this->codeModel,true); + $model=new $modelClass; + $model->loadStickyAttributes(); + if(isset($_POST[$modelClass])) + { + $model->attributes=$_POST[$modelClass]; + $model->status=CCodeModel::STATUS_PREVIEW; + if($model->validate()) + { + $model->saveStickyAttributes(); + $model->prepare(); + } + } + return $model; + } +}
\ No newline at end of file diff --git a/framework/gii/CCodeModel.php b/framework/gii/CCodeModel.php new file mode 100644 index 0000000..368c67b --- /dev/null +++ b/framework/gii/CCodeModel.php @@ -0,0 +1,482 @@ +<?php +/** + * CCodeModel class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CCodeModel is the base class for model classes that are used to generate code. + * + * Each code generator should have at least one code model class that extends from this class. + * The purpose of a code model is to represent user-supplied parameters and use them to + * generate customized code. + * + * Derived classes should implement the {@link prepare} method whose main task is to + * fill up the {@link files} property based on the user parameters. + * + * The {@link files} property should be filled with a set of {@link CCodeFile} instances, + * each representing a single code file to be generated. + * + * CCodeModel implements the feature of "sticky attributes". A sticky attribute is an attribute + * that can remember its last valid value, even if the user closes his browser window + * and reopen it. To declare an attribute is sticky, simply list it in a validation rule with + * the validator name being "sticky". + * + * @property array $templates A list of available code templates (name=>directory). + * @property string $templatePath The directory that contains the template files. + * @property string $stickyFile The file path that stores the sticky attribute values. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CCodeModel.php 3547 2012-01-24 10:07:28Z mdomba $ + * @package system.gii + * @since 1.1.2 + */ +abstract class CCodeModel extends CFormModel +{ + const STATUS_NEW=1; + const STATUS_PREVIEW=2; + const STATUS_SUCCESS=3; + const STATUS_ERROR=4; + + static $keywords=array( + '__class__', + '__dir__', + '__file__', + '__function__', + '__line__', + '__method__', + '__namespace__', + 'abstract', + 'and', + 'array', + 'as', + 'break', + 'case', + 'catch', + 'cfunction', + 'class', + 'clone', + 'const', + 'continue', + 'declare', + 'default', + 'die', + 'do', + 'echo', + 'else', + 'elseif', + 'empty', + 'enddeclare', + 'endfor', + 'endforeach', + 'endif', + 'endswitch', + 'endwhile', + 'eval', + 'exception', + 'exit', + 'extends', + 'final', + 'final', + 'for', + 'foreach', + 'function', + 'global', + 'goto', + 'if', + 'implements', + 'include', + 'include_once', + 'instanceof', + 'interface', + 'isset', + 'list', + 'namespace', + 'new', + 'old_function', + 'or', + 'parent', + 'php_user_filter', + 'print', + 'private', + 'protected', + 'public', + 'require', + 'require_once', + 'return', + 'static', + 'switch', + 'this', + 'throw', + 'try', + 'unset', + 'use', + 'var', + 'while', + 'xor', + ); + + /** + * @var array user confirmations on whether to overwrite existing code files with the newly generated ones. + * The value of this property is internally managed by this class and {@link CCodeGenerator}. + */ + public $answers; + /** + * @var string the name of the code template that the user has selected. + * The value of this property is internally managed by this class and {@link CCodeGenerator}. + */ + public $template; + /** + * @var array a list of {@link CCodeFile} objects that represent the code files to be generated. + * The {@link prepare()} method is responsible to populate this property. + */ + public $files=array(); + /** + * @var integer the status of this model. T + * The value of this property is internally managed by {@link CCodeGenerator}. + */ + public $status=self::STATUS_NEW; + + private $_stickyAttributes=array(); + + /** + * Prepares the code files to be generated. + * This is the main method that child classes should implement. It should contain the logic + * that populates the {@link files} property with a list of code files to be generated. + */ + abstract public function prepare(); + + /** + * Declares the model validation rules. + * Child classes must override this method in the following format: + * <pre> + * return array_merge(parent::rules(), array( + * ...rules for the child class... + * )); + * </pre> + * @return array validation rules + */ + public function rules() + { + return array( + array('template', 'required'), + array('template', 'validateTemplate', 'skipOnError'=>true), + array('template', 'sticky'), + ); + } + + /** + * Validates the template selection. + * This method validates whether the user selects an existing template + * and the template contains all required template files as specified in {@link requiredTemplates}. + * @param string $attribute the attribute to be validated + * @param array $params validation parameters + */ + public function validateTemplate($attribute,$params) + { + $templates=$this->templates; + if(!isset($templates[$this->template])) + $this->addError('template', 'Invalid template selection.'); + else + { + $templatePath=$this->templatePath; + foreach($this->requiredTemplates() as $template) + { + if(!is_file($templatePath.'/'.$template)) + $this->addError('template', "Unable to find the required code template file '$template'."); + } + } + } + + /** + * Checks if the named class exists (in a case sensitive manner). + * @param string $name class name to be checked + * @return boolean whether the class exists + */ + public function classExists($name) + { + return class_exists($name,false) && in_array($name, get_declared_classes()); + } + + /** + * Declares the model attribute labels. + * Child classes must override this method in the following format: + * <pre> + * return array_merge(parent::attributeLabels(), array( + * ...labels for the child class attributes... + * )); + * </pre> + * @return array the attribute labels + */ + public function attributeLabels() + { + return array( + 'template'=>'Code Template', + ); + } + + /** + * Returns a list of code templates that are required. + * Derived classes usually should override this method. + * @return array list of code templates that are required. They should be file paths + * relative to {@link templatePath}. + */ + public function requiredTemplates() + { + return array(); + } + + /** + * Saves the generated code into files. + */ + public function save() + { + $result=true; + foreach($this->files as $file) + { + if($this->confirmed($file)) + $result=$file->save() && $result; + } + return $result; + } + + /** + * Returns the message to be displayed when the newly generated code is saved successfully. + * Child classes should override this method if the message needs to be customized. + * @return string the message to be displayed when the newly generated code is saved successfully. + */ + public function successMessage() + { + return 'The code has been generated successfully.'; + } + + /** + * Returns the message to be displayed when some error occurred during code file saving. + * Child classes should override this method if the message needs to be customized. + * @return string the message to be displayed when some error occurred during code file saving. + */ + public function errorMessage() + { + return 'There was some error when generating the code. Please check the following messages.'; + } + + /** + * Returns a list of available code templates (name=>directory). + * This method simply returns the {@link CCodeGenerator::templates} property value. + * @return array a list of available code templates (name=>directory). + */ + public function getTemplates() + { + return Yii::app()->controller->templates; + } + + /** + * @return string the directory that contains the template files. + * @throw CException if {@link templates} is empty or template selection is invalid + */ + public function getTemplatePath() + { + $templates=$this->getTemplates(); + if(isset($templates[$this->template])) + return $templates[$this->template]; + else if(empty($templates)) + throw new CHttpException(500,'No templates are available.'); + else + throw new CHttpException(500,'Invalid template selection.'); + + } + + /** + * @param CCodeFile $file whether the code file should be saved + */ + public function confirmed($file) + { + return $this->answers===null && $file->operation===CCodeFile::OP_NEW + || is_array($this->answers) && isset($this->answers[md5($file->path)]); + } + + /** + * Generates the code using the specified code template file. + * This method is manly used in {@link generate} to generate code. + * @param string $templateFile the code template file path + * @param array $_params_ a set of parameters to be extracted and made available in the code template + * @return string the generated code + */ + public function render($templateFile,$_params_=null) + { + if(!is_file($templateFile)) + throw new CException("The template file '$templateFile' does not exist."); + + if(is_array($_params_)) + extract($_params_,EXTR_PREFIX_SAME,'params'); + else + $params=$_params_; + ob_start(); + ob_implicit_flush(false); + require($templateFile); + return ob_get_clean(); + } + + /** + * @return string the code generation result log. + */ + public function renderResults() + { + $output='Generating code using template "'.$this->templatePath."\"...\n"; + foreach($this->files as $file) + { + if($file->error!==null) + $output.="<span class=\"error\">generating {$file->relativePath}<br/> {$file->error}</span>\n"; + else if($file->operation===CCodeFile::OP_NEW && $this->confirmed($file)) + $output.=' generated '.$file->relativePath."\n"; + else if($file->operation===CCodeFile::OP_OVERWRITE && $this->confirmed($file)) + $output.=' overwrote '.$file->relativePath."\n"; + else + $output.=' skipped '.$file->relativePath."\n"; + } + $output.="done!\n"; + return $output; + } + + /** + * The "sticky" validator. + * This validator does not really validate the attributes. + * It actually saves the attribute value in a file to make it sticky. + * @param string $attribute the attribute to be validated + * @param array $params the validation parameters + */ + public function sticky($attribute,$params) + { + if(!$this->hasErrors()) + $this->_stickyAttributes[$attribute]=$this->$attribute; + } + + /** + * Loads sticky attributes from a file and populates them into the model. + */ + public function loadStickyAttributes() + { + $this->_stickyAttributes=array(); + $path=$this->getStickyFile(); + if(is_file($path)) + { + $result=@include($path); + if(is_array($result)) + { + $this->_stickyAttributes=$result; + foreach($this->_stickyAttributes as $name=>$value) + { + if(property_exists($this,$name) || $this->canSetProperty($name)) + $this->$name=$value; + } + } + } + } + + /** + * Saves sticky attributes into a file. + */ + public function saveStickyAttributes() + { + $path=$this->getStickyFile(); + @mkdir(dirname($path),0755,true); + file_put_contents($path,"<?php\nreturn ".var_export($this->_stickyAttributes,true).";\n"); + } + + /** + * @return string the file path that stores the sticky attribute values. + */ + public function getStickyFile() + { + return Yii::app()->runtimePath.'/gii-'.Yii::getVersion().'/'.get_class($this).'.php'; + } + + /** + * Converts a word to its plural form. + * Note that this is for English only! + * For example, 'apple' will become 'apples', and 'child' will become 'children'. + * @param string $name the word to be pluralized + * @return string the pluralized word + */ + public function pluralize($name) + { + $rules=array( + '/move$/i' => 'moves', + '/foot$/i' => 'feet', + '/child$/i' => 'children', + '/human$/i' => 'humans', + '/man$/i' => 'men', + '/tooth$/i' => 'teeth', + '/person$/i' => 'people', + '/([m|l])ouse$/i' => '\1ice', + '/(x|ch|ss|sh|us|as|is|os)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/(shea|lea|loa|thie)f$/i' => '\1ves', + '/([ti])um$/i' => '\1a', + '/(tomat|potat|ech|her|vet)o$/i' => '\1oes', + '/(bu)s$/i' => '\1ses', + '/(ax|test)is$/i' => '\1es', + '/s$/' => 's', + ); + foreach($rules as $rule=>$replacement) + { + if(preg_match($rule,$name)) + return preg_replace($rule,$replacement,$name); + } + return $name.'s'; + } + + /** + * Converts a class name into a HTML ID. + * For example, 'PostTag' will be converted as 'post-tag'. + * @param string $name the string to be converted + * @return string the resulting ID + */ + public function class2id($name) + { + return trim(strtolower(str_replace('_','-',preg_replace('/(?<![A-Z])[A-Z]/', '-\0', $name))),'-'); + } + + /** + * Converts a class name into space-separated words. + * For example, 'PostTag' will be converted as 'Post Tag'. + * @param string $name the string to be converted + * @param boolean $ucwords whether to capitalize the first letter in each word + * @return string the resulting words + */ + public function class2name($name,$ucwords=true) + { + $result=trim(strtolower(str_replace('_',' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $name)))); + return $ucwords ? ucwords($result) : $result; + } + + /** + * Converts a class name into a variable name with the first letter in lower case. + * This method is provided because lcfirst() PHP function is only available for PHP 5.3+. + * @param string $name the class name + * @return string the variable name converted from the class name + * @since 1.1.4 + */ + public function class2var($name) + { + $name[0]=strtolower($name[0]); + return $name; + } + + /** + * Validates an attribute to make sure it is not taking a PHP reserved keyword. + * @param string $attribute the attribute to be validated + * @param array $params validation parameters + */ + public function validateReservedWord($attribute,$params) + { + $value=$this->$attribute; + if(in_array(strtolower($value),self::$keywords)) + $this->addError($attribute, $this->getAttributeLabel($attribute).' cannot take a reserved PHP keyword.'); + } +}
\ No newline at end of file diff --git a/framework/gii/GiiModule.php b/framework/gii/GiiModule.php new file mode 100644 index 0000000..ebe75c0 --- /dev/null +++ b/framework/gii/GiiModule.php @@ -0,0 +1,239 @@ +<?php +/** + * GiiModule class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +Yii::import('system.gii.CCodeGenerator'); +Yii::import('system.gii.CCodeModel'); +Yii::import('system.gii.CCodeFile'); +Yii::import('system.gii.CCodeForm'); + +/** + * GiiModule is a module that provides Web-based code generation capabilities. + * + * To use GiiModule, you must include it as a module in the application configuration like the following: + * <pre> + * return array( + * ...... + * 'modules'=>array( + * 'gii'=>array( + * 'class'=>'system.gii.GiiModule', + * 'password'=>***choose a password*** + * ), + * ), + * ) + * </pre> + * + * Because GiiModule generates new code files on the server, you should only use it on your own + * development machine. To prevent other people from using this module, it is required that + * you specify a secret password in the configuration. Later when you access + * the module via browser, you will be prompted to enter the correct password. + * + * By default, GiiModule can only be accessed by localhost. You may configure its {@link ipFilters} + * property if you want to make it accessible on other machines. + * + * With the above configuration, you will be able to access GiiModule in your browser using + * the following URL: + * + * http://localhost/path/to/index.php?r=gii + * + * If your application is using path-format URLs with some customized URL rules, you may need to add + * the following URLs in your application configuration in order to access GiiModule: + * <pre> + * 'components'=>array( + * 'urlManager'=>array( + * 'urlFormat'=>'path', + * 'rules'=>array( + * 'gii'=>'gii', + * 'gii/<controller:\w+>'=>'gii/<controller>', + * 'gii/<controller:\w+>/<action:\w+>'=>'gii/<controller>/<action>', + * ...other rules... + * ), + * ) + * ) + * </pre> + * + * You can then access GiiModule via: + * + * http://localhost/path/to/index.php/gii + * + * @property string $assetsUrl The base URL that contains all published asset files of gii. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: GiiModule.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.gii + * @since 1.1.2 + */ +class GiiModule extends CWebModule +{ + /** + * @var string the password that can be used to access GiiModule. + * If this property is set false, then GiiModule can be accessed without password + * (DO NOT DO THIS UNLESS YOU KNOW THE CONSEQUENCE!!!) + */ + public $password; + /** + * @var array the IP filters that specify which IP addresses are allowed to access GiiModule. + * Each array element represents a single filter. A filter can be either an IP address + * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. + * If you want to allow all IPs to access gii, you may set this property to be false + * (DO NOT DO THIS UNLESS YOU KNOW THE CONSEQUENCE!!!) + * The default value is array('127.0.0.1', '::1'), which means GiiModule can only be accessed + * on the localhost. + */ + public $ipFilters=array('127.0.0.1','::1'); + /** + * @var array a list of path aliases that refer to the directories containing code generators. + * The directory referred by a single path alias may contain multiple code generators, each stored + * under a sub-directory whose name is the generator name. + * Defaults to array('application.gii'). + */ + public $generatorPaths=array('application.gii'); + /** + * @var integer the permission to be set for newly generated code files. + * This value will be used by PHP chmod function. + * Defaults to 0666, meaning the file is read-writable by all users. + */ + public $newFileMode=0666; + /** + * @var integer the permission to be set for newly generated directories. + * This value will be used by PHP chmod function. + * Defaults to 0777, meaning the directory can be read, written and executed by all users. + */ + public $newDirMode=0777; + + private $_assetsUrl; + + /** + * Initializes the gii module. + */ + public function init() + { + parent::init(); + Yii::app()->setComponents(array( + 'errorHandler'=>array( + 'class'=>'CErrorHandler', + 'errorAction'=>$this->getId().'/default/error', + ), + 'user'=>array( + 'class'=>'CWebUser', + 'stateKeyPrefix'=>'gii', + 'loginUrl'=>Yii::app()->createUrl($this->getId().'/default/login'), + ), + ), false); + $this->generatorPaths[]='gii.generators'; + $this->controllerMap=$this->findGenerators(); + } + + /** + * @return string the base URL that contains all published asset files of gii. + */ + public function getAssetsUrl() + { + if($this->_assetsUrl===null) + $this->_assetsUrl=Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('gii.assets')); + return $this->_assetsUrl; + } + + /** + * @param string $value the base URL that contains all published asset files of gii. + */ + public function setAssetsUrl($value) + { + $this->_assetsUrl=$value; + } + + /** + * Performs access check to gii. + * This method will check to see if user IP and password are correct if they attempt + * to access actions other than "default/login" and "default/error". + * @param CController $controller the controller to be accessed. + * @param CAction $action the action to be accessed. + * @return boolean whether the action should be executed. + */ + public function beforeControllerAction($controller, $action) + { + if(parent::beforeControllerAction($controller, $action)) + { + $route=$controller->id.'/'.$action->id; + if(!$this->allowIp(Yii::app()->request->userHostAddress) && $route!=='default/error') + throw new CHttpException(403,"You are not allowed to access this page."); + + $publicPages=array( + 'default/login', + 'default/error', + ); + if($this->password!==false && Yii::app()->user->isGuest && !in_array($route,$publicPages)) + Yii::app()->user->loginRequired(); + else + return true; + } + return false; + } + + /** + * Checks to see if the user IP is allowed by {@link ipFilters}. + * @param string $ip the user IP + * @return boolean whether the user IP is allowed by {@link ipFilters}. + */ + protected function allowIp($ip) + { + if(empty($this->ipFilters)) + return true; + foreach($this->ipFilters as $filter) + { + if($filter==='*' || $filter===$ip || (($pos=strpos($filter,'*'))!==false && !strncmp($ip,$filter,$pos))) + return true; + } + return false; + } + + /** + * Finds all available code generators and their code templates. + * @return array + */ + protected function findGenerators() + { + $generators=array(); + $n=count($this->generatorPaths); + for($i=$n-1;$i>=0;--$i) + { + $alias=$this->generatorPaths[$i]; + $path=Yii::getPathOfAlias($alias); + if($path===false || !is_dir($path)) + continue; + + $names=scandir($path); + foreach($names as $name) + { + if($name[0]!=='.' && is_dir($path.'/'.$name)) + { + $className=ucfirst($name).'Generator'; + if(is_file("$path/$name/$className.php")) + { + $generators[$name]=array( + 'class'=>"$alias.$name.$className", + ); + } + + if(isset($generators[$name]) && is_dir("$path/$name/templates")) + { + $templatePath="$path/$name/templates"; + $dirs=scandir($templatePath); + foreach($dirs as $dir) + { + if($dir[0]!=='.' && is_dir($templatePath.'/'.$dir)) + $generators[$name]['templates'][$dir]=strtr($templatePath.'/'.$dir,array('/'=>DIRECTORY_SEPARATOR,'\\'=>DIRECTORY_SEPARATOR)); + } + } + } + } + } + return $generators; + } +}
\ No newline at end of file diff --git a/framework/gii/assets/css/ie.css b/framework/gii/assets/css/ie.css new file mode 100644 index 0000000..f336f0e --- /dev/null +++ b/framework/gii/assets/css/ie.css @@ -0,0 +1,35 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 0.9 + 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 div.span-1, * html div.span-2, * html div.span-3, * html div.span-4, * html div.span-5, * html div.span-6, * html div.span-7, * html div.span-8, * html div.span-9, * html div.span-10, * html div.span-11, * html div.span-12, * html div.span-13, * html div.span-14, * html div.span-15, * html div.span-16, * html div.span-17, * html div.span-18, * html div.span-19, * html div.span-20, * html div.span-21, * html div.span-22, * html div.span-23, * html div.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;} +textarea {overflow:auto;} +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 label {position:relative;top:-0.25em;} +form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;} +button, input.button {position:relative;top:0.25em;}
\ No newline at end of file diff --git a/framework/gii/assets/css/main.css b/framework/gii/assets/css/main.css new file mode 100644 index 0000000..f569d77 --- /dev/null +++ b/framework/gii/assets/css/main.css @@ -0,0 +1,529 @@ +body +{ + margin: 0; + padding: 0; + color: #555; + font: normal 10pt Arial,Helvetica,sans-serif; + font: Verdana,"DejaVu Sans","Bitstream Vera Sans",Geneva,sans-serif; + background: #EFEFEF; +} + +h1 +{ + font-size: 1.6em; + color: #666; +} + +h2 +{ + font-size: 1.4em; + color: #666; +} + +h3 +{ + font-size: 1.2em; + color: #666; +} + +#page +{ + margin-top: 5px; + margin-bottom: 5px; + background: white; + border: 1px solid #C9E0ED; +} + +#header +{ + padding: 0px; + margin: 0px 20px; + border-bottom: 1px solid #C9E0ED; +} + +#content +{ + padding: 20px; + min-height: 400px; +} + +#sidebar +{ + padding: 20px 0 20px 20px; +} + +#footer +{ + margin: 0 auto; + width: 950px; + font-size: 0.8em; + text-align: center; +} + +#logo +{ + padding: 5px 0px; +} + +#logo a +{ + text-decoration: none; +} + +#header .top-menus +{ + margin: 20px 0px; + float: right; +} + +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.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: #79B4DC; + border-left: 5px solid #6293B3; +} + +.portlet-title +{ + font-size: 12px; + font-weight: bold; + padding: 0; + margin: 0; + color: white; +} + +.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; +} + +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 .row.buttons +{ + padding: 5px; + margin: 10px 0; +} + +div.form .row.buttons input +{ + margin: 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, +div.form label.error, +div.form span.error +{ + color: #C00; +} + +div.form div.error input, +div.form div.error textarea, +div.form div.error select, +div.form input.error, +div.form textarea.error, +div.form select.error +{ + background: #FEE; + border-color: #C00; +} + +div.form div.success input, +div.form div.success textarea, +div.form div.success select, +div.form input.success, +div.form textarea.success, +div.form select.success +{ + background: #E6EFC2; + border-color: #C6D880; +} + + +div.form .errorSummary +{ + border: 2px solid #C00; + padding: 7px 7px 12px 7px; + margin: 0 0 20px 0; + background: #FEE; + font-size: 0.9em; +} + +div.form .errorMessage +{ + color: red; + font-size: 0.9em; +} + +div.form .errorSummary p +{ + margin: 0; + padding: 5px; +} + +div.form .errorSummary ul +{ + margin: 0; + padding: 0 0 0 20px; +} + +div.wide.form label +{ + float: left; + margin-right: 10px; + position: relative; + text-align: right; + width: 100px; +} + +div.wide.form .row +{ + clear: left; +} + +div.wide.form .buttons, div.wide.form .hint, div.wide.form .errorMessage +{ + clear: left; + padding-left: 110px; +} + +div.form .tooltip +{ + display: none; + background-color:#EFFDFF; + border:1px solid #79B4DC; + padding: 10px; + width: 300px; +} + +div.form .tooltip ul +{ + margin: 0; + padding: 10px 0 0 20px; +} + +div.form .tooltip code +{ + color: #CA0EE3; + font-size:0.9em; +} + +div.form.login +{ + border: 1px solid #C9E0ED; + width: 200px; + margin: 0 auto; + margin-top: 50px; + margin-bottom: 50px; + padding: 20px 10px 10px 10px; + text-align: center; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +div.form.login p +{ + margin: 0 0 10px 0; +} + +div.form.gii .row.sticky .value, span.sticky +{ + padding: 3px; + background: lightyellow; +} + +div.form.gii .row.template select +{ + width: 420px; +} + +div.form.gii table.preview +{ + border-collapse: collapse; +} + +div.form.gii table.preview th +{ + text-align: center; +} + +div.form.gii table.preview th.confirm +{ + text-align: right; +} + +div.form.gii table.preview th.confirm label +{ + display: inline; +} + +div.form.gii table.preview td.confirm +{ + width: 80px; + text-align: right; +} + +div.form.gii table.preview td.confirm input +{ + margin:0; +} + +div.form.gii table.preview td.confirm label +{ + display: inline; + font-weight: normal; +} + +div.form.gii table.preview, +div.form.gii table.preview th, +div.form.gii table.preview td +{ + border: 1px solid #529EC6; +} + +div.form.gii table.preview tr.skip +{ + background-color: #eee; +} + +div.form.gii table.preview tr.new +{ + background-color: #C5FBBD; +} + +div.form.gii table.preview tr.overwrite +{ + background-color: #FFE0E1; +} + +div.form.gii pre.results +{ + overflow: auto; + background-color: gray; + max-height: 300px; + color: white; + padding: 10px; +} + +div.form.gii div.success +{ + background: #C5FBBD; + border: 1px solid #76C376; + padding: 10px; + margin: 10px 0; +} + +div.form.gii div.error +{ + background: #FFE0E1; + border: 1px solid #FFA0A2; + padding: 10px; + margin: 10px 0; +} + +div.form.gii div.success code +{ + overflow: auto; + display: block; + padding: 5px; + font-size: 12px; + background: white; +} + +div.form.gii pre.results span.error +{ + background: #FFE0E1; + color: black; + padding: 1px; +} + +#fancybox-inner .error +{ + color: red; +} + +#fancybox-inner .title +{ + font-size: 12px; + font-weight: bold; + text-decoration: underline; +} + +#fancybox-inner .buttons +{ + float: right; + padding: 0 10px 0 0; +} + +#fancybox-inner .content +{ + background: #F0F4FF; + text-align: left; +} + +#fancybox-inner pre.diff +{ + margin:0; +} + +#fancybox-inner pre.diff del +{ + background: pink; +} + +#fancybox-inner pre.diff ins +{ + background: lightgreen; + text-decoration: none; +} + +#fancybox-wrap #tip7-title +{ + text-align: left; +} + +#fancybox-wrap #tip7-title b +{ + display: block; +} + +#fancybox-wrap #tip7-title span +{ + float: right; +} diff --git a/framework/gii/assets/css/print.css b/framework/gii/assets/css/print.css new file mode 100644 index 0000000..fdb8220 --- /dev/null +++ b/framework/gii/assets/css/print.css @@ -0,0 +1,29 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 0.9 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* print.css */ +body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;} +.container {background:none;} +hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;} +hr.space {background:#fff;color:#fff;visibility:hidden;} +h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;} +code {font:.9em "Courier New", Monaco, Courier, monospace;} +a img {border:none;} +p img.top {margin-top:0;} +blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;} +.small {font-size:.9em;} +.large {font-size:1.1em;} +.quiet {color:#999;} +.hide {display:none;} +a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;} +a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;}
\ No newline at end of file diff --git a/framework/gii/assets/css/screen.css b/framework/gii/assets/css/screen.css new file mode 100644 index 0000000..98eef32 --- /dev/null +++ b/framework/gii/assets/css/screen.css @@ -0,0 +1,235 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 0.9 + 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, 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 {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} +body {line-height:1.5;} +table {border-collapse:separate;border-spacing:0;} +caption, th, td {text-align:left;font-weight:normal;} +table, td, th {vertical-align:middle;} +blockquote:before, blockquote:after, q:before, q:after {content:"";} +blockquote, q {quotes:"" "";} +a img {border:none;} + +/* 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;} +p img.left {float:left;margin:1.5em 1.5em 1.5em 0;padding:0;} +p img.right {float:right;margin:1.5em 0 1.5em 1.5em;} +a:focus, a:hover {color:#000;} +a {color:#009;text-decoration:underline;} +blockquote {margin:1.5em;color:#666;font-style:italic;} +strong {font-weight:bold;} +em, dfn {font-style:italic;} +dfn {font-weight:bold;} +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:3.333em;} +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;} +.showgrid {background:url(src/grid.png);} +.column, div.span-1, div.span-2, div.span-3, div.span-4, div.span-5, div.span-6, div.span-7, div.span-8, div.span-9, div.span-10, div.span-11, div.span-12, div.span-13, div.span-14, div.span-15, div.span-16, div.span-17, div.span-18, div.span-19, div.span-20, div.span-21, div.span-22, div.span-23, div.span-24 {float:left;margin-right:10px;} +.last, div.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, div.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!important;border-right-width:1px!important;padding-left:5px!important;padding-right:5px!important;} +input.span-1, textarea.span-1 {width:18px!important;} +input.span-2, textarea.span-2 {width:58px!important;} +input.span-3, textarea.span-3 {width:98px!important;} +input.span-4, textarea.span-4 {width:138px!important;} +input.span-5, textarea.span-5 {width:178px!important;} +input.span-6, textarea.span-6 {width:218px!important;} +input.span-7, textarea.span-7 {width:258px!important;} +input.span-8, textarea.span-8 {width:298px!important;} +input.span-9, textarea.span-9 {width:338px!important;} +input.span-10, textarea.span-10 {width:378px!important;} +input.span-11, textarea.span-11 {width:418px!important;} +input.span-12, textarea.span-12 {width:458px!important;} +input.span-13, textarea.span-13 {width:498px!important;} +input.span-14, textarea.span-14 {width:538px!important;} +input.span-15, textarea.span-15 {width:578px!important;} +input.span-16, textarea.span-16 {width:618px!important;} +input.span-17, textarea.span-17 {width:658px!important;} +input.span-18, textarea.span-18 {width:698px!important;} +input.span-19, textarea.span-19 {width:738px!important;} +input.span-20, textarea.span-20 {width:778px!important;} +input.span-21, textarea.span-21 {width:818px!important;} +input.span-22, textarea.span-22 {width:858px!important;} +input.span-23, textarea.span-23 {width:898px!important;} +input.span-24, textarea.span-24 {width:938px!important;} +.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;} +div.border {padding-right:4px;margin-right:5px;border-right:1px solid #eee;} +div.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #eee;} +.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:right;position:relative;} +.prepend-top {margin-top:1.5em;} +.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:.1em;margin:0 0 1.45em;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;}
\ No newline at end of file diff --git a/framework/gii/assets/images/logo.png b/framework/gii/assets/images/logo.png Binary files differnew file mode 100644 index 0000000..a920241 --- /dev/null +++ b/framework/gii/assets/images/logo.png diff --git a/framework/gii/assets/js/fancybox/blank.gif b/framework/gii/assets/js/fancybox/blank.gif Binary files differnew file mode 100644 index 0000000..35d42e8 --- /dev/null +++ b/framework/gii/assets/js/fancybox/blank.gif diff --git a/framework/gii/assets/js/fancybox/fancy_close.png b/framework/gii/assets/js/fancybox/fancy_close.png Binary files differnew file mode 100644 index 0000000..20bf870 --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_close.png diff --git a/framework/gii/assets/js/fancybox/fancy_loading.png b/framework/gii/assets/js/fancybox/fancy_loading.png Binary files differnew file mode 100644 index 0000000..86759ea --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_loading.png diff --git a/framework/gii/assets/js/fancybox/fancy_nav_left.png b/framework/gii/assets/js/fancybox/fancy_nav_left.png Binary files differnew file mode 100644 index 0000000..69ebc3e --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_nav_left.png diff --git a/framework/gii/assets/js/fancybox/fancy_nav_right.png b/framework/gii/assets/js/fancybox/fancy_nav_right.png Binary files differnew file mode 100644 index 0000000..99cb3dd --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_nav_right.png diff --git a/framework/gii/assets/js/fancybox/fancy_shadow_e.png b/framework/gii/assets/js/fancybox/fancy_shadow_e.png Binary files differnew file mode 100644 index 0000000..e29ddba --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_shadow_e.png diff --git a/framework/gii/assets/js/fancybox/fancy_shadow_n.png b/framework/gii/assets/js/fancybox/fancy_shadow_n.png Binary files differnew file mode 100644 index 0000000..f850840 --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_shadow_n.png diff --git a/framework/gii/assets/js/fancybox/fancy_shadow_ne.png b/framework/gii/assets/js/fancybox/fancy_shadow_ne.png Binary files differnew file mode 100644 index 0000000..a340f27 --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_shadow_ne.png diff --git a/framework/gii/assets/js/fancybox/fancy_shadow_nw.png b/framework/gii/assets/js/fancybox/fancy_shadow_nw.png Binary files differnew file mode 100644 index 0000000..a17c0fe --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_shadow_nw.png diff --git a/framework/gii/assets/js/fancybox/fancy_shadow_s.png b/framework/gii/assets/js/fancybox/fancy_shadow_s.png Binary files differnew file mode 100644 index 0000000..ceba06b --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_shadow_s.png diff --git a/framework/gii/assets/js/fancybox/fancy_shadow_se.png b/framework/gii/assets/js/fancybox/fancy_shadow_se.png Binary files differnew file mode 100644 index 0000000..567f2e7 --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_shadow_se.png diff --git a/framework/gii/assets/js/fancybox/fancy_shadow_sw.png b/framework/gii/assets/js/fancybox/fancy_shadow_sw.png Binary files differnew file mode 100644 index 0000000..35dbec8 --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_shadow_sw.png diff --git a/framework/gii/assets/js/fancybox/fancy_shadow_w.png b/framework/gii/assets/js/fancybox/fancy_shadow_w.png Binary files differnew file mode 100644 index 0000000..7843c2a --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_shadow_w.png diff --git a/framework/gii/assets/js/fancybox/fancy_title_left.png b/framework/gii/assets/js/fancybox/fancy_title_left.png Binary files differnew file mode 100644 index 0000000..2a68d05 --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_title_left.png diff --git a/framework/gii/assets/js/fancybox/fancy_title_main.png b/framework/gii/assets/js/fancybox/fancy_title_main.png Binary files differnew file mode 100644 index 0000000..ad0d91f --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_title_main.png diff --git a/framework/gii/assets/js/fancybox/fancy_title_over.png b/framework/gii/assets/js/fancybox/fancy_title_over.png Binary files differnew file mode 100644 index 0000000..75b2bac --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_title_over.png diff --git a/framework/gii/assets/js/fancybox/fancy_title_right.png b/framework/gii/assets/js/fancybox/fancy_title_right.png Binary files differnew file mode 100644 index 0000000..fa9318e --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancy_title_right.png diff --git a/framework/gii/assets/js/fancybox/fancybox-x.png b/framework/gii/assets/js/fancybox/fancybox-x.png Binary files differnew file mode 100644 index 0000000..09f8646 --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancybox-x.png diff --git a/framework/gii/assets/js/fancybox/fancybox-y.png b/framework/gii/assets/js/fancybox/fancybox-y.png Binary files differnew file mode 100644 index 0000000..9f6301d --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancybox-y.png diff --git a/framework/gii/assets/js/fancybox/fancybox.png b/framework/gii/assets/js/fancybox/fancybox.png Binary files differnew file mode 100644 index 0000000..f546239 --- /dev/null +++ b/framework/gii/assets/js/fancybox/fancybox.png diff --git a/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.css b/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.css new file mode 100644 index 0000000..fd71d74 --- /dev/null +++ b/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.css @@ -0,0 +1,363 @@ +/* + * FancyBox - jQuery Plugin + * Simple and fancy lightbox alternative + * + * Examples and documentation at: http://fancybox.net + * + * Copyright (c) 2008 - 2010 Janis Skarnelis + * + * Version: 1.3.1 (05/03/2010) + * Requires: jQuery v1.3+ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +#fancybox-loading { + position: fixed; + top: 50%; + left: 50%; + height: 40px; + width: 40px; + margin-top: -20px; + margin-left: -20px; + cursor: pointer; + overflow: hidden; + z-index: 1104; + display: none; +} + +* html #fancybox-loading { /* IE6 */ + position: absolute; + margin-top: 0; +} + +#fancybox-loading div { + position: absolute; + top: 0; + left: 0; + width: 40px; + height: 480px; + background-image: url('fancybox.png'); +} + +#fancybox-overlay { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: #000; + z-index: 1100; + display: none; +} + +* html #fancybox-overlay { /* IE6 */ + position: absolute; + width: 100%; +} + +#fancybox-tmp { + padding: 0; + margin: 0; + border: 0; + overflow: auto; + display: none; +} + +#fancybox-wrap { + position: absolute; + top: 0; + left: 0; + margin: 0; + padding: 20px; + z-index: 1101; + display: none; +} + +#fancybox-outer { + position: relative; + width: 100%; + height: 100%; + background: #FFF; +} + +#fancybox-inner { + position: absolute; + top: 0; + left: 0; + width: 1px; + height: 1px; + padding: 0; + margin: 0; + outline: none; + overflow: hidden; +} + +#fancybox-hide-sel-frame { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: transparent; +} + +#fancybox-close { + position: absolute; + top: -15px; + right: -15px; + width: 30px; + height: 30px; + background-image: url('fancybox.png'); + background-position: -40px 0px; + cursor: pointer; + z-index: 1103; + display: none; +} + +#fancybox_error { + color: #444; + font: normal 12px/20px Arial; + padding: 7px; + margin: 0; +} + +#fancybox-content { + height: auto; + width: auto; + padding: 0; + margin: 0; +} + +#fancybox-img { + width: 100%; + height: 100%; + padding: 0; + margin: 0; + border: none; + outline: none; + line-height: 0; + vertical-align: top; + -ms-interpolation-mode: bicubic; +} + +#fancybox-frame { + position: relative; + width: 100%; + height: 100%; + border: none; + display: block; +} + +#fancybox-title { + position: absolute; + bottom: 0; + left: 0; + font-family: Arial; + font-size: 12px; + z-index: 1102; +} + +.fancybox-title-inside { + padding: 10px 0; + text-align: center; + color: #333; +} + +.fancybox-title-outside { + padding-top: 5px; + color: #FFF; + text-align: center; + font-weight: bold; +} + +.fancybox-title-over { + color: #FFF; + text-align: left; +} + +#fancybox-title-over { + padding: 10px; + background-image: url('fancy_title_over.png'); + display: block; +} + +#fancybox-title-wrap { + display: inline-block; +} + +#fancybox-title-wrap span { + height: 32px; + float: left; +} + +#fancybox-title-left { + padding-left: 15px; + background-image: url('fancybox.png'); + background-position: -40px -90px; + background-repeat: no-repeat; +} + +#fancybox-title-main { + font-weight: bold; + line-height: 29px; + background-image: url('fancybox-x.png'); + background-position: 0px -40px; + color: #FFF; +} + +#fancybox-title-right { + padding-left: 15px; + background-image: url('fancybox.png'); + background-position: -55px -90px; + background-repeat: no-repeat; +} + +#fancybox-left, #fancybox-right { + position: absolute; + bottom: 0px; + height: 100%; + width: 35%; + cursor: pointer; + outline: none; + background-image: url('blank.gif'); + z-index: 1102; + display: none; +} + +#fancybox-left { + left: 0px; +} + +#fancybox-right { + right: 0px; +} + +#fancybox-left-ico, #fancybox-right-ico { + position: absolute; + top: 50%; + left: -9999px; + width: 30px; + height: 30px; + margin-top: -15px; + cursor: pointer; + z-index: 1102; + display: block; +} + +#fancybox-left-ico { + background-image: url('fancybox.png'); + background-position: -40px -30px; +} + +#fancybox-right-ico { + background-image: url('fancybox.png'); + background-position: -40px -60px; +} + +#fancybox-left:hover, #fancybox-right:hover { + visibility: visible; /* IE6 */ +} + +#fancybox-left:hover span { + left: 20px; +} + +#fancybox-right:hover span { + left: auto; + right: 20px; +} + +.fancy-bg { + position: absolute; + padding: 0; + margin: 0; + border: 0; + width: 20px; + height: 20px; + z-index: 1001; +} + +#fancy-bg-n { + top: -20px; + left: 0; + width: 100%; + background-image: url('fancybox-x.png'); +} + +#fancy-bg-ne { + top: -20px; + right: -20px; + background-image: url('fancybox.png'); + background-position: -40px -162px; +} + +#fancy-bg-e { + top: 0; + right: -20px; + height: 100%; + background-image: url('fancybox-y.png'); + background-position: -20px 0px; +} + +#fancy-bg-se { + bottom: -20px; + right: -20px; + background-image: url('fancybox.png'); + background-position: -40px -182px; +} + +#fancy-bg-s { + bottom: -20px; + left: 0; + width: 100%; + background-image: url('fancybox-x.png'); + background-position: 0px -20px; +} + +#fancy-bg-sw { + bottom: -20px; + left: -20px; + background-image: url('fancybox.png'); + background-position: -40px -142px; +} + +#fancy-bg-w { + top: 0; + left: -20px; + height: 100%; + background-image: url('fancybox-y.png'); +} + +#fancy-bg-nw { + top: -20px; + left: -20px; + background-image: url('fancybox.png'); + background-position: -40px -122px; +} + +/* IE */ + +#fancybox-loading.fancybox-ie div { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_loading.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-close { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_close.png', sizingMethod='scale'); } + +.fancybox-ie #fancybox-title-over { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_over.png', sizingMethod='scale'); zoom: 1; } +.fancybox-ie #fancybox-title-left { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_left.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-title-main { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_main.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-title-right { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_title_right.png', sizingMethod='scale'); } + +.fancybox-ie #fancybox-left-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_left.png', sizingMethod='scale'); } +.fancybox-ie #fancybox-right-ico { background: transparent; filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_nav_right.png', sizingMethod='scale'); } + +.fancybox-ie .fancy-bg { background: transparent !important; } + +.fancybox-ie #fancy-bg-n { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_n.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-ne { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_ne.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-e { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_e.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-se { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_se.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-s { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_s.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-sw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_sw.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-w { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_w.png', sizingMethod='scale'); } +.fancybox-ie #fancy-bg-nw { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='fancybox/fancy_shadow_nw.png', sizingMethod='scale'); } diff --git a/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.pack.js b/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.pack.js new file mode 100644 index 0000000..8421d53 --- /dev/null +++ b/framework/gii/assets/js/fancybox/jquery.fancybox-1.3.1.pack.js @@ -0,0 +1,44 @@ +/* + * FancyBox - jQuery Plugin + * Simple and fancy lightbox alternative + * + * Examples and documentation at: http://fancybox.net + * + * Copyright (c) 2008 - 2010 Janis Skarnelis + * + * Version: 1.3.1 (05/03/2010) + * Requires: jQuery v1.3+ + * + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + */ + +(function(b){var m,u,x,g,D,i,z,A,B,p=0,e={},q=[],n=0,c={},j=[],E=null,s=new Image,G=/\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,S=/[^\.]\.(swf)\s*$/i,H,I=1,k,l,h=false,y=b.extend(b("<div/>")[0],{prop:0}),v=0,O=!b.support.opacity&&!window.XMLHttpRequest,J=function(){u.hide();s.onerror=s.onload=null;E&&E.abort();m.empty()},P=function(){b.fancybox('<p id="fancybox_error">The requested content cannot be loaded.<br />Please try again later.</p>',{scrolling:"no",padding:20,transitionIn:"none",transitionOut:"none"})}, +K=function(){return[b(window).width(),b(window).height(),b(document).scrollLeft(),b(document).scrollTop()]},T=function(){var a=K(),d={},f=c.margin,o=c.autoScale,t=(20+f)*2,w=(20+f)*2,r=c.padding*2;if(c.width.toString().indexOf("%")>-1){d.width=a[0]*parseFloat(c.width)/100-40;o=false}else d.width=c.width+r;if(c.height.toString().indexOf("%")>-1){d.height=a[1]*parseFloat(c.height)/100-40;o=false}else d.height=c.height+r;if(o&&(d.width>a[0]-t||d.height>a[1]-w))if(e.type=="image"||e.type=="swf"){t+=r; +w+=r;o=Math.min(Math.min(a[0]-t,c.width)/c.width,Math.min(a[1]-w,c.height)/c.height);d.width=Math.round(o*(d.width-r))+r;d.height=Math.round(o*(d.height-r))+r}else{d.width=Math.min(d.width,a[0]-t);d.height=Math.min(d.height,a[1]-w)}d.top=a[3]+(a[1]-(d.height+40))*0.5;d.left=a[2]+(a[0]-(d.width+40))*0.5;if(c.autoScale===false){d.top=Math.max(a[3]+f,d.top);d.left=Math.max(a[2]+f,d.left)}return d},U=function(a){if(a&&a.length)switch(c.titlePosition){case "inside":return a;case "over":return'<span id="fancybox-title-over">'+ +a+"</span>";default:return'<span id="fancybox-title-wrap"><span id="fancybox-title-left"></span><span id="fancybox-title-main">'+a+'</span><span id="fancybox-title-right"></span></span>'}return false},V=function(){var a=c.title,d=l.width-c.padding*2,f="fancybox-title-"+c.titlePosition;b("#fancybox-title").remove();v=0;if(c.titleShow!==false){a=b.isFunction(c.titleFormat)?c.titleFormat(a,j,n,c):U(a);if(!(!a||a==="")){b('<div id="fancybox-title" class="'+f+'" />').css({width:d,paddingLeft:c.padding, +paddingRight:c.padding}).html(a).appendTo("body");switch(c.titlePosition){case "inside":v=b("#fancybox-title").outerHeight(true)-c.padding;l.height+=v;break;case "over":b("#fancybox-title").css("bottom",c.padding);break;default:b("#fancybox-title").css("bottom",b("#fancybox-title").outerHeight(true)*-1);break}b("#fancybox-title").appendTo(D).hide()}}},W=function(){b(document).unbind("keydown.fb").bind("keydown.fb",function(a){if(a.keyCode==27&&c.enableEscapeButton){a.preventDefault();b.fancybox.close()}else if(a.keyCode== +37){a.preventDefault();b.fancybox.prev()}else if(a.keyCode==39){a.preventDefault();b.fancybox.next()}});if(b.fn.mousewheel){g.unbind("mousewheel.fb");j.length>1&&g.bind("mousewheel.fb",function(a,d){a.preventDefault();h||d===0||(d>0?b.fancybox.prev():b.fancybox.next())})}if(c.showNavArrows){if(c.cyclic&&j.length>1||n!==0)A.show();if(c.cyclic&&j.length>1||n!=j.length-1)B.show()}},X=function(){var a,d;if(j.length-1>n){a=j[n+1].href;if(typeof a!=="undefined"&&a.match(G)){d=new Image;d.src=a}}if(n>0){a= +j[n-1].href;if(typeof a!=="undefined"&&a.match(G)){d=new Image;d.src=a}}},L=function(){i.css("overflow",c.scrolling=="auto"?c.type=="image"||c.type=="iframe"||c.type=="swf"?"hidden":"auto":c.scrolling=="yes"?"auto":"visible");if(!b.support.opacity){i.get(0).style.removeAttribute("filter");g.get(0).style.removeAttribute("filter")}b("#fancybox-title").show();c.hideOnContentClick&&i.one("click",b.fancybox.close);c.hideOnOverlayClick&&x.one("click",b.fancybox.close);c.showCloseButton&&z.show();W();b(window).bind("resize.fb", +b.fancybox.center);c.centerOnScroll?b(window).bind("scroll.fb",b.fancybox.center):b(window).unbind("scroll.fb");b.isFunction(c.onComplete)&&c.onComplete(j,n,c);h=false;X()},M=function(a){var d=Math.round(k.width+(l.width-k.width)*a),f=Math.round(k.height+(l.height-k.height)*a),o=Math.round(k.top+(l.top-k.top)*a),t=Math.round(k.left+(l.left-k.left)*a);g.css({width:d+"px",height:f+"px",top:o+"px",left:t+"px"});d=Math.max(d-c.padding*2,0);f=Math.max(f-(c.padding*2+v*a),0);i.css({width:d+"px",height:f+ +"px"});if(typeof l.opacity!=="undefined")g.css("opacity",a<0.5?0.5:a)},Y=function(a){var d=a.offset();d.top+=parseFloat(a.css("paddingTop"))||0;d.left+=parseFloat(a.css("paddingLeft"))||0;d.top+=parseFloat(a.css("border-top-width"))||0;d.left+=parseFloat(a.css("border-left-width"))||0;d.width=a.width();d.height=a.height();return d},Q=function(){var a=e.orig?b(e.orig):false,d={};if(a&&a.length){a=Y(a);d={width:a.width+c.padding*2,height:a.height+c.padding*2,top:a.top-c.padding-20,left:a.left-c.padding- +20}}else{a=K();d={width:1,height:1,top:a[3]+a[1]*0.5,left:a[2]+a[0]*0.5}}return d},N=function(){u.hide();if(g.is(":visible")&&b.isFunction(c.onCleanup))if(c.onCleanup(j,n,c)===false){b.event.trigger("fancybox-cancel");h=false;return}j=q;n=p;c=e;i.get(0).scrollTop=0;i.get(0).scrollLeft=0;if(c.overlayShow){O&&b("select:not(#fancybox-tmp select)").filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one("fancybox-cleanup",function(){this.style.visibility="inherit"}); +x.css({"background-color":c.overlayColor,opacity:c.overlayOpacity}).unbind().show()}l=T();V();if(g.is(":visible")){b(z.add(A).add(B)).hide();var a=g.position(),d;k={top:a.top,left:a.left,width:g.width(),height:g.height()};d=k.width==l.width&&k.height==l.height;i.fadeOut(c.changeFade,function(){var f=function(){i.html(m.contents()).fadeIn(c.changeFade,L)};b.event.trigger("fancybox-change");i.empty().css("overflow","hidden");if(d){i.css({top:c.padding,left:c.padding,width:Math.max(l.width-c.padding* +2,1),height:Math.max(l.height-c.padding*2-v,1)});f()}else{i.css({top:c.padding,left:c.padding,width:Math.max(k.width-c.padding*2,1),height:Math.max(k.height-c.padding*2,1)});y.prop=0;b(y).animate({prop:1},{duration:c.changeSpeed,easing:c.easingChange,step:M,complete:f})}})}else{g.css("opacity",1);if(c.transitionIn=="elastic"){k=Q();i.css({top:c.padding,left:c.padding,width:Math.max(k.width-c.padding*2,1),height:Math.max(k.height-c.padding*2,1)}).html(m.contents());g.css(k).show();if(c.opacity)l.opacity= +0;y.prop=0;b(y).animate({prop:1},{duration:c.speedIn,easing:c.easingIn,step:M,complete:L})}else{i.css({top:c.padding,left:c.padding,width:Math.max(l.width-c.padding*2,1),height:Math.max(l.height-c.padding*2-v,1)}).html(m.contents());g.css(l).fadeIn(c.transitionIn=="none"?0:c.speedIn,L)}}},F=function(){m.width(e.width);m.height(e.height);if(e.width=="auto")e.width=m.width();if(e.height=="auto")e.height=m.height();N()},Z=function(){h=true;e.width=s.width;e.height=s.height;b("<img />").attr({id:"fancybox-img", +src:s.src,alt:e.title}).appendTo(m);N()},C=function(){J();var a=q[p],d,f,o,t,w;e=b.extend({},b.fn.fancybox.defaults,typeof b(a).data("fancybox")=="undefined"?e:b(a).data("fancybox"));o=a.title||b(a).title||e.title||"";if(a.nodeName&&!e.orig)e.orig=b(a).children("img:first").length?b(a).children("img:first"):b(a);if(o===""&&e.orig)o=e.orig.attr("alt");d=a.nodeName&&/^(?:javascript|#)/i.test(a.href)?e.href||null:e.href||a.href||null;if(e.type){f=e.type;if(!d)d=e.content}else if(e.content)f="html";else if(d)if(d.match(G))f= +"image";else if(d.match(S))f="swf";else if(b(a).hasClass("iframe"))f="iframe";else if(d.match(/#/)){a=d.substr(d.indexOf("#"));f=b(a).length>0?"inline":"ajax"}else f="ajax";else f="inline";e.type=f;e.href=d;e.title=o;if(e.autoDimensions&&e.type!=="iframe"&&e.type!=="swf"){e.width="auto";e.height="auto"}if(e.modal){e.overlayShow=true;e.hideOnOverlayClick=false;e.hideOnContentClick=false;e.enableEscapeButton=false;e.showCloseButton=false}if(b.isFunction(e.onStart))if(e.onStart(q,p,e)===false){h=false; +return}m.css("padding",20+e.padding+e.margin);b(".fancybox-inline-tmp").unbind("fancybox-cancel").bind("fancybox-change",function(){b(this).replaceWith(i.children())});switch(f){case "html":m.html(e.content);F();break;case "inline":b('<div class="fancybox-inline-tmp" />').hide().insertBefore(b(a)).bind("fancybox-cleanup",function(){b(this).replaceWith(i.children())}).bind("fancybox-cancel",function(){b(this).replaceWith(m.children())});b(a).appendTo(m);F();break;case "image":h=false;b.fancybox.showActivity(); +s=new Image;s.onerror=function(){P()};s.onload=function(){s.onerror=null;s.onload=null;Z()};s.src=d;break;case "swf":t='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+e.width+'" height="'+e.height+'"><param name="movie" value="'+d+'"></param>';w="";b.each(e.swf,function(r,R){t+='<param name="'+r+'" value="'+R+'"></param>';w+=" "+r+'="'+R+'"'});t+='<embed src="'+d+'" type="application/x-shockwave-flash" width="'+e.width+'" height="'+e.height+'"'+w+"></embed></object>";m.html(t); +F();break;case "ajax":a=d.split("#",2);f=e.ajax.data||{};if(a.length>1){d=a[0];if(typeof f=="string")f+="&selector="+a[1];else f.selector=a[1]}h=false;b.fancybox.showActivity();E=b.ajax(b.extend(e.ajax,{url:d,data:f,error:P,success:function(r){if(E.status==200){m.html(r);F()}}}));break;case "iframe":b('<iframe id="fancybox-frame" name="fancybox-frame'+(new Date).getTime()+'" frameborder="0" hspace="0" scrolling="'+e.scrolling+'" src="'+e.href+'"></iframe>').appendTo(m);N();break}},$=function(){if(u.is(":visible")){b("div", +u).css("top",I*-40+"px");I=(I+1)%12}else clearInterval(H)},aa=function(){if(!b("#fancybox-wrap").length){b("body").append(m=b('<div id="fancybox-tmp"></div>'),u=b('<div id="fancybox-loading"><div></div></div>'),x=b('<div id="fancybox-overlay"></div>'),g=b('<div id="fancybox-wrap"></div>'));if(!b.support.opacity){g.addClass("fancybox-ie");u.addClass("fancybox-ie")}D=b('<div id="fancybox-outer"></div>').append('<div class="fancy-bg" id="fancy-bg-n"></div><div class="fancy-bg" id="fancy-bg-ne"></div><div class="fancy-bg" id="fancy-bg-e"></div><div class="fancy-bg" id="fancy-bg-se"></div><div class="fancy-bg" id="fancy-bg-s"></div><div class="fancy-bg" id="fancy-bg-sw"></div><div class="fancy-bg" id="fancy-bg-w"></div><div class="fancy-bg" id="fancy-bg-nw"></div>').appendTo(g); +D.append(i=b('<div id="fancybox-inner"></div>'),z=b('<a id="fancybox-close"></a>'),A=b('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'),B=b('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>'));z.click(b.fancybox.close);u.click(b.fancybox.cancel);A.click(function(a){a.preventDefault();b.fancybox.prev()});B.click(function(a){a.preventDefault();b.fancybox.next()});if(O){x.get(0).style.setExpression("height", +"document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'");u.get(0).style.setExpression("top","(-20 + (document.documentElement.clientHeight ? document.documentElement.clientHeight/2 : document.body.clientHeight/2 ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop )) + 'px'");D.prepend('<iframe id="fancybox-hide-sel-frame" src="javascript:\'\';" scrolling="no" frameborder="0" ></iframe>')}}}; +b.fn.fancybox=function(a){b(this).data("fancybox",b.extend({},a,b.metadata?b(this).metadata():{})).unbind("click.fb").bind("click.fb",function(d){d.preventDefault();if(!h){h=true;b(this).blur();q=[];p=0;d=b(this).attr("rel")||"";if(!d||d==""||d==="nofollow")q.push(this);else{q=b("a[rel="+d+"], area[rel="+d+"]");p=q.index(this)}C();return false}});return this};b.fancybox=function(a,d){if(!h){h=true;d=typeof d!=="undefined"?d:{};q=[];p=d.index||0;if(b.isArray(a)){for(var f=0,o=a.length;f<o;f++)if(typeof a[f]== +"object")b(a[f]).data("fancybox",b.extend({},d,a[f]));else a[f]=b({}).data("fancybox",b.extend({content:a[f]},d));q=jQuery.merge(q,a)}else{if(typeof a=="object")b(a).data("fancybox",b.extend({},d,a));else a=b({}).data("fancybox",b.extend({content:a},d));q.push(a)}if(p>q.length||p<0)p=0;C()}};b.fancybox.showActivity=function(){clearInterval(H);u.show();H=setInterval($,66)};b.fancybox.hideActivity=function(){u.hide()};b.fancybox.next=function(){return b.fancybox.pos(n+1)};b.fancybox.prev=function(){return b.fancybox.pos(n- +1)};b.fancybox.pos=function(a){if(!h){a=parseInt(a,10);if(a>-1&&j.length>a){p=a;C()}if(c.cyclic&&j.length>1&&a<0){p=j.length-1;C()}if(c.cyclic&&j.length>1&&a>=j.length){p=0;C()}}};b.fancybox.cancel=function(){if(!h){h=true;b.event.trigger("fancybox-cancel");J();e&&b.isFunction(e.onCancel)&&e.onCancel(q,p,e);h=false}};b.fancybox.close=function(){function a(){x.fadeOut("fast");g.hide();b.event.trigger("fancybox-cleanup");i.empty();b.isFunction(c.onClosed)&&c.onClosed(j,n,c);j=e=[];n=p=0;c=e={};h=false} +if(!(h||g.is(":hidden"))){h=true;if(c&&b.isFunction(c.onCleanup))if(c.onCleanup(j,n,c)===false){h=false;return}J();b(z.add(A).add(B)).hide();b("#fancybox-title").remove();g.add(i).add(x).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");i.css("overflow","hidden");if(c.transitionOut=="elastic"){k=Q();var d=g.position();l={top:d.top,left:d.left,width:g.width(),height:g.height()};if(c.opacity)l.opacity=1;y.prop=1;b(y).animate({prop:0},{duration:c.speedOut,easing:c.easingOut, +step:M,complete:a})}else g.fadeOut(c.transitionOut=="none"?0:c.speedOut,a)}};b.fancybox.resize=function(){var a,d;if(!(h||g.is(":hidden"))){h=true;a=i.wrapInner("<div style='overflow:auto'></div>").children();d=a.height();g.css({height:d+c.padding*2+v});i.css({height:d});a.replaceWith(a.children());b.fancybox.center()}};b.fancybox.center=function(){h=true;var a=K(),d=c.margin,f={};f.top=a[3]+(a[1]-(g.height()-v+40))*0.5;f.left=a[2]+(a[0]-(g.width()+40))*0.5;f.top=Math.max(a[3]+d,f.top);f.left=Math.max(a[2]+ +d,f.left);g.css(f);h=false};b.fn.fancybox.defaults={padding:10,margin:20,opacity:false,modal:false,cyclic:false,scrolling:"auto",width:560,height:340,autoScale:true,autoDimensions:true,centerOnScroll:false,ajax:{},swf:{wmode:"transparent"},hideOnOverlayClick:true,hideOnContentClick:false,overlayShow:true,overlayOpacity:0.3,overlayColor:"#666",titleShow:true,titlePosition:"outside",titleFormat:null,transitionIn:"fade",transitionOut:"fade",speedIn:300,speedOut:300,changeSpeed:300,changeFade:"fast", +easingIn:"swing",easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,onStart:null,onCancel:null,onComplete:null,onCleanup:null,onClosed:null};b(document).ready(function(){aa()})})(jQuery);
\ No newline at end of file diff --git a/framework/gii/assets/js/jquery.tooltip-1.2.6.min.js b/framework/gii/assets/js/jquery.tooltip-1.2.6.min.js new file mode 100644 index 0000000..9f28bef --- /dev/null +++ b/framework/gii/assets/js/jquery.tooltip-1.2.6.min.js @@ -0,0 +1,11 @@ +/*! + * jQuery Tools v1.2.6 - The missing UI library for the Web + * + * tooltip/tooltip.js + * + * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. + * + * http://flowplayer.org/tools/ + * + */ +(function(a){a.tools=a.tools||{version:"v1.2.6"},a.tools.tooltip={conf:{effect:"toggle",fadeOutSpeed:"fast",predelay:0,delay:30,opacity:1,tip:0,fadeIE:!1,position:["top","center"],offset:[0,0],relative:!1,cancelDefault:!0,events:{def:"mouseenter,mouseleave",input:"focus,blur",widget:"focus mouseenter,blur mouseleave",tooltip:"mouseenter,mouseleave"},layout:"<div/>",tipClass:"tooltip"},addEffect:function(a,c,d){b[a]=[c,d]}};var b={toggle:[function(a){var b=this.getConf(),c=this.getTip(),d=b.opacity;d<1&&c.css({opacity:d}),c.show(),a.call()},function(a){this.getTip().hide(),a.call()}],fade:[function(b){var c=this.getConf();!a.browser.msie||c.fadeIE?this.getTip().fadeTo(c.fadeInSpeed,c.opacity,b):(this.getTip().show(),b())},function(b){var c=this.getConf();!a.browser.msie||c.fadeIE?this.getTip().fadeOut(c.fadeOutSpeed,b):(this.getTip().hide(),b())}]};function c(b,c,d){var e=d.relative?b.position().top:b.offset().top,f=d.relative?b.position().left:b.offset().left,g=d.position[0];e-=c.outerHeight()-d.offset[0],f+=b.outerWidth()+d.offset[1],/iPad/i.test(navigator.userAgent)&&(e-=a(window).scrollTop());var h=c.outerHeight()+b.outerHeight();g=="center"&&(e+=h/2),g=="bottom"&&(e+=h),g=d.position[1];var i=c.outerWidth()+b.outerWidth();g=="center"&&(f-=i/2),g=="left"&&(f-=i);return{top:e,left:f}}function d(d,e){var f=this,g=d.add(f),h,i=0,j=0,k=d.attr("title"),l=d.attr("data-tooltip"),m=b[e.effect],n,o=d.is(":input"),p=o&&d.is(":checkbox, :radio, select, :button, :submit"),q=d.attr("type"),r=e.events[q]||e.events[o?p?"widget":"input":"def"];if(!m)throw"Nonexistent effect \""+e.effect+"\"";r=r.split(/,\s*/);if(r.length!=2)throw"Tooltip: bad events configuration for "+q;d.bind(r[0],function(a){clearTimeout(i),e.predelay?j=setTimeout(function(){f.show(a)},e.predelay):f.show(a)}).bind(r[1],function(a){clearTimeout(j),e.delay?i=setTimeout(function(){f.hide(a)},e.delay):f.hide(a)}),k&&e.cancelDefault&&(d.removeAttr("title"),d.data("title",k)),a.extend(f,{show:function(b){if(!h){l?h=a(l):e.tip?h=a(e.tip).eq(0):k?h=a(e.layout).addClass(e.tipClass).appendTo(document.body).hide().append(k):(h=d.next(),h.length||(h=d.parent().next()));if(!h.length)throw"Cannot find tooltip for "+d}if(f.isShown())return f;h.stop(!0,!0);var o=c(d,h,e);e.tip&&h.html(d.data("title")),b=a.Event(),b.type="onBeforeShow",g.trigger(b,[o]);if(b.isDefaultPrevented())return f;o=c(d,h,e),h.css({position:"absolute",top:o.top,left:o.left}),n=!0,m[0].call(f,function(){b.type="onShow",n="full",g.trigger(b)});var p=e.events.tooltip.split(/,\s*/);h.data("__set")||(h.unbind(p[0]).bind(p[0],function(){clearTimeout(i),clearTimeout(j)}),p[1]&&!d.is("input:not(:checkbox, :radio), textarea")&&h.unbind(p[1]).bind(p[1],function(a){a.relatedTarget!=d[0]&&d.trigger(r[1].split(" ")[0])}),e.tip||h.data("__set",!0));return f},hide:function(c){if(!h||!f.isShown())return f;c=a.Event(),c.type="onBeforeHide",g.trigger(c);if(!c.isDefaultPrevented()){n=!1,b[e.effect][1].call(f,function(){c.type="onHide",g.trigger(c)});return f}},isShown:function(a){return a?n=="full":n},getConf:function(){return e},getTip:function(){return h},getTrigger:function(){return d}}),a.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","),function(b,c){a.isFunction(e[c])&&a(f).bind(c,e[c]),f[c]=function(b){b&&a(f).bind(c,b);return f}})}a.fn.tooltip=function(b){var c=this.data("tooltip");if(c)return c;b=a.extend(!0,{},a.tools.tooltip.conf,b),typeof b.position=="string"&&(b.position=b.position.split(/,?\s/)),this.each(function(){c=new d(a(this),b),a(this).data("tooltip",c)});return b.api?c:this}})(jQuery); diff --git a/framework/gii/assets/js/main.js b/framework/gii/assets/js/main.js new file mode 100644 index 0000000..d44c317 --- /dev/null +++ b/framework/gii/assets/js/main.js @@ -0,0 +1,79 @@ +$(document).ready(function() { + if($('div.form.login').length) { // in login page + $('input#LoginForm_password').focus(); + } + + $('table.preview input[name="checkAll"]').click(function() { + $('table.preview .confirm input').prop('checked', this.checked); + }); + + $('table.preview td.confirm input').click(function() { + $('table.preview input[name="checkAll"]').prop('checked', !$('table.preview td.confirm input:not(:checked)').length); + }); + $('table.preview input[name="checkAll"]').prop('checked', !$('table.preview td.confirm input:not(:checked)').length); + + $('.form .row.sticky input:not(.error), .form .row.sticky select:not(.error), .form .row.sticky textarea:not(.error)').each(function(){ + var value; + if(this.tagName=='SELECT') + value=this.options[this.selectedIndex].text; + else if(this.tagName=='TEXTAREA') + value=$(this).html(); + else + value=$(this).val(); + if(value=='') + value='[empty]'; + $(this).before('<div class="value">'+value+'</div>').hide(); + }); + + $('.form.gii .row.sticky .value').live('click', function(){ + $(this).hide(); + $(this).next().show().get(0).focus(); + }); + + + $('.form.gii .row input, .form.gii .row textarea, .form.gii .row select, .with-tooltip').not('.no-tooltip, .no-tooltip *').tooltip({ + position: "center right", + offset: [-2, 10] + }); + + $('.form.gii .row input').change(function(){ + $('.form.gii .feedback').hide(); + $('.form.gii input[name="generate"]').hide(); + }); + + $('.form.gii .view-code').click(function(){ + var title=$(this).attr('rel'); + $.fancybox.showActivity(); + $.ajax({ + type: 'POST', + cache: false, + url: $(this).attr('href'), + data: $('.form.gii form').serializeArray(), + success: function(data){ + $.fancybox(data, { + 'title': title, + 'titlePosition': 'inside', + 'titleFormat': function(title, currentArray, currentIndex, currentOpts) { + return '<div id="tip7-title"><span><a href="javascript:;" onclick="$.fancybox.close();">close</a></span>' + (title && title.length ? '<b>' + title + '</b>' : '' ) + '</div>'; + }, + 'showCloseButton': false, + 'autoDimensions': false, + 'width': 900, + 'height': 'auto', + 'onComplete':function(){ + $('#fancybox-inner').scrollTop(0); + } + }); + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $.fancybox('<div class="error">'+XMLHttpRequest.responseText+'</div>'); + } + }); + return false; + }); + + $('#fancybox-inner .close-code').live('click', function(){ + $.fancybox.close(); + return false; + }); +});
\ No newline at end of file diff --git a/framework/gii/components/Pear/Text/Diff.php b/framework/gii/components/Pear/Text/Diff.php new file mode 100644 index 0000000..205b8a7 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff.php @@ -0,0 +1,453 @@ +<?php +/** + * General API for generating and formatting diffs - the differences between + * two sequences of strings. + * + * The original PHP version of this code was written by Geoffrey T. Dairiki + * <dairiki@dairiki.org>, and is used/adapted with his permission. + * + * $Horde: framework/Text_Diff/Diff.php,v 1.11.2.11 2008/02/24 10:57:46 jan Exp $ + * + * Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_Diff { + + /** + * Array of changes. + * + * @var array + */ + var $_edits; + + /** + * Computes diffs between sequences of strings. + * + * @param string $engine Name of the diffing engine to use. 'auto' + * will automatically select the best. + * @param array $params Parameters to pass to the diffing engine. + * Normally an array of two arrays, each + * containing the lines from a file. + */ + function Text_Diff($engine, $params) + { + // Backward compatibility workaround. + if (!is_string($engine)) { + $params = array($engine, $params); + $engine = 'auto'; + } + + if ($engine == 'auto') { + $engine = extension_loaded('xdiff') ? 'xdiff' : 'native'; + } else { + $engine = basename($engine); + } + + require_once 'Text/Diff/Engine/' . $engine . '.php'; + $class = 'Text_Diff_Engine_' . $engine; + $diff_engine = new $class(); + + $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params); + } + + /** + * Returns the array of differences. + */ + function getDiff() + { + return $this->_edits; + } + + /** + * returns the number of new (added) lines in a given diff. + * + * @since Text_Diff 1.1.0 + * @since Horde 3.2 + * + * @return integer The number of new lines + */ + function countAddedLines() + { + $count = 0; + foreach ($this->_edits as $edit) { + if (is_a($edit, 'Text_Diff_Op_add') || + is_a($edit, 'Text_Diff_Op_change')) { + $count += $edit->nfinal(); + } + } + return $count; + } + + /** + * Returns the number of deleted (removed) lines in a given diff. + * + * @since Text_Diff 1.1.0 + * @since Horde 3.2 + * + * @return integer The number of deleted lines + */ + function countDeletedLines() + { + $count = 0; + foreach ($this->_edits as $edit) { + if (is_a($edit, 'Text_Diff_Op_delete') || + is_a($edit, 'Text_Diff_Op_change')) { + $count += $edit->norig(); + } + } + return $count; + } + + /** + * Computes a reversed diff. + * + * Example: + * <code> + * $diff = new Text_Diff($lines1, $lines2); + * $rev = $diff->reverse(); + * </code> + * + * @return Text_Diff A Diff object representing the inverse of the + * original diff. Note that we purposely don't return a + * reference here, since this essentially is a clone() + * method. + */ + function reverse() + { + if (version_compare(zend_version(), '2', '>')) { + $rev = clone($this); + } else { + $rev = $this; + } + $rev->_edits = array(); + foreach ($this->_edits as $edit) { + $rev->_edits[] = $edit->reverse(); + } + return $rev; + } + + /** + * Checks for an empty diff. + * + * @return boolean True if two sequences were identical. + */ + function isEmpty() + { + foreach ($this->_edits as $edit) { + if (!is_a($edit, 'Text_Diff_Op_copy')) { + return false; + } + } + return true; + } + + /** + * Computes the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposes. + * + * @return integer The length of the LCS. + */ + function lcs() + { + $lcs = 0; + foreach ($this->_edits as $edit) { + if (is_a($edit, 'Text_Diff_Op_copy')) { + $lcs += count($edit->orig); + } + } + return $lcs; + } + + /** + * Gets the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the constructor. + * + * @return array The original sequence of strings. + */ + function getOriginal() + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->orig) { + array_splice($lines, count($lines), 0, $edit->orig); + } + } + return $lines; + } + + /** + * Gets the final set of lines. + * + * This reconstructs the $to_lines parameter passed to the constructor. + * + * @return array The sequence of strings. + */ + function getFinal() + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->final) { + array_splice($lines, count($lines), 0, $edit->final); + } + } + return $lines; + } + + /** + * Removes trailing newlines from a line of text. This is meant to be used + * with array_walk(). + * + * @param string $line The line to trim. + * @param integer $key The index of the line in the array. Not used. + */ + static function trimNewlines(&$line, $key) + { + $line = str_replace(array("\n", "\r"), '', $line); + } + + /** + * Determines the location of the system temporary directory. + * + * @static + * + * @access protected + * + * @return string A directory name which can be used for temp files. + * Returns false if one could not be found. + */ + function _getTempDir() + { + $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp', + 'c:\windows\temp', 'c:\winnt\temp'); + + /* Try PHP's upload_tmp_dir directive. */ + $tmp = ini_get('upload_tmp_dir'); + + /* Otherwise, try to determine the TMPDIR environment variable. */ + if (!strlen($tmp)) { + $tmp = getenv('TMPDIR'); + } + + /* If we still cannot determine a value, then cycle through a list of + * preset possibilities. */ + while (!strlen($tmp) && count($tmp_locations)) { + $tmp_check = array_shift($tmp_locations); + if (@is_dir($tmp_check)) { + $tmp = $tmp_check; + } + } + + /* If it is still empty, we have failed, so return false; otherwise + * return the directory determined. */ + return strlen($tmp) ? $tmp : false; + } + + /** + * Checks a diff for validity. + * + * This is here only for debugging purposes. + */ + function _check($from_lines, $to_lines) + { + if (serialize($from_lines) != serialize($this->getOriginal())) { + trigger_error("Reconstructed original doesn't match", E_USER_ERROR); + } + if (serialize($to_lines) != serialize($this->getFinal())) { + trigger_error("Reconstructed final doesn't match", E_USER_ERROR); + } + + $rev = $this->reverse(); + if (serialize($to_lines) != serialize($rev->getOriginal())) { + trigger_error("Reversed original doesn't match", E_USER_ERROR); + } + if (serialize($from_lines) != serialize($rev->getFinal())) { + trigger_error("Reversed final doesn't match", E_USER_ERROR); + } + + $prevtype = null; + foreach ($this->_edits as $edit) { + if ($prevtype == get_class($edit)) { + trigger_error("Edit sequence is non-optimal", E_USER_ERROR); + } + $prevtype = get_class($edit); + } + + return true; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_MappedDiff extends Text_Diff { + + /** + * Computes a diff between sequences of strings. + * + * This can be used to compute things like case-insensitve diffs, or diffs + * which ignore changes in white-space. + * + * @param array $from_lines An array of strings. + * @param array $to_lines An array of strings. + * @param array $mapped_from_lines This array should have the same size + * number of elements as $from_lines. The + * elements in $mapped_from_lines and + * $mapped_to_lines are what is actually + * compared when computing the diff. + * @param array $mapped_to_lines This array should have the same number + * of elements as $to_lines. + */ + function Text_MappedDiff($from_lines, $to_lines, + $mapped_from_lines, $mapped_to_lines) + { + assert(count($from_lines) == count($mapped_from_lines)); + assert(count($to_lines) == count($mapped_to_lines)); + + parent::Text_Diff($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < count($this->_edits); $i++) { + $orig = &$this->_edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, count($orig)); + $xi += count($orig); + } + + $final = &$this->_edits[$i]->final; + if (is_array($final)) { + $final = array_slice($to_lines, $yi, count($final)); + $yi += count($final); + } + } + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op { + + var $orig; + var $final; + + function &reverse() + { + trigger_error('Abstract method', E_USER_ERROR); + } + + function norig() + { + return $this->orig ? count($this->orig) : 0; + } + + function nfinal() + { + return $this->final ? count($this->final) : 0; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op_copy extends Text_Diff_Op { + + function Text_Diff_Op_copy($orig, $final = false) + { + if (!is_array($final)) { + $final = $orig; + } + $this->orig = $orig; + $this->final = $final; + } + + function &reverse() + { + $reverse = new Text_Diff_Op_copy($this->final, $this->orig); + return $reverse; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op_delete extends Text_Diff_Op { + + function Text_Diff_Op_delete($lines) + { + $this->orig = $lines; + $this->final = false; + } + + function &reverse() + { + $reverse = new Text_Diff_Op_add($this->orig); + return $reverse; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op_add extends Text_Diff_Op { + + function Text_Diff_Op_add($lines) + { + $this->final = $lines; + $this->orig = false; + } + + function &reverse() + { + $reverse = new Text_Diff_Op_delete($this->final); + return $reverse; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_Op_change extends Text_Diff_Op { + + function Text_Diff_Op_change($orig, $final) + { + $this->orig = $orig; + $this->final = $final; + } + + function &reverse() + { + $reverse = new Text_Diff_Op_change($this->final, $this->orig); + return $reverse; + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Engine/native.php b/framework/gii/components/Pear/Text/Diff/Engine/native.php new file mode 100644 index 0000000..410f849 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Engine/native.php @@ -0,0 +1,438 @@ +<?php +/** + * Class used internally by Text_Diff to actually compute the diffs. + * + * This class is implemented using native PHP code. + * + * The algorithm used here is mostly lifted from the perl module + * Algorithm::Diff (version 1.06) by Ned Konz, which is available at: + * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip + * + * More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html + * + * Some ideas (and a bit of code) are taken from analyze.c, of GNU + * diffutils-2.7, which can be found at: + * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz + * + * Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from + * Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this + * code was written by him, and is used/adapted with his permission. + * + * $Horde: framework/Text_Diff/Diff/Engine/native.php,v 1.7.2.4 2008/01/04 10:38:10 jan Exp $ + * + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * @package Text_Diff + */ +class Text_Diff_Engine_native { + + function diff($from_lines, $to_lines) + { + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); + + $n_from = count($from_lines); + $n_to = count($to_lines); + + $this->xchanged = $this->ychanged = array(); + $this->xv = $this->yv = array(); + $this->xind = $this->yind = array(); + unset($this->seq); + unset($this->in_seq); + unset($this->lcs); + + // Skip leading common lines. + for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { + if ($from_lines[$skip] !== $to_lines[$skip]) { + break; + } + $this->xchanged[$skip] = $this->ychanged[$skip] = false; + } + + // Skip trailing common lines. + $xi = $n_from; $yi = $n_to; + for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { + if ($from_lines[$xi] !== $to_lines[$yi]) { + break; + } + $this->xchanged[$xi] = $this->ychanged[$yi] = false; + } + + // Ignore lines which do not exist in both files. + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { + $xhash[$from_lines[$xi]] = 1; + } + for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { + $line = $to_lines[$yi]; + if (($this->ychanged[$yi] = empty($xhash[$line]))) { + continue; + } + $yhash[$line] = 1; + $this->yv[] = $line; + $this->yind[] = $yi; + } + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { + $line = $from_lines[$xi]; + if (($this->xchanged[$xi] = empty($yhash[$line]))) { + continue; + } + $this->xv[] = $line; + $this->xind[] = $xi; + } + + // Find the LCS. + $this->_compareseq(0, count($this->xv), 0, count($this->yv)); + + // Merge edits when possible. + $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged); + $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged); + + // Compute the edit operations. + $edits = array(); + $xi = $yi = 0; + while ($xi < $n_from || $yi < $n_to) { + assert($yi < $n_to || $this->xchanged[$xi]); + assert($xi < $n_from || $this->ychanged[$yi]); + + // Skip matching "snake". + $copy = array(); + while ($xi < $n_from && $yi < $n_to + && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { + $copy[] = $from_lines[$xi++]; + ++$yi; + } + if ($copy) { + $edits[] = new Text_Diff_Op_copy($copy); + } + + // Find deletes & adds. + $delete = array(); + while ($xi < $n_from && $this->xchanged[$xi]) { + $delete[] = $from_lines[$xi++]; + } + + $add = array(); + while ($yi < $n_to && $this->ychanged[$yi]) { + $add[] = $to_lines[$yi++]; + } + + if ($delete && $add) { + $edits[] = new Text_Diff_Op_change($delete, $add); + } elseif ($delete) { + $edits[] = new Text_Diff_Op_delete($delete); + } elseif ($add) { + $edits[] = new Text_Diff_Op_add($add); + } + } + + return $edits; + } + + /** + * Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, + * XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized + * segments. + * + * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of + * NCHUNKS+1 (X, Y) indexes giving the diving points between sub + * sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1), + * the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) == + * (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). + * + * This function assumes that the first lines of the specified portions of + * the two files do not match, and likewise that the last lines do not + * match. The caller must trim matching lines from the beginning and end + * of the portions it is going to specify. + */ + function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) + { + $flip = false; + + if ($xlim - $xoff > $ylim - $yoff) { + /* Things seems faster (I'm not sure I understand why) when the + * shortest sequence is in X. */ + $flip = true; + list ($xoff, $xlim, $yoff, $ylim) + = array($yoff, $ylim, $xoff, $xlim); + } + + if ($flip) { + for ($i = $ylim - 1; $i >= $yoff; $i--) { + $ymatches[$this->xv[$i]][] = $i; + } + } else { + for ($i = $ylim - 1; $i >= $yoff; $i--) { + $ymatches[$this->yv[$i]][] = $i; + } + } + + $this->lcs = 0; + $this->seq[0]= $yoff - 1; + $this->in_seq = array(); + $ymids[0] = array(); + + $numer = $xlim - $xoff + $nchunks - 1; + $x = $xoff; + for ($chunk = 0; $chunk < $nchunks; $chunk++) { + if ($chunk > 0) { + for ($i = 0; $i <= $this->lcs; $i++) { + $ymids[$i][$chunk - 1] = $this->seq[$i]; + } + } + + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks); + for (; $x < $x1; $x++) { + $line = $flip ? $this->yv[$x] : $this->xv[$x]; + if (empty($ymatches[$line])) { + continue; + } + $matches = $ymatches[$line]; + reset($matches); + while (list(, $y) = each($matches)) { + if (empty($this->in_seq[$y])) { + $k = $this->_lcsPos($y); + assert($k > 0); + $ymids[$k] = $ymids[$k - 1]; + break; + } + } + while (list(, $y) = each($matches)) { + if ($y > $this->seq[$k - 1]) { + assert($y <= $this->seq[$k]); + /* Optimization: this is a common case: next match is + * just replacing previous match. */ + $this->in_seq[$this->seq[$k]] = false; + $this->seq[$k] = $y; + $this->in_seq[$y] = 1; + } elseif (empty($this->in_seq[$y])) { + $k = $this->_lcsPos($y); + assert($k > 0); + $ymids[$k] = $ymids[$k - 1]; + } + } + } + } + + $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); + $ymid = $ymids[$this->lcs]; + for ($n = 0; $n < $nchunks - 1; $n++) { + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); + $y1 = $ymid[$n] + 1; + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); + } + $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); + + return array($this->lcs, $seps); + } + + function _lcsPos($ypos) + { + $end = $this->lcs; + if ($end == 0 || $ypos > $this->seq[$end]) { + $this->seq[++$this->lcs] = $ypos; + $this->in_seq[$ypos] = 1; + return $this->lcs; + } + + $beg = 1; + while ($beg < $end) { + $mid = (int)(($beg + $end) / 2); + if ($ypos > $this->seq[$mid]) { + $beg = $mid + 1; + } else { + $end = $mid; + } + } + + assert($ypos != $this->seq[$end]); + + $this->in_seq[$this->seq[$end]] = false; + $this->seq[$end] = $ypos; + $this->in_seq[$ypos] = 1; + return $end; + } + + /** + * Finds LCS of two sequences. + * + * The results are recorded in the vectors $this->{x,y}changed[], by + * storing a 1 in the element for each line that is an insertion or + * deletion (ie. is not in the LCS). + * + * The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1. + * + * Note that XLIM, YLIM are exclusive bounds. All line numbers are + * origin-0 and discarded lines are not counted. + */ + function _compareseq ($xoff, $xlim, $yoff, $ylim) + { + /* Slide down the bottom initial diagonal. */ + while ($xoff < $xlim && $yoff < $ylim + && $this->xv[$xoff] == $this->yv[$yoff]) { + ++$xoff; + ++$yoff; + } + + /* Slide up the top initial diagonal. */ + while ($xlim > $xoff && $ylim > $yoff + && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { + --$xlim; + --$ylim; + } + + if ($xoff == $xlim || $yoff == $ylim) { + $lcs = 0; + } else { + /* This is ad hoc but seems to work well. $nchunks = + * sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks = + * max(2,min(8,(int)$nchunks)); */ + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; + list($lcs, $seps) + = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); + } + + if ($lcs == 0) { + /* X and Y sequences have no common subsequence: mark all + * changed. */ + while ($yoff < $ylim) { + $this->ychanged[$this->yind[$yoff++]] = 1; + } + while ($xoff < $xlim) { + $this->xchanged[$this->xind[$xoff++]] = 1; + } + } else { + /* Use the partitions to split this problem into subproblems. */ + reset($seps); + $pt1 = $seps[0]; + while ($pt2 = next($seps)) { + $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); + $pt1 = $pt2; + } + } + } + + /** + * Adjusts inserts/deletes of identical lines to join changes as much as + * possible. + * + * We do something when a run of changed lines include a line at one end + * and has an excluded, identical line at the other. We are free to + * choose which identical line is included. `compareseq' usually chooses + * the one at the beginning, but usually it is cleaner to consider the + * following identical line to be the "change". + * + * This is extracted verbatim from analyze.c (GNU diffutils-2.7). + */ + function _shiftBoundaries($lines, &$changed, $other_changed) + { + $i = 0; + $j = 0; + + assert('count($lines) == count($changed)'); + $len = count($lines); + $other_len = count($other_changed); + + while (1) { + /* Scan forward to find the beginning of another run of + * changes. Also keep track of the corresponding point in the + * other file. + * + * Throughout this code, $i and $j are adjusted together so that + * the first $i elements of $changed and the first $j elements of + * $other_changed both contain the same number of zeros (unchanged + * lines). + * + * Furthermore, $j is always kept so that $j == $other_len or + * $other_changed[$j] == false. */ + while ($j < $other_len && $other_changed[$j]) { + $j++; + } + + while ($i < $len && ! $changed[$i]) { + assert('$j < $other_len && ! $other_changed[$j]'); + $i++; $j++; + while ($j < $other_len && $other_changed[$j]) { + $j++; + } + } + + if ($i == $len) { + break; + } + + $start = $i; + + /* Find the end of this run of changes. */ + while (++$i < $len && $changed[$i]) { + continue; + } + + do { + /* Record the length of this run of changes, so that we can + * later determine whether the run has grown. */ + $runlength = $i - $start; + + /* Move the changed region back, so long as the previous + * unchanged line matches the last changed one. This merges + * with previous changed regions. */ + while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { + $changed[--$start] = 1; + $changed[--$i] = false; + while ($start > 0 && $changed[$start - 1]) { + $start--; + } + assert('$j > 0'); + while ($other_changed[--$j]) { + continue; + } + assert('$j >= 0 && !$other_changed[$j]'); + } + + /* Set CORRESPONDING to the end of the changed run, at the + * last point where it corresponds to a changed run in the + * other file. CORRESPONDING == LEN means no such point has + * been found. */ + $corresponding = $j < $other_len ? $i : $len; + + /* Move the changed region forward, so long as the first + * changed line matches the following unchanged one. This + * merges with following changed regions. Do this second, so + * that if there are no merges, the changed region is moved + * forward as far as possible. */ + while ($i < $len && $lines[$start] == $lines[$i]) { + $changed[$start++] = false; + $changed[$i++] = 1; + while ($i < $len && $changed[$i]) { + $i++; + } + + assert('$j < $other_len && ! $other_changed[$j]'); + $j++; + if ($j < $other_len && $other_changed[$j]) { + $corresponding = $i; + while ($j < $other_len && $other_changed[$j]) { + $j++; + } + } + } + } while ($runlength != $i - $start); + + /* If possible, move the fully-merged run of changes back to a + * corresponding run in the other file. */ + while ($corresponding < $i) { + $changed[--$start] = 1; + $changed[--$i] = 0; + assert('$j > 0'); + while ($other_changed[--$j]) { + continue; + } + assert('$j >= 0 && !$other_changed[$j]'); + } + } + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Engine/shell.php b/framework/gii/components/Pear/Text/Diff/Engine/shell.php new file mode 100644 index 0000000..f1aaa98 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Engine/shell.php @@ -0,0 +1,162 @@ +<?php +/** + * Class used internally by Diff to actually compute the diffs. + * + * This class uses the Unix `diff` program via shell_exec to compute the + * differences between the two input arrays. + * + * $Horde: framework/Text_Diff/Diff/Engine/shell.php,v 1.6.2.3 2008/01/04 10:37:27 jan Exp $ + * + * Copyright 2007-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Milian Wolff <mail@milianw.de> + * @package Text_Diff + * @since 0.3.0 + */ +class Text_Diff_Engine_shell { + + /** + * Path to the diff executable + * + * @var string + */ + var $_diffCommand = 'diff'; + + /** + * Returns the array of differences. + * + * @param array $from_lines lines of text from old file + * @param array $to_lines lines of text from new file + * + * @return array all changes made (array with Text_Diff_Op_* objects) + */ + function diff($from_lines, $to_lines) + { + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); + + $temp_dir = Text_Diff::_getTempDir(); + + // Execute gnu diff or similar to get a standard diff file. + $from_file = tempnam($temp_dir, 'Text_Diff'); + $to_file = tempnam($temp_dir, 'Text_Diff'); + $fp = fopen($from_file, 'w'); + fwrite($fp, implode("\n", $from_lines)); + fclose($fp); + $fp = fopen($to_file, 'w'); + fwrite($fp, implode("\n", $to_lines)); + fclose($fp); + $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); + unlink($from_file); + unlink($to_file); + + if (is_null($diff)) { + // No changes were made + return array(new Text_Diff_Op_copy($from_lines)); + } + + $from_line_no = 1; + $to_line_no = 1; + $edits = array(); + + // Get changed lines by parsing something like: + // 0a1,2 + // 1,2c4,6 + // 1,5d6 + preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, + $matches, PREG_SET_ORDER); + + foreach ($matches as $match) { + if (!isset($match[5])) { + // This paren is not set every time (see regex). + $match[5] = false; + } + + if ($match[3] == 'a') { + $from_line_no--; + } + + if ($match[3] == 'd') { + $to_line_no--; + } + + if ($from_line_no < $match[1] || $to_line_no < $match[4]) { + // copied lines + assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); + array_push($edits, + new Text_Diff_Op_copy( + $this->_getLines($from_lines, $from_line_no, $match[1] - 1), + $this->_getLines($to_lines, $to_line_no, $match[4] - 1))); + } + + switch ($match[3]) { + case 'd': + // deleted lines + array_push($edits, + new Text_Diff_Op_delete( + $this->_getLines($from_lines, $from_line_no, $match[2]))); + $to_line_no++; + break; + + case 'c': + // changed lines + array_push($edits, + new Text_Diff_Op_change( + $this->_getLines($from_lines, $from_line_no, $match[2]), + $this->_getLines($to_lines, $to_line_no, $match[5]))); + break; + + case 'a': + // added lines + array_push($edits, + new Text_Diff_Op_add( + $this->_getLines($to_lines, $to_line_no, $match[5]))); + $from_line_no++; + break; + } + } + + if (!empty($from_lines)) { + // Some lines might still be pending. Add them as copied + array_push($edits, + new Text_Diff_Op_copy( + $this->_getLines($from_lines, $from_line_no, + $from_line_no + count($from_lines) - 1), + $this->_getLines($to_lines, $to_line_no, + $to_line_no + count($to_lines) - 1))); + } + + return $edits; + } + + /** + * Get lines from either the old or new text + * + * @access private + * + * @param array &$text_lines Either $from_lines or $to_lines + * @param integer &$line_no Current line number + * @param integer $end Optional end line, when we want to chop more than one line. + * @return array The chopped lines + */ + function _getLines(&$text_lines, &$line_no, $end = false) + { + if (!empty($end)) { + $lines = array(); + // We can shift even more + while ($line_no <= $end) { + array_push($lines, array_shift($text_lines)); + $line_no++; + } + } else { + $lines = array(array_shift($text_lines)); + $line_no++; + } + + return $lines; + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Engine/string.php b/framework/gii/components/Pear/Text/Diff/Engine/string.php new file mode 100644 index 0000000..4b29daa --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Engine/string.php @@ -0,0 +1,237 @@ +<?php +/** + * Parses unified or context diffs output from eg. the diff utility. + * + * Example: + * <code> + * $patch = file_get_contents('example.patch'); + * $diff = new Text_Diff('string', array($patch)); + * $renderer = new Text_Diff_Renderer_inline(); + * echo $renderer->render($diff); + * </code> + * + * $Horde: framework/Text_Diff/Diff/Engine/string.php,v 1.5.2.5 2008/09/10 08:31:58 jan Exp $ + * + * Copyright 2005 Örjan Persson <o@42mm.org> + * Copyright 2005-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Örjan Persson <o@42mm.org> + * @package Text_Diff + * @since 0.2.0 + */ +class Text_Diff_Engine_string { + + /** + * Parses a unified or context diff. + * + * First param contains the whole diff and the second can be used to force + * a specific diff type. If the second parameter is 'autodetect', the + * diff will be examined to find out which type of diff this is. + * + * @param string $diff The diff content. + * @param string $mode The diff mode of the content in $diff. One of + * 'context', 'unified', or 'autodetect'. + * + * @return array List of all diff operations. + */ + function diff($diff, $mode = 'autodetect') + { + if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { + return PEAR::raiseError('Type of diff is unsupported'); + } + + if ($mode == 'autodetect') { + $context = strpos($diff, '***'); + $unified = strpos($diff, '---'); + if ($context === $unified) { + return PEAR::raiseError('Type of diff could not be detected'); + } elseif ($context === false || $unified === false) { + $mode = $context !== false ? 'context' : 'unified'; + } else { + $mode = $context < $unified ? 'context' : 'unified'; + } + } + + // Split by new line and remove the diff header, if there is one. + $diff = explode("\n", $diff); + if (($mode == 'context' && strpos($diff[0], '***') === 0) || + ($mode == 'unified' && strpos($diff[0], '---') === 0)) { + array_shift($diff); + array_shift($diff); + } + + if ($mode == 'context') { + return $this->parseContextDiff($diff); + } else { + return $this->parseUnifiedDiff($diff); + } + } + + /** + * Parses an array containing the unified diff. + * + * @param array $diff Array of lines. + * + * @return array List of all diff operations. + */ + function parseUnifiedDiff($diff) + { + $edits = array(); + $end = count($diff) - 1; + for ($i = 0; $i < $end;) { + $diff1 = array(); + switch (substr($diff[$i], 0, 1)) { + case ' ': + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); + $edits[] = new Text_Diff_Op_copy($diff1); + break; + + case '+': + // get all new lines + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff1); + break; + + case '-': + // get changed or removed lines + $diff2 = array(); + do { + $diff1[] = substr($diff[$i], 1); + } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); + + while ($i < $end && substr($diff[$i], 0, 1) == '+') { + $diff2[] = substr($diff[$i++], 1); + } + if (count($diff2) == 0) { + $edits[] = new Text_Diff_Op_delete($diff1); + } else { + $edits[] = new Text_Diff_Op_change($diff1, $diff2); + } + break; + + default: + $i++; + break; + } + } + + return $edits; + } + + /** + * Parses an array containing the context diff. + * + * @param array $diff Array of lines. + * + * @return array List of all diff operations. + */ + function parseContextDiff(&$diff) + { + $edits = array(); + $i = $max_i = $j = $max_j = 0; + $end = count($diff) - 1; + while ($i < $end && $j < $end) { + while ($i >= $max_i && $j >= $max_j) { + // Find the boundaries of the diff output of the two files + for ($i = $j; + $i < $end && substr($diff[$i], 0, 3) == '***'; + $i++); + for ($max_i = $i; + $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; + $max_i++); + for ($j = $max_i; + $j < $end && substr($diff[$j], 0, 3) == '---'; + $j++); + for ($max_j = $j; + $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; + $max_j++); + } + + // find what hasn't been changed + $array = array(); + while ($i < $max_i && + $j < $max_j && + strcmp($diff[$i], $diff[$j]) == 0) { + $array[] = substr($diff[$i], 2); + $i++; + $j++; + } + + while ($i < $max_i && ($max_j-$j) <= 1) { + if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { + break; + } + $array[] = substr($diff[$i++], 2); + } + + while ($j < $max_j && ($max_i-$i) <= 1) { + if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { + break; + } + $array[] = substr($diff[$j++], 2); + } + if (count($array) > 0) { + $edits[] = new Text_Diff_Op_copy($array); + } + + if ($i < $max_i) { + $diff1 = array(); + switch (substr($diff[$i], 0, 1)) { + case '!': + $diff2 = array(); + do { + $diff1[] = substr($diff[$i], 2); + if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { + $diff2[] = substr($diff[$j++], 2); + } + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); + $edits[] = new Text_Diff_Op_change($diff1, $diff2); + break; + + case '+': + do { + $diff1[] = substr($diff[$i], 2); + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff1); + break; + + case '-': + do { + $diff1[] = substr($diff[$i], 2); + } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); + $edits[] = new Text_Diff_Op_delete($diff1); + break; + } + } + + if ($j < $max_j) { + $diff2 = array(); + switch (substr($diff[$j], 0, 1)) { + case '+': + do { + $diff2[] = substr($diff[$j++], 2); + } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); + $edits[] = new Text_Diff_Op_add($diff2); + break; + + case '-': + do { + $diff2[] = substr($diff[$j++], 2); + } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); + $edits[] = new Text_Diff_Op_delete($diff2); + break; + } + } + } + + return $edits; + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Engine/xdiff.php b/framework/gii/components/Pear/Text/Diff/Engine/xdiff.php new file mode 100644 index 0000000..681ea4c --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Engine/xdiff.php @@ -0,0 +1,63 @@ +<?php +/** + * Class used internally by Diff to actually compute the diffs. + * + * This class uses the xdiff PECL package (http://pecl.php.net/package/xdiff) + * to compute the differences between the two input arrays. + * + * $Horde: framework/Text_Diff/Diff/Engine/xdiff.php,v 1.4.2.3 2008/01/04 10:37:27 jan Exp $ + * + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Jon Parise <jon@horde.org> + * @package Text_Diff + */ +class Text_Diff_Engine_xdiff { + + /** + */ + function diff($from_lines, $to_lines) + { + array_walk($from_lines, array('Text_Diff', 'trimNewlines')); + array_walk($to_lines, array('Text_Diff', 'trimNewlines')); + + /* Convert the two input arrays into strings for xdiff processing. */ + $from_string = implode("\n", $from_lines); + $to_string = implode("\n", $to_lines); + + /* Diff the two strings and convert the result to an array. */ + $diff = xdiff_string_diff($from_string, $to_string, count($to_lines)); + $diff = explode("\n", $diff); + + /* Walk through the diff one line at a time. We build the $edits + * array of diff operations by reading the first character of the + * xdiff output (which is in the "unified diff" format). + * + * Note that we don't have enough information to detect "changed" + * lines using this approach, so we can't add Text_Diff_Op_changed + * instances to the $edits array. The result is still perfectly + * valid, albeit a little less descriptive and efficient. */ + $edits = array(); + foreach ($diff as $line) { + switch ($line[0]) { + case ' ': + $edits[] = new Text_Diff_Op_copy(array(substr($line, 1))); + break; + + case '+': + $edits[] = new Text_Diff_Op_add(array(substr($line, 1))); + break; + + case '-': + $edits[] = new Text_Diff_Op_delete(array(substr($line, 1))); + break; + } + } + + return $edits; + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Mapped.php b/framework/gii/components/Pear/Text/Diff/Mapped.php new file mode 100644 index 0000000..8403759 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Mapped.php @@ -0,0 +1,55 @@ +<?php +/** + * $Horde: framework/Text_Diff/Diff/Mapped.php,v 1.3.2.3 2008/01/04 10:37:27 jan Exp $ + * + * Copyright 2007-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_Diff_Mapped extends Text_Diff { + + /** + * Computes a diff between sequences of strings. + * + * This can be used to compute things like case-insensitve diffs, or diffs + * which ignore changes in white-space. + * + * @param array $from_lines An array of strings. + * @param array $to_lines An array of strings. + * @param array $mapped_from_lines This array should have the same size + * number of elements as $from_lines. The + * elements in $mapped_from_lines and + * $mapped_to_lines are what is actually + * compared when computing the diff. + * @param array $mapped_to_lines This array should have the same number + * of elements as $to_lines. + */ + function Text_Diff_Mapped($from_lines, $to_lines, + $mapped_from_lines, $mapped_to_lines) + { + assert(count($from_lines) == count($mapped_from_lines)); + assert(count($to_lines) == count($mapped_to_lines)); + + parent::Text_Diff($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < count($this->_edits); $i++) { + $orig = &$this->_edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, count($orig)); + $xi += count($orig); + } + + $final = &$this->_edits[$i]->final; + if (is_array($final)) { + $final = array_slice($to_lines, $yi, count($final)); + $yi += count($final); + } + } + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Renderer.php b/framework/gii/components/Pear/Text/Diff/Renderer.php new file mode 100644 index 0000000..c0e2395 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Renderer.php @@ -0,0 +1,237 @@ +<?php +/** + * A class to render Diffs in different formats. + * + * This class renders the diff in classic diff format. It is intended that + * this class be customized via inheritance, to obtain fancier outputs. + * + * $Horde: framework/Text_Diff/Diff/Renderer.php,v 1.5.10.10 2008/01/04 10:37:27 jan Exp $ + * + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + */ +class Text_Diff_Renderer { + + /** + * Number of leading context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses may want to + * set this to other values. + */ + var $_leading_context_lines = 0; + + /** + * Number of trailing context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses may want to + * set this to other values. + */ + var $_trailing_context_lines = 0; + + /** + * Constructor. + */ + function Text_Diff_Renderer($params = array()) + { + foreach ($params as $param => $value) { + $v = '_' . $param; + if (isset($this->$v)) { + $this->$v = $value; + } + } + } + + /** + * Get any renderer parameters. + * + * @return array All parameters of this renderer object. + */ + function getParams() + { + $params = array(); + foreach (get_object_vars($this) as $k => $v) { + if ($k[0] == '_') { + $params[substr($k, 1)] = $v; + } + } + + return $params; + } + + /** + * Renders a diff. + * + * @param Text_Diff $diff A Text_Diff object. + * + * @return string The formatted output. + */ + function render($diff) + { + $xi = $yi = 1; + $block = false; + $context = array(); + + $nlead = $this->_leading_context_lines; + $ntrail = $this->_trailing_context_lines; + + $output = $this->_startDiff(); + + $diffs = $diff->getDiff(); + foreach ($diffs as $i => $edit) { + /* If these are unchanged (copied) lines, and we want to keep + * leading or trailing context lines, extract them from the copy + * block. */ + if (is_a($edit, 'Text_Diff_Op_copy')) { + /* Do we have any diff blocks yet? */ + if (is_array($block)) { + /* How many lines to keep as context from the copy + * block. */ + $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail; + if (count($edit->orig) <= $keep) { + /* We have less lines in the block than we want for + * context => keep the whole block. */ + $block[] = $edit; + } else { + if ($ntrail) { + /* Create a new block with as many lines as we need + * for the trailing context. */ + $context = array_slice($edit->orig, 0, $ntrail); + $block[] = new Text_Diff_Op_copy($context); + } + /* @todo */ + $output .= $this->_block($x0, $ntrail + $xi - $x0, + $y0, $ntrail + $yi - $y0, + $block); + $block = false; + } + } + /* Keep the copy block as the context for the next block. */ + $context = $edit->orig; + } else { + /* Don't we have any diff blocks yet? */ + if (!is_array($block)) { + /* Extract context lines from the preceding copy block. */ + $context = array_slice($context, count($context) - $nlead); + $x0 = $xi - count($context); + $y0 = $yi - count($context); + $block = array(); + if ($context) { + $block[] = new Text_Diff_Op_copy($context); + } + } + $block[] = $edit; + } + + if ($edit->orig) { + $xi += count($edit->orig); + } + if ($edit->final) { + $yi += count($edit->final); + } + } + + if (is_array($block)) { + $output .= $this->_block($x0, $xi - $x0, + $y0, $yi - $y0, + $block); + } + + return $output . $this->_endDiff(); + } + + function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) + { + $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen)); + + foreach ($edits as $edit) { + switch (strtolower(get_class($edit))) { + case 'text_diff_op_copy': + $output .= $this->_context($edit->orig); + break; + + case 'text_diff_op_add': + $output .= $this->_added($edit->final); + break; + + case 'text_diff_op_delete': + $output .= $this->_deleted($edit->orig); + break; + + case 'text_diff_op_change': + $output .= $this->_changed($edit->orig, $edit->final); + break; + } + } + + return $output . $this->_endBlock(); + } + + function _startDiff() + { + return ''; + } + + function _endDiff() + { + return ''; + } + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen > 1) { + $xbeg .= ',' . ($xbeg + $xlen - 1); + } + if ($ylen > 1) { + $ybeg .= ',' . ($ybeg + $ylen - 1); + } + + // this matches the GNU Diff behaviour + if ($xlen && !$ylen) { + $ybeg--; + } elseif (!$xlen) { + $xbeg--; + } + + return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; + } + + function _startBlock($header) + { + return $header . "\n"; + } + + function _endBlock() + { + return ''; + } + + function _lines($lines, $prefix = ' ') + { + return $prefix . implode("\n$prefix", $lines) . "\n"; + } + + function _context($lines) + { + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '> '); + } + + function _deleted($lines) + { + return $this->_lines($lines, '< '); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . "---\n" . $this->_added($final); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Renderer/context.php b/framework/gii/components/Pear/Text/Diff/Renderer/context.php new file mode 100644 index 0000000..7977500 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Renderer/context.php @@ -0,0 +1,77 @@ +<?php +/** + * "Context" diff renderer. + * + * This class renders the diff in classic "context diff" format. + * + * $Horde: framework/Text_Diff/Diff/Renderer/context.php,v 1.3.2.3 2008/01/04 10:37:27 jan Exp $ + * + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + */ + +/** Text_Diff_Renderer */ +require_once 'Text/Diff/Renderer.php'; + +/** + * @package Text_Diff + */ +class Text_Diff_Renderer_context extends Text_Diff_Renderer { + + /** + * Number of leading context "lines" to preserve. + */ + var $_leading_context_lines = 4; + + /** + * Number of trailing context "lines" to preserve. + */ + var $_trailing_context_lines = 4; + + var $_second_block = ''; + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen != 1) { + $xbeg .= ',' . $xlen; + } + if ($ylen != 1) { + $ybeg .= ',' . $ylen; + } + $this->_second_block = "--- $ybeg ----\n"; + return "***************\n*** $xbeg ****"; + } + + function _endBlock() + { + return $this->_second_block; + } + + function _context($lines) + { + $this->_second_block .= $this->_lines($lines, ' '); + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + $this->_second_block .= $this->_lines($lines, '+ '); + return ''; + } + + function _deleted($lines) + { + return $this->_lines($lines, '- '); + } + + function _changed($orig, $final) + { + $this->_second_block .= $this->_lines($final, '! '); + return $this->_lines($orig, '! '); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Renderer/inline.php b/framework/gii/components/Pear/Text/Diff/Renderer/inline.php new file mode 100644 index 0000000..7f4e5ef --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Renderer/inline.php @@ -0,0 +1,170 @@ +<?php +/** + * "Inline" diff renderer. + * + * $Horde: framework/Text_Diff/Diff/Renderer/inline.php,v 1.4.10.14 2008/01/04 10:37:27 jan Exp $ + * + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Ciprian Popovici + * @package Text_Diff + */ + +/** Text_Diff_Renderer */ +require_once 'Text/Diff/Renderer.php'; + +/** + * "Inline" diff renderer. + * + * This class renders diffs in the Wiki-style "inline" format. + * + * @author Ciprian Popovici + * @package Text_Diff + */ +class Text_Diff_Renderer_inline extends Text_Diff_Renderer { + + /** + * Number of leading context "lines" to preserve. + */ + var $_leading_context_lines = 10000; + + /** + * Number of trailing context "lines" to preserve. + */ + var $_trailing_context_lines = 10000; + + /** + * Prefix for inserted text. + */ + var $_ins_prefix = '<ins>'; + + /** + * Suffix for inserted text. + */ + var $_ins_suffix = '</ins>'; + + /** + * Prefix for deleted text. + */ + var $_del_prefix = '<del>'; + + /** + * Suffix for deleted text. + */ + var $_del_suffix = '</del>'; + + /** + * Header for each change block. + */ + var $_block_header = ''; + + /** + * What are we currently splitting on? Used to recurse to show word-level + * changes. + */ + var $_split_level = 'lines'; + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + return $this->_block_header; + } + + function _startBlock($header) + { + return $header; + } + + function _lines($lines, $prefix = ' ', $encode = true) + { + if ($encode) { + array_walk($lines, array(&$this, '_encode')); + } + + if ($this->_split_level == 'words') { + return implode('', $lines); + } else { + return implode("\n", $lines) . "\n"; + } + } + + function _added($lines) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_ins_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_ins_suffix; + return $this->_lines($lines, ' ', false); + } + + function _deleted($lines, $words = false) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_del_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_del_suffix; + return $this->_lines($lines, ' ', false); + } + + function _changed($orig, $final) + { + /* If we've already split on words, don't try to do so again - just + * display. */ + if ($this->_split_level == 'words') { + $prefix = ''; + while ($orig[0] !== false && $final[0] !== false && + substr($orig[0], 0, 1) == ' ' && + substr($final[0], 0, 1) == ' ') { + $prefix .= substr($orig[0], 0, 1); + $orig[0] = substr($orig[0], 1); + $final[0] = substr($final[0], 1); + } + return $prefix . $this->_deleted($orig) . $this->_added($final); + } + + $text1 = implode("\n", $orig); + $text2 = implode("\n", $final); + + /* Non-printing newline marker. */ + $nl = "\0"; + + /* We want to split on word boundaries, but we need to + * preserve whitespace as well. Therefore we split on words, + * but include all blocks of whitespace in the wordlist. */ + $diff = new Text_Diff($this->_splitOnWords($text1, $nl), + $this->_splitOnWords($text2, $nl)); + + /* Get the diff in inline format. */ + $renderer = new Text_Diff_Renderer_inline(array_merge($this->getParams(), + array('split_level' => 'words'))); + + /* Run the diff and get the output. */ + return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; + } + + function _splitOnWords($string, $newlineEscape = "\n") + { + // Ignore \0; otherwise the while loop will never finish. + $string = str_replace("\0", '', $string); + + $words = array(); + $length = strlen($string); + $pos = 0; + + while ($pos < $length) { + // Eat a word with any preceding whitespace. + $spaces = strspn(substr($string, $pos), " \n"); + $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); + $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); + $pos += $spaces + $nextpos; + } + + return $words; + } + + function _encode(&$string) + { + $string = htmlspecialchars($string); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/Renderer/unified.php b/framework/gii/components/Pear/Text/Diff/Renderer/unified.php new file mode 100644 index 0000000..943d519 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/Renderer/unified.php @@ -0,0 +1,67 @@ +<?php +/** + * "Unified" diff renderer. + * + * This class renders the diff in classic "unified diff" format. + * + * $Horde: framework/Text_Diff/Diff/Renderer/unified.php,v 1.3.10.6 2008/01/04 10:37:27 jan Exp $ + * + * Copyright 2004-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @author Ciprian Popovici + * @package Text_Diff + */ + +/** Text_Diff_Renderer */ +require_once 'Text/Diff/Renderer.php'; + +/** + * @package Text_Diff + */ +class Text_Diff_Renderer_unified extends Text_Diff_Renderer { + + /** + * Number of leading context "lines" to preserve. + */ + var $_leading_context_lines = 4; + + /** + * Number of trailing context "lines" to preserve. + */ + var $_trailing_context_lines = 4; + + function _blockHeader($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen != 1) { + $xbeg .= ',' . $xlen; + } + if ($ylen != 1) { + $ybeg .= ',' . $ylen; + } + return "@@ -$xbeg +$ybeg @@"; + } + + function _context($lines) + { + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '+'); + } + + function _deleted($lines) + { + return $this->_lines($lines, '-'); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff/ThreeWay.php b/framework/gii/components/Pear/Text/Diff/ThreeWay.php new file mode 100644 index 0000000..4e4b939 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff/ThreeWay.php @@ -0,0 +1,276 @@ +<?php +/** + * A class for computing three way diffs. + * + * $Horde: framework/Text_Diff/Diff/ThreeWay.php,v 1.3.2.3 2008/01/04 10:37:27 jan Exp $ + * + * Copyright 2007-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + * @since 0.3.0 + */ + +/** Text_Diff */ +require_once 'Text/Diff.php'; + +/** + * A class for computing three way diffs. + * + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_Diff_ThreeWay extends Text_Diff { + + /** + * Conflict counter. + * + * @var integer + */ + var $_conflictingBlocks = 0; + + /** + * Computes diff between 3 sequences of strings. + * + * @param array $orig The original lines to use. + * @param array $final1 The first version to compare to. + * @param array $final2 The second version to compare to. + */ + function Text_Diff_ThreeWay($orig, $final1, $final2) + { + if (extension_loaded('xdiff')) { + $engine = new Text_Diff_Engine_xdiff(); + } else { + $engine = new Text_Diff_Engine_native(); + } + + $this->_edits = $this->_diff3($engine->diff($orig, $final1), + $engine->diff($orig, $final2)); + } + + /** + */ + function mergedOutput($label1 = false, $label2 = false) + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->isConflict()) { + /* FIXME: this should probably be moved somewhere else. */ + $lines = array_merge($lines, + array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), + $edit->final1, + array("======="), + $edit->final2, + array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); + $this->_conflictingBlocks++; + } else { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * @access private + */ + function _diff3($edits1, $edits2) + { + $edits = array(); + $bb = new Text_Diff_ThreeWay_BlockBuilder(); + + $e1 = current($edits1); + $e2 = current($edits2); + while ($e1 || $e2) { + if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) { + /* We have copy blocks from both diffs. This is the (only) + * time we want to emit a diff3 copy block. Flush current + * diff3 diff block, if any. */ + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + $ncopy = min($e1->norig(), $e2->norig()); + assert($ncopy > 0); + $edits[] = new Text_Diff_ThreeWay_Op_copy(array_slice($e1->orig, 0, $ncopy)); + + if ($e1->norig() > $ncopy) { + array_splice($e1->orig, 0, $ncopy); + array_splice($e1->final, 0, $ncopy); + } else { + $e1 = next($edits1); + } + + if ($e2->norig() > $ncopy) { + array_splice($e2->orig, 0, $ncopy); + array_splice($e2->final, 0, $ncopy); + } else { + $e2 = next($edits2); + } + } else { + if ($e1 && $e2) { + if ($e1->orig && $e2->orig) { + $norig = min($e1->norig(), $e2->norig()); + $orig = array_splice($e1->orig, 0, $norig); + array_splice($e2->orig, 0, $norig); + $bb->input($orig); + } + + if (is_a($e1, 'Text_Diff_Op_copy')) { + $bb->out1(array_splice($e1->final, 0, $norig)); + } + + if (is_a($e2, 'Text_Diff_Op_copy')) { + $bb->out2(array_splice($e2->final, 0, $norig)); + } + } + + if ($e1 && ! $e1->orig) { + $bb->out1($e1->final); + $e1 = next($edits1); + } + if ($e2 && ! $e2->orig) { + $bb->out2($e2->final); + $e2 = next($edits2); + } + } + } + + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + return $edits; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_ThreeWay_Op { + + function Text_Diff_ThreeWay_Op($orig = false, $final1 = false, $final2 = false) + { + $this->orig = $orig ? $orig : array(); + $this->final1 = $final1 ? $final1 : array(); + $this->final2 = $final2 ? $final2 : array(); + } + + function merged() + { + if (!isset($this->_merged)) { + if ($this->final1 === $this->final2) { + $this->_merged = &$this->final1; + } elseif ($this->final1 === $this->orig) { + $this->_merged = &$this->final2; + } elseif ($this->final2 === $this->orig) { + $this->_merged = &$this->final1; + } else { + $this->_merged = false; + } + } + + return $this->_merged; + } + + function isConflict() + { + return $this->merged() === false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_ThreeWay_Op_copy extends Text_Diff_ThreeWay_Op { + + function Text_Diff_ThreeWay_Op_Copy($lines = false) + { + $this->orig = $lines ? $lines : array(); + $this->final1 = &$this->orig; + $this->final2 = &$this->orig; + } + + function merged() + { + return $this->orig; + } + + function isConflict() + { + return false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff_ThreeWay_BlockBuilder { + + function Text_Diff_ThreeWay_BlockBuilder() + { + $this->_init(); + } + + function input($lines) + { + if ($lines) { + $this->_append($this->orig, $lines); + } + } + + function out1($lines) + { + if ($lines) { + $this->_append($this->final1, $lines); + } + } + + function out2($lines) + { + if ($lines) { + $this->_append($this->final2, $lines); + } + } + + function isEmpty() + { + return !$this->orig && !$this->final1 && !$this->final2; + } + + function finish() + { + if ($this->isEmpty()) { + return false; + } else { + $edit = new Text_Diff_ThreeWay_Op($this->orig, $this->final1, $this->final2); + $this->_init(); + return $edit; + } + } + + function _init() + { + $this->orig = $this->final1 = $this->final2 = array(); + } + + function _append(&$array, $lines) + { + array_splice($array, sizeof($array), 0, $lines); + } + +} diff --git a/framework/gii/components/Pear/Text/Diff3.php b/framework/gii/components/Pear/Text/Diff3.php new file mode 100644 index 0000000..2c28370 --- /dev/null +++ b/framework/gii/components/Pear/Text/Diff3.php @@ -0,0 +1,276 @@ +<?php +/** + * A class for computing three way diffs. + * + * $Horde: framework/Text_Diff/Diff3.php,v 1.2.10.6 2008/01/04 10:37:26 jan Exp $ + * + * Copyright 2007-2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you did + * not receive this file, see http://opensource.org/licenses/lgpl-license.php. + * + * @package Text_Diff + * @since 0.3.0 + */ + +/** Text_Diff */ +require_once 'Text/Diff.php'; + +/** + * A class for computing three way diffs. + * + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + */ +class Text_Diff3 extends Text_Diff { + + /** + * Conflict counter. + * + * @var integer + */ + var $_conflictingBlocks = 0; + + /** + * Computes diff between 3 sequences of strings. + * + * @param array $orig The original lines to use. + * @param array $final1 The first version to compare to. + * @param array $final2 The second version to compare to. + */ + function Text_Diff3($orig, $final1, $final2) + { + if (extension_loaded('xdiff')) { + $engine = new Text_Diff_Engine_xdiff(); + } else { + $engine = new Text_Diff_Engine_native(); + } + + $this->_edits = $this->_diff3($engine->diff($orig, $final1), + $engine->diff($orig, $final2)); + } + + /** + */ + function mergedOutput($label1 = false, $label2 = false) + { + $lines = array(); + foreach ($this->_edits as $edit) { + if ($edit->isConflict()) { + /* FIXME: this should probably be moved somewhere else. */ + $lines = array_merge($lines, + array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), + $edit->final1, + array("======="), + $edit->final2, + array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); + $this->_conflictingBlocks++; + } else { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * @access private + */ + function _diff3($edits1, $edits2) + { + $edits = array(); + $bb = new Text_Diff3_BlockBuilder(); + + $e1 = current($edits1); + $e2 = current($edits2); + while ($e1 || $e2) { + if ($e1 && $e2 && is_a($e1, 'Text_Diff_Op_copy') && is_a($e2, 'Text_Diff_Op_copy')) { + /* We have copy blocks from both diffs. This is the (only) + * time we want to emit a diff3 copy block. Flush current + * diff3 diff block, if any. */ + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + $ncopy = min($e1->norig(), $e2->norig()); + assert($ncopy > 0); + $edits[] = new Text_Diff3_Op_copy(array_slice($e1->orig, 0, $ncopy)); + + if ($e1->norig() > $ncopy) { + array_splice($e1->orig, 0, $ncopy); + array_splice($e1->final, 0, $ncopy); + } else { + $e1 = next($edits1); + } + + if ($e2->norig() > $ncopy) { + array_splice($e2->orig, 0, $ncopy); + array_splice($e2->final, 0, $ncopy); + } else { + $e2 = next($edits2); + } + } else { + if ($e1 && $e2) { + if ($e1->orig && $e2->orig) { + $norig = min($e1->norig(), $e2->norig()); + $orig = array_splice($e1->orig, 0, $norig); + array_splice($e2->orig, 0, $norig); + $bb->input($orig); + } + + if (is_a($e1, 'Text_Diff_Op_copy')) { + $bb->out1(array_splice($e1->final, 0, $norig)); + } + + if (is_a($e2, 'Text_Diff_Op_copy')) { + $bb->out2(array_splice($e2->final, 0, $norig)); + } + } + + if ($e1 && ! $e1->orig) { + $bb->out1($e1->final); + $e1 = next($edits1); + } + if ($e2 && ! $e2->orig) { + $bb->out2($e2->final); + $e2 = next($edits2); + } + } + } + + if ($edit = $bb->finish()) { + $edits[] = $edit; + } + + return $edits; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff3_Op { + + function Text_Diff3_Op($orig = false, $final1 = false, $final2 = false) + { + $this->orig = $orig ? $orig : array(); + $this->final1 = $final1 ? $final1 : array(); + $this->final2 = $final2 ? $final2 : array(); + } + + function merged() + { + if (!isset($this->_merged)) { + if ($this->final1 === $this->final2) { + $this->_merged = &$this->final1; + } elseif ($this->final1 === $this->orig) { + $this->_merged = &$this->final2; + } elseif ($this->final2 === $this->orig) { + $this->_merged = &$this->final1; + } else { + $this->_merged = false; + } + } + + return $this->_merged; + } + + function isConflict() + { + return $this->merged() === false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff3_Op_copy extends Text_Diff3_Op { + + function Text_Diff3_Op_Copy($lines = false) + { + $this->orig = $lines ? $lines : array(); + $this->final1 = &$this->orig; + $this->final2 = &$this->orig; + } + + function merged() + { + return $this->orig; + } + + function isConflict() + { + return false; + } + +} + +/** + * @package Text_Diff + * @author Geoffrey T. Dairiki <dairiki@dairiki.org> + * + * @access private + */ +class Text_Diff3_BlockBuilder { + + function Text_Diff3_BlockBuilder() + { + $this->_init(); + } + + function input($lines) + { + if ($lines) { + $this->_append($this->orig, $lines); + } + } + + function out1($lines) + { + if ($lines) { + $this->_append($this->final1, $lines); + } + } + + function out2($lines) + { + if ($lines) { + $this->_append($this->final2, $lines); + } + } + + function isEmpty() + { + return !$this->orig && !$this->final1 && !$this->final2; + } + + function finish() + { + if ($this->isEmpty()) { + return false; + } else { + $edit = new Text_Diff3_Op($this->orig, $this->final1, $this->final2); + $this->_init(); + return $edit; + } + } + + function _init() + { + $this->orig = $this->final1 = $this->final2 = array(); + } + + function _append(&$array, $lines) + { + array_splice($array, sizeof($array), 0, $lines); + } + +} diff --git a/framework/gii/components/TextDiff.php b/framework/gii/components/TextDiff.php new file mode 100644 index 0000000..b9e13f5 --- /dev/null +++ b/framework/gii/components/TextDiff.php @@ -0,0 +1,22 @@ +<?php + +error_reporting(E_ALL); + +Yii::import('gii.components.Pear.*'); +require_once 'Text/Diff.php'; +require_once 'Text/Diff/Renderer.php'; +require_once 'Text/Diff/Renderer/inline.php'; + +class TextDiff extends CComponent +{ + public static function compare($lines1, $lines2) + { + if(is_string($lines1)) + $lines1=explode("\n",$lines1); + if(is_string($lines2)) + $lines2=explode("\n",$lines2); + $diff = new Text_Diff('auto', array($lines1, $lines2)); + $renderer = new Text_Diff_Renderer_inline(); + return $renderer->render($diff); + } +}
\ No newline at end of file diff --git a/framework/gii/components/UserIdentity.php b/framework/gii/components/UserIdentity.php new file mode 100644 index 0000000..9de480e --- /dev/null +++ b/framework/gii/components/UserIdentity.php @@ -0,0 +1,20 @@ +<?php + +class UserIdentity extends CUserIdentity +{ + /** + * Authenticates a user. + * @return boolean whether authentication succeeds. + */ + public function authenticate() + { + $password=Yii::app()->getModule('gii')->password; + if($password===null) + throw new CException('Please configure the "password" property of the "gii" module.'); + else if($password===false || $password===$this->password) + $this->errorCode=self::ERROR_NONE; + else + $this->errorCode=self::ERROR_UNKNOWN_IDENTITY; + return !$this->errorCode; + } +}
\ No newline at end of file diff --git a/framework/gii/controllers/DefaultController.php b/framework/gii/controllers/DefaultController.php new file mode 100644 index 0000000..44815b5 --- /dev/null +++ b/framework/gii/controllers/DefaultController.php @@ -0,0 +1,58 @@ +<?php + +class DefaultController extends CController +{ + public $layout='/layouts/column1'; + + public function getPageTitle() + { + if($this->action->id==='index') + return 'Gii: a Web-based code generator for Yii'; + else + return 'Gii - '.ucfirst($this->action->id).' Generator'; + } + + public function actionIndex() + { + $this->render('index'); + } + + public function actionError() + { + if($error=Yii::app()->errorHandler->error) + { + if(Yii::app()->request->isAjaxRequest) + echo $error['message']; + else + $this->render('error', $error); + } + } + + /** + * Displays the login page + */ + public function actionLogin() + { + $model=Yii::createComponent('gii.models.LoginForm'); + + // 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()->createUrl('gii/default/index')); + } + // 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(false); + $this->redirect(Yii::app()->createUrl('gii/default/index')); + } +}
\ No newline at end of file diff --git a/framework/gii/generators/controller/ControllerCode.php b/framework/gii/generators/controller/ControllerCode.php new file mode 100644 index 0000000..6cdc9fd --- /dev/null +++ b/framework/gii/generators/controller/ControllerCode.php @@ -0,0 +1,130 @@ +<?php + +class ControllerCode extends CCodeModel +{ + public $controller; + public $baseClass='Controller'; + public $actions='index'; + + public function rules() + { + return array_merge(parent::rules(), array( + array('controller, actions, baseClass', 'filter', 'filter'=>'trim'), + array('controller, baseClass', 'required'), + array('controller', 'match', 'pattern'=>'/^\w+[\w+\\/]*$/', 'message'=>'{attribute} should only contain word characters and slashes.'), + array('actions', 'match', 'pattern'=>'/^\w+[\w\s,]*$/', 'message'=>'{attribute} should only contain word characters, spaces and commas.'), + array('baseClass', 'match', 'pattern'=>'/^[a-zA-Z_]\w*$/', 'message'=>'{attribute} should only contain word characters.'), + array('baseClass', 'validateReservedWord', 'skipOnError'=>true), + array('baseClass, actions', 'sticky'), + )); + } + + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), array( + 'baseClass'=>'Base Class', + 'controller'=>'Controller ID', + 'actions'=>'Action IDs', + )); + } + + public function requiredTemplates() + { + return array( + 'controller.php', + 'view.php', + ); + } + + public function successMessage() + { + $link=CHtml::link('try it now', Yii::app()->createUrl($this->controller), array('target'=>'_blank')); + return "The controller has been generated successfully. You may $link."; + } + + public function prepare() + { + $this->files=array(); + $templatePath=$this->templatePath; + + $this->files[]=new CCodeFile( + $this->controllerFile, + $this->render($templatePath.'/controller.php') + ); + + foreach($this->getActionIDs() as $action) + { + $this->files[]=new CCodeFile( + $this->getViewFile($action), + $this->render($templatePath.'/view.php', array('action'=>$action)) + ); + } + } + + public function getActionIDs() + { + $actions=preg_split('/[\s,]+/',$this->actions,-1,PREG_SPLIT_NO_EMPTY); + $actions=array_unique($actions); + sort($actions); + return $actions; + } + + public function getControllerClass() + { + if(($pos=strrpos($this->controller,'/'))!==false) + return ucfirst(substr($this->controller,$pos+1)).'Controller'; + else + return ucfirst($this->controller).'Controller'; + } + + public function getModule() + { + if(($pos=strpos($this->controller,'/'))!==false) + { + $id=substr($this->controller,0,$pos); + if(($module=Yii::app()->getModule($id))!==null) + return $module; + } + return Yii::app(); + } + + public function getControllerID() + { + if($this->getModule()!==Yii::app()) + $id=substr($this->controller,strpos($this->controller,'/')+1); + else + $id=$this->controller; + if(($pos=strrpos($id,'/'))!==false) + $id[$pos+1]=strtolower($id[$pos+1]); + else + $id[0]=strtolower($id[0]); + return $id; + } + + public function getUniqueControllerID() + { + $id=$this->controller; + if(($pos=strrpos($id,'/'))!==false) + $id[$pos+1]=strtolower($id[$pos+1]); + else + $id[0]=strtolower($id[0]); + return $id; + } + + public function getControllerFile() + { + $module=$this->getModule(); + $id=$this->getControllerID(); + if(($pos=strrpos($id,'/'))!==false) + $id[$pos+1]=strtoupper($id[$pos+1]); + else + $id[0]=strtoupper($id[0]); + return $module->getControllerPath().'/'.$id.'Controller.php'; + } + + public function getViewFile($action) + { + $module=$this->getModule(); + return $module->getViewPath().'/'.$this->getControllerID().'/'.$action.'.php'; + } +}
\ No newline at end of file diff --git a/framework/gii/generators/controller/ControllerGenerator.php b/framework/gii/generators/controller/ControllerGenerator.php new file mode 100644 index 0000000..91ffaaa --- /dev/null +++ b/framework/gii/generators/controller/ControllerGenerator.php @@ -0,0 +1,6 @@ +<?php + +class ControllerGenerator extends CCodeGenerator +{ + public $codeModel='gii.generators.controller.ControllerCode'; +}
\ No newline at end of file diff --git a/framework/gii/generators/controller/templates/default/controller.php b/framework/gii/generators/controller/templates/default/controller.php new file mode 100644 index 0000000..ba0b627 --- /dev/null +++ b/framework/gii/generators/controller/templates/default/controller.php @@ -0,0 +1,45 @@ +<?php +/** + * This is the template for generating a controller class file. + * The following variables are available in this template: + * - $this: the ControllerCode object + */ +?> +<?php echo "<?php\n"; ?> + +class <?php echo $this->controllerClass; ?> extends <?php echo $this->baseClass."\n"; ?> +{ +<?php foreach($this->getActionIDs() as $action): ?> + public function action<?php echo ucfirst($action); ?>() + { + $this->render('<?php echo $action; ?>'); + } + +<?php endforeach; ?> + // Uncomment the following methods and override them if needed + /* + public function filters() + { + // return the filter configuration for this controller, e.g.: + return array( + 'inlineFilterName', + array( + 'class'=>'path.to.FilterClass', + 'propertyName'=>'propertyValue', + ), + ); + } + + public function actions() + { + // return external action classes, e.g.: + return array( + 'action1'=>'path.to.ActionClass', + 'action2'=>array( + 'class'=>'path.to.AnotherActionClass', + 'propertyName'=>'propertyValue', + ), + ); + } + */ +}
\ No newline at end of file diff --git a/framework/gii/generators/controller/templates/default/view.php b/framework/gii/generators/controller/templates/default/view.php new file mode 100644 index 0000000..26fb36a --- /dev/null +++ b/framework/gii/generators/controller/templates/default/view.php @@ -0,0 +1,33 @@ +<?php +/** + * This is the template for generating an action view file. + * The following variables are available in this template: + * - $this: the ControllerCode object + * - $action: the action ID + */ +?> +<?php +echo "<?php\n"; +$label=ucwords(trim(strtolower(str_replace(array('-','_','.'),' ',preg_replace('/(?<![A-Z])[A-Z]/', ' \0', basename($this->getControllerID())))))); +if($action==='index') +{ + echo "\$this->breadcrumbs=array( + '$label', +);"; +} +else +{ + $action=ucfirst($action); + echo "\$this->breadcrumbs=array( + '$label'=>array('/{$this->uniqueControllerID}'), + '$action', +);"; +} +?> +?> +<h1><?php echo '<?php'; ?> echo $this->id . '/' . $this->action->id; ?></h1> + +<p> + You may change the content of this page by modifying + the file <tt><?php echo '<?php'; ?> echo __FILE__; ?></tt>. +</p> diff --git a/framework/gii/generators/controller/views/index.php b/framework/gii/generators/controller/views/index.php new file mode 100644 index 0000000..6224e7e --- /dev/null +++ b/framework/gii/generators/controller/views/index.php @@ -0,0 +1,44 @@ +<h1>Controller Generator</h1> + +<p>This generator helps you to quickly generate a new controller class, +one or several controller actions and their corresponding views.</p> + +<?php $form=$this->beginWidget('CCodeForm', array('model'=>$model)); ?> + + <div class="row"> + <?php echo $form->labelEx($model,'controller'); ?> + <?php echo $form->textField($model,'controller',array('size'=>65)); ?> + <div class="tooltip"> + Controller ID is case-sensitive. Below are some examples: + <ul> + <li><code>post</code> generates <code>PostController.php</code></li> + <li><code>postTag</code> generates <code>PostTagController.php</code></li> + <li><code>admin/user</code> generates <code>admin/UserController.php</code>. + If the application has an <code>admin</code> module enabled, + it will generate <code>UserController</code> within the module instead. + </li> + </ul> + </div> + <?php echo $form->error($model,'controller'); ?> + </div> + + <div class="row sticky"> + <?php echo $form->labelEx($model,'baseClass'); ?> + <?php echo $form->textField($model,'baseClass',array('size'=>65)); ?> + <div class="tooltip"> + This is the class that the new controller class will extend from. + Please make sure the class exists and can be autoloaded. + </div> + <?php echo $form->error($model,'baseClass'); ?> + </div> + + <div class="row"> + <?php echo $form->labelEx($model,'actions'); ?> + <?php echo $form->textField($model,'actions',array('size'=>65)); ?> + <div class="tooltip"> + Action IDs are case-insensitive. Separate multiple action IDs with commas or spaces. + </div> + <?php echo $form->error($model,'actions'); ?> + </div> + +<?php $this->endWidget(); ?> diff --git a/framework/gii/generators/crud/CrudCode.php b/framework/gii/generators/crud/CrudCode.php new file mode 100644 index 0000000..b08eb6d --- /dev/null +++ b/framework/gii/generators/crud/CrudCode.php @@ -0,0 +1,248 @@ +<?php + +class CrudCode extends CCodeModel +{ + public $model; + public $controller; + public $baseControllerClass='Controller'; + + private $_modelClass; + private $_table; + + public function rules() + { + return array_merge(parent::rules(), array( + array('model, controller', 'filter', 'filter'=>'trim'), + array('model, controller, baseControllerClass', 'required'), + array('model', 'match', 'pattern'=>'/^\w+[\w+\\.]*$/', 'message'=>'{attribute} should only contain word characters and dots.'), + array('controller', 'match', 'pattern'=>'/^\w+[\w+\\/]*$/', 'message'=>'{attribute} should only contain word characters and slashes.'), + array('baseControllerClass', 'match', 'pattern'=>'/^[a-zA-Z_]\w*$/', 'message'=>'{attribute} should only contain word characters.'), + array('baseControllerClass', 'validateReservedWord', 'skipOnError'=>true), + array('model', 'validateModel'), + array('baseControllerClass', 'sticky'), + )); + } + + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), array( + 'model'=>'Model Class', + 'controller'=>'Controller ID', + 'baseControllerClass'=>'Base Controller Class', + )); + } + + public function requiredTemplates() + { + return array( + 'controller.php', + ); + } + + public function init() + { + if(Yii::app()->db===null) + throw new CHttpException(500,'An active "db" connection is required to run this generator.'); + parent::init(); + } + + public function successMessage() + { + $link=CHtml::link('try it now', Yii::app()->createUrl($this->controller), array('target'=>'_blank')); + return "The controller has been generated successfully. You may $link."; + } + + public function validateModel($attribute,$params) + { + if($this->hasErrors('model')) + return; + $class=@Yii::import($this->model,true); + if(!is_string($class) || !$this->classExists($class)) + $this->addError('model', "Class '{$this->model}' does not exist or has syntax error."); + else if(!is_subclass_of($class,'CActiveRecord')) + $this->addError('model', "'{$this->model}' must extend from CActiveRecord."); + else + { + $table=CActiveRecord::model($class)->tableSchema; + if($table->primaryKey===null) + $this->addError('model',"Table '{$table->name}' does not have a primary key."); + else if(is_array($table->primaryKey)) + $this->addError('model',"Table '{$table->name}' has a composite primary key which is not supported by crud generator."); + else + { + $this->_modelClass=$class; + $this->_table=$table; + } + } + } + + public function prepare() + { + $this->files=array(); + $templatePath=$this->templatePath; + $controllerTemplateFile=$templatePath.DIRECTORY_SEPARATOR.'controller.php'; + + $this->files[]=new CCodeFile( + $this->controllerFile, + $this->render($controllerTemplateFile) + ); + + $files=scandir($templatePath); + foreach($files as $file) + { + if(is_file($templatePath.'/'.$file) && CFileHelper::getExtension($file)==='php' && $file!=='controller.php') + { + $this->files[]=new CCodeFile( + $this->viewPath.DIRECTORY_SEPARATOR.$file, + $this->render($templatePath.'/'.$file) + ); + } + } + } + + public function getModelClass() + { + return $this->_modelClass; + } + + public function getControllerClass() + { + if(($pos=strrpos($this->controller,'/'))!==false) + return ucfirst(substr($this->controller,$pos+1)).'Controller'; + else + return ucfirst($this->controller).'Controller'; + } + + public function getModule() + { + if(($pos=strpos($this->controller,'/'))!==false) + { + $id=substr($this->controller,0,$pos); + if(($module=Yii::app()->getModule($id))!==null) + return $module; + } + return Yii::app(); + } + + public function getControllerID() + { + if($this->getModule()!==Yii::app()) + $id=substr($this->controller,strpos($this->controller,'/')+1); + else + $id=$this->controller; + if(($pos=strrpos($id,'/'))!==false) + $id[$pos+1]=strtolower($id[$pos+1]); + else + $id[0]=strtolower($id[0]); + return $id; + } + + public function getUniqueControllerID() + { + $id=$this->controller; + if(($pos=strrpos($id,'/'))!==false) + $id[$pos+1]=strtolower($id[$pos+1]); + else + $id[0]=strtolower($id[0]); + return $id; + } + + public function getControllerFile() + { + $module=$this->getModule(); + $id=$this->getControllerID(); + if(($pos=strrpos($id,'/'))!==false) + $id[$pos+1]=strtoupper($id[$pos+1]); + else + $id[0]=strtoupper($id[0]); + return $module->getControllerPath().'/'.$id.'Controller.php'; + } + + public function getViewPath() + { + return $this->getModule()->getViewPath().'/'.$this->getControllerID(); + } + + public function getTableSchema() + { + return $this->_table; + } + + public function generateInputLabel($modelClass,$column) + { + return "CHtml::activeLabelEx(\$model,'{$column->name}')"; + } + + public function generateInputField($modelClass,$column) + { + if($column->type==='boolean') + return "CHtml::activeCheckBox(\$model,'{$column->name}')"; + else if(stripos($column->dbType,'text')!==false) + return "CHtml::activeTextArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))"; + else + { + if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name)) + $inputField='activePasswordField'; + else + $inputField='activeTextField'; + + if($column->type!=='string' || $column->size===null) + return "CHtml::{$inputField}(\$model,'{$column->name}')"; + else + { + if(($size=$maxLength=$column->size)>60) + $size=60; + return "CHtml::{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))"; + } + } + } + + public function generateActiveLabel($modelClass,$column) + { + return "\$form->labelEx(\$model,'{$column->name}')"; + } + + public function generateActiveField($modelClass,$column) + { + if($column->type==='boolean') + return "\$form->checkBox(\$model,'{$column->name}')"; + else if(stripos($column->dbType,'text')!==false) + return "\$form->textArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))"; + else + { + if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name)) + $inputField='passwordField'; + else + $inputField='textField'; + + if($column->type!=='string' || $column->size===null) + return "\$form->{$inputField}(\$model,'{$column->name}')"; + else + { + if(($size=$maxLength=$column->size)>60) + $size=60; + return "\$form->{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))"; + } + } + } + + public function guessNameColumn($columns) + { + foreach($columns as $column) + { + if(!strcasecmp($column->name,'name')) + return $column->name; + } + foreach($columns as $column) + { + if(!strcasecmp($column->name,'title')) + return $column->name; + } + foreach($columns as $column) + { + if($column->isPrimaryKey) + return $column->name; + } + return 'id'; + } +}
\ No newline at end of file diff --git a/framework/gii/generators/crud/CrudGenerator.php b/framework/gii/generators/crud/CrudGenerator.php new file mode 100644 index 0000000..790e073 --- /dev/null +++ b/framework/gii/generators/crud/CrudGenerator.php @@ -0,0 +1,6 @@ +<?php + +class CrudGenerator extends CCodeGenerator +{ + public $codeModel='gii.generators.crud.CrudCode'; +}
\ No newline at end of file diff --git a/framework/gii/generators/crud/templates/default/_form.php b/framework/gii/generators/crud/templates/default/_form.php new file mode 100644 index 0000000..6f86788 --- /dev/null +++ b/framework/gii/generators/crud/templates/default/_form.php @@ -0,0 +1,39 @@ +<?php +/** + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<div class="form"> + +<?php echo "<?php \$form=\$this->beginWidget('CActiveForm', array( + 'id'=>'".$this->class2id($this->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($this->tableSchema->columns as $column) +{ + if($column->autoIncrement) + continue; +?> + <div class="row"> + <?php echo "<?php echo ".$this->generateActiveLabel($this->modelClass,$column)."; ?>\n"; ?> + <?php echo "<?php echo ".$this->generateActiveField($this->modelClass,$column)."; ?>\n"; ?> + <?php echo "<?php echo \$form->error(\$model,'{$column->name}'); ?>\n"; ?> + </div> + +<?php +} +?> + <div class="row buttons"> + <?php echo "<?php echo CHtml::submitButton(\$model->isNewRecord ? 'Create' : 'Save'); ?>\n"; ?> + </div> + +<?php echo "<?php \$this->endWidget(); ?>\n"; ?> + +</div><!-- form -->
\ No newline at end of file diff --git a/framework/gii/generators/crud/templates/default/_search.php b/framework/gii/generators/crud/templates/default/_search.php new file mode 100644 index 0000000..91c4fe8 --- /dev/null +++ b/framework/gii/generators/crud/templates/default/_search.php @@ -0,0 +1,32 @@ +<?php +/** + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<div class="wide form"> + +<?php echo "<?php \$form=\$this->beginWidget('CActiveForm', array( + 'action'=>Yii::app()->createUrl(\$this->route), + 'method'=>'get', +)); ?>\n"; ?> + +<?php foreach($this->tableSchema->columns as $column): ?> +<?php + $field=$this->generateInputField($this->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($this->modelClass,$column)."; ?>\n"; ?> + </div> + +<?php endforeach; ?> + <div class="row buttons"> + <?php echo "<?php echo CHtml::submitButton('Search'); ?>\n"; ?> + </div> + +<?php echo "<?php \$this->endWidget(); ?>\n"; ?> + +</div><!-- search-form -->
\ No newline at end of file diff --git a/framework/gii/generators/crud/templates/default/_view.php b/framework/gii/generators/crud/templates/default/_view.php new file mode 100644 index 0000000..9c3529a --- /dev/null +++ b/framework/gii/generators/crud/templates/default/_view.php @@ -0,0 +1,26 @@ +<?php +/** + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<div class="view"> + +<?php +echo "\t<b><?php echo CHtml::encode(\$data->getAttributeLabel('{$this->tableSchema->primaryKey}')); ?>:</b>\n"; +echo "\t<?php echo CHtml::link(CHtml::encode(\$data->{$this->tableSchema->primaryKey}), array('view', 'id'=>\$data->{$this->tableSchema->primaryKey})); ?>\n\t<br />\n\n"; +$count=0; +foreach($this->tableSchema->columns as $column) +{ + if($column->isPrimaryKey) + continue; + if(++$count==7) + echo "\t<?php /*\n"; + echo "\t<b><?php echo CHtml::encode(\$data->getAttributeLabel('{$column->name}')); ?>:</b>\n"; + echo "\t<?php echo CHtml::encode(\$data->{$column->name}); ?>\n\t<br />\n\n"; +} +if($count>=7) + echo "\t*/ ?>\n"; +?> + +</div>
\ No newline at end of file diff --git a/framework/gii/generators/crud/templates/default/admin.php b/framework/gii/generators/crud/templates/default/admin.php new file mode 100644 index 0000000..cc060c2 --- /dev/null +++ b/framework/gii/generators/crud/templates/default/admin.php @@ -0,0 +1,70 @@ +<?php +/** + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<?php +echo "<?php\n"; +$label=$this->pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label'=>array('index'), + 'Manage', +);\n"; +?> + +$this->menu=array( + array('label'=>'List <?php echo $this->modelClass; ?>', 'url'=>array('index')), + array('label'=>'Create <?php echo $this->modelClass; ?>', 'url'=>array('create')), +); + +Yii::app()->clientScript->registerScript('search', " +$('.search-button').click(function(){ + $('.search-form').toggle(); + return false; +}); +$('.search-form form').submit(function(){ + $.fn.yiiGridView.update('<?php echo $this->class2id($this->modelClass); ?>-grid', { + data: $(this).serialize() + }); + return false; +}); +"); +?> + +<h1>Manage <?php echo $this->pluralize($this->class2name($this->modelClass)); ?></h1> + +<p> +You may optionally enter a comparison operator (<b><</b>, <b><=</b>, <b>></b>, <b>>=</b>, <b><></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($this->modelClass); ?>-grid', + 'dataProvider'=>$model->search(), + 'filter'=>$model, + 'columns'=>array( +<?php +$count=0; +foreach($this->tableSchema->columns as $column) +{ + if(++$count==7) + echo "\t\t/*\n"; + echo "\t\t'".$column->name."',\n"; +} +if($count>=7) + echo "\t\t*/\n"; +?> + array( + 'class'=>'CButtonColumn', + ), + ), +)); ?> diff --git a/framework/gii/generators/crud/templates/default/controller.php b/framework/gii/generators/crud/templates/default/controller.php new file mode 100644 index 0000000..56eb826 --- /dev/null +++ b/framework/gii/generators/crud/templates/default/controller.php @@ -0,0 +1,183 @@ +<?php +/** + * This is the template for generating a controller class file for CRUD feature. + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<?php echo "<?php\n"; ?> + +class <?php echo $this->controllerClass; ?> extends <?php echo $this->baseControllerClass."\n"; ?> +{ + /** + * @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'; + + /** + * @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. + * @param integer $id the ID of the model to be displayed + */ + public function actionView($id) + { + $this->render('view',array( + 'model'=>$this->loadModel($id), + )); + } + + /** + * Creates a new model. + * If creation is successful, the browser will be redirected to the 'view' page. + */ + public function actionCreate() + { + $model=new <?php echo $this->modelClass; ?>; + + // Uncomment the following line if AJAX validation is needed + // $this->performAjaxValidation($model); + + if(isset($_POST['<?php echo $this->modelClass; ?>'])) + { + $model->attributes=$_POST['<?php echo $this->modelClass; ?>']; + if($model->save()) + $this->redirect(array('view','id'=>$model-><?php echo $this->tableSchema->primaryKey; ?>)); + } + + $this->render('create',array( + 'model'=>$model, + )); + } + + /** + * Updates a particular model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id the ID of the model to be updated + */ + public function actionUpdate($id) + { + $model=$this->loadModel($id); + + // Uncomment the following line if AJAX validation is needed + // $this->performAjaxValidation($model); + + if(isset($_POST['<?php echo $this->modelClass; ?>'])) + { + $model->attributes=$_POST['<?php echo $this->modelClass; ?>']; + if($model->save()) + $this->redirect(array('view','id'=>$model-><?php echo $this->tableSchema->primaryKey; ?>)); + } + + $this->render('update',array( + 'model'=>$model, + )); + } + + /** + * Deletes a particular model. + * If deletion is successful, the browser will be redirected to the 'admin' page. + * @param integer $id the ID of the model to be deleted + */ + public function actionDelete($id) + { + if(Yii::app()->request->isPostRequest) + { + // we only allow deletion via POST request + $this->loadModel($id)->delete(); + + // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser + if(!isset($_GET['ajax'])) + $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin')); + } + 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 $this->modelClass; ?>'); + $this->render('index',array( + 'dataProvider'=>$dataProvider, + )); + } + + /** + * Manages all models. + */ + public function actionAdmin() + { + $model=new <?php echo $this->modelClass; ?>('search'); + $model->unsetAttributes(); // clear any default values + if(isset($_GET['<?php echo $this->modelClass; ?>'])) + $model->attributes=$_GET['<?php echo $this->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. + * @param integer the ID of the model to be loaded + */ + public function loadModel($id) + { + $model=<?php echo $this->modelClass; ?>::model()->findByPk($id); + if($model===null) + throw new CHttpException(404,'The requested page does not exist.'); + return $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($this->modelClass); ?>-form') + { + echo CActiveForm::validate($model); + Yii::app()->end(); + } + } +} diff --git a/framework/gii/generators/crud/templates/default/create.php b/framework/gii/generators/crud/templates/default/create.php new file mode 100644 index 0000000..817a7f6 --- /dev/null +++ b/framework/gii/generators/crud/templates/default/create.php @@ -0,0 +1,24 @@ +<?php +/** + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<?php +echo "<?php\n"; +$label=$this->pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label'=>array('index'), + 'Create', +);\n"; +?> + +$this->menu=array( + array('label'=>'List <?php echo $this->modelClass; ?>', 'url'=>array('index')), + array('label'=>'Manage <?php echo $this->modelClass; ?>', 'url'=>array('admin')), +); +?> + +<h1>Create <?php echo $this->modelClass; ?></h1> + +<?php echo "<?php echo \$this->renderPartial('_form', array('model'=>\$model)); ?>"; ?> diff --git a/framework/gii/generators/crud/templates/default/index.php b/framework/gii/generators/crud/templates/default/index.php new file mode 100644 index 0000000..5bd1b0f --- /dev/null +++ b/framework/gii/generators/crud/templates/default/index.php @@ -0,0 +1,26 @@ +<?php +/** + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<?php +echo "<?php\n"; +$label=$this->pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label', +);\n"; +?> + +$this->menu=array( + array('label'=>'Create <?php echo $this->modelClass; ?>', 'url'=>array('create')), + array('label'=>'Manage <?php echo $this->modelClass; ?>', 'url'=>array('admin')), +); +?> + +<h1><?php echo $label; ?></h1> + +<?php echo "<?php"; ?> $this->widget('zii.widgets.CListView', array( + 'dataProvider'=>$dataProvider, + 'itemView'=>'_view', +)); ?> diff --git a/framework/gii/generators/crud/templates/default/update.php b/framework/gii/generators/crud/templates/default/update.php new file mode 100644 index 0000000..0004686 --- /dev/null +++ b/framework/gii/generators/crud/templates/default/update.php @@ -0,0 +1,28 @@ +<?php +/** + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<?php +echo "<?php\n"; +$nameColumn=$this->guessNameColumn($this->tableSchema->columns); +$label=$this->pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label'=>array('index'), + \$model->{$nameColumn}=>array('view','id'=>\$model->{$this->tableSchema->primaryKey}), + 'Update', +);\n"; +?> + +$this->menu=array( + array('label'=>'List <?php echo $this->modelClass; ?>', 'url'=>array('index')), + array('label'=>'Create <?php echo $this->modelClass; ?>', 'url'=>array('create')), + array('label'=>'View <?php echo $this->modelClass; ?>', 'url'=>array('view', 'id'=>$model-><?php echo $this->tableSchema->primaryKey; ?>)), + array('label'=>'Manage <?php echo $this->modelClass; ?>', 'url'=>array('admin')), +); +?> + +<h1>Update <?php echo $this->modelClass." <?php echo \$model->{$this->tableSchema->primaryKey}; ?>"; ?></h1> + +<?php echo "<?php echo \$this->renderPartial('_form', array('model'=>\$model)); ?>"; ?>
\ No newline at end of file diff --git a/framework/gii/generators/crud/templates/default/view.php b/framework/gii/generators/crud/templates/default/view.php new file mode 100644 index 0000000..b52004d --- /dev/null +++ b/framework/gii/generators/crud/templates/default/view.php @@ -0,0 +1,36 @@ +<?php +/** + * The following variables are available in this template: + * - $this: the CrudCode object + */ +?> +<?php +echo "<?php\n"; +$nameColumn=$this->guessNameColumn($this->tableSchema->columns); +$label=$this->pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label'=>array('index'), + \$model->{$nameColumn}, +);\n"; +?> + +$this->menu=array( + array('label'=>'List <?php echo $this->modelClass; ?>', 'url'=>array('index')), + array('label'=>'Create <?php echo $this->modelClass; ?>', 'url'=>array('create')), + array('label'=>'Update <?php echo $this->modelClass; ?>', 'url'=>array('update', 'id'=>$model-><?php echo $this->tableSchema->primaryKey; ?>)), + array('label'=>'Delete <?php echo $this->modelClass; ?>', 'url'=>'#', 'linkOptions'=>array('submit'=>array('delete','id'=>$model-><?php echo $this->tableSchema->primaryKey; ?>),'confirm'=>'Are you sure you want to delete this item?')), + array('label'=>'Manage <?php echo $this->modelClass; ?>', 'url'=>array('admin')), +); +?> + +<h1>View <?php echo $this->modelClass." #<?php echo \$model->{$this->tableSchema->primaryKey}; ?>"; ?></h1> + +<?php echo "<?php"; ?> $this->widget('zii.widgets.CDetailView', array( + 'data'=>$model, + 'attributes'=>array( +<?php +foreach($this->tableSchema->columns as $column) + echo "\t\t'".$column->name."',\n"; +?> + ), +)); ?> diff --git a/framework/gii/generators/crud/views/index.php b/framework/gii/generators/crud/views/index.php new file mode 100644 index 0000000..d7271dc --- /dev/null +++ b/framework/gii/generators/crud/views/index.php @@ -0,0 +1,64 @@ +<?php +$class=get_class($model); +Yii::app()->clientScript->registerScript('gii.crud'," +$('#{$class}_controller').change(function(){ + $(this).data('changed',$(this).val()!=''); +}); +$('#{$class}_model').bind('keyup change', function(){ + var controller=$('#{$class}_controller'); + if(!controller.data('changed')) { + var id=new String($(this).val().match(/\\w*$/)); + if(id.length>0) + id=id.substring(0,1).toLowerCase()+id.substring(1); + controller.val(id); + } +}); +"); +?> +<h1>Crud Generator</h1> + +<p>This generator generates a controller and views that implement CRUD operations for the specified data model.</p> + +<?php $form=$this->beginWidget('CCodeForm', array('model'=>$model)); ?> + + <div class="row"> + <?php echo $form->labelEx($model,'model'); ?> + <?php echo $form->textField($model,'model',array('size'=>65)); ?> + <div class="tooltip"> + Model class is case-sensitive. It can be either a class name (e.g. <code>Post</code>) + or the path alias of the class file (e.g. <code>application.models.Post</code>). + Note that if the former, the class must be auto-loadable. + </div> + <?php echo $form->error($model,'model'); ?> + </div> + + <div class="row"> + <?php echo $form->labelEx($model,'controller'); ?> + <?php echo $form->textField($model,'controller',array('size'=>65)); ?> + <div class="tooltip"> + Controller ID is case-sensitive. CRUD controllers are often named after + the model class name that they are dealing with. Below are some examples: + <ul> + <li><code>post</code> generates <code>PostController.php</code></li> + <li><code>postTag</code> generates <code>PostTagController.php</code></li> + <li><code>admin/user</code> generates <code>admin/UserController.php</code>. + If the application has an <code>admin</code> module enabled, + it will generate <code>UserController</code> (and other CRUD code) + within the module instead. + </li> + </ul> + </div> + <?php echo $form->error($model,'controller'); ?> + </div> + + <div class="row sticky"> + <?php echo $form->labelEx($model,'baseControllerClass'); ?> + <?php echo $form->textField($model,'baseControllerClass',array('size'=>65)); ?> + <div class="tooltip"> + This is the class that the new CRUD controller class will extend from. + Please make sure the class exists and can be autoloaded. + </div> + <?php echo $form->error($model,'baseControllerClass'); ?> + </div> + +<?php $this->endWidget(); ?> diff --git a/framework/gii/generators/form/FormCode.php b/framework/gii/generators/form/FormCode.php new file mode 100644 index 0000000..e1def8d --- /dev/null +++ b/framework/gii/generators/form/FormCode.php @@ -0,0 +1,94 @@ +<?php + +class FormCode extends CCodeModel +{ + public $model; + public $viewPath='application.views'; + public $viewName; + public $scenario; + + private $_modelClass; + + public function rules() + { + return array_merge(parent::rules(), array( + array('model, viewName, scenario', 'filter', 'filter'=>'trim'), + array('model, viewName, viewPath', 'required'), + array('model, viewPath', 'match', 'pattern'=>'/^\w+[\.\w+]*$/', 'message'=>'{attribute} should only contain word characters and dots.'), + array('viewName', 'match', 'pattern'=>'/^\w+[\\/\w+]*$/', 'message'=>'{attribute} should only contain word characters and slashes.'), + array('model', 'validateModel'), + array('viewPath', 'validateViewPath'), + array('scenario', 'match', 'pattern'=>'/^\w+$/', 'message'=>'{attribute} should only contain word characters.'), + array('viewPath', 'sticky'), + )); + } + + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), array( + 'model'=>'Model Class', + 'viewName'=>'View Name', + 'viewPath'=>'View Path', + 'scenario'=>'Scenario', + )); + } + + public function requiredTemplates() + { + return array( + 'form.php', + 'action.php', + ); + } + + public function successMessage() + { + $output=<<<EOD +<p>The form has been generated successfully.</p> +<p>You may add the following code in an appropriate controller class to invoke the view:</p> +EOD; + $code="<?php\n".$this->render($this->templatePath.'/action.php'); + return $output.highlight_string($code,true); + } + + public function validateModel($attribute,$params) + { + if($this->hasErrors('model')) + return; + $class=@Yii::import($this->model,true); + if(!is_string($class) || !$this->classExists($class)) + $this->addError('model', "Class '{$this->model}' does not exist or has syntax error."); + else if(!is_subclass_of($class,'CModel')) + $this->addError('model', "'{$this->model}' must extend from CModel."); + else + $this->_modelClass=$class; + } + + public function validateViewPath($attribute,$params) + { + if($this->hasErrors('viewPath')) + return; + if(Yii::getPathOfAlias($this->viewPath)===false) + $this->addError('viewPath','View Path must be a valid path alias.'); + } + + public function prepare() + { + $templatePath=$this->templatePath; + $this->files[]=new CCodeFile( + Yii::getPathOfAlias($this->viewPath).'/'.$this->viewName.'.php', + $this->render($templatePath.'/form.php') + ); + } + + public function getModelClass() + { + return $this->_modelClass; + } + + public function getModelAttributes() + { + $model=new $this->_modelClass($this->scenario); + return $model->getSafeAttributeNames(); + } +}
\ No newline at end of file diff --git a/framework/gii/generators/form/FormGenerator.php b/framework/gii/generators/form/FormGenerator.php new file mode 100644 index 0000000..a9ec2df --- /dev/null +++ b/framework/gii/generators/form/FormGenerator.php @@ -0,0 +1,6 @@ +<?php + +class FormGenerator extends CCodeGenerator +{ + public $codeModel='gii.generators.form.FormCode'; +}
\ No newline at end of file diff --git a/framework/gii/generators/form/templates/default/action.php b/framework/gii/generators/form/templates/default/action.php new file mode 100644 index 0000000..4e88e5b --- /dev/null +++ b/framework/gii/generators/form/templates/default/action.php @@ -0,0 +1,33 @@ +<?php +/** + * This is the template for generating the action script for the form. + * - $this: the CrudCode object + */ +?> +<?php +$viewName=basename($this->viewName); +?> +public function action<?php echo ucfirst(trim($viewName,'_')); ?>() +{ + $model=new <?php echo $this->modelClass; ?><?php echo empty($this->scenario) ? '' : "('{$this->scenario}')"; ?>; + + // uncomment the following code to enable ajax-based validation + /* + if(isset($_POST['ajax']) && $_POST['ajax']==='<?php echo $this->class2id($this->modelClass); ?>-<?php echo $viewName; ?>-form') + { + echo CActiveForm::validate($model); + Yii::app()->end(); + } + */ + + if(isset($_POST['<?php echo $this->modelClass; ?>'])) + { + $model->attributes=$_POST['<?php echo $this->modelClass; ?>']; + if($model->validate()) + { + // form inputs are valid, do something here + return; + } + } + $this->render('<?php echo $viewName; ?>',array('model'=>$model)); +}
\ No newline at end of file diff --git a/framework/gii/generators/form/templates/default/form.php b/framework/gii/generators/form/templates/default/form.php new file mode 100644 index 0000000..1405618 --- /dev/null +++ b/framework/gii/generators/form/templates/default/form.php @@ -0,0 +1,34 @@ +<?php +/** + * This is the template for generating a form script file. + * The following variables are available in this template: + * - $this: the FormCode object + */ +?> +<div class="form"> + +<?php echo "<?php \$form=\$this->beginWidget('CActiveForm', array( + 'id'=>'".$this->class2id($this->modelClass).'-'.basename($this->viewName)."-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($this->getModelAttributes() 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 endforeach; ?> + + <div class="row buttons"> + <?php echo "<?php echo CHtml::submitButton('Submit'); ?>\n"; ?> + </div> + +<?php echo "<?php \$this->endWidget(); ?>\n"; ?> + +</div><!-- form -->
\ No newline at end of file diff --git a/framework/gii/generators/form/views/index.php b/framework/gii/generators/form/views/index.php new file mode 100644 index 0000000..8dcdf33 --- /dev/null +++ b/framework/gii/generators/form/views/index.php @@ -0,0 +1,49 @@ +<h1>Form Generator</h1> + +<p>This generator generates a view script file that displays a form to collect input for the specified model class.</p> + +<?php $form=$this->beginWidget('CCodeForm', array('model'=>$model)); ?> + + <div class="row"> + <?php echo $form->labelEx($model,'model'); ?> + <?php echo $form->textField($model,'model', array('size'=>65)); ?> + <div class="tooltip"> + Model class is case-sensitive. It can be either a class name (e.g. <code>Post</code>) + or the path alias of the class file (e.g. <code>application.models.LoginForm</code>). + Note that if the former, the class must be auto-loadable. + </div> + <?php echo $form->error($model,'model'); ?> + </div> + <div class="row"> + <?php echo $form->labelEx($model,'viewName'); ?> + <?php echo $form->textField($model,'viewName', array('size'=>65)); ?> + <div class="tooltip"> + This refers to the name of the view script to be generated, for example, + <code>site/contact</code>, <code>user/login</code>. The actual view script file will be generated + under the View Path specified below. + </div> + <?php echo $form->error($model,'viewName'); ?> + </div> + <div class="row sticky"> + <?php echo $form->labelEx($model,'viewPath'); ?> + <?php echo $form->textField($model,'viewPath', array('size'=>65)); ?> + <div class="tooltip"> + This refers to the directory that the new view script file should be generated under. + It should be specified in the form of a path alias, for example, <code>application.views</code>, + <code>mymodule.views</code>. + </div> + <?php echo $form->error($model,'viewPath'); ?> + </div> + <div class="row"> + <?php echo $form->labelEx($model,'scenario'); ?> + <?php echo $form->textField($model,'scenario', array('size'=>65)); ?> + <div class="tooltip"> + This refers to the scenario in which the model should be used to collect user input. + For example, a <code>User</code> model can be used in both <code>login</code> and <code>register</code> scenarios. + To create a form for the login purpose, the scenario should be specified as <code>login</code>. + Leave this empty if the model does not need to differentiate scenarios. + </div> + <?php echo $form->error($model,'scenario'); ?> + </div> + +<?php $this->endWidget(); ?> diff --git a/framework/gii/generators/model/ModelCode.php b/framework/gii/generators/model/ModelCode.php new file mode 100644 index 0000000..08b2491 --- /dev/null +++ b/framework/gii/generators/model/ModelCode.php @@ -0,0 +1,395 @@ +<?php + +class ModelCode extends CCodeModel +{ + public $tablePrefix; + public $tableName; + public $modelClass; + public $modelPath='application.models'; + public $baseClass='CActiveRecord'; + public $buildRelations=true; + + /** + * @var array list of candidate relation code. The array are indexed by AR class names and relation names. + * Each element represents the code of the one relation in one AR class. + */ + protected $relations; + + public function rules() + { + return array_merge(parent::rules(), array( + array('tablePrefix, baseClass, tableName, modelClass, modelPath', 'filter', 'filter'=>'trim'), + array('tableName, modelPath, baseClass', 'required'), + array('tablePrefix, tableName, modelPath', 'match', 'pattern'=>'/^(\w+[\w\.]*|\*?|\w+\.\*)$/', 'message'=>'{attribute} should only contain word characters, dots, and an optional ending asterisk.'), + array('tableName', 'validateTableName', 'skipOnError'=>true), + array('tablePrefix, modelClass, baseClass', 'match', 'pattern'=>'/^[a-zA-Z_]\w*$/', 'message'=>'{attribute} should only contain word characters.'), + array('modelPath', 'validateModelPath', 'skipOnError'=>true), + array('baseClass, modelClass', 'validateReservedWord', 'skipOnError'=>true), + array('baseClass', 'validateBaseClass', 'skipOnError'=>true), + array('tablePrefix, modelPath, baseClass, buildRelations', 'sticky'), + )); + } + + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), array( + 'tablePrefix'=>'Table Prefix', + 'tableName'=>'Table Name', + 'modelPath'=>'Model Path', + 'modelClass'=>'Model Class', + 'baseClass'=>'Base Class', + 'buildRelations'=>'Build Relations', + )); + } + + public function requiredTemplates() + { + return array( + 'model.php', + ); + } + + public function init() + { + if(Yii::app()->db===null) + throw new CHttpException(500,'An active "db" connection is required to run this generator.'); + $this->tablePrefix=Yii::app()->db->tablePrefix; + parent::init(); + } + + public function prepare() + { + if(($pos=strrpos($this->tableName,'.'))!==false) + { + $schema=substr($this->tableName,0,$pos); + $tableName=substr($this->tableName,$pos+1); + } + else + { + $schema=''; + $tableName=$this->tableName; + } + if($tableName[strlen($tableName)-1]==='*') + { + $tables=Yii::app()->db->schema->getTables($schema); + if($this->tablePrefix!='') + { + foreach($tables as $i=>$table) + { + if(strpos($table->name,$this->tablePrefix)!==0) + unset($tables[$i]); + } + } + } + else + $tables=array($this->getTableSchema($this->tableName)); + + $this->files=array(); + $templatePath=$this->templatePath; + $this->relations=$this->generateRelations(); + + foreach($tables as $table) + { + $tableName=$this->removePrefix($table->name); + $className=$this->generateClassName($table->name); + $params=array( + 'tableName'=>$schema==='' ? $tableName : $schema.'.'.$tableName, + 'modelClass'=>$className, + 'columns'=>$table->columns, + 'labels'=>$this->generateLabels($table), + 'rules'=>$this->generateRules($table), + 'relations'=>isset($this->relations[$className]) ? $this->relations[$className] : array(), + ); + $this->files[]=new CCodeFile( + Yii::getPathOfAlias($this->modelPath).'/'.$className.'.php', + $this->render($templatePath.'/model.php', $params) + ); + } + } + + public function validateTableName($attribute,$params) + { + $invalidTables=array(); + $invalidColumns=array(); + + if($this->tableName[strlen($this->tableName)-1]==='*') + { + if(($pos=strrpos($this->tableName,'.'))!==false) + $schema=substr($this->tableName,0,$pos); + else + $schema=''; + + $this->modelClass=''; + $tables=Yii::app()->db->schema->getTables($schema); + foreach($tables as $table) + { + if($this->tablePrefix=='' || strpos($table->name,$this->tablePrefix)===0) + { + if(in_array(strtolower($table->name),self::$keywords)) + $invalidTables[]=$table->name; + if(($invalidColumn=$this->checkColumns($table))!==null) + $invalidColumns[]=$invalidColumn; + } + } + } + else + { + if(($table=$this->getTableSchema($this->tableName))===null) + $this->addError('tableName',"Table '{$this->tableName}' does not exist."); + if($this->modelClass==='') + $this->addError('modelClass','Model Class cannot be blank.'); + + if(!$this->hasErrors($attribute) && ($invalidColumn=$this->checkColumns($table))!==null) + $invalidColumns[]=$invalidColumn; + } + + if($invalidTables!=array()) + $this->addError('tableName', 'Model class cannot take a reserved PHP keyword! Table name: '.implode(', ', $invalidTables)."."); + if($invalidColumns!=array()) + $this->addError('tableName', 'Column names that does not follow PHP variable naming convention: '.implode(', ', $invalidColumns)."."); + } + + /* + * Check that all database field names conform to PHP variable naming rules + * For example mysql allows field name like "2011aa", but PHP does not allow variable like "$model->2011aa" + * @param CDbTableSchema $table the table schema object + * @return string the invalid table column name. Null if no error. + */ + public function checkColumns($table) + { + foreach($table->columns as $column) + { + if(!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/',$column->name)) + return $table->name.'.'.$column->name; + } + } + + public function validateModelPath($attribute,$params) + { + if(Yii::getPathOfAlias($this->modelPath)===false) + $this->addError('modelPath','Model Path must be a valid path alias.'); + } + + public function validateBaseClass($attribute,$params) + { + $class=@Yii::import($this->baseClass,true); + if(!is_string($class) || !$this->classExists($class)) + $this->addError('baseClass', "Class '{$this->baseClass}' does not exist or has syntax error."); + else if($class!=='CActiveRecord' && !is_subclass_of($class,'CActiveRecord')) + $this->addError('baseClass', "'{$this->model}' must extend from CActiveRecord."); + } + + public function getTableSchema($tableName) + { + return Yii::app()->db->getSchema()->getTable($tableName); + } + + public function generateLabels($table) + { + $labels=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); + if($label==='Id') + $label='ID'; + $labels[$column->name]=$label; + } + return $labels; + } + + public function generateRules($table) + { + $rules=array(); + $required=array(); + $integers=array(); + $numerical=array(); + $length=array(); + $safe=array(); + foreach($table->columns as $column) + { + if($column->autoIncrement) + continue; + $r=!$column->allowNull && $column->defaultValue===null; + if($r) + $required[]=$column->name; + if($column->type==='integer') + $integers[]=$column->name; + else if($column->type==='double') + $numerical[]=$column->name; + else if($column->type==='string' && $column->size>0) + $length[$column->size][]=$column->name; + else if(!$column->isPrimaryKey && !$r) + $safe[]=$column->name; + } + if($required!==array()) + $rules[]="array('".implode(', ',$required)."', 'required')"; + if($integers!==array()) + $rules[]="array('".implode(', ',$integers)."', 'numerical', 'integerOnly'=>true)"; + if($numerical!==array()) + $rules[]="array('".implode(', ',$numerical)."', 'numerical')"; + if($length!==array()) + { + foreach($length as $len=>$cols) + $rules[]="array('".implode(', ',$cols)."', 'length', 'max'=>$len)"; + } + if($safe!==array()) + $rules[]="array('".implode(', ',$safe)."', 'safe')"; + + return $rules; + } + + public function getRelations($className) + { + return isset($this->relations[$className]) ? $this->relations[$className] : array(); + } + + protected function removePrefix($tableName,$addBrackets=true) + { + if($addBrackets && Yii::app()->db->tablePrefix=='') + return $tableName; + $prefix=$this->tablePrefix!='' ? $this->tablePrefix : Yii::app()->db->tablePrefix; + if($prefix!='') + { + if($addBrackets && Yii::app()->db->tablePrefix!='') + { + $prefix=Yii::app()->db->tablePrefix; + $lb='{{'; + $rb='}}'; + } + else + $lb=$rb=''; + if(($pos=strrpos($tableName,'.'))!==false) + { + $schema=substr($tableName,0,$pos); + $name=substr($tableName,$pos+1); + if(strpos($name,$prefix)===0) + return $schema.'.'.$lb.substr($name,strlen($prefix)).$rb; + } + else if(strpos($tableName,$prefix)===0) + return $lb.substr($tableName,strlen($prefix)).$rb; + } + return $tableName; + } + + protected function generateRelations() + { + if(!$this->buildRelations) + return array(); + $relations=array(); + foreach(Yii::app()->db->schema->getTables() as $table) + { + if($this->tablePrefix!='' && strpos($table->name,$this->tablePrefix)!==0) + continue; + $tableName=$table->name; + + if ($this->isRelationTable($table)) + { + $pks=$table->primaryKey; + $fks=$table->foreignKeys; + + $table0=$fks[$pks[0]][0]; + $table1=$fks[$pks[1]][0]; + $className0=$this->generateClassName($table0); + $className1=$this->generateClassName($table1); + + $unprefixedTableName=$this->removePrefix($tableName); + + $relationName=$this->generateRelationName($table0, $table1, true); + $relations[$className0][$relationName]="array(self::MANY_MANY, '$className1', '$unprefixedTableName($pks[0], $pks[1])')"; + + $relationName=$this->generateRelationName($table1, $table0, true); + $relations[$className1][$relationName]="array(self::MANY_MANY, '$className0', '$unprefixedTableName($pks[1], $pks[0])')"; + } + else + { + $className=$this->generateClassName($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->generateClassName($refTable); + + // Add relation for this table + $relationName=$this->generateRelationName($tableName, $fkName, false); + $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,false), $relationType==='HAS_MANY'); + $i=1; + $rawName=$relationName; + while(isset($relations[$refClassName][$relationName])) + $relationName=$rawName.($i++); + $relations[$refClassName][$relationName]="array(self::$relationType, '$className', '$fkName')"; + } + } + } + return $relations; + } + + /** + * Checks if the given table is a "many to many" pivot table. + * Their PK has 2 fields, and both of those fields are also FK to other separate tables. + * @param CDbTableSchema table to inspect + * @return boolean true if table matches description of helpter table. + */ + protected function isRelationTable($table) + { + $pk=$table->primaryKey; + return (count($pk) === 2 // we want 2 columns + && isset($table->foreignKeys[$pk[0]]) // pk column 1 is also a foreign key + && isset($table->foreignKeys[$pk[1]]) // pk column 2 is also a foriegn key + && $table->foreignKeys[$pk[0]][0] !== $table->foreignKeys[$pk[1]][0]); // and the foreign keys point different tables + } + + protected function generateClassName($tableName) + { + if($this->tableName===$tableName || ($pos=strrpos($this->tableName,'.'))!==false && substr($this->tableName,$pos+1)===$tableName) + return $this->modelClass; + + $tableName=$this->removePrefix($tableName,false); + $className=''; + foreach(explode('_',$tableName) as $name) + { + if($name!=='') + $className.=ucfirst($name); + } + return $className; + } + + /** + * Generate a name for use as a relation name (inside relations() function in a model). + * @param string the name of the table to hold the relation + * @param string the foreign key name + * @param boolean whether the relation would contain multiple objects + * @return string the 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); + + if($multiple) + $relationName=$this->pluralize($relationName); + + $names=preg_split('/_+/',$relationName,-1,PREG_SPLIT_NO_EMPTY); + if(empty($names)) return $relationName; // unlikely + for($name=$names[0], $i=1;$i<count($names);++$i) + $name.=ucfirst($names[$i]); + + $rawName=$name; + $table=Yii::app()->db->schema->getTable($tableName); + $i=0; + while(isset($table->columns[$name])) + $name=$rawName.($i++); + + return $name; + } +}
\ No newline at end of file diff --git a/framework/gii/generators/model/ModelGenerator.php b/framework/gii/generators/model/ModelGenerator.php new file mode 100644 index 0000000..fd2574e --- /dev/null +++ b/framework/gii/generators/model/ModelGenerator.php @@ -0,0 +1,6 @@ +<?php + +class ModelGenerator extends CCodeGenerator +{ + public $codeModel='gii.generators.model.ModelCode'; +}
\ No newline at end of file diff --git a/framework/gii/generators/model/templates/default/model.php b/framework/gii/generators/model/templates/default/model.php new file mode 100644 index 0000000..46e185e --- /dev/null +++ b/framework/gii/generators/model/templates/default/model.php @@ -0,0 +1,145 @@ +<?php +/** + * This is the template for generating the model class of a specified table. + * - $this: the ModelCode object + * - $tableName: the table name for this class (prefix is already removed if necessary) + * - $modelClass: the model class name + * - $columns: list of table columns (name=>CDbColumnSchema) + * - $labels: list of attribute labels (name=>label) + * - $rules: list of validation rules + * - $relations: list of relations (name=>relation declaration) + */ +?> +<?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; ?> +<?php if(!empty($relations)): ?> + * + * The followings are the available model relations: +<?php foreach($relations as $name=>$relation): ?> + * @property <?php + if (preg_match("~^array\(self::([^,]+), '([^']+)', '([^']+)'\)$~", $relation, $matches)) + { + $relationType = $matches[1]; + $relationModel = $matches[2]; + + switch($relationType){ + case 'HAS_ONE': + echo $relationModel.' $'.$name."\n"; + break; + case 'BELONGS_TO': + echo $relationModel.' $'.$name."\n"; + break; + case 'HAS_MANY': + echo $relationModel.'[] $'.$name."\n"; + break; + case 'MANY_MANY': + echo $relationModel.'[] $'.$name."\n"; + break; + default: + echo 'mixed $'.$name."\n"; + } + } + ?> +<?php endforeach; ?> +<?php endif; ?> + */ +class <?php echo $modelClass; ?> extends <?php echo $this->baseClass."\n"; ?> +{ + /** + * Returns the static model of the specified AR class. + * @param string $className active record class name. + * @return <?php echo $modelClass; ?> the static model class + */ + public static function model($className=__CLASS__) + { + return parent::model($className); + } + + /** + * @return string the associated database table name + */ + public function tableName() + { + return '<?php echo $tableName; ?>'; + } + + /** + * @return array validation rules for model attributes. + */ + public function rules() + { + // NOTE: you should only define rules for those attributes that + // will receive user inputs. + return array( +<?php foreach($rules as $rule): ?> + <?php echo $rule.",\n"; ?> +<?php endforeach; ?> + // The following rule is used by search(). + // Please remove those attributes that should not be searched. + array('<?php echo implode(', ', array_keys($columns)); ?>', 'safe', 'on'=>'search'), + ); + } + + /** + * @return array relational rules. + */ + public function relations() + { + // NOTE: you may need to adjust the relation name and the related + // class name for the relations automatically generated below. + return array( +<?php foreach($relations as $name=>$relation): ?> + <?php echo "'$name' => $relation,\n"; ?> +<?php endforeach; ?> + ); + } + + /** + * @return array customized attribute labels (name=>label) + */ + public function attributeLabels() + { + return array( +<?php foreach($labels as $name=>$label): ?> + <?php echo "'$name' => '$label',\n"; ?> +<?php endforeach; ?> + ); + } + + /** + * Retrieves a list of models based on the current search/filter conditions. + * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions. + */ + public function search() + { + // Warning: Please modify the following code to remove attributes that + // should not be searched. + + $criteria=new CDbCriteria; + +<?php +foreach($columns as $name=>$column) +{ + if($column->type==='string') + { + echo "\t\t\$criteria->compare('$name',\$this->$name,true);\n"; + } + else + { + echo "\t\t\$criteria->compare('$name',\$this->$name);\n"; + } +} +?> + + return new CActiveDataProvider($this, array( + 'criteria'=>$criteria, + )); + } +}
\ No newline at end of file diff --git a/framework/gii/generators/model/views/index.php b/framework/gii/generators/model/views/index.php new file mode 100644 index 0000000..28040d3 --- /dev/null +++ b/framework/gii/generators/model/views/index.php @@ -0,0 +1,115 @@ +<?php +$class=get_class($model); +Yii::app()->clientScript->registerScript('gii.model'," +$('#{$class}_modelClass').change(function(){ + $(this).data('changed',$(this).val()!=''); +}); +$('#{$class}_tableName').bind('keyup change', function(){ + var model=$('#{$class}_modelClass'); + var tableName=$(this).val(); + if(tableName.substring(tableName.length-1)!='*') { + $('.form .row.model-class').show(); + } + else { + $('#{$class}_modelClass').val(''); + $('.form .row.model-class').hide(); + } + if(!model.data('changed')) { + var i=tableName.lastIndexOf('.'); + if(i>=0) + tableName=tableName.substring(i+1); + var tablePrefix=$('#{$class}_tablePrefix').val(); + if(tablePrefix!='' && tableName.indexOf(tablePrefix)==0) + tableName=tableName.substring(tablePrefix.length); + var modelClass=''; + $.each(tableName.split('_'), function() { + if(this.length>0) + modelClass+=this.substring(0,1).toUpperCase()+this.substring(1); + }); + model.val(modelClass); + } +}); +$('.form .row.model-class').toggle($('#{$class}_tableName').val().substring($('#{$class}_tableName').val().length-1)!='*'); +"); +?> +<h1>Model Generator</h1> + +<p>This generator generates a model class for the specified database table.</p> + +<?php $form=$this->beginWidget('CCodeForm', array('model'=>$model)); ?> + + <div class="row sticky"> + <?php echo $form->labelEx($model,'tablePrefix'); ?> + <?php echo $form->textField($model,'tablePrefix', array('size'=>65)); ?> + <div class="tooltip"> + This refers to the prefix name that is shared by all database tables. + Setting this property mainly affects how model classes are named based on + the table names. For example, a table prefix <code>tbl_</code> with a table name <code>tbl_post</code> + will generate a model class named <code>Post</code>. + <br/> + Leave this field empty if your database tables do not use common prefix. + </div> + <?php echo $form->error($model,'tablePrefix'); ?> + </div> + <div class="row"> + <?php echo $form->labelEx($model,'tableName'); ?> + <?php $this->widget('zii.widgets.jui.CJuiAutoComplete',array( + 'model'=>$model, + 'attribute'=>'tableName', + 'name'=>'tableName', + 'source'=>array_keys(Yii::app()->db->schema->getTables()), + 'options'=>array( + 'minLength'=>'0', + ), + 'htmlOptions'=>array( + 'id'=>'ModelCode_tableName', + 'size'=>'65' + ), + )); ?> + <div class="tooltip"> + This refers to the table name that a new model class should be generated for + (e.g. <code>tbl_user</code>). It can contain schema name, if needed (e.g. <code>public.tbl_post</code>). + You may also enter <code>*</code> (or <code>schemaName.*</code> for a particular DB schema) + to generate a model class for EVERY table. + </div> + <?php echo $form->error($model,'tableName'); ?> + </div> + <div class="row model-class"> + <?php echo $form->label($model,'modelClass',array('required'=>true)); ?> + <?php echo $form->textField($model,'modelClass', array('size'=>65)); ?> + <div class="tooltip"> + This is the name of the model class to be generated (e.g. <code>Post</code>, <code>Comment</code>). + It is case-sensitive. + </div> + <?php echo $form->error($model,'modelClass'); ?> + </div> + <div class="row sticky"> + <?php echo $form->labelEx($model,'baseClass'); ?> + <?php echo $form->textField($model,'baseClass',array('size'=>65)); ?> + <div class="tooltip"> + This is the class that the new model class will extend from. + Please make sure the class exists and can be autoloaded. + </div> + <?php echo $form->error($model,'baseClass'); ?> + </div> + <div class="row sticky"> + <?php echo $form->labelEx($model,'modelPath'); ?> + <?php echo $form->textField($model,'modelPath', array('size'=>65)); ?> + <div class="tooltip"> + This refers to the directory that the new model class file should be generated under. + It should be specified in the form of a path alias, for example, <code>application.models</code>. + </div> + <?php echo $form->error($model,'modelPath'); ?> + </div> + <div class="row"> + <?php echo $form->labelEx($model,'buildRelations'); ?> + <?php echo $form->checkBox($model,'buildRelations'); ?> + <div class="tooltip"> + Whether relations should be generated for the model class. + In order to generate relations, full scan of the whole database is needed. + You should disable this option if your database contains too many tables. + </div> + <?php echo $form->error($model,'buildRelations'); ?> + </div> + +<?php $this->endWidget(); ?> diff --git a/framework/gii/generators/module/ModuleCode.php b/framework/gii/generators/module/ModuleCode.php new file mode 100644 index 0000000..4fb90f0 --- /dev/null +++ b/framework/gii/generators/module/ModuleCode.php @@ -0,0 +1,91 @@ +<?php + +class ModuleCode extends CCodeModel +{ + public $moduleID; + + public function rules() + { + return array_merge(parent::rules(), array( + array('moduleID', 'filter', 'filter'=>'trim'), + array('moduleID', 'required'), + array('moduleID', 'match', 'pattern'=>'/^\w+$/', 'message'=>'{attribute} should only contain word characters.'), + )); + } + + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), array( + 'moduleID'=>'Module ID', + )); + } + + public function successMessage() + { + if(Yii::app()->hasModule($this->moduleID)) + return 'The module has been generated successfully. You may '.CHtml::link('try it now', Yii::app()->createUrl($this->moduleID), array('target'=>'_blank')).'.'; + + $output=<<<EOD +<p>The module has been generated successfully.</p> +<p>To access the module, you need to modify the application configuration as follows:</p> +EOD; + $code=<<<EOD +<?php +return array( + 'modules'=>array( + '{$this->moduleID}', + ), + ...... +); +EOD; + + return $output.highlight_string($code,true); + } + + public function prepare() + { + $this->files=array(); + $templatePath=$this->templatePath; + $modulePath=$this->modulePath; + $moduleTemplateFile=$templatePath.DIRECTORY_SEPARATOR.'module.php'; + + $this->files[]=new CCodeFile( + $modulePath.'/'.$this->moduleClass.'.php', + $this->render($moduleTemplateFile) + ); + + $files=CFileHelper::findFiles($templatePath,array( + 'exclude'=>array('.svn'), + )); + + foreach($files as $file) + { + if($file!==$moduleTemplateFile) + { + if(CFileHelper::getExtension($file)==='php') + $content=$this->render($file); + else if(basename($file)==='.yii') // an empty directory + { + $file=dirname($file); + $content=null; + } + else + $content=file_get_contents($file); + $this->files[]=new CCodeFile( + $modulePath.substr($file,strlen($templatePath)), + $content + ); + } + } + } + + public function getModuleClass() + { + return ucfirst($this->moduleID).'Module'; + } + + public function getModulePath() + { + return Yii::app()->modulePath.DIRECTORY_SEPARATOR.$this->moduleID; + } +}
\ No newline at end of file diff --git a/framework/gii/generators/module/ModuleGenerator.php b/framework/gii/generators/module/ModuleGenerator.php new file mode 100644 index 0000000..c1ba1b6 --- /dev/null +++ b/framework/gii/generators/module/ModuleGenerator.php @@ -0,0 +1,6 @@ +<?php + +class ModuleGenerator extends CCodeGenerator +{ + public $codeModel='gii.generators.module.ModuleCode'; +}
\ No newline at end of file diff --git a/framework/gii/generators/module/templates/default/components/.yii b/framework/gii/generators/module/templates/default/components/.yii new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/framework/gii/generators/module/templates/default/components/.yii diff --git a/framework/gii/generators/module/templates/default/controllers/DefaultController.php b/framework/gii/generators/module/templates/default/controllers/DefaultController.php new file mode 100644 index 0000000..1936819 --- /dev/null +++ b/framework/gii/generators/module/templates/default/controllers/DefaultController.php @@ -0,0 +1,9 @@ +<?php echo "<?php\n"; ?> + +class DefaultController extends Controller +{ + public function actionIndex() + { + $this->render('index'); + } +}
\ No newline at end of file diff --git a/framework/gii/generators/module/templates/default/messages/.yii b/framework/gii/generators/module/templates/default/messages/.yii new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/framework/gii/generators/module/templates/default/messages/.yii diff --git a/framework/gii/generators/module/templates/default/models/.yii b/framework/gii/generators/module/templates/default/models/.yii new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/framework/gii/generators/module/templates/default/models/.yii diff --git a/framework/gii/generators/module/templates/default/module.php b/framework/gii/generators/module/templates/default/module.php new file mode 100644 index 0000000..b0d98d0 --- /dev/null +++ b/framework/gii/generators/module/templates/default/module.php @@ -0,0 +1,28 @@ +<?php echo "<?php\n"; ?> + +class <?php echo $this->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 $this->moduleID; ?>.models.*', + '<?php echo $this->moduleID; ?>.components.*', + )); + } + + public function beforeControllerAction($controller, $action) + { + if(parent::beforeControllerAction($controller, $action)) + { + // this method is called before any module controller action is performed + // you may place customized code here + return true; + } + else + return false; + } +} diff --git a/framework/gii/generators/module/templates/default/views/default/index.php b/framework/gii/generators/module/templates/default/views/default/index.php new file mode 100644 index 0000000..3ee8a6e --- /dev/null +++ b/framework/gii/generators/module/templates/default/views/default/index.php @@ -0,0 +1,15 @@ +<?php echo "<?php\n"; ?> +$this->breadcrumbs=array( + $this->module->id, +); +?> +<h1><?php echo "<?php"; ?> echo $this->uniqueId . '/' . $this->action->id; ?></h1> + +<p> +This is the view content for action "<?php echo "<?php"; ?> echo $this->action->id; ?>". +The action belongs to the controller "<?php echo "<?php"; ?> echo get_class($this); ?>" +in the "<?php echo "<?php"; ?> echo $this->module->id; ?>" module. +</p> +<p> +You may customize this page by editing <tt><?php echo "<?php"; ?> echo __FILE__; ?></tt> +</p>
\ No newline at end of file diff --git a/framework/gii/generators/module/templates/default/views/layouts/.yii b/framework/gii/generators/module/templates/default/views/layouts/.yii new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/framework/gii/generators/module/templates/default/views/layouts/.yii diff --git a/framework/gii/generators/module/views/index.php b/framework/gii/generators/module/views/index.php new file mode 100644 index 0000000..711e7e7 --- /dev/null +++ b/framework/gii/generators/module/views/index.php @@ -0,0 +1,19 @@ +<h1>Module Generator</h1> + +<p>This generator helps you to generate the skeleton code needed by a Yii module.</p> + +<?php $form=$this->beginWidget('CCodeForm', array('model'=>$model)); ?> + + <div class="row"> + <?php echo $form->labelEx($model,'moduleID'); ?> + <?php echo $form->textField($model,'moduleID',array('size'=>65)); ?> + <div class="tooltip"> + Module ID is case-sensitive. It should only contain word characters. + The generated module class will be named after the module ID. + For example, a module ID <code>forum</code> will generate the module class + <code>ForumModule</code>. + </div> + <?php echo $form->error($model,'moduleID'); ?> + </div> + +<?php $this->endWidget(); ?> diff --git a/framework/gii/models/LoginForm.php b/framework/gii/models/LoginForm.php new file mode 100644 index 0000000..f4b838c --- /dev/null +++ b/framework/gii/models/LoginForm.php @@ -0,0 +1,49 @@ +<?php + +Yii::import('gii.components.UserIdentity'); + +class LoginForm extends CFormModel +{ + public $password; + + private $_identity; + + public function rules() + { + return array( + array('password', 'required'), + array('password', 'authenticate'), + ); + } + + /** + * Authenticates the password. + * This is the 'authenticate' validator as declared in rules(). + */ + public function authenticate($attribute,$params) + { + $this->_identity=new UserIdentity('yiier',$this->password); + if(!$this->_identity->authenticate()) + $this->addError('password','Incorrect password.'); + } + + /** + * Logs in the user using the given password in the model. + * @return boolean whether login is successful + */ + public function login() + { + if($this->_identity===null) + { + $this->_identity=new UserIdentity('yiier',$this->password); + $this->_identity->authenticate(); + } + if($this->_identity->errorCode===UserIdentity::ERROR_NONE) + { + Yii::app()->user->login($this->_identity); + return true; + } + else + return false; + } +} diff --git a/framework/gii/views/common/code.php b/framework/gii/views/common/code.php new file mode 100644 index 0000000..9d996c9 --- /dev/null +++ b/framework/gii/views/common/code.php @@ -0,0 +1,16 @@ +<?php +if($file->type==='php') +{ + echo '<div class="content">'; + highlight_string($file->content); + echo '</div>'; +} +else if(in_array($file->type,array('txt','js','css'))) +{ + echo '<div class="content">'; + echo nl2br($file->content); + echo '</div>'; +} +else + echo '<div class="error">Preview is not available for this file type.</div>'; +?>
\ No newline at end of file diff --git a/framework/gii/views/common/diff.php b/framework/gii/views/common/diff.php new file mode 100644 index 0000000..ed3f89c --- /dev/null +++ b/framework/gii/views/common/diff.php @@ -0,0 +1,9 @@ +<?php if($diff===false): ?> + <div class="error">Diff is not supported for this file type.</div> +<?php elseif(empty($diff)): ?> + <div class="error">No changes.</div> +<?php else: ?> + <div class="content"> + <pre class="diff"><?php echo $diff; ?></pre> + </div> +<?php endif; ?> diff --git a/framework/gii/views/common/generator.php b/framework/gii/views/common/generator.php new file mode 100644 index 0000000..8da17d5 --- /dev/null +++ b/framework/gii/views/common/generator.php @@ -0,0 +1,76 @@ +<div class="row template sticky"> + <?php echo $this->labelEx($model,'template'); ?> + <?php echo $this->dropDownList($model,'template',$templates); ?> + <div class="tooltip"> + Please select which set of the templates should be used to generated the code. + </div> + <?php echo $this->error($model,'template'); ?> +</div> + +<div class="buttons"> + <?php echo CHtml::submitButton('Preview',array('name'=>'preview')); ?> + + <?php if($model->status===CCodeModel::STATUS_PREVIEW && !$model->hasErrors()): ?> + <?php echo CHtml::submitButton('Generate',array('name'=>'generate')); ?> + <?php endif; ?> +</div> + +<?php if(!$model->hasErrors()): ?> + <div class="feedback"> + <?php if($model->status===CCodeModel::STATUS_SUCCESS): ?> + <div class="success"> + <?php echo $model->successMessage(); ?> + </div> + <?php elseif($model->status===CCodeModel::STATUS_ERROR): ?> + <div class="error"> + <?php echo $model->errorMessage(); ?> + </div> + <?php endif; ?> + + <?php if(isset($_POST['generate'])): ?> + <pre class="results"><?php echo $model->renderResults(); ?></pre> + <?php elseif(isset($_POST['preview'])): ?> + <?php echo CHtml::hiddenField("answers"); ?> + <table class="preview"> + <tr> + <th class="file">Code File</th> + <th class="confirm"> + <label for="check-all">Generate</label> + <?php + $count=0; + foreach($model->files as $file) + { + if($file->operation!==CCodeFile::OP_SKIP) + $count++; + } + if($count>1) + echo '<input type="checkbox" name="checkAll" id="check-all" />'; + ?> + </th> + </tr> + <?php foreach($model->files as $i=>$file): ?> + <tr class="<?php echo $file->operation; ?>"> + <td class="file"> + <?php echo CHtml::link(CHtml::encode($file->relativePath), array('code','id'=>$i), array('class'=>'view-code','rel'=>$file->path)); ?> + <?php if($file->operation===CCodeFile::OP_OVERWRITE): ?> + (<?php echo CHtml::link('diff', array('diff','id'=>$i), array('class'=>'view-code','rel'=>$file->path)); ?>) + <?php endif; ?> + </td> + <td class="confirm"> + <?php + if($file->operation===CCodeFile::OP_SKIP) + echo 'unchanged'; + else + { + $key=md5($file->path); + echo CHtml::label($file->operation, "answers_{$key}") + . ' ' . CHtml::checkBox("answers[$key]", $model->confirmed($file)); + } + ?> + </td> + </tr> + <?php endforeach; ?> + </table> + <?php endif; ?> + </div> +<?php endif; ?> diff --git a/framework/gii/views/default/error.php b/framework/gii/views/default/error.php new file mode 100644 index 0000000..a6a25a3 --- /dev/null +++ b/framework/gii/views/default/error.php @@ -0,0 +1,5 @@ +<h1>Error <?php echo $code; ?></h1> + +<div class="error"> +<?php echo CHtml::encode($message); ?> +</div>
\ No newline at end of file diff --git a/framework/gii/views/default/index.php b/framework/gii/views/default/index.php new file mode 100644 index 0000000..f3470c9 --- /dev/null +++ b/framework/gii/views/default/index.php @@ -0,0 +1,11 @@ +<h1>Welcome to Yii Code Generator!</h1> + +<p> + You may use the following generators to quickly build up your Yii application: +</p> +<ul> + <?php foreach($this->module->controllerMap as $name=>$config): ?> + <li><?php echo CHtml::link(ucwords(CHtml::encode($name).' generator'),array('/gii/'.$name));?></li> + <?php endforeach; ?> +</ul> + diff --git a/framework/gii/views/default/login.php b/framework/gii/views/default/login.php new file mode 100644 index 0000000..6d462a0 --- /dev/null +++ b/framework/gii/views/default/login.php @@ -0,0 +1,11 @@ +<div class="form login"> +<?php $form=$this->beginWidget('CActiveForm'); ?> + <p>Please enter your password</p> + + <?php echo $form->passwordField($model,'password'); ?> + <?php echo $form->error($model,'password'); ?> + + <?php echo CHtml::submitButton('Enter'); ?> + +<?php $this->endWidget(); ?> +</div><!-- form --> diff --git a/framework/gii/views/layouts/column1.php b/framework/gii/views/layouts/column1.php new file mode 100644 index 0000000..2d35771 --- /dev/null +++ b/framework/gii/views/layouts/column1.php @@ -0,0 +1,7 @@ +<?php $this->beginContent('gii.views.layouts.main'); ?> +<div class="container"> + <div id="content"> + <?php echo $content; ?> + </div><!-- content --> +</div> +<?php $this->endContent(); ?>
\ No newline at end of file diff --git a/framework/gii/views/layouts/generator.php b/framework/gii/views/layouts/generator.php new file mode 100644 index 0000000..3abbcf2 --- /dev/null +++ b/framework/gii/views/layouts/generator.php @@ -0,0 +1,25 @@ +<?php $this->beginContent('gii.views.layouts.main'); ?> +<div class="container"> + <div class="span-4"> + <div id="sidebar"> + <?php $this->beginWidget('zii.widgets.CPortlet', array( + 'title'=>'Generators', + )); ?> + <ul> + <?php foreach($this->module->controllerMap as $name=>$config): ?> + <li><?php echo CHtml::link(ucwords(CHtml::encode($name).' generator'),array('/gii/'.$name));?></li> + <?php endforeach; ?> + </ul> + <?php $this->endWidget(); ?> + </div><!-- sidebar --> + </div> + <div class="span-16"> + <div id="content"> + <?php echo $content; ?> + </div><!-- content --> + </div> + <div class="span-4 last"> + + </div> +</div> +<?php $this->endContent(); ?>
\ No newline at end of file diff --git a/framework/gii/views/layouts/main.php b/framework/gii/views/layouts/main.php new file mode 100644 index 0000000..7886025 --- /dev/null +++ b/framework/gii/views/layouts/main.php @@ -0,0 +1,57 @@ +<?php +$cs=Yii::app()->clientScript; +$cs->coreScriptPosition=CClientScript::POS_HEAD; +$cs->scriptMap=array(); +$baseUrl=$this->module->assetsUrl; +$cs->registerCoreScript('jquery'); +$cs->registerScriptFile($baseUrl.'/js/jquery.tooltip-1.2.6.min.js'); +$cs->registerScriptFile($baseUrl.'/js/fancybox/jquery.fancybox-1.3.1.pack.js'); +$cs->registerCssFile($baseUrl.'/js/fancybox/jquery.fancybox-1.3.1.css'); +?> +<!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 $this->module->assetsUrl; ?>/css/screen.css" media="screen, projection" /> + <link rel="stylesheet" type="text/css" href="<?php echo $this->module->assetsUrl; ?>/css/print.css" media="print" /> + <!--[if lt IE 8]> + <link rel="stylesheet" type="text/css" href="<?php echo $this->module->assetsUrl; ?>/css/ie.css" media="screen, projection" /> + <![endif]--> + + <link rel="stylesheet" type="text/css" href="<?php echo $this->module->assetsUrl; ?>/css/main.css" /> + + <title><?php echo CHtml::encode($this->pageTitle); ?></title> + + <script type="text/javascript" src="<?php echo $this->module->assetsUrl; ?>/js/main.js"></script> + +</head> + +<body> + +<div class="container" id="page"> + <div id="header"> + <div class="top-menus"> + <?php echo CHtml::link('help','http://www.yiiframework.com/doc/guide/topics.gii'); ?> | + <?php echo CHtml::link('webapp',Yii::app()->homeUrl); ?> | + <a href="http://www.yiiframework.com">yii</a> + <?php if(!Yii::app()->user->isGuest): ?> + | <?php echo CHtml::link('logout',array('/gii/default/logout')); ?> + <?php endif; ?> + </div> + <div id="logo"><?php echo CHtml::link(CHtml::image($this->module->assetsUrl.'/images/logo.png'),array('/gii')); ?></div> + </div><!-- header --> + + <?php echo $content; ?> + +</div><!-- page --> + +<div id="footer"> + <?php echo Yii::powered(); ?> + <br/>A product of <a href="http://www.yiisoft.com">Yii Software LLC</a>. +</div><!-- footer --> + +</body> +</html>
\ No newline at end of file |
