1
0

Added new (clean) yii boilerplate

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

134
framework/gii/CCodeFile.php Normal file
View File

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

View File

@@ -0,0 +1,59 @@
<?php
/**
* CCodeForm class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CCodeForm represents the form for collecting code generation parameters.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @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>";
}
}

View File

@@ -0,0 +1,167 @@
<?php
/**
* CCodeGenerator class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* 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>
* @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.
* @throws CHttpException if unable to find code generated.
*/
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.
* @throws CHttpException if unable to find code generated.
*/
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;
elseif($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;
}
}

View File

@@ -0,0 +1,484 @@
<?php
/**
* CCodeModel class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* 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>
* @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.
* @throws CHttpException 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];
elseif(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
* @return bool whether the confirmation is found in {@link answers} with appropriate {@link operation}
*/
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
* @throws CException is template file does not exist
* @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";
elseif($file->operation===CCodeFile::OP_NEW && $this->confirmed($file))
$output.=' generated '.$file->relativePath."\n";
elseif($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(
'/(m)ove$/i' => '\1oves',
'/(f)oot$/i' => '\1eet',
'/(c)hild$/i' => '\1hildren',
'/(h)uman$/i' => '\1umans',
'/(m)an$/i' => '\1en',
'/(s)taff$/i' => '\1taff',
'/(t)ooth$/i' => '\1eeth',
'/(p)erson$/i' => '\1eople',
'/([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.');
}
}

244
framework/gii/GiiModule.php Normal file
View File

@@ -0,0 +1,244 @@
<?php
/**
* GiiModule class file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
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>
* @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::setPathOfAlias('gii',dirname(__FILE__));
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'),
),
'widgetFactory' => array(
'class'=>'CWidgetFactory',
'widgets' => array()
)
), 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.
* @throws CHttpException if access denied
* @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;
}
}

View File

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

View File

@@ -0,0 +1,528 @@
body
{
margin: 0;
padding: 0;
color: #555;
font: normal 10pt Arial,Helvetica,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;
}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

View File

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

View File

@@ -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();
});
$(document).on('click', '.form.gii .row.sticky .value', 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 *').tooltip2({
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;
});
$(document).on('click', '#fancybox-inner .close-code', function(){
$.fancybox.close();
return false;
});
});

View File

@@ -0,0 +1,364 @@
/**
* CHANGES MADE BY YII DEVELOPERS, READ CAREFULLY BEFORE UPGRADING THIS FILE:
* 1. This commit has been used:
* https://github.com/jquerytools/jquerytools/commit/4f3f3f14e83b0ff276a795e9f45400930904adff#src/tooltip/tooltip.js
* 2. Original `$.fn.tooltip` has been changed to `$.fn.tooltip2` to prevent conflict between jQuery UI Tooltip and
* jQuery Tools Tooltip.
*
* @license
* jQuery Tools @VERSION Tooltip - UI essentials
*
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
*
* http://flowplayer.org/tools/tooltip/
*
* Since: November 2008
* Date: @DATE
*/
(function($) {
// static constructs
$.tools = $.tools || {version: '@VERSION'};
$.tools.tooltip = {
conf: {
// default effect variables
effect: 'toggle',
fadeOutSpeed: "fast",
predelay: 0,
delay: 30,
opacity: 1,
tip: 0,
fadeIE: false, // enables fade effect in IE
// 'top', 'bottom', 'right', 'left', 'center'
position: ['top', 'center'],
offset: [0, 0],
relative: false,
cancelDefault: true,
// type to event mapping
events: {
def: "mouseenter,mouseleave",
input: "focus,blur",
widget: "focus mouseenter,blur mouseleave",
tooltip: "mouseenter,mouseleave"
},
// 1.2
layout: '<div/>',
tipClass: 'tooltip'
},
addEffect: function(name, loadFn, hideFn) {
effects[name] = [loadFn, hideFn];
}
};
var effects = {
toggle: [
function(done) {
var conf = this.getConf(), tip = this.getTip(), o = conf.opacity;
if (o < 1) { tip.css({opacity: o}); }
tip.show();
done.call();
},
function(done) {
this.getTip().hide();
done.call();
}
],
fade: [
function(done) {
var conf = this.getConf();
if (!$.browser.msie || conf.fadeIE) {
this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done);
}
else {
this.getTip().show();
done();
}
},
function(done) {
var conf = this.getConf();
if (!$.browser.msie || conf.fadeIE) {
this.getTip().fadeOut(conf.fadeOutSpeed, done);
}
else {
this.getTip().hide();
done();
}
}
]
};
/* calculate tip position relative to the trigger */
function getPosition(trigger, tip, conf) {
// get origin top/left position
var top = conf.relative ? trigger.position().top : trigger.offset().top,
left = conf.relative ? trigger.position().left : trigger.offset().left,
pos = conf.position[0];
top -= tip.outerHeight() - conf.offset[0];
left += trigger.outerWidth() + conf.offset[1];
// iPad position fix
if (/iPad/i.test(navigator.userAgent)) {
top -= $(window).scrollTop();
}
// adjust Y
var height = tip.outerHeight() + trigger.outerHeight();
if (pos == 'center') { top += height / 2; }
if (pos == 'bottom') { top += height; }
// adjust X
pos = conf.position[1];
var width = tip.outerWidth() + trigger.outerWidth();
if (pos == 'center') { left -= width / 2; }
if (pos == 'left') { left -= width; }
return {top: top, left: left};
}
function Tooltip(trigger, conf) {
var self = this,
fire = trigger.add(self),
tip,
timer = 0,
pretimer = 0,
title = trigger.attr("title"),
tipAttr = trigger.attr("data-tooltip"),
effect = effects[conf.effect],
shown,
// get show/hide configuration
isInput = trigger.is(":input"),
isWidget = isInput && trigger.is(":checkbox, :radio, select, :button, :submit"),
type = trigger.attr("type"),
evt = conf.events[type] || conf.events[isInput ? (isWidget ? 'widget' : 'input') : 'def'];
// check that configuration is sane
if (!effect) { throw "Nonexistent effect \"" + conf.effect + "\""; }
evt = evt.split(/,\s*/);
if (evt.length != 2) { throw "Tooltip: bad events configuration for " + type; }
// trigger --> show
trigger.on(evt[0], function(e) {
clearTimeout(timer);
if (conf.predelay) {
pretimer = setTimeout(function() { self.show(e); }, conf.predelay);
} else {
self.show(e);
}
// trigger --> hide
}).on(evt[1], function(e) {
clearTimeout(pretimer);
if (conf.delay) {
timer = setTimeout(function() { self.hide(e); }, conf.delay);
} else {
self.hide(e);
}
});
// remove default title
if (title && conf.cancelDefault) {
trigger.removeAttr("title");
trigger.data("title", title);
}
$.extend(self, {
show: function(e) {
// tip not initialized yet
if (!tip) {
// data-tooltip
if (tipAttr) {
tip = $(tipAttr);
// single tip element for all
} else if (conf.tip) {
tip = $(conf.tip).eq(0);
// autogenerated tooltip
} else if (title) {
tip = $(conf.layout).addClass(conf.tipClass).appendTo(document.body)
.hide().append(title);
// manual tooltip
} else {
tip = trigger.next();
if (!tip.length) { tip = trigger.parent().next(); }
}
if (!tip.length) { throw "Cannot find tooltip for " + trigger; }
}
if (self.isShown()) { return self; }
// stop previous animation
tip.stop(true, true);
// get position
var pos = getPosition(trigger, tip, conf);
// restore title for single tooltip element
if (conf.tip) {
tip.html(trigger.data("title"));
}
// onBeforeShow
e = $.Event();
e.type = "onBeforeShow";
fire.trigger(e, [pos]);
if (e.isDefaultPrevented()) { return self; }
// onBeforeShow may have altered the configuration
pos = getPosition(trigger, tip, conf);
// set position
tip.css({position:'absolute', top: pos.top, left: pos.left});
shown = true;
// invoke effect
effect[0].call(self, function() {
e.type = "onShow";
shown = 'full';
fire.trigger(e);
});
// tooltip events
var event = conf.events.tooltip.split(/,\s*/);
if (!tip.data("__set")) {
tip.off(event[0]).on(event[0], function() {
clearTimeout(timer);
clearTimeout(pretimer);
});
if (event[1] && !trigger.is("input:not(:checkbox, :radio), textarea")) {
tip.off(event[1]).on(event[1], function(e) {
// being moved to the trigger element
if (e.relatedTarget != trigger[0]) {
trigger.trigger(evt[1].split(" ")[0]);
}
});
}
// bind agein for if same tip element
if (!conf.tip) tip.data("__set", true);
}
return self;
},
hide: function(e) {
if (!tip || !self.isShown()) { return self; }
// onBeforeHide
e = $.Event();
e.type = "onBeforeHide";
fire.trigger(e);
if (e.isDefaultPrevented()) { return; }
shown = false;
effects[conf.effect][1].call(self, function() {
e.type = "onHide";
fire.trigger(e);
});
return self;
},
isShown: function(fully) {
return fully ? shown == 'full' : shown;
},
getConf: function() {
return conf;
},
getTip: function() {
return tip;
},
getTrigger: function() {
return trigger;
}
});
// callbacks
$.each("onHide,onBeforeShow,onShow,onBeforeHide".split(","), function(i, name) {
// configuration
if ($.isFunction(conf[name])) {
$(self).on(name, conf[name]);
}
// API
self[name] = function(fn) {
if (fn) { $(self).on(name, fn); }
return self;
};
});
}
// jQuery plugin implementation
$.fn.tooltip2 = function(conf) {
// return existing instance
var api = this.data("tooltip");
if (api) { return api; }
conf = $.extend(true, {}, $.tools.tooltip.conf, conf);
// position can also be given as string
if (typeof conf.position == 'string') {
conf.position = conf.position.split(/,?\s/);
}
// install tooltip for each entry in jQuery object
this.each(function() {
api = new Tooltip($(this), conf);
$(this).data("tooltip", api);
});
return conf.api ? api: this;
};
}) (jQuery);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,20 @@
<?php
class UserIdentity extends CUserIdentity
{
/**
* Authenticates a user.
* @return boolean whether authentication succeeds.
*/
public function authenticate()
{
$password=Yii::app()->getController()->getModule()->password;
if($password===null)
throw new CException('Please configure the "password" property of the "gii" module.');
elseif($password===false || $password===$this->password)
$this->errorCode=self::ERROR_NONE;
else
$this->errorCode=self::ERROR_UNKNOWN_IDENTITY;
return !$this->errorCode;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
<?php
class ControllerGenerator extends CCodeGenerator
{
public $codeModel='gii.generators.controller.ControllerCode';
}

View File

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

View File

@@ -0,0 +1,36 @@
<?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"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
<?php
$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>

View File

@@ -0,0 +1,45 @@
<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.
Make sure to write module name in the correct case if it has a camelCase name.
</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(); ?>

View File

@@ -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 and backslashes.'),
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.");
elseif(!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.");
elseif(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}')";
elseif(stripos($column->dbType,'text')!==false)
return "CHtml::activeTextArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))";
else
{
if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name))
$inputField='activePasswordField';
else
$inputField='activeTextField';
if($column->type!=='string' || $column->size===null)
return "CHtml::{$inputField}(\$model,'{$column->name}')";
else
{
if(($size=$maxLength=$column->size)>60)
$size=60;
return "CHtml::{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))";
}
}
}
public function generateActiveLabel($modelClass,$column)
{
return "\$form->labelEx(\$model,'{$column->name}')";
}
public function generateActiveField($modelClass,$column)
{
if($column->type==='boolean')
return "\$form->checkBox(\$model,'{$column->name}')";
elseif(stripos($column->dbType,'text')!==false)
return "\$form->textArea(\$model,'{$column->name}',array('rows'=>6, 'cols'=>50))";
else
{
if(preg_match('/^(password|pass|passwd|passcode)$/i',$column->name))
$inputField='passwordField';
else
$inputField='textField';
if($column->type!=='string' || $column->size===null)
return "\$form->{$inputField}(\$model,'{$column->name}')";
else
{
if(($size=$maxLength=$column->size)>60)
$size=60;
return "\$form->{$inputField}(\$model,'{$column->name}',array('size'=>$size,'maxlength'=>$maxLength))";
}
}
}
public function guessNameColumn($columns)
{
foreach($columns as $column)
{
if(!strcasecmp($column->name,'name'))
return $column->name;
}
foreach($columns as $column)
{
if(!strcasecmp($column->name,'title'))
return $column->name;
}
foreach($columns as $column)
{
if($column->isPrimaryKey)
return $column->name;
}
return 'id';
}
}

View File

@@ -0,0 +1,6 @@
<?php
class CrudGenerator extends CCodeGenerator
{
public $codeModel='gii.generators.crud.CrudCode';
}

View File

@@ -0,0 +1,49 @@
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
/* @var $model <?php echo $this->getModelClass(); ?> */
/* @var $form CActiveForm */
?>
<div class="form">
<?php echo "<?php \$form=\$this->beginWidget('CActiveForm', array(
'id'=>'".$this->class2id($this->modelClass)."-form',
// Please note: When you enable ajax validation, make sure the corresponding
// controller action is handling ajax validation correctly.
// There is a call to performAjaxValidation() commented in generated controller code.
// See class documentation of CActiveForm for details on this.
'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 -->

View File

@@ -0,0 +1,38 @@
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
/* @var $model <?php echo $this->getModelClass(); ?> */
/* @var $form CActiveForm */
?>
<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 -->

View File

@@ -0,0 +1,31 @@
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
/* @var $data <?php echo $this->getModelClass(); ?> */
?>
<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>

View File

@@ -0,0 +1,73 @@
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
/* @var $model <?php echo $this->getModelClass(); ?> */
<?php
$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(){
$('#<?php echo $this->class2id($this->modelClass); ?>-grid').yiiGridView('update', {
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>&lt;</b>, <b>&lt;=</b>, <b>&gt;</b>, <b>&gt;=</b>, <b>&lt;&gt;</b>
or <b>=</b>) at the beginning of each of your search values to specify how the comparison should be done.
</p>
<?php echo "<?php echo CHtml::link('Advanced Search','#',array('class'=>'search-button')); ?>"; ?>
<div class="search-form" style="display:none">
<?php echo "<?php \$this->renderPartial('_search',array(
'model'=>\$model,
)); ?>\n"; ?>
</div><!-- search-form -->
<?php echo "<?php"; ?> $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'<?php echo $this->class2id($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',
),
),
)); ?>

View File

@@ -0,0 +1,180 @@
<?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
'postOnly + delete', // we only allow deletion via POST request
);
}
/**
* 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)
{
$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'));
}
/**
* 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 $id the ID of the model to be loaded
* @return <?php echo $this->modelClass; ?> the loaded model
* @throws CHttpException
*/
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 <?php echo $this->modelClass; ?> $model 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();
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
/* @var $model <?php echo $this->getModelClass(); ?> */
<?php
$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 \$this->renderPartial('_form', array('model'=>\$model)); ?>"; ?>

View File

@@ -0,0 +1,29 @@
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
/* @var $dataProvider CActiveDataProvider */
<?php
$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',
)); ?>

View File

@@ -0,0 +1,31 @@
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
/* @var $model <?php echo $this->getModelClass(); ?> */
<?php
$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 \$this->renderPartial('_form', array('model'=>\$model)); ?>"; ?>

View File

@@ -0,0 +1,39 @@
<?php
/**
* The following variables are available in this template:
* - $this: the CrudCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getControllerClass(); ?> */
/* @var $model <?php echo $this->getModelClass(); ?> */
<?php
$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";
?>
),
)); ?>

View File

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

View File

@@ -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.");
elseif(!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();
}
}

View File

@@ -0,0 +1,6 @@
<?php
class FormGenerator extends CCodeGenerator
{
public $codeModel='gii.generators.form.FormCode';
}

View File

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

View File

@@ -0,0 +1,44 @@
<?php
/**
* This is the template for generating a form script file.
* The following variables are available in this template:
* - $this: the FormCode object
*/
?>
<?php echo "<?php\n"; ?>
/* @var $this <?php echo $this->getModelClass(); ?>Controller */
/* @var $model <?php echo $this->getModelClass(); ?> */
/* @var $form CActiveForm */
?>
<div class="form">
<?php echo "<?php \$form=\$this->beginWidget('CActiveForm', array(
'id'=>'".$this->class2id($this->modelClass).'-'.basename($this->viewName)."-form',
// Please note: When you enable ajax validation, make sure the corresponding
// controller action is handling ajax validation correctly.
// See class documentation of CActiveForm for details on this,
// you need to use the performAjaxValidation()-method described there.
'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 -->

View File

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

View File

@@ -0,0 +1,431 @@
<?php
class ModelCode extends CCodeModel
{
public $connectionId='db';
public $tablePrefix;
public $tableName;
public $modelClass;
public $modelPath='application.models';
public $baseClass='CActiveRecord';
public $buildRelations=true;
public $commentsAsLabels=false;
/**
* @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, connectionId', 'filter', 'filter'=>'trim'),
array('connectionId, 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('connectionId', 'validateConnectionId', 'skipOnError'=>true),
array('tableName', 'validateTableName', 'skipOnError'=>true),
array('tablePrefix, modelClass', 'match', 'pattern'=>'/^[a-zA-Z_]\w*$/', 'message'=>'{attribute} should only contain word characters.'),
array('baseClass', 'match', 'pattern'=>'/^[a-zA-Z_][\w\\\\]*$/', 'message'=>'{attribute} should only contain word characters and backslashes.'),
array('modelPath', 'validateModelPath', 'skipOnError'=>true),
array('baseClass, modelClass', 'validateReservedWord', 'skipOnError'=>true),
array('baseClass', 'validateBaseClass', 'skipOnError'=>true),
array('connectionId, tablePrefix, modelPath, baseClass, buildRelations, commentsAsLabels', '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',
'commentsAsLabels'=>'Use Column Comments as Attribute Labels',
'connectionId'=>'Database Connection',
));
}
public function requiredTemplates()
{
return array(
'model.php',
);
}
public function init()
{
if(Yii::app()->{$this->connectionId}===null)
throw new CHttpException(500,'A valid database connection is required to run this generator.');
$this->tablePrefix=Yii::app()->{$this->connectionId}->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()->{$this->connectionId}->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(),
'connectionId'=>$this->connectionId,
);
$this->files[]=new CCodeFile(
Yii::getPathOfAlias($this->modelPath).'/'.$className.'.php',
$this->render($templatePath.'/model.php', $params)
);
}
}
public function validateTableName($attribute,$params)
{
if($this->hasErrors())
return;
$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()->{$this->connectionId}->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.");
elseif($class!=='CActiveRecord' && !is_subclass_of($class,'CActiveRecord'))
$this->addError('baseClass', "'{$this->model}' must extend from CActiveRecord.");
}
public function getTableSchema($tableName)
{
$connection=Yii::app()->{$this->connectionId};
return $connection->getSchema()->getTable($tableName, $connection->schemaCachingDuration!==0);
}
public function generateLabels($table)
{
$labels=array();
foreach($table->columns as $column)
{
if($this->commentsAsLabels && $column->comment)
$labels[$column->name]=$column->comment;
else
{
$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';
$label=str_replace("'","\\'",$label);
$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;
elseif($column->type==='double')
$numerical[]=$column->name;
elseif($column->type==='string' && $column->size>0)
$length[$column->size][]=$column->name;
elseif(!$column->isPrimaryKey && !$r)
$safe[]=$column->name;
}
if($required!==array())
$rules[]="array('".implode(', ',$required)."', 'required')";
if($integers!==array())
$rules[]="array('".implode(', ',$integers)."', 'numerical', 'integerOnly'=>true)";
if($numerical!==array())
$rules[]="array('".implode(', ',$numerical)."', 'numerical')";
if($length!==array())
{
foreach($length as $len=>$cols)
$rules[]="array('".implode(', ',$cols)."', 'length', 'max'=>$len)";
}
if($safe!==array())
$rules[]="array('".implode(', ',$safe)."', 'safe')";
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()->{$this->connectionId}->tablePrefix=='')
return $tableName;
$prefix=$this->tablePrefix!='' ? $this->tablePrefix : Yii::app()->{$this->connectionId}->tablePrefix;
if($prefix!='')
{
if($addBrackets && Yii::app()->{$this->connectionId}->tablePrefix!='')
{
$prefix=Yii::app()->{$this->connectionId}->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;
}
elseif(strpos($tableName,$prefix)===0)
return $lb.substr($tableName,strlen($prefix)).$rb;
}
return $tableName;
}
protected function generateRelations()
{
if(!$this->buildRelations)
return array();
$schemaName='';
if(($pos=strpos($this->tableName,'.'))!==false)
$schemaName=substr($this->tableName,0,$pos);
$relations=array();
foreach(Yii::app()->{$this->connectionId}->schema->getTables($schemaName) 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);
$i=1;
$rawName=$relationName;
while(isset($relations[$className1][$relationName]))
$relationName=$rawName.$i++;
$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);
if(($pos=strpos($tableName,'.'))!==false) // remove schema part (e.g. remove 'public2.' from 'public2.post')
$tableName=substr($tableName,$pos+1);
$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()->{$this->connectionId}->schema->getTable($tableName);
$i=0;
while(isset($table->columns[$name]))
$name=$rawName.($i++);
return $name;
}
public function validateConnectionId($attribute, $params)
{
if(Yii::app()->hasComponent($this->connectionId)===false || !(Yii::app()->getComponent($this->connectionId) instanceof CDbConnection))
$this->addError('connectionId','A valid database connection is required to run this generator.');
}
}

View File

@@ -0,0 +1,25 @@
<?php
class ModelGenerator extends CCodeGenerator
{
public $codeModel='gii.generators.model.ModelCode';
/**
* Provides autocomplete table names
* @param string $db the database connection component id
* @return string the json array of tablenames that contains the entered term $q
*/
public function actionGetTableNames($db)
{
if(Yii::app()->getRequest()->getIsAjaxRequest())
{
$all = array();
if(!empty($db) && Yii::app()->hasComponent($db)!==false && (Yii::app()->getComponent($db) instanceof CDbConnection))
$all=array_keys(Yii::app()->{$db}->schema->getTables());
echo json_encode($all);
}
else
throw new CHttpException(404,'The requested page does not exist.');
}
}

View File

@@ -0,0 +1,163 @@
<?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"; ?>
{
/**
* @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().
// @todo 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.
*
* Typical usecase:
* - Initialize the model fields with values from filter form.
* - Execute this method to get CActiveDataProvider instance which will filter
* models according to data in model fields.
* - Pass data provider to CGridView, CListView or any similar widget.
*
* @return CActiveDataProvider the data provider that can return the models
* based on the search/filter conditions.
*/
public function search()
{
// @todo 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,
));
}
<?php if($connectionId!='db'):?>
/**
* @return CDbConnection the database connection used for this class
*/
public function getDbConnection()
{
return Yii::app()-><?php echo $connectionId ?>;
}
<?php endif?>
/**
* Returns the static model of the specified AR class.
* Please note that you should have this exact method in all your CActiveRecord descendants!
* @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);
}
}

View File

@@ -0,0 +1,149 @@
<?php
$class=get_class($model);
Yii::app()->clientScript->registerScript('gii.model',"
$('#{$class}_connectionId').change(function(){
var tableName=$('#{$class}_tableName');
tableName.autocomplete('option', 'source', []);
$.ajax({
url: '".Yii::app()->getUrlManager()->createUrl('gii/model/getTableNames')."',
data: {db: this.value},
dataType: 'json'
}).done(function(data){
tableName.autocomplete('option', 'source', data);
});
});
$('#{$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, 'connectionId')?>
<?php echo $form->textField($model, 'connectionId', array('size'=>65))?>
<div class="tooltip">
The database component that should be used.
</div>
<?php echo $form->error($model,'connectionId'); ?>
</div>
<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'=>Yii::app()->hasComponent($model->connectionId) ? array_keys(Yii::app()->{$model->connectionId}->schema->getTables()) : array(),
'options'=>array(
'minLength'=>'0',
'focus'=>new CJavaScriptExpression('function(event,ui) {
$("#'.CHtml::activeId($model,'tableName').'").val(ui.item.label).change();
return false;
}')
),
'htmlOptions'=>array(
'id'=>CHtml::activeId($model,'tableName'),
'size'=>'65',
'data-tooltip'=>'#tableName-tooltip'
),
)); ?>
<div class="tooltip" id="tableName-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>
<div class="row">
<?php echo $form->labelEx($model,'commentsAsLabels'); ?>
<?php echo $form->checkBox($model,'commentsAsLabels'); ?>
<div class="tooltip">
Whether comments specified for the table columns should be used as the new model's attribute labels.
In case your RDBMS doesn't support feature of commenting columns or column comment wasn't set,
column name would be used as the attribute name base.
</div>
<?php echo $form->error($model,'commentsAsLabels'); ?>
</div>
<?php $this->endWidget(); ?>

View File

@@ -0,0 +1,94 @@
<?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',
'.gitignore'
),
));
foreach($files as $file)
{
if($file!==$moduleTemplateFile)
{
if(CFileHelper::getExtension($file)==='php')
$content=$this->render($file);
elseif(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;
}
}

View File

@@ -0,0 +1,6 @@
<?php
class ModuleGenerator extends CCodeGenerator
{
public $codeModel='gii.generators.module.ModuleCode';
}

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
<?php echo "<?php\n"; ?>
/* @var $this DefaultController */
$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>

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
<?php
if($file->type==='php')
{
echo '<div class="content">';
highlight_string($file->content);
echo '</div>';
}
elseif(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>';
?>

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
<h1>Error <?php echo $code; ?></h1>
<div class="error">
<?php echo CHtml::encode($message); ?>
</div>

View File

@@ -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($name.'/index'));?></li>
<?php endforeach; ?>
</ul>

View File

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

View File

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

View File

@@ -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($name.'/index'));?></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">
&nbsp;
</div>
</div>
<?php $this->endContent(); ?>

View File

@@ -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/tooltip.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('default/logout')); ?>
<?php endif; ?>
</div>
<div id="logo"><?php echo CHtml::link(CHtml::image($this->module->assetsUrl.'/images/logo.png'),array('default/index')); ?></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>