1
0

Added new (clean) yii boilerplate

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

View File

@@ -0,0 +1,232 @@
<?php
/**
* CActiveDataProvider 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/
*/
/**
* CActiveDataProvider implements a data provider based on ActiveRecord.
*
* CActiveDataProvider provides data in terms of ActiveRecord objects which are
* of class {@link modelClass}. It uses the AR {@link CActiveRecord::findAll} method
* to retrieve the data from database. The {@link criteria} property can be used to
* specify various query options.
*
* CActiveDataProvider may be used in the following way:
* <pre>
* $dataProvider=new CActiveDataProvider('Post', array(
* 'criteria'=>array(
* 'condition'=>'status=1',
* 'order'=>'create_time DESC',
* 'with'=>array('author'),
* ),
* 'countCriteria'=>array(
* 'condition'=>'status=1',
* // 'order' and 'with' clauses have no meaning for the count query
* ),
* 'pagination'=>array(
* 'pageSize'=>20,
* ),
* ));
* // $dataProvider->getData() will return a list of Post objects
* </pre>
*
* @property CDbCriteria $criteria The query criteria.
* @property CDbCriteria $countCriteria The count query criteria. This property is available
* since 1.1.14
* @property CSort $sort The sorting object. If this is false, it means the sorting is disabled.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.1
*/
class CActiveDataProvider extends CDataProvider
{
/**
* @var string the primary ActiveRecord class name. The {@link getData()} method
* will return a list of objects of this class.
*/
public $modelClass;
/**
* @var CActiveRecord the AR finder instance (eg <code>Post::model()</code>).
* This property can be set by passing the finder instance as the first parameter
* to the constructor. For example, <code>Post::model()->published()</code>.
* @since 1.1.3
*/
public $model;
/**
* @var string the name of key attribute for {@link modelClass}. If not set,
* it means the primary key of the corresponding database table will be used.
*/
public $keyAttribute;
/**
* @var CDbCriteria
*/
private $_criteria;
/**
* @var CDbCriteria
*/
private $_countCriteria;
/**
* Constructor.
* @param mixed $modelClass the model class (e.g. 'Post') or the model finder instance
* (e.g. <code>Post::model()</code>, <code>Post::model()->published()</code>).
* @param array $config configuration (name=>value) to be applied as the initial property values of this class.
*/
public function __construct($modelClass,$config=array())
{
if(is_string($modelClass))
{
$this->modelClass=$modelClass;
$this->model=$this->getModel($this->modelClass);
}
elseif($modelClass instanceof CActiveRecord)
{
$this->modelClass=get_class($modelClass);
$this->model=$modelClass;
}
$this->setId(CHtml::modelName($this->model));
foreach($config as $key=>$value)
$this->$key=$value;
}
/**
* Returns the query criteria.
* @return CDbCriteria the query criteria
*/
public function getCriteria()
{
if($this->_criteria===null)
$this->_criteria=new CDbCriteria;
return $this->_criteria;
}
/**
* Sets the query criteria.
* @param CDbCriteria|array $value the query criteria. This can be either a CDbCriteria object or an array
* representing the query criteria.
*/
public function setCriteria($value)
{
$this->_criteria=$value instanceof CDbCriteria ? $value : new CDbCriteria($value);
}
/**
* Returns the count query criteria.
* @return CDbCriteria the count query criteria.
* @since 1.1.14
*/
public function getCountCriteria()
{
if($this->_countCriteria===null)
return $this->getCriteria();
return $this->_countCriteria;
}
/**
* Sets the count query criteria.
* @param CDbCriteria|array $value the count query criteria. This can be either a CDbCriteria object
* or an array representing the query criteria.
* @since 1.1.14
*/
public function setCountCriteria($value)
{
$this->_countCriteria=$value instanceof CDbCriteria ? $value : new CDbCriteria($value);
}
/**
* Returns the sorting object.
* @param string $className the sorting object class name. Parameter is available since version 1.1.13.
* @return CSort the sorting object. If this is false, it means the sorting is disabled.
*/
public function getSort($className='CSort')
{
if(($sort=parent::getSort($className))!==false)
$sort->modelClass=$this->modelClass;
return $sort;
}
/**
* Given active record class name returns new model instance.
*
* @param string $className active record class name.
* @return CActiveRecord active record model instance.
*
* @since 1.1.14
*/
protected function getModel($className)
{
return CActiveRecord::model($className);
}
/**
* Fetches the data from the persistent data storage.
* @return array list of data items
*/
protected function fetchData()
{
$criteria=clone $this->getCriteria();
if(($pagination=$this->getPagination())!==false)
{
$pagination->setItemCount($this->getTotalItemCount());
$pagination->applyLimit($criteria);
}
$baseCriteria=$this->model->getDbCriteria(false);
if(($sort=$this->getSort())!==false)
{
// set model criteria so that CSort can use its table alias setting
if($baseCriteria!==null)
{
$c=clone $baseCriteria;
$c->mergeWith($criteria);
$this->model->setDbCriteria($c);
}
else
$this->model->setDbCriteria($criteria);
$sort->applyOrder($criteria);
}
$this->model->setDbCriteria($baseCriteria!==null ? clone $baseCriteria : null);
$data=$this->model->findAll($criteria);
$this->model->setDbCriteria($baseCriteria); // restore original criteria
return $data;
}
/**
* Fetches the data item keys from the persistent data storage.
* @return array list of data item keys.
*/
protected function fetchKeys()
{
$keys=array();
foreach($this->getData() as $i=>$data)
{
$key=$this->keyAttribute===null ? $data->getPrimaryKey() : $data->{$this->keyAttribute};
$keys[$i]=is_array($key) ? implode(',',$key) : $key;
}
return $keys;
}
/**
* Calculates the total number of data items.
* @return integer the total number of data items.
*/
protected function calculateTotalItemCount()
{
$baseCriteria=$this->model->getDbCriteria(false);
if($baseCriteria!==null)
$baseCriteria=clone $baseCriteria;
$count=$this->model->count($this->getCountCriteria());
$this->model->setDbCriteria($baseCriteria);
return $count;
}
}

View File

@@ -0,0 +1,193 @@
<?php
/**
* CArrayDataProvider 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/
*/
/**
* CArrayDataProvider implements a data provider based on a raw data array.
*
* The {@link rawData} property contains all data that may be sorted and/or paginated.
* CArrayDataProvider will supply the data after sorting and/or pagination.
* You may configure the {@link sort} and {@link pagination} properties to
* customize sorting and pagination behaviors.
*
* Elements in the raw data array may be either objects (e.g. model objects)
* or associative arrays (e.g. query results of DAO).
* Make sure to set the {@link keyField} property to the name of the field that uniquely
* identifies a data record or false if you do not have such a field.
*
* CArrayDataProvider may be used in the following way:
* <pre>
* $rawData=Yii::app()->db->createCommand('SELECT * FROM tbl_user')->queryAll();
* // or using: $rawData=User::model()->findAll();
* $dataProvider=new CArrayDataProvider($rawData, array(
* 'id'=>'user',
* 'sort'=>array(
* 'attributes'=>array(
* 'id', 'username', 'email',
* ),
* ),
* 'pagination'=>array(
* 'pageSize'=>10,
* ),
* ));
* // $dataProvider->getData() will return a list of arrays.
* </pre>
*
* Note: if you want to use the sorting feature, you must configure {@link sort} property
* so that the provider knows which columns can be sorted.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.1.4
*/
class CArrayDataProvider extends CDataProvider
{
/**
* @var string the name of the key field. This is a field that uniquely identifies a
* data record. In database this would be the primary key.
* Defaults to 'id'. If it's set to false, keys of {@link rawData} array are used.
*/
public $keyField='id';
/**
* @var array the data that is not paginated or sorted. When pagination is enabled,
* this property usually contains more elements than {@link data}.
* The array elements must use zero-based integer keys.
*/
public $rawData=array();
/**
* @var boolean controls how sorting works. True value means that case will be
* taken into account. False value will lead to the case insensitive sort. Default
* value is true.
* @since 1.1.13
*/
public $caseSensitiveSort=true;
/**
* Constructor.
* @param array $rawData the data that is not paginated or sorted. The array elements must use zero-based integer keys.
* @param array $config configuration (name=>value) to be applied as the initial property values of this class.
*/
public function __construct($rawData,$config=array())
{
$this->rawData=$rawData;
foreach($config as $key=>$value)
$this->$key=$value;
}
/**
* Fetches the data from the persistent data storage.
* @return array list of data items
*/
protected function fetchData()
{
if(($sort=$this->getSort())!==false && ($order=$sort->getOrderBy())!='')
$this->sortData($this->getSortDirections($order));
if(($pagination=$this->getPagination())!==false)
{
$pagination->setItemCount($this->getTotalItemCount());
return array_slice($this->rawData, $pagination->getOffset(), $pagination->getLimit());
}
else
return $this->rawData;
}
/**
* Fetches the data item keys from the persistent data storage.
* @return array list of data item keys.
*/
protected function fetchKeys()
{
if($this->keyField===false)
return array_keys($this->rawData);
$keys=array();
foreach($this->getData() as $i=>$data)
$keys[$i]=is_object($data) ? $data->{$this->keyField} : $data[$this->keyField];
return $keys;
}
/**
* Calculates the total number of data items.
* This method simply returns the number of elements in {@link rawData}.
* @return integer the total number of data items.
*/
protected function calculateTotalItemCount()
{
return count($this->rawData);
}
/**
* Sorts the raw data according to the specified sorting instructions.
* After calling this method, {@link rawData} will be modified.
* @param array $directions the sorting directions (field name => whether it is descending sort)
*/
protected function sortData($directions)
{
if(empty($directions))
return;
$args=array();
$dummy=array();
foreach($directions as $name=>$descending)
{
$column=array();
$fields_array=preg_split('/\.+/',$name,-1,PREG_SPLIT_NO_EMPTY);
foreach($this->rawData as $index=>$data)
$column[$index]=$this->getSortingFieldValue($data, $fields_array);
$args[]=&$column;
$dummy[]=&$column;
unset($column);
$direction=$descending ? SORT_DESC : SORT_ASC;
$args[]=&$direction;
$dummy[]=&$direction;
unset($direction);
}
$args[]=&$this->rawData;
call_user_func_array('array_multisort', $args);
}
/**
* Get field for sorting, using dot like delimiter in query.
* @param mixed $data array or object
* @param array $fields sorting fields in $data
* @return mixed $data sorting field value
*/
protected function getSortingFieldValue($data, $fields)
{
if(is_object($data))
{
foreach($fields as $field)
$data=isset($data->$field) ? $data->$field : null;
}
else
{
foreach($fields as $field)
$data=isset($data[$field]) ? $data[$field] : null;
}
return $this->caseSensitiveSort ? $data : mb_strtolower($data,Yii::app()->charset);
}
/**
* Converts the "ORDER BY" clause into an array representing the sorting directions.
* @param string $order the "ORDER BY" clause.
* @return array the sorting directions (field name => whether it is descending sort)
*/
protected function getSortDirections($order)
{
$segs=explode(',',$order);
$directions=array();
foreach($segs as $seg)
{
if(preg_match('/(.*?)(\s+(desc|asc))?$/i',trim($seg),$matches))
$directions[$matches[1]]=isset($matches[3]) && !strcasecmp($matches[3],'desc');
else
$directions[trim($seg)]=false;
}
return $directions;
}
}

View File

@@ -0,0 +1,334 @@
<?php
/**
* CAssetManager 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/
*/
/**
* CAssetManager is a Web application component that manages private files (called assets) and makes them accessible by Web clients.
*
* It achieves this goal by copying assets to a Web-accessible directory
* and returns the corresponding URL for accessing them.
*
* To publish an asset, simply call {@link publish()}.
*
* The Web-accessible directory holding the published files is specified
* by {@link setBasePath basePath}, which defaults to the "assets" directory
* under the directory containing the application entry script file.
* The property {@link setBaseUrl baseUrl} refers to the URL for accessing
* the {@link setBasePath basePath}.
*
* @property string $basePath The root directory storing the published asset files. Defaults to 'WebRoot/assets'.
* @property string $baseUrl The base url that the published asset files can be accessed.
* Note, the ending slashes are stripped off. Defaults to '/AppBaseUrl/assets'.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CAssetManager extends CApplicationComponent
{
/**
* Default web accessible base path for storing private files
*/
const DEFAULT_BASEPATH='assets';
/**
* @var boolean whether to use symbolic link to publish asset files. Defaults to false, meaning
* asset files are copied to public folders. Using symbolic links has the benefit that the published
* assets will always be consistent with the source assets. This is especially useful during development.
*
* However, there are special requirements for hosting environments in order to use symbolic links.
* In particular, symbolic links are supported only on Linux/Unix, and Windows Vista/2008 or greater.
* The latter requires PHP 5.3 or greater.
*
* Moreover, some Web servers need to be properly configured so that the linked assets are accessible
* to Web users. For example, for Apache Web server, the following configuration directive should be added
* for the Web folder:
* <pre>
* Options FollowSymLinks
* </pre>
*
* Note that this property cannot be true when {@link $forceCopy} property has true value too. Otherwise
* an exception would be thrown. Using both properties at the same time is illogical because both of them
* are solving similar tasks but in a different ways. Please refer to the {@link $forceCopy} documentation
* for more details.
*
* @since 1.1.5
*/
public $linkAssets=false;
/**
* @var array list of directories and files which should be excluded from the publishing process.
* Defaults to exclude '.svn' and '.gitignore' files only. This option has no effect if {@link linkAssets} is enabled.
* @since 1.1.6
**/
public $excludeFiles=array('.svn','.gitignore');
/**
* @var integer the permission to be set for newly generated asset files.
* This value will be used by PHP chmod function.
* Defaults to 0666, meaning the file is read-writable by all users.
* @since 1.1.8
*/
public $newFileMode=0666;
/**
* @var integer the permission to be set for newly generated asset 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.
* @since 1.1.8
*/
public $newDirMode=0777;
/**
* @var boolean whether we should copy the asset files and directories even if they already published before.
* This property is used only during development stage. The main use case of this property is when you need
* to force the original assets always copied by changing only one value without searching needed {@link publish}
* method calls across the application codebase. Also it is useful in operating systems which does not fully
* support symbolic links (therefore it is not possible to use {@link $linkAssets}) or we don't want to use them.
* This property sets the default value of the $forceCopy parameter in {@link publish} method. Default value
* of this property is false meaning that the assets will be published only in case they don't exist in webroot
* assets directory.
*
* Note that this property cannot be true when {@link $linkAssets} property has true value too. Otherwise
* an exception would be thrown. Using both properties at the same time is illogical because both of them
* are solving similar tasks but in a different ways. Please refer to the {@link $linkAssets} documentation
* for more details.
*
* @since 1.1.11
*/
public $forceCopy=false;
/**
* @var string base web accessible path for storing private files
*/
private $_basePath;
/**
* @var string base URL for accessing the publishing directory.
*/
private $_baseUrl;
/**
* @var array published assets
*/
private $_published=array();
/**
* @return string the root directory storing the published asset files. Defaults to 'WebRoot/assets'.
*/
public function getBasePath()
{
if($this->_basePath===null)
{
$request=Yii::app()->getRequest();
$this->setBasePath(dirname($request->getScriptFile()).DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH);
}
return $this->_basePath;
}
/**
* Sets the root directory storing published asset files.
* @param string $value the root directory storing published asset files
* @throws CException if the base path is invalid
*/
public function setBasePath($value)
{
if(($basePath=realpath($value))!==false && is_dir($basePath) && is_writable($basePath))
$this->_basePath=$basePath;
else
throw new CException(Yii::t('yii','CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.',
array('{path}'=>$value)));
}
/**
* @return string the base url that the published asset files can be accessed.
* Note, the ending slashes are stripped off. Defaults to '/AppBaseUrl/assets'.
*/
public function getBaseUrl()
{
if($this->_baseUrl===null)
{
$request=Yii::app()->getRequest();
$this->setBaseUrl($request->getBaseUrl().'/'.self::DEFAULT_BASEPATH);
}
return $this->_baseUrl;
}
/**
* @param string $value the base url that the published asset files can be accessed
*/
public function setBaseUrl($value)
{
$this->_baseUrl=rtrim($value,'/');
}
/**
* Publishes a file or a directory.
* This method will copy the specified asset to a web accessible directory
* and return the URL for accessing the published asset.
* <ul>
* <li>If the asset is a file, its file modification time will be checked
* to avoid unnecessary file copying;</li>
* <li>If the asset is a directory, all files and subdirectories under it will
* be published recursively. Note, in case $forceCopy is false the method only checks the
* existence of the target directory to avoid repetitive copying.</li>
* </ul>
*
* Note: On rare scenario, a race condition can develop that will lead to a
* one-time-manifestation of a non-critical problem in the creation of the directory
* that holds the published assets. This problem can be avoided altogether by 'requesting'
* in advance all the resources that are supposed to trigger a 'publish()' call, and doing
* that in the application deployment phase, before system goes live. See more in the following
* discussion: http://code.google.com/p/yii/issues/detail?id=2579
*
* @param string $path the asset (file or directory) to be published
* @param boolean $hashByName whether the published directory should be named as the hashed basename.
* If false, the name will be the hash taken from dirname of the path being published and path mtime.
* Defaults to false. Set true if the path being published is shared among
* different extensions.
* @param integer $level level of recursive copying when the asset is a directory.
* Level -1 means publishing all subdirectories and files;
* Level 0 means publishing only the files DIRECTLY under the directory;
* level N means copying those directories that are within N levels.
* @param boolean $forceCopy whether we should copy the asset file or directory even if it is already
* published before. In case of publishing a directory old files will not be removed.
* This parameter is set true mainly during development stage when the original
* assets are being constantly changed. The consequence is that the performance is degraded,
* which is not a concern during development, however. Default value of this parameter is null meaning
* that it's value is controlled by {@link $forceCopy} class property. This parameter has been available
* since version 1.1.2. Default value has been changed since 1.1.11.
* Note that this parameter cannot be true when {@link $linkAssets} property has true value too. Otherwise
* an exception would be thrown. Using this parameter with {@link $linkAssets} property at the same time
* is illogical because both of them are solving similar tasks but in a different ways. Please refer
* to the {@link $linkAssets} documentation for more details.
* @return string an absolute URL to the published asset
* @throws CException if the asset to be published does not exist.
*/
public function publish($path,$hashByName=false,$level=-1,$forceCopy=null)
{
if($forceCopy===null)
$forceCopy=$this->forceCopy;
if($forceCopy && $this->linkAssets)
throw new CException(Yii::t('yii','The "forceCopy" and "linkAssets" cannot be both true.'));
if(isset($this->_published[$path]))
return $this->_published[$path];
elseif(($src=realpath($path))!==false)
{
$dir=$this->generatePath($src,$hashByName);
$dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir;
if(is_file($src))
{
$fileName=basename($src);
$dstFile=$dstDir.DIRECTORY_SEPARATOR.$fileName;
if(!is_dir($dstDir))
{
mkdir($dstDir,$this->newDirMode,true);
@chmod($dstDir,$this->newDirMode);
}
if($this->linkAssets && !is_file($dstFile)) symlink($src,$dstFile);
elseif(@filemtime($dstFile)<@filemtime($src))
{
copy($src,$dstFile);
@chmod($dstFile,$this->newFileMode);
}
return $this->_published[$path]=$this->getBaseUrl()."/$dir/$fileName";
}
elseif(is_dir($src))
{
if($this->linkAssets && !is_dir($dstDir))
{
symlink($src,$dstDir);
}
elseif(!is_dir($dstDir) || $forceCopy)
{
CFileHelper::copyDirectory($src,$dstDir,array(
'exclude'=>$this->excludeFiles,
'level'=>$level,
'newDirMode'=>$this->newDirMode,
'newFileMode'=>$this->newFileMode,
));
}
return $this->_published[$path]=$this->getBaseUrl().'/'.$dir;
}
}
throw new CException(Yii::t('yii','The asset "{asset}" to be published does not exist.',
array('{asset}'=>$path)));
}
/**
* Returns the published path of a file path.
* This method does not perform any publishing. It merely tells you
* if the file or directory is published, where it will go.
* @param string $path directory or file path being published
* @param boolean $hashByName whether the published directory should be named as the hashed basename.
* If false, the name will be the hash taken from dirname of the path being published and path mtime.
* Defaults to false. Set true if the path being published is shared among
* different extensions.
* @return string the published file path. False if the file or directory does not exist
*/
public function getPublishedPath($path,$hashByName=false)
{
if(($path=realpath($path))!==false)
{
$base=$this->getBasePath().DIRECTORY_SEPARATOR.$this->generatePath($path,$hashByName);
return is_file($path) ? $base.DIRECTORY_SEPARATOR.basename($path) : $base ;
}
else
return false;
}
/**
* Returns the URL of a published file path.
* This method does not perform any publishing. It merely tells you
* if the file path is published, what the URL will be to access it.
* @param string $path directory or file path being published
* @param boolean $hashByName whether the published directory should be named as the hashed basename.
* If false, the name will be the hash taken from dirname of the path being published and path mtime.
* Defaults to false. Set true if the path being published is shared among
* different extensions.
* @return string the published URL for the file or directory. False if the file or directory does not exist.
*/
public function getPublishedUrl($path,$hashByName=false)
{
if(isset($this->_published[$path]))
return $this->_published[$path];
if(($path=realpath($path))!==false)
{
$base=$this->getBaseUrl().'/'.$this->generatePath($path,$hashByName);
return is_file($path) ? $base.'/'.basename($path) : $base;
}
else
return false;
}
/**
* Generate a CRC32 hash for the directory path. Collisions are higher
* than MD5 but generates a much smaller hash string.
* @param string $path string to be hashed.
* @return string hashed string.
*/
protected function hash($path)
{
return sprintf('%x',crc32($path.Yii::getVersion()));
}
/**
* Generates path segments relative to basePath.
* @param string $file for which public path will be created.
* @param bool $hashByName whether the published directory should be named as the hashed basename.
* @return string path segments without basePath.
* @since 1.1.13
*/
protected function generatePath($file,$hashByName=false)
{
if (is_file($file))
$pathForHashing=$hashByName ? basename($file) : dirname($file).filemtime($file);
else
$pathForHashing=$hashByName ? basename($file) : $file.filemtime($file);
return $this->hash($pathForHashing);
}
}

View File

@@ -0,0 +1,302 @@
<?php
/**
* CBaseController 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/
*/
/**
* CBaseController is the base class for {@link CController} and {@link CWidget}.
*
* It provides the common functionalities shared by controllers who need to render views.
*
* CBaseController also implements the support for the following features:
* <ul>
* <li>{@link CClipWidget Clips} : a clip is a piece of captured output that can be inserted elsewhere.</li>
* <li>{@link CWidget Widgets} : a widget is a self-contained sub-controller with its own view and model.</li>
* <li>{@link COutputCache Fragment cache} : fragment cache selectively caches a portion of the output.</li>
* </ul>
*
* To use a widget in a view, use the following in the view:
* <pre>
* $this->widget('path.to.widgetClass',array('property1'=>'value1',...));
* </pre>
* or
* <pre>
* $this->beginWidget('path.to.widgetClass',array('property1'=>'value1',...));
* // ... display other contents here
* $this->endWidget();
* </pre>
*
* To create a clip, use the following:
* <pre>
* $this->beginClip('clipID');
* // ... display the clip contents
* $this->endClip();
* </pre>
* Then, in a different view or place, the captured clip can be inserted as:
* <pre>
* echo $this->clips['clipID'];
* </pre>
*
* Note that $this in the code above refers to current controller so, for example,
* if you need to access clip from a widget where $this refers to widget itself
* you need to do it the following way:
*
* <pre>
* echo $this->getController()->clips['clipID'];
* </pre>
*
* To use fragment cache, do as follows,
* <pre>
* if($this->beginCache('cacheID',array('property1'=>'value1',...))
* {
* // ... display the content to be cached here
* $this->endCache();
* }
* </pre>
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
abstract class CBaseController extends CComponent
{
private $_widgetStack=array();
/**
* Returns the view script file according to the specified view name.
* This method must be implemented by child classes.
* @param string $viewName view name
* @return string the file path for the named view. False if the view cannot be found.
*/
abstract public function getViewFile($viewName);
/**
* Renders a view file.
*
* @param string $viewFile view file path
* @param array $data data to be extracted and made available to the view
* @param boolean $return whether the rendering result should be returned instead of being echoed
* @return string the rendering result. Null if the rendering result is not required.
* @throws CException if the view file does not exist
*/
public function renderFile($viewFile,$data=null,$return=false)
{
$widgetCount=count($this->_widgetStack);
if(($renderer=Yii::app()->getViewRenderer())!==null && $renderer->fileExtension==='.'.CFileHelper::getExtension($viewFile))
$content=$renderer->renderFile($this,$viewFile,$data,$return);
else
$content=$this->renderInternal($viewFile,$data,$return);
if(count($this->_widgetStack)===$widgetCount)
return $content;
else
{
$widget=end($this->_widgetStack);
throw new CException(Yii::t('yii','{controller} contains improperly nested widget tags in its view "{view}". A {widget} widget does not have an endWidget() call.',
array('{controller}'=>get_class($this), '{view}'=>$viewFile, '{widget}'=>get_class($widget))));
}
}
/**
* Renders a view file.
* This method includes the view file as a PHP script
* and captures the display result if required.
* @param string $_viewFile_ view file
* @param array $_data_ data to be extracted and made available to the view file
* @param boolean $_return_ whether the rendering result should be returned as a string
* @return string the rendering result. Null if the rendering result is not required.
*/
public function renderInternal($_viewFile_,$_data_=null,$_return_=false)
{
// we use special variable names here to avoid conflict when extracting data
if(is_array($_data_))
extract($_data_,EXTR_PREFIX_SAME,'data');
else
$data=$_data_;
if($_return_)
{
ob_start();
ob_implicit_flush(false);
require($_viewFile_);
return ob_get_clean();
}
else
require($_viewFile_);
}
/**
* Creates a widget and initializes it.
* This method first creates the specified widget instance.
* It then configures the widget's properties with the given initial values.
* At the end it calls {@link CWidget::init} to initialize the widget.
* Starting from version 1.1, if a {@link CWidgetFactory widget factory} is enabled,
* this method will use the factory to create the widget, instead.
* @param string $className class name (can be in path alias format)
* @param array $properties initial property values
* @return CWidget the fully initialized widget instance.
*/
public function createWidget($className,$properties=array())
{
$widget=Yii::app()->getWidgetFactory()->createWidget($this,$className,$properties);
$widget->init();
return $widget;
}
/**
* Creates a widget and executes it.
* @param string $className the widget class name or class in dot syntax (e.g. application.widgets.MyWidget)
* @param array $properties list of initial property values for the widget (Property Name => Property Value)
* @param boolean $captureOutput whether to capture the output of the widget. If true, the method will capture
* and return the output generated by the widget. If false, the output will be directly sent for display
* and the widget object will be returned. This parameter is available since version 1.1.2.
* @return mixed the widget instance when $captureOutput is false, or the widget output when $captureOutput is true.
*/
public function widget($className,$properties=array(),$captureOutput=false)
{
if($captureOutput)
{
ob_start();
ob_implicit_flush(false);
$widget=$this->createWidget($className,$properties);
$widget->run();
return ob_get_clean();
}
else
{
$widget=$this->createWidget($className,$properties);
$widget->run();
return $widget;
}
}
/**
* Creates a widget and executes it.
* This method is similar to {@link widget()} except that it is expecting
* a {@link endWidget()} call to end the execution.
* @param string $className the widget class name or class in dot syntax (e.g. application.widgets.MyWidget)
* @param array $properties list of initial property values for the widget (Property Name => Property Value)
* @return CWidget the widget created to run
* @see endWidget
*/
public function beginWidget($className,$properties=array())
{
$widget=$this->createWidget($className,$properties);
$this->_widgetStack[]=$widget;
return $widget;
}
/**
* Ends the execution of the named widget.
* This method is used together with {@link beginWidget()}.
* @param string $id optional tag identifying the method call for debugging purpose.
* @return CWidget the widget just ended running
* @throws CException if an extra endWidget call is made
* @see beginWidget
*/
public function endWidget($id='')
{
if(($widget=array_pop($this->_widgetStack))!==null)
{
$widget->run();
return $widget;
}
else
throw new CException(Yii::t('yii','{controller} has an extra endWidget({id}) call in its view.',
array('{controller}'=>get_class($this),'{id}'=>$id)));
}
/**
* Begins recording a clip.
* This method is a shortcut to beginning {@link CClipWidget}.
* @param string $id the clip ID.
* @param array $properties initial property values for {@link CClipWidget}.
*/
public function beginClip($id,$properties=array())
{
$properties['id']=$id;
$this->beginWidget('CClipWidget',$properties);
}
/**
* Ends recording a clip.
* This method is an alias to {@link endWidget}.
*/
public function endClip()
{
$this->endWidget('CClipWidget');
}
/**
* Begins fragment caching.
* This method will display cached content if it is availabe.
* If not, it will start caching and would expect a {@link endCache()}
* call to end the cache and save the content into cache.
* A typical usage of fragment caching is as follows,
* <pre>
* if($this->beginCache($id))
* {
* // ...generate content here
* $this->endCache();
* }
* </pre>
* @param string $id a unique ID identifying the fragment to be cached.
* @param array $properties initial property values for {@link COutputCache}.
* @return boolean whether we need to generate content for caching. False if cached version is available.
* @see endCache
*/
public function beginCache($id,$properties=array())
{
$properties['id']=$id;
$cache=$this->beginWidget('COutputCache',$properties);
if($cache->getIsContentCached())
{
$this->endCache();
return false;
}
else
return true;
}
/**
* Ends fragment caching.
* This is an alias to {@link endWidget}.
* @see beginCache
*/
public function endCache()
{
$this->endWidget('COutputCache');
}
/**
* Begins the rendering of content that is to be decorated by the specified view.
* @param mixed $view the name of the view that will be used to decorate the content. The actual view script
* is resolved via {@link getViewFile}. If this parameter is null (default),
* the default layout will be used as the decorative view.
* Note that if the current controller does not belong to
* any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
* If the controller belongs to a module, the default layout refers to the module's
* {@link CWebModule::layout default layout}.
* @param array $data the variables (name=>value) to be extracted and made available in the decorative view.
* @see endContent
* @see CContentDecorator
*/
public function beginContent($view=null,$data=array())
{
$this->beginWidget('CContentDecorator',array('view'=>$view, 'data'=>$data));
}
/**
* Ends the rendering of content.
* @see beginContent
*/
public function endContent()
{
$this->endWidget('CContentDecorator');
}
}

View File

@@ -0,0 +1,112 @@
<?php
/**
* CCacheHttpSession class
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CCacheHttpSession implements a session component using cache as storage medium.
*
* The cache being used can be any cache application component implementing {@link ICache} interface.
* The ID of the cache application component is specified via {@link cacheID}, which defaults to 'cache'.
*
* Beware, by definition cache storage are volatile, which means the data stored on them
* may be swapped out and get lost. Therefore, you must make sure the cache used by this component
* is NOT volatile. If you want to use {@link CDbCache} as storage medium, use {@link CDbHttpSession}
* is a better choice.
*
* @property boolean $useCustomStorage Whether to use custom storage.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CCacheHttpSession extends CHttpSession
{
/**
* Prefix to the keys for storing cached data
*/
const CACHE_KEY_PREFIX='Yii.CCacheHttpSession.';
/**
* @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.)
*/
public $cacheID='cache';
/**
* @var ICache the cache component
*/
private $_cache;
/**
* Initializes the application component.
* This method overrides the parent implementation by checking if cache is available.
*/
public function init()
{
$this->_cache=Yii::app()->getComponent($this->cacheID);
if(!($this->_cache instanceof ICache))
throw new CException(Yii::t('yii','CCacheHttpSession.cacheID is invalid. Please make sure "{id}" refers to a valid cache application component.',
array('{id}'=>$this->cacheID)));
parent::init();
}
/**
* Returns a value indicating whether to use custom session storage.
* This method overrides the parent implementation and always returns true.
* @return boolean whether to use custom storage.
*/
public function getUseCustomStorage()
{
return true;
}
/**
* Session read handler.
* Do not call this method directly.
* @param string $id session ID
* @return string the session data
*/
public function readSession($id)
{
$data=$this->_cache->get($this->calculateKey($id));
return $data===false?'':$data;
}
/**
* Session write handler.
* Do not call this method directly.
* @param string $id session ID
* @param string $data session data
* @return boolean whether session write is successful
*/
public function writeSession($id,$data)
{
return $this->_cache->set($this->calculateKey($id),$data,$this->getTimeout());
}
/**
* Session destroy handler.
* Do not call this method directly.
* @param string $id session ID
* @return boolean whether session is destroyed successfully
*/
public function destroySession($id)
{
return $this->_cache->delete($this->calculateKey($id));
}
/**
* Generates a unique key used for storing session data in cache.
* @param string $id session variable name
* @return string a safe cache key associated with the session variable name
*/
protected function calculateKey($id)
{
return self::CACHE_KEY_PREFIX.$id;
}
}

View File

@@ -0,0 +1,841 @@
<?php
/**
* CClientScript 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/
*/
/**
* CClientScript manages JavaScript and CSS stylesheets for views.
*
* @property string $coreScriptUrl The base URL of all core javascript files.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CClientScript extends CApplicationComponent
{
/**
* The script is rendered in the head section right before the title element.
*/
const POS_HEAD=0;
/**
* The script is rendered at the beginning of the body section.
*/
const POS_BEGIN=1;
/**
* The script is rendered at the end of the body section.
*/
const POS_END=2;
/**
* The script is rendered inside window onload function.
*/
const POS_LOAD=3;
/**
* The body script is rendered inside a jQuery ready function.
*/
const POS_READY=4;
/**
* @var boolean whether JavaScript should be enabled. Defaults to true.
*/
public $enableJavaScript=true;
/**
* @var array the mapping between script file names and the corresponding script URLs.
* The array keys are script file names (without directory part) and the array values are the corresponding URLs.
* If an array value is false, the corresponding script file will not be rendered.
* If an array key is '*.js' or '*.css', the corresponding URL will replace all
* JavaScript files or CSS files, respectively.
*
* This property is mainly used to optimize the generated HTML pages
* by merging different scripts files into fewer and optimized script files.
*/
public $scriptMap=array();
/**
* @var array list of custom script packages (name=>package spec).
* This property keeps a list of named script packages, each of which can contain
* a set of CSS and/or JavaScript script files, and their dependent package names.
* By calling {@link registerPackage}, one can register a whole package of client
* scripts together with their dependent packages and render them in the HTML output.
*
* The array structure is as follows:
* <pre>
* array(
* 'package-name'=>array(
* 'basePath'=>'alias of the directory containing the script files',
* 'baseUrl'=>'base URL for the script files',
* 'js'=>array(list of js files relative to basePath/baseUrl),
* 'css'=>array(list of css files relative to basePath/baseUrl),
* 'depends'=>array(list of dependent packages),
* ),
* ......
* )
* </pre>
*
* The JS and CSS files listed are relative to 'basePath'.
* For example, if 'basePath' is 'application.assets', a script named 'comments.js'
* will refer to the file 'protected/assets/comments.js'.
*
* When a script is being rendered in HTML, it will be prefixed with 'baseUrl'.
* For example, if 'baseUrl' is '/assets', the 'comments.js' script will be rendered
* using URL '/assets/comments.js'.
*
* If 'baseUrl' does not start with '/', the relative URL of the application entry
* script will be inserted at the beginning. For example, if 'baseUrl' is 'assets'
* and the current application runs with the URL 'http://localhost/demo/index.php',
* then the 'comments.js' script will be rendered using URL '/demo/assets/comments.js'.
*
* If 'baseUrl' is not set, the script will be published by {@link CAssetManager}
* and the corresponding published URL will be used.
*
* When calling {@link registerPackage} to register a script package,
* this property will be checked first followed by {@link corePackages}.
* If a package is found, it will be registered for rendering later on.
*
* @since 1.1.7
*/
public $packages=array();
/**
* @var array list of core script packages (name=>package spec).
* Please refer to {@link packages} for details about package spec.
*
* By default, the core script packages are specified in 'framework/web/js/packages.php'.
* You may configure this property to customize the core script packages.
*
* When calling {@link registerPackage} to register a script package,
* {@link packages} will be checked first followed by this property.
* If a package is found, it will be registered for rendering later on.
*
* @since 1.1.7
*/
public $corePackages;
/**
* @var array the registered JavaScript code blocks (position, key => code)
*/
public $scripts=array();
/**
* @var array the registered CSS files (CSS URL=>media type).
*/
protected $cssFiles=array();
/**
* @var array the registered JavaScript files (position, key => URL)
*/
protected $scriptFiles=array();
/**
* @var array the registered head meta tags. Each array element represents an option array
* that will be passed as the last parameter of {@link CHtml::metaTag}.
* @since 1.1.3
*/
protected $metaTags=array();
/**
* @var array the registered head link tags. Each array element represents an option array
* that will be passed as the last parameter of {@link CHtml::linkTag}.
* @since 1.1.3
*/
protected $linkTags=array();
/**
* @var array the registered css code blocks (key => array(CSS code, media type)).
* @since 1.1.3
*/
protected $css=array();
/**
* @var boolean whether there are any javascript or css to be rendered.
* @since 1.1.7
*/
protected $hasScripts=false;
/**
* @var array the registered script packages (name => package spec)
* @since 1.1.7
*/
protected $coreScripts=array();
/**
* @var integer Where the scripts registered using {@link registerCoreScript} or {@link registerPackage}
* will be inserted in the page. This can be one of the CClientScript::POS_* constants.
* Defaults to CClientScript::POS_HEAD.
* @since 1.1.3
*/
public $coreScriptPosition=self::POS_HEAD;
/**
* @var integer Where the scripts registered using {@link registerScriptFile} will be inserted in the page.
* This can be one of the CClientScript::POS_* constants.
* Defaults to CClientScript::POS_HEAD.
* @since 1.1.11
*/
public $defaultScriptFilePosition=self::POS_HEAD;
/**
* @var integer Where the scripts registered using {@link registerScript} will be inserted in the page.
* This can be one of the CClientScript::POS_* constants.
* Defaults to CClientScript::POS_READY.
* @since 1.1.11
*/
public $defaultScriptPosition=self::POS_READY;
private $_baseUrl;
/**
* Cleans all registered scripts.
*/
public function reset()
{
$this->hasScripts=false;
$this->coreScripts=array();
$this->cssFiles=array();
$this->css=array();
$this->scriptFiles=array();
$this->scripts=array();
$this->metaTags=array();
$this->linkTags=array();
$this->recordCachingAction('clientScript','reset',array());
}
/**
* Renders the registered scripts.
* This method is called in {@link CController::render} when it finishes
* rendering content. CClientScript thus gets a chance to insert script tags
* at <code>head</code> and <code>body</code> sections in the HTML output.
* @param string $output the existing output that needs to be inserted with script tags
*/
public function render(&$output)
{
if(!$this->hasScripts)
return;
$this->renderCoreScripts();
if(!empty($this->scriptMap))
$this->remapScripts();
$this->unifyScripts();
$this->renderHead($output);
if($this->enableJavaScript)
{
$this->renderBodyBegin($output);
$this->renderBodyEnd($output);
}
}
/**
* Removes duplicated scripts from {@link scriptFiles}.
* @since 1.1.5
*/
protected function unifyScripts()
{
if(!$this->enableJavaScript)
return;
$map=array();
if(isset($this->scriptFiles[self::POS_HEAD]))
$map=$this->scriptFiles[self::POS_HEAD];
if(isset($this->scriptFiles[self::POS_BEGIN]))
{
foreach($this->scriptFiles[self::POS_BEGIN] as $scriptFile=>$scriptFileValue)
{
if(isset($map[$scriptFile]))
unset($this->scriptFiles[self::POS_BEGIN][$scriptFile]);
else
$map[$scriptFile]=true;
}
}
if(isset($this->scriptFiles[self::POS_END]))
{
foreach($this->scriptFiles[self::POS_END] as $key=>$scriptFile)
{
if(isset($map[$key]))
unset($this->scriptFiles[self::POS_END][$key]);
}
}
}
/**
* Uses {@link scriptMap} to re-map the registered scripts.
*/
protected function remapScripts()
{
$cssFiles=array();
foreach($this->cssFiles as $url=>$media)
{
$name=basename($url);
if(isset($this->scriptMap[$name]))
{
if($this->scriptMap[$name]!==false)
$cssFiles[$this->scriptMap[$name]]=$media;
}
elseif(isset($this->scriptMap['*.css']))
{
if($this->scriptMap['*.css']!==false)
$cssFiles[$this->scriptMap['*.css']]=$media;
}
else
$cssFiles[$url]=$media;
}
$this->cssFiles=$cssFiles;
$jsFiles=array();
foreach($this->scriptFiles as $position=>$scriptFiles)
{
$jsFiles[$position]=array();
foreach($scriptFiles as $scriptFile=>$scriptFileValue)
{
$name=basename($scriptFile);
if(isset($this->scriptMap[$name]))
{
if($this->scriptMap[$name]!==false)
$jsFiles[$position][$this->scriptMap[$name]]=$this->scriptMap[$name];
}
elseif(isset($this->scriptMap['*.js']))
{
if($this->scriptMap['*.js']!==false)
$jsFiles[$position][$this->scriptMap['*.js']]=$this->scriptMap['*.js'];
}
else
$jsFiles[$position][$scriptFile]=$scriptFileValue;
}
}
$this->scriptFiles=$jsFiles;
}
/**
* Composes script HTML block from the given script values,
* attempting to group scripts at single 'script' tag if possible.
* @param array $scripts script values to process.
* @return string HTML output
*/
protected function renderScriptBatch(array $scripts)
{
$html = '';
$scriptBatches = array();
foreach($scripts as $scriptValue)
{
if(is_array($scriptValue))
{
$scriptContent = $scriptValue['content'];
unset($scriptValue['content']);
$scriptHtmlOptions = $scriptValue;
}
else
{
$scriptContent = $scriptValue;
$scriptHtmlOptions = array();
}
$key=serialize(ksort($scriptHtmlOptions));
$scriptBatches[$key]['htmlOptions']=$scriptHtmlOptions;
$scriptBatches[$key]['scripts'][]=$scriptContent;
}
foreach($scriptBatches as $scriptBatch)
if(!empty($scriptBatch['scripts']))
$html.=CHtml::script(implode("\n",$scriptBatch['scripts']),$scriptBatch['htmlOptions'])."\n";
return $html;
}
/**
* Renders the specified core javascript library.
*/
public function renderCoreScripts()
{
if($this->coreScripts===null)
return;
$cssFiles=array();
$jsFiles=array();
foreach($this->coreScripts as $name=>$package)
{
$baseUrl=$this->getPackageBaseUrl($name);
if(!empty($package['js']))
{
foreach($package['js'] as $js)
$jsFiles[$baseUrl.'/'.$js]=$baseUrl.'/'.$js;
}
if(!empty($package['css']))
{
foreach($package['css'] as $css)
$cssFiles[$baseUrl.'/'.$css]='';
}
}
// merge in place
if($cssFiles!==array())
{
foreach($this->cssFiles as $cssFile=>$media)
$cssFiles[$cssFile]=$media;
$this->cssFiles=$cssFiles;
}
if($jsFiles!==array())
{
if(isset($this->scriptFiles[$this->coreScriptPosition]))
{
foreach($this->scriptFiles[$this->coreScriptPosition] as $url => $value)
$jsFiles[$url]=$value;
}
$this->scriptFiles[$this->coreScriptPosition]=$jsFiles;
}
}
/**
* Inserts the scripts in the head section.
* @param string $output the output to be inserted with scripts.
*/
public function renderHead(&$output)
{
$html='';
foreach($this->metaTags as $meta)
$html.=CHtml::metaTag($meta['content'],null,null,$meta)."\n";
foreach($this->linkTags as $link)
$html.=CHtml::linkTag(null,null,null,null,$link)."\n";
foreach($this->cssFiles as $url=>$media)
$html.=CHtml::cssFile($url,$media)."\n";
foreach($this->css as $css)
$html.=CHtml::css($css[0],$css[1])."\n";
if($this->enableJavaScript)
{
if(isset($this->scriptFiles[self::POS_HEAD]))
{
foreach($this->scriptFiles[self::POS_HEAD] as $scriptFileValueUrl=>$scriptFileValue)
{
if(is_array($scriptFileValue))
$html.=CHtml::scriptFile($scriptFileValueUrl,$scriptFileValue)."\n";
else
$html.=CHtml::scriptFile($scriptFileValueUrl)."\n";
}
}
if(isset($this->scripts[self::POS_HEAD]))
$html.=$this->renderScriptBatch($this->scripts[self::POS_HEAD]);
}
if($html!=='')
{
$count=0;
$output=preg_replace('/(<title\b[^>]*>|<\\/head\s*>)/is','<###head###>$1',$output,1,$count);
if($count)
$output=str_replace('<###head###>',$html,$output);
else
$output=$html.$output;
}
}
/**
* Inserts the scripts at the beginning of the body section.
* @param string $output the output to be inserted with scripts.
*/
public function renderBodyBegin(&$output)
{
$html='';
if(isset($this->scriptFiles[self::POS_BEGIN]))
{
foreach($this->scriptFiles[self::POS_BEGIN] as $scriptFileUrl=>$scriptFileValue)
{
if(is_array($scriptFileValue))
$html.=CHtml::scriptFile($scriptFileUrl,$scriptFileValue)."\n";
else
$html.=CHtml::scriptFile($scriptFileUrl)."\n";
}
}
if(isset($this->scripts[self::POS_BEGIN]))
$html.=$this->renderScriptBatch($this->scripts[self::POS_BEGIN]);
if($html!=='')
{
$count=0;
$output=preg_replace('/(<body\b[^>]*>)/is','$1<###begin###>',$output,1,$count);
if($count)
$output=str_replace('<###begin###>',$html,$output);
else
$output=$html.$output;
}
}
/**
* Inserts the scripts at the end of the body section.
* @param string $output the output to be inserted with scripts.
*/
public function renderBodyEnd(&$output)
{
if(!isset($this->scriptFiles[self::POS_END]) && !isset($this->scripts[self::POS_END])
&& !isset($this->scripts[self::POS_READY]) && !isset($this->scripts[self::POS_LOAD]))
return;
$fullPage=0;
$output=preg_replace('/(<\\/body\s*>)/is','<###end###>$1',$output,1,$fullPage);
$html='';
if(isset($this->scriptFiles[self::POS_END]))
{
foreach($this->scriptFiles[self::POS_END] as $scriptFileUrl=>$scriptFileValue)
{
if(is_array($scriptFileValue))
$html.=CHtml::scriptFile($scriptFileUrl,$scriptFileValue)."\n";
else
$html.=CHtml::scriptFile($scriptFileUrl)."\n";
}
}
$scripts=isset($this->scripts[self::POS_END]) ? $this->scripts[self::POS_END] : array();
if(isset($this->scripts[self::POS_READY]))
{
if($fullPage)
$scripts[]="jQuery(function($) {\n".implode("\n",$this->scripts[self::POS_READY])."\n});";
else
$scripts[]=implode("\n",$this->scripts[self::POS_READY]);
}
if(isset($this->scripts[self::POS_LOAD]))
{
if($fullPage)
$scripts[]="jQuery(window).on('load',function() {\n".implode("\n",$this->scripts[self::POS_LOAD])."\n});";
else
$scripts[]=implode("\n",$this->scripts[self::POS_LOAD]);
}
if(!empty($scripts))
$html.=$this->renderScriptBatch($scripts);
if($fullPage)
$output=str_replace('<###end###>',$html,$output);
else
$output=$output.$html;
}
/**
* Returns the base URL of all core javascript files.
* If the base URL is not explicitly set, this method will publish the whole directory
* 'framework/web/js/source' and return the corresponding URL.
* @return string the base URL of all core javascript files
*/
public function getCoreScriptUrl()
{
if($this->_baseUrl!==null)
return $this->_baseUrl;
else
return $this->_baseUrl=Yii::app()->getAssetManager()->publish(YII_PATH.'/web/js/source');
}
/**
* Sets the base URL of all core javascript files.
* This setter is provided in case when core javascript files are manually published
* to a pre-specified location. This may save asset publishing time for large-scale applications.
* @param string $value the base URL of all core javascript files.
*/
public function setCoreScriptUrl($value)
{
$this->_baseUrl=$value;
}
/**
* Returns the base URL for a registered package with the specified name.
* If needed, this method may publish the assets of the package and returns the published base URL.
* @param string $name the package name
* @return string the base URL for the named package. False is returned if the package is not registered yet.
* @see registerPackage
* @since 1.1.8
*/
public function getPackageBaseUrl($name)
{
if(!isset($this->coreScripts[$name]))
return false;
$package=$this->coreScripts[$name];
if(isset($package['baseUrl']))
{
$baseUrl=$package['baseUrl'];
if($baseUrl==='' || $baseUrl[0]!=='/' && strpos($baseUrl,'://')===false)
$baseUrl=Yii::app()->getRequest()->getBaseUrl().'/'.$baseUrl;
$baseUrl=rtrim($baseUrl,'/');
}
elseif(isset($package['basePath']))
$baseUrl=Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias($package['basePath']));
else
$baseUrl=$this->getCoreScriptUrl();
return $this->coreScripts[$name]['baseUrl']=$baseUrl;
}
/**
* Registers a script package that is listed in {@link packages}.
* This method is the same as {@link registerCoreScript}.
* @param string $name the name of the script package.
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
* @since 1.1.7
* @see renderCoreScript
*/
public function registerPackage($name)
{
return $this->registerCoreScript($name);
}
/**
* Registers a script package that is listed in {@link packages}.
* @param string $name the name of the script package.
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
* @see renderCoreScript
*/
public function registerCoreScript($name)
{
if(isset($this->coreScripts[$name]))
return $this;
if(isset($this->packages[$name]))
$package=$this->packages[$name];
else
{
if($this->corePackages===null)
$this->corePackages=require(YII_PATH.'/web/js/packages.php');
if(isset($this->corePackages[$name]))
$package=$this->corePackages[$name];
}
if(isset($package))
{
if(!empty($package['depends']))
{
foreach($package['depends'] as $p)
$this->registerCoreScript($p);
}
$this->coreScripts[$name]=$package;
$this->hasScripts=true;
$params=func_get_args();
$this->recordCachingAction('clientScript','registerCoreScript',$params);
}
return $this;
}
/**
* Registers a CSS file
* @param string $url URL of the CSS file
* @param string $media media that the CSS file should be applied to. If empty, it means all media types.
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerCssFile($url,$media='')
{
$this->hasScripts=true;
$this->cssFiles[$url]=$media;
$params=func_get_args();
$this->recordCachingAction('clientScript','registerCssFile',$params);
return $this;
}
/**
* Registers a piece of CSS code.
* @param string $id ID that uniquely identifies this piece of CSS code
* @param string $css the CSS code
* @param string $media media that the CSS code should be applied to. If empty, it means all media types.
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerCss($id,$css,$media='')
{
$this->hasScripts=true;
$this->css[$id]=array($css,$media);
$params=func_get_args();
$this->recordCachingAction('clientScript','registerCss',$params);
return $this;
}
/**
* Registers a javascript file.
* @param string $url URL of the javascript file
* @param integer $position the position of the JavaScript code. Valid values include the following:
* <ul>
* <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
* <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
* <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
* </ul>
* @param array $htmlOptions additional HTML attributes
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerScriptFile($url,$position=null,array $htmlOptions=array())
{
if($position===null)
$position=$this->defaultScriptFilePosition;
$this->hasScripts=true;
if(empty($htmlOptions))
$value=$url;
else
{
$value=$htmlOptions;
$value['src']=$url;
}
$this->scriptFiles[$position][$url]=$value;
$params=func_get_args();
$this->recordCachingAction('clientScript','registerScriptFile',$params);
return $this;
}
/**
* Registers a piece of javascript code.
* @param string $id ID that uniquely identifies this piece of JavaScript code
* @param string $script the javascript code
* @param integer $position the position of the JavaScript code. Valid values include the following:
* <ul>
* <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
* <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
* <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
* <li>CClientScript::POS_LOAD : the script is inserted in the window.onload() function.</li>
* <li>CClientScript::POS_READY : the script is inserted in the jQuery's ready function.</li>
* </ul>
* @param array $htmlOptions additional HTML attributes
* Note: HTML attributes are not allowed for script positions "CClientScript::POS_LOAD" and "CClientScript::POS_READY".
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerScript($id,$script,$position=null,array $htmlOptions=array())
{
if($position===null)
$position=$this->defaultScriptPosition;
$this->hasScripts=true;
if(empty($htmlOptions))
$scriptValue=$script;
else
{
if($position==self::POS_LOAD || $position==self::POS_READY)
throw new CException(Yii::t('yii','Script HTML options are not allowed for "CClientScript::POS_LOAD" and "CClientScript::POS_READY".'));
$scriptValue=$htmlOptions;
$scriptValue['content']=$script;
}
$this->scripts[$position][$id]=$scriptValue;
if($position===self::POS_READY || $position===self::POS_LOAD)
$this->registerCoreScript('jquery');
$params=func_get_args();
$this->recordCachingAction('clientScript','registerScript',$params);
return $this;
}
/**
* Registers a meta tag that will be inserted in the head section (right before the title element) of the resulting page.
*
* <b>Note:</b>
* Each call of this method will cause a rendering of new meta tag, even if their attributes are equal.
*
* <b>Example:</b>
* <pre>
* $cs->registerMetaTag('example', 'description', null, array('lang' => 'en'));
* $cs->registerMetaTag('beispiel', 'description', null, array('lang' => 'de'));
* </pre>
* @param string $content content attribute of the meta tag
* @param string $name name attribute of the meta tag. If null, the attribute will not be generated
* @param string $httpEquiv http-equiv attribute of the meta tag. If null, the attribute will not be generated
* @param array $options other options in name-value pairs (e.g. 'scheme', 'lang')
* @param string $id Optional id of the meta tag to avoid duplicates
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerMetaTag($content,$name=null,$httpEquiv=null,$options=array(),$id=null)
{
$this->hasScripts=true;
if($name!==null)
$options['name']=$name;
if($httpEquiv!==null)
$options['http-equiv']=$httpEquiv;
$options['content']=$content;
$this->metaTags[null===$id?count($this->metaTags):$id]=$options;
$params=func_get_args();
$this->recordCachingAction('clientScript','registerMetaTag',$params);
return $this;
}
/**
* Registers a link tag that will be inserted in the head section (right before the title element) of the resulting page.
* @param string $relation rel attribute of the link tag. If null, the attribute will not be generated.
* @param string $type type attribute of the link tag. If null, the attribute will not be generated.
* @param string $href href attribute of the link tag. If null, the attribute will not be generated.
* @param string $media media attribute of the link tag. If null, the attribute will not be generated.
* @param array $options other options in name-value pairs
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerLinkTag($relation=null,$type=null,$href=null,$media=null,$options=array())
{
$this->hasScripts=true;
if($relation!==null)
$options['rel']=$relation;
if($type!==null)
$options['type']=$type;
if($href!==null)
$options['href']=$href;
if($media!==null)
$options['media']=$media;
$this->linkTags[serialize($options)]=$options;
$params=func_get_args();
$this->recordCachingAction('clientScript','registerLinkTag',$params);
return $this;
}
/**
* Checks whether the CSS file has been registered.
* @param string $url URL of the CSS file
* @return boolean whether the CSS file is already registered
*/
public function isCssFileRegistered($url)
{
return isset($this->cssFiles[$url]);
}
/**
* Checks whether the CSS code has been registered.
* @param string $id ID that uniquely identifies the CSS code
* @return boolean whether the CSS code is already registered
*/
public function isCssRegistered($id)
{
return isset($this->css[$id]);
}
/**
* Checks whether the JavaScript file has been registered.
* @param string $url URL of the javascript file
* @param integer $position the position of the JavaScript code. Valid values include the following:
* <ul>
* <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
* <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
* <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
* </ul>
* @return boolean whether the javascript file is already registered
*/
public function isScriptFileRegistered($url,$position=self::POS_HEAD)
{
return isset($this->scriptFiles[$position][$url]);
}
/**
* Checks whether the JavaScript code has been registered.
* @param string $id ID that uniquely identifies the JavaScript code
* @param integer $position the position of the JavaScript code. Valid values include the following:
* <ul>
* <li>CClientScript::POS_HEAD : the script is inserted in the head section right before the title element.</li>
* <li>CClientScript::POS_BEGIN : the script is inserted at the beginning of the body section.</li>
* <li>CClientScript::POS_END : the script is inserted at the end of the body section.</li>
* <li>CClientScript::POS_LOAD : the script is inserted in the window.onload() function.</li>
* <li>CClientScript::POS_READY : the script is inserted in the jQuery's ready function.</li>
* </ul>
* @return boolean whether the javascript code is already registered
*/
public function isScriptRegistered($id,$position=self::POS_READY)
{
return isset($this->scripts[$position][$id]);
}
/**
* Records a method call when an output cache is in effect.
* This is a shortcut to Yii::app()->controller->recordCachingAction.
* In case when controller is absent, nothing is recorded.
* @param string $context a property name of the controller. It refers to an object
* whose method is being called. If empty it means the controller itself.
* @param string $method the method name
* @param array $params parameters passed to the method
* @see COutputCache
*/
protected function recordCachingAction($context,$method,$params)
{
if(($controller=Yii::app()->getController())!==null)
$controller->recordCachingAction($context,$method,$params);
}
/**
* Adds a package to packages list.
*
* @param string $name the name of the script package.
* @param array $definition the definition array of the script package,
* @see CClientScript::packages.
* @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.10).
*
* @since 1.1.9
*/
public function addPackage($name,$definition)
{
$this->packages[$name]=$definition;
return $this;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,238 @@
<?php
/**
* CDataProvider is a base class that implements the {@link IDataProvider} interface.
*
* Derived classes mainly need to implement three methods: {@link fetchData},
* {@link fetchKeys} and {@link calculateTotalItemCount}.
*
* @property string $id The unique ID that uniquely identifies the data provider among all data providers.
* @property CPagination $pagination The pagination object. If this is false, it means the pagination is disabled.
* @property CSort $sort The sorting object. If this is false, it means the sorting is disabled.
* @property array $data The list of data items currently available in this data provider.
* @property array $keys The list of key values corresponding to {@link data}. Each data item in {@link data}
* is uniquely identified by the corresponding key value in this array.
* @property integer $itemCount The number of data items in the current page.
* @property integer $totalItemCount Total number of possible data items.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.1
*/
abstract class CDataProvider extends CComponent implements IDataProvider
{
private $_id;
private $_data;
private $_keys;
private $_totalItemCount;
private $_sort;
private $_pagination;
/**
* Fetches the data from the persistent data storage.
* @return array list of data items
*/
abstract protected function fetchData();
/**
* Fetches the data item keys from the persistent data storage.
* @return array list of data item keys.
*/
abstract protected function fetchKeys();
/**
* Calculates the total number of data items.
* @return integer the total number of data items.
*/
abstract protected function calculateTotalItemCount();
/**
* Returns the ID that uniquely identifies the data provider.
* @return string the unique ID that uniquely identifies the data provider among all data providers.
*/
public function getId()
{
return $this->_id;
}
/**
* Sets the provider ID.
* @param string $value the unique ID that uniquely identifies the data provider among all data providers.
*/
public function setId($value)
{
$this->_id=$value;
}
/**
* Returns the pagination object.
* @param string $className the pagination object class name. Parameter is available since version 1.1.13.
* @return CPagination|false the pagination object. If this is false, it means the pagination is disabled.
*/
public function getPagination($className='CPagination')
{
if($this->_pagination===null)
{
$this->_pagination=new $className;
if(($id=$this->getId())!='')
$this->_pagination->pageVar=$id.'_page';
}
return $this->_pagination;
}
/**
* Sets the pagination for this data provider.
* @param mixed $value the pagination to be used by this data provider. This could be a {@link CPagination} object
* or an array used to configure the pagination object. If this is false, it means the pagination should be disabled.
*
* You can configure this property same way as a component:
* <pre>
* array(
* 'class' => 'MyPagination',
* 'pageSize' => 20,
* ),
* </pre>
*/
public function setPagination($value)
{
if(is_array($value))
{
if(isset($value['class']))
{
$pagination=$this->getPagination($value['class']);
unset($value['class']);
}
else
$pagination=$this->getPagination();
foreach($value as $k=>$v)
$pagination->$k=$v;
}
else
$this->_pagination=$value;
}
/**
* Returns the sort object.
* @param string $className the sorting object class name. Parameter is available since version 1.1.13.
* @return CSort|false the sorting object. If this is false, it means the sorting is disabled.
*/
public function getSort($className='CSort')
{
if($this->_sort===null)
{
$this->_sort=new $className;
if(($id=$this->getId())!='')
$this->_sort->sortVar=$id.'_sort';
}
return $this->_sort;
}
/**
* Sets the sorting for this data provider.
* @param mixed $value the sorting to be used by this data provider. This could be a {@link CSort} object
* or an array used to configure the sorting object. If this is false, it means the sorting should be disabled.
*
* You can configure this property same way as a component:
* <pre>
* array(
* 'class' => 'MySort',
* 'attributes' => array('name', 'weight'),
* ),
* </pre>
*/
public function setSort($value)
{
if(is_array($value))
{
if(isset($value['class']))
{
$sort=$this->getSort($value['class']);
unset($value['class']);
}
else
$sort=$this->getSort();
foreach($value as $k=>$v)
$sort->$k=$v;
}
else
$this->_sort=$value;
}
/**
* Returns the data items currently available.
* @param boolean $refresh whether the data should be re-fetched from persistent storage.
* @return array the list of data items currently available in this data provider.
*/
public function getData($refresh=false)
{
if($this->_data===null || $refresh)
$this->_data=$this->fetchData();
return $this->_data;
}
/**
* Sets the data items for this provider.
* @param array $value put the data items into this provider.
*/
public function setData($value)
{
$this->_data=$value;
}
/**
* Returns the key values associated with the data items.
* @param boolean $refresh whether the keys should be re-calculated.
* @return array the list of key values corresponding to {@link data}. Each data item in {@link data}
* is uniquely identified by the corresponding key value in this array.
*/
public function getKeys($refresh=false)
{
if($this->_keys===null || $refresh)
$this->_keys=$this->fetchKeys();
return $this->_keys;
}
/**
* Sets the data item keys for this provider.
* @param array $value put the data item keys into this provider.
*/
public function setKeys($value)
{
$this->_keys=$value;
}
/**
* Returns the number of data items in the current page.
* This is equivalent to <code>count($provider->getData())</code>.
* When {@link pagination} is set false, this returns the same value as {@link totalItemCount}.
* @param boolean $refresh whether the number of data items should be re-calculated.
* @return integer the number of data items in the current page.
*/
public function getItemCount($refresh=false)
{
return count($this->getData($refresh));
}
/**
* Returns the total number of data items.
* When {@link pagination} is set false, this returns the same value as {@link itemCount}.
* @param boolean $refresh whether the total number of data items should be re-calculated.
* @return integer total number of possible data items.
*/
public function getTotalItemCount($refresh=false)
{
if($this->_totalItemCount===null || $refresh)
$this->_totalItemCount=$this->calculateTotalItemCount();
return $this->_totalItemCount;
}
/**
* Sets the total number of data items.
* This method is provided in case when the total number cannot be determined by {@link calculateTotalItemCount}.
* @param integer $value the total number of data items.
* @since 1.1.1
*/
public function setTotalItemCount($value)
{
$this->_totalItemCount=$value;
}
}

View File

@@ -0,0 +1,155 @@
<?php
/**
* CDataProviderIterator class file.
*
* @author Charles Pick <charles.pick@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDataProviderIterator allows iteration over large data sets without holding the entire set in memory.
*
* CDataProviderIterator iterates over the results of a data provider, starting at the first page
* of results and ending at the last page. It is usually only suited for use with {@link CActiveDataProvider}.
*
* For example, the following code will iterate over all registered users (active record class User) without
* running out of memory, even if there are millions of users in the database.
* <pre>
* $dataProvider = new CActiveDataProvider("User");
* $iterator = new CDataProviderIterator($dataProvider);
* foreach($iterator as $user) {
* echo $user->name."\n";
* }
* </pre>
*
* @property CDataProvider $dataProvider the data provider to iterate over
* @property integer $totalItemCount the total number of items in the iterator
*
* @author Charles Pick <charles.pick@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @package system.web
* @since 1.1.13
*/
class CDataProviderIterator extends CComponent implements Iterator, Countable
{
private $_dataProvider;
private $_currentIndex=-1;
private $_currentPage=0;
private $_totalItemCount=-1;
private $_items;
/**
* Constructor.
* @param CDataProvider $dataProvider the data provider to iterate over
* @param integer $pageSize pageSize to use for iteration. This is the number of objects loaded into memory at the same time.
*/
public function __construct(CDataProvider $dataProvider, $pageSize=null)
{
$this->_dataProvider=$dataProvider;
$this->_totalItemCount=$dataProvider->getTotalItemCount();
if(($pagination=$this->_dataProvider->getPagination())===false)
$this->_dataProvider->setPagination($pagination=new CPagination());
if($pageSize!==null)
$pagination->setPageSize($pageSize);
}
/**
* Returns the data provider to iterate over
* @return CDataProvider the data provider to iterate over
*/
public function getDataProvider()
{
return $this->_dataProvider;
}
/**
* Gets the total number of items to iterate over
* @return integer the total number of items to iterate over
*/
public function getTotalItemCount()
{
return $this->_totalItemCount;
}
/**
* Loads a page of items
* @return array the items from the next page of results
*/
protected function loadPage()
{
$this->_dataProvider->getPagination()->setCurrentPage($this->_currentPage);
return $this->_items=$this->dataProvider->getData(true);
}
/**
* Gets the current item in the list.
* This method is required by the Iterator interface.
* @return mixed the current item in the list
*/
public function current()
{
return $this->_items[$this->_currentIndex];
}
/**
* Gets the key of the current item.
* This method is required by the Iterator interface.
* @return integer the key of the current item
*/
public function key()
{
$pageSize=$this->_dataProvider->getPagination()->getPageSize();
return $this->_currentPage*$pageSize+$this->_currentIndex;
}
/**
* Moves the pointer to the next item in the list.
* This method is required by the Iterator interface.
*/
public function next()
{
$pageSize=$this->_dataProvider->getPagination()->getPageSize();
$this->_currentIndex++;
if($this->_currentIndex >= $pageSize)
{
$this->_currentPage++;
$this->_currentIndex=0;
$this->loadPage();
}
}
/**
* Rewinds the iterator to the start of the list.
* This method is required by the Iterator interface.
*/
public function rewind()
{
$this->_currentIndex=0;
$this->_currentPage=0;
$this->loadPage();
}
/**
* Checks if the current position is valid or not.
* This method is required by the Iterator interface.
* @return boolean true if this index is valid
*/
public function valid()
{
return $this->key() < $this->_totalItemCount;
}
/**
* Gets the total number of items in the dataProvider.
* This method is required by the Countable interface.
* @return integer the total number of items
*/
public function count()
{
return $this->_totalItemCount;
}
}

View File

@@ -0,0 +1,298 @@
<?php
/**
* CDbHttpSession class
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CDbHttpSession extends {@link CHttpSession} by using database as session data storage.
*
* CDbHttpSession stores session data in a DB table named 'YiiSession'. The table name
* can be changed by setting {@link sessionTableName}. If the table does not exist,
* it will be automatically created if {@link autoCreateSessionTable} is set true.
*
* The following is the table structure:
*
* <pre>
* CREATE TABLE YiiSession
* (
* id CHAR(32) PRIMARY KEY,
* expire INTEGER,
* data BLOB
* )
* </pre>
* Where 'BLOB' refers to the BLOB-type of your preffered database.
*
* Note that if your session IDs are more than 32 characters (can be changed via
* session.hash_bits_per_character or session.hash_function) you should modify
* SQL schema accordingly.
*
* CDbHttpSession relies on {@link http://www.php.net/manual/en/ref.pdo.php PDO} to access database.
*
* By default, it will use an SQLite3 database named 'session-YiiVersion.db' under the application runtime directory.
* You can also specify {@link connectionID} so that it makes use of a DB application component to access database.
*
* When using CDbHttpSession in a production server, we recommend you pre-create the session DB table
* and set {@link autoCreateSessionTable} to be false. This will greatly improve the performance.
* You may also create a DB index for the 'expire' column in the session table to further improve the performance.
*
* @property boolean $useCustomStorage Whether to use custom storage.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CDbHttpSession extends CHttpSession
{
/**
* @var string the ID of a {@link CDbConnection} application component. If not set, a SQLite database
* will be automatically created and used. The SQLite database file is
* is <code>protected/runtime/session-YiiVersion.db</code>.
*/
public $connectionID;
/**
* @var string the name of the DB table to store session content.
* Note, if {@link autoCreateSessionTable} is false and you want to create the DB table manually by yourself,
* you need to make sure the DB table is of the following structure:
* <pre>
* (id CHAR(32) PRIMARY KEY, expire INTEGER, data BLOB)
* </pre>
* @see autoCreateSessionTable
*/
public $sessionTableName='YiiSession';
/**
* @var boolean whether the session DB table should be automatically created if not exists. Defaults to true.
* @see sessionTableName
*/
public $autoCreateSessionTable=true;
/**
* @var CDbConnection the DB connection instance
*/
private $_db;
/**
* Returns a value indicating whether to use custom session storage.
* This method overrides the parent implementation and always returns true.
* @return boolean whether to use custom storage.
*/
public function getUseCustomStorage()
{
return true;
}
/**
* Updates the current session id with a newly generated one.
* Please refer to {@link http://php.net/session_regenerate_id} for more details.
* @param boolean $deleteOldSession Whether to delete the old associated session file or not.
* @since 1.1.8
*/
public function regenerateID($deleteOldSession=false)
{
$oldID=session_id();
// if no session is started, there is nothing to regenerate
if(empty($oldID))
return;
parent::regenerateID(false);
$newID=session_id();
$db=$this->getDbConnection();
$row=$db->createCommand()
->select()
->from($this->sessionTableName)
->where('id=:id',array(':id'=>$oldID))
->queryRow();
if($row!==false)
{
if($deleteOldSession)
$db->createCommand()->update($this->sessionTableName,array(
'id'=>$newID
),'id=:oldID',array(':oldID'=>$oldID));
else
{
$row['id']=$newID;
$db->createCommand()->insert($this->sessionTableName, $row);
}
}
else
{
// shouldn't reach here normally
$db->createCommand()->insert($this->sessionTableName, array(
'id'=>$newID,
'expire'=>time()+$this->getTimeout(),
'data'=>'',
));
}
}
/**
* Creates the session DB table.
* @param CDbConnection $db the database connection
* @param string $tableName the name of the table to be created
*/
protected function createSessionTable($db,$tableName)
{
switch($db->getDriverName())
{
case 'mysql':
$blob='LONGBLOB';
break;
case 'pgsql':
$blob='BYTEA';
break;
case 'sqlsrv':
case 'mssql':
case 'dblib':
$blob='VARBINARY(MAX)';
break;
default:
$blob='BLOB';
break;
}
$db->createCommand()->createTable($tableName,array(
'id'=>'CHAR(32) PRIMARY KEY',
'expire'=>'integer',
'data'=>$blob,
));
}
/**
* @return CDbConnection the DB connection instance
* @throws CException if {@link connectionID} does not point to a valid application component.
*/
protected function getDbConnection()
{
if($this->_db!==null)
return $this->_db;
elseif(($id=$this->connectionID)!==null)
{
if(($this->_db=Yii::app()->getComponent($id)) instanceof CDbConnection)
return $this->_db;
else
throw new CException(Yii::t('yii','CDbHttpSession.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.',
array('{id}'=>$id)));
}
else
{
$dbFile=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'session-'.Yii::getVersion().'.db';
return $this->_db=new CDbConnection('sqlite:'.$dbFile);
}
}
/**
* Session open handler.
* Do not call this method directly.
* @param string $savePath session save path
* @param string $sessionName session name
* @return boolean whether session is opened successfully
*/
public function openSession($savePath,$sessionName)
{
if($this->autoCreateSessionTable)
{
$db=$this->getDbConnection();
$db->setActive(true);
try
{
$db->createCommand()->delete($this->sessionTableName,'expire<:expire',array(':expire'=>time()));
}
catch(Exception $e)
{
$this->createSessionTable($db,$this->sessionTableName);
}
}
return true;
}
/**
* Session read handler.
* Do not call this method directly.
* @param string $id session ID
* @return string the session data
*/
public function readSession($id)
{
$db=$this->getDbConnection();
if($db->getDriverName()=='sqlsrv' || $db->getDriverName()=='mssql' || $db->getDriverName()=='dblib')
$select='CONVERT(VARCHAR(MAX), data)';
else
$select='data';
$data=$db->createCommand()
->select($select)
->from($this->sessionTableName)
->where('expire>:expire AND id=:id',array(':expire'=>time(),':id'=>$id))
->queryScalar();
return $data===false?'':$data;
}
/**
* Session write handler.
* Do not call this method directly.
* @param string $id session ID
* @param string $data session data
* @return boolean whether session write is successful
*/
public function writeSession($id,$data)
{
// exception must be caught in session write handler
// http://us.php.net/manual/en/function.session-set-save-handler.php
try
{
$expire=time()+$this->getTimeout();
$db=$this->getDbConnection();
if($db->getDriverName()=='sqlsrv' || $db->getDriverName()=='mssql' || $db->getDriverName()=='dblib')
$data=new CDbExpression('CONVERT(VARBINARY(MAX), '.$db->quoteValue($data).')');
if($db->createCommand()->select('id')->from($this->sessionTableName)->where('id=:id',array(':id'=>$id))->queryScalar()===false)
$db->createCommand()->insert($this->sessionTableName,array(
'id'=>$id,
'data'=>$data,
'expire'=>$expire,
));
else
$db->createCommand()->update($this->sessionTableName,array(
'data'=>$data,
'expire'=>$expire
),'id=:id',array(':id'=>$id));
}
catch(Exception $e)
{
if(YII_DEBUG)
echo $e->getMessage();
// it is too late to log an error message here
return false;
}
return true;
}
/**
* Session destroy handler.
* Do not call this method directly.
* @param string $id session ID
* @return boolean whether session is destroyed successfully
*/
public function destroySession($id)
{
$this->getDbConnection()->createCommand()
->delete($this->sessionTableName,'id=:id',array(':id'=>$id));
return true;
}
/**
* Session GC (garbage collection) handler.
* Do not call this method directly.
* @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
* @return boolean whether session is GCed successfully
*/
public function gcSession($maxLifetime)
{
$this->getDbConnection()->createCommand()
->delete($this->sessionTableName,'expire<:expire',array(':expire'=>time()));
return true;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* CExtController 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/
*/
/**
* CExtController is the base class for controllers distributed as extension.
*
* The main purpose of CExtController is to redefine the {@link viewPath} property
* so that it points to the "views" subdirectory under the directory containing
* the controller class file.
*
* @property string $viewPath The directory containing the view files for this controller.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CExtController extends CController
{
private $_viewPath;
/**
* Returns the directory containing view files for this controller.
* This method overrides the parent implementation by specifying the view path
* to be the "views" subdirectory under the directory containing the controller
* class file.
* @return string the directory containing the view files for this controller.
*/
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 directory containing the view files for this controller.
*/
public function setViewPath($value)
{
$this->_viewPath=$value;
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* CFormModel 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/
*/
/**
* CFormModel represents a data model that collects HTML form inputs.
*
* Unlike {@link CActiveRecord}, the data collected by CFormModel are stored
* in memory only, instead of database.
*
* To collect user inputs, you may extend CFormModel and define the attributes
* whose values are to be collected from user inputs. You may override
* {@link rules()} to declare validation rules that should be applied to
* the attributes.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CFormModel extends CModel
{
private static $_names=array();
/**
* Constructor.
* @param string $scenario name of the scenario that this model is used in.
* See {@link CModel::scenario} on how scenario is used by models.
* @see getScenario
*/
public function __construct($scenario='')
{
$this->setScenario($scenario);
$this->init();
$this->attachBehaviors($this->behaviors());
$this->afterConstruct();
}
/**
* Initializes this model.
* This method is invoked in the constructor right after {@link scenario} is set.
* You may override this method to provide code that is needed to initialize the model (e.g. setting
* initial property values.)
*/
public function init()
{
}
/**
* Returns the list of attribute names.
* By default, this method returns all public properties of the class.
* You may override this method to change the default.
* @return array list of attribute names. Defaults to all public properties of the class.
*/
public function attributeNames()
{
$className=get_class($this);
if(!isset(self::$_names[$className]))
{
$class=new ReflectionClass(get_class($this));
$names=array();
foreach($class->getProperties() as $property)
{
$name=$property->getName();
if($property->isPublic() && !$property->isStatic())
$names[]=$name;
}
return self::$_names[$className]=$names;
}
else
return self::$_names[$className];
}
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* CHttpCookie 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/
*/
/**
* A CHttpCookie instance stores a single cookie, including the cookie name, value, domain, path, expire, and secure.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CHttpCookie extends CComponent
{
/**
* @var string name of the cookie
*/
public $name;
/**
* @var string value of the cookie
*/
public $value='';
/**
* @var string domain of the cookie
*/
public $domain='';
/**
* @var integer the timestamp at which the cookie expires. This is the server timestamp. Defaults to 0, meaning "until the browser is closed".
*/
public $expire=0;
/**
* @var string the path on the server in which the cookie will be available on. The default is '/'.
*/
public $path='/';
/**
* @var boolean whether cookie should be sent via secure connection
*/
public $secure=false;
/**
* @var boolean whether the cookie should be accessible only through the HTTP protocol.
* By setting this property to true, the cookie will not be accessible by scripting languages,
* such as JavaScript, which can effectly help to reduce identity theft through XSS attacks.
* Note, this property is only effective for PHP 5.2.0 or above.
*/
public $httpOnly=false;
/**
* Constructor.
* @param string $name name of this cookie
* @param string $value value of this cookie
* @param array $options the configuration array consisting of name-value pairs
* that are used to configure this cookie
*/
public function __construct($name,$value,$options=array())
{
$this->name=$name;
$this->value=$value;
$this->configure($options);
}
/**
* This method can be used to configure the CookieObject with an array
* Note: you cannot use this method to set the name and/or the value of the cookie
* @param array $options the configuration array consisting of name-value pairs
* that are used to configure this cookie
* @since 1.1.11
*/
public function configure($options=array())
{
foreach($options as $name=>$value)
{
if($name==='name'||$name==='value')
continue;
$this->$name=$value;
}
}
/**
* Magic method to use the cookie object as a string without having to call value property first.
* <code>
* $value = (string)$cookies['name'];
* </code>
* Note, that you still have to check if the cookie exists.
* @return string The value of the cookie. If the value property is null an empty string will be returned.
* @since 1.1.11
*/
public function __toString()
{
return (string)$this->value;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,572 @@
<?php
/**
* CHttpSession 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/
*/
/**
* CHttpSession provides session-level data management and the related configurations.
*
* To start the session, call {@link open()}; To complete and send out session data, call {@link close()};
* To destroy the session, call {@link destroy()}.
*
* If {@link autoStart} is set true, the session will be started automatically
* when the application component is initialized by the application.
*
* CHttpSession can be used like an array to set and get session data. For example,
* <pre>
* $session=new CHttpSession;
* $session->open();
* $value1=$session['name1']; // get session variable 'name1'
* $value2=$session['name2']; // get session variable 'name2'
* foreach($session as $name=>$value) // traverse all session variables
* $session['name3']=$value3; // set session variable 'name3'
* </pre>
*
* The following configurations are available for session:
* <ul>
* <li>{@link setSessionID sessionID};</li>
* <li>{@link setSessionName sessionName};</li>
* <li>{@link autoStart};</li>
* <li>{@link setSavePath savePath};</li>
* <li>{@link setCookieParams cookieParams};</li>
* <li>{@link setGCProbability gcProbability};</li>
* <li>{@link setCookieMode cookieMode};</li>
* <li>{@link setUseTransparentSessionID useTransparentSessionID};</li>
* <li>{@link setTimeout timeout}.</li>
* </ul>
* See the corresponding setter and getter documentation for more information.
* Note, these properties must be set before the session is started.
*
* CHttpSession can be extended to support customized session storage.
* Override {@link openSession}, {@link closeSession}, {@link readSession},
* {@link writeSession}, {@link destroySession} and {@link gcSession}
* and set {@link useCustomStorage} to true.
* Then, the session data will be stored and retrieved using the above methods.
*
* CHttpSession is a Web application component that can be accessed via
* {@link CWebApplication::getSession()}.
*
* @property boolean $useCustomStorage Whether to use custom storage.
* @property boolean $isStarted Whether the session has started.
* @property string $sessionID The current session ID.
* @property string $sessionName The current session name.
* @property string $savePath The current session save path, defaults to {@link http://php.net/session.save_path}.
* @property array $cookieParams The session cookie parameters.
* @property string $cookieMode How to use cookie to store session ID. Defaults to 'Allow'.
* @property float $gCProbability The probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
* @property boolean $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to false.
* @property integer $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds.
* @property CHttpSessionIterator $iterator An iterator for traversing the session variables.
* @property integer $count The number of session variables.
* @property array $keys The list of session variable names.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CHttpSession extends CApplicationComponent implements IteratorAggregate,ArrayAccess,Countable
{
/**
* @var boolean whether the session should be automatically started when the session application component is initialized, defaults to true.
*/
public $autoStart=true;
/**
* Initializes the application component.
* This method is required by IApplicationComponent and is invoked by application.
*/
public function init()
{
parent::init();
if($this->autoStart)
$this->open();
register_shutdown_function(array($this,'close'));
}
/**
* Returns a value indicating whether to use custom session storage.
* This method should be overriden to return true if custom session storage handler should be used.
* If returning true, make sure the methods {@link openSession}, {@link closeSession}, {@link readSession},
* {@link writeSession}, {@link destroySession}, and {@link gcSession} are overridden in child
* class, because they will be used as the callback handlers.
* The default implementation always return false.
* @return boolean whether to use custom storage.
*/
public function getUseCustomStorage()
{
return false;
}
/**
* Starts the session if it has not started yet.
*/
public function open()
{
if($this->getUseCustomStorage())
@session_set_save_handler(array($this,'openSession'),array($this,'closeSession'),array($this,'readSession'),array($this,'writeSession'),array($this,'destroySession'),array($this,'gcSession'));
@session_start();
if(YII_DEBUG && session_id()=='')
{
$message=Yii::t('yii','Failed to start session.');
if(function_exists('error_get_last'))
{
$error=error_get_last();
if(isset($error['message']))
$message=$error['message'];
}
Yii::log($message, CLogger::LEVEL_WARNING, 'system.web.CHttpSession');
}
}
/**
* Ends the current session and store session data.
*/
public function close()
{
if(session_id()!=='')
@session_write_close();
}
/**
* Frees all session variables and destroys all data registered to a session.
*/
public function destroy()
{
if(session_id()!=='')
{
@session_unset();
@session_destroy();
}
}
/**
* @return boolean whether the session has started
*/
public function getIsStarted()
{
return session_id()!=='';
}
/**
* @return string the current session ID
*/
public function getSessionID()
{
return session_id();
}
/**
* @param string $value the session ID for the current session
*/
public function setSessionID($value)
{
session_id($value);
}
/**
* Updates the current session id with a newly generated one .
* Please refer to {@link http://php.net/session_regenerate_id} for more details.
* @param boolean $deleteOldSession Whether to delete the old associated session file or not.
* @since 1.1.8
*/
public function regenerateID($deleteOldSession=false)
{
session_regenerate_id($deleteOldSession);
}
/**
* @return string the current session name
*/
public function getSessionName()
{
return session_name();
}
/**
* @param string $value the session name for the current session, must be an alphanumeric string, defaults to PHPSESSID
*/
public function setSessionName($value)
{
session_name($value);
}
/**
* @return string the current session save path, defaults to {@link http://php.net/session.save_path}.
*/
public function getSavePath()
{
return session_save_path();
}
/**
* @param string $value the current session save path
* @throws CException if the path is not a valid directory
*/
public function setSavePath($value)
{
if(is_dir($value))
session_save_path($value);
else
throw new CException(Yii::t('yii','CHttpSession.savePath "{path}" is not a valid directory.',
array('{path}'=>$value)));
}
/**
* @return array the session cookie parameters.
* @see http://us2.php.net/manual/en/function.session-get-cookie-params.php
*/
public function getCookieParams()
{
return session_get_cookie_params();
}
/**
* Sets the session cookie parameters.
* The effect of this method only lasts for the duration of the script.
* Call this method before the session starts.
* @param array $value cookie parameters, valid keys include: lifetime, path,
* domain, secure, httponly. Note that httponly is all lowercase.
* @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
*/
public function setCookieParams($value)
{
$data=session_get_cookie_params();
extract($data);
extract($value);
if(isset($httponly))
session_set_cookie_params($lifetime,$path,$domain,$secure,$httponly);
else
session_set_cookie_params($lifetime,$path,$domain,$secure);
}
/**
* @return string how to use cookie to store session ID. Defaults to 'Allow'.
*/
public function getCookieMode()
{
if(ini_get('session.use_cookies')==='0')
return 'none';
elseif(ini_get('session.use_only_cookies')==='0')
return 'allow';
else
return 'only';
}
/**
* @param string $value how to use cookie to store session ID. Valid values include 'none', 'allow' and 'only'.
*/
public function setCookieMode($value)
{
if($value==='none')
{
ini_set('session.use_cookies','0');
ini_set('session.use_only_cookies','0');
}
elseif($value==='allow')
{
ini_set('session.use_cookies','1');
ini_set('session.use_only_cookies','0');
}
elseif($value==='only')
{
ini_set('session.use_cookies','1');
ini_set('session.use_only_cookies','1');
}
else
throw new CException(Yii::t('yii','CHttpSession.cookieMode can only be "none", "allow" or "only".'));
}
/**
* @return float the probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
*/
public function getGCProbability()
{
return (float)(ini_get('session.gc_probability')/ini_get('session.gc_divisor')*100);
}
/**
* @param float $value the probability (percentage) that the gc (garbage collection) process is started on every session initialization.
* @throws CException if the value is beyond [0,100]
*/
public function setGCProbability($value)
{
if($value>=0 && $value<=100)
{
// percent * 21474837 / 2147483647 ≈ percent * 0.01
ini_set('session.gc_probability',floor($value*21474836.47));
ini_set('session.gc_divisor',2147483647);
}
else
throw new CException(Yii::t('yii','CHttpSession.gcProbability "{value}" is invalid. It must be a float between 0 and 100.',
array('{value}'=>$value)));
}
/**
* @return boolean whether transparent sid support is enabled or not, defaults to false.
*/
public function getUseTransparentSessionID()
{
return ini_get('session.use_trans_sid')==1;
}
/**
* @param boolean $value whether transparent sid support is enabled or not.
*/
public function setUseTransparentSessionID($value)
{
ini_set('session.use_trans_sid',$value?'1':'0');
}
/**
* @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds.
*/
public function getTimeout()
{
return (int)ini_get('session.gc_maxlifetime');
}
/**
* @param integer $value the number of seconds after which data will be seen as 'garbage' and cleaned up
*/
public function setTimeout($value)
{
ini_set('session.gc_maxlifetime',$value);
}
/**
* Session open handler.
* This method should be overridden if {@link useCustomStorage} is set true.
* Do not call this method directly.
* @param string $savePath session save path
* @param string $sessionName session name
* @return boolean whether session is opened successfully
*/
public function openSession($savePath,$sessionName)
{
return true;
}
/**
* Session close handler.
* This method should be overridden if {@link useCustomStorage} is set true.
* Do not call this method directly.
* @return boolean whether session is closed successfully
*/
public function closeSession()
{
return true;
}
/**
* Session read handler.
* This method should be overridden if {@link useCustomStorage} is set true.
* Do not call this method directly.
* @param string $id session ID
* @return string the session data
*/
public function readSession($id)
{
return '';
}
/**
* Session write handler.
* This method should be overridden if {@link useCustomStorage} is set true.
* Do not call this method directly.
* @param string $id session ID
* @param string $data session data
* @return boolean whether session write is successful
*/
public function writeSession($id,$data)
{
return true;
}
/**
* Session destroy handler.
* This method should be overridden if {@link useCustomStorage} is set true.
* Do not call this method directly.
* @param string $id session ID
* @return boolean whether session is destroyed successfully
*/
public function destroySession($id)
{
return true;
}
/**
* Session GC (garbage collection) handler.
* This method should be overridden if {@link useCustomStorage} is set true.
* Do not call this method directly.
* @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
* @return boolean whether session is GCed successfully
*/
public function gcSession($maxLifetime)
{
return true;
}
//------ The following methods enable CHttpSession to be CMap-like -----
/**
* Returns an iterator for traversing the session variables.
* This method is required by the interface IteratorAggregate.
* @return CHttpSessionIterator an iterator for traversing the session variables.
*/
public function getIterator()
{
return new CHttpSessionIterator;
}
/**
* Returns the number of items in the session.
* @return integer the number of session variables
*/
public function getCount()
{
return count($_SESSION);
}
/**
* Returns the number of items in the session.
* This method is required by Countable interface.
* @return integer number of items in the session.
*/
public function count()
{
return $this->getCount();
}
/**
* @return array the list of session variable names
*/
public function getKeys()
{
return array_keys($_SESSION);
}
/**
* Returns the session variable value with the session variable name.
* This method is very similar to {@link itemAt} and {@link offsetGet},
* except that it will return $defaultValue if the session variable does not exist.
* @param mixed $key the session variable name
* @param mixed $defaultValue the default value to be returned when the session variable does not exist.
* @return mixed the session variable value, or $defaultValue if the session variable does not exist.
* @since 1.1.2
*/
public function get($key,$defaultValue=null)
{
return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
}
/**
* Returns the session variable value with the session variable name.
* This method is exactly the same as {@link offsetGet}.
* @param mixed $key the session variable name
* @return mixed the session variable value, null if no such variable exists
*/
public function itemAt($key)
{
return isset($_SESSION[$key]) ? $_SESSION[$key] : null;
}
/**
* Adds a session variable.
* Note, if the specified name already exists, the old value will be removed first.
* @param mixed $key session variable name
* @param mixed $value session variable value
*/
public function add($key,$value)
{
$_SESSION[$key]=$value;
}
/**
* Removes a session variable.
* @param mixed $key the name of the session variable to be removed
* @return mixed the removed value, null if no such session variable.
*/
public function remove($key)
{
if(isset($_SESSION[$key]))
{
$value=$_SESSION[$key];
unset($_SESSION[$key]);
return $value;
}
else
return null;
}
/**
* Removes all session variables
*/
public function clear()
{
foreach(array_keys($_SESSION) as $key)
unset($_SESSION[$key]);
}
/**
* @param mixed $key session variable name
* @return boolean whether there is the named session variable
*/
public function contains($key)
{
return isset($_SESSION[$key]);
}
/**
* @return array the list of all session variables in array
*/
public function toArray()
{
return $_SESSION;
}
/**
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to check on
* @return boolean
*/
public function offsetExists($offset)
{
return isset($_SESSION[$offset]);
}
/**
* This method is required by the interface ArrayAccess.
* @param integer $offset the offset to retrieve element.
* @return mixed the element at the offset, null if no element is found at the offset
*/
public function offsetGet($offset)
{
return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
}
/**
* This method is required by the interface ArrayAccess.
* @param integer $offset the offset to set element
* @param mixed $item the element value
*/
public function offsetSet($offset,$item)
{
$_SESSION[$offset]=$item;
}
/**
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to unset element
*/
public function offsetUnset($offset)
{
unset($_SESSION[$offset]);
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* CHttpSessionIterator 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/
*/
/**
* CHttpSessionIterator implements an iterator for {@link CHttpSession}.
*
* It allows CHttpSession to return a new iterator for traversing the session variables.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CHttpSessionIterator implements Iterator
{
/**
* @var array list of keys in the map
*/
private $_keys;
/**
* @var mixed current key
*/
private $_key;
/**
* Constructor.
* @param array the data to be iterated through
*/
public function __construct()
{
$this->_keys=array_keys($_SESSION);
}
/**
* Rewinds internal array pointer.
* This method is required by the interface Iterator.
*/
public function rewind()
{
$this->_key=reset($this->_keys);
}
/**
* Returns the key of the current array element.
* This method is required by the interface Iterator.
* @return mixed the key of the current array element
*/
public function key()
{
return $this->_key;
}
/**
* Returns the current array element.
* This method is required by the interface Iterator.
* @return mixed the current array element
*/
public function current()
{
return isset($_SESSION[$this->_key])?$_SESSION[$this->_key]:null;
}
/**
* Moves the internal pointer to the next array element.
* This method is required by the interface Iterator.
*/
public function next()
{
do
{
$this->_key=next($this->_keys);
}
while(!isset($_SESSION[$this->_key]) && $this->_key!==false);
}
/**
* Returns whether there is an element at current position.
* This method is required by the interface Iterator.
* @return boolean
*/
public function valid()
{
return $this->_key!==false;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* COutputEvent 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/
*/
/**
* COutputEvent represents the parameter for events related with output handling.
*
* An event handler may retrieve the captured {@link output} for further processing.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class COutputEvent extends CEvent
{
/**
* @var string the output to be processed. The processed output should be stored back to this property.
*/
public $output;
/**
* Constructor.
* @param mixed $sender sender of the event
* @param string $output the output to be processed
*/
public function __construct($sender,$output)
{
parent::__construct($sender);
$this->output=$output;
}
}

View File

@@ -0,0 +1,240 @@
<?php
/**
* CPagination 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/
*/
/**
* CPagination represents information relevant to pagination.
*
* When data needs to be rendered in multiple pages, we can use CPagination to
* represent information such as {@link getItemCount total item count},
* {@link getPageSize page size}, {@link getCurrentPage current page}, etc.
* These information can be passed to {@link CBasePager pagers} to render
* pagination buttons or links.
*
* Example:
*
* Controller action:
* <pre>
* function actionIndex(){
* $criteria=new CDbCriteria();
* $count=Article::model()->count($criteria);
* $pages=new CPagination($count);
*
* // results per page
* $pages->pageSize=10;
* $pages->applyLimit($criteria);
* $models=Article::model()->findAll($criteria);
*
* $this->render('index', array(
* 'models' => $models,
* 'pages' => $pages
* ));
* }
* </pre>
*
* View:
* <pre>
* <?php foreach($models as $model): ?>
* // display a model
* <?php endforeach; ?>
*
* // display pagination
* <?php $this->widget('CLinkPager', array(
* 'pages' => $pages,
* )) ?>
* </pre>
*
* @property integer $pageSize Number of items in each page. Defaults to 10.
* @property integer $itemCount Total number of items. Defaults to 0.
* @property integer $pageCount Number of pages.
* @property integer $currentPage The zero-based index of the current page. Defaults to 0.
* @property integer $offset The offset of the data. This may be used to set the
* OFFSET value for a SQL statement for fetching the current page of data.
* @property integer $limit The limit of the data. This may be used to set the
* LIMIT value for a SQL statement for fetching the current page of data.
* This returns the same value as {@link pageSize}.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CPagination extends CComponent
{
/**
* The default page size.
*/
const DEFAULT_PAGE_SIZE=10;
/**
* @var string name of the GET variable storing the current page index. Defaults to 'page'.
*/
public $pageVar='page';
/**
* @var string the route (controller ID and action ID) for displaying the paged contents.
* Defaults to empty string, meaning using the current route.
*/
public $route='';
/**
* @var array of parameters (name=>value) that should be used instead of GET when generating pagination URLs.
* Defaults to null, meaning using the currently available GET parameters.
*/
public $params;
/**
* @var boolean whether to ensure {@link currentPage} is returning a valid page number.
* When this property is true, the value returned by {@link currentPage} will always be between
* 0 and ({@link pageCount}-1). Because {@link pageCount} relies on the correct value of {@link itemCount},
* it means you must have knowledge about the total number of data items when you want to access {@link currentPage}.
* This is fine for SQL-based queries, but may not be feasible for other kinds of queries (e.g. MongoDB).
* In those cases, you may set this property to be false to skip the validation (you may need to validate yourself then).
* Defaults to true.
* @since 1.1.4
*/
public $validateCurrentPage=true;
private $_pageSize=self::DEFAULT_PAGE_SIZE;
private $_itemCount=0;
private $_currentPage;
/**
* Constructor.
* @param integer $itemCount total number of items.
*/
public function __construct($itemCount=0)
{
$this->setItemCount($itemCount);
}
/**
* @return integer number of items in each page. Defaults to 10.
*/
public function getPageSize()
{
return $this->_pageSize;
}
/**
* @param integer $value number of items in each page
*/
public function setPageSize($value)
{
if(($this->_pageSize=$value)<=0)
$this->_pageSize=self::DEFAULT_PAGE_SIZE;
}
/**
* @return integer total number of items. Defaults to 0.
*/
public function getItemCount()
{
return $this->_itemCount;
}
/**
* @param integer $value total number of items.
*/
public function setItemCount($value)
{
if(($this->_itemCount=$value)<0)
$this->_itemCount=0;
}
/**
* @return integer number of pages
*/
public function getPageCount()
{
return (int)(($this->_itemCount+$this->_pageSize-1)/$this->_pageSize);
}
/**
* @param boolean $recalculate whether to recalculate the current page based on the page size and item count.
* @return integer the zero-based index of the current page. Defaults to 0.
*/
public function getCurrentPage($recalculate=true)
{
if($this->_currentPage===null || $recalculate)
{
if(isset($_GET[$this->pageVar]))
{
$this->_currentPage=(int)$_GET[$this->pageVar]-1;
if($this->validateCurrentPage)
{
$pageCount=$this->getPageCount();
if($this->_currentPage>=$pageCount)
$this->_currentPage=$pageCount-1;
}
if($this->_currentPage<0)
$this->_currentPage=0;
}
else
$this->_currentPage=0;
}
return $this->_currentPage;
}
/**
* @param integer $value the zero-based index of the current page.
*/
public function setCurrentPage($value)
{
$this->_currentPage=$value;
$_GET[$this->pageVar]=$value+1;
}
/**
* Creates the URL suitable for pagination.
* This method is mainly called by pagers when creating URLs used to
* perform pagination. The default implementation is to call
* the controller's createUrl method with the page information.
* You may override this method if your URL scheme is not the same as
* the one supported by the controller's createUrl method.
* @param CController $controller the controller that will create the actual URL
* @param integer $page the page that the URL should point to. This is a zero-based index.
* @return string the created URL
*/
public function createPageUrl($controller,$page)
{
$params=$this->params===null ? $_GET : $this->params;
if($page>0) // page 0 is the default
$params[$this->pageVar]=$page+1;
else
unset($params[$this->pageVar]);
return $controller->createUrl($this->route,$params);
}
/**
* Applies LIMIT and OFFSET to the specified query criteria.
* @param CDbCriteria $criteria the query criteria that should be applied with the limit
*/
public function applyLimit($criteria)
{
$criteria->limit=$this->getLimit();
$criteria->offset=$this->getOffset();
}
/**
* @return integer the offset of the data. This may be used to set the
* OFFSET value for a SQL statement for fetching the current page of data.
* @since 1.1.0
*/
public function getOffset()
{
return $this->getCurrentPage()*$this->getPageSize();
}
/**
* @return integer the limit of the data. This may be used to set the
* LIMIT value for a SQL statement for fetching the current page of data.
* This returns the same value as {@link pageSize}.
* @since 1.1.0
*/
public function getLimit()
{
return $this->getPageSize();
}
}

472
framework/web/CSort.php Normal file
View File

@@ -0,0 +1,472 @@
<?php
/**
* CSort 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/
*/
/**
* CSort represents information relevant to sorting.
*
* When data needs to be sorted according to one or several attributes,
* we can use CSort to represent the sorting information and generate
* appropriate hyperlinks that can lead to sort actions.
*
* CSort is designed to be used together with {@link CActiveRecord}.
* When creating a CSort instance, you need to specify {@link modelClass}.
* You can use CSort to generate hyperlinks by calling {@link link}.
* You can also use CSort to modify a {@link CDbCriteria} instance by calling {@link applyOrder} so that
* it can cause the query results to be sorted according to the specified
* attributes.
*
* In order to prevent SQL injection attacks, CSort ensures that only valid model attributes
* can be sorted. This is determined based on {@link modelClass} and {@link attributes}.
* When {@link attributes} is not set, all attributes belonging to {@link modelClass}
* can be sorted. When {@link attributes} is set, only those attributes declared in the property
* can be sorted.
*
* By configuring {@link attributes}, one can perform more complex sorts that may
* consist of things like compound attributes (e.g. sort based on the combination of
* first name and last name of users).
*
* The property {@link attributes} should be an array of key-value pairs, where the keys
* represent the attribute names, while the values represent the virtual attribute definitions.
* For more details, please check the documentation about {@link attributes}.
*
* @property string $orderBy The order-by columns represented by this sort object.
* This can be put in the ORDER BY clause of a SQL statement.
* @property array $directions Sort directions indexed by attribute names.
* The sort direction. Can be either CSort::SORT_ASC for ascending order or
* CSort::SORT_DESC for descending order.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
*/
class CSort extends CComponent
{
/**
* Sort ascending
* @since 1.1.10
*/
const SORT_ASC = false;
/**
* Sort descending
* @since 1.1.10
*/
const SORT_DESC = true;
/**
* @var boolean whether the sorting can be applied to multiple attributes simultaneously.
* Defaults to false, which means each time the data can only be sorted by one attribute.
*/
public $multiSort=false;
/**
* @var string the name of the model class whose attributes can be sorted.
* The model class must be a child class of {@link CActiveRecord}.
*/
public $modelClass;
/**
* @var array list of attributes that are allowed to be sorted.
* For example, array('user_id','create_time') would specify that only 'user_id'
* and 'create_time' of the model {@link modelClass} can be sorted.
* By default, this property is an empty array, which means all attributes in
* {@link modelClass} are allowed to be sorted.
*
* This property can also be used to specify complex sorting. To do so,
* a virtual attribute can be declared in terms of a key-value pair in the array.
* The key refers to the name of the virtual attribute that may appear in the sort request,
* while the value specifies the definition of the virtual attribute.
*
* In the simple case, a key-value pair can be like <code>'user'=>'user_id'</code>
* where 'user' is the name of the virtual attribute while 'user_id' means the virtual
* attribute is the 'user_id' attribute in the {@link modelClass}.
*
* A more flexible way is to specify the key-value pair as
* <pre>
* 'user'=>array(
* 'asc'=>'first_name, last_name',
* 'desc'=>'first_name DESC, last_name DESC',
* 'label'=>'Name'
* )
* </pre>
* where 'user' is the name of the virtual attribute that specifies the full name of user
* (a compound attribute consisting of first name and last name of user). In this case,
* we have to use an array to define the virtual attribute with three elements: 'asc',
* 'desc' and 'label'.
*
* The above approach can also be used to declare virtual attributes that consist of relational
* attributes. For example,
* <pre>
* 'price'=>array(
* 'asc'=>'item.price',
* 'desc'=>'item.price DESC',
* 'label'=>'Item Price'
* )
* </pre>
*
* Note, the attribute name should not contain '-' or '.' characters because
* they are used as {@link separators}.
*
* Starting from version 1.1.3, an additional option named 'default' can be used in the virtual attribute
* declaration. This option specifies whether an attribute should be sorted in ascending or descending
* order upon user clicking the corresponding sort hyperlink if it is not currently sorted. The valid
* option values include 'asc' (default) and 'desc'. For example,
* <pre>
* 'price'=>array(
* 'asc'=>'item.price',
* 'desc'=>'item.price DESC',
* 'label'=>'Item Price',
* 'default'=>'desc',
* )
* </pre>
*
* Also starting from version 1.1.3, you can include a star ('*') element in this property so that
* all model attributes are available for sorting, in addition to those virtual attributes. For example,
* <pre>
* 'attributes'=>array(
* 'price'=>array(
* 'asc'=>'item.price',
* 'desc'=>'item.price DESC',
* 'label'=>'Item Price',
* 'default'=>'desc',
* ),
* '*',
* )
* </pre>
* Note that when a name appears as both a model attribute and a virtual attribute, the position of
* the star element in the array determines which one takes precedence. In particular, if the star
* element is the first element in the array, the model attribute takes precedence; and if the star
* element is the last one, the virtual attribute takes precedence.
*/
public $attributes=array();
/**
* @var string the name of the GET parameter that specifies which attributes to be sorted
* in which direction. Defaults to 'sort'.
*/
public $sortVar='sort';
/**
* @var string the tag appeared in the GET parameter that indicates the attribute should be sorted
* in descending order. Defaults to 'desc'.
*/
public $descTag='desc';
/**
* @var mixed the default order that should be applied to the query criteria when
* the current request does not specify any sort. For example, 'name, create_time DESC' or
* 'UPPER(name)'.
*
* Starting from version 1.1.3, you can also specify the default order using an array.
* The array keys could be attribute names or virtual attribute names as declared in {@link attributes},
* and the array values indicate whether the sorting of the corresponding attributes should
* be in descending order. For example,
* <pre>
* 'defaultOrder'=>array(
* 'price'=>CSort::SORT_DESC,
* )
* </pre>
* `SORT_DESC` and `SORT_ASC` are available since 1.1.10. In earlier Yii versions you should use
* `true` and `false` respectively.
*
* Please note when using array to specify the default order, the corresponding attributes
* will be put into {@link directions} and thus affect how the sort links are rendered
* (e.g. an arrow may be displayed next to the currently active sort link).
*/
public $defaultOrder;
/**
* @var string the route (controller ID and action ID) for generating the sorted contents.
* Defaults to empty string, meaning using the currently requested route.
*/
public $route='';
/**
* @var array separators used in the generated URL. This must be an array consisting of
* two elements. The first element specifies the character separating different
* attributes, while the second element specifies the character separating attribute name
* and the corresponding sort direction. Defaults to array('-','.').
*/
public $separators=array('-','.');
/**
* @var array the additional GET parameters (name=>value) that should be used when generating sort URLs.
* Defaults to null, meaning using the currently available GET parameters.
*/
public $params;
private $_directions;
/**
* Constructor.
* @param string $modelClass the class name of data models that need to be sorted.
* This should be a child class of {@link CActiveRecord}.
*/
public function __construct($modelClass=null)
{
$this->modelClass=$modelClass;
}
/**
* Modifies the query criteria by changing its {@link CDbCriteria::order} property.
* This method will use {@link directions} to determine which columns need to be sorted.
* They will be put in the ORDER BY clause. If the criteria already has non-empty {@link CDbCriteria::order} value,
* the new value will be appended to it.
* @param CDbCriteria $criteria the query criteria
*/
public function applyOrder($criteria)
{
$order=$this->getOrderBy($criteria);
if(!empty($order))
{
if(!empty($criteria->order))
$criteria->order.=', ';
$criteria->order.=$order;
}
}
/**
* @param CDbCriteria $criteria the query criteria
* @return string the order-by columns represented by this sort object.
* This can be put in the ORDER BY clause of a SQL statement.
* @since 1.1.0
*/
public function getOrderBy($criteria=null)
{
$directions=$this->getDirections();
if(empty($directions))
return is_string($this->defaultOrder) ? $this->defaultOrder : '';
else
{
if($this->modelClass!==null)
$schema=$this->getModel($this->modelClass)->getDbConnection()->getSchema();
$orders=array();
foreach($directions as $attribute=>$descending)
{
$definition=$this->resolveAttribute($attribute);
if(is_array($definition))
{
if($descending)
$orders[]=isset($definition['desc']) ? $definition['desc'] : $attribute.' DESC';
else
$orders[]=isset($definition['asc']) ? $definition['asc'] : $attribute;
}
elseif($definition!==false)
{
$attribute=$definition;
if(isset($schema))
{
if(($pos=strpos($attribute,'.'))!==false)
$attribute=$schema->quoteTableName(substr($attribute,0,$pos)).'.'.$schema->quoteColumnName(substr($attribute,$pos+1));
else
$attribute=($criteria===null || $criteria->alias===null ? $this->getModel($this->modelClass)->getTableAlias(true) : $schema->quoteTableName($criteria->alias)).'.'.$schema->quoteColumnName($attribute);
}
$orders[]=$descending?$attribute.' DESC':$attribute;
}
}
return implode(', ',$orders);
}
}
/**
* Generates a hyperlink that can be clicked to cause sorting.
* @param string $attribute the attribute name. This must be the actual attribute name, not alias.
* If it is an attribute of a related AR object, the name should be prefixed with
* the relation name (e.g. 'author.name', where 'author' is the relation name).
* @param string $label the link label. If null, the label will be determined according
* to the attribute (see {@link resolveLabel}).
* @param array $htmlOptions additional HTML attributes for the hyperlink tag
* @return string the generated hyperlink
*/
public function link($attribute,$label=null,$htmlOptions=array())
{
if($label===null)
$label=$this->resolveLabel($attribute);
if(($definition=$this->resolveAttribute($attribute))===false)
return $label;
$directions=$this->getDirections();
if(isset($directions[$attribute]))
{
$class=$directions[$attribute] ? 'desc' : 'asc';
if(isset($htmlOptions['class']))
$htmlOptions['class'].=' '.$class;
else
$htmlOptions['class']=$class;
$descending=!$directions[$attribute];
unset($directions[$attribute]);
}
elseif(is_array($definition) && isset($definition['default']))
$descending=$definition['default']==='desc';
else
$descending=false;
if($this->multiSort)
$directions=array_merge(array($attribute=>$descending),$directions);
else
$directions=array($attribute=>$descending);
$url=$this->createUrl(Yii::app()->getController(),$directions);
return $this->createLink($attribute,$label,$url,$htmlOptions);
}
/**
* Resolves the attribute label for the specified attribute.
* This will invoke {@link CActiveRecord::getAttributeLabel} to determine what label to use.
* If the attribute refers to a virtual attribute declared in {@link attributes},
* then the label given in the {@link attributes} will be returned instead.
* @param string $attribute the attribute name.
* @return string the attribute label
*/
public function resolveLabel($attribute)
{
$definition=$this->resolveAttribute($attribute);
if(is_array($definition))
{
if(isset($definition['label']))
return $definition['label'];
}
elseif(is_string($definition))
$attribute=$definition;
if($this->modelClass!==null)
return $this->getModel($this->modelClass)->getAttributeLabel($attribute);
else
return $attribute;
}
/**
* Returns the currently requested sort information.
* @return array sort directions indexed by attribute names.
* Sort direction can be either CSort::SORT_ASC for ascending order or
* CSort::SORT_DESC for descending order.
*/
public function getDirections()
{
if($this->_directions===null)
{
$this->_directions=array();
if(isset($_GET[$this->sortVar]) && is_string($_GET[$this->sortVar]))
{
$attributes=explode($this->separators[0],$_GET[$this->sortVar]);
foreach($attributes as $attribute)
{
if(($pos=strrpos($attribute,$this->separators[1]))!==false)
{
$descending=substr($attribute,$pos+1)===$this->descTag;
if($descending)
$attribute=substr($attribute,0,$pos);
}
else
$descending=false;
if(($this->resolveAttribute($attribute))!==false)
{
$this->_directions[$attribute]=$descending;
if(!$this->multiSort)
return $this->_directions;
}
}
}
if($this->_directions===array() && is_array($this->defaultOrder))
$this->_directions=$this->defaultOrder;
}
return $this->_directions;
}
/**
* Returns the sort direction of the specified attribute in the current request.
* @param string $attribute the attribute name
* @return mixed Sort direction of the attribute. Can be either CSort::SORT_ASC
* for ascending order or CSort::SORT_DESC for descending order. Value is null
* if the attribute doesn't need to be sorted.
*/
public function getDirection($attribute)
{
$this->getDirections();
return isset($this->_directions[$attribute]) ? $this->_directions[$attribute] : null;
}
/**
* Creates a URL that can lead to generating sorted data.
* @param CController $controller the controller that will be used to create the URL.
* @param array $directions the sort directions indexed by attribute names.
* The sort direction can be either CSort::SORT_ASC for ascending order or
* CSort::SORT_DESC for descending order.
* @return string the URL for sorting
*/
public function createUrl($controller,$directions)
{
$sorts=array();
foreach($directions as $attribute=>$descending)
$sorts[]=$descending ? $attribute.$this->separators[1].$this->descTag : $attribute;
$params=$this->params===null ? $_GET : $this->params;
$params[$this->sortVar]=implode($this->separators[0],$sorts);
return $controller->createUrl($this->route,$params);
}
/**
* Returns the real definition of an attribute given its name.
*
* The resolution is based on {@link attributes} and {@link CActiveRecord::attributeNames}.
* <ul>
* <li>When {@link attributes} is an empty array, if the name refers to an attribute of {@link modelClass},
* then the name is returned back.</li>
* <li>When {@link attributes} is not empty, if the name refers to an attribute declared in {@link attributes},
* then the corresponding virtual attribute definition is returned. Starting from version 1.1.3, if {@link attributes}
* contains a star ('*') element, the name will also be used to match against all model attributes.</li>
* <li>In all other cases, false is returned, meaning the name does not refer to a valid attribute.</li>
* </ul>
* @param string $attribute the attribute name that the user requests to sort on
* @return mixed the attribute name or the virtual attribute definition. False if the attribute cannot be sorted.
*/
public function resolveAttribute($attribute)
{
if($this->attributes!==array())
$attributes=$this->attributes;
elseif($this->modelClass!==null)
$attributes=$this->getModel($this->modelClass)->attributeNames();
else
return false;
foreach($attributes as $name=>$definition)
{
if(is_string($name))
{
if($name===$attribute)
return $definition;
}
elseif($definition==='*')
{
if($this->modelClass!==null && $this->getModel($this->modelClass)->hasAttribute($attribute))
return $attribute;
}
elseif($definition===$attribute)
return $attribute;
}
return false;
}
/**
* Given active record class name returns new model instance.
*
* @param string $className active record class name.
* @return CActiveRecord active record model instance.
*
* @since 1.1.14
*/
protected function getModel($className)
{
return CActiveRecord::model($className);
}
/**
* Creates a hyperlink based on the given label and URL.
* You may override this method to customize the link generation.
* @param string $attribute the name of the attribute that this link is for
* @param string $label the label of the hyperlink
* @param string $url the URL
* @param array $htmlOptions additional HTML options
* @return string the generated hyperlink
*/
protected function createLink($attribute,$label,$url,$htmlOptions)
{
return CHtml::link($label,$url,$htmlOptions);
}
}

View File

@@ -0,0 +1,142 @@
<?php
/**
* CSqlDataProvider implements a data provider based on a plain SQL statement.
*
* CSqlDataProvider provides data in terms of arrays, each representing a row of query result.
*
* Like other data providers, CSqlDataProvider also supports sorting and pagination.
* It does so by modifying the given {@link sql} statement with "ORDER BY" and "LIMIT"
* clauses. You may configure the {@link sort} and {@link pagination} properties to
* customize sorting and pagination behaviors.
*
* CSqlDataProvider may be used in the following way:
* <pre>
* $count=Yii::app()->db->createCommand('SELECT COUNT(*) FROM tbl_user')->queryScalar();
* $sql='SELECT * FROM tbl_user';
* $dataProvider=new CSqlDataProvider($sql, array(
* 'totalItemCount'=>$count,
* 'sort'=>array(
* 'attributes'=>array(
* 'id', 'username', 'email',
* ),
* ),
* 'pagination'=>array(
* 'pageSize'=>10,
* ),
* ));
* // $dataProvider->getData() will return a list of arrays.
* </pre>
*
* Note: if you want to use the pagination feature, you must configure the {@link totalItemCount} property
* to be the total number of rows (without pagination). And if you want to use the sorting feature,
* you must configure {@link sort} property so that the provider knows which columns can be sorted.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.1.4
*/
class CSqlDataProvider extends CDataProvider
{
/**
* @var CDbConnection the database connection to be used in the queries.
* Defaults to null, meaning using Yii::app()->db.
*/
public $db;
/**
* @var string|CDbCommand the SQL statement to be used for fetching data rows.
* Since version 1.1.13 this can also be an instance of {@link CDbCommand}.
*/
public $sql;
/**
* @var array parameters (name=>value) to be bound to the SQL statement.
*/
public $params=array();
/**
* @var string the name of key field. Defaults to 'id'.
*/
public $keyField='id';
/**
* Constructor.
* @param string|CDbCommand $sql the SQL statement to be used for fetching data rows. Since version 1.1.13 this can also be an instance of {@link CDbCommand}.
* @param array $config configuration (name=>value) to be applied as the initial property values of this class.
*/
public function __construct($sql,$config=array())
{
$this->sql=$sql;
foreach($config as $key=>$value)
$this->$key=$value;
}
/**
* Fetches the data from the persistent data storage.
* @return array list of data items
*/
protected function fetchData()
{
if(!($this->sql instanceof CDbCommand))
{
$db=$this->db===null ? Yii::app()->db : $this->db;
$command=$db->createCommand($this->sql);
}
else
$command=clone $this->sql;
if(($sort=$this->getSort())!==false)
{
$order=$sort->getOrderBy();
if(!empty($order))
{
if(preg_match('/\s+order\s+by\s+[\w\s,\.]+$/i',$command->text))
$command->text.=', '.$order;
else
$command->text.=' ORDER BY '.$order;
}
}
if(($pagination=$this->getPagination())!==false)
{
$pagination->setItemCount($this->getTotalItemCount());
$limit=$pagination->getLimit();
$offset=$pagination->getOffset();
$command->text=$command->getConnection()->getCommandBuilder()->applyLimit($command->text,$limit,$offset);
}
foreach($this->params as $name=>$value)
$command->bindValue($name,$value);
return $command->queryAll();
}
/**
* Fetches the data item keys from the persistent data storage.
* @return array list of data item keys.
*/
protected function fetchKeys()
{
$keys=array();
if($data=$this->getData())
{
if(is_object(reset($data)))
foreach($data as $i=>$item)
$keys[$i]=$item->{$this->keyField};
else
foreach($data as $i=>$item)
$keys[$i]=$item[$this->keyField];
}
return $keys;
}
/**
* Calculates the total number of data items.
* This method is invoked when {@link getTotalItemCount()} is invoked
* and {@link totalItemCount} is not set previously.
* The default implementation simply returns 0.
* You may override this method to return accurate total number of data items.
* @return integer the total number of data items.
*/
protected function calculateTotalItemCount()
{
return 0;
}
}

140
framework/web/CTheme.php Normal file
View File

@@ -0,0 +1,140 @@
<?php
/**
* CTheme 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/
*/
/**
* CTheme represents an application theme.
*
* @property string $name Theme name.
* @property string $baseUrl The relative URL to the theme folder (without ending slash).
* @property string $basePath The file path to the theme folder.
* @property string $viewPath The path for controller views. Defaults to 'ThemeRoot/views'.
* @property string $systemViewPath The path for system views. Defaults to 'ThemeRoot/views/system'.
* @property string $skinPath The path for widget skins. Defaults to 'ThemeRoot/views/skins'.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CTheme extends CComponent
{
private $_name;
private $_basePath;
private $_baseUrl;
/**
* Constructor.
* @param string $name name of the theme
* @param string $basePath base theme path
* @param string $baseUrl base theme URL
*/
public function __construct($name,$basePath,$baseUrl)
{
$this->_name=$name;
$this->_baseUrl=$baseUrl;
$this->_basePath=$basePath;
}
/**
* @return string theme name
*/
public function getName()
{
return $this->_name;
}
/**
* @return string the relative URL to the theme folder (without ending slash)
*/
public function getBaseUrl()
{
return $this->_baseUrl;
}
/**
* @return string the file path to the theme folder
*/
public function getBasePath()
{
return $this->_basePath;
}
/**
* @return string the path for controller views. Defaults to 'ThemeRoot/views'.
*/
public function getViewPath()
{
return $this->_basePath.DIRECTORY_SEPARATOR.'views';
}
/**
* @return string the path for system views. Defaults to 'ThemeRoot/views/system'.
*/
public function getSystemViewPath()
{
return $this->getViewPath().DIRECTORY_SEPARATOR.'system';
}
/**
* @return string the path for widget skins. Defaults to 'ThemeRoot/views/skins'.
* @since 1.1
*/
public function getSkinPath()
{
return $this->getViewPath().DIRECTORY_SEPARATOR.'skins';
}
/**
* Finds the view file for the specified controller's view.
* @param CController $controller the controller
* @param string $viewName the view name
* @return string the view file path. False if the file does not exist.
*/
public function getViewFile($controller,$viewName)
{
$moduleViewPath=$this->getViewPath();
if(($module=$controller->getModule())!==null)
$moduleViewPath.='/'.$module->getId();
return $controller->resolveViewFile($viewName,$this->getViewPath().'/'.$controller->getUniqueId(),$this->getViewPath(),$moduleViewPath);
}
/**
* Finds the layout file for the specified controller's layout.
* @param CController $controller the controller
* @param string $layoutName the layout name
* @return string the layout file path. False if the file does not exist.
*/
public function getLayoutFile($controller,$layoutName)
{
$moduleViewPath=$basePath=$this->getViewPath();
$module=$controller->getModule();
if(empty($layoutName))
{
while($module!==null)
{
if($module->layout===false)
return false;
if(!empty($module->layout))
break;
$module=$module->getParentModule();
}
if($module===null)
$layoutName=Yii::app()->layout;
else
{
$layoutName=$module->layout;
$moduleViewPath.='/'.$module->getId();
}
}
elseif($module!==null)
$moduleViewPath.='/'.$module->getId();
return $controller->resolveViewFile($layoutName,$moduleViewPath.'/layouts',$basePath,$moduleViewPath);
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* CThemeManager 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/
*/
/**
* CThemeManager manages the themes for the Web application.
*
* A theme is a collection of view/layout files and resource files
* (e.g. css, image, js files). When a theme is active, {@link CController}
* will look for the specified view/layout under the theme folder first.
* The corresponding view/layout files will be used if the theme provides them.
* Otherwise, the default view/layout files will be used.
*
* By default, each theme is organized as a directory whose name is the theme name.
* All themes are located under the "WebRootPath/themes" directory.
*
* To activate a theme, set the {@link CWebApplication::setTheme theme} property
* to be the name of that theme.
*
* Since a self-contained theme often contains resource files that are made
* Web accessible, please make sure the view/layout files are protected from Web access.
*
* @property array $themeNames List of available theme names.
* @property string $basePath The base path for all themes. Defaults to "WebRootPath/themes".
* @property string $baseUrl The base URL for all themes. Defaults to "/WebRoot/themes".
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CThemeManager extends CApplicationComponent
{
/**
* default themes base path
*/
const DEFAULT_BASEPATH='themes';
/**
* @var string the name of the theme class for representing a theme.
* Defaults to {@link CTheme}. This can also be a class name in dot syntax.
*/
public $themeClass='CTheme';
private $_basePath=null;
private $_baseUrl=null;
/**
* @param string $name name of the theme to be retrieved
* @return CTheme the theme retrieved. Null if the theme does not exist.
*/
public function getTheme($name)
{
$themePath=$this->getBasePath().DIRECTORY_SEPARATOR.$name;
if(is_dir($themePath))
{
$class=Yii::import($this->themeClass, true);
return new $class($name,$themePath,$this->getBaseUrl().'/'.$name);
}
else
return null;
}
/**
* @return array list of available theme names
*/
public function getThemeNames()
{
static $themes;
if($themes===null)
{
$themes=array();
$basePath=$this->getBasePath();
$folder=@opendir($basePath);
while(($file=@readdir($folder))!==false)
{
if($file!=='.' && $file!=='..' && $file!=='.svn' && $file!=='.gitignore' && is_dir($basePath.DIRECTORY_SEPARATOR.$file))
$themes[]=$file;
}
closedir($folder);
sort($themes);
}
return $themes;
}
/**
* @return string the base path for all themes. Defaults to "WebRootPath/themes".
*/
public function getBasePath()
{
if($this->_basePath===null)
$this->setBasePath(dirname(Yii::app()->getRequest()->getScriptFile()).DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH);
return $this->_basePath;
}
/**
* @param string $value the base path for all themes.
* @throws CException if the base path does not exist
*/
public function setBasePath($value)
{
$this->_basePath=realpath($value);
if($this->_basePath===false || !is_dir($this->_basePath))
throw new CException(Yii::t('yii','Theme directory "{directory}" does not exist.',array('{directory}'=>$value)));
}
/**
* @return string the base URL for all themes. Defaults to "/WebRoot/themes".
*/
public function getBaseUrl()
{
if($this->_baseUrl===null)
$this->_baseUrl=Yii::app()->getBaseUrl().'/'.self::DEFAULT_BASEPATH;
return $this->_baseUrl;
}
/**
* @param string $value the base URL for all themes.
*/
public function setBaseUrl($value)
{
$this->_baseUrl=rtrim($value,'/');
}
}

View File

@@ -0,0 +1,275 @@
<?php
/**
* CUploadedFile 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/
*/
/**
* CUploadedFile represents the information for an uploaded file.
*
* Call {@link getInstance} to retrieve the instance of an uploaded file,
* and then use {@link saveAs} to save it on the server.
* You may also query other information about the file, including {@link name},
* {@link tempName}, {@link type}, {@link size} and {@link error}.
*
* @property string $name The original name of the file being uploaded.
* @property string $tempName The path of the uploaded file on the server.
* Note, this is a temporary file which will be automatically deleted by PHP
* after the current request is processed.
* @property string $type The MIME-type of the uploaded file (such as "image/gif").
* Since this MIME type is not checked on the server side, do not take this value for granted.
* Instead, use {@link CFileHelper::getMimeType} to determine the exact MIME type.
* @property integer $size The actual size of the uploaded file in bytes.
* @property integer $error The error code.
* @property boolean $hasError Whether there is an error with the uploaded file.
* Check {@link error} for detailed error code information.
* @property string $extensionName The file extension name for {@link name}.
* The extension name does not include the dot character. An empty string
* is returned if {@link name} does not have an extension name.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CUploadedFile extends CComponent
{
static private $_files;
private $_name;
private $_tempName;
private $_type;
private $_size;
private $_error;
/**
* Returns an instance of the specified uploaded file.
* The file should be uploaded using {@link CHtml::activeFileField}.
* @param CModel $model the model instance
* @param string $attribute the attribute name. For tabular file uploading, this can be in the format of "[$i]attributeName", where $i stands for an integer index.
* @return CUploadedFile the instance of the uploaded file.
* Null is returned if no file is uploaded for the specified model attribute.
* @see getInstanceByName
*/
public static function getInstance($model, $attribute)
{
return self::getInstanceByName(CHtml::resolveName($model, $attribute));
}
/**
* Returns all uploaded files for the given model attribute.
* @param CModel $model the model instance
* @param string $attribute the attribute name. For tabular file uploading, this can be in the format of "[$i]attributeName", where $i stands for an integer index.
* @return CUploadedFile[] array of CUploadedFile objects.
* Empty array is returned if no available file was found for the given attribute.
*/
public static function getInstances($model, $attribute)
{
return self::getInstancesByName(CHtml::resolveName($model, $attribute));
}
/**
* Returns an instance of the specified uploaded file.
* The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
* @param string $name the name of the file input field.
* @return CUploadedFile the instance of the uploaded file.
* Null is returned if no file is uploaded for the specified name.
*/
public static function getInstanceByName($name)
{
if(null===self::$_files)
self::prefetchFiles();
return isset(self::$_files[$name]) && self::$_files[$name]->getError()!=UPLOAD_ERR_NO_FILE ? self::$_files[$name] : null;
}
/**
* Returns an array of instances starting with specified array name.
*
* If multiple files were uploaded and saved as 'Files[0]', 'Files[1]',
* 'Files[n]'..., you can have them all by passing 'Files' as array name.
* @param string $name the name of the array of files
* @return CUploadedFile[] the array of CUploadedFile objects. Empty array is returned
* if no adequate upload was found. Please note that this array will contain
* all files from all subarrays regardless how deeply nested they are.
*/
public static function getInstancesByName($name)
{
if(null===self::$_files)
self::prefetchFiles();
$len=strlen($name);
$results=array();
foreach(array_keys(self::$_files) as $key)
if(0===strncmp($key, $name.'[', $len+1) && self::$_files[$key]->getError()!=UPLOAD_ERR_NO_FILE)
$results[] = self::$_files[$key];
return $results;
}
/**
* Cleans up the loaded CUploadedFile instances.
* This method is mainly used by test scripts to set up a fixture.
* @since 1.1.4
*/
public static function reset()
{
self::$_files=null;
}
/**
* Initially processes $_FILES superglobal for easier use.
* Only for internal usage.
*/
protected static function prefetchFiles()
{
self::$_files = array();
if(!isset($_FILES) || !is_array($_FILES))
return;
foreach($_FILES as $class=>$info)
self::collectFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
}
/**
* Processes incoming files for {@link getInstanceByName}.
* @param string $key key for identifiing uploaded file: class name and subarray indexes
* @param mixed $names file names provided by PHP
* @param mixed $tmp_names temporary file names provided by PHP
* @param mixed $types filetypes provided by PHP
* @param mixed $sizes file sizes provided by PHP
* @param mixed $errors uploading issues provided by PHP
*/
protected static function collectFilesRecursive($key, $names, $tmp_names, $types, $sizes, $errors)
{
if(is_array($names))
{
foreach($names as $item=>$name)
self::collectFilesRecursive($key.'['.$item.']', $names[$item], $tmp_names[$item], $types[$item], $sizes[$item], $errors[$item]);
}
else
self::$_files[$key] = new CUploadedFile($names, $tmp_names, $types, $sizes, $errors);
}
/**
* Constructor.
* Use {@link getInstance} to get an instance of an uploaded file.
* @param string $name the original name of the file being uploaded
* @param string $tempName the path of the uploaded file on the server.
* @param string $type the MIME-type of the uploaded file (such as "image/gif").
* @param integer $size the actual size of the uploaded file in bytes
* @param integer $error the error code
*/
public function __construct($name,$tempName,$type,$size,$error)
{
$this->_name=$name;
$this->_tempName=$tempName;
$this->_type=$type;
$this->_size=$size;
$this->_error=$error;
}
/**
* String output.
* This is PHP magic method that returns string representation of an object.
* The implementation here returns the uploaded file's name.
* @return string the string representation of the object
*/
public function __toString()
{
return $this->_name;
}
/**
* Saves the uploaded file.
* Note: this method uses php's move_uploaded_file() method. As such, if the target file ($file)
* already exists it is overwritten.
* @param string $file the file path used to save the uploaded file
* @param boolean $deleteTempFile whether to delete the temporary file after saving.
* If true, you will not be able to save the uploaded file again in the current request.
* @return boolean true whether the file is saved successfully
*/
public function saveAs($file,$deleteTempFile=true)
{
if($this->_error==UPLOAD_ERR_OK)
{
if($deleteTempFile)
return move_uploaded_file($this->_tempName,$file);
elseif(is_uploaded_file($this->_tempName))
return copy($this->_tempName, $file);
else
return false;
}
else
return false;
}
/**
* @return string the original name of the file being uploaded
*/
public function getName()
{
return $this->_name;
}
/**
* @return string the path of the uploaded file on the server.
* Note, this is a temporary file which will be automatically deleted by PHP
* after the current request is processed.
*/
public function getTempName()
{
return $this->_tempName;
}
/**
* @return string the MIME-type of the uploaded file (such as "image/gif").
* Since this MIME type is not checked on the server side, do not take this value for granted.
* Instead, use {@link CFileHelper::getMimeType} to determine the exact MIME type.
*/
public function getType()
{
return $this->_type;
}
/**
* @return integer the actual size of the uploaded file in bytes
*/
public function getSize()
{
return $this->_size;
}
/**
* Returns an error code describing the status of this file uploading.
* @return integer the error code
* @see http://www.php.net/manual/en/features.file-upload.errors.php
*/
public function getError()
{
return $this->_error;
}
/**
* @return boolean whether there is an error with the uploaded file.
* Check {@link error} for detailed error code information.
*/
public function getHasError()
{
return $this->_error!=UPLOAD_ERR_OK;
}
/**
* @return string the file extension name for {@link name}.
* The extension name does not include the dot character. An empty string
* is returned if {@link name} does not have an extension name.
*/
public function getExtensionName()
{
if(($pos=strrpos($this->_name,'.'))!==false)
return (string)substr($this->_name,$pos+1);
else
return '';
}
}

View File

@@ -0,0 +1,850 @@
<?php
/**
* CUrlManager 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/
*/
/**
* CUrlManager manages the URLs of Yii Web applications.
*
* It provides URL construction ({@link createUrl()}) as well as parsing ({@link parseUrl()}) functionality.
*
* URLs managed via CUrlManager can be in one of the following two formats,
* by setting {@link setUrlFormat urlFormat} property:
* <ul>
* <li>'path' format: /path/to/EntryScript.php/name1/value1/name2/value2...</li>
* <li>'get' format: /path/to/EntryScript.php?name1=value1&name2=value2...</li>
* </ul>
*
* When using 'path' format, CUrlManager uses a set of {@link setRules rules} to:
* <ul>
* <li>parse the requested URL into a route ('ControllerID/ActionID') and GET parameters;</li>
* <li>create URLs based on the given route and GET parameters.</li>
* </ul>
*
* A rule consists of a route and a pattern. The latter is used by CUrlManager to determine
* which rule is used for parsing/creating URLs. A pattern is meant to match the path info
* part of a URL. It may contain named parameters using the syntax '&lt;ParamName:RegExp&gt;'.
*
* When parsing a URL, a matching rule will extract the named parameters from the path info
* and put them into the $_GET variable; when creating a URL, a matching rule will extract
* the named parameters from $_GET and put them into the path info part of the created URL.
*
* If a pattern ends with '/*', it means additional GET parameters may be appended to the path
* info part of the URL; otherwise, the GET parameters can only appear in the query string part.
*
* To specify URL rules, set the {@link setRules rules} property as an array of rules (pattern=>route).
* For example,
* <pre>
* array(
* 'articles'=>'article/list',
* 'article/<id:\d+>/*'=>'article/read',
* )
* </pre>
* Two rules are specified in the above:
* <ul>
* <li>The first rule says that if the user requests the URL '/path/to/index.php/articles',
* it should be treated as '/path/to/index.php/article/list'; and vice versa applies
* when constructing such a URL.</li>
* <li>The second rule contains a named parameter 'id' which is specified using
* the &lt;ParamName:RegExp&gt; syntax. It says that if the user requests the URL
* '/path/to/index.php/article/13', it should be treated as '/path/to/index.php/article/read?id=13';
* and vice versa applies when constructing such a URL.</li>
* </ul>
*
* The route part may contain references to named parameters defined in the pattern part.
* This allows a rule to be applied to different routes based on matching criteria.
* For example,
* <pre>
* array(
* '<_c:(post|comment)>/<id:\d+>/<_a:(create|update|delete)>'=>'<_c>/<_a>',
* '<_c:(post|comment)>/<id:\d+>'=>'<_c>/view',
* '<_c:(post|comment)>s/*'=>'<_c>/list',
* )
* </pre>
* In the above, we use two named parameters '<_c>' and '<_a>' in the route part. The '<_c>'
* parameter matches either 'post' or 'comment', while the '<_a>' parameter matches an action ID.
*
* Like normal rules, these rules can be used for both parsing and creating URLs.
* For example, using the rules above, the URL '/index.php/post/123/create'
* would be parsed as the route 'post/create' with GET parameter 'id' being 123.
* And given the route 'post/list' and GET parameter 'page' being 2, we should get a URL
* '/index.php/posts/page/2'.
*
* It is also possible to include hostname into the rules for parsing and creating URLs.
* One may extract part of the hostname to be a GET parameter.
* For example, the URL <code>http://admin.example.com/en/profile</code> may be parsed into GET parameters
* <code>user=admin</code> and <code>lang=en</code>. On the other hand, rules with hostname may also be used to
* create URLs with parameterized hostnames.
*
* In order to use parameterized hostnames, simply declare URL rules with host info, e.g.:
* <pre>
* array(
* 'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile',
* )
* </pre>
*
* Starting from version 1.1.8, one can write custom URL rule classes and use them for one or several URL rules.
* For example,
* <pre>
* array(
* // a standard rule
* '<action:(login|logout)>' => 'site/<action>',
* // a custom rule using data in DB
* array(
* 'class' => 'application.components.MyUrlRule',
* 'connectionID' => 'db',
* ),
* )
* </pre>
* Please note that the custom URL rule class should extend from {@link CBaseUrlRule} and
* implement the following two methods,
* <ul>
* <li>{@link CBaseUrlRule::createUrl()}</li>
* <li>{@link CBaseUrlRule::parseUrl()}</li>
* </ul>
*
* CUrlManager is a default application component that may be accessed via
* {@link CWebApplication::getUrlManager()}.
*
* @property string $baseUrl The base URL of the application (the part after host name and before query string).
* If {@link showScriptName} is true, it will include the script name part.
* Otherwise, it will not, and the ending slashes are stripped off.
* @property string $urlFormat The URL format. Defaults to 'path'. Valid values include 'path' and 'get'.
* Please refer to the guide for more details about the difference between these two formats.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CUrlManager extends CApplicationComponent
{
const CACHE_KEY='Yii.CUrlManager.rules';
const GET_FORMAT='get';
const PATH_FORMAT='path';
/**
* @var array the URL rules (pattern=>route).
*/
public $rules=array();
/**
* @var string the URL suffix used when in 'path' format.
* For example, ".html" can be used so that the URL looks like pointing to a static HTML page. Defaults to empty.
*/
public $urlSuffix='';
/**
* @var boolean whether to show entry script name in the constructed URL. Defaults to true.
*/
public $showScriptName=true;
/**
* @var boolean whether to append GET parameters to the path info part. Defaults to true.
* This property is only effective when {@link urlFormat} is 'path' and is mainly used when
* creating URLs. When it is true, GET parameters will be appended to the path info and
* separate from each other using slashes. If this is false, GET parameters will be in query part.
*/
public $appendParams=true;
/**
* @var string the GET variable name for route. Defaults to 'r'.
*/
public $routeVar='r';
/**
* @var boolean whether routes are case-sensitive. Defaults to true. By setting this to false,
* the route in the incoming request will be turned to lower case first before further processing.
* As a result, you should follow the convention that you use lower case when specifying
* controller mapping ({@link CWebApplication::controllerMap}) and action mapping
* ({@link CController::actions}). Also, the directory names for organizing controllers should
* be in lower case.
*/
public $caseSensitive=true;
/**
* @var boolean whether the GET parameter values should match the corresponding
* sub-patterns in a rule before using it to create a URL. Defaults to false, meaning
* a rule will be used for creating a URL only if its route and parameter names match the given ones.
* If this property is set true, then the given parameter values must also match the corresponding
* parameter sub-patterns. Note that setting this property to true will degrade performance.
* @since 1.1.0
*/
public $matchValue=false;
/**
* @var string the ID of the cache application component that is used to cache the parsed URL rules.
* Defaults to 'cache' which refers to the primary cache application component.
* Set this property to false if you want to disable caching URL rules.
*/
public $cacheID='cache';
/**
* @var boolean whether to enable strict URL parsing.
* This property is only effective when {@link urlFormat} is 'path'.
* If it is set true, then an incoming URL must match one of the {@link rules URL rules}.
* Otherwise, it will be treated as an invalid request and trigger a 404 HTTP exception.
* Defaults to false.
*/
public $useStrictParsing=false;
/**
* @var string the class name or path alias for the URL rule instances. Defaults to 'CUrlRule'.
* If you change this to something else, please make sure that the new class must extend from
* {@link CBaseUrlRule} and have the same constructor signature as {@link CUrlRule}.
* It must also be serializable and autoloadable.
* @since 1.1.8
*/
public $urlRuleClass='CUrlRule';
private $_urlFormat=self::GET_FORMAT;
private $_rules=array();
private $_baseUrl;
/**
* Initializes the application component.
*/
public function init()
{
parent::init();
$this->processRules();
}
/**
* Processes the URL rules.
*/
protected function processRules()
{
if(empty($this->rules) || $this->getUrlFormat()===self::GET_FORMAT)
return;
if($this->cacheID!==false && ($cache=Yii::app()->getComponent($this->cacheID))!==null)
{
$hash=md5(serialize($this->rules));
if(($data=$cache->get(self::CACHE_KEY))!==false && isset($data[1]) && $data[1]===$hash)
{
$this->_rules=$data[0];
return;
}
}
foreach($this->rules as $pattern=>$route)
$this->_rules[]=$this->createUrlRule($route,$pattern);
if(isset($cache))
$cache->set(self::CACHE_KEY,array($this->_rules,$hash));
}
/**
* Adds new URL rules.
* In order to make the new rules effective, this method must be called BEFORE
* {@link CWebApplication::processRequest}.
* @param array $rules new URL rules (pattern=>route).
* @param boolean $append whether the new URL rules should be appended to the existing ones. If false,
* they will be inserted at the beginning.
* @since 1.1.4
*/
public function addRules($rules,$append=true)
{
if ($append)
{
foreach($rules as $pattern=>$route)
$this->_rules[]=$this->createUrlRule($route,$pattern);
}
else
{
$rules=array_reverse($rules);
foreach($rules as $pattern=>$route)
array_unshift($this->_rules, $this->createUrlRule($route,$pattern));
}
}
/**
* Creates a URL rule instance.
* The default implementation returns a CUrlRule object.
* @param mixed $route the route part of the rule. This could be a string or an array
* @param string $pattern the pattern part of the rule
* @return CUrlRule the URL rule instance
* @since 1.1.0
*/
protected function createUrlRule($route,$pattern)
{
if(is_array($route) && isset($route['class']))
return $route;
else
{
$urlRuleClass=Yii::import($this->urlRuleClass,true);
return new $urlRuleClass($route,$pattern);
}
}
/**
* Constructs a URL.
* @param string $route the controller and the action (e.g. article/read)
* @param array $params list of GET parameters (name=>value). Both the name and value will be URL-encoded.
* If the name is '#', the corresponding value will be treated as an anchor
* and will be appended at the end of the URL.
* @param string $ampersand the token separating name-value pairs in the URL. Defaults to '&'.
* @return string the constructed URL
*/
public function createUrl($route,$params=array(),$ampersand='&')
{
unset($params[$this->routeVar]);
foreach($params as $i=>$param)
if($param===null)
$params[$i]='';
if(isset($params['#']))
{
$anchor='#'.$params['#'];
unset($params['#']);
}
else
$anchor='';
$route=trim($route,'/');
foreach($this->_rules as $i=>$rule)
{
if(is_array($rule))
$this->_rules[$i]=$rule=Yii::createComponent($rule);
if(($url=$rule->createUrl($this,$route,$params,$ampersand))!==false)
{
if($rule->hasHostInfo)
return $url==='' ? '/'.$anchor : $url.$anchor;
else
return $this->getBaseUrl().'/'.$url.$anchor;
}
}
return $this->createUrlDefault($route,$params,$ampersand).$anchor;
}
/**
* Creates a URL based on default settings.
* @param string $route the controller and the action (e.g. article/read)
* @param array $params list of GET parameters
* @param string $ampersand the token separating name-value pairs in the URL.
* @return string the constructed URL
*/
protected function createUrlDefault($route,$params,$ampersand)
{
if($this->getUrlFormat()===self::PATH_FORMAT)
{
$url=rtrim($this->getBaseUrl().'/'.$route,'/');
if($this->appendParams)
{
$url=rtrim($url.'/'.$this->createPathInfo($params,'/','/'),'/');
return $route==='' ? $url : $url.$this->urlSuffix;
}
else
{
if($route!=='')
$url.=$this->urlSuffix;
$query=$this->createPathInfo($params,'=',$ampersand);
return $query==='' ? $url : $url.'?'.$query;
}
}
else
{
$url=$this->getBaseUrl();
if(!$this->showScriptName)
$url.='/';
if($route!=='')
{
$url.='?'.$this->routeVar.'='.$route;
if(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
$url.=$ampersand.$query;
}
elseif(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
$url.='?'.$query;
return $url;
}
}
/**
* Parses the user request.
* @param CHttpRequest $request the request application component
* @return string the route (controllerID/actionID) and perhaps GET parameters in path format.
*/
public function parseUrl($request)
{
if($this->getUrlFormat()===self::PATH_FORMAT)
{
$rawPathInfo=$request->getPathInfo();
$pathInfo=$this->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
foreach($this->_rules as $i=>$rule)
{
if(is_array($rule))
$this->_rules[$i]=$rule=Yii::createComponent($rule);
if(($r=$rule->parseUrl($this,$request,$pathInfo,$rawPathInfo))!==false)
return isset($_GET[$this->routeVar]) ? $_GET[$this->routeVar] : $r;
}
if($this->useStrictParsing)
throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
array('{route}'=>$pathInfo)));
else
return $pathInfo;
}
elseif(isset($_GET[$this->routeVar]))
return $_GET[$this->routeVar];
elseif(isset($_POST[$this->routeVar]))
return $_POST[$this->routeVar];
else
return '';
}
/**
* Parses a path info into URL segments and saves them to $_GET and $_REQUEST.
* @param string $pathInfo path info
*/
public function parsePathInfo($pathInfo)
{
if($pathInfo==='')
return;
$segs=explode('/',$pathInfo.'/');
$n=count($segs);
for($i=0;$i<$n-1;$i+=2)
{
$key=$segs[$i];
if($key==='') continue;
$value=$segs[$i+1];
if(($pos=strpos($key,'['))!==false && ($m=preg_match_all('/\[(.*?)\]/',$key,$matches))>0)
{
$name=substr($key,0,$pos);
for($j=$m-1;$j>=0;--$j)
{
if($matches[1][$j]==='')
$value=array($value);
else
$value=array($matches[1][$j]=>$value);
}
if(isset($_GET[$name]) && is_array($_GET[$name]))
$value=CMap::mergeArray($_GET[$name],$value);
$_REQUEST[$name]=$_GET[$name]=$value;
}
else
$_REQUEST[$key]=$_GET[$key]=$value;
}
}
/**
* Creates a path info based on the given parameters.
* @param array $params list of GET parameters
* @param string $equal the separator between name and value
* @param string $ampersand the separator between name-value pairs
* @param string $key this is used internally.
* @return string the created path info
*/
public function createPathInfo($params,$equal,$ampersand, $key=null)
{
$pairs = array();
foreach($params as $k => $v)
{
if ($key!==null)
$k = $key.'['.$k.']';
if (is_array($v))
$pairs[]=$this->createPathInfo($v,$equal,$ampersand, $k);
else
$pairs[]=urlencode($k).$equal.urlencode($v);
}
return implode($ampersand,$pairs);
}
/**
* Removes the URL suffix from path info.
* @param string $pathInfo path info part in the URL
* @param string $urlSuffix the URL suffix to be removed
* @return string path info with URL suffix removed.
*/
public function removeUrlSuffix($pathInfo,$urlSuffix)
{
if($urlSuffix!=='' && substr($pathInfo,-strlen($urlSuffix))===$urlSuffix)
return substr($pathInfo,0,-strlen($urlSuffix));
else
return $pathInfo;
}
/**
* Returns the base URL of the application.
* @return string the base URL of the application (the part after host name and before query string).
* If {@link showScriptName} is true, it will include the script name part.
* Otherwise, it will not, and the ending slashes are stripped off.
*/
public function getBaseUrl()
{
if($this->_baseUrl!==null)
return $this->_baseUrl;
else
{
if($this->showScriptName)
$this->_baseUrl=Yii::app()->getRequest()->getScriptUrl();
else
$this->_baseUrl=Yii::app()->getRequest()->getBaseUrl();
return $this->_baseUrl;
}
}
/**
* Sets the base URL of the application (the part after host name and before query string).
* This method is provided in case the {@link baseUrl} cannot be determined automatically.
* The ending slashes should be stripped off. And you are also responsible to remove the script name
* if you set {@link showScriptName} to be false.
* @param string $value the base URL of the application
* @since 1.1.1
*/
public function setBaseUrl($value)
{
$this->_baseUrl=$value;
}
/**
* Returns the URL format.
* @return string the URL format. Defaults to 'path'. Valid values include 'path' and 'get'.
* Please refer to the guide for more details about the difference between these two formats.
*/
public function getUrlFormat()
{
return $this->_urlFormat;
}
/**
* Sets the URL format.
* @param string $value the URL format. It must be either 'path' or 'get'.
*/
public function setUrlFormat($value)
{
if($value===self::PATH_FORMAT || $value===self::GET_FORMAT)
$this->_urlFormat=$value;
else
throw new CException(Yii::t('yii','CUrlManager.UrlFormat must be either "path" or "get".'));
}
}
/**
* CBaseUrlRule is the base class for a URL rule class.
*
* Custom URL rule classes should extend from this class and implement two methods:
* {@link createUrl} and {@link parseUrl}.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.1.8
*/
abstract class CBaseUrlRule extends CComponent
{
/**
* @var boolean whether this rule will also parse the host info part. Defaults to false.
*/
public $hasHostInfo=false;
/**
* Creates a URL based on this rule.
* @param CUrlManager $manager the manager
* @param string $route the route
* @param array $params list of parameters (name=>value) associated with the route
* @param string $ampersand the token separating name-value pairs in the URL.
* @return mixed the constructed URL. False if this rule does not apply.
*/
abstract public function createUrl($manager,$route,$params,$ampersand);
/**
* Parses a URL based on this rule.
* @param CUrlManager $manager the URL manager
* @param CHttpRequest $request the request object
* @param string $pathInfo path info part of the URL (URL suffix is already removed based on {@link CUrlManager::urlSuffix})
* @param string $rawPathInfo path info that contains the potential URL suffix
* @return mixed the route that consists of the controller ID and action ID. False if this rule does not apply.
*/
abstract public function parseUrl($manager,$request,$pathInfo,$rawPathInfo);
}
/**
* CUrlRule represents a URL formatting/parsing rule.
*
* It mainly consists of two parts: route and pattern. The former classifies
* the rule so that it only applies to specific controller-action route.
* The latter performs the actual formatting and parsing role. The pattern
* may have a set of named parameters.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CUrlRule extends CBaseUrlRule
{
/**
* @var string the URL suffix used for this rule.
* For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
* Defaults to null, meaning using the value of {@link CUrlManager::urlSuffix}.
*/
public $urlSuffix;
/**
* @var boolean whether the rule is case sensitive. Defaults to null, meaning
* using the value of {@link CUrlManager::caseSensitive}.
*/
public $caseSensitive;
/**
* @var array the default GET parameters (name=>value) that this rule provides.
* When this rule is used to parse the incoming request, the values declared in this property
* will be injected into $_GET.
*/
public $defaultParams=array();
/**
* @var boolean whether the GET parameter values should match the corresponding
* sub-patterns in the rule when creating a URL. Defaults to null, meaning using the value
* of {@link CUrlManager::matchValue}. When this property is false, it means
* a rule will be used for creating a URL if its route and parameter names match the given ones.
* If this property is set true, then the given parameter values must also match the corresponding
* parameter sub-patterns. Note that setting this property to true will degrade performance.
* @since 1.1.0
*/
public $matchValue;
/**
* @var string the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
* If this rule can match multiple verbs, please separate them with commas.
* If this property is not set, the rule can match any verb.
* Note that this property is only used when parsing a request. It is ignored for URL creation.
* @since 1.1.7
*/
public $verb;
/**
* @var boolean whether this rule is only used for request parsing.
* Defaults to false, meaning the rule is used for both URL parsing and creation.
* @since 1.1.7
*/
public $parsingOnly=false;
/**
* @var string the controller/action pair
*/
public $route;
/**
* @var array the mapping from route param name to token name (e.g. _r1=><1>)
*/
public $references=array();
/**
* @var string the pattern used to match route
*/
public $routePattern;
/**
* @var string regular expression used to parse a URL
*/
public $pattern;
/**
* @var string template used to construct a URL
*/
public $template;
/**
* @var array list of parameters (name=>regular expression)
*/
public $params=array();
/**
* @var boolean whether the URL allows additional parameters at the end of the path info.
*/
public $append;
/**
* @var boolean whether host info should be considered for this rule
*/
public $hasHostInfo;
/**
* Constructor.
* @param string $route the route of the URL (controller/action)
* @param string $pattern the pattern for matching the URL
*/
public function __construct($route,$pattern)
{
if(is_array($route))
{
foreach(array('urlSuffix', 'caseSensitive', 'defaultParams', 'matchValue', 'verb', 'parsingOnly') as $name)
{
if(isset($route[$name]))
$this->$name=$route[$name];
}
if(isset($route['pattern']))
$pattern=$route['pattern'];
$route=$route[0];
}
$this->route=trim($route,'/');
$tr2['/']=$tr['/']='\\/';
if(strpos($route,'<')!==false && preg_match_all('/<(\w+)>/',$route,$matches2))
{
foreach($matches2[1] as $name)
$this->references[$name]="<$name>";
}
$this->hasHostInfo=!strncasecmp($pattern,'http://',7) || !strncasecmp($pattern,'https://',8);
if($this->verb!==null)
$this->verb=preg_split('/[\s,]+/',strtoupper($this->verb),-1,PREG_SPLIT_NO_EMPTY);
if(preg_match_all('/<(\w+):?(.*?)?>/',$pattern,$matches))
{
$tokens=array_combine($matches[1],$matches[2]);
foreach($tokens as $name=>$value)
{
if($value==='')
$value='[^\/]+';
$tr["<$name>"]="(?P<$name>$value)";
if(isset($this->references[$name]))
$tr2["<$name>"]=$tr["<$name>"];
else
$this->params[$name]=$value;
}
}
$p=rtrim($pattern,'*');
$this->append=$p!==$pattern;
$p=trim($p,'/');
$this->template=preg_replace('/<(\w+):?.*?>/','<$1>',$p);
$this->pattern='/^'.strtr($this->template,$tr).'\/';
if($this->append)
$this->pattern.='/u';
else
$this->pattern.='$/u';
if($this->references!==array())
$this->routePattern='/^'.strtr($this->route,$tr2).'$/u';
if(YII_DEBUG && @preg_match($this->pattern,'test')===false)
throw new CException(Yii::t('yii','The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.',
array('{route}'=>$route,'{pattern}'=>$pattern)));
}
/**
* Creates a URL based on this rule.
* @param CUrlManager $manager the manager
* @param string $route the route
* @param array $params list of parameters
* @param string $ampersand the token separating name-value pairs in the URL.
* @return mixed the constructed URL or false on error
*/
public function createUrl($manager,$route,$params,$ampersand)
{
if($this->parsingOnly)
return false;
if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
$case='';
else
$case='i';
$tr=array();
if($route!==$this->route)
{
if($this->routePattern!==null && preg_match($this->routePattern.$case,$route,$matches))
{
foreach($this->references as $key=>$name)
$tr[$name]=$matches[$key];
}
else
return false;
}
foreach($this->defaultParams as $key=>$value)
{
if(isset($params[$key]))
{
if($params[$key]==$value)
unset($params[$key]);
else
return false;
}
}
foreach($this->params as $key=>$value)
if(!isset($params[$key]))
return false;
if($manager->matchValue && $this->matchValue===null || $this->matchValue)
{
foreach($this->params as $key=>$value)
{
if(!preg_match('/\A'.$value.'\z/u'.$case,$params[$key]))
return false;
}
}
foreach($this->params as $key=>$value)
{
$tr["<$key>"]=urlencode($params[$key]);
unset($params[$key]);
}
$suffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
$url=strtr($this->template,$tr);
if($this->hasHostInfo)
{
$hostInfo=Yii::app()->getRequest()->getHostInfo();
if(stripos($url,$hostInfo)===0)
$url=substr($url,strlen($hostInfo));
}
if(empty($params))
return $url!=='' ? $url.$suffix : $url;
if($this->append)
$url.='/'.$manager->createPathInfo($params,'/','/').$suffix;
else
{
if($url!=='')
$url.=$suffix;
$url.='?'.$manager->createPathInfo($params,'=',$ampersand);
}
return $url;
}
/**
* Parses a URL based on this rule.
* @param CUrlManager $manager the URL manager
* @param CHttpRequest $request the request object
* @param string $pathInfo path info part of the URL
* @param string $rawPathInfo path info that contains the potential URL suffix
* @return mixed the route that consists of the controller ID and action ID or false on error
*/
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
if($this->verb!==null && !in_array($request->getRequestType(), $this->verb, true))
return false;
if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
$case='';
else
$case='i';
if($this->urlSuffix!==null)
$pathInfo=$manager->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
// URL suffix required, but not found in the requested URL
if($manager->useStrictParsing && $pathInfo===$rawPathInfo)
{
$urlSuffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
if($urlSuffix!='' && $urlSuffix!=='/')
return false;
}
if($this->hasHostInfo)
$pathInfo=strtolower($request->getHostInfo()).rtrim('/'.$pathInfo,'/');
$pathInfo.='/';
if(preg_match($this->pattern.$case,$pathInfo,$matches))
{
foreach($this->defaultParams as $name=>$value)
{
if(!isset($_GET[$name]))
$_REQUEST[$name]=$_GET[$name]=$value;
}
$tr=array();
foreach($matches as $key=>$value)
{
if(isset($this->references[$key]))
$tr[$this->references[$key]]=$value;
elseif(isset($this->params[$key]))
$_REQUEST[$key]=$_GET[$key]=$value;
}
if($pathInfo!==$matches[0]) // there're additional GET params
$manager->parsePathInfo(ltrim(substr($pathInfo,strlen($matches[0])),'/'));
if($this->routePattern!==null)
return strtr($this->route,$tr);
else
return $this->route;
}
else
return false;
}
}

View File

@@ -0,0 +1,547 @@
<?php
/**
* CWebApplication 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/
*/
/**
* CWebApplication extends CApplication by providing functionalities specific to Web requests.
*
* CWebApplication manages the controllers in MVC pattern, and provides the following additional
* core application components:
* <ul>
* <li>{@link urlManager}: provides URL parsing and constructing functionality;</li>
* <li>{@link request}: encapsulates the Web request information;</li>
* <li>{@link session}: provides the session-related functionalities;</li>
* <li>{@link assetManager}: manages the publishing of private asset files.</li>
* <li>{@link user}: represents the user session information.</li>
* <li>{@link themeManager}: manages themes.</li>
* <li>{@link authManager}: manages role-based access control (RBAC).</li>
* <li>{@link clientScript}: manages client scripts (javascripts and CSS).</li>
* <li>{@link widgetFactory}: creates widgets and supports widget skinning.</li>
* </ul>
*
* User requests are resolved as controller-action pairs and additional parameters.
* CWebApplication creates the requested controller instance and let it to handle
* the actual user request. If the user does not specify controller ID, it will
* assume {@link defaultController} is requested (which defaults to 'site').
*
* Controller class files must reside under the directory {@link getControllerPath controllerPath}
* (defaults to 'protected/controllers'). The file name and the class name must be
* the same as the controller ID with the first letter in upper case and appended with 'Controller'.
* For example, the controller 'article' is defined by the class 'ArticleController'
* which is in the file 'protected/controllers/ArticleController.php'.
*
* @property IAuthManager $authManager The authorization manager component.
* @property CAssetManager $assetManager The asset manager component.
* @property CHttpSession $session The session component.
* @property CWebUser $user The user session information.
* @property IViewRenderer $viewRenderer The view renderer.
* @property CClientScript $clientScript The client script manager.
* @property IWidgetFactory $widgetFactory The widget factory.
* @property CThemeManager $themeManager The theme manager.
* @property CTheme $theme The theme used currently. Null if no theme is being used.
* @property CController $controller The currently active controller.
* @property string $controllerPath The directory that contains the controller classes. Defaults to 'protected/controllers'.
* @property string $viewPath The root directory of view files. Defaults to 'protected/views'.
* @property string $systemViewPath The root directory of system view files. Defaults to 'protected/views/system'.
* @property string $layoutPath The root directory of layout files. Defaults to 'protected/views/layouts'.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.0
*/
class CWebApplication extends CApplication
{
/**
* @return string the route of the default controller, action or module. Defaults to 'site'.
*/
public $defaultController='site';
/**
* @var mixed the application-wide layout. Defaults to 'main' (relative to {@link getLayoutPath layoutPath}).
* If this is false, then no layout will be used.
*/
public $layout='main';
/**
* @var array mapping from controller ID to controller configurations.
* Each name-value pair specifies the configuration for a single controller.
* A controller configuration can be either a string or an array.
* If the former, the string should be the class name or
* {@link YiiBase::getPathOfAlias class path alias} of the controller.
* If the latter, the array must contain a 'class' element which specifies
* the controller's class name or {@link YiiBase::getPathOfAlias class path alias}.
* The rest name-value pairs in the array are used to initialize
* the corresponding controller properties. For example,
* <pre>
* array(
* 'post'=>array(
* 'class'=>'path.to.PostController',
* 'pageTitle'=>'something new',
* ),
* 'user'=>'path.to.UserController',
* )
* </pre>
*
* Note, when processing an incoming request, the controller map will first be
* checked to see if the request can be handled by one of the controllers in the map.
* If not, a controller will be searched for under the {@link getControllerPath default controller path}.
*/
public $controllerMap=array();
/**
* @var array the configuration specifying a controller which should handle
* all user requests. This is mainly used when the application is in maintenance mode
* and we should use a controller to handle all incoming requests.
* The configuration specifies the controller route (the first element)
* and GET parameters (the rest name-value pairs). For example,
* <pre>
* array(
* 'offline/notice',
* 'param1'=>'value1',
* 'param2'=>'value2',
* )
* </pre>
* Defaults to null, meaning catch-all is not effective.
*/
public $catchAllRequest;
/**
* @var string Namespace that should be used when loading controllers.
* Default is to use global namespace.
* @since 1.1.11
*/
public $controllerNamespace;
private $_controllerPath;
private $_viewPath;
private $_systemViewPath;
private $_layoutPath;
private $_controller;
private $_theme;
/**
* Processes the current request.
* It first resolves the request into controller and action,
* and then creates the controller to perform the action.
*/
public function processRequest()
{
if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0]))
{
$route=$this->catchAllRequest[0];
foreach(array_splice($this->catchAllRequest,1) as $name=>$value)
$_GET[$name]=$value;
}
else
$route=$this->getUrlManager()->parseUrl($this->getRequest());
$this->runController($route);
}
/**
* Registers the core application components.
* This method overrides the parent implementation by registering additional core components.
* @see setComponents
*/
protected function registerCoreComponents()
{
parent::registerCoreComponents();
$components=array(
'session'=>array(
'class'=>'CHttpSession',
),
'assetManager'=>array(
'class'=>'CAssetManager',
),
'user'=>array(
'class'=>'CWebUser',
),
'themeManager'=>array(
'class'=>'CThemeManager',
),
'authManager'=>array(
'class'=>'CPhpAuthManager',
),
'clientScript'=>array(
'class'=>'CClientScript',
),
'widgetFactory'=>array(
'class'=>'CWidgetFactory',
),
);
$this->setComponents($components);
}
/**
* @return IAuthManager the authorization manager component
*/
public function getAuthManager()
{
return $this->getComponent('authManager');
}
/**
* @return CAssetManager the asset manager component
*/
public function getAssetManager()
{
return $this->getComponent('assetManager');
}
/**
* @return CHttpSession the session component
*/
public function getSession()
{
return $this->getComponent('session');
}
/**
* @return CWebUser the user session information
*/
public function getUser()
{
return $this->getComponent('user');
}
/**
* Returns the view renderer.
* If this component is registered and enabled, the default
* view rendering logic defined in {@link CBaseController} will
* be replaced by this renderer.
* @return IViewRenderer the view renderer.
*/
public function getViewRenderer()
{
return $this->getComponent('viewRenderer');
}
/**
* Returns the client script manager.
* @return CClientScript the client script manager
*/
public function getClientScript()
{
return $this->getComponent('clientScript');
}
/**
* Returns the widget factory.
* @return IWidgetFactory the widget factory
* @since 1.1
*/
public function getWidgetFactory()
{
return $this->getComponent('widgetFactory');
}
/**
* @return CThemeManager the theme manager.
*/
public function getThemeManager()
{
return $this->getComponent('themeManager');
}
/**
* @return CTheme the theme used currently. Null if no theme is being used.
*/
public function getTheme()
{
if(is_string($this->_theme))
$this->_theme=$this->getThemeManager()->getTheme($this->_theme);
return $this->_theme;
}
/**
* @param string $value the theme name
*/
public function setTheme($value)
{
$this->_theme=$value;
}
/**
* Creates the controller and performs the specified action.
* @param string $route the route of the current request. See {@link createController} for more details.
* @throws CHttpException if the controller could not be created.
*/
public function runController($route)
{
if(($ca=$this->createController($route))!==null)
{
list($controller,$actionID)=$ca;
$oldController=$this->_controller;
$this->_controller=$controller;
$controller->init();
$controller->run($actionID);
$this->_controller=$oldController;
}
else
throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
array('{route}'=>$route===''?$this->defaultController:$route)));
}
/**
* Creates a controller instance based on a route.
* The route should contain the controller ID and the action ID.
* It may also contain additional GET variables. All these must be concatenated together with slashes.
*
* This method will attempt to create a controller in the following order:
* <ol>
* <li>If the first segment is found in {@link controllerMap}, the corresponding
* controller configuration will be used to create the controller;</li>
* <li>If the first segment is found to be a module ID, the corresponding module
* will be used to create the controller;</li>
* <li>Otherwise, it will search under the {@link controllerPath} to create
* the corresponding controller. For example, if the route is "admin/user/create",
* then the controller will be created using the class file "protected/controllers/admin/UserController.php".</li>
* </ol>
* @param string $route the route of the request.
* @param CWebModule $owner the module that the new controller will belong to. Defaults to null, meaning the application
* instance is the owner.
* @return array the controller instance and the action ID. Null if the controller class does not exist or the route is invalid.
*/
public function createController($route,$owner=null)
{
if($owner===null)
$owner=$this;
if(($route=trim($route,'/'))==='')
$route=$owner->defaultController;
$caseSensitive=$this->getUrlManager()->caseSensitive;
$route.='/';
while(($pos=strpos($route,'/'))!==false)
{
$id=substr($route,0,$pos);
if(!preg_match('/^\w+$/',$id))
return null;
if(!$caseSensitive)
$id=strtolower($id);
$route=(string)substr($route,$pos+1);
if(!isset($basePath)) // first segment
{
if(isset($owner->controllerMap[$id]))
{
return array(
Yii::createComponent($owner->controllerMap[$id],$id,$owner===$this?null:$owner),
$this->parseActionParams($route),
);
}
if(($module=$owner->getModule($id))!==null)
return $this->createController($route,$module);
$basePath=$owner->getControllerPath();
$controllerID='';
}
else
$controllerID.='/';
$className=ucfirst($id).'Controller';
$classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
if($owner->controllerNamespace!==null)
$className=$owner->controllerNamespace.'\\'.$className;
if(is_file($classFile))
{
if(!class_exists($className,false))
require($classFile);
if(class_exists($className,false) && is_subclass_of($className,'CController'))
{
$id[0]=strtolower($id[0]);
return array(
new $className($controllerID.$id,$owner===$this?null:$owner),
$this->parseActionParams($route),
);
}
return null;
}
$controllerID.=$id;
$basePath.=DIRECTORY_SEPARATOR.$id;
}
}
/**
* Parses a path info into an action ID and GET variables.
* @param string $pathInfo path info
* @return string action ID
*/
protected function parseActionParams($pathInfo)
{
if(($pos=strpos($pathInfo,'/'))!==false)
{
$manager=$this->getUrlManager();
$manager->parsePathInfo((string)substr($pathInfo,$pos+1));
$actionID=substr($pathInfo,0,$pos);
return $manager->caseSensitive ? $actionID : strtolower($actionID);
}
else
return $pathInfo;
}
/**
* @return CController the currently active controller
*/
public function getController()
{
return $this->_controller;
}
/**
* @param CController $value the currently active controller
*/
public function setController($value)
{
$this->_controller=$value;
}
/**
* @return string the directory that contains the controller classes. Defaults to 'protected/controllers'.
*/
public function getControllerPath()
{
if($this->_controllerPath!==null)
return $this->_controllerPath;
else
return $this->_controllerPath=$this->getBasePath().DIRECTORY_SEPARATOR.'controllers';
}
/**
* @param string $value the directory that contains the controller classes.
* @throws CException if the directory is invalid
*/
public function setControllerPath($value)
{
if(($this->_controllerPath=realpath($value))===false || !is_dir($this->_controllerPath))
throw new CException(Yii::t('yii','The controller path "{path}" is not a valid directory.',
array('{path}'=>$value)));
}
/**
* @return string the root directory of view files. Defaults to 'protected/views'.
*/
public function getViewPath()
{
if($this->_viewPath!==null)
return $this->_viewPath;
else
return $this->_viewPath=$this->getBasePath().DIRECTORY_SEPARATOR.'views';
}
/**
* @param string $path the root directory of view files.
* @throws CException if the directory does not exist.
*/
public function setViewPath($path)
{
if(($this->_viewPath=realpath($path))===false || !is_dir($this->_viewPath))
throw new CException(Yii::t('yii','The view path "{path}" is not a valid directory.',
array('{path}'=>$path)));
}
/**
* @return string the root directory of system view files. Defaults to 'protected/views/system'.
*/
public function getSystemViewPath()
{
if($this->_systemViewPath!==null)
return $this->_systemViewPath;
else
return $this->_systemViewPath=$this->getViewPath().DIRECTORY_SEPARATOR.'system';
}
/**
* @param string $path the root directory of system view files.
* @throws CException if the directory does not exist.
*/
public function setSystemViewPath($path)
{
if(($this->_systemViewPath=realpath($path))===false || !is_dir($this->_systemViewPath))
throw new CException(Yii::t('yii','The system view path "{path}" is not a valid directory.',
array('{path}'=>$path)));
}
/**
* @return string the root directory of layout files. Defaults to 'protected/views/layouts'.
*/
public function getLayoutPath()
{
if($this->_layoutPath!==null)
return $this->_layoutPath;
else
return $this->_layoutPath=$this->getViewPath().DIRECTORY_SEPARATOR.'layouts';
}
/**
* @param string $path the root directory of layout files.
* @throws CException if the directory does not exist.
*/
public function setLayoutPath($path)
{
if(($this->_layoutPath=realpath($path))===false || !is_dir($this->_layoutPath))
throw new CException(Yii::t('yii','The layout path "{path}" is not a valid directory.',
array('{path}'=>$path)));
}
/**
* The pre-filter for controller actions.
* This method is invoked before the currently requested controller action and all its filters
* are executed. You may override this method with logic that needs to be done
* before all controller actions.
* @param CController $controller the controller
* @param CAction $action the action
* @return boolean whether the action should be executed.
*/
public function beforeControllerAction($controller,$action)
{
return true;
}
/**
* The post-filter for controller actions.
* This method is invoked after the currently requested controller action and all its filters
* are executed. You may override this method with logic that needs to be done
* after all controller actions.
* @param CController $controller the controller
* @param CAction $action the action
*/
public function afterControllerAction($controller,$action)
{
}
/**
* Do not call this method. This method is used internally to search for a module by its ID.
* @param string $id module ID
* @return CWebModule the module that has the specified ID. Null if no module is found.
*/
public function findModule($id)
{
if(($controller=$this->getController())!==null && ($module=$controller->getModule())!==null)
{
do
{
if(($m=$module->getModule($id))!==null)
return $m;
} while(($module=$module->getParentModule())!==null);
}
if(($m=$this->getModule($id))!==null)
return $m;
}
/**
* Initializes the application.
* This method overrides the parent implementation by preloading the 'request' component.
*/
protected function init()
{
parent::init();
// preload 'request' so that it has chance to respond to onBeginRequest event.
$this->getRequest();
}
}

View File

@@ -0,0 +1,202 @@
<?php
/**
* CWebModule 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/
*/
/**
* CWebModule represents an application module.
*
* An application module may be considered as a self-contained sub-application
* that has its own controllers, models and views and can be reused in a different
* project as a whole. Controllers inside a module must be accessed with routes
* that are prefixed with the module ID.
*
* @property string $name The name of this module.
* @property string $description The description of this module.
* @property string $version The version of this module.
* @property string $controllerPath The directory that contains the controller classes. Defaults to 'moduleDir/controllers'
* where moduleDir is the directory containing the module class.
* @property string $viewPath The root directory of view files. Defaults to 'moduleDir/views' where moduleDir is
* the directory containing the module class.
* @property string $layoutPath The root directory of layout files. Defaults to 'moduleDir/views/layouts' where
* moduleDir is the directory containing the module class.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
*/
class CWebModule extends CModule
{
/**
* @var string the ID of the default controller for this module. Defaults to 'default'.
*/
public $defaultController='default';
/**
* @var mixed the layout that is shared by the controllers inside this module.
* If a controller has explicitly declared its own {@link CController::layout layout},
* this property will be ignored.
* If this is null (default), the application's layout or the parent module's layout (if available)
* will be used. If this is false, then no layout will be used.
*/
public $layout;
/**
* @var string Namespace that should be used when loading controllers.
* Default is to use global namespace.
* @since 1.1.11
*/
public $controllerNamespace;
/**
* @var array mapping from controller ID to controller configurations.
* Pleaser refer to {@link CWebApplication::controllerMap} for more details.
*/
public $controllerMap=array();
private $_controllerPath;
private $_viewPath;
private $_layoutPath;
/**
* Returns the name of this module.
* The default implementation simply returns {@link id}.
* You may override this method to customize the name of this module.
* @return string the name of this module.
*/
public function getName()
{
return basename($this->getId());
}
/**
* Returns the description of this module.
* The default implementation returns an empty string.
* You may override this method to customize the description of this module.
* @return string the description of this module.
*/
public function getDescription()
{
return '';
}
/**
* Returns the version of this module.
* The default implementation returns '1.0'.
* You may override this method to customize the version of this module.
* @return string the version of this module.
*/
public function getVersion()
{
return '1.0';
}
/**
* @return string the directory that contains the controller classes. Defaults to 'moduleDir/controllers' where
* moduleDir is the directory containing the module class.
*/
public function getControllerPath()
{
if($this->_controllerPath!==null)
return $this->_controllerPath;
else
return $this->_controllerPath=$this->getBasePath().DIRECTORY_SEPARATOR.'controllers';
}
/**
* @param string $value the directory that contains the controller classes.
* @throws CException if the directory is invalid
*/
public function setControllerPath($value)
{
if(($this->_controllerPath=realpath($value))===false || !is_dir($this->_controllerPath))
throw new CException(Yii::t('yii','The controller path "{path}" is not a valid directory.',
array('{path}'=>$value)));
}
/**
* @return string the root directory of view files. Defaults to 'moduleDir/views' where
* moduleDir is the directory containing the module class.
*/
public function getViewPath()
{
if($this->_viewPath!==null)
return $this->_viewPath;
else
return $this->_viewPath=$this->getBasePath().DIRECTORY_SEPARATOR.'views';
}
/**
* @param string $path the root directory of view files.
* @throws CException if the directory does not exist.
*/
public function setViewPath($path)
{
if(($this->_viewPath=realpath($path))===false || !is_dir($this->_viewPath))
throw new CException(Yii::t('yii','The view path "{path}" is not a valid directory.',
array('{path}'=>$path)));
}
/**
* @return string the root directory of layout files. Defaults to 'moduleDir/views/layouts' where
* moduleDir is the directory containing the module class.
*/
public function getLayoutPath()
{
if($this->_layoutPath!==null)
return $this->_layoutPath;
else
return $this->_layoutPath=$this->getViewPath().DIRECTORY_SEPARATOR.'layouts';
}
/**
* @param string $path the root directory of layout files.
* @throws CException if the directory does not exist.
*/
public function setLayoutPath($path)
{
if(($this->_layoutPath=realpath($path))===false || !is_dir($this->_layoutPath))
throw new CException(Yii::t('yii','The layout path "{path}" is not a valid directory.',
array('{path}'=>$path)));
}
/**
* The pre-filter for controller actions.
* This method is invoked before the currently requested controller action and all its filters
* are executed. You may override this method in the following way:
* <pre>
* if(parent::beforeControllerAction($controller,$action))
* {
* // your code
* return true;
* }
* else
* return false;
* </pre>
* @param CController $controller the controller
* @param CAction $action the action
* @return boolean whether the action should be executed.
*/
public function beforeControllerAction($controller,$action)
{
if(($parent=$this->getParentModule())===null)
$parent=Yii::app();
return $parent->beforeControllerAction($controller,$action);
}
/**
* The post-filter for controller actions.
* This method is invoked after the currently requested controller action and all its filters
* are executed. If you override this method, make sure you call the parent implementation at the end.
* @param CController $controller the controller
* @param CAction $action the action
*/
public function afterControllerAction($controller,$action)
{
if(($parent=$this->getParentModule())===null)
$parent=Yii::app();
$parent->afterControllerAction($controller,$action);
}
}

View File

@@ -0,0 +1,197 @@
<?php
/**
* CWidgetFactory 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/
*/
/**
* CWidgetFactory creates new widgets to be used in views.
*
* CWidgetFactory is used as the default "widgetFactory" application component.
*
* When calling {@link CBaseController::createWidget}, {@link CBaseController::widget}
* or {@link CBaseController::beginWidget}, if the "widgetFactory" component is installed,
* it will be used to create the requested widget. To install the "widgetFactory" component,
* we should have the following application configuration:
* <pre>
* return array(
* 'components'=>array(
* 'widgetFactory'=>array(
* 'class'=>'CWidgetFactory',
* ),
* ),
* )
* </pre>
*
* CWidgetFactory implements the "skin" feature, which allows a new widget to be created
* and initialized with a set of predefined property values (called skin).
*
* When CWidgetFactory is used to create a new widget, it will first instantiate the
* widget instance. It then checks if there is a skin available for this widget
* according to the widget class name and the widget {@link CWidget::skin} property.
* If a skin is found, it will be merged with the initial properties passed via
* {@link createWidget}. Then the merged initial properties will be used to initialize
* the newly created widget instance.
*
* As aforementioned, a skin is a set of initial property values for a widget.
* It is thus represented as an associative array of name-value pairs.
* Skins are stored in PHP scripts like other configurations. Each script file stores the skins
* for a particular widget type and is named as the widget class name (e.g. CLinkPager.php).
* Each widget type may have one or several skins, identified by the skin name set via
* {@link CWidget::skin} property. If the {@link CWidget::skin} property is not set for a given
* widget, it means the default skin would be used. The following shows the possible skins for
* the {@link CLinkPager} widget:
* <pre>
* return array(
* 'default'=>array(
* 'nextPageLabel'=>'&gt;&gt;',
* 'prevPageLabel'=>'&lt;&lt;',
* ),
* 'short'=>array(
* 'header'=>'',
* 'maxButtonCount'=>5,
* ),
* );
* </pre>
* In the above, there are two skins. The first one is the default skin which is indexed by the string "default".
* Note that {@link CWidget::skin} defaults to "default". Therefore, this is the skin that will be applied
* if we do not explicitly specify the {@link CWidget::skin} property.
* The second one is named as the "short" skin which will be used only when we set {@link CWidget::skin}
* to be "short".
*
* By default, CWidgetFactory looks for the skin of a widget under the "skins" directory
* of the current application's {@link CWebApplication::viewPath} (e.g. protected/views/skins).
* If a theme is being used, it will look for the skin under the "skins" directory of
* the theme's {@link CTheme::viewPath} (as well as the aforementioned skin directory).
* In case the specified skin is not found, a widget will still be created
* normally without causing any error.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web
* @since 1.1
*/
class CWidgetFactory extends CApplicationComponent implements IWidgetFactory
{
/**
* @var boolean whether to enable widget skinning. Defaults to false.
* @see skinnableWidgets
* @since 1.1.3
*/
public $enableSkin=false;
/**
* @var array widget initial property values. Each array key-value pair
* represents the initial property values for a single widget class, with
* the array key being the widget class name, and array value being the initial
* property value array. For example,
* <pre>
* array(
* 'CLinkPager'=>array(
* 'maxButtonCount'=>5,
* 'cssFile'=>false,
* ),
* 'CJuiDatePicker'=>array(
* 'language'=>'ru',
* ),
* )
* </pre>
*
* Note that the initial values specified here may be overridden by
* the values given in {@link CBaseController::createWidget} calls.
* They may also be overridden by widget skins, if {@link enableSkin} is true.
* @since 1.1.3
*/
public $widgets=array();
/**
* @var array list of widget class names that can be skinned.
* Because skinning widgets has performance impact, you may want to specify this property
* to limit skinning only to specific widgets. Any widgets that are not in this list
* will not be skinned. Defaults to null, meaning all widgets can be skinned.
* @since 1.1.3
*/
public $skinnableWidgets;
/**
* @var string the directory containing all the skin files. Defaults to null,
* meaning using the "skins" directory under the current application's {@link CWebApplication::viewPath}.
*/
public $skinPath;
private $_skins=array(); // class name, skin name, property name => value
/**
* Initializes the application component.
* This method overrides the parent implementation by resolving the skin path.
*/
public function init()
{
parent::init();
if($this->enableSkin && $this->skinPath===null)
$this->skinPath=Yii::app()->getViewPath().DIRECTORY_SEPARATOR.'skins';
}
/**
* Creates a new widget based on the given class name and initial properties.
* @param CBaseController $owner the owner of the new widget
* @param string $className the class name of the widget. This can also be a path alias (e.g. system.web.widgets.COutputCache)
* @param array $properties the initial property values (name=>value) of the widget.
* @return CWidget the newly created widget whose properties have been initialized with the given values.
*/
public function createWidget($owner,$className,$properties=array())
{
$className=Yii::import($className,true);
$widget=new $className($owner);
if(isset($this->widgets[$className]))
$properties=$properties===array() ? $this->widgets[$className] : CMap::mergeArray($this->widgets[$className],$properties);
if($this->enableSkin)
{
if($this->skinnableWidgets===null || in_array($className,$this->skinnableWidgets))
{
$skinName=isset($properties['skin']) ? $properties['skin'] : 'default';
if($skinName!==false && ($skin=$this->getSkin($className,$skinName))!==array())
$properties=$properties===array() ? $skin : CMap::mergeArray($skin,$properties);
}
}
foreach($properties as $name=>$value)
$widget->$name=$value;
return $widget;
}
/**
* Returns the skin for the specified widget class and skin name.
* @param string $className the widget class name
* @param string $skinName the widget skin name
* @return array the skin (name=>value) for the widget
*/
protected function getSkin($className,$skinName)
{
if(!isset($this->_skins[$className][$skinName]))
{
$skinFile=$this->skinPath.DIRECTORY_SEPARATOR.$className.'.php';
if(is_file($skinFile))
$this->_skins[$className]=require($skinFile);
else
$this->_skins[$className]=array();
if(($theme=Yii::app()->getTheme())!==null)
{
$skinFile=$theme->getSkinPath().DIRECTORY_SEPARATOR.$className.'.php';
if(is_file($skinFile))
{
$skins=require($skinFile);
foreach($skins as $name=>$skin)
$this->_skins[$className][$name]=$skin;
}
}
if(!isset($this->_skins[$className][$skinName]))
$this->_skins[$className][$skinName]=array();
}
return $this->_skins[$className][$skinName];
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* CAction 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/
*/
/**
* CAction is the base class for all controller action classes.
*
* CAction provides a way to divide a complex controller into
* smaller actions in separate class files.
*
* Derived classes must implement {@link run()} which is invoked by
* controller when the action is requested.
*
* An action instance can access its controller via {@link getController controller} property.
*
* @property CController $controller The controller who owns this action.
* @property string $id Id of this action.
*
* @method run() executes action
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.actions
* @since 1.0
*/
abstract class CAction extends CComponent implements IAction
{
private $_id;
private $_controller;
/**
* Constructor.
* @param CController $controller the controller who owns this action.
* @param string $id id of the action.
*/
public function __construct($controller,$id)
{
$this->_controller=$controller;
$this->_id=$id;
}
/**
* @return CController the controller who owns this action.
*/
public function getController()
{
return $this->_controller;
}
/**
* @return string id of this action
*/
public function getId()
{
return $this->_id;
}
/**
* Runs the action with the supplied request parameters.
* This method is internally called by {@link CController::runAction()}.
* @param array $params the request parameters (name=>value)
* @return boolean whether the request parameters are valid
* @since 1.1.7
*/
public function runWithParams($params)
{
$method=new ReflectionMethod($this, 'run');
if($method->getNumberOfParameters()>0)
return $this->runWithParamsInternal($this, $method, $params);
else
return $this->run();
}
/**
* Executes a method of an object with the supplied named parameters.
* This method is internally used.
* @param mixed $object the object whose method is to be executed
* @param ReflectionMethod $method the method reflection
* @param array $params the named parameters
* @return boolean whether the named parameters are valid
* @since 1.1.7
*/
protected function runWithParamsInternal($object, $method, $params)
{
$ps=array();
foreach($method->getParameters() as $i=>$param)
{
$name=$param->getName();
if(isset($params[$name]))
{
if($param->isArray())
$ps[]=is_array($params[$name]) ? $params[$name] : array($params[$name]);
elseif(!is_array($params[$name]))
$ps[]=$params[$name];
else
return false;
}
elseif($param->isDefaultValueAvailable())
$ps[]=$param->getDefaultValue();
else
return false;
}
$method->invokeArgs($object,$ps);
return true;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/**
* CInlineAction 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/
*/
/**
* CInlineAction represents an action that is defined as a controller method.
*
* The method name is like 'actionXYZ' where 'XYZ' stands for the action name.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.actions
* @since 1.0
*/
class CInlineAction extends CAction
{
/**
* Runs the action.
* The action method defined in the controller is invoked.
* This method is required by {@link CAction}.
*/
public function run()
{
$method='action'.$this->getId();
$this->getController()->$method();
}
/**
* Runs the action with the supplied request parameters.
* This method is internally called by {@link CController::runAction()}.
* @param array $params the request parameters (name=>value)
* @return boolean whether the request parameters are valid
* @since 1.1.7
*/
public function runWithParams($params)
{
$methodName='action'.$this->getId();
$controller=$this->getController();
$method=new ReflectionMethod($controller, $methodName);
if($method->getNumberOfParameters()>0)
return $this->runWithParamsInternal($controller, $method, $params);
else
return $controller->$methodName();
}
}

View File

@@ -0,0 +1,167 @@
<?php
/**
* CViewAction 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/
*/
/**
* CViewAction represents an action that displays a view according to a user-specified parameter.
*
* By default, the view being displayed is specified via the <code>view</code> GET parameter.
* The name of the GET parameter can be customized via {@link viewParam}.
* If the user doesn't provide the GET parameter, the default view specified by {@link defaultView}
* will be displayed.
*
* Users specify a view in the format of <code>path.to.view</code>, which translates to the view name
* <code>BasePath/path/to/view</code> where <code>BasePath</code> is given by {@link basePath}.
*
* Note, the user specified view can only contain word characters, dots and dashes and
* the first letter must be a word letter.
*
* @property string $requestedView The name of the view requested by the user.
* This is in the format of 'path.to.view'.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.actions
* @since 1.0
*/
class CViewAction extends CAction
{
/**
* @var string the name of the GET parameter that contains the requested view name. Defaults to 'view'.
*/
public $viewParam='view';
/**
* @var string the name of the default view when {@link viewParam} GET parameter is not provided by user. Defaults to 'index'.
* This should be in the format of 'path.to.view', similar to that given in
* the GET parameter.
* @see basePath
*/
public $defaultView='index';
/**
* @var string the name of the view to be rendered. This property will be set
* once the user requested view is resolved.
*/
public $view;
/**
* @var string the base path for the views. Defaults to 'pages'.
* The base path will be prefixed to any user-specified page view.
* For example, if a user requests for <code>tutorial.chap1</code>, the corresponding view name will
* be <code>pages/tutorial/chap1</code>, assuming the base path is <code>pages</code>.
* The actual view file is determined by {@link CController::getViewFile}.
* @see CController::getViewFile
*/
public $basePath='pages';
/**
* @var mixed the name of the layout to be applied to the views.
* This will be assigned to {@link CController::layout} before the view is rendered.
* Defaults to null, meaning the controller's layout will be used.
* If false, no layout will be applied.
*/
public $layout;
/**
* @var boolean whether the view should be rendered as PHP script or static text. Defaults to false.
*/
public $renderAsText=false;
private $_viewPath;
/**
* Returns the name of the view requested by the user.
* If the user doesn't specify any view, the {@link defaultView} will be returned.
* @return string the name of the view requested by the user.
* This is in the format of 'path.to.view'.
*/
public function getRequestedView()
{
if($this->_viewPath===null)
{
if(!empty($_GET[$this->viewParam]) && is_string($_GET[$this->viewParam]))
$this->_viewPath=$_GET[$this->viewParam];
else
$this->_viewPath=$this->defaultView;
}
return $this->_viewPath;
}
/**
* Resolves the user-specified view into a valid view name.
* @param string $viewPath user-specified view in the format of 'path.to.view'.
* @return string fully resolved view in the format of 'path/to/view'.
* @throw CHttpException if the user-specified view is invalid
*/
protected function resolveView($viewPath)
{
// start with a word char and have word chars, dots and dashes only
if(preg_match('/^\w[\w\.\-]*$/',$viewPath))
{
$view=strtr($viewPath,'.','/');
if(!empty($this->basePath))
$view=$this->basePath.'/'.$view;
if($this->getController()->getViewFile($view)!==false)
{
$this->view=$view;
return;
}
}
throw new CHttpException(404,Yii::t('yii','The requested view "{name}" was not found.',
array('{name}'=>$viewPath)));
}
/**
* Runs the action.
* This method displays the view requested by the user.
* @throws CHttpException if the view is invalid
*/
public function run()
{
$this->resolveView($this->getRequestedView());
$controller=$this->getController();
if($this->layout!==null)
{
$layout=$controller->layout;
$controller->layout=$this->layout;
}
$this->onBeforeRender($event=new CEvent($this));
if(!$event->handled)
{
if($this->renderAsText)
{
$text=file_get_contents($controller->getViewFile($this->view));
$controller->renderText($text);
}
else
$controller->render($this->view);
$this->onAfterRender(new CEvent($this));
}
if($this->layout!==null)
$controller->layout=$layout;
}
/**
* Raised right before the action invokes the render method.
* Event handlers can set the {@link CEvent::handled} property
* to be true to stop further view rendering.
* @param CEvent $event event parameter
*/
public function onBeforeRender($event)
{
$this->raiseEvent('onBeforeRender',$event);
}
/**
* Raised right after the action invokes the render method.
* @param CEvent $event event parameter
*/
public function onAfterRender($event)
{
$this->raiseEvent('onAfterRender',$event);
}
}

View File

@@ -0,0 +1,394 @@
<?php
/**
* CAccessControlFilter 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/
*/
/**
* CAccessControlFilter performs authorization checks for the specified actions.
*
* By enabling this filter, controller actions can be checked for access permissions.
* When the user is not denied by one of the security rules or allowed by a rule explicitly,
* he will be able to access the action.
*
* For maximum security consider adding
* <pre>array('deny')</pre>
* as a last rule in a list so all actions will be denied by default.
*
* To specify the access rules, set the {@link setRules rules} property, which should
* be an array of the rules. Each rule is specified as an array of the following structure:
* <pre>
* array(
* 'allow', // or 'deny'
*
* // optional, list of action IDs (case insensitive) that this rule applies to
* // if not specified or empty, rule applies to all actions
* 'actions'=>array('edit', 'delete'),
*
* // optional, list of controller IDs (case insensitive) that this rule applies to
* 'controllers'=>array('post', 'admin/user'),
*
* // optional, list of usernames (case insensitive) that this rule applies to
* // Use * to represent all users, ? guest users, and @ authenticated users
* 'users'=>array('thomas', 'kevin'),
*
* // optional, list of roles (case sensitive!) that this rule applies to.
* 'roles'=>array('admin', 'editor'),
*
* // since version 1.1.11 you can pass parameters for RBAC bizRules
* 'roles'=>array('updateTopic'=>array('topic'=>$topic))
*
* // optional, list of IP address/patterns that this rule applies to
* // e.g. 127.0.0.1, 127.0.0.*
* 'ips'=>array('127.0.0.1'),
*
* // optional, list of request types (case insensitive) that this rule applies to
* 'verbs'=>array('GET', 'POST'),
*
* // optional, a PHP expression whose value indicates whether this rule applies
* // The PHP expression will be evaluated using {@link evaluateExpression}.
* // A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
* // please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
* 'expression'=>'!$user->isGuest && $user->level==2',
*
* // optional, the customized error message to be displayed
* // This option is available since version 1.1.1.
* 'message'=>'Access Denied.',
*
* // optional, the denied method callback name, that will be called once the
* // access is denied, instead of showing the customized error message. It can also be
* // a valid PHP callback, including class method name (array(ClassName/Object, MethodName)),
* // or anonymous function (PHP 5.3.0+). The function/method signature should be as follows:
* // function foo($user, $rule) { ... }
* // where $user is the current application user object and $rule is this access rule.
* // This option is available since version 1.1.11.
* 'deniedCallback'=>'redirectToDeniedMethod',
* )
* </pre>
*
* @property array $rules List of access rules.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
class CAccessControlFilter extends CFilter
{
/**
* @var string the error message to be displayed when authorization fails.
* This property can be overridden by individual access rule via {@link CAccessRule::message}.
* If this property is not set, a default error message will be displayed.
* @since 1.1.1
*/
public $message;
private $_rules=array();
/**
* @return array list of access rules.
*/
public function getRules()
{
return $this->_rules;
}
/**
* @param array $rules list of access rules.
*/
public function setRules($rules)
{
foreach($rules as $rule)
{
if(is_array($rule) && isset($rule[0]))
{
$r=new CAccessRule;
$r->allow=$rule[0]==='allow';
foreach(array_slice($rule,1) as $name=>$value)
{
if($name==='expression' || $name==='roles' || $name==='message' || $name==='deniedCallback')
$r->$name=$value;
else
$r->$name=array_map('strtolower',$value);
}
$this->_rules[]=$r;
}
}
}
/**
* Performs the pre-action filtering.
* @param CFilterChain $filterChain the filter chain that the filter is on.
* @return boolean whether the filtering process should continue and the action
* should be executed.
*/
protected function preFilter($filterChain)
{
$app=Yii::app();
$request=$app->getRequest();
$user=$app->getUser();
$verb=$request->getRequestType();
$ip=$request->getUserHostAddress();
foreach($this->getRules() as $rule)
{
if(($allow=$rule->isUserAllowed($user,$filterChain->controller,$filterChain->action,$ip,$verb))>0) // allowed
break;
elseif($allow<0) // denied
{
if(isset($rule->deniedCallback))
call_user_func($rule->deniedCallback, $rule);
else
$this->accessDenied($user,$this->resolveErrorMessage($rule));
return false;
}
}
return true;
}
/**
* Resolves the error message to be displayed.
* This method will check {@link message} and {@link CAccessRule::message} to see
* what error message should be displayed.
* @param CAccessRule $rule the access rule
* @return string the error message
* @since 1.1.1
*/
protected function resolveErrorMessage($rule)
{
if($rule->message!==null)
return $rule->message;
elseif($this->message!==null)
return $this->message;
else
return Yii::t('yii','You are not authorized to perform this action.');
}
/**
* Denies the access of the user.
* This method is invoked when access check fails.
* @param IWebUser $user the current user
* @param string $message the error message to be displayed
*/
protected function accessDenied($user,$message)
{
if($user->getIsGuest())
$user->loginRequired();
else
throw new CHttpException(403,$message);
}
}
/**
* CAccessRule represents an access rule that is managed by {@link CAccessControlFilter}.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
class CAccessRule extends CComponent
{
/**
* @var boolean whether this is an 'allow' rule or 'deny' rule.
*/
public $allow;
/**
* @var array list of action IDs that this rule applies to. The comparison is case-insensitive.
* If no actions are specified, rule applies to all actions.
*/
public $actions;
/**
* @var array list of controller IDs that this rule applies to. The comparison is case-insensitive.
*/
public $controllers;
/**
* @var array list of user names that this rule applies to. The comparison is case-insensitive.
* If no user names are specified, rule applies to all users.
*/
public $users;
/**
* @var array list of roles this rule applies to. For each role, the current user's
* {@link CWebUser::checkAccess} method will be invoked. If one of the invocations
* returns true, the rule will be applied.
* Note, you should mainly use roles in an "allow" rule because by definition,
* a role represents a permission collection.
* @see CAuthManager
*/
public $roles;
/**
* @var array IP patterns.
*/
public $ips;
/**
* @var array list of request types (e.g. GET, POST) that this rule applies to.
*/
public $verbs;
/**
* @var string a PHP expression whose value indicates whether this rule should be applied.
* In this expression, you can use <code>$user</code> which refers to <code>Yii::app()->user</code>.
* The expression can also be a valid PHP callback,
* including class method name (array(ClassName/Object, MethodName)),
* or anonymous function (PHP 5.3.0+). The function/method signature should be as follows:
* <pre>
* function foo($user, $rule) { ... }
* </pre>
* where $user is the current application user object and $rule is this access rule.
*
* The PHP expression will be evaluated using {@link evaluateExpression}.
*
* A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
* please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
*/
public $expression;
/**
* @var string the error message to be displayed when authorization is denied by this rule.
* If not set, a default error message will be displayed.
* @since 1.1.1
*/
public $message;
/**
* @var mixed the denied method callback that will be called once the
* access is denied. It replaces the behavior that shows an error message.
* It can be a valid PHP callback including class method name (array(ClassName/Object, MethodName)),
* or anonymous function (PHP 5.3.0+). For more information, on different options, check
* @link http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback
* The function/method signature should be as follows:
* <pre>
* function foo($rule) { ... }
* </pre>
* where $rule is this access rule.
*
* @since 1.1.11
*/
public $deniedCallback;
/**
* Checks whether the Web user is allowed to perform the specified action.
* @param CWebUser $user the user object
* @param CController $controller the controller currently being executed
* @param CAction $action the action to be performed
* @param string $ip the request IP address
* @param string $verb the request verb (GET, POST, etc.)
* @return integer 1 if the user is allowed, -1 if the user is denied, 0 if the rule does not apply to the user
*/
public function isUserAllowed($user,$controller,$action,$ip,$verb)
{
if($this->isActionMatched($action)
&& $this->isUserMatched($user)
&& $this->isRoleMatched($user)
&& $this->isIpMatched($ip)
&& $this->isVerbMatched($verb)
&& $this->isControllerMatched($controller)
&& $this->isExpressionMatched($user))
return $this->allow ? 1 : -1;
else
return 0;
}
/**
* @param CAction $action the action
* @return boolean whether the rule applies to the action
*/
protected function isActionMatched($action)
{
return empty($this->actions) || in_array(strtolower($action->getId()),$this->actions);
}
/**
* @param CController $controller the controller
* @return boolean whether the rule applies to the controller
*/
protected function isControllerMatched($controller)
{
return empty($this->controllers) || in_array(strtolower($controller->getUniqueId()),$this->controllers);
}
/**
* @param IWebUser $user the user
* @return boolean whether the rule applies to the user
*/
protected function isUserMatched($user)
{
if(empty($this->users))
return true;
foreach($this->users as $u)
{
if($u==='*')
return true;
elseif($u==='?' && $user->getIsGuest())
return true;
elseif($u==='@' && !$user->getIsGuest())
return true;
elseif(!strcasecmp($u,$user->getName()))
return true;
}
return false;
}
/**
* @param IWebUser $user the user object
* @return boolean whether the rule applies to the role
*/
protected function isRoleMatched($user)
{
if(empty($this->roles))
return true;
foreach($this->roles as $key=>$role)
{
if(is_numeric($key))
{
if($user->checkAccess($role))
return true;
}
else
{
if($user->checkAccess($key,$role))
return true;
}
}
return false;
}
/**
* @param string $ip the IP address
* @return boolean whether the rule applies to the IP address
*/
protected function isIpMatched($ip)
{
if(empty($this->ips))
return true;
foreach($this->ips as $rule)
{
if($rule==='*' || $rule===$ip || (($pos=strpos($rule,'*'))!==false && !strncmp($ip,$rule,$pos)))
return true;
}
return false;
}
/**
* @param string $verb the request method
* @return boolean whether the rule applies to the request
*/
protected function isVerbMatched($verb)
{
return empty($this->verbs) || in_array(strtolower($verb),$this->verbs);
}
/**
* @param IWebUser $user the user
* @return boolean the expression value. True if the expression is not specified.
*/
protected function isExpressionMatched($user)
{
if($this->expression===null)
return true;
else
return $this->evaluateExpression($this->expression, array('user'=>$user));
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* CAuthAssignment 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/
*/
/**
* CAuthAssignment represents an assignment of a role to a user.
* It includes additional assignment information such as {@link bizRule} and {@link data}.
* Do not create a CAuthAssignment instance using the 'new' operator.
* Instead, call {@link IAuthManager::assign}.
*
* @property mixed $userId User ID (see {@link IWebUser::getId}).
* @property string $itemName The authorization item name.
* @property string $bizRule The business rule associated with this assignment.
* @property mixed $data Additional data for this assignment.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
class CAuthAssignment extends CComponent
{
private $_auth;
private $_itemName;
private $_userId;
private $_bizRule;
private $_data;
/**
* Constructor.
* @param IAuthManager $auth the authorization manager
* @param string $itemName authorization item name
* @param mixed $userId user ID (see {@link IWebUser::getId})
* @param string $bizRule the business rule associated with this assignment
* @param mixed $data additional data for this assignment
*/
public function __construct($auth,$itemName,$userId,$bizRule=null,$data=null)
{
$this->_auth=$auth;
$this->_itemName=$itemName;
$this->_userId=$userId;
$this->_bizRule=$bizRule;
$this->_data=$data;
}
/**
* @return mixed user ID (see {@link IWebUser::getId})
*/
public function getUserId()
{
return $this->_userId;
}
/**
* @return string the authorization item name
*/
public function getItemName()
{
return $this->_itemName;
}
/**
* @return string the business rule associated with this assignment
*/
public function getBizRule()
{
return $this->_bizRule;
}
/**
* @param string $value the business rule associated with this assignment
*/
public function setBizRule($value)
{
if($this->_bizRule!==$value)
{
$this->_bizRule=$value;
$this->_auth->saveAuthAssignment($this);
}
}
/**
* @return mixed additional data for this assignment
*/
public function getData()
{
return $this->_data;
}
/**
* @param mixed $value additional data for this assignment
*/
public function setData($value)
{
if($this->_data!==$value)
{
$this->_data=$value;
$this->_auth->saveAuthAssignment($this);
}
}
}

View File

@@ -0,0 +1,277 @@
<?php
/**
* CAuthItem 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/
*/
/**
* CAuthItem represents an authorization item.
* An authorization item can be an operation, a task or a role.
* They form an authorization hierarchy. Items on higher levels of the hierarchy
* inherit the permissions represented by items on lower levels.
* A user may be assigned one or several authorization items (called {@link CAuthAssignment assignments}.
* He can perform an operation only when it is among his assigned items.
*
* @property IAuthManager $authManager The authorization manager.
* @property integer $type The authorization item type. This could be 0 (operation), 1 (task) or 2 (role).
* @property string $name The item name.
* @property string $description The item description.
* @property string $bizRule The business rule associated with this item.
* @property mixed $data The additional data associated with this item.
* @property array $children All child items of this item.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
class CAuthItem extends CComponent
{
const TYPE_OPERATION=0;
const TYPE_TASK=1;
const TYPE_ROLE=2;
private $_auth;
private $_type;
private $_name;
private $_description;
private $_bizRule;
private $_data;
/**
* Constructor.
* @param IAuthManager $auth authorization manager
* @param string $name authorization item name
* @param integer $type authorization item type. This can be 0 (operation), 1 (task) or 2 (role).
* @param string $description the description
* @param string $bizRule the business rule associated with this item
* @param mixed $data additional data for this item
*/
public function __construct($auth,$name,$type,$description='',$bizRule=null,$data=null)
{
$this->_type=(int)$type;
$this->_auth=$auth;
$this->_name=$name;
$this->_description=$description;
$this->_bizRule=$bizRule;
$this->_data=$data;
}
/**
* Checks to see if the specified item is within the hierarchy starting from this item.
* This method is expected to be internally used by the actual implementations
* of the {@link IAuthManager::checkAccess}.
* @param string $itemName the name of the item to be checked
* @param array $params the parameters to be passed to business rule evaluation
* @return boolean whether the specified item is within the hierarchy starting from this item.
*/
public function checkAccess($itemName,$params=array())
{
Yii::trace('Checking permission "'.$this->_name.'"','system.web.auth.CAuthItem');
if($this->_auth->executeBizRule($this->_bizRule,$params,$this->_data))
{
if($this->_name==$itemName)
return true;
foreach($this->_auth->getItemChildren($this->_name) as $item)
{
if($item->checkAccess($itemName,$params))
return true;
}
}
return false;
}
/**
* @return IAuthManager the authorization manager
*/
public function getAuthManager()
{
return $this->_auth;
}
/**
* @return integer the authorization item type. This could be 0 (operation), 1 (task) or 2 (role).
*/
public function getType()
{
return $this->_type;
}
/**
* @return string the item name
*/
public function getName()
{
return $this->_name;
}
/**
* @param string $value the item name
*/
public function setName($value)
{
if($this->_name!==$value)
{
$oldName=$this->_name;
$this->_name=$value;
$this->_auth->saveAuthItem($this,$oldName);
}
}
/**
* @return string the item description
*/
public function getDescription()
{
return $this->_description;
}
/**
* @param string $value the item description
*/
public function setDescription($value)
{
if($this->_description!==$value)
{
$this->_description=$value;
$this->_auth->saveAuthItem($this);
}
}
/**
* @return string the business rule associated with this item
*/
public function getBizRule()
{
return $this->_bizRule;
}
/**
* @param string $value the business rule associated with this item
*/
public function setBizRule($value)
{
if($this->_bizRule!==$value)
{
$this->_bizRule=$value;
$this->_auth->saveAuthItem($this);
}
}
/**
* @return mixed the additional data associated with this item
*/
public function getData()
{
return $this->_data;
}
/**
* @param mixed $value the additional data associated with this item
*/
public function setData($value)
{
if($this->_data!==$value)
{
$this->_data=$value;
$this->_auth->saveAuthItem($this);
}
}
/**
* Adds a child item.
* @param string $name the name of the child item
* @return boolean whether the item is added successfully
* @throws CException if either parent or child doesn't exist or if a loop has been detected.
* @see IAuthManager::addItemChild
*/
public function addChild($name)
{
return $this->_auth->addItemChild($this->_name,$name);
}
/**
* Removes a child item.
* Note, the child item is not deleted. Only the parent-child relationship is removed.
* @param string $name the child item name
* @return boolean whether the removal is successful
* @see IAuthManager::removeItemChild
*/
public function removeChild($name)
{
return $this->_auth->removeItemChild($this->_name,$name);
}
/**
* Returns a value indicating whether a child exists
* @param string $name the child item name
* @return boolean whether the child exists
* @see IAuthManager::hasItemChild
*/
public function hasChild($name)
{
return $this->_auth->hasItemChild($this->_name,$name);
}
/**
* Returns the children of this item.
* @return array all child items of this item.
* @see IAuthManager::getItemChildren
*/
public function getChildren()
{
return $this->_auth->getItemChildren($this->_name);
}
/**
* Assigns this item to a user.
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @param string $bizRule the business rule to be executed when {@link checkAccess} is called
* for this particular authorization item.
* @param mixed $data additional data associated with this assignment
* @return CAuthAssignment the authorization assignment information.
* @throws CException if the item has already been assigned to the user
* @see IAuthManager::assign
*/
public function assign($userId,$bizRule=null,$data=null)
{
return $this->_auth->assign($this->_name,$userId,$bizRule,$data);
}
/**
* Revokes an authorization assignment from a user.
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return boolean whether removal is successful
* @see IAuthManager::revoke
*/
public function revoke($userId)
{
return $this->_auth->revoke($this->_name,$userId);
}
/**
* Returns a value indicating whether this item has been assigned to the user.
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return boolean whether the item has been assigned to the user.
* @see IAuthManager::isAssigned
*/
public function isAssigned($userId)
{
return $this->_auth->isAssigned($this->_name,$userId);
}
/**
* Returns the item assignment information.
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return CAuthAssignment the item assignment information. Null is returned if
* this item is not assigned to the user.
* @see IAuthManager::getAuthAssignment
*/
public function getAssignment($userId)
{
return $this->_auth->getAuthAssignment($this->_name,$userId);
}
}

View File

@@ -0,0 +1,165 @@
<?php
/**
* CAuthManager 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/
*/
/**
* CAuthManager is the base class for authorization manager classes.
*
* CAuthManager extends {@link CApplicationComponent} and implements some methods
* that are common among authorization manager classes.
*
* CAuthManager together with its concrete child classes implement the Role-Based
* Access Control (RBAC).
*
* The main idea is that permissions are organized as a hierarchy of
* {@link CAuthItem authorization items}. Items on higer level inherit the permissions
* represented by items on lower level. And roles are simply top-level authorization items
* that may be assigned to individual users. A user is said to have a permission
* to do something if the corresponding authorization item is inherited by one of his roles.
*
* Using authorization manager consists of two aspects. First, the authorization hierarchy
* and assignments have to be established. CAuthManager and its child classes
* provides APIs to accomplish this task. Developers may need to develop some GUI
* so that it is more intuitive to end-users. Second, developers call {@link IAuthManager::checkAccess}
* at appropriate places in the application code to check if the current user
* has the needed permission for an operation.
*
* @property array $roles Roles (name=>CAuthItem).
* @property array $tasks Tasks (name=>CAuthItem).
* @property array $operations Operations (name=>CAuthItem).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
abstract class CAuthManager extends CApplicationComponent implements IAuthManager
{
/**
* @var boolean Enable error reporting for bizRules.
* @since 1.1.3
*/
public $showErrors = false;
/**
* @var array list of role names that are assigned to all users implicitly.
* These roles do not need to be explicitly assigned to any user.
* When calling {@link checkAccess}, these roles will be checked first.
* For performance reason, you should minimize the number of such roles.
* A typical usage of such roles is to define an 'authenticated' role and associate
* it with a biz rule which checks if the current user is authenticated.
* And then declare 'authenticated' in this property so that it can be applied to
* every authenticated user.
*/
public $defaultRoles=array();
/**
* Creates a role.
* This is a shortcut method to {@link IAuthManager::createAuthItem}.
* @param string $name the item name
* @param string $description the item description.
* @param string $bizRule the business rule associated with this item
* @param mixed $data additional data to be passed when evaluating the business rule
* @return CAuthItem the authorization item
*/
public function createRole($name,$description='',$bizRule=null,$data=null)
{
return $this->createAuthItem($name,CAuthItem::TYPE_ROLE,$description,$bizRule,$data);
}
/**
* Creates a task.
* This is a shortcut method to {@link IAuthManager::createAuthItem}.
* @param string $name the item name
* @param string $description the item description.
* @param string $bizRule the business rule associated with this item
* @param mixed $data additional data to be passed when evaluating the business rule
* @return CAuthItem the authorization item
*/
public function createTask($name,$description='',$bizRule=null,$data=null)
{
return $this->createAuthItem($name,CAuthItem::TYPE_TASK,$description,$bizRule,$data);
}
/**
* Creates an operation.
* This is a shortcut method to {@link IAuthManager::createAuthItem}.
* @param string $name the item name
* @param string $description the item description.
* @param string $bizRule the business rule associated with this item
* @param mixed $data additional data to be passed when evaluating the business rule
* @return CAuthItem the authorization item
*/
public function createOperation($name,$description='',$bizRule=null,$data=null)
{
return $this->createAuthItem($name,CAuthItem::TYPE_OPERATION,$description,$bizRule,$data);
}
/**
* Returns roles.
* This is a shortcut method to {@link IAuthManager::getAuthItems}.
* @param mixed $userId the user ID. If not null, only the roles directly assigned to the user
* will be returned. Otherwise, all roles will be returned.
* @return array roles (name=>CAuthItem)
*/
public function getRoles($userId=null)
{
return $this->getAuthItems(CAuthItem::TYPE_ROLE,$userId);
}
/**
* Returns tasks.
* This is a shortcut method to {@link IAuthManager::getAuthItems}.
* @param mixed $userId the user ID. If not null, only the tasks directly assigned to the user
* will be returned. Otherwise, all tasks will be returned.
* @return array tasks (name=>CAuthItem)
*/
public function getTasks($userId=null)
{
return $this->getAuthItems(CAuthItem::TYPE_TASK,$userId);
}
/**
* Returns operations.
* This is a shortcut method to {@link IAuthManager::getAuthItems}.
* @param mixed $userId the user ID. If not null, only the operations directly assigned to the user
* will be returned. Otherwise, all operations will be returned.
* @return array operations (name=>CAuthItem)
*/
public function getOperations($userId=null)
{
return $this->getAuthItems(CAuthItem::TYPE_OPERATION,$userId);
}
/**
* Executes the specified business rule.
* @param string $bizRule the business rule to be executed.
* @param array $params parameters passed to {@link IAuthManager::checkAccess}.
* @param mixed $data additional data associated with the authorization item or assignment.
* @return boolean whether the business rule returns true.
* If the business rule is empty, it will still return true.
*/
public function executeBizRule($bizRule,$params,$data)
{
return $bizRule==='' || $bizRule===null || ($this->showErrors ? eval($bizRule)!=0 : @eval($bizRule)!=0);
}
/**
* Checks the item types to make sure a child can be added to a parent.
* @param integer $parentType parent item type
* @param integer $childType child item type
* @throws CException if the item cannot be added as a child due to its incompatible type.
*/
protected function checkItemChildType($parentType,$childType)
{
static $types=array('operation','task','role');
if($parentType < $childType)
throw new CException(Yii::t('yii','Cannot add an item of type "{child}" to an item of type "{parent}".',
array('{child}'=>$types[$childType], '{parent}'=>$types[$parentType])));
}
}

View File

@@ -0,0 +1,131 @@
<?php
/**
* CBaseUserIdentity 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/
*/
/**
* CBaseUserIdentity is a base class implementing {@link IUserIdentity}.
*
* CBaseUserIdentity implements the scheme for representing identity
* information that needs to be persisted. It also provides the way
* to represent the authentication errors.
*
* Derived classes should implement {@link IUserIdentity::authenticate}
* and {@link IUserIdentity::getId} that are required by the {@link IUserIdentity}
* interface.
*
* @property mixed $id A value that uniquely represents the identity (e.g. primary key value).
* The default implementation simply returns {@link name}.
* @property string $name The display name for the identity.
* The default implementation simply returns empty string.
* @property array $persistentStates The identity states that should be persisted.
* @property boolean $isAuthenticated Whether the authentication is successful.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
abstract class CBaseUserIdentity extends CComponent implements IUserIdentity
{
const ERROR_NONE=0;
const ERROR_USERNAME_INVALID=1;
const ERROR_PASSWORD_INVALID=2;
const ERROR_UNKNOWN_IDENTITY=100;
/**
* @var integer the authentication error code. If there is an error, the error code will be non-zero.
* Defaults to 100, meaning unknown identity. Calling {@link authenticate} will change this value.
*/
public $errorCode=self::ERROR_UNKNOWN_IDENTITY;
/**
* @var string the authentication error message. Defaults to empty.
*/
public $errorMessage='';
private $_state=array();
/**
* Returns a value that uniquely represents the identity.
* @return mixed a value that uniquely represents the identity (e.g. primary key value).
* The default implementation simply returns {@link name}.
*/
public function getId()
{
return $this->getName();
}
/**
* Returns the display name for the identity (e.g. username).
* @return string the display name for the identity.
* The default implementation simply returns empty string.
*/
public function getName()
{
return '';
}
/**
* Returns the identity states that should be persisted.
* This method is required by {@link IUserIdentity}.
* @return array the identity states that should be persisted.
*/
public function getPersistentStates()
{
return $this->_state;
}
/**
* Sets an array of persistent states.
*
* @param array $states the identity states that should be persisted.
*/
public function setPersistentStates($states)
{
$this->_state = $states;
}
/**
* Returns a value indicating whether the identity is authenticated.
* This method is required by {@link IUserIdentity}.
* @return boolean whether the authentication is successful.
*/
public function getIsAuthenticated()
{
return $this->errorCode==self::ERROR_NONE;
}
/**
* Gets the persisted state by the specified name.
* @param string $name the name of the state
* @param mixed $defaultValue the default value to be returned if the named state does not exist
* @return mixed the value of the named state
*/
public function getState($name,$defaultValue=null)
{
return isset($this->_state[$name])?$this->_state[$name]:$defaultValue;
}
/**
* Sets the named state with a given value.
* @param string $name the name of the state
* @param mixed $value the value of the named state
*/
public function setState($name,$value)
{
$this->_state[$name]=$value;
}
/**
* Removes the specified state.
* @param string $name the name of the state
*/
public function clearState($name)
{
unset($this->_state[$name]);
}
}

View File

@@ -0,0 +1,603 @@
<?php
/**
* CDbAuthManager 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/
*/
/**
* CDbAuthManager represents an authorization manager that stores authorization information in database.
*
* The database connection is specified by {@link connectionID}. And the database schema
* should be as described in "framework/web/auth/*.sql". You may change the names of
* the three tables used to store the authorization data by setting {@link itemTable},
* {@link itemChildTable} and {@link assignmentTable}.
*
* @property array $authItems The authorization items of the specific type.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
class CDbAuthManager extends CAuthManager
{
/**
* @var string the ID of the {@link CDbConnection} application component. Defaults to 'db'.
* The database must have the tables as declared in "framework/web/auth/*.sql".
*/
public $connectionID='db';
/**
* @var string the name of the table storing authorization items. Defaults to 'AuthItem'.
*/
public $itemTable='AuthItem';
/**
* @var string the name of the table storing authorization item hierarchy. Defaults to 'AuthItemChild'.
*/
public $itemChildTable='AuthItemChild';
/**
* @var string the name of the table storing authorization item assignments. Defaults to 'AuthAssignment'.
*/
public $assignmentTable='AuthAssignment';
/**
* @var CDbConnection the database connection. By default, this is initialized
* automatically as the application component whose ID is indicated as {@link connectionID}.
*/
public $db;
private $_usingSqlite;
/**
* Initializes the application component.
* This method overrides the parent implementation by establishing the database connection.
*/
public function init()
{
parent::init();
$this->_usingSqlite=!strncmp($this->getDbConnection()->getDriverName(),'sqlite',6);
}
/**
* Performs access check for the specified user.
* @param string $itemName the name of the operation that need access check
* @param mixed $userId the user ID. This should can be either an integer and a string representing
* the unique identifier of a user. See {@link IWebUser::getId}.
* @param array $params name-value pairs that would be passed to biz rules associated
* with the tasks and roles assigned to the user.
* Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of <code>$userId</code>.
* @return boolean whether the operations can be performed by the user.
*/
public function checkAccess($itemName,$userId,$params=array())
{
$assignments=$this->getAuthAssignments($userId);
return $this->checkAccessRecursive($itemName,$userId,$params,$assignments);
}
/**
* Performs access check for the specified user.
* This method is internally called by {@link checkAccess}.
* @param string $itemName the name of the operation that need access check
* @param mixed $userId the user ID. This should can be either an integer and a string representing
* the unique identifier of a user. See {@link IWebUser::getId}.
* @param array $params name-value pairs that would be passed to biz rules associated
* with the tasks and roles assigned to the user.
* Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of <code>$userId</code>.
* @param array $assignments the assignments to the specified user
* @return boolean whether the operations can be performed by the user.
* @since 1.1.3
*/
protected function checkAccessRecursive($itemName,$userId,$params,$assignments)
{
if(($item=$this->getAuthItem($itemName))===null)
return false;
Yii::trace('Checking permission "'.$item->getName().'"','system.web.auth.CDbAuthManager');
if(!isset($params['userId']))
$params['userId'] = $userId;
if($this->executeBizRule($item->getBizRule(),$params,$item->getData()))
{
if(in_array($itemName,$this->defaultRoles))
return true;
if(isset($assignments[$itemName]))
{
$assignment=$assignments[$itemName];
if($this->executeBizRule($assignment->getBizRule(),$params,$assignment->getData()))
return true;
}
$parents=$this->db->createCommand()
->select('parent')
->from($this->itemChildTable)
->where('child=:name', array(':name'=>$itemName))
->queryColumn();
foreach($parents as $parent)
{
if($this->checkAccessRecursive($parent,$userId,$params,$assignments))
return true;
}
}
return false;
}
/**
* Adds an item as a child of another item.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the item is added successfully
* @throws CException if either parent or child doesn't exist or if a loop has been detected.
*/
public function addItemChild($itemName,$childName)
{
if($itemName===$childName)
throw new CException(Yii::t('yii','Cannot add "{name}" as a child of itself.',
array('{name}'=>$itemName)));
$rows=$this->db->createCommand()
->select()
->from($this->itemTable)
->where('name=:name1 OR name=:name2', array(
':name1'=>$itemName,
':name2'=>$childName
))
->queryAll();
if(count($rows)==2)
{
if($rows[0]['name']===$itemName)
{
$parentType=$rows[0]['type'];
$childType=$rows[1]['type'];
}
else
{
$childType=$rows[0]['type'];
$parentType=$rows[1]['type'];
}
$this->checkItemChildType($parentType,$childType);
if($this->detectLoop($itemName,$childName))
throw new CException(Yii::t('yii','Cannot add "{child}" as a child of "{name}". A loop has been detected.',
array('{child}'=>$childName,'{name}'=>$itemName)));
$this->db->createCommand()
->insert($this->itemChildTable, array(
'parent'=>$itemName,
'child'=>$childName,
));
return true;
}
else
throw new CException(Yii::t('yii','Either "{parent}" or "{child}" does not exist.',array('{child}'=>$childName,'{parent}'=>$itemName)));
}
/**
* Removes a child from its parent.
* Note, the child item is not deleted. Only the parent-child relationship is removed.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the removal is successful
*/
public function removeItemChild($itemName,$childName)
{
return $this->db->createCommand()
->delete($this->itemChildTable, 'parent=:parent AND child=:child', array(
':parent'=>$itemName,
':child'=>$childName
)) > 0;
}
/**
* Returns a value indicating whether a child exists within a parent.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the child exists
*/
public function hasItemChild($itemName,$childName)
{
return $this->db->createCommand()
->select('parent')
->from($this->itemChildTable)
->where('parent=:parent AND child=:child', array(
':parent'=>$itemName,
':child'=>$childName))
->queryScalar() !== false;
}
/**
* Returns the children of the specified item.
* @param mixed $names the parent item name. This can be either a string or an array.
* The latter represents a list of item names.
* @return array all child items of the parent
*/
public function getItemChildren($names)
{
if(is_string($names))
$condition='parent='.$this->db->quoteValue($names);
elseif(is_array($names) && $names!==array())
{
foreach($names as &$name)
$name=$this->db->quoteValue($name);
$condition='parent IN ('.implode(', ',$names).')';
}
$rows=$this->db->createCommand()
->select('name, type, description, bizrule, data')
->from(array(
$this->itemTable,
$this->itemChildTable
))
->where($condition.' AND name=child')
->queryAll();
$children=array();
foreach($rows as $row)
{
if(($data=@unserialize($row['data']))===false)
$data=null;
$children[$row['name']]=new CAuthItem($this,$row['name'],$row['type'],$row['description'],$row['bizrule'],$data);
}
return $children;
}
/**
* Assigns an authorization item to a user.
* @param string $itemName the item name
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @param string $bizRule the business rule to be executed when {@link checkAccess} is called
* for this particular authorization item.
* @param mixed $data additional data associated with this assignment
* @return CAuthAssignment the authorization assignment information.
* @throws CException if the item does not exist or if the item has already been assigned to the user
*/
public function assign($itemName,$userId,$bizRule=null,$data=null)
{
if($this->usingSqlite() && $this->getAuthItem($itemName)===null)
throw new CException(Yii::t('yii','The item "{name}" does not exist.',array('{name}'=>$itemName)));
$this->db->createCommand()
->insert($this->assignmentTable, array(
'itemname'=>$itemName,
'userid'=>$userId,
'bizrule'=>$bizRule,
'data'=>serialize($data)
));
return new CAuthAssignment($this,$itemName,$userId,$bizRule,$data);
}
/**
* Revokes an authorization assignment from a user.
* @param string $itemName the item name
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return boolean whether removal is successful
*/
public function revoke($itemName,$userId)
{
return $this->db->createCommand()
->delete($this->assignmentTable, 'itemname=:itemname AND userid=:userid', array(
':itemname'=>$itemName,
':userid'=>$userId
)) > 0;
}
/**
* Returns a value indicating whether the item has been assigned to the user.
* @param string $itemName the item name
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return boolean whether the item has been assigned to the user.
*/
public function isAssigned($itemName,$userId)
{
return $this->db->createCommand()
->select('itemname')
->from($this->assignmentTable)
->where('itemname=:itemname AND userid=:userid', array(
':itemname'=>$itemName,
':userid'=>$userId))
->queryScalar() !== false;
}
/**
* Returns the item assignment information.
* @param string $itemName the item name
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return CAuthAssignment the item assignment information. Null is returned if
* the item is not assigned to the user.
*/
public function getAuthAssignment($itemName,$userId)
{
$row=$this->db->createCommand()
->select()
->from($this->assignmentTable)
->where('itemname=:itemname AND userid=:userid', array(
':itemname'=>$itemName,
':userid'=>$userId))
->queryRow();
if($row!==false)
{
if(($data=@unserialize($row['data']))===false)
$data=null;
return new CAuthAssignment($this,$row['itemname'],$row['userid'],$row['bizrule'],$data);
}
else
return null;
}
/**
* Returns the item assignments for the specified user.
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return array the item assignment information for the user. An empty array will be
* returned if there is no item assigned to the user.
*/
public function getAuthAssignments($userId)
{
$rows=$this->db->createCommand()
->select()
->from($this->assignmentTable)
->where('userid=:userid', array(':userid'=>$userId))
->queryAll();
$assignments=array();
foreach($rows as $row)
{
if(($data=@unserialize($row['data']))===false)
$data=null;
$assignments[$row['itemname']]=new CAuthAssignment($this,$row['itemname'],$row['userid'],$row['bizrule'],$data);
}
return $assignments;
}
/**
* Saves the changes to an authorization assignment.
* @param CAuthAssignment $assignment the assignment that has been changed.
*/
public function saveAuthAssignment($assignment)
{
$this->db->createCommand()
->update($this->assignmentTable, array(
'bizrule'=>$assignment->getBizRule(),
'data'=>serialize($assignment->getData()),
), 'itemname=:itemname AND userid=:userid', array(
'itemname'=>$assignment->getItemName(),
'userid'=>$assignment->getUserId()
));
}
/**
* Returns the authorization items of the specific type and user.
* @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
* meaning returning all items regardless of their type.
* @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
* they are not assigned to a user.
* @return array the authorization items of the specific type.
*/
public function getAuthItems($type=null,$userId=null)
{
if($type===null && $userId===null)
{
$command=$this->db->createCommand()
->select()
->from($this->itemTable);
}
elseif($userId===null)
{
$command=$this->db->createCommand()
->select()
->from($this->itemTable)
->where('type=:type', array(':type'=>$type));
}
elseif($type===null)
{
$command=$this->db->createCommand()
->select('name,type,description,t1.bizrule,t1.data')
->from(array(
$this->itemTable.' t1',
$this->assignmentTable.' t2'
))
->where('name=itemname AND userid=:userid', array(':userid'=>$userId));
}
else
{
$command=$this->db->createCommand()
->select('name,type,description,t1.bizrule,t1.data')
->from(array(
$this->itemTable.' t1',
$this->assignmentTable.' t2'
))
->where('name=itemname AND type=:type AND userid=:userid', array(
':type'=>$type,
':userid'=>$userId
));
}
$items=array();
foreach($command->queryAll() as $row)
{
if(($data=@unserialize($row['data']))===false)
$data=null;
$items[$row['name']]=new CAuthItem($this,$row['name'],$row['type'],$row['description'],$row['bizrule'],$data);
}
return $items;
}
/**
* Creates an authorization item.
* An authorization item represents an action permission (e.g. creating a post).
* It has three types: operation, task and role.
* Authorization items form a hierarchy. Higher level items inheirt permissions representing
* by lower level items.
* @param string $name the item name. This must be a unique identifier.
* @param integer $type the item type (0: operation, 1: task, 2: role).
* @param string $description description of the item
* @param string $bizRule business rule associated with the item. This is a piece of
* PHP code that will be executed when {@link checkAccess} is called for the item.
* @param mixed $data additional data associated with the item.
* @return CAuthItem the authorization item
* @throws CException if an item with the same name already exists
*/
public function createAuthItem($name,$type,$description='',$bizRule=null,$data=null)
{
$this->db->createCommand()
->insert($this->itemTable, array(
'name'=>$name,
'type'=>$type,
'description'=>$description,
'bizrule'=>$bizRule,
'data'=>serialize($data)
));
return new CAuthItem($this,$name,$type,$description,$bizRule,$data);
}
/**
* Removes the specified authorization item.
* @param string $name the name of the item to be removed
* @return boolean whether the item exists in the storage and has been removed
*/
public function removeAuthItem($name)
{
if($this->usingSqlite())
{
$this->db->createCommand()
->delete($this->itemChildTable, 'parent=:name1 OR child=:name2', array(
':name1'=>$name,
':name2'=>$name
));
$this->db->createCommand()
->delete($this->assignmentTable, 'itemname=:name', array(
':name'=>$name,
));
}
return $this->db->createCommand()
->delete($this->itemTable, 'name=:name', array(
':name'=>$name
)) > 0;
}
/**
* Returns the authorization item with the specified name.
* @param string $name the name of the item
* @return CAuthItem the authorization item. Null if the item cannot be found.
*/
public function getAuthItem($name)
{
$row=$this->db->createCommand()
->select()
->from($this->itemTable)
->where('name=:name', array(':name'=>$name))
->queryRow();
if($row!==false)
{
if(($data=@unserialize($row['data']))===false)
$data=null;
return new CAuthItem($this,$row['name'],$row['type'],$row['description'],$row['bizrule'],$data);
}
else
return null;
}
/**
* Saves an authorization item to persistent storage.
* @param CAuthItem $item the item to be saved.
* @param string $oldName the old item name. If null, it means the item name is not changed.
*/
public function saveAuthItem($item,$oldName=null)
{
if($this->usingSqlite() && $oldName!==null && $item->getName()!==$oldName)
{
$this->db->createCommand()
->update($this->itemChildTable, array(
'parent'=>$item->getName(),
), 'parent=:whereName', array(
':whereName'=>$oldName,
));
$this->db->createCommand()
->update($this->itemChildTable, array(
'child'=>$item->getName(),
), 'child=:whereName', array(
':whereName'=>$oldName,
));
$this->db->createCommand()
->update($this->assignmentTable, array(
'itemname'=>$item->getName(),
), 'itemname=:whereName', array(
':whereName'=>$oldName,
));
}
$this->db->createCommand()
->update($this->itemTable, array(
'name'=>$item->getName(),
'type'=>$item->getType(),
'description'=>$item->getDescription(),
'bizrule'=>$item->getBizRule(),
'data'=>serialize($item->getData()),
), 'name=:whereName', array(
':whereName'=>$oldName===null?$item->getName():$oldName,
));
}
/**
* Saves the authorization data to persistent storage.
*/
public function save()
{
}
/**
* Removes all authorization data.
*/
public function clearAll()
{
$this->clearAuthAssignments();
$this->db->createCommand()->delete($this->itemChildTable);
$this->db->createCommand()->delete($this->itemTable);
}
/**
* Removes all authorization assignments.
*/
public function clearAuthAssignments()
{
$this->db->createCommand()->delete($this->assignmentTable);
}
/**
* Checks whether there is a loop in the authorization item hierarchy.
* @param string $itemName parent item name
* @param string $childName the name of the child item that is to be added to the hierarchy
* @return boolean whether a loop exists
*/
protected function detectLoop($itemName,$childName)
{
if($childName===$itemName)
return true;
foreach($this->getItemChildren($childName) as $child)
{
if($this->detectLoop($itemName,$child->getName()))
return true;
}
return false;
}
/**
* @return CDbConnection the DB connection instance
* @throws CException if {@link connectionID} does not point to a valid application component.
*/
protected function getDbConnection()
{
if($this->db!==null)
return $this->db;
elseif(($this->db=Yii::app()->getComponent($this->connectionID)) instanceof CDbConnection)
return $this->db;
else
throw new CException(Yii::t('yii','CDbAuthManager.connectionID "{id}" is invalid. Please make sure it refers to the ID of a CDbConnection application component.',
array('{id}'=>$this->connectionID)));
}
/**
* @return boolean whether the database is a SQLite database
*/
protected function usingSqlite()
{
return $this->_usingSqlite;
}
}

View File

@@ -0,0 +1,506 @@
<?php
/**
* CPhpAuthManager 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/
*/
/**
* CPhpAuthManager represents an authorization manager that stores authorization information in terms of a PHP script file.
*
* The authorization data will be saved to and loaded from a file
* specified by {@link authFile}, which defaults to 'protected/data/auth.php'.
*
* CPhpAuthManager is mainly suitable for authorization data that is not too big
* (for example, the authorization data for a personal blog system).
* Use {@link CDbAuthManager} for more complex authorization data.
*
* @property array $authItems The authorization items of the specific type.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
class CPhpAuthManager extends CAuthManager
{
/**
* @var string the path of the PHP script that contains the authorization data.
* If not set, it will be using 'protected/data/auth.php' as the data file.
* Make sure this file is writable by the Web server process if the authorization
* needs to be changed.
* @see loadFromFile
* @see saveToFile
*/
public $authFile;
private $_items=array(); // itemName => item
private $_children=array(); // itemName, childName => child
private $_assignments=array(); // userId, itemName => assignment
/**
* Initializes the application component.
* This method overrides parent implementation by loading the authorization data
* from PHP script.
*/
public function init()
{
parent::init();
if($this->authFile===null)
$this->authFile=Yii::getPathOfAlias('application.data.auth').'.php';
$this->load();
}
/**
* Performs access check for the specified user.
* @param string $itemName the name of the operation that need access check
* @param mixed $userId the user ID. This can be either an integer or a string representing
* the unique identifier of a user. See {@link IWebUser::getId}.
* @param array $params name-value pairs that would be passed to biz rules associated
* with the tasks and roles assigned to the user.
* Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of <code>$userId</code>.
* @return boolean whether the operations can be performed by the user.
*/
public function checkAccess($itemName,$userId,$params=array())
{
if(!isset($this->_items[$itemName]))
return false;
$item=$this->_items[$itemName];
Yii::trace('Checking permission "'.$item->getName().'"','system.web.auth.CPhpAuthManager');
if(!isset($params['userId']))
$params['userId'] = $userId;
if($this->executeBizRule($item->getBizRule(),$params,$item->getData()))
{
if(in_array($itemName,$this->defaultRoles))
return true;
if(isset($this->_assignments[$userId][$itemName]))
{
$assignment=$this->_assignments[$userId][$itemName];
if($this->executeBizRule($assignment->getBizRule(),$params,$assignment->getData()))
return true;
}
foreach($this->_children as $parentName=>$children)
{
if(isset($children[$itemName]) && $this->checkAccess($parentName,$userId,$params))
return true;
}
}
return false;
}
/**
* Adds an item as a child of another item.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the item is added successfully
* @throws CException if either parent or child doesn't exist or if a loop has been detected.
*/
public function addItemChild($itemName,$childName)
{
if(!isset($this->_items[$childName],$this->_items[$itemName]))
throw new CException(Yii::t('yii','Either "{parent}" or "{child}" does not exist.',array('{child}'=>$childName,'{name}'=>$itemName)));
$child=$this->_items[$childName];
$item=$this->_items[$itemName];
$this->checkItemChildType($item->getType(),$child->getType());
if($this->detectLoop($itemName,$childName))
throw new CException(Yii::t('yii','Cannot add "{child}" as a child of "{parent}". A loop has been detected.',
array('{child}'=>$childName,'{parent}'=>$itemName)));
if(isset($this->_children[$itemName][$childName]))
throw new CException(Yii::t('yii','The item "{parent}" already has a child "{child}".',
array('{child}'=>$childName,'{parent}'=>$itemName)));
$this->_children[$itemName][$childName]=$this->_items[$childName];
return true;
}
/**
* Removes a child from its parent.
* Note, the child item is not deleted. Only the parent-child relationship is removed.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the removal is successful
*/
public function removeItemChild($itemName,$childName)
{
if(isset($this->_children[$itemName][$childName]))
{
unset($this->_children[$itemName][$childName]);
return true;
}
else
return false;
}
/**
* Returns a value indicating whether a child exists within a parent.
* @param string $itemName the parent item name
* @param string $childName the child item name
* @return boolean whether the child exists
*/
public function hasItemChild($itemName,$childName)
{
return isset($this->_children[$itemName][$childName]);
}
/**
* Returns the children of the specified item.
* @param mixed $names the parent item name. This can be either a string or an array.
* The latter represents a list of item names.
* @return array all child items of the parent
*/
public function getItemChildren($names)
{
if(is_string($names))
return isset($this->_children[$names]) ? $this->_children[$names] : array();
$children=array();
foreach($names as $name)
{
if(isset($this->_children[$name]))
$children=array_merge($children,$this->_children[$name]);
}
return $children;
}
/**
* Assigns an authorization item to a user.
* @param string $itemName the item name
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @param string $bizRule the business rule to be executed when {@link checkAccess} is called
* for this particular authorization item.
* @param mixed $data additional data associated with this assignment
* @return CAuthAssignment the authorization assignment information.
* @throws CException if the item does not exist or if the item has already been assigned to the user
*/
public function assign($itemName,$userId,$bizRule=null,$data=null)
{
if(!isset($this->_items[$itemName]))
throw new CException(Yii::t('yii','Unknown authorization item "{name}".',array('{name}'=>$itemName)));
elseif(isset($this->_assignments[$userId][$itemName]))
throw new CException(Yii::t('yii','Authorization item "{item}" has already been assigned to user "{user}".',
array('{item}'=>$itemName,'{user}'=>$userId)));
else
return $this->_assignments[$userId][$itemName]=new CAuthAssignment($this,$itemName,$userId,$bizRule,$data);
}
/**
* Revokes an authorization assignment from a user.
* @param string $itemName the item name
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return boolean whether removal is successful
*/
public function revoke($itemName,$userId)
{
if(isset($this->_assignments[$userId][$itemName]))
{
unset($this->_assignments[$userId][$itemName]);
return true;
}
else
return false;
}
/**
* Returns a value indicating whether the item has been assigned to the user.
* @param string $itemName the item name
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return boolean whether the item has been assigned to the user.
*/
public function isAssigned($itemName,$userId)
{
return isset($this->_assignments[$userId][$itemName]);
}
/**
* Returns the item assignment information.
* @param string $itemName the item name
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return CAuthAssignment the item assignment information. Null is returned if
* the item is not assigned to the user.
*/
public function getAuthAssignment($itemName,$userId)
{
return isset($this->_assignments[$userId][$itemName])?$this->_assignments[$userId][$itemName]:null;
}
/**
* Returns the item assignments for the specified user.
* @param mixed $userId the user ID (see {@link IWebUser::getId})
* @return array the item assignment information for the user. An empty array will be
* returned if there is no item assigned to the user.
*/
public function getAuthAssignments($userId)
{
return isset($this->_assignments[$userId])?$this->_assignments[$userId]:array();
}
/**
* Returns the authorization items of the specific type and user.
* @param integer $type the item type (0: operation, 1: task, 2: role). Defaults to null,
* meaning returning all items regardless of their type.
* @param mixed $userId the user ID. Defaults to null, meaning returning all items even if
* they are not assigned to a user.
* @return array the authorization items of the specific type.
*/
public function getAuthItems($type=null,$userId=null)
{
if($type===null && $userId===null)
return $this->_items;
$items=array();
if($userId===null)
{
foreach($this->_items as $name=>$item)
{
if($item->getType()==$type)
$items[$name]=$item;
}
}
elseif(isset($this->_assignments[$userId]))
{
foreach($this->_assignments[$userId] as $assignment)
{
$name=$assignment->getItemName();
if(isset($this->_items[$name]) && ($type===null || $this->_items[$name]->getType()==$type))
$items[$name]=$this->_items[$name];
}
}
return $items;
}
/**
* Creates an authorization item.
* An authorization item represents an action permission (e.g. creating a post).
* It has three types: operation, task and role.
* Authorization items form a hierarchy. Higher level items inheirt permissions representing
* by lower level items.
* @param string $name the item name. This must be a unique identifier.
* @param integer $type the item type (0: operation, 1: task, 2: role).
* @param string $description description of the item
* @param string $bizRule business rule associated with the item. This is a piece of
* PHP code that will be executed when {@link checkAccess} is called for the item.
* @param mixed $data additional data associated with the item.
* @return CAuthItem the authorization item
* @throws CException if an item with the same name already exists
*/
public function createAuthItem($name,$type,$description='',$bizRule=null,$data=null)
{
if(isset($this->_items[$name]))
throw new CException(Yii::t('yii','Unable to add an item whose name is the same as an existing item.'));
return $this->_items[$name]=new CAuthItem($this,$name,$type,$description,$bizRule,$data);
}
/**
* Removes the specified authorization item.
* @param string $name the name of the item to be removed
* @return boolean whether the item exists in the storage and has been removed
*/
public function removeAuthItem($name)
{
if(isset($this->_items[$name]))
{
foreach($this->_children as &$children)
unset($children[$name]);
foreach($this->_assignments as &$assignments)
unset($assignments[$name]);
unset($this->_items[$name]);
return true;
}
else
return false;
}
/**
* Returns the authorization item with the specified name.
* @param string $name the name of the item
* @return CAuthItem the authorization item. Null if the item cannot be found.
*/
public function getAuthItem($name)
{
return isset($this->_items[$name])?$this->_items[$name]:null;
}
/**
* Saves an authorization item to persistent storage.
* @param CAuthItem $item the item to be saved.
* @param string $oldName the old item name. If null, it means the item name is not changed.
*/
public function saveAuthItem($item,$oldName=null)
{
if($oldName!==null && ($newName=$item->getName())!==$oldName) // name changed
{
if(isset($this->_items[$newName]))
throw new CException(Yii::t('yii','Unable to change the item name. The name "{name}" is already used by another item.',array('{name}'=>$newName)));
if(isset($this->_items[$oldName]) && $this->_items[$oldName]===$item)
{
unset($this->_items[$oldName]);
$this->_items[$newName]=$item;
if(isset($this->_children[$oldName]))
{
$this->_children[$newName]=$this->_children[$oldName];
unset($this->_children[$oldName]);
}
foreach($this->_children as &$children)
{
if(isset($children[$oldName]))
{
$children[$newName]=$children[$oldName];
unset($children[$oldName]);
}
}
foreach($this->_assignments as &$assignments)
{
if(isset($assignments[$oldName]))
{
$assignments[$newName]=$assignments[$oldName];
unset($assignments[$oldName]);
}
}
}
}
}
/**
* Saves the changes to an authorization assignment.
* @param CAuthAssignment $assignment the assignment that has been changed.
*/
public function saveAuthAssignment($assignment)
{
}
/**
* Saves authorization data into persistent storage.
* If any change is made to the authorization data, please make
* sure you call this method to save the changed data into persistent storage.
*/
public function save()
{
$items=array();
foreach($this->_items as $name=>$item)
{
$items[$name]=array(
'type'=>$item->getType(),
'description'=>$item->getDescription(),
'bizRule'=>$item->getBizRule(),
'data'=>$item->getData(),
);
if(isset($this->_children[$name]))
{
foreach($this->_children[$name] as $child)
$items[$name]['children'][]=$child->getName();
}
}
foreach($this->_assignments as $userId=>$assignments)
{
foreach($assignments as $name=>$assignment)
{
if(isset($items[$name]))
{
$items[$name]['assignments'][$userId]=array(
'bizRule'=>$assignment->getBizRule(),
'data'=>$assignment->getData(),
);
}
}
}
$this->saveToFile($items,$this->authFile);
}
/**
* Loads authorization data.
*/
public function load()
{
$this->clearAll();
$items=$this->loadFromFile($this->authFile);
foreach($items as $name=>$item)
$this->_items[$name]=new CAuthItem($this,$name,$item['type'],$item['description'],$item['bizRule'],$item['data']);
foreach($items as $name=>$item)
{
if(isset($item['children']))
{
foreach($item['children'] as $childName)
{
if(isset($this->_items[$childName]))
$this->_children[$name][$childName]=$this->_items[$childName];
}
}
if(isset($item['assignments']))
{
foreach($item['assignments'] as $userId=>$assignment)
{
$this->_assignments[$userId][$name]=new CAuthAssignment($this,$name,$userId,$assignment['bizRule'],$assignment['data']);
}
}
}
}
/**
* Removes all authorization data.
*/
public function clearAll()
{
$this->clearAuthAssignments();
$this->_children=array();
$this->_items=array();
}
/**
* Removes all authorization assignments.
*/
public function clearAuthAssignments()
{
$this->_assignments=array();
}
/**
* Checks whether there is a loop in the authorization item hierarchy.
* @param string $itemName parent item name
* @param string $childName the name of the child item that is to be added to the hierarchy
* @return boolean whether a loop exists
*/
protected function detectLoop($itemName,$childName)
{
if($childName===$itemName)
return true;
if(!isset($this->_children[$childName], $this->_items[$itemName]))
return false;
foreach($this->_children[$childName] as $child)
{
if($this->detectLoop($itemName,$child->getName()))
return true;
}
return false;
}
/**
* Loads the authorization data from a PHP script file.
* @param string $file the file path.
* @return array the authorization data
* @see saveToFile
*/
protected function loadFromFile($file)
{
if(is_file($file))
return require($file);
else
return array();
}
/**
* Saves the authorization data to a PHP script file.
* @param array $data the authorization data
* @param string $file the file path.
* @see loadFromFile
*/
protected function saveToFile($data,$file)
{
file_put_contents($file,"<?php\nreturn ".var_export($data,true).";\n");
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* CUserIdentity 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/
*/
/**
* CUserIdentity is a base class for representing identities that are authenticated based on a username and a password.
*
* Derived classes should implement {@link authenticate} with the actual
* authentication scheme (e.g. checking username and password against a DB table).
*
* By default, CUserIdentity assumes the {@link username} is a unique identifier
* and thus use it as the {@link id ID} of the identity.
*
* @property string $id The unique identifier for the identity.
* @property string $name The display name for the identity.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
class CUserIdentity extends CBaseUserIdentity
{
/**
* @var string username
*/
public $username;
/**
* @var string password
*/
public $password;
/**
* Constructor.
* @param string $username username
* @param string $password password
*/
public function __construct($username,$password)
{
$this->username=$username;
$this->password=$password;
}
/**
* Authenticates a user based on {@link username} and {@link password}.
* Derived classes should override this method, or an exception will be thrown.
* This method is required by {@link IUserIdentity}.
* @return boolean whether authentication succeeds.
*/
public function authenticate()
{
throw new CException(Yii::t('yii','{class}::authenticate() must be implemented.',array('{class}'=>get_class($this))));
}
/**
* Returns the unique identifier for the identity.
* The default implementation simply returns {@link username}.
* This method is required by {@link IUserIdentity}.
* @return string the unique identifier for the identity.
*/
public function getId()
{
return $this->username;
}
/**
* Returns the display name for the identity.
* The default implementation simply returns {@link username}.
* This method is required by {@link IUserIdentity}.
* @return string the display name for the identity.
*/
public function getName()
{
return $this->username;
}
}

View File

@@ -0,0 +1,821 @@
<?php
/**
* CWebUser 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/
*/
/**
* CWebUser represents the persistent state for a Web application user.
*
* CWebUser is used as an application component whose ID is 'user'.
* Therefore, at any place one can access the user state via
* <code>Yii::app()->user</code>.
*
* CWebUser should be used together with an {@link IUserIdentity identity}
* which implements the actual authentication algorithm.
*
* A typical authentication process using CWebUser is as follows:
* <ol>
* <li>The user provides information needed for authentication.</li>
* <li>An {@link IUserIdentity identity instance} is created with the user-provided information.</li>
* <li>Call {@link IUserIdentity::authenticate} to check if the identity is valid.</li>
* <li>If valid, call {@link CWebUser::login} to login the user, and
* Redirect the user browser to {@link returnUrl}.</li>
* <li>If not valid, retrieve the error code or message from the identity
* instance and display it.</li>
* </ol>
*
* The property {@link id} and {@link name} are both identifiers
* for the user. The former is mainly used internally (e.g. primary key), while
* the latter is for display purpose (e.g. username). The {@link id} property
* is a unique identifier for a user that is persistent
* during the whole user session. It can be a username, or something else,
* depending on the implementation of the {@link IUserIdentity identity class}.
*
* Both {@link id} and {@link name} are persistent during the user session.
* Besides, an identity may have additional persistent data which can
* be accessed by calling {@link getState}.
* Note, when {@link allowAutoLogin cookie-based authentication} is enabled,
* all these persistent data will be stored in cookie. Therefore, do not
* store password or other sensitive data in the persistent storage. Instead,
* you should store them directly in session on the server side if needed.
*
* @property boolean $isGuest Whether the current application user is a guest.
* @property mixed $id The unique identifier for the user. If null, it means the user is a guest.
* @property string $name The user name. If the user is not logged in, this will be {@link guestName}.
* @property string $returnUrl The URL that the user should be redirected to after login.
* @property string $stateKeyPrefix A prefix for the name of the session variables storing user session data.
* @property array $flashes Flash messages (key => message).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.auth
* @since 1.0
*/
class CWebUser extends CApplicationComponent implements IWebUser
{
const FLASH_KEY_PREFIX='Yii.CWebUser.flash.';
const FLASH_COUNTERS='Yii.CWebUser.flashcounters';
const STATES_VAR='__states';
const AUTH_TIMEOUT_VAR='__timeout';
const AUTH_ABSOLUTE_TIMEOUT_VAR='__absolute_timeout';
/**
* @var boolean whether to enable cookie-based login. Defaults to false.
*/
public $allowAutoLogin=false;
/**
* @var string the name for a guest user. Defaults to 'Guest'.
* This is used by {@link getName} when the current user is a guest (not authenticated).
*/
public $guestName='Guest';
/**
* @var string|array the URL for login. If using array, the first element should be
* the route to the login action, and the rest name-value pairs are GET parameters
* to construct the login URL (e.g. array('/site/login')). If this property is null,
* a 403 HTTP exception will be raised instead.
* @see CController::createUrl
*/
public $loginUrl=array('/site/login');
/**
* @var array the property values (in name-value pairs) used to initialize the identity cookie.
* Any property of {@link CHttpCookie} may be initialized.
* This property is effective only when {@link allowAutoLogin} is true.
*/
public $identityCookie;
/**
* @var integer timeout in seconds after which user is logged out if inactive.
* If this property is not set, the user will be logged out after the current session expires
* (c.f. {@link CHttpSession::timeout}).
* @since 1.1.7
*/
public $authTimeout;
/**
* @var integer timeout in seconds after which user is logged out regardless of activity.
* @since 1.1.14
*/
public $absoluteAuthTimeout;
/**
* @var boolean whether to automatically renew the identity cookie each time a page is requested.
* Defaults to false. This property is effective only when {@link allowAutoLogin} is true.
* When this is false, the identity cookie will expire after the specified duration since the user
* is initially logged in. When this is true, the identity cookie will expire after the specified duration
* since the user visits the site the last time.
* @see allowAutoLogin
* @since 1.1.0
*/
public $autoRenewCookie=false;
/**
* @var boolean whether to automatically update the validity of flash messages.
* Defaults to true, meaning flash messages will be valid only in the current and the next requests.
* If this is set false, you will be responsible for ensuring a flash message is deleted after usage.
* (This can be achieved by calling {@link getFlash} with the 3rd parameter being true).
* @since 1.1.7
*/
public $autoUpdateFlash=true;
/**
* @var string value that will be echoed in case that user session has expired during an ajax call.
* When a request is made and user session has expired, {@link loginRequired} redirects to {@link loginUrl} for login.
* If that happens during an ajax call, the complete HTML login page is returned as the result of that ajax call. That could be
* a problem if the ajax call expects the result to be a json array or a predefined string, as the login page is ignored in that case.
* To solve this, set this property to the desired return value.
*
* If this property is set, this value will be returned as the result of the ajax call in case that the user session has expired.
* @since 1.1.9
* @see loginRequired
*/
public $loginRequiredAjaxResponse;
private $_keyPrefix;
private $_access=array();
/**
* PHP magic method.
* This method is overriden so that persistent states can be accessed like properties.
* @param string $name property name
* @return mixed property value
*/
public function __get($name)
{
if($this->hasState($name))
return $this->getState($name);
else
return parent::__get($name);
}
/**
* PHP magic method.
* This method is overriden so that persistent states can be set like properties.
* @param string $name property name
* @param mixed $value property value
*/
public function __set($name,$value)
{
if($this->hasState($name))
$this->setState($name,$value);
else
parent::__set($name,$value);
}
/**
* PHP magic method.
* This method is overriden so that persistent states can also be checked for null value.
* @param string $name property name
* @return boolean
*/
public function __isset($name)
{
if($this->hasState($name))
return $this->getState($name)!==null;
else
return parent::__isset($name);
}
/**
* PHP magic method.
* This method is overriden so that persistent states can also be unset.
* @param string $name property name
* @throws CException if the property is read only.
*/
public function __unset($name)
{
if($this->hasState($name))
$this->setState($name,null);
else
parent::__unset($name);
}
/**
* Initializes the application component.
* This method overrides the parent implementation by starting session,
* performing cookie-based authentication if enabled, and updating the flash variables.
*/
public function init()
{
parent::init();
Yii::app()->getSession()->open();
if($this->getIsGuest() && $this->allowAutoLogin)
$this->restoreFromCookie();
elseif($this->autoRenewCookie && $this->allowAutoLogin)
$this->renewCookie();
if($this->autoUpdateFlash)
$this->updateFlash();
$this->updateAuthStatus();
}
/**
* Logs in a user.
*
* The user identity information will be saved in storage that is
* persistent during the user session. By default, the storage is simply
* the session storage. If the duration parameter is greater than 0,
* a cookie will be sent to prepare for cookie-based login in future.
*
* Note, you have to set {@link allowAutoLogin} to true
* if you want to allow user to be authenticated based on the cookie information.
*
* @param IUserIdentity $identity the user identity (which should already be authenticated)
* @param integer $duration number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser.
* If greater than 0, cookie-based login will be used. In this case, {@link allowAutoLogin}
* must be set true, otherwise an exception will be thrown.
* @return boolean whether the user is logged in
*/
public function login($identity,$duration=0)
{
$id=$identity->getId();
$states=$identity->getPersistentStates();
if($this->beforeLogin($id,$states,false))
{
$this->changeIdentity($id,$identity->getName(),$states);
if($duration>0)
{
if($this->allowAutoLogin)
$this->saveToCookie($duration);
else
throw new CException(Yii::t('yii','{class}.allowAutoLogin must be set true in order to use cookie-based authentication.',
array('{class}'=>get_class($this))));
}
if ($this->absoluteAuthTimeout)
$this->setState(self::AUTH_ABSOLUTE_TIMEOUT_VAR, time()+$this->absoluteAuthTimeout);
$this->afterLogin(false);
}
return !$this->getIsGuest();
}
/**
* Logs out the current user.
* This will remove authentication-related session data.
* If the parameter is true, the whole session will be destroyed as well.
* @param boolean $destroySession whether to destroy the whole session. Defaults to true. If false,
* then {@link clearStates} will be called, which removes only the data stored via {@link setState}.
*/
public function logout($destroySession=true)
{
if($this->beforeLogout())
{
if($this->allowAutoLogin)
{
Yii::app()->getRequest()->getCookies()->remove($this->getStateKeyPrefix());
if($this->identityCookie!==null)
{
$cookie=$this->createIdentityCookie($this->getStateKeyPrefix());
$cookie->value=null;
$cookie->expire=0;
Yii::app()->getRequest()->getCookies()->add($cookie->name,$cookie);
}
}
if($destroySession)
Yii::app()->getSession()->destroy();
else
$this->clearStates();
$this->_access=array();
$this->afterLogout();
}
}
/**
* Returns a value indicating whether the user is a guest (not authenticated).
* @return boolean whether the current application user is a guest.
*/
public function getIsGuest()
{
return $this->getState('__id')===null;
}
/**
* Returns a value that uniquely represents the user.
* @return mixed the unique identifier for the user. If null, it means the user is a guest.
*/
public function getId()
{
return $this->getState('__id');
}
/**
* @param mixed $value the unique identifier for the user. If null, it means the user is a guest.
*/
public function setId($value)
{
$this->setState('__id',$value);
}
/**
* Returns the unique identifier for the user (e.g. username).
* This is the unique identifier that is mainly used for display purpose.
* @return string the user name. If the user is not logged in, this will be {@link guestName}.
*/
public function getName()
{
if(($name=$this->getState('__name'))!==null)
return $name;
else
return $this->guestName;
}
/**
* Sets the unique identifier for the user (e.g. username).
* @param string $value the user name.
* @see getName
*/
public function setName($value)
{
$this->setState('__name',$value);
}
/**
* Returns the URL that the user should be redirected to after successful login.
* This property is usually used by the login action. If the login is successful,
* the action should read this property and use it to redirect the user browser.
* @param string $defaultUrl the default return URL in case it was not set previously. If this is null,
* the application entry URL will be considered as the default return URL.
* @return string the URL that the user should be redirected to after login.
* @see loginRequired
*/
public function getReturnUrl($defaultUrl=null)
{
if($defaultUrl===null)
{
$defaultReturnUrl=Yii::app()->getUrlManager()->showScriptName ? Yii::app()->getRequest()->getScriptUrl() : Yii::app()->getRequest()->getBaseUrl().'/';
}
else
{
$defaultReturnUrl=CHtml::normalizeUrl($defaultUrl);
}
return $this->getState('__returnUrl',$defaultReturnUrl);
}
/**
* @param string $value the URL that the user should be redirected to after login.
*/
public function setReturnUrl($value)
{
$this->setState('__returnUrl',$value);
}
/**
* Redirects the user browser to the login page.
* Before the redirection, the current URL (if it's not an AJAX url) will be
* kept in {@link returnUrl} so that the user browser may be redirected back
* to the current page after successful login. Make sure you set {@link loginUrl}
* so that the user browser can be redirected to the specified login URL after
* calling this method.
* After calling this method, the current request processing will be terminated.
*/
public function loginRequired()
{
$app=Yii::app();
$request=$app->getRequest();
if(!$request->getIsAjaxRequest())
{
$this->setReturnUrl($request->getUrl());
if(($url=$this->loginUrl)!==null)
{
if(is_array($url))
{
$route=isset($url[0]) ? $url[0] : $app->defaultController;
$url=$app->createUrl($route,array_splice($url,1));
}
$request->redirect($url);
}
}
elseif(isset($this->loginRequiredAjaxResponse))
{
echo $this->loginRequiredAjaxResponse;
Yii::app()->end();
}
throw new CHttpException(403,Yii::t('yii','Login Required'));
}
/**
* This method is called before logging in a user.
* You may override this method to provide additional security check.
* For example, when the login is cookie-based, you may want to verify
* that the user ID together with a random token in the states can be found
* in the database. This will prevent hackers from faking arbitrary
* identity cookies even if they crack down the server private key.
* @param mixed $id the user ID. This is the same as returned by {@link getId()}.
* @param array $states a set of name-value pairs that are provided by the user identity.
* @param boolean $fromCookie whether the login is based on cookie
* @return boolean whether the user should be logged in
* @since 1.1.3
*/
protected function beforeLogin($id,$states,$fromCookie)
{
return true;
}
/**
* This method is called after the user is successfully logged in.
* You may override this method to do some postprocessing (e.g. log the user
* login IP and time; load the user permission information).
* @param boolean $fromCookie whether the login is based on cookie.
* @since 1.1.3
*/
protected function afterLogin($fromCookie)
{
}
/**
* This method is invoked when calling {@link logout} to log out a user.
* If this method return false, the logout action will be cancelled.
* You may override this method to provide additional check before
* logging out a user.
* @return boolean whether to log out the user
* @since 1.1.3
*/
protected function beforeLogout()
{
return true;
}
/**
* This method is invoked right after a user is logged out.
* You may override this method to do some extra cleanup work for the user.
* @since 1.1.3
*/
protected function afterLogout()
{
}
/**
* Populates the current user object with the information obtained from cookie.
* This method is used when automatic login ({@link allowAutoLogin}) is enabled.
* The user identity information is recovered from cookie.
* Sufficient security measures are used to prevent cookie data from being tampered.
* @see saveToCookie
*/
protected function restoreFromCookie()
{
$app=Yii::app();
$request=$app->getRequest();
$cookie=$request->getCookies()->itemAt($this->getStateKeyPrefix());
if($cookie && !empty($cookie->value) && is_string($cookie->value) && ($data=$app->getSecurityManager()->validateData($cookie->value))!==false)
{
$data=@unserialize($data);
if(is_array($data) && isset($data[0],$data[1],$data[2],$data[3]))
{
list($id,$name,$duration,$states)=$data;
if($this->beforeLogin($id,$states,true))
{
$this->changeIdentity($id,$name,$states);
if($this->autoRenewCookie)
{
$this->saveToCookie($duration);
}
$this->afterLogin(true);
}
}
}
}
/**
* Renews the identity cookie.
* This method will set the expiration time of the identity cookie to be the current time
* plus the originally specified cookie duration.
* @since 1.1.3
*/
protected function renewCookie()
{
$request=Yii::app()->getRequest();
$cookies=$request->getCookies();
$cookie=$cookies->itemAt($this->getStateKeyPrefix());
if($cookie && !empty($cookie->value) && ($data=Yii::app()->getSecurityManager()->validateData($cookie->value))!==false)
{
$data=@unserialize($data);
if(is_array($data) && isset($data[0],$data[1],$data[2],$data[3]))
{
$this->saveToCookie($data[2]);
}
}
}
/**
* Saves necessary user data into a cookie.
* This method is used when automatic login ({@link allowAutoLogin}) is enabled.
* This method saves user ID, username, other identity states and a validation key to cookie.
* These information are used to do authentication next time when user visits the application.
* @param integer $duration number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser.
* @see restoreFromCookie
*/
protected function saveToCookie($duration)
{
$app=Yii::app();
$cookie=$this->createIdentityCookie($this->getStateKeyPrefix());
$cookie->expire=time()+$duration;
$data=array(
$this->getId(),
$this->getName(),
$duration,
$this->saveIdentityStates(),
);
$cookie->value=$app->getSecurityManager()->hashData(serialize($data));
$app->getRequest()->getCookies()->add($cookie->name,$cookie);
}
/**
* Creates a cookie to store identity information.
* @param string $name the cookie name
* @return CHttpCookie the cookie used to store identity information
*/
protected function createIdentityCookie($name)
{
$cookie=new CHttpCookie($name,'');
if(is_array($this->identityCookie))
{
foreach($this->identityCookie as $name=>$value)
$cookie->$name=$value;
}
return $cookie;
}
/**
* @return string a prefix for the name of the session variables storing user session data.
*/
public function getStateKeyPrefix()
{
if($this->_keyPrefix!==null)
return $this->_keyPrefix;
else
return $this->_keyPrefix=md5('Yii.'.get_class($this).'.'.Yii::app()->getId());
}
/**
* @param string $value a prefix for the name of the session variables storing user session data.
*/
public function setStateKeyPrefix($value)
{
$this->_keyPrefix=$value;
}
/**
* Returns the value of a variable that is stored in user session.
*
* This function is designed to be used by CWebUser descendant classes
* who want to store additional user information in user session.
* A variable, if stored in user session using {@link setState} can be
* retrieved back using this function.
*
* @param string $key variable name
* @param mixed $defaultValue default value
* @return mixed the value of the variable. If it doesn't exist in the session,
* the provided default value will be returned
* @see setState
*/
public function getState($key,$defaultValue=null)
{
$key=$this->getStateKeyPrefix().$key;
return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
}
/**
* Stores a variable in user session.
*
* This function is designed to be used by CWebUser descendant classes
* who want to store additional user information in user session.
* By storing a variable using this function, the variable may be retrieved
* back later using {@link getState}. The variable will be persistent
* across page requests during a user session.
*
* @param string $key variable name
* @param mixed $value variable value
* @param mixed $defaultValue default value. If $value===$defaultValue, the variable will be
* removed from the session
* @see getState
*/
public function setState($key,$value,$defaultValue=null)
{
$key=$this->getStateKeyPrefix().$key;
if($value===$defaultValue)
unset($_SESSION[$key]);
else
$_SESSION[$key]=$value;
}
/**
* Returns a value indicating whether there is a state of the specified name.
* @param string $key state name
* @return boolean whether there is a state of the specified name.
*/
public function hasState($key)
{
$key=$this->getStateKeyPrefix().$key;
return isset($_SESSION[$key]);
}
/**
* Clears all user identity information from persistent storage.
* This will remove the data stored via {@link setState}.
*/
public function clearStates()
{
$keys=array_keys($_SESSION);
$prefix=$this->getStateKeyPrefix();
$n=strlen($prefix);
foreach($keys as $key)
{
if(!strncmp($key,$prefix,$n))
unset($_SESSION[$key]);
}
}
/**
* Returns all flash messages.
* This method is similar to {@link getFlash} except that it returns all
* currently available flash messages.
* @param boolean $delete whether to delete the flash messages after calling this method.
* @return array flash messages (key => message).
* @since 1.1.3
*/
public function getFlashes($delete=true)
{
$flashes=array();
$prefix=$this->getStateKeyPrefix().self::FLASH_KEY_PREFIX;
$keys=array_keys($_SESSION);
$n=strlen($prefix);
foreach($keys as $key)
{
if(!strncmp($key,$prefix,$n))
{
$flashes[substr($key,$n)]=$_SESSION[$key];
if($delete)
unset($_SESSION[$key]);
}
}
if($delete)
$this->setState(self::FLASH_COUNTERS,array());
return $flashes;
}
/**
* Returns a flash message.
* A flash message is available only in the current and the next requests.
* @param string $key key identifying the flash message
* @param mixed $defaultValue value to be returned if the flash message is not available.
* @param boolean $delete whether to delete this flash message after accessing it.
* Defaults to true.
* @return mixed the message message
*/
public function getFlash($key,$defaultValue=null,$delete=true)
{
$value=$this->getState(self::FLASH_KEY_PREFIX.$key,$defaultValue);
if($delete)
$this->setFlash($key,null);
return $value;
}
/**
* Stores a flash message.
* A flash message is available only in the current and the next requests.
* @param string $key key identifying the flash message
* @param mixed $value flash message
* @param mixed $defaultValue if this value is the same as the flash message, the flash message
* will be removed. (Therefore, you can use setFlash('key',null) to remove a flash message.)
*/
public function setFlash($key,$value,$defaultValue=null)
{
$this->setState(self::FLASH_KEY_PREFIX.$key,$value,$defaultValue);
$counters=$this->getState(self::FLASH_COUNTERS,array());
if($value===$defaultValue)
unset($counters[$key]);
else
$counters[$key]=0;
$this->setState(self::FLASH_COUNTERS,$counters,array());
}
/**
* @param string $key key identifying the flash message
* @return boolean whether the specified flash message exists
*/
public function hasFlash($key)
{
return $this->getFlash($key, null, false)!==null;
}
/**
* Changes the current user with the specified identity information.
* This method is called by {@link login} and {@link restoreFromCookie}
* when the current user needs to be populated with the corresponding
* identity information. Derived classes may override this method
* by retrieving additional user-related information. Make sure the
* parent implementation is called first.
* @param mixed $id a unique identifier for the user
* @param string $name the display name for the user
* @param array $states identity states
*/
protected function changeIdentity($id,$name,$states)
{
Yii::app()->getSession()->regenerateID(true);
$this->setId($id);
$this->setName($name);
$this->loadIdentityStates($states);
}
/**
* Retrieves identity states from persistent storage and saves them as an array.
* @return array the identity states
*/
protected function saveIdentityStates()
{
$states=array();
foreach($this->getState(self::STATES_VAR,array()) as $name=>$dummy)
$states[$name]=$this->getState($name);
return $states;
}
/**
* Loads identity states from an array and saves them to persistent storage.
* @param array $states the identity states
*/
protected function loadIdentityStates($states)
{
$names=array();
if(is_array($states))
{
foreach($states as $name=>$value)
{
$this->setState($name,$value);
$names[$name]=true;
}
}
$this->setState(self::STATES_VAR,$names);
}
/**
* Updates the internal counters for flash messages.
* This method is internally used by {@link CWebApplication}
* to maintain the availability of flash messages.
*/
protected function updateFlash()
{
$counters=$this->getState(self::FLASH_COUNTERS);
if(!is_array($counters))
return;
foreach($counters as $key=>$count)
{
if($count)
{
unset($counters[$key]);
$this->setState(self::FLASH_KEY_PREFIX.$key,null);
}
else
$counters[$key]++;
}
$this->setState(self::FLASH_COUNTERS,$counters,array());
}
/**
* Updates the authentication status according to {@link authTimeout}.
* If the user has been inactive for {@link authTimeout} seconds, or {link absoluteAuthTimeout} has passed,
* he will be automatically logged out.
* @since 1.1.7
*/
protected function updateAuthStatus()
{
if(($this->authTimeout!==null || $this->absoluteAuthTimeout!==null) && !$this->getIsGuest())
{
$expires=$this->getState(self::AUTH_TIMEOUT_VAR);
$expiresAbsolute=$this->getState(self::AUTH_ABSOLUTE_TIMEOUT_VAR);
if ($expires!==null && $expires < time() || $expiresAbsolute!==null && $expiresAbsolute < time())
$this->logout(false);
else
$this->setState(self::AUTH_TIMEOUT_VAR,time()+$this->authTimeout);
}
}
/**
* Performs access check for this user.
* @param string $operation the name of the operation that need access check.
* @param array $params name-value pairs that would be passed to business rules associated
* with the tasks and roles assigned to the user.
* Since version 1.1.11 a param with name 'userId' is added to this array, which holds the value of
* {@link getId()} when {@link CDbAuthManager} or {@link CPhpAuthManager} is used.
* @param boolean $allowCaching whether to allow caching the result of access check.
* When this parameter
* is true (default), if the access check of an operation was performed before,
* its result will be directly returned when calling this method to check the same operation.
* If this parameter is false, this method will always call {@link CAuthManager::checkAccess}
* to obtain the up-to-date access result. Note that this caching is effective
* only within the same request and only works when <code>$params=array()</code>.
* @return boolean whether the operations can be performed by this user.
*/
public function checkAccess($operation,$params=array(),$allowCaching=true)
{
if($allowCaching && $params===array() && isset($this->_access[$operation]))
return $this->_access[$operation];
$access=Yii::app()->getAuthManager()->checkAccess($operation,$this->getId(),$params);
if($allowCaching && $params===array())
$this->_access[$operation]=$access;
return $access;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Database schema required by CDbAuthManager.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @since 1.0
*/
drop table if exists [AuthAssignment];
drop table if exists [AuthItemChild];
drop table if exists [AuthItem];
create table [AuthItem]
(
[name] varchar(64) not null,
[type] integer not null,
[description] text,
[bizrule] text,
[data] text,
primary key ([name])
);
create table [AuthItemChild]
(
[parent] varchar(64) not null,
[child] varchar(64) not null,
primary key ([parent],[child]),
foreign key ([parent]) references [AuthItem] ([name]) on delete cascade on update cascade,
foreign key ([child]) references [AuthItem] ([name]) on delete cascade on update cascade
);
create table [AuthAssignment]
(
[itemname] varchar(64) not null,
[userid] varchar(64) not null,
[bizrule] text,
[data] text,
primary key ([itemname],[userid]),
foreign key ([itemname]) references [AuthItem] ([name]) on delete cascade on update cascade
);

View File

@@ -0,0 +1,42 @@
/**
* Database schema required by CDbAuthManager.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @since 1.0
*/
drop table if exists `AuthAssignment`;
drop table if exists `AuthItemChild`;
drop table if exists `AuthItem`;
create table `AuthItem`
(
`name` varchar(64) not null,
`type` integer not null,
`description` text,
`bizrule` text,
`data` text,
primary key (`name`)
) engine InnoDB;
create table `AuthItemChild`
(
`parent` varchar(64) not null,
`child` varchar(64) not null,
primary key (`parent`,`child`),
foreign key (`parent`) references `AuthItem` (`name`) on delete cascade on update cascade,
foreign key (`child`) references `AuthItem` (`name`) on delete cascade on update cascade
) engine InnoDB;
create table `AuthAssignment`
(
`itemname` varchar(64) not null,
`userid` varchar(64) not null,
`bizrule` text,
`data` text,
primary key (`itemname`,`userid`),
foreign key (`itemname`) references `AuthItem` (`name`) on delete cascade on update cascade
) engine InnoDB;

View File

@@ -0,0 +1,42 @@
/**
* Database schema required by CDbAuthManager.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @since 1.0
*/
drop table if exists "AuthAssignment";
drop table if exists "AuthItemChild";
drop table if exists "AuthItem";
create table "AuthItem"
(
"name" varchar(64) not null,
"type" integer not null,
"description" text,
"bizrule" text,
"data" text,
primary key ("name")
);
create table "AuthItemChild"
(
"parent" varchar(64) not null,
"child" varchar(64) not null,
primary key ("parent","child"),
foreign key ("parent") references "AuthItem" ("name") on delete cascade on update cascade,
foreign key ("child") references "AuthItem" ("name") on delete cascade on update cascade
);
create table "AuthAssignment"
(
"itemname" varchar(64) not null,
"userid" varchar(64) not null,
"bizrule" text,
"data" text,
primary key ("itemname","userid"),
foreign key ("itemname") references "AuthItem" ("name") on delete cascade on update cascade
);

View File

@@ -0,0 +1,42 @@
/**
* Database schema required by CDbAuthManager.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @since 1.0
*/
drop table if exists "AuthAssignment";
drop table if exists "AuthItemChild";
drop table if exists "AuthItem";
create table "AuthItem"
(
"name" varchar(64) not null,
"type" integer not null,
"description" text,
"bizrule" text,
"data" text,
primary key ("name")
);
create table "AuthItemChild"
(
"parent" varchar(64) not null,
"child" varchar(64) not null,
primary key ("parent","child"),
foreign key ("parent") references "AuthItem" ("name") on delete cascade on update cascade,
foreign key ("child") references "AuthItem" ("name") on delete cascade on update cascade
);
create table "AuthAssignment"
(
"itemname" varchar(64) not null,
"userid" varchar(64) not null,
"bizrule" text,
"data" text,
primary key ("itemname","userid"),
foreign key ("itemname") references "AuthItem" ("name") on delete cascade on update cascade
);

View File

@@ -0,0 +1,42 @@
/**
* Database schema required by CDbAuthManager.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @since 1.0
*/
drop table if exists 'AuthAssignment';
drop table if exists 'AuthItemChild';
drop table if exists 'AuthItem';
create table 'AuthItem'
(
"name" varchar(64) not null,
"type" integer not null,
"description" text,
"bizrule" text,
"data" text,
primary key ("name")
);
create table 'AuthItemChild'
(
"parent" varchar(64) not null,
"child" varchar(64) not null,
primary key ("parent","child"),
foreign key ("parent") references 'AuthItem' ("name") on delete cascade on update cascade,
foreign key ("child") references 'AuthItem' ("name") on delete cascade on update cascade
);
create table 'AuthAssignment'
(
"itemname" varchar(64) not null,
"userid" varchar(64) not null,
"bizrule" text,
"data" text,
primary key ("itemname","userid"),
foreign key ("itemname") references 'AuthItem' ("name") on delete cascade on update cascade
);

View File

@@ -0,0 +1,74 @@
<?php
/**
* CFilter 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/
*/
/**
* CFilter is the base class for all filters.
*
* A filter can be applied before and after an action is executed.
* It can modify the context that the action is to run or decorate the result that the
* action generates.
*
* Override {@link preFilter()} to specify the filtering logic that should be applied
* before the action, and {@link postFilter()} for filtering logic after the action.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.filters
* @since 1.0
*/
class CFilter extends CComponent implements IFilter
{
/**
* Performs the filtering.
* The default implementation is to invoke {@link preFilter}
* and {@link postFilter} which are meant to be overridden
* child classes. If a child class needs to override this method,
* make sure it calls <code>$filterChain->run()</code>
* if the action should be executed.
* @param CFilterChain $filterChain the filter chain that the filter is on.
*/
public function filter($filterChain)
{
if($this->preFilter($filterChain))
{
$filterChain->run();
$this->postFilter($filterChain);
}
}
/**
* Initializes the filter.
* This method is invoked after the filter properties are initialized
* and before {@link preFilter} is called.
* You may override this method to include some initialization logic.
* @since 1.1.4
*/
public function init()
{
}
/**
* Performs the pre-action filtering.
* @param CFilterChain $filterChain the filter chain that the filter is on.
* @return boolean whether the filtering process should continue and the action
* should be executed.
*/
protected function preFilter($filterChain)
{
return true;
}
/**
* Performs the post-action filtering.
* @param CFilterChain $filterChain the filter chain that the filter is on.
*/
protected function postFilter($filterChain)
{
}
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* CFilterChain 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/
*/
/**
* CFilterChain represents a list of filters being applied to an action.
*
* CFilterChain executes the filter list by {@link run()}.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.filters
* @since 1.0
*/
class CFilterChain extends CList
{
/**
* @var CController the controller who executes the action.
*/
public $controller;
/**
* @var CAction the action being filtered by this chain.
*/
public $action;
/**
* @var integer the index of the filter that is to be executed when calling {@link run()}.
*/
public $filterIndex=0;
/**
* Constructor.
* @param CController $controller the controller who executes the action.
* @param CAction $action the action being filtered by this chain.
*/
public function __construct($controller,$action)
{
$this->controller=$controller;
$this->action=$action;
}
/**
* CFilterChain factory method.
* This method creates a CFilterChain instance.
* @param CController $controller the controller who executes the action.
* @param CAction $action the action being filtered by this chain.
* @param array $filters list of filters to be applied to the action.
* @return CFilterChain
*/
public static function create($controller,$action,$filters)
{
$chain=new CFilterChain($controller,$action);
$actionID=$action->getId();
foreach($filters as $filter)
{
if(is_string($filter)) // filterName [+|- action1 action2]
{
if(($pos=strpos($filter,'+'))!==false || ($pos=strpos($filter,'-'))!==false)
{
$matched=preg_match("/\b{$actionID}\b/i",substr($filter,$pos+1))>0;
if(($filter[$pos]==='+')===$matched)
$filter=CInlineFilter::create($controller,trim(substr($filter,0,$pos)));
}
else
$filter=CInlineFilter::create($controller,$filter);
}
elseif(is_array($filter)) // array('path.to.class [+|- action1, action2]','param1'=>'value1',...)
{
if(!isset($filter[0]))
throw new CException(Yii::t('yii','The first element in a filter configuration must be the filter class.'));
$filterClass=$filter[0];
unset($filter[0]);
if(($pos=strpos($filterClass,'+'))!==false || ($pos=strpos($filterClass,'-'))!==false)
{
$matched=preg_match("/\b{$actionID}\b/i",substr($filterClass,$pos+1))>0;
if(($filterClass[$pos]==='+')===$matched)
$filterClass=trim(substr($filterClass,0,$pos));
else
continue;
}
$filter['class']=$filterClass;
$filter=Yii::createComponent($filter);
}
if(is_object($filter))
{
$filter->init();
$chain->add($filter);
}
}
return $chain;
}
/**
* Inserts an item at the specified position.
* This method overrides the parent implementation by adding
* additional check for the item to be added. In particular,
* only objects implementing {@link IFilter} can be added to the list.
* @param integer $index the specified position.
* @param mixed $item new item
* @throws CException If the index specified exceeds the bound or the list is read-only, or the item is not an {@link IFilter} instance.
*/
public function insertAt($index,$item)
{
if($item instanceof IFilter)
parent::insertAt($index,$item);
else
throw new CException(Yii::t('yii','CFilterChain can only take objects implementing the IFilter interface.'));
}
/**
* Executes the filter indexed at {@link filterIndex}.
* After this method is called, {@link filterIndex} will be automatically incremented by one.
* This method is usually invoked in filters so that the filtering process
* can continue and the action can be executed.
*/
public function run()
{
if($this->offsetExists($this->filterIndex))
{
$filter=$this->itemAt($this->filterIndex++);
Yii::trace('Running filter '.($filter instanceof CInlineFilter ? get_class($this->controller).'.filter'.$filter->name.'()':get_class($filter).'.filter()'),'system.web.filters.CFilterChain');
$filter->filter($this);
}
else
$this->controller->runAction($this->action);
}
}

View File

@@ -0,0 +1,209 @@
<?php
/**
* CHttpCacheFilter class file.
*
* @author Da:Sourcerer <webmaster@dasourcerer.net>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CHttpCacheFilter implements http caching. It works a lot like {@link COutputCache}
* as a filter, except that content caching is being done on the client side.
*
* @author Da:Sourcerer <webmaster@dasourcerer.net>
* @package system.web.filters
* @since 1.1.11
*/
class CHttpCacheFilter extends CFilter
{
/**
* @var string|integer Timestamp for the last modification date.
* Must be either a string parsable by {@link http://php.net/strtotime strtotime()}
* or an integer representing a unix timestamp.
*/
public $lastModified;
/**
* @var string|callback PHP Expression for the last modification date.
* If set, this takes precedence over {@link lastModified}.
*
* The PHP expression will be evaluated using {@link evaluateExpression}.
*
* A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
* please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
*/
public $lastModifiedExpression;
/**
* @var mixed Seed for the ETag.
* Can be anything that passes through {@link http://php.net/serialize serialize()}.
*/
public $etagSeed;
/**
* @var string|callback Expression for the ETag seed.
* If set, this takes precedence over {@link etagSeed}.
*
* The PHP expression will be evaluated using {@link evaluateExpression}.
*
* A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
* please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
*/
public $etagSeedExpression;
/**
* @var string Http cache control headers. Set this to an empty string in order to keep this
* header from being sent entirely.
*/
public $cacheControl='max-age=3600, public';
/**
* Performs the pre-action filtering.
* @param CFilterChain $filterChain the filter chain that the filter is on.
* @return boolean whether the filtering process should continue and the action should be executed.
*/
public function preFilter($filterChain)
{
// Only cache GET and HEAD requests
if(!in_array(Yii::app()->getRequest()->getRequestType(), array('GET', 'HEAD')))
return true;
$lastModified=$this->getLastModifiedValue();
$etag=$this->getEtagValue();
if($etag===false&&$lastModified===false)
return true;
if($etag)
header('ETag: '.$etag);
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])&&isset($_SERVER['HTTP_IF_NONE_MATCH']))
{
if($this->checkLastModified($lastModified)&&$this->checkEtag($etag))
{
$this->send304Header();
$this->sendCacheControlHeader();
return false;
}
}
elseif(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']))
{
if($this->checkLastModified($lastModified))
{
$this->send304Header();
$this->sendCacheControlHeader();
return false;
}
}
elseif(isset($_SERVER['HTTP_IF_NONE_MATCH']))
{
if($this->checkEtag($etag))
{
$this->send304Header();
$this->sendCacheControlHeader();
return false;
}
}
if($lastModified)
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $lastModified).' GMT');
$this->sendCacheControlHeader();
return true;
}
/**
* Gets the last modified value from either {@link lastModifiedExpression} or {@link lastModified}
* and converts it into a unix timestamp if necessary
* @throws CException
* @return integer|boolean A unix timestamp or false if neither lastModified nor
* lastModifiedExpression have been set
*/
protected function getLastModifiedValue()
{
if($this->lastModifiedExpression)
{
$value=$this->evaluateExpression($this->lastModifiedExpression);
if(is_numeric($value)&&$value==(int)$value)
return $value;
elseif(($lastModified=strtotime($value))===false)
throw new CException(Yii::t('yii','Invalid expression for CHttpCacheFilter.lastModifiedExpression: The evaluation result "{value}" could not be understood by strtotime()',
array('{value}'=>$value)));
return $lastModified;
}
if($this->lastModified)
{
if(is_numeric($this->lastModified)&&$this->lastModified==(int)$this->lastModified)
return $this->lastModified;
elseif(($lastModified=strtotime($this->lastModified))===false)
throw new CException(Yii::t('yii','CHttpCacheFilter.lastModified contained a value that could not be understood by strtotime()'));
return $lastModified;
}
return false;
}
/**
* Gets the ETag out of either {@link etagSeedExpression} or {@link etagSeed}
* @return string|boolean Either a quoted string serving as ETag or false if neither etagSeed nor etagSeedExpression have been set
*/
protected function getEtagValue()
{
if($this->etagSeedExpression)
return $this->generateEtag($this->evaluateExpression($this->etagSeedExpression));
elseif($this->etagSeed)
return $this->generateEtag($this->etagSeed);
return false;
}
/**
* Check if the etag supplied by the client matches our generated one
* @param string $etag the supplied etag
* @return boolean true if the supplied etag matches $etag
*/
protected function checkEtag($etag)
{
return isset($_SERVER['HTTP_IF_NONE_MATCH'])&&$_SERVER['HTTP_IF_NONE_MATCH']==$etag;
}
/**
* Checks if the last modified date supplied by the client is still up to date
* @param integer $lastModified the last modified date
* @return boolean true if the last modified date sent by the client is newer or equal to $lastModified
*/
protected function checkLastModified($lastModified)
{
return isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])&&@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$lastModified;
}
/**
* Sends the 304 HTTP status code to the client
*/
protected function send304Header()
{
header('HTTP/1.1 304 Not Modified');
}
/**
* Sends the cache control header to the client
* @see cacheControl
* @since 1.1.12
*/
protected function sendCacheControlHeader()
{
if(Yii::app()->session->isStarted)
{
session_cache_limiter('public');
header('Pragma:',true);
}
header('Cache-Control: '.$this->cacheControl,true);
}
/**
* Generates a quoted string out of the seed
* @param mixed $seed Seed for the ETag
*/
protected function generateEtag($seed)
{
return '"'.base64_encode(sha1(serialize($seed),true)).'"';
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* CInlineFilter 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/
*/
/**
* CInlineFilter represents a filter defined as a controller method.
*
* CInlineFilter executes the 'filterXYZ($action)' method defined
* in the controller, where the name 'XYZ' can be retrieved from the {@link name} property.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.filters
* @since 1.0
*/
class CInlineFilter extends CFilter
{
/**
* @var string name of the filter. It stands for 'XYZ' in the filter method name 'filterXYZ'.
*/
public $name;
/**
* Creates an inline filter instance.
* The creation is based on a string describing the inline method name
* and action names that the filter shall or shall not apply to.
* @param CController $controller the controller who hosts the filter methods
* @param string $filterName the filter name
* @return CInlineFilter the created instance
* @throws CException if the filter method does not exist
*/
public static function create($controller,$filterName)
{
if(method_exists($controller,'filter'.$filterName))
{
$filter=new CInlineFilter;
$filter->name=$filterName;
return $filter;
}
else
throw new CException(Yii::t('yii','Filter "{filter}" is invalid. Controller "{class}" does not have the filter method "filter{filter}".',
array('{filter}'=>$filterName, '{class}'=>get_class($controller))));
}
/**
* Performs the filtering.
* This method calls the filter method defined in the controller class.
* @param CFilterChain $filterChain the filter chain that the filter is on.
*/
public function filter($filterChain)
{
$method='filter'.$this->name;
$filterChain->controller->$method($filterChain);
}
}

View File

@@ -0,0 +1,635 @@
<?php
/**
* CForm 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/
*/
/**
* CForm represents a form object that contains form input specifications.
*
* The main purpose of introducing the abstraction of form objects is to enhance the
* reusability of forms. In particular, we can divide a form in two parts: those
* that specify each individual form inputs, and those that decorate the form inputs.
* A CForm object represents the former part. It relies on the rendering process to
* accomplish form input decoration. Reusability is mainly achieved in the rendering process.
* That is, a rendering process can be reused to render different CForm objects.
*
* A form can be rendered in different ways. One can call the {@link render} method
* to get a quick form rendering without writing any HTML code; one can also override
* {@link render} to render the form in a different layout; and one can use an external
* view template to render each form element explicitly. In these ways, the {@link render}
* method can be applied to all kinds of forms and thus achieves maximum reusability;
* while the external view template keeps maximum flexibility in rendering complex forms.
*
* Form input specifications are organized in terms of a form element hierarchy.
* At the root of the hierarchy, it is the root CForm object. The root form object maintains
* its children in two collections: {@link elements} and {@link buttons}.
* The former contains non-button form elements ({@link CFormStringElement},
* {@link CFormInputElement} and CForm); while the latter mainly contains
* button elements ({@link CFormButtonElement}). When a CForm object is embedded in the
* {@link elements} collection, it is called a sub-form which can have its own {@link elements}
* and {@link buttons} collections and thus form the whole form hierarchy.
*
* Sub-forms are mainly used to handle multiple models. For example, in a user
* registration form, we can have the root form to collect input for the user
* table while a sub-form to collect input for the profile table. Sub-form is also
* a good way to partition a lengthy form into shorter ones, even though all inputs
* may belong to the same model.
*
* Form input specifications are given in terms of a configuration array which is
* used to initialize the property values of a CForm object. The {@link elements} and
* {@link buttons} properties need special attention as they are the main properties
* to be configured. To configure {@link elements}, we should give it an array like
* the following:
* <pre>
* 'elements'=>array(
* 'username'=>array('type'=>'text', 'maxlength'=>80),
* 'password'=>array('type'=>'password', 'maxlength'=>80),
* )
* </pre>
* The above code specifies two input elements: 'username' and 'password'. Note the model
* object must have exactly the same attributes 'username' and 'password'. Each element
* has a type which specifies what kind of input should be used. The rest of the array elements
* (e.g. 'maxlength') in an input specification are rendered as HTML element attributes
* when the input field is rendered. The {@link buttons} property is configured similarly.
*
* If you're going to use AJAX and/or client form validation with the enabled error summary
* you have to set {@link $showErrors} property to true. Please refer to it's documentation
* for more details.
*
* For more details about configuring form elements, please refer to {@link CFormInputElement}
* and {@link CFormButtonElement}.
*
* @property CForm $root The top-level form object.
* @property CActiveForm $activeFormWidget The active form widget associated with this form.
* This method will return the active form widget as specified by {@link activeForm}.
* @property CBaseController $owner The owner of this form. This refers to either a controller or a widget
* by which the form is created and rendered.
* @property CModel $model The model associated with this form. If this form does not have a model,
* it will look for a model in its ancestors.
* @property array $models The models that are associated with this form or its sub-forms.
* @property CFormElementCollection $elements The form elements.
* @property CFormElementCollection $buttons The form elements.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.form
* @since 1.1
*/
class CForm extends CFormElement implements ArrayAccess
{
/**
* @var string the title for this form. By default, if this is set, a fieldset may be rendered
* around the form body using the title as its legend. Defaults to null.
*/
public $title;
/**
* @var string the description of this form.
*/
public $description;
/**
* @var string the submission method of this form. Defaults to 'post'.
* This property is ignored when this form is a sub-form.
*/
public $method='post';
/**
* @var mixed the form action URL (see {@link CHtml::normalizeUrl} for details about this parameter.)
* Defaults to an empty string, meaning the current request URL.
* This property is ignored when this form is a sub-form.
*/
public $action='';
/**
* @var string the name of the class for representing a form input element. Defaults to 'CFormInputElement'.
*/
public $inputElementClass='CFormInputElement';
/**
* @var string the name of the class for representing a form button element. Defaults to 'CFormButtonElement'.
*/
public $buttonElementClass='CFormButtonElement';
/**
* @var array HTML attribute values for the form tag. When the form is embedded within another form,
* this property will be used to render the HTML attribute values for the fieldset enclosing the child form.
*/
public $attributes=array();
/**
* @var boolean whether to show error summary. Defaults to false.
*/
public $showErrorSummary=false;
/**
* @var boolean|null whether error elements of the form attributes should be rendered. There are three possible
* valid values: null, true and false.
*
* Defaults to null meaning that {@link $showErrorSummary} will be used as value. This is done mainly to keep
* backward compatibility with existing applications. If you want to use error summary with AJAX and/or client
* validation you have to set this property to true (recall that {@link CActiveForm::error()} should be called
* for each attribute that is going to be AJAX and/or client validated).
*
* False value means that the error elements of the form attributes shall not be displayed. True value means that
* the error elements of the form attributes will be rendered.
*
* @since 1.1.14
*/
public $showErrors;
/**
* @var array the configuration used to create the active form widget.
* The widget will be used to render the form tag and the error messages.
* The 'class' option is required, which specifies the class of the widget.
* The rest of the options will be passed to {@link CBaseController::beginWidget()} call.
* Defaults to array('class'=>'CActiveForm').
* @since 1.1.1
*/
public $activeForm=array('class'=>'CActiveForm');
private $_model;
private $_elements;
private $_buttons;
private $_activeForm;
/**
* Constructor.
* If you override this method, make sure you do not modify the method
* signature, and also make sure you call the parent implementation.
* @param mixed $config the configuration for this form. It can be a configuration array
* or the path alias of a PHP script file that returns a configuration array.
* The configuration array consists of name-value pairs that are used to initialize
* the properties of this form.
* @param CModel $model the model object associated with this form. If it is null,
* the parent's model will be used instead.
* @param mixed $parent the direct parent of this form. This could be either a {@link CBaseController}
* object (a controller or a widget), or a {@link CForm} object.
* If the former, it means the form is a top-level form; if the latter, it means this form is a sub-form.
*/
public function __construct($config,$model=null,$parent=null)
{
$this->setModel($model);
if($parent===null)
$parent=Yii::app()->getController();
parent::__construct($config,$parent);
if($this->showErrors===null)
$this->showErrors=!$this->showErrorSummary;
$this->init();
}
/**
* Initializes this form.
* This method is invoked at the end of the constructor.
* You may override this method to provide customized initialization (such as
* configuring the form object).
*/
protected function init()
{
}
/**
* Returns a value indicating whether this form is submitted.
* @param string $buttonName the name of the submit button
* @param boolean $loadData whether to call {@link loadData} if the form is submitted so that
* the submitted data can be populated to the associated models.
* @return boolean whether this form is submitted.
* @see loadData
*/
public function submitted($buttonName='submit',$loadData=true)
{
$ret=$this->clicked($this->getUniqueId()) && $this->clicked($buttonName);
if($ret && $loadData)
$this->loadData();
return $ret;
}
/**
* Returns a value indicating whether the specified button is clicked.
* @param string $name the button name
* @return boolean whether the button is clicked.
*/
public function clicked($name)
{
if(strcasecmp($this->getRoot()->method,'get'))
return isset($_POST[$name]);
else
return isset($_GET[$name]);
}
/**
* Validates the models associated with this form.
* All models, including those associated with sub-forms, will perform
* the validation. You may use {@link CModel::getErrors()} to retrieve the validation
* error messages.
* @return boolean whether all models are valid
*/
public function validate()
{
$ret=true;
foreach($this->getModels() as $model)
$ret=$model->validate() && $ret;
return $ret;
}
/**
* Loads the submitted data into the associated model(s) to the form.
* This method will go through all models associated with this form and its sub-forms
* and massively assign the submitted data to the models.
* @see submitted
*/
public function loadData()
{
if($this->_model!==null)
{
$class=CHtml::modelName($this->_model);
if(strcasecmp($this->getRoot()->method,'get'))
{
if(isset($_POST[$class]))
$this->_model->setAttributes($_POST[$class]);
}
elseif(isset($_GET[$class]))
$this->_model->setAttributes($_GET[$class]);
}
foreach($this->getElements() as $element)
{
if($element instanceof self)
$element->loadData();
}
}
/**
* @return CForm the top-level form object
*/
public function getRoot()
{
$root=$this;
while($root->getParent() instanceof self)
$root=$root->getParent();
return $root;
}
/**
* @return CActiveForm the active form widget associated with this form.
* This method will return the active form widget as specified by {@link activeForm}.
* @since 1.1.1
*/
public function getActiveFormWidget()
{
if($this->_activeForm!==null)
return $this->_activeForm;
else
return $this->getRoot()->_activeForm;
}
/**
* @return CBaseController the owner of this form. This refers to either a controller or a widget
* by which the form is created and rendered.
*/
public function getOwner()
{
$owner=$this->getParent();
while($owner instanceof self)
$owner=$owner->getParent();
return $owner;
}
/**
* Returns the model that this form is associated with.
* @param boolean $checkParent whether to return parent's model if this form doesn't have model by itself.
* @return CModel the model associated with this form. If this form does not have a model,
* it will look for a model in its ancestors.
*/
public function getModel($checkParent=true)
{
if(!$checkParent)
return $this->_model;
$form=$this;
while($form->_model===null && $form->getParent() instanceof self)
$form=$form->getParent();
return $form->_model;
}
/**
* @param CModel $model the model to be associated with this form
*/
public function setModel($model)
{
$this->_model=$model;
}
/**
* Returns all models that are associated with this form or its sub-forms.
* @return array the models that are associated with this form or its sub-forms.
*/
public function getModels()
{
$models=array();
if($this->_model!==null)
$models[]=$this->_model;
foreach($this->getElements() as $element)
{
if($element instanceof self)
$models=array_merge($models,$element->getModels());
}
return $models;
}
/**
* Returns the input elements of this form.
* This includes text strings, input elements and sub-forms.
* Note that the returned result is a {@link CFormElementCollection} object, which
* means you can use it like an array. For more details, see {@link CMap}.
* @return CFormElementCollection the form elements.
*/
public function getElements()
{
if($this->_elements===null)
$this->_elements=new CFormElementCollection($this,false);
return $this->_elements;
}
/**
* Configures the input elements of this form.
* The configuration must be an array of input configuration array indexed by input name.
* Each input configuration array consists of name-value pairs that are used to initialize
* a {@link CFormStringElement} object (when 'type' is 'string'), a {@link CFormElement} object
* (when 'type' is a string ending with 'Form'), or a {@link CFormInputElement} object in
* all other cases.
* @param array $elements the elements configurations
*/
public function setElements($elements)
{
$collection=$this->getElements();
foreach($elements as $name=>$config)
$collection->add($name,$config);
}
/**
* Returns the button elements of this form.
* Note that the returned result is a {@link CFormElementCollection} object, which
* means you can use it like an array. For more details, see {@link CMap}.
* @return CFormElementCollection the form elements.
*/
public function getButtons()
{
if($this->_buttons===null)
$this->_buttons=new CFormElementCollection($this,true);
return $this->_buttons;
}
/**
* Configures the buttons of this form.
* The configuration must be an array of button configuration array indexed by button name.
* Each button configuration array consists of name-value pairs that are used to initialize
* a {@link CFormButtonElement} object.
* @param array $buttons the button configurations
*/
public function setButtons($buttons)
{
$collection=$this->getButtons();
foreach($buttons as $name=>$config)
$collection->add($name,$config);
}
/**
* Renders the form.
* The default implementation simply calls {@link renderBegin}, {@link renderBody} and {@link renderEnd}.
* @return string the rendering result
*/
public function render()
{
return $this->renderBegin() . $this->renderBody() . $this->renderEnd();
}
/**
* Renders the open tag of the form.
* The default implementation will render the open form tag.
* @return string the rendering result
*/
public function renderBegin()
{
if($this->getParent() instanceof self)
return '';
else
{
$options=$this->activeForm;
if(isset($options['class']))
{
$class=$options['class'];
unset($options['class']);
}
else
$class='CActiveForm';
$options['action']=$this->action;
$options['method']=$this->method;
if(isset($options['htmlOptions']))
{
foreach($this->attributes as $name=>$value)
$options['htmlOptions'][$name]=$value;
}
else
$options['htmlOptions']=$this->attributes;
ob_start();
$this->_activeForm=$this->getOwner()->beginWidget($class, $options);
return ob_get_clean() . "<div style=\"visibility:hidden\">".CHtml::hiddenField($this->getUniqueID(),1)."</div>\n";
}
}
/**
* Renders the close tag of the form.
* @return string the rendering result
*/
public function renderEnd()
{
if($this->getParent() instanceof self)
return '';
else
{
ob_start();
$this->getOwner()->endWidget();
return ob_get_clean();
}
}
/**
* Renders the body content of this form.
* This method mainly renders {@link elements} and {@link buttons}.
* If {@link title} or {@link description} is specified, they will be rendered as well.
* And if the associated model contains error, the error summary may also be displayed.
* The form tag will not be rendered. Please call {@link renderBegin} and {@link renderEnd}
* to render the open and close tags of the form.
* You may override this method to customize the rendering of the form.
* @return string the rendering result
*/
public function renderBody()
{
$output='';
if($this->title!==null)
{
if($this->getParent() instanceof self)
{
$attributes=$this->attributes;
unset($attributes['name'],$attributes['type']);
$output=CHtml::openTag('fieldset', $attributes)."<legend>".$this->title."</legend>\n";
}
else
$output="<fieldset>\n<legend>".$this->title."</legend>\n";
}
if($this->description!==null)
$output.="<div class=\"description\">\n".$this->description."</div>\n";
if($this->showErrorSummary && ($model=$this->getModel(false))!==null)
$output.=$this->getActiveFormWidget()->errorSummary($model)."\n";
$output.=$this->renderElements()."\n".$this->renderButtons()."\n";
if($this->title!==null)
$output.="</fieldset>\n";
return $output;
}
/**
* Renders the {@link elements} in this form.
* @return string the rendering result
*/
public function renderElements()
{
$output='';
foreach($this->getElements() as $element)
$output.=$this->renderElement($element);
return $output;
}
/**
* Renders the {@link buttons} in this form.
* @return string the rendering result
*/
public function renderButtons()
{
$output='';
foreach($this->getButtons() as $button)
$output.=$this->renderElement($button);
return $output!=='' ? "<div class=\"row buttons\">".$output."</div>\n" : '';
}
/**
* Renders a single element which could be an input element, a sub-form, a string, or a button.
* @param mixed $element the form element to be rendered. This can be either a {@link CFormElement} instance
* or a string representing the name of the form element.
* @return string the rendering result
*/
public function renderElement($element)
{
if(is_string($element))
{
if(($e=$this[$element])===null && ($e=$this->getButtons()->itemAt($element))===null)
return $element;
else
$element=$e;
}
if($element->getVisible())
{
if($element instanceof CFormInputElement)
{
if($element->type==='hidden')
return "<div style=\"visibility:hidden\">\n".$element->render()."</div>\n";
else
return "<div class=\"row field_{$element->name}\">\n".$element->render()."</div>\n";
}
elseif($element instanceof CFormButtonElement)
return $element->render()."\n";
else
return $element->render();
}
return '';
}
/**
* This method is called after an element is added to the element collection.
* @param string $name the name of the element
* @param CFormElement $element the element that is added
* @param boolean $forButtons whether the element is added to the {@link buttons} collection.
* If false, it means the element is added to the {@link elements} collection.
*/
public function addedElement($name,$element,$forButtons)
{
}
/**
* This method is called after an element is removed from the element collection.
* @param string $name the name of the element
* @param CFormElement $element the element that is removed
* @param boolean $forButtons whether the element is removed from the {@link buttons} collection
* If false, it means the element is removed from the {@link elements} collection.
*/
public function removedElement($name,$element,$forButtons)
{
}
/**
* Evaluates the visibility of this form.
* This method will check the visibility of the {@link elements}.
* If any one of them is visible, the form is considered as visible. Otherwise, it is invisible.
* @return boolean whether this form is visible.
*/
protected function evaluateVisible()
{
foreach($this->getElements() as $element)
if($element->getVisible())
return true;
return false;
}
/**
* Returns a unique ID that identifies this form in the current page.
* @return string the unique ID identifying this form
*/
protected function getUniqueId()
{
if(isset($this->attributes['id']))
return 'yform_'.$this->attributes['id'];
else
return 'yform_'.sprintf('%x',crc32(serialize(array_keys($this->getElements()->toArray()))));
}
/**
* Returns whether there is an element at the specified offset.
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to check on
* @return boolean
*/
public function offsetExists($offset)
{
return $this->getElements()->contains($offset);
}
/**
* Returns the element at the specified offset.
* This method is required by the interface ArrayAccess.
* @param integer $offset the offset to retrieve element.
* @return mixed the element at the offset, null if no element is found at the offset
*/
public function offsetGet($offset)
{
return $this->getElements()->itemAt($offset);
}
/**
* Sets the element at the specified offset.
* This method is required by the interface ArrayAccess.
* @param integer $offset the offset to set element
* @param mixed $item the element value
*/
public function offsetSet($offset,$item)
{
$this->getElements()->add($offset,$item);
}
/**
* Unsets the element at the specified offset.
* This method is required by the interface ArrayAccess.
* @param mixed $offset the offset to unset element
*/
public function offsetUnset($offset)
{
$this->getElements()->remove($offset);
}
}

View File

@@ -0,0 +1,138 @@
<?php
/**
* CFormButtonElement 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/
*/
/**
* CFormButtonElement represents a form button element.
*
* CFormButtonElement can represent the following types of button based on {@link type} property:
* <ul>
* <li>htmlButton: a normal button generated using {@link CHtml::htmlButton}</li>
* <li>htmlReset a reset button generated using {@link CHtml::htmlButton}</li>
* <li>htmlSubmit: a submit button generated using {@link CHtml::htmlButton}</li>
* <li>submit: a submit button generated using {@link CHtml::submitButton}</li>
* <li>button: a normal button generated using {@link CHtml::button}</li>
* <li>image: an image button generated using {@link CHtml::imageButton}</li>
* <li>reset: a reset button generated using {@link CHtml::resetButton}</li>
* <li>link: a link button generated using {@link CHtml::linkButton}</li>
* </ul>
* The {@link type} property can also be a class name or a path alias to the class. In this case,
* the button is generated using a widget of the specified class. Note, the widget must
* have a property called "name".
*
* Because CFormElement is an ancestor class of CFormButtonElement, a value assigned to a non-existing property will be
* stored in {@link attributes} which will be passed as HTML attribute values to the {@link CHtml} method
* generating the button or initial values of the widget properties.
*
* @property string $on Scenario names separated by commas. Defaults to null.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.form
* @since 1.1
*/
class CFormButtonElement extends CFormElement
{
/**
* @var array Core button types (alias=>CHtml method name)
*/
public static $coreTypes=array(
'htmlButton'=>'htmlButton',
'htmlSubmit'=>'htmlButton',
'htmlReset'=>'htmlButton',
'button'=>'button',
'submit'=>'submitButton',
'reset'=>'resetButton',
'image'=>'imageButton',
'link'=>'linkButton',
);
/**
* @var string the type of this button. This can be a class name, a path alias of a class name,
* or a button type alias (submit, button, image, reset, link, htmlButton, htmlSubmit, htmlReset).
*/
public $type;
/**
* @var string name of this button
*/
public $name;
/**
* @var string the label of this button. This property is ignored when a widget is used to generate the button.
*/
public $label;
private $_on;
/**
* Returns a value indicating under which scenarios this button is visible.
* If the value is empty, it means the button is visible under all scenarios.
* Otherwise, only when the model is in the scenario whose name can be found in
* this value, will the button be visible. See {@link CModel::scenario} for more
* information about model scenarios.
* @return string scenario names separated by commas. Defaults to null.
*/
public function getOn()
{
return $this->_on;
}
/**
* @param string $value scenario names separated by commas.
*/
public function setOn($value)
{
$this->_on=preg_split('/[\s,]+/',$value,-1,PREG_SPLIT_NO_EMPTY);
}
/**
* Returns this button.
* @return string the rendering result
*/
public function render()
{
$attributes=$this->attributes;
if(isset(self::$coreTypes[$this->type]))
{
$method=self::$coreTypes[$this->type];
if($method==='linkButton')
{
if(!isset($attributes['params'][$this->name]))
$attributes['params'][$this->name]=1;
}
elseif($method==='htmlButton')
{
$attributes['type']=$this->type==='htmlSubmit' ? 'submit' : ($this->type==='htmlReset' ? 'reset' : 'button');
$attributes['name']=$this->name;
}
else
$attributes['name']=$this->name;
if($method==='imageButton')
return CHtml::imageButton(isset($attributes['src']) ? $attributes['src'] : '',$attributes);
else
return CHtml::$method($this->label,$attributes);
}
else
{
$attributes['name']=$this->name;
ob_start();
$this->getParent()->getOwner()->widget($this->type, $attributes);
return ob_get_clean();
}
}
/**
* Evaluates the visibility of this element.
* This method will check the {@link on} property to see if
* the model is in a scenario that should have this string displayed.
* @return boolean whether this element is visible.
*/
protected function evaluateVisible()
{
return empty($this->_on) || in_array($this->getParent()->getModel()->getScenario(),$this->_on);
}
}

View File

@@ -0,0 +1,167 @@
<?php
/**
* CFormElement 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/
*/
/**
* CFormElement is the base class for presenting all kinds of form element.
*
* CFormElement implements the way to get and set arbitrary attributes.
*
* @property boolean $visible Whether this element is visible and should be rendered.
* @property mixed $parent The direct parent of this element. This could be either a {@link CForm} object or a {@link CBaseController} object
* (a controller or a widget).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.form
* @since 1.1
*/
abstract class CFormElement extends CComponent
{
/**
* @var array list of attributes (name=>value) for the HTML element represented by this object.
*/
public $attributes=array();
private $_parent;
private $_visible;
/**
* Renders this element.
* @return string the rendering result
*/
abstract function render();
/**
* Constructor.
* @param mixed $config the configuration for this element.
* @param mixed $parent the direct parent of this element.
* @see configure
*/
public function __construct($config,$parent)
{
$this->configure($config);
$this->_parent=$parent;
}
/**
* Converts the object to a string.
* This is a PHP magic method.
* The default implementation simply calls {@link render} and return
* the rendering result.
* @return string the string representation of this object.
*/
public function __toString()
{
return $this->render();
}
/**
* Returns a property value or an attribute value.
* Do not call this method. This is a PHP magic method that we override
* to allow using the following syntax to read a property or attribute:
* <pre>
* $value=$element->propertyName;
* $value=$element->attributeName;
* </pre>
* @param string $name the property or attribute name
* @return mixed the property or attribute value
* @throws CException if the property or attribute is not defined
* @see __set
*/
public function __get($name)
{
$getter='get'.$name;
if(method_exists($this,$getter))
return $this->$getter();
elseif(isset($this->attributes[$name]))
return $this->attributes[$name];
else
throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',
array('{class}'=>get_class($this), '{property}'=>$name)));
}
/**
* Sets value of a property or attribute.
* Do not call this method. This is a PHP magic method that we override
* to allow using the following syntax to set a property or attribute.
* <pre>
* $this->propertyName=$value;
* $this->attributeName=$value;
* </pre>
* @param string $name the property or attribute name
* @param mixed $value the property or attribute value
* @see __get
*/
public function __set($name,$value)
{
$setter='set'.$name;
if(method_exists($this,$setter))
$this->$setter($value);
else
$this->attributes[$name]=$value;
}
/**
* Configures this object with property initial values.
* @param mixed $config the configuration for this object. This can be an array
* representing the property names and their initial values.
* It can also be a string representing the file name of the PHP script
* that returns a configuration array.
*/
public function configure($config)
{
if(is_string($config))
$config=require(Yii::getPathOfAlias($config).'.php');
if(is_array($config))
{
foreach($config as $name=>$value)
$this->$name=$value;
}
}
/**
* Returns a value indicating whether this element is visible and should be rendered.
* This method will call {@link evaluateVisible} to determine the visibility of this element.
* @return boolean whether this element is visible and should be rendered.
*/
public function getVisible()
{
if($this->_visible===null)
$this->_visible=$this->evaluateVisible();
return $this->_visible;
}
/**
* @param boolean $value whether this element is visible and should be rendered.
*/
public function setVisible($value)
{
$this->_visible=$value;
}
/**
* @return mixed the direct parent of this element. This could be either a {@link CForm} object or a {@link CBaseController} object
* (a controller or a widget).
*/
public function getParent()
{
return $this->_parent;
}
/**
* Evaluates the visibility of this element.
* Child classes should override this method to implement the actual algorithm
* for determining the visibility.
* @return boolean whether this element is visible. Defaults to true.
*/
protected function evaluateVisible()
{
return true;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* CFormElementCollection 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/
*/
/**
* CFormElementCollection implements the collection for storing form elements.
*
* Because CFormElementCollection extends from {@link CMap}, it can be used like an associative array.
* For example,
* <pre>
* $element=$collection['username'];
* $collection['username']=array('type'=>'text', 'maxlength'=>128);
* $collection['password']=new CFormInputElement(array('type'=>'password'),$form);
* $collection[]='some string';
* </pre>
*
* CFormElementCollection can store three types of value: a configuration array, a {@link CFormElement}
* object, or a string, as shown in the above example. Internally, these values will be converted
* to {@link CFormElement} objects.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.form
* @since 1.1
*/
class CFormElementCollection extends CMap
{
private $_form;
private $_forButtons;
/**
* Constructor.
* @param CForm $form the form object that owns this collection
* @param boolean $forButtons whether this collection is used to store buttons.
*/
public function __construct($form,$forButtons=false)
{
parent::__construct();
$this->_form=$form;
$this->_forButtons=$forButtons;
}
/**
* Adds an item to the collection.
* This method overrides the parent implementation to ensure
* only configuration arrays, strings, or {@link CFormElement} objects
* can be stored in this collection.
* @param mixed $key key
* @param mixed $value value
* @throws CException if the value is invalid.
*/
public function add($key,$value)
{
if(is_array($value))
{
if(is_string($key))
$value['name']=$key;
if($this->_forButtons)
{
$class=$this->_form->buttonElementClass;
$element=new $class($value,$this->_form);
}
else
{
if(!isset($value['type']))
$value['type']='text';
if($value['type']==='string')
{
unset($value['type'],$value['name']);
$element=new CFormStringElement($value,$this->_form);
}
elseif(!strcasecmp(substr($value['type'],-4),'form')) // a form
{
$class=$value['type']==='form' ? get_class($this->_form) : Yii::import($value['type']);
$element=new $class($value,null,$this->_form);
}
else
{
$class=$this->_form->inputElementClass;
$element=new $class($value,$this->_form);
}
}
}
elseif($value instanceof CFormElement)
{
if(property_exists($value,'name') && is_string($key))
$value->name=$key;
$element=$value;
}
else
$element=new CFormStringElement(array('content'=>$value),$this->_form);
parent::add($key,$element);
$this->_form->addedElement($key,$element,$this->_forButtons);
}
/**
* Removes the specified element by key.
* @param string $key the name of the element to be removed from the collection
*/
public function remove($key)
{
if(($item=parent::remove($key))!==null)
$this->_form->removedElement($key,$item,$this->_forButtons);
}
}

View File

@@ -0,0 +1,264 @@
<?php
/**
* CFormInputElement 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/
*/
/**
* CFormInputElement represents form input element.
*
* CFormInputElement can represent the following types of form input based on {@link type} property:
* <ul>
* <li>text: a normal text input generated using {@link CHtml::activeTextField}</li>
* <li>hidden: a hidden input generated using {@link CHtml::activeHiddenField}</li>
* <li>password: a password input generated using {@link CHtml::activePasswordField}</li>
* <li>textarea: a text area generated using {@link CHtml::activeTextArea}</li>
* <li>file: a file input generated using {@link CHtml::activeFileField}</li>
* <li>radio: a radio button generated using {@link CHtml::activeRadioButton}</li>
* <li>checkbox: a check box generated using {@link CHtml::activeCheckBox}</li>
* <li>listbox: a list box generated using {@link CHtml::activeListBox}</li>
* <li>dropdownlist: a drop-down list generated using {@link CHtml::activeDropDownList}</li>
* <li>checkboxlist: a list of check boxes generated using {@link CHtml::activeCheckBoxList}</li>
* <li>radiolist: a list of radio buttons generated using {@link CHtml::activeRadioButtonList}</li>
* <li>url: an HTML5 url input generated using {@link CHtml::activeUrlField}</li>
* <li>email: an HTML5 email input generated using {@link CHtml::activeEmailField}</li>
* <li>number: an HTML5 number input generated using {@link CHtml::activeNumberField}</li>
* <li>range: an HTML5 range input generated using {@link CHtml::activeRangeField}</li>
* <li>date: an HTML5 date input generated using {@link CHtml::activeDateField}</li>
* </ul>
* The {@link type} property can also be a class name or a path alias to the class. In this case,
* the input is generated using a widget of the specified class. Note, the widget must
* have a property called "model" which expects a model object, and a property called "attribute"
* which expects the name of a model attribute.
*
* Because CFormElement is an ancestor class of CFormInputElement, a value assigned to a non-existing property will be
* stored in {@link attributes} which will be passed as HTML attribute values to the {@link CHtml} method
* generating the input or initial values of the widget properties.
*
* @property boolean $required Whether this input is required.
* @property string $label The label for this input. If the label is not manually set,
* this method will call {@link CModel::getAttributeLabel} to determine the label.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.form
* @since 1.1
*/
class CFormInputElement extends CFormElement
{
/**
* @var array Core input types (alias=>CHtml method name)
*/
public static $coreTypes=array(
'text'=>'activeTextField',
'hidden'=>'activeHiddenField',
'password'=>'activePasswordField',
'textarea'=>'activeTextArea',
'file'=>'activeFileField',
'radio'=>'activeRadioButton',
'checkbox'=>'activeCheckBox',
'listbox'=>'activeListBox',
'dropdownlist'=>'activeDropDownList',
'checkboxlist'=>'activeCheckBoxList',
'radiolist'=>'activeRadioButtonList',
'url'=>'activeUrlField',
'email'=>'activeEmailField',
'number'=>'activeNumberField',
'range'=>'activeRangeField',
'date'=>'activeDateField'
);
/**
* @var string the type of this input. This can be a widget class name, a path alias of a widget class name,
* or an input type alias (text, hidden, password, textarea, file, radio, checkbox, listbox, dropdownlist, checkboxlist, or radiolist).
* If a widget class, it must extend from {@link CInputWidget} or (@link CJuiInputWidget).
*/
public $type;
/**
* @var string name of this input
*/
public $name;
/**
* @var string hint text of this input
*/
public $hint;
/**
* @var array the options for this input when it is a list box, drop-down list, check box list, or radio button list.
* Please see {@link CHtml::listData} for details of generating this property value.
*/
public $items=array();
/**
* @var array the options used when rendering the error part. This property will be passed
* to the {@link CActiveForm::error} method call as its $htmlOptions parameter.
* @see CActiveForm::error
* @since 1.1.1
*/
public $errorOptions=array();
/**
* @var boolean whether to allow AJAX-based validation for this input. Note that in order to use
* AJAX-based validation, {@link CForm::activeForm} must be configured with 'enableAjaxValidation'=>true.
* This property allows turning on or off AJAX-based validation for individual input fields.
* Defaults to true.
* @since 1.1.7
*/
public $enableAjaxValidation=true;
/**
* @var boolean whether to allow client-side validation for this input. Note that in order to use
* client-side validation, {@link CForm::activeForm} must be configured with 'enableClientValidation'=>true.
* This property allows turning on or off client-side validation for individual input fields.
* Defaults to true.
* @since 1.1.7
*/
public $enableClientValidation=true;
/**
* @var string the layout used to render label, input, hint and error. They correspond to the placeholders
* "{label}", "{input}", "{hint}" and "{error}".
*/
public $layout="{label}\n{input}\n{hint}\n{error}";
private $_label;
private $_required;
/**
* Gets the value indicating whether this input is required.
* If this property is not set explicitly, it will be determined by calling
* {@link CModel::isAttributeRequired} for the associated model and attribute of this input.
* @return boolean whether this input is required.
*/
public function getRequired()
{
if($this->_required!==null)
return $this->_required;
else
return $this->getParent()->getModel()->isAttributeRequired($this->name);
}
/**
* @param boolean $value whether this input is required.
*/
public function setRequired($value)
{
$this->_required=$value;
}
/**
* @return string the label for this input. If the label is not manually set,
* this method will call {@link CModel::getAttributeLabel} to determine the label.
*/
public function getLabel()
{
if($this->_label!==null)
return $this->_label;
else
return $this->getParent()->getModel()->getAttributeLabel($this->name);
}
/**
* @param string $value the label for this input
*/
public function setLabel($value)
{
$this->_label=$value;
}
/**
* Renders everything for this input.
* The default implementation simply returns the result of {@link renderLabel}, {@link renderInput},
* {@link renderHint}. When {@link CForm::showErrorSummary} is false, {@link renderError} is also called
* to show error messages after individual input fields.
* @return string the complete rendering result for this input, including label, input field, hint, and error.
*/
public function render()
{
if($this->type==='hidden')
return $this->renderInput();
$output=array(
'{label}'=>$this->renderLabel(),
'{input}'=>$this->renderInput(),
'{hint}'=>$this->renderHint(),
'{error}'=>!$this->getParent()->showErrors ? '' : $this->renderError(),
);
return strtr($this->layout,$output);
}
/**
* Renders the label for this input.
* The default implementation returns the result of {@link CHtml activeLabelEx}.
* @return string the rendering result
*/
public function renderLabel()
{
$options = array(
'label'=>$this->getLabel(),
'required'=>$this->getRequired()
);
if(!empty($this->attributes['id']))
{
$options['for'] = $this->attributes['id'];
}
return CHtml::activeLabel($this->getParent()->getModel(), $this->name, $options);
}
/**
* Renders the input field.
* The default implementation returns the result of the appropriate CHtml method or the widget.
* @return string the rendering result
*/
public function renderInput()
{
if(isset(self::$coreTypes[$this->type]))
{
$method=self::$coreTypes[$this->type];
if(strpos($method,'List')!==false)
return CHtml::$method($this->getParent()->getModel(), $this->name, $this->items, $this->attributes);
else
return CHtml::$method($this->getParent()->getModel(), $this->name, $this->attributes);
}
else
{
$attributes=$this->attributes;
$attributes['model']=$this->getParent()->getModel();
$attributes['attribute']=$this->name;
ob_start();
$this->getParent()->getOwner()->widget($this->type, $attributes);
return ob_get_clean();
}
}
/**
* Renders the error display of this input.
* The default implementation returns the result of {@link CHtml::error}
* @return string the rendering result
*/
public function renderError()
{
$parent=$this->getParent();
return $parent->getActiveFormWidget()->error($parent->getModel(), $this->name, $this->errorOptions, $this->enableAjaxValidation, $this->enableClientValidation);
}
/**
* Renders the hint text for this input.
* The default implementation returns the {@link hint} property enclosed in a paragraph HTML tag.
* @return string the rendering result.
*/
public function renderHint()
{
return $this->hint===null ? '' : '<div class="hint">'.$this->hint.'</div>';
}
/**
* Evaluates the visibility of this element.
* This method will check if the attribute associated with this input is safe for
* the current model scenario.
* @return boolean whether this element is visible.
*/
protected function evaluateVisible()
{
return $this->getParent()->getModel()->isAttributeSafe($this->name);
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* CFormStringElement 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/
*/
/**
* CFormStringElement represents a string in a form.
*
* @property string $on Scenario names separated by commas. Defaults to null.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.form
* @since 1.1
*/
class CFormStringElement extends CFormElement
{
/**
* @var string the string content
*/
public $content;
private $_on;
/**
* Returns a value indicating under which scenarios this string is visible.
* If the value is empty, it means the string is visible under all scenarios.
* Otherwise, only when the model is in the scenario whose name can be found in
* this value, will the string be visible. See {@link CModel::scenario} for more
* information about model scenarios.
* @return string scenario names separated by commas. Defaults to null.
*/
public function getOn()
{
return $this->_on;
}
/**
* @param string $value scenario names separated by commas.
*/
public function setOn($value)
{
$this->_on=preg_split('/[\s,]+/',$value,-1,PREG_SPLIT_NO_EMPTY);
}
/**
* Renders this element.
* The default implementation simply returns {@link content}.
* @return string the string content
*/
public function render()
{
return $this->content;
}
/**
* Evaluates the visibility of this element.
* This method will check the {@link on} property to see if
* the model is in a scenario that should have this string displayed.
* @return boolean whether this element is visible.
*/
protected function evaluateVisible()
{
return empty($this->_on) || in_array($this->getParent()->getModel()->getScenario(),$this->_on);
}
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* CGoogleApi 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/
*/
/**
* CGoogleApi provides helper methods to easily access the {@link https://developers.google.com/loader/ Google API loader}.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.helpers
*/
class CGoogleApi
{
/**
* @var string Protocol relative url to the Google API loader which allows easy access
* to most of the Google AJAX APIs
*/
public static $bootstrapUrl='//www.google.com/jsapi';
/**
* Renders the jsapi script file.
* @param string $apiKey the API key. Null if you do not have a key.
* @return string the script tag that loads Google jsapi.
*/
public static function init($apiKey=null)
{
if($apiKey===null)
return CHtml::scriptFile(self::$bootstrapUrl);
else
return CHtml::scriptFile(self::$bootstrapUrl.'?key='.$apiKey);
}
/**
* Loads the specified API module.
* Note that you should call {@link init} first.
* @param string $name the module name
* @param string $version the module version
* @param array $options additional js options that are to be passed to the load() function.
* @return string the js code for loading the module. You can use {@link CHtml::script()}
* to enclose it in a script tag.
*/
public static function load($name,$version='1',$options=array())
{
if(empty($options))
return "google.load(\"{$name}\",\"{$version}\");";
else
return "google.load(\"{$name}\",\"{$version}\",".CJavaScript::encode($options).");";
}
/**
* Registers the specified API module.
* This is similar to {@link load} except that it registers the loading code
* with {@link CClientScript} instead of returning it.
* This method also registers the jsapi script needed by the loading call.
* @param string $name the module name
* @param string $version the module version
* @param array $options additional js options that are to be passed to the load() function.
* @param string $apiKey the API key. Null if you do not have a key.
*/
public static function register($name,$version='1',$options=array(),$apiKey=null)
{
$cs=Yii::app()->getClientScript();
$url=$apiKey===null?self::$bootstrapUrl:self::$bootstrapUrl.'?key='.$apiKey;
$cs->registerScriptFile($url,CClientScript::POS_HEAD);
$js=self::load($name,$version,$options);
$cs->registerScript($name,$js,CClientScript::POS_HEAD);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,709 @@
<?php
/**
* JSON (JavaScript Object Notation) is a lightweight data-interchange
* format. It is easy for humans to read and write. It is easy for machines
* to parse and generate. It is based on a subset of the JavaScript
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
* This feature can also be found in Python. JSON is a text format that is
* completely language independent but uses conventions that are familiar
* to programmers of the C-family of languages, including C, C++, C#, Java,
* JavaScript, Perl, TCL, and many others. These properties make JSON an
* ideal data-interchange language.
*
* This package provides a simple encoder and decoder for JSON notation. It
* is intended for use with client-side Javascript applications that make
* use of HTTPRequest to perform server communication functions - data can
* be encoded into JSON notation for use in a client-side javascript, or
* decoded from incoming Javascript requests. JSON format is native to
* Javascript, and can be directly eval()'ed with no further parsing
* overhead
*
* All strings should be in ASCII or UTF-8 format!
*
* LICENSE: Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met: Redistributions of source code must retain the
* above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* @author Michal Migurski <mike-json@teczno.com>
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @copyright 2005 Michal Migurski
* @license http://www.opensource.org/licenses/bsd-license.php
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
/**
* CJSON converts PHP data to and from JSON format.
*
* @author Michal Migurski <mike-json@teczno.com>
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @package system.web.helpers
* @since 1.0
*/
class CJSON
{
/**
* Marker constant for JSON::decode(), used to flag stack state
*/
const JSON_SLICE = 1;
/**
* Marker constant for JSON::decode(), used to flag stack state
*/
const JSON_IN_STR = 2;
/**
* Marker constant for JSON::decode(), used to flag stack state
*/
const JSON_IN_ARR = 4;
/**
* Marker constant for JSON::decode(), used to flag stack state
*/
const JSON_IN_OBJ = 8;
/**
* Marker constant for JSON::decode(), used to flag stack state
*/
const JSON_IN_CMT = 16;
/**
* Encodes an arbitrary variable into JSON format
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* If var is a string, it will be converted to UTF-8 format first before being encoded.
* @return string JSON string representation of input var
*/
public static function encode($var)
{
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false';
case 'NULL':
return 'null';
case 'integer':
return (int) $var;
case 'double':
case 'float':
return str_replace(',','.',(float)$var); // locale-independent representation
case 'string':
if (($enc=strtoupper(Yii::app()->charset))!=='UTF-8')
$var=iconv($enc, 'UTF-8', $var);
if(function_exists('json_encode'))
return json_encode($var);
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
$ascii = '';
$strlen_var = strlen($var);
/*
* Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary
*/
for ($c = 0; $c < $strlen_var; ++$c) {
$ord_var_c = ord($var{$c});
switch (true) {
case $ord_var_c == 0x08:
$ascii .= '\b';
break;
case $ord_var_c == 0x09:
$ascii .= '\t';
break;
case $ord_var_c == 0x0A:
$ascii .= '\n';
break;
case $ord_var_c == 0x0C:
$ascii .= '\f';
break;
case $ord_var_c == 0x0D:
$ascii .= '\r';
break;
case $ord_var_c == 0x22:
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
$ascii .= '\\'.$var{$c};
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $var{$c};
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, ord($var{$c+1}));
$c+=1;
$utf16 = self::utf8ToUTF16BE($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF0) == 0xE0):
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c+1}),
ord($var{$c+2}));
$c+=2;
$utf16 = self::utf8ToUTF16BE($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF8) == 0xF0):
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c+1}),
ord($var{$c+2}),
ord($var{$c+3}));
$c+=3;
$utf16 = self::utf8ToUTF16BE($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c+1}),
ord($var{$c+2}),
ord($var{$c+3}),
ord($var{$c+4}));
$c+=4;
$utf16 = self::utf8ToUTF16BE($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFE) == 0xFC):
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c+1}),
ord($var{$c+2}),
ord($var{$c+3}),
ord($var{$c+4}),
ord($var{$c+5}));
$c+=5;
$utf16 = self::utf8ToUTF16BE($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
}
}
return '"'.$ascii.'"';
case 'array':
/*
* As per JSON spec if any array key is not an integer
* we must treat the the whole array as an object. We
* also try to catch a sparsely populated associative
* array with numeric keys here because some JS engines
* will create an array with empty indexes up to
* max_index which can cause memory issues and because
* the keys, which may be relevant, will be remapped
* otherwise.
*
* As per the ECMA and JSON specification an object may
* have any string as a property. Unfortunately due to
* a hole in the ECMA specification if the key is a
* ECMA reserved word or starts with a digit the
* parameter is only accessible using ECMAScript's
* bracket notation.
*/
// treat as a JSON object
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
return '{' .
join(',', array_map(array('CJSON', 'nameValue'),
array_keys($var),
array_values($var)))
. '}';
}
// treat it like a regular array
return '[' . join(',', array_map(array('CJSON', 'encode'), $var)) . ']';
case 'object':
if ($var instanceof Traversable)
{
$vars = array();
foreach ($var as $k=>$v)
$vars[$k] = $v;
}
else
$vars = get_object_vars($var);
return '{' .
join(',', array_map(array('CJSON', 'nameValue'),
array_keys($vars),
array_values($vars)))
. '}';
default:
return '';
}
}
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
* @param string $name name of key to use
* @param mixed $value reference to an array element to be encoded
*
* @return string JSON-formatted name-value pair, like '"name":value'
* @access private
*/
protected static function nameValue($name, $value)
{
return self::encode(strval($name)) . ':' . self::encode($value);
}
/**
* reduce a string by removing leading and trailing comments and whitespace
*
* @param string $str string value to strip of comments and whitespace
*
* @return string string value stripped of comments and whitespace
* @access private
*/
protected static function reduceString($str)
{
$str = preg_replace(array(
// eliminate single line comments in '// ...' form
'#^\s*//(.+)$#m',
// eliminate multi-line comments in '/* ... */' form, at start of string
'#^\s*/\*(.+)\*/#Us',
// eliminate multi-line comments in '/* ... */' form, at end of string
'#/\*(.+)\*/\s*$#Us'
), '', $str);
// eliminate extraneous space
return trim($str);
}
/**
* decodes a JSON string into appropriate variable
*
* @param string $str JSON-formatted string
* @param boolean $useArray whether to use associative array to represent object data
* @return mixed number, boolean, string, array, or object corresponding to given JSON input string.
* Note that decode() always returns strings in ASCII or UTF-8 format!
* @access public
*/
public static function decode($str, $useArray=true)
{
if(function_exists('json_decode'))
{
$json = json_decode($str,$useArray);
// based on investigation, native fails sometimes returning null.
// see: http://gggeek.altervista.org/sw/article_20070425.html
// As of PHP 5.3.6 it still fails on some valid JSON strings
if($json !== null)
return $json;
}
$str = self::reduceString($str);
switch (strtolower($str)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
if (is_numeric($str)) {
// Lookie-loo, it's a number
// This would work on its own, but I'm trying to be
// good about returning integers where appropriate:
// return (float)$str;
// Return float or int, as appropriate
return ((float)$str == (integer)$str)
? (integer)$str
: (float)$str;
} elseif (preg_match('/^("|\').+(\1)$/s', $str, $m) && $m[1] == $m[2]) {
// STRINGS RETURNED IN UTF-8 FORMAT
$delim = substr($str, 0, 1);
$chrs = substr($str, 1, -1);
$utf8 = '';
$strlen_chrs = strlen($chrs);
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = substr($chrs, $c, 2);
$ord_chrs_c = ord($chrs{$c});
switch (true) {
case $substr_chrs_c_2 == '\b':
$utf8 .= chr(0x08);
++$c;
break;
case $substr_chrs_c_2 == '\t':
$utf8 .= chr(0x09);
++$c;
break;
case $substr_chrs_c_2 == '\n':
$utf8 .= chr(0x0A);
++$c;
break;
case $substr_chrs_c_2 == '\f':
$utf8 .= chr(0x0C);
++$c;
break;
case $substr_chrs_c_2 == '\r':
$utf8 .= chr(0x0D);
++$c;
break;
case $substr_chrs_c_2 == '\\"':
case $substr_chrs_c_2 == '\\\'':
case $substr_chrs_c_2 == '\\\\':
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
$utf8 .= $chrs{++$c};
}
break;
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
// single, escaped unicode character
$utf16 = chr(hexdec(substr($chrs, ($c+2), 2)))
. chr(hexdec(substr($chrs, ($c+4), 2)));
$utf8 .= self::utf16beToUTF8($utf16);
$c+=5;
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
$utf8 .= $chrs{$c};
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 2);
++$c;
break;
case ($ord_chrs_c & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 3);
$c += 2;
break;
case ($ord_chrs_c & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 4);
$c += 3;
break;
case ($ord_chrs_c & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 5);
$c += 4;
break;
case ($ord_chrs_c & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 6);
$c += 5;
break;
}
}
return $utf8;
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
if ($str{0} == '[') {
$stk = array(self::JSON_IN_ARR);
$arr = array();
} else {
if ($useArray) {
$stk = array(self::JSON_IN_OBJ);
$obj = array();
} else {
$stk = array(self::JSON_IN_OBJ);
$obj = new stdClass();
}
}
$stk[] = array('what' => self::JSON_SLICE, 'where' => 0, 'delim' => false);
$chrs = substr($str, 1, -1);
$chrs = self::reduceString($chrs);
if ($chrs == '') {
if (reset($stk) == self::JSON_IN_ARR) {
return $arr;
} else {
return $obj;
}
}
//print("\nparsing {$chrs}\n");
$strlen_chrs = strlen($chrs);
for ($c = 0; $c <= $strlen_chrs; ++$c) {
$top = end($stk);
$substr_chrs_c_2 = substr($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == self::JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = substr($chrs, $top['where'], ($c - $top['where']));
$stk[] = array('what' => self::JSON_SLICE, 'where' => ($c + 1), 'delim' => false);
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
if (reset($stk) == self::JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack
$arr[] = self::decode($slice,$useArray);
} elseif (reset($stk) == self::JSON_IN_OBJ) {
// we are in an object, so figure
// out the property name and set an
// element in an associative array,
// for now
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// "name":value pair
$key = self::decode($parts[1],$useArray);
$val = self::decode($parts[2],$useArray);
if ($useArray) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// name:value pair, where name is unquoted
$key = $parts[1];
$val = self::decode($parts[2],$useArray);
if ($useArray) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
}
}
} elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != self::JSON_IN_STR)) {
// found a quote, and we are not inside a string
$stk[] = array('what' => self::JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c});
//print("Found start of string at {$c}\n");
} elseif (($chrs{$c} == $top['delim']) &&
($top['what'] == self::JSON_IN_STR) &&
(($chrs{$c - 1} != "\\") ||
($chrs{$c - 1} == "\\" && $chrs{$c - 2} == "\\"))) {
// found a quote, we're in a string, and it's not escaped
array_pop($stk);
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
} elseif (($chrs{$c} == '[') &&
in_array($top['what'], array(self::JSON_SLICE, self::JSON_IN_ARR, self::JSON_IN_OBJ))) {
// found a left-bracket, and we are in an array, object, or slice
$stk[] = array('what' => self::JSON_IN_ARR, 'where' => $c, 'delim' => false);
//print("Found start of array at {$c}\n");
} elseif (($chrs{$c} == ']') && ($top['what'] == self::JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($chrs{$c} == '{') &&
in_array($top['what'], array(self::JSON_SLICE, self::JSON_IN_ARR, self::JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice
$stk[] = array('what' => self::JSON_IN_OBJ, 'where' => $c, 'delim' => false);
//print("Found start of object at {$c}\n");
} elseif (($chrs{$c} == '}') && ($top['what'] == self::JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($substr_chrs_c_2 == '/*') &&
in_array($top['what'], array(self::JSON_SLICE, self::JSON_IN_ARR, self::JSON_IN_OBJ))) {
// found a comment start, and we are in an array, object, or slice
$stk[] = array('what' => self::JSON_IN_CMT, 'where' => $c, 'delim' => false);
$c++;
//print("Found start of comment at {$c}\n");
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == self::JSON_IN_CMT)) {
// found a comment end, and we're in one now
array_pop($stk);
$c++;
for ($i = $top['where']; $i <= $c; ++$i)
$chrs = substr_replace($chrs, ' ', $i, 1);
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
}
}
if (reset($stk) == self::JSON_IN_ARR) {
return $arr;
} elseif (reset($stk) == self::JSON_IN_OBJ) {
return $obj;
}
}
}
}
/**
* This function returns any UTF-8 encoded text as a list of
* Unicode values:
* @param string $str string to convert
* @return string
* @author Scott Michael Reynen <scott@randomchaos.com>
* @link http://www.randomchaos.com/document.php?source=php_and_unicode
* @see unicodeToUTF8()
*/
protected static function utf8ToUnicode( &$str )
{
$unicode = array();
$values = array();
$lookingFor = 1;
for ($i = 0; $i < strlen( $str ); $i++ )
{
$thisValue = ord( $str[ $i ] );
if ( $thisValue < 128 )
$unicode[] = $thisValue;
else
{
if ( count( $values ) == 0 )
$lookingFor = ( $thisValue < 224 ) ? 2 : 3;
$values[] = $thisValue;
if ( count( $values ) == $lookingFor )
{
$number = ( $lookingFor == 3 ) ?
( ( $values[0] % 16 ) * 4096 ) + ( ( $values[1] % 64 ) * 64 ) + ( $values[2] % 64 ):
( ( $values[0] % 32 ) * 64 ) + ( $values[1] % 64 );
$unicode[] = $number;
$values = array();
$lookingFor = 1;
}
}
}
return $unicode;
}
/**
* This function converts a Unicode array back to its UTF-8 representation
* @param string $str string to convert
* @return string
* @author Scott Michael Reynen <scott@randomchaos.com>
* @link http://www.randomchaos.com/document.php?source=php_and_unicode
* @see utf8ToUnicode()
*/
protected static function unicodeToUTF8( &$str )
{
$utf8 = '';
foreach( $str as $unicode )
{
if ( $unicode < 128 )
{
$utf8.= chr( $unicode );
}
elseif ( $unicode < 2048 )
{
$utf8.= chr( 192 + ( ( $unicode - ( $unicode % 64 ) ) / 64 ) );
$utf8.= chr( 128 + ( $unicode % 64 ) );
}
else
{
$utf8.= chr( 224 + ( ( $unicode - ( $unicode % 4096 ) ) / 4096 ) );
$utf8.= chr( 128 + ( ( ( $unicode % 4096 ) - ( $unicode % 64 ) ) / 64 ) );
$utf8.= chr( 128 + ( $unicode % 64 ) );
}
}
return $utf8;
}
/**
* UTF-8 to UTF-16BE conversion.
*
* Maybe really UCS-2 without mb_string due to utf8ToUnicode limits
* @param string $str string to convert
* @param boolean $bom whether to output BOM header
* @return string
*/
protected static function utf8ToUTF16BE(&$str, $bom = false)
{
$out = $bom ? "\xFE\xFF" : '';
if(function_exists('mb_convert_encoding'))
return $out.mb_convert_encoding($str,'UTF-16BE','UTF-8');
$uni = self::utf8ToUnicode($str);
foreach($uni as $cp)
$out .= pack('n',$cp);
return $out;
}
/**
* UTF-8 to UTF-16BE conversion.
*
* Maybe really UCS-2 without mb_string due to utf8ToUnicode limits
* @param string $str string to convert
* @return string
*/
protected static function utf16beToUTF8(&$str)
{
$uni = unpack('n*',$str);
return self::unicodeToUTF8($uni);
}
}

View File

@@ -0,0 +1,127 @@
<?php
/**
* CJavaScript helper 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/
*/
/**
* CJavaScript is a helper class containing JavaScript-related handling functions.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @package system.web.helpers
* @since 1.0
*/
class CJavaScript
{
/**
* Quotes a javascript string.
* After processing, the string can be safely enclosed within a pair of
* quotation marks and serve as a javascript string.
* @param string $js string to be quoted
* @param boolean $forUrl whether this string is used as a URL
* @return string the quoted string
*/
public static function quote($js,$forUrl=false)
{
if($forUrl)
return strtr($js,array('%'=>'%25',"\t"=>'\t',"\n"=>'\n',"\r"=>'\r','"'=>'\"','\''=>'\\\'','\\'=>'\\\\','</'=>'<\/'));
else
return strtr($js,array("\t"=>'\t',"\n"=>'\n',"\r"=>'\r','"'=>'\"','\''=>'\\\'','\\'=>'\\\\','</'=>'<\/'));
}
/**
* Encodes a PHP variable into javascript representation.
*
* Example:
* <pre>
* $options=array('key1'=>true,'key2'=>123,'key3'=>'value');
* echo CJavaScript::encode($options);
* // The following javascript code would be generated:
* // {'key1':true,'key2':123,'key3':'value'}
* </pre>
*
* For highly complex data structures use {@link jsonEncode} and {@link jsonDecode}
* to serialize and unserialize.
*
* If you are encoding user input, make sure $safe is set to true.
*
* @param mixed $value PHP variable to be encoded
* @param boolean $safe If true, 'js:' will not be allowed. In case of
* wrapping code with {@link CJavaScriptExpression} JavaScript expression
* will stay as is no matter what value this parameter is set to.
* Default is false. This parameter is available since 1.1.11.
* @return string the encoded string
*/
public static function encode($value,$safe=false)
{
if(is_string($value))
{
if(strpos($value,'js:')===0 && $safe===false)
return substr($value,3);
else
return "'".self::quote($value)."'";
}
elseif($value===null)
return 'null';
elseif(is_bool($value))
return $value?'true':'false';
elseif(is_integer($value))
return "$value";
elseif(is_float($value))
{
if($value===-INF)
return 'Number.NEGATIVE_INFINITY';
elseif($value===INF)
return 'Number.POSITIVE_INFINITY';
else
return str_replace(',','.',(float)$value); // locale-independent representation
}
elseif($value instanceof CJavaScriptExpression)
return $value->__toString();
elseif(is_object($value))
return self::encode(get_object_vars($value),$safe);
elseif(is_array($value))
{
$es=array();
if(($n=count($value))>0 && array_keys($value)!==range(0,$n-1))
{
foreach($value as $k=>$v)
$es[]="'".self::quote($k)."':".self::encode($v,$safe);
return '{'.implode(',',$es).'}';
}
else
{
foreach($value as $v)
$es[]=self::encode($v,$safe);
return '['.implode(',',$es).']';
}
}
else
return '';
}
/**
* Returns the JSON representation of the PHP data.
* @param mixed $data the data to be encoded
* @return string the JSON representation of the PHP data.
*/
public static function jsonEncode($data)
{
return CJSON::encode($data);
}
/**
* Decodes a JSON string.
* @param string $data the data to be decoded
* @param boolean $useArray whether to use associative array to represent object data
* @return mixed the decoded PHP data
*/
public static function jsonDecode($data,$useArray=true)
{
return CJSON::decode($data,$useArray);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* CJavaScriptExpression class file.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
/**
* CJavaScriptExpression represents a JavaScript expression that does not need escaping.
* It can be passed to {@link CJavaScript::encode()} and the code will stay as is.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @package system.web.helpers
* @since 1.1.11
*/
class CJavaScriptExpression
{
/**
* @var string the javascript expression wrapped by this object
*/
public $code;
/**
* @param string $code a javascript expression that is to be wrapped by this object
* @throws CException if argument is not a string
*/
public function __construct($code)
{
if(!is_string($code))
throw new CException('Value passed to CJavaScriptExpression should be a string.');
if(strpos($code, 'js:')===0)
$code=substr($code,3);
$this->code=$code;
}
/**
* String magic method
* @return string the javascript expression wrapped by this object
*/
public function __toString()
{
return $this->code;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* Built-in client script packages.
*
* Please see {@link CClientScript::packages} for explanation of the structure
* of the returned array.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2013 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
return array(
'jquery'=>array(
'js'=>array(YII_DEBUG ? 'jquery.js' : 'jquery.min.js'),
),
'yii'=>array(
'js'=>array('jquery.yii.js'),
'depends'=>array('jquery'),
),
'yiitab'=>array(
'js'=>array('jquery.yiitab.js'),
'depends'=>array('jquery'),
),
'yiiactiveform'=>array(
'js'=>array('jquery.yiiactiveform.js'),
'depends'=>array('jquery'),
),
'jquery.ui'=>array(
'js'=>array('jui/js/jquery-ui.min.js'),
'depends'=>array('jquery'),
),
'bgiframe'=>array(
'js'=>array('jquery.bgiframe.js'),
'depends'=>array('jquery'),
),
'ajaxqueue'=>array(
'js'=>array('jquery.ajaxqueue.js'),
'depends'=>array('jquery'),
),
'autocomplete'=>array(
'js'=>array('jquery.autocomplete.js'),
'depends'=>array('jquery', 'bgiframe', 'ajaxqueue'),
),
'maskedinput'=>array(
'js'=>array(YII_DEBUG ? 'jquery.maskedinput.js' : 'jquery.maskedinput.min.js'),
'depends'=>array('jquery'),
),
'cookie'=>array(
'js'=>array('jquery.cookie.js'),
'depends'=>array('jquery'),
),
'treeview'=>array(
'js'=>array('jquery.treeview.js', 'jquery.treeview.edit.js', 'jquery.treeview.async.js'),
'depends'=>array('jquery', 'cookie'),
),
'multifile'=>array(
'js'=>array('jquery.multifile.js'),
'depends'=>array('jquery'),
),
'rating'=>array(
'js'=>array('jquery.rating.js'),
'depends'=>array('jquery', 'metadata'),
),
'metadata'=>array(
'js'=>array('jquery.metadata.js'),
'depends'=>array('jquery'),
),
'bbq'=>array(
'js'=>array(YII_DEBUG ? 'jquery.ba-bbq.js' : 'jquery.ba-bbq.min.js'),
'depends'=>array('jquery'),
),
'history'=>array(
'js'=>array('jquery.history.js'),
'depends'=>array('jquery'),
),
'punycode'=>array(
'js'=>array(YII_DEBUG ? 'punycode.js' : 'punycode.min.js'),
),
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,48 @@
.ac_results {
padding: 0px;
border: 1px solid black;
background-color: white;
overflow: hidden;
z-index: 99999;
}
.ac_results ul {
width: 100%;
list-style-position: outside;
list-style: none;
padding: 0;
margin: 0;
}
.ac_results li {
margin: 0px;
padding: 2px 5px;
cursor: default;
display: block;
/*
if width will be 100% horizontal scrollbar will apear
when scroll mode will be used
*/
/*width: 100%;*/
font: menu;
font-size: 12px;
/*
it is very important, if line-height not setted or setted
in relative units scroll will be broken in firefox
*/
line-height: 16px;
overflow: hidden;
}
.ac_loading {
background: white url('indicator.gif') right center no-repeat;
}
.ac_odd {
background-color: #eee;
}
.ac_over {
background-color: #0A246A;
color: white;
}

View File

@@ -0,0 +1,116 @@
/**
* Ajax Queue Plugin
*
* Homepage: http://jquery.com/plugins/project/ajaxqueue
* Documentation: http://docs.jquery.com/AjaxQueue
*/
/**
<script>
$(function(){
jQuery.ajaxQueue({
url: "test.php",
success: function(html){ jQuery("ul").append(html); }
});
jQuery.ajaxQueue({
url: "test.php",
success: function(html){ jQuery("ul").append(html); }
});
jQuery.ajaxSync({
url: "test.php",
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
});
jQuery.ajaxSync({
url: "test.php",
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
});
});
</script>
<ul style="position: absolute; top: 5px; right: 5px;"></ul>
*/
/*
* Queued Ajax requests.
* A new Ajax request won't be started until the previous queued
* request has finished.
*/
/*
* Synced Ajax requests.
* The Ajax request will happen as soon as you call this method, but
* the callbacks (success/error/complete) won't fire until all previous
* synced requests have been completed.
*/
(function($) {
var ajax = $.ajax;
var pendingRequests = {};
var synced = [];
var syncedData = [];
$.ajax = function(settings) {
// create settings for compatibility with ajaxSetup
settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
var port = settings.port;
switch(settings.mode) {
case "abort":
if ( pendingRequests[port] ) {
pendingRequests[port].abort();
}
return pendingRequests[port] = ajax.apply(this, arguments);
case "queue":
var _old = settings.complete;
settings.complete = function(){
if ( _old )
_old.apply( this, arguments );
jQuery([ajax]).dequeue("ajax" + port );;
};
jQuery([ ajax ]).queue("ajax" + port, function(){
ajax( settings );
});
return;
case "sync":
var pos = synced.length;
synced[ pos ] = {
error: settings.error,
success: settings.success,
complete: settings.complete,
done: false
};
syncedData[ pos ] = {
error: [],
success: [],
complete: []
};
settings.error = function(){ syncedData[ pos ].error = arguments; };
settings.success = function(){ syncedData[ pos ].success = arguments; };
settings.complete = function(){
syncedData[ pos ].complete = arguments;
synced[ pos ].done = true;
if ( pos == 0 || !synced[ pos-1 ] )
for ( var i = pos; i < synced.length && synced[i].done; i++ ) {
if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error );
if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success );
if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete );
synced[i] = null;
syncedData[i] = null;
}
};
}
return ajax.apply(this, arguments);
};
})(jQuery);

View File

@@ -0,0 +1,813 @@
/*
* jQuery Autocomplete plugin 1.1
*
* Modified for Yii Framework:
* - Renamed "autocomplete" to "legacyautocomplete".
* - Fixed IE8 problems (mario.ffranco).
*
* Copyright (c) 2009 Jörn Zaefferer
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
*/
;(function($) {
$.fn.extend({
legacyautocomplete: function(urlOrData, options) {
var isUrl = typeof urlOrData == "string";
options = $.extend({}, $.Autocompleter.defaults, {
url: isUrl ? urlOrData : null,
data: isUrl ? null : urlOrData,
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
max: options && !options.scroll ? 10 : 150
}, options);
// if highlight is set to false, replace it with a do-nothing function
options.highlight = options.highlight || function(value) { return value; };
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
options.formatMatch = options.formatMatch || options.formatItem;
return this.each(function() {
new $.Autocompleter(this, options);
});
},
result: function(handler) {
return this.bind("result", handler);
},
search: function(handler) {
return this.trigger("search", [handler]);
},
flushCache: function() {
return this.trigger("flushCache");
},
setOptions: function(options){
return this.trigger("setOptions", [options]);
},
unautocomplete: function() {
return this.trigger("unautocomplete");
}
});
$.Autocompleter = function(input, options) {
var KEY = {
UP: 38,
DOWN: 40,
DEL: 46,
TAB: 9,
RETURN: 13,
ESC: 27,
COMMA: 188,
PAGEUP: 33,
PAGEDOWN: 34,
BACKSPACE: 8
};
// Create $ object for input element
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
var timeout;
var previousValue = "";
var cache = $.Autocompleter.Cache(options);
var hasFocus = 0;
var lastKeyPressCode;
var config = {
mouseDownOnSelect: false
};
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
var blockSubmit;
// prevent form submit in opera when selecting with return key
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
if (blockSubmit) {
blockSubmit = false;
return false;
}
});
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
// a keypress means the input has focus
// avoids issue where input had focus before the autocomplete was applied
hasFocus = 1;
// track last key pressed
lastKeyPressCode = event.keyCode;
switch(event.keyCode) {
case KEY.UP:
event.preventDefault();
if ( select.visible() ) {
select.prev();
} else {
onChange(0, true);
}
break;
case KEY.DOWN:
event.preventDefault();
if ( select.visible() ) {
select.next();
} else {
onChange(0, true);
}
break;
case KEY.PAGEUP:
event.preventDefault();
if ( select.visible() ) {
select.pageUp();
} else {
onChange(0, true);
}
break;
case KEY.PAGEDOWN:
event.preventDefault();
if ( select.visible() ) {
select.pageDown();
} else {
onChange(0, true);
}
break;
// matches also semicolon
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
case KEY.TAB:
case KEY.RETURN:
if( selectCurrent() ) {
// stop default to prevent a form submit, Opera needs special handling
event.preventDefault();
blockSubmit = true;
return false;
}
break;
case KEY.ESC:
select.hide();
break;
default:
clearTimeout(timeout);
timeout = setTimeout(onChange, options.delay);
break;
}
}).focus(function(){
// track whether the field has focus, we shouldn't process any
// results if the field no longer has focus
hasFocus++;
}).blur(function() {
hasFocus = 0;
if (!config.mouseDownOnSelect) {
hideResults();
}
}).click(function() {
// show select when clicking in a focused field
if ( hasFocus++ > 1 && !select.visible() ) {
onChange(0, true);
}
}).bind("search", function() {
// TODO why not just specifying both arguments?
var fn = (arguments.length > 1) ? arguments[1] : null;
function findValueCallback(q, data) {
var result;
if( data && data.length ) {
for (var i=0; i < data.length; i++) {
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
result = data[i];
break;
}
}
}
if( typeof fn == "function" ) fn(result);
else $input.trigger("result", result && [result.data, result.value]);
}
$.each(trimWords($input.val()), function(i, value) {
request(value, findValueCallback, findValueCallback);
});
}).bind("flushCache", function() {
cache.flush();
}).bind("setOptions", function() {
$.extend(options, arguments[1]);
// if we've updated the data, repopulate
if ( "data" in arguments[1] )
cache.populate();
}).bind("unautocomplete", function() {
select.unbind();
$input.unbind();
$(input.form).unbind(".autocomplete");
});
function selectCurrent() {
var selected = select.selected();
if( !selected )
return false;
var v = selected.result;
previousValue = v;
if ( options.multiple ) {
var words = trimWords($input.val());
if ( words.length > 1 ) {
var seperator = options.multipleSeparator.length;
var cursorAt = $(input).selection().start;
var wordAt, progress = 0;
$.each(words, function(i, word) {
progress += word.length;
if (cursorAt <= progress) {
wordAt = i;
// Following return caused IE8 to set cursor to the start of the line.
// return false;
}
progress += seperator;
});
words[wordAt] = v;
// TODO this should set the cursor to the right position, but it gets overriden somewhere
//$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
v = words.join( options.multipleSeparator );
}
v += options.multipleSeparator;
}
$input.val(v);
hideResultsNow();
$input.trigger("result", [selected.data, selected.value]);
return true;
}
function onChange(crap, skipPrevCheck) {
if( lastKeyPressCode == KEY.DEL ) {
select.hide();
return;
}
var currentValue = $input.val();
if ( !skipPrevCheck && currentValue == previousValue )
return;
previousValue = currentValue;
currentValue = lastWord(currentValue);
if ( currentValue.length >= options.minChars) {
$input.addClass(options.loadingClass);
if (!options.matchCase)
currentValue = currentValue.toLowerCase();
request(currentValue, receiveData, hideResultsNow);
} else {
stopLoading();
select.hide();
}
};
function trimWords(value) {
if (!value)
return [""];
if (!options.multiple)
return [$.trim(value)];
return $.map(value.split(options.multipleSeparator), function(word) {
return $.trim(value).length ? $.trim(word) : null;
});
}
function lastWord(value) {
if ( !options.multiple )
return value;
var words = trimWords(value);
if (words.length == 1)
return words[0];
var cursorAt = $(input).selection().start;
if (cursorAt == value.length) {
words = trimWords(value)
} else {
words = trimWords(value.replace(value.substring(cursorAt), ""));
}
return words[words.length - 1];
}
// fills in the input box w/the first match (assumed to be the best match)
// q: the term entered
// sValue: the first matching result
function autoFill(q, sValue){
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
// if the last user key pressed was backspace, don't autofill
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
// fill in the value (keep the case the user has typed)
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
// select the portion of the value not typed by the user (so the next character will erase)
$(input).selection(previousValue.length, previousValue.length + sValue.length);
}
};
function hideResults() {
clearTimeout(timeout);
timeout = setTimeout(hideResultsNow, 200);
};
function hideResultsNow() {
var wasVisible = select.visible();
select.hide();
clearTimeout(timeout);
stopLoading();
if (options.mustMatch) {
// call search and run callback
$input.search(
function (result){
// if no value found, clear the input box
if( !result ) {
if (options.multiple) {
var words = trimWords($input.val()).slice(0, -1);
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
}
else {
$input.val( "" );
$input.trigger("result", null);
}
}
}
);
}
};
function receiveData(q, data) {
if ( data && data.length && hasFocus ) {
stopLoading();
select.display(data, q);
autoFill(q, data[0].value);
select.show();
} else {
hideResultsNow();
}
};
function request(term, success, failure) {
if (!options.matchCase)
term = term.toLowerCase();
var data = cache.load(term);
// recieve the cached data
if (data && data.length) {
success(term, data);
// if an AJAX url has been supplied, try loading the data now
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
var extraParams = {
timestamp: +new Date()
};
$.each(options.extraParams, function(key, param) {
extraParams[key] = typeof param == "function" ? param() : param;
});
$.ajax({
// try to leverage ajaxQueue plugin to abort previous requests
mode: "abort",
// limit abortion to this input
port: "autocomplete" + input.name,
dataType: options.dataType,
url: options.url,
data: $.extend({
q: lastWord(term),
limit: options.max
}, extraParams),
success: function(data) {
var parsed = options.parse && options.parse(data) || parse(data);
cache.add(term, parsed);
success(term, parsed);
}
});
} else {
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
select.emptyList();
failure(term);
}
};
function parse(data) {
var parsed = [];
var rows = data.split("\n");
for (var i=0; i < rows.length; i++) {
var row = $.trim(rows[i]);
if (row) {
row = row.split("|");
parsed[parsed.length] = {
data: row,
value: row[0],
result: options.formatResult && options.formatResult(row, row[0]) || row[0]
};
}
}
return parsed;
};
function stopLoading() {
$input.removeClass(options.loadingClass);
};
};
$.Autocompleter.defaults = {
inputClass: "ac_input",
resultsClass: "ac_results",
loadingClass: "ac_loading",
minChars: 1,
delay: 400,
matchCase: false,
matchSubset: true,
matchContains: false,
cacheLength: 10,
max: 100,
mustMatch: false,
extraParams: {},
selectFirst: true,
formatItem: function(row) { return row[0]; },
formatMatch: null,
autoFill: false,
width: 0,
multiple: false,
multipleSeparator: ", ",
highlight: function(value, term) {
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
},
scroll: true,
scrollHeight: 180
};
$.Autocompleter.Cache = function(options) {
var data = {};
var length = 0;
function matchSubset(s, sub) {
if (!options.matchCase)
s = s.toLowerCase();
var i = s.indexOf(sub);
if (options.matchContains == "word"){
i = s.toLowerCase().search("\\b" + sub.toLowerCase());
}
if (i == -1) return false;
return i == 0 || options.matchContains;
};
function add(q, value) {
if (length > options.cacheLength){
flush();
}
if (!data[q]){
length++;
}
data[q] = value;
}
function populate(){
if( !options.data ) return false;
// track the matches
var stMatchSets = {},
nullData = 0;
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
if( !options.url ) options.cacheLength = 1;
// track all options for minChars = 0
stMatchSets[""] = [];
// loop through the array and create a lookup structure
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
var rawValue = options.data[i];
// if rawValue is a string, make an array otherwise just reference the array
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
var value = options.formatMatch(rawValue, i+1, options.data.length);
if ( value === false )
continue;
var firstChar = value.charAt(0).toLowerCase();
// if no lookup array for this character exists, look it up now
if( !stMatchSets[firstChar] )
stMatchSets[firstChar] = [];
// if the match is a string
var row = {
value: value,
data: rawValue,
result: options.formatResult && options.formatResult(rawValue) || value
};
// push the current match into the set list
stMatchSets[firstChar].push(row);
// keep track of minChars zero items
if ( nullData++ < options.max ) {
stMatchSets[""].push(row);
}
};
// add the data items to the cache
$.each(stMatchSets, function(i, value) {
// increase the cache size
options.cacheLength++;
// add to the cache
add(i, value);
});
}
// populate any existing data
setTimeout(populate, 25);
function flush(){
data = {};
length = 0;
}
return {
flush: flush,
add: add,
populate: populate,
load: function(q) {
if (!options.cacheLength || !length)
return null;
/*
* if dealing w/local data and matchContains than we must make sure
* to loop through all the data collections looking for matches
*/
if( !options.url && options.matchContains ){
// track all matches
var csub = [];
// loop through all the data grids for matches
for( var k in data ){
// don't search through the stMatchSets[""] (minChars: 0) cache
// this prevents duplicates
if( k.length > 0 ){
var c = data[k];
$.each(c, function(i, x) {
// if we've got a match, add it to the array
if (matchSubset(x.value, q)) {
csub.push(x);
}
});
}
}
return csub;
} else
// if the exact item exists, use it
if (data[q]){
return data[q];
} else
if (options.matchSubset) {
for (var i = q.length - 1; i >= options.minChars; i--) {
var c = data[q.substr(0, i)];
if (c) {
var csub = [];
$.each(c, function(i, x) {
if (matchSubset(x.value, q)) {
csub[csub.length] = x;
}
});
return csub;
}
}
}
return null;
}
};
};
$.Autocompleter.Select = function (options, input, select, config) {
var CLASSES = {
ACTIVE: "ac_over"
};
var listItems,
active = -1,
data,
term = "",
needsInit = true,
element,
list;
// Create results
function init() {
if (!needsInit)
return;
element = $("<div/>")
.hide()
.addClass(options.resultsClass)
.css("position", "absolute")
.appendTo(document.body);
list = $("<ul/>").appendTo(element).mouseover( function(event) {
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
$(target(event)).addClass(CLASSES.ACTIVE);
}
}).click(function(event) {
$(target(event)).addClass(CLASSES.ACTIVE);
select();
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
input.focus();
return false;
}).mousedown(function() {
config.mouseDownOnSelect = true;
}).mouseup(function() {
config.mouseDownOnSelect = false;
});
if( options.width > 0 )
element.css("width", options.width);
needsInit = false;
}
function target(event) {
var element = event.target;
while(element && element.tagName != "LI")
element = element.parentNode;
// more fun with IE, sometimes event.target is empty, just ignore it then
if(!element)
return [];
return element;
}
function moveSelect(step) {
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
movePosition(step);
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
if(options.scroll) {
var offset = 0;
listItems.slice(0, active).each(function() {
offset += this.offsetHeight;
});
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
} else if(offset < list.scrollTop()) {
list.scrollTop(offset);
}
}
};
function movePosition(step) {
active += step;
if (active < 0) {
active = listItems.size() - 1;
} else if (active >= listItems.size()) {
active = 0;
}
}
function limitNumberOfItems(available) {
return options.max && options.max < available
? options.max
: available;
}
function fillList() {
list.empty();
var max = limitNumberOfItems(data.length);
for (var i=0; i < max; i++) {
if (!data[i])
continue;
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
if ( formatted === false )
continue;
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
$.data(li, "ac_data", data[i]);
}
listItems = list.find("li");
if ( options.selectFirst ) {
listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
active = 0;
}
// apply bgiframe if available
if ( $.fn.bgiframe )
list.bgiframe();
}
return {
display: function(d, q) {
init();
data = d;
term = q;
fillList();
},
next: function() {
moveSelect(1);
},
prev: function() {
moveSelect(-1);
},
pageUp: function() {
if (active != 0 && active - 8 < 0) {
moveSelect( -active );
} else {
moveSelect(-8);
}
},
pageDown: function() {
if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
moveSelect( listItems.size() - 1 - active );
} else {
moveSelect(8);
}
},
hide: function() {
element && element.hide();
listItems && listItems.removeClass(CLASSES.ACTIVE);
active = -1;
},
visible : function() {
return element && element.is(":visible");
},
current: function() {
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
},
show: function() {
var offset = $(input).offset();
element.css({
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
top: offset.top + input.offsetHeight,
left: offset.left
}).show();
if(options.scroll) {
list.scrollTop(0);
list.css({
maxHeight: options.scrollHeight,
overflow: 'auto'
});
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
var listHeight = 0;
listItems.each(function() {
listHeight += this.offsetHeight;
});
var scrollbarsVisible = listHeight > options.scrollHeight;
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
if (!scrollbarsVisible) {
// IE doesn't recalculate width when scrollbar disappears
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
}
}
}
},
selected: function() {
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
return selected && selected.length && $.data(selected[0], "ac_data");
},
emptyList: function (){
list && list.empty();
},
unbind: function() {
element && element.remove();
}
};
};
$.fn.selection = function(start, end) {
if (start !== undefined) {
return this.each(function() {
if( this.createTextRange ){
var selRange = this.createTextRange();
if (end === undefined || start == end) {
selRange.move("character", start);
selRange.select();
} else {
selRange.collapse(true);
selRange.moveStart("character", start);
selRange.moveEnd("character", end);
selRange.select();
}
} else if( this.setSelectionRange ){
this.setSelectionRange(start, end);
} else if( this.selectionStart ){
this.selectionStart = start;
this.selectionEnd = end;
}
});
}
var field = this[0];
if ( field.createTextRange ) {
var range = document.selection.createRange(),
orig = field.value,
teststring = "<->",
textLength = range.text.length;
range.text = teststring;
var caretAt = field.value.indexOf(teststring);
field.value = orig;
this.selection(caretAt, caretAt + textLength);
return {
start: caretAt,
end: caretAt + textLength
}
} else if( field.selectionStart !== undefined ){
return {
start: field.selectionStart,
end: field.selectionEnd
}
}
};
})(jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
/*
* jQuery BBQ: Back Button & Query Library - v1.3pre - 8/26/2010
* http://benalman.com/projects/jquery-bbq-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,r){var h,n=Array.prototype.slice,t=decodeURIComponent,a=$.param,j,c,m,y,b=$.bbq=$.bbq||{},s,x,k,e=$.event.special,d="hashchange",B="querystring",F="fragment",z="elemUrlAttr",l="href",w="src",p=/^.*\?|#.*$/g,u,H,g,i,C,E={};function G(I){return typeof I==="string"}function D(J){var I=n.call(arguments,1);return function(){return J.apply(this,I.concat(n.call(arguments)))}}function o(I){return I.replace(H,"$2")}function q(I){return I.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(K,P,I,L,J){var R,O,N,Q,M;if(L!==h){N=I.match(K?H:/^([^#?]*)\??([^#]*)(#?.*)/);M=N[3]||"";if(J===2&&G(L)){O=L.replace(K?u:p,"")}else{Q=m(N[2]);L=G(L)?m[K?F:B](L):L;O=J===2?L:J===1?$.extend({},L,Q):$.extend({},Q,L);O=j(O);if(K){O=O.replace(g,t)}}R=N[1]+(K?C:O||!N[1]?"?":"")+O+M}else{R=P(I!==h?I:location.href)}return R}a[B]=D(f,0,q);a[F]=c=D(f,1,o);a.sorted=j=function(J,K){var I=[],L={};$.each(a(J,K).split("&"),function(P,M){var O=M.replace(/(?:%5B|=).*$/,""),N=L[O];if(!N){N=L[O]=[];I.push(O)}N.push(M)});return $.map(I.sort(),function(M){return L[M]}).join("&")};c.noEscape=function(J){J=J||"";var I=$.map(J.split(""),encodeURIComponent);g=new RegExp(I.join("|"),"g")};c.noEscape(",/");c.ajaxCrawlable=function(I){if(I!==h){if(I){u=/^.*(?:#!|#)/;H=/^([^#]*)(?:#!|#)?(.*)$/;C="#!"}else{u=/^.*#/;H=/^([^#]*)#?(.*)$/;C="#"}i=!!I}return i};c.ajaxCrawlable(0);$.deparam=m=function(L,I){var K={},J={"true":!0,"false":!1,"null":null};$.each(L.replace(/\+/g," ").split("&"),function(O,T){var N=T.split("="),S=t(N[0]),M,R=K,P=0,U=S.split("]["),Q=U.length-1;if(/\[/.test(U[0])&&/\]$/.test(U[Q])){U[Q]=U[Q].replace(/\]$/,"");U=U.shift().split("[").concat(U);Q=U.length-1}else{Q=0}if(N.length===2){M=t(N[1]);if(I){M=M&&!isNaN(M)?+M:M==="undefined"?h:J[M]!==h?J[M]:M}if(Q){for(;P<=Q;P++){S=U[P]===""?R.length:U[P];R=R[S]=P<Q?R[S]||(U[P+1]&&isNaN(U[P+1])?{}:[]):M}}else{if($.isArray(K[S])){K[S].push(M)}else{if(K[S]!==h){K[S]=[K[S],M]}else{K[S]=M}}}}else{if(S){K[S]=I?h:""}}});return K};function A(K,I,J){if(I===h||typeof I==="boolean"){J=I;I=a[K?F:B]()}else{I=G(I)?I.replace(K?u:p,""):I}return m(I,J)}m[B]=D(A,0);m[F]=y=D(A,1);$[z]||($[z]=function(I){return $.extend(E,I)})({a:l,base:l,iframe:w,img:w,input:w,form:"action",link:l,script:w});k=$[z];function v(L,J,K,I){if(!G(K)&&typeof K!=="object"){I=K;K=J;J=h}return this.each(function(){var O=$(this),M=J||k()[(this.nodeName||"").toLowerCase()]||"",N=M&&O.attr(M)||"";O.attr(M,a[L](N,K,I))})}$.fn[B]=D(v,B);$.fn[F]=D(v,F);b.pushState=s=function(L,I){if(G(L)&&/^#/.test(L)&&I===h){I=2}var K=L!==h,J=c(location.href,K?L:{},K?I:2);location.href=J};b.getState=x=function(I,J){return I===h||typeof I==="boolean"?y(I):y(J)[I]};b.removeState=function(I){var J={};if(I!==h){J=x();$.each($.isArray(I)?I:arguments,function(L,K){delete J[K]})}s(J,2)};e[d]=$.extend(e[d],{add:function(I){var K;function J(M){var L=M[F]=c();M.getState=function(N,O){return N===h||typeof N==="boolean"?m(L,N):m(L,O)[N]};K.apply(this,arguments)}if($.isFunction(I)){K=I;return J}else{K=I.handler;I.handler=J}}})})(jQuery,this);
/*
* jQuery hashchange event - v1.3 - 7/21/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,e,b){var c="hashchange",h=document,f,g=$.event.special,i=h.documentMode,d="on"+c in e&&(i===b||i>7);function a(j){j=j||location.href;return"#"+j.replace(/^[^#]*#?(.*)$/,"$1")}$.fn[c]=function(j){return j?this.bind(c,j):this.trigger(c)};$.fn[c].delay=50;g[c]=$.extend(g[c],{setup:function(){if(d){return false}$(f.start)},teardown:function(){if(d){return false}$(f.stop)}});f=(function(){var j={},p,m=a(),k=function(q){return q},l=k,o=k;j.start=function(){p||n()};j.stop=function(){p&&clearTimeout(p);p=b};function n(){var r=a(),q=o(m);if(r!==m){l(m=r,q);$(e).trigger(c)}else{if(q!==m){location.href=location.href.replace(/#.*/,"")+q}}p=setTimeout(n,$.fn[c].delay)}!d&&(function(){var q,r;j.start=function(){if(!q){r=$.fn[c].src;r=r&&r+a();q=$('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){r||l(a());n()}).attr("src",r||"javascript:0").insertAfter("body")[0].contentWindow;h.onpropertychange=function(){try{if(event.propertyName==="title"){q.document.title=h.title}}catch(s){}}}};j.stop=k;o=function(){return a(q.location.href)};l=function(v,s){var u=q.document,t=$.fn[c].domain;if(v!==s){u.title=h.title;u.open();t&&u.write('<script>document.domain="'+t+'"<\/script>');u.close();q.location.hash=v}}})();return j})()})(jQuery,this);

View File

@@ -0,0 +1,39 @@
/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Version 2.1.2
*/
(function($){
$.fn.bgiframe = ($.browser.msie && /msie 6\.0/i.test(navigator.userAgent) ? function(s) {
s = $.extend({
top : 'auto', // auto == .currentStyle.borderTopWidth
left : 'auto', // auto == .currentStyle.borderLeftWidth
width : 'auto', // auto == offsetWidth
height : 'auto', // auto == offsetHeight
opacity : true,
src : 'javascript:false;'
}, s);
var html = '<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+
'style="display:block;position:absolute;z-index:-1;'+
(s.opacity !== false?'filter:Alpha(Opacity=\'0\');':'')+
'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+
'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+
'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+
'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+
'"/>';
return this.each(function() {
if ( $(this).children('iframe.bgiframe').length === 0 )
this.insertBefore( document.createElement(html), this.firstChild );
});
} : function() { return this; });
// old alias
$.fn.bgIframe = $.fn.bgiframe;
function prop(n) {
return n && n.constructor === Number ? n + 'px' : n;
}
})(jQuery);

View File

@@ -0,0 +1,92 @@
/**
* Cookie plugin
*
* Copyright (c) 2006 Klaus Hartl (stilbuero.de)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
*/
/**
* Create a cookie with the given name and value and other optional parameters.
*
* @example $.cookie('the_cookie', 'the_value');
* @desc Set the value of a cookie.
* @example $.cookie('the_cookie', 'the_value', {expires: 7, path: '/', domain: 'jquery.com', secure: true});
* @desc Create a cookie with all available options.
* @example $.cookie('the_cookie', 'the_value');
* @desc Create a session cookie.
* @example $.cookie('the_cookie', null);
* @desc Delete a cookie by passing null as value.
*
* @param String name The name of the cookie.
* @param String value The value of the cookie.
* @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
* @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
* If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
* If set to null or omitted, the cookie will be a session cookie and will not be retained
* when the the browser exits.
* @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
* @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
* @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
* require a secure protocol (like HTTPS).
* @type undefined
*
* @name $.cookie
* @cat Plugins/Cookie
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
/**
* Get the value of a cookie with the given name.
*
* @example $.cookie('the_cookie');
* @desc Get the value of a cookie.
*
* @param String name The name of the cookie.
* @return The value of the cookie.
* @type String
*
* @name $.cookie
* @cat Plugins/Cookie
* @author Klaus Hartl/klaus.hartl@stilbuero.de
*/
jQuery.cookie = function(name, value, options) {
if (typeof value != 'undefined') { // name and value given, set cookie
options = options || {};
if (value === null) {
value = '';
options.expires = -1;
}
var expires = '';
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
var date;
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
}
var path = options.path ? '; path=' + options.path : '';
var domain = options.domain ? '; domain=' + options.domain : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else { // only name given, get cookie
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
};

File diff suppressed because one or more lines are too long

9472
framework/web/js/source/jquery.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,338 @@
/*
Masked Input plugin for jQuery
Copyright (c) 2007-2013 Josh Bush (digitalbush.com)
Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
Version: 1.3.1
*/
(function($) {
function getPasteEvent() {
var el = document.createElement('input'),
name = 'onpaste';
el.setAttribute(name, '');
return (typeof el[name] === 'function')?'paste':'input';
}
var pasteEventName = getPasteEvent() + ".mask",
ua = navigator.userAgent,
iPhone = /iphone/i.test(ua),
android=/android/i.test(ua),
caretTimeoutId;
$.mask = {
//Predefined character definitions
definitions: {
'9': "[0-9]",
'a': "[A-Za-z]",
'*': "[A-Za-z0-9]"
},
dataName: "rawMaskFn",
placeholder: '_',
};
$.fn.extend({
//Helper Function for Caret positioning
caret: function(begin, end) {
var range;
if (this.length === 0 || this.is(":hidden")) {
return;
}
if (typeof begin == 'number') {
end = (typeof end === 'number') ? end : begin;
return this.each(function() {
if (this.setSelectionRange) {
this.setSelectionRange(begin, end);
} else if (this.createTextRange) {
range = this.createTextRange();
range.collapse(true);
range.moveEnd('character', end);
range.moveStart('character', begin);
range.select();
}
});
} else {
if (this[0].setSelectionRange) {
begin = this[0].selectionStart;
end = this[0].selectionEnd;
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
begin = 0 - range.duplicate().moveStart('character', -100000);
end = begin + range.text.length;
}
return { begin: begin, end: end };
}
},
unmask: function() {
return this.trigger("unmask");
},
mask: function(mask, settings) {
var input,
defs,
tests,
partialPosition,
firstNonMaskPos,
len;
if (!mask && this.length > 0) {
input = $(this[0]);
return input.data($.mask.dataName)();
}
settings = $.extend({
placeholder: $.mask.placeholder, // Load default placeholder
completed: null
}, settings);
defs = $.mask.definitions;
tests = [];
partialPosition = len = mask.length;
firstNonMaskPos = null;
$.each(mask.split(""), function(i, c) {
if (c == '?') {
len--;
partialPosition = i;
} else if (defs[c]) {
tests.push(new RegExp(defs[c]));
if (firstNonMaskPos === null) {
firstNonMaskPos = tests.length - 1;
}
} else {
tests.push(null);
}
});
return this.trigger("unmask").each(function() {
var input = $(this),
buffer = $.map(
mask.split(""),
function(c, i) {
if (c != '?') {
return defs[c] ? settings.placeholder : c;
}
}),
focusText = input.val();
function seekNext(pos) {
while (++pos < len && !tests[pos]);
return pos;
}
function seekPrev(pos) {
while (--pos >= 0 && !tests[pos]);
return pos;
}
function shiftL(begin,end) {
var i,
j;
if (begin<0) {
return;
}
for (i = begin, j = seekNext(end); i < len; i++) {
if (tests[i]) {
if (j < len && tests[i].test(buffer[j])) {
buffer[i] = buffer[j];
buffer[j] = settings.placeholder;
} else {
break;
}
j = seekNext(j);
}
}
writeBuffer();
input.caret(Math.max(firstNonMaskPos, begin));
}
function shiftR(pos) {
var i,
c,
j,
t;
for (i = pos, c = settings.placeholder; i < len; i++) {
if (tests[i]) {
j = seekNext(i);
t = buffer[i];
buffer[i] = c;
if (j < len && tests[j].test(t)) {
c = t;
} else {
break;
}
}
}
}
function keydownEvent(e) {
var k = e.which,
pos,
begin,
end;
//backspace, delete, and escape get special treatment
if (k === 8 || k === 46 || (iPhone && k === 127)) {
pos = input.caret();
begin = pos.begin;
end = pos.end;
if (end - begin === 0) {
begin=k!==46?seekPrev(begin):(end=seekNext(begin-1));
end=k===46?seekNext(end):end;
}
clearBuffer(begin, end);
shiftL(begin, end - 1);
e.preventDefault();
} else if (k == 27) {//escape
input.val(focusText);
input.caret(0, checkVal());
e.preventDefault();
}
}
function keypressEvent(e) {
var k = e.which,
pos = input.caret(),
p,
c,
next;
if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore
return;
} else if (k) {
if (pos.end - pos.begin !== 0){
clearBuffer(pos.begin, pos.end);
shiftL(pos.begin, pos.end-1);
}
p = seekNext(pos.begin - 1);
if (p < len) {
c = String.fromCharCode(k);
if (tests[p].test(c)) {
shiftR(p);
buffer[p] = c;
writeBuffer();
next = seekNext(p);
if(android){
setTimeout($.proxy($.fn.caret,input,next),0);
}else{
input.caret(next);
}
if (settings.completed && next >= len) {
settings.completed.call(input);
}
}
}
e.preventDefault();
}
}
function clearBuffer(start, end) {
var i;
for (i = start; i < end && i < len; i++) {
if (tests[i]) {
buffer[i] = settings.placeholder;
}
}
}
function writeBuffer() { input.val(buffer.join('')); }
function checkVal(allow) {
//try to place characters where they belong
var test = input.val(),
lastMatch = -1,
i,
c;
for (i = 0, pos = 0; i < len; i++) {
if (tests[i]) {
buffer[i] = settings.placeholder;
while (pos++ < test.length) {
c = test.charAt(pos - 1);
if (tests[i].test(c)) {
buffer[i] = c;
lastMatch = i;
break;
}
}
if (pos > test.length) {
break;
}
} else if (buffer[i] === test.charAt(pos) && i !== partialPosition) {
pos++;
lastMatch = i;
}
}
if (allow) {
writeBuffer();
} else if (lastMatch + 1 < partialPosition) {
input.val("");
clearBuffer(0, len);
} else {
writeBuffer();
input.val(input.val().substring(0, lastMatch + 1));
}
return (partialPosition ? i : firstNonMaskPos);
}
input.data($.mask.dataName,function(){
return $.map(buffer, function(c, i) {
return tests[i]&&c!=settings.placeholder ? c : null;
}).join('');
});
if (!input.attr("readonly"))
input
.one("unmask", function() {
input
.unbind(".mask")
.removeData($.mask.dataName);
})
.bind("focus.mask", function() {
clearTimeout(caretTimeoutId);
var pos,
moveCaret;
focusText = input.val();
pos = checkVal();
caretTimeoutId = setTimeout(function(){
writeBuffer();
if (pos == mask.length) {
input.caret(0, pos);
} else {
input.caret(pos);
}
}, 10);
})
.bind("blur.mask", function() {
checkVal();
if (input.val() != focusText)
input.change();
})
.bind("keydown.mask", keydownEvent)
.bind("keypress.mask", keypressEvent)
.bind(pasteEventName, function() {
setTimeout(function() {
var pos=checkVal(true);
input.caret(pos);
if (settings.completed && pos == input.val().length)
settings.completed.call(input);
}, 0);
});
checkVal(); //Perform initial check for existing values
});
}
});
})(jQuery);

View File

@@ -0,0 +1,7 @@
/*
Masked Input plugin for jQuery
Copyright (c) 2007-2013 Josh Bush (digitalbush.com)
Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
Version: 1.3.1
*/
(function(e){function t(){var e=document.createElement("input"),t="onpaste";return e.setAttribute(t,""),"function"==typeof e[t]?"paste":"input"}var n,a=t()+".mask",r=navigator.userAgent,i=/iphone/i.test(r),o=/android/i.test(r);e.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},dataName:"rawMaskFn",placeholder:"_"},e.fn.extend({caret:function(e,t){var n;if(0!==this.length&&!this.is(":hidden"))return"number"==typeof e?(t="number"==typeof t?t:e,this.each(function(){this.setSelectionRange?this.setSelectionRange(e,t):this.createTextRange&&(n=this.createTextRange(),n.collapse(!0),n.moveEnd("character",t),n.moveStart("character",e),n.select())})):(this[0].setSelectionRange?(e=this[0].selectionStart,t=this[0].selectionEnd):document.selection&&document.selection.createRange&&(n=document.selection.createRange(),e=0-n.duplicate().moveStart("character",-1e5),t=e+n.text.length),{begin:e,end:t})},unmask:function(){return this.trigger("unmask")},mask:function(t,r){var c,l,s,u,f,h;return!t&&this.length>0?(c=e(this[0]),c.data(e.mask.dataName)()):(r=e.extend({placeholder:e.mask.placeholder,completed:null},r),l=e.mask.definitions,s=[],u=h=t.length,f=null,e.each(t.split(""),function(e,t){"?"==t?(h--,u=e):l[t]?(s.push(RegExp(l[t])),null===f&&(f=s.length-1)):s.push(null)}),this.trigger("unmask").each(function(){function c(e){for(;h>++e&&!s[e];);return e}function d(e){for(;--e>=0&&!s[e];);return e}function m(e,t){var n,a;if(!(0>e)){for(n=e,a=c(t);h>n;n++)if(s[n]){if(!(h>a&&s[n].test(R[a])))break;R[n]=R[a],R[a]=r.placeholder,a=c(a)}b(),x.caret(Math.max(f,e))}}function p(e){var t,n,a,i;for(t=e,n=r.placeholder;h>t;t++)if(s[t]){if(a=c(t),i=R[t],R[t]=n,!(h>a&&s[a].test(i)))break;n=i}}function g(e){var t,n,a,r=e.which;8===r||46===r||i&&127===r?(t=x.caret(),n=t.begin,a=t.end,0===a-n&&(n=46!==r?d(n):a=c(n-1),a=46===r?c(a):a),k(n,a),m(n,a-1),e.preventDefault()):27==r&&(x.val(S),x.caret(0,y()),e.preventDefault())}function v(t){var n,a,i,l=t.which,u=x.caret();t.ctrlKey||t.altKey||t.metaKey||32>l||l&&(0!==u.end-u.begin&&(k(u.begin,u.end),m(u.begin,u.end-1)),n=c(u.begin-1),h>n&&(a=String.fromCharCode(l),s[n].test(a)&&(p(n),R[n]=a,b(),i=c(n),o?setTimeout(e.proxy(e.fn.caret,x,i),0):x.caret(i),r.completed&&i>=h&&r.completed.call(x))),t.preventDefault())}function k(e,t){var n;for(n=e;t>n&&h>n;n++)s[n]&&(R[n]=r.placeholder)}function b(){x.val(R.join(""))}function y(e){var t,n,a=x.val(),i=-1;for(t=0,pos=0;h>t;t++)if(s[t]){for(R[t]=r.placeholder;pos++<a.length;)if(n=a.charAt(pos-1),s[t].test(n)){R[t]=n,i=t;break}if(pos>a.length)break}else R[t]===a.charAt(pos)&&t!==u&&(pos++,i=t);return e?b():u>i+1?(x.val(""),k(0,h)):(b(),x.val(x.val().substring(0,i+1))),u?t:f}var x=e(this),R=e.map(t.split(""),function(e){return"?"!=e?l[e]?r.placeholder:e:void 0}),S=x.val();x.data(e.mask.dataName,function(){return e.map(R,function(e,t){return s[t]&&e!=r.placeholder?e:null}).join("")}),x.attr("readonly")||x.one("unmask",function(){x.unbind(".mask").removeData(e.mask.dataName)}).bind("focus.mask",function(){clearTimeout(n);var e;S=x.val(),e=y(),n=setTimeout(function(){b(),e==t.length?x.caret(0,e):x.caret(e)},10)}).bind("blur.mask",function(){y(),x.val()!=S&&x.change()}).bind("keydown.mask",g).bind("keypress.mask",v).bind(a,function(){setTimeout(function(){var e=y(!0);x.caret(e),r.completed&&e==x.val().length&&r.completed.call(x)},0)}),y()}))}})})(jQuery);

View File

@@ -0,0 +1,148 @@
/*
* Metadata - jQuery plugin for parsing metadata from elements
*
* Copyright (c) 2006 John Resig, Yehuda Katz, J<>örn Zaefferer, Paul McLanahan
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id: jquery.metadata.js 3640 2007-10-11 18:34:38Z pmclanahan $
*
*/
/**
* Sets the type of metadata to use. Metadata is encoded in JSON, and each property
* in the JSON will become a property of the element itself.
*
* There are four supported types of metadata storage:
*
* attr: Inside an attribute. The name parameter indicates *which* attribute.
*
* class: Inside the class attribute, wrapped in curly braces: { }
*
* elem: Inside a child element (e.g. a script tag). The
* name parameter indicates *which* element.
* html5: Values are stored in data-* attributes.
*
* The metadata for an element is loaded the first time the element is accessed via jQuery.
*
* As a result, you can define the metadata type, use $(expr) to load the metadata into the elements
* matched by expr, then redefine the metadata type and run another $(expr) for other elements.
*
* @name $.metadata.setType
*
* @example <p id="one" class="some_class {item_id: 1, item_label: 'Label'}">This is a p</p>
* @before $.metadata.setType("class")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from the class attribute
*
* @example <p id="one" class="some_class" data="{item_id: 1, item_label: 'Label'}">This is a p</p>
* @before $.metadata.setType("attr", "data")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a "data" attribute
*
* @example <p id="one" class="some_class"><script>{item_id: 1, item_label: 'Label'}</script>This is a p</p>
* @before $.metadata.setType("elem", "script")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a nested script element
*
* @example <p id="one" class="some_class" data-item_id="1" data-item_label="Label">This is a p</p>
* @before $.metadata.setType("html5")
* @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label"
* @desc Reads metadata from a series of data-* attributes
*
* @param String type The encoding type
* @param String name The name of the attribute to be used to get metadata (optional)
* @cat Plugins/Metadata
* @descr Sets the type of encoding to be used when loading metadata for the first time
* @type undefined
* @see metadata()
*/
(function($) {
$.extend({
metadata : {
defaults : {
type: 'class',
name: 'metadata',
cre: /({.*})/,
single: 'metadata'
},
setType: function( type, name ){
this.defaults.type = type;
this.defaults.name = name;
},
get: function( elem, opts ){
var settings = $.extend({},this.defaults,opts);
// check for empty string in single property
if ( !settings.single.length ) settings.single = 'metadata';
var data = $.data(elem, settings.single);
// returned cached data if it already exists
if ( data ) return data;
data = "{}";
var getData = function(data) {
if(typeof data != "string") return data;
if( data.indexOf('{') < 0 ) {
data = eval("(" + data + ")");
}
};
var getObject = function(data) {
if(typeof data != "string") return data;
data = eval("(" + data + ")");
return data;
};
if ( settings.type == "html5" ) {
var object = {};
$( elem.attributes ).each(function() {
var name = this.nodeName;
if(name.match(/^data-/)) name = name.replace(/^data-/, '');
else return true;
object[name] = getObject(this.nodeValue);
});
} else {
if ( settings.type == "class" ) {
var m = settings.cre.exec( elem.className );
if ( m )
data = m[1];
} else if ( settings.type == "elem" ) {
if( !elem.getElementsByTagName ) return;
var e = elem.getElementsByTagName(settings.name);
if ( e.length )
data = $.trim(e[0].innerHTML);
} else if ( elem.getAttribute != undefined ) {
var attr = elem.getAttribute( settings.name );
if ( attr )
data = attr;
}
object = getObject(data.indexOf("{") < 0 ? "{" + data + "}" : data);
}
$.data( elem, settings.single, object );
return object;
}
}
});
/**
* Returns the metadata object for the first member of the jQuery object.
*
* @name metadata
* @descr Returns element's metadata object
* @param Object opts An object contianing settings to override the defaults
* @type jQuery
* @cat Plugins/Metadata
*/
$.fn.metadata = function( opts ){
return $.metadata.get( this[0], opts );
};
})(jQuery);

2
framework/web/js/source/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,536 @@
/*
### jQuery Multiple File Upload Plugin v1.47 - 2010-03-26 ###
* Home: http://www.fyneworks.com/jquery/multiple-file-upload/
* Code: http://code.google.com/p/jquery-multifile-plugin/
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
###
*/
/*# AVOID COLLISIONS #*/
;if(window.jQuery) (function($){
/*# AVOID COLLISIONS #*/
// plugin initialization
$.fn.MultiFile = function(options){
if(this.length==0) return this; // quick fail
// Handle API methods
if(typeof arguments[0]=='string'){
// Perform API methods on individual elements
if(this.length>1){
var args = arguments;
return this.each(function(){
$.fn.MultiFile.apply($(this), args);
});
};
// Invoke API method handler
$.fn.MultiFile[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []);
// Quick exit...
return this;
};
// Initialize options for this call
var options = $.extend(
{}/* new object */,
$.fn.MultiFile.options/* default options */,
options || {} /* just-in-time options */
);
// Empty Element Fix!!!
// this code will automatically intercept native form submissions
// and disable empty file elements
$('form')
.not('MultiFile-intercepted')
.addClass('MultiFile-intercepted')
.submit($.fn.MultiFile.disableEmpty);
//### http://plugins.jquery.com/node/1363
// utility method to integrate this plugin with others...
if($.fn.MultiFile.options.autoIntercept){
$.fn.MultiFile.intercept( $.fn.MultiFile.options.autoIntercept /* array of methods to intercept */ );
$.fn.MultiFile.options.autoIntercept = null; /* only run this once */
};
// loop through each matched element
this
.not('.MultiFile-applied')
.addClass('MultiFile-applied')
.each(function(){
//#####################################################################
// MAIN PLUGIN FUNCTIONALITY - START
//#####################################################################
// BUG 1251 FIX: http://plugins.jquery.com/project/comments/add/1251
// variable group_count would repeat itself on multiple calls to the plugin.
// this would cause a conflict with multiple elements
// changes scope of variable to global so id will be unique over n calls
window.MultiFile = (window.MultiFile || 0) + 1;
var group_count = window.MultiFile;
// Copy parent attributes - Thanks to Jonas Wagner
// we will use this one to create new input elements
var MultiFile = {e:this, E:$(this), clone:$(this).clone()};
//===
//# USE CONFIGURATION
if(typeof options=='number') options = {max:options};
var o = $.extend({},
$.fn.MultiFile.options,
options || {},
($.metadata? MultiFile.E.metadata(): ($.meta?MultiFile.E.data():null)) || {}, /* metadata options */
{} /* internals */
);
// limit number of files that can be selected?
if(!(o.max>0) /*IsNull(MultiFile.max)*/){
o.max = MultiFile.E.attr('maxlength');
if(!(o.max>0) /*IsNull(MultiFile.max)*/){
o.max = (String(MultiFile.e.className.match(/\b(max|limit)\-([0-9]+)\b/gi) || ['']).match(/[0-9]+/gi) || [''])[0];
if(!(o.max>0)) o.max = -1;
else o.max = String(o.max).match(/[0-9]+/gi)[0];
}
};
o.max = new Number(o.max);
// limit extensions?
o.accept = o.accept || MultiFile.E.attr('accept') || '';
if(!o.accept){
o.accept = (MultiFile.e.className.match(/\b(accept\-[\w\|]+)\b/gi)) || '';
o.accept = new String(o.accept).replace(/^(accept|ext)\-/i,'');
};
//===
// APPLY CONFIGURATION
$.extend(MultiFile, o || {});
MultiFile.STRING = $.extend({},$.fn.MultiFile.options.STRING,MultiFile.STRING);
//===
//#########################################
// PRIVATE PROPERTIES/METHODS
$.extend(MultiFile, {
n: 0, // How many elements are currently selected?
slaves: [], files: [],
instanceKey: MultiFile.e.id || 'MultiFile'+String(group_count), // Instance Key?
generateID: function(z){ return MultiFile.instanceKey + (z>0 ?'_F'+String(z):''); },
trigger: function(event, element){
var handler = MultiFile[event], value = $(element).attr('value');
if(handler){
var returnValue = handler(element, value, MultiFile);
if( returnValue!=null ) return returnValue;
}
return true;
}
});
//===
// Setup dynamic regular expression for extension validation
// - thanks to John-Paul Bader: http://smyck.de/2006/08/11/javascript-dynamic-regular-expresions/
if(String(MultiFile.accept).length>1){
MultiFile.accept = MultiFile.accept.replace(/\W+/g,'|').replace(/^\W|\W$/g,'');
MultiFile.rxAccept = new RegExp('\\.('+(MultiFile.accept?MultiFile.accept:'')+')$','gi');
};
//===
// Create wrapper to hold our file list
MultiFile.wrapID = MultiFile.instanceKey+'_wrap'; // Wrapper ID?
MultiFile.E.wrap('<div class="MultiFile-wrap" id="'+MultiFile.wrapID+'"></div>');
MultiFile.wrapper = $('#'+MultiFile.wrapID+'');
//===
// MultiFile MUST have a name - default: file1[], file2[], file3[]
MultiFile.e.name = MultiFile.e.name || 'file'+ group_count +'[]';
//===
if(!MultiFile.list){
// Create a wrapper for the list
// * OPERA BUG: NO_MODIFICATION_ALLOWED_ERR ('list' is a read-only property)
// this change allows us to keep the files in the order they were selected
MultiFile.wrapper.append( '<div class="MultiFile-list" id="'+MultiFile.wrapID+'_list"></div>' );
MultiFile.list = $('#'+MultiFile.wrapID+'_list');
};
MultiFile.list = $(MultiFile.list);
//===
// Bind a new element
MultiFile.addSlave = function( slave, slave_count ){
//if(window.console) console.log('MultiFile.addSlave',slave_count);
// Keep track of how many elements have been displayed
MultiFile.n++;
// Add reference to master element
slave.MultiFile = MultiFile;
// BUG FIX: http://plugins.jquery.com/node/1495
// Clear identifying properties from clones
if(slave_count>0) slave.id = slave.name = '';
// Define element's ID and name (upload components need this!)
//slave.id = slave.id || MultiFile.generateID(slave_count);
if(slave_count>0) slave.id = MultiFile.generateID(slave_count);
//FIX for: http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=23
// 2008-Apr-29: New customizable naming convention (see url below)
// http://groups.google.com/group/jquery-dev/browse_frm/thread/765c73e41b34f924#
slave.name = String(MultiFile.namePattern
/*master name*/.replace(/\$name/gi,$(MultiFile.clone).attr('name'))
/*master id */.replace(/\$id/gi, $(MultiFile.clone).attr('id'))
/*group count*/.replace(/\$g/gi, group_count)//(group_count>0?group_count:''))
/*slave count*/.replace(/\$i/gi, slave_count)//(slave_count>0?slave_count:''))
);
// If we've reached maximum number, disable input slave
if( (MultiFile.max > 0) && ((MultiFile.n-1) > (MultiFile.max)) )//{ // MultiFile.n Starts at 1, so subtract 1 to find true count
slave.disabled = true;
//};
// Remember most recent slave
MultiFile.current = MultiFile.slaves[slave_count] = slave;
// We'll use jQuery from now on
slave = $(slave);
// Clear value
slave.val('').attr('value','')[0].value = '';
// Stop plugin initializing on slaves
slave.addClass('MultiFile-applied');
// Triggered when a file is selected
slave.change(function(){
//if(window.console) console.log('MultiFile.slave.change',slave_count);
// Lose focus to stop IE7 firing onchange again
$(this).blur();
//# Trigger Event! onFileSelect
if(!MultiFile.trigger('onFileSelect', this, MultiFile)) return false;
//# End Event!
//# Retrive value of selected file from element
var ERROR = '', v = String(this.value || ''/*.attr('value)*/);
// check extension
if(MultiFile.accept && v && !v.match(MultiFile.rxAccept))//{
ERROR = MultiFile.STRING.denied.replace('$ext', String(v.match(/\.\w{1,4}$/gi)));
//}
//};
// Disallow duplicates
for(var f in MultiFile.slaves)//{
if(MultiFile.slaves[f] && MultiFile.slaves[f]!=this)//{
//console.log(MultiFile.slaves[f],MultiFile.slaves[f].value);
if(MultiFile.slaves[f].value==v)//{
ERROR = MultiFile.STRING.duplicate.replace('$file', v.match(/[^\/\\]+$/gi));
//};
//};
//};
// Create a new file input element
var newEle = $(MultiFile.clone).clone();// Copy parent attributes - Thanks to Jonas Wagner
//# Let's remember which input we've generated so
// we can disable the empty ones before submission
// See: http://plugins.jquery.com/node/1495
newEle.addClass('MultiFile');
// Handle error
if(ERROR!=''){
// Handle error
MultiFile.error(ERROR);
// 2007-06-24: BUG FIX - Thanks to Adrian Wr<57>bel <adrian [dot] wrobel [at] gmail.com>
// Ditch the trouble maker and add a fresh new element
MultiFile.n--;
MultiFile.addSlave(newEle[0], slave_count);
slave.parent().prepend(newEle);
slave.remove();
return false;
};
// Hide this element (NB: display:none is evil!)
$(this).css({ position:'absolute', top: '-3000px' });
// Add new element to the form
slave.after(newEle);
// Update list
MultiFile.addToList( this, slave_count );
// Bind functionality
MultiFile.addSlave( newEle[0], slave_count+1 );
//# Trigger Event! afterFileSelect
if(!MultiFile.trigger('afterFileSelect', this, MultiFile)) return false;
//# End Event!
}); // slave.change()
// Save control to element
$(slave).data('MultiFile', MultiFile);
};// MultiFile.addSlave
// Bind a new element
// Add a new file to the list
MultiFile.addToList = function( slave, slave_count ){
//if(window.console) console.log('MultiFile.addToList',slave_count);
//# Trigger Event! onFileAppend
if(!MultiFile.trigger('onFileAppend', slave, MultiFile)) return false;
//# End Event!
// Create label elements
var
r = $('<div class="MultiFile-label"></div>'),
v = String(slave.value || ''/*.attr('value)*/),
a = $('<span class="MultiFile-title" title="'+MultiFile.STRING.selected.replace('$file', v)+'">'+MultiFile.STRING.file.replace('$file', v.match(/[^\/\\]+$/gi)[0])+'</span>'),
b = $('<a class="MultiFile-remove" href="#'+MultiFile.wrapID+'">'+MultiFile.STRING.remove+'</a>');
// Insert label
MultiFile.list.append(
r.append(b, ' ', a)
);
b
.click(function(){
//# Trigger Event! onFileRemove
if(!MultiFile.trigger('onFileRemove', slave, MultiFile)) return false;
//# End Event!
MultiFile.n--;
MultiFile.current.disabled = false;
// Remove element, remove label, point to current
MultiFile.slaves[slave_count] = null;
$(slave).remove();
$(this).parent().remove();
// Show most current element again (move into view) and clear selection
$(MultiFile.current).css({ position:'', top: '' });
$(MultiFile.current).reset().val('').attr('value', '')[0].value = '';
//# Trigger Event! afterFileRemove
if(!MultiFile.trigger('afterFileRemove', slave, MultiFile)) return false;
//# End Event!
return false;
});
//# Trigger Event! afterFileAppend
if(!MultiFile.trigger('afterFileAppend', slave, MultiFile)) return false;
//# End Event!
}; // MultiFile.addToList
// Add element to selected files list
// Bind functionality to the first element
if(!MultiFile.MultiFile) MultiFile.addSlave(MultiFile.e, 0);
// Increment control count
//MultiFile.I++; // using window.MultiFile
MultiFile.n++;
// Save control to element
MultiFile.E.data('MultiFile', MultiFile);
//#####################################################################
// MAIN PLUGIN FUNCTIONALITY - END
//#####################################################################
}); // each element
};
/*--------------------------------------------------------*/
/*
### Core functionality and API ###
*/
$.extend($.fn.MultiFile, {
/**
* This method removes all selected files
*
* Returns a jQuery collection of all affected elements.
*
* @name reset
* @type jQuery
* @cat Plugins/MultiFile
* @author Diego A. (http://www.fyneworks.com/)
*
* @example $.fn.MultiFile.reset();
*/
reset: function(){
var settings = $(this).data('MultiFile');
//if(settings) settings.wrapper.find('a.MultiFile-remove').click();
if(settings) settings.list.find('a.MultiFile-remove').click();
return $(this);
},
/**
* This utility makes it easy to disable all 'empty' file elements in the document before submitting a form.
* It marks the affected elements so they can be easily re-enabled after the form submission or validation.
*
* Returns a jQuery collection of all affected elements.
*
* @name disableEmpty
* @type jQuery
* @cat Plugins/MultiFile
* @author Diego A. (http://www.fyneworks.com/)
*
* @example $.fn.MultiFile.disableEmpty();
* @param String class (optional) A string specifying a class to be applied to all affected elements - Default: 'mfD'.
*/
disableEmpty: function(klass){ klass = (typeof(klass)=='string'?klass:'')||'mfD';
var o = [];
$('input:file.MultiFile').each(function(){ if($(this).val()=='') o[o.length] = this; });
return $(o).each(function(){ this.disabled = true }).addClass(klass);
},
/**
* This method re-enables 'empty' file elements that were disabled (and marked) with the $.fn.MultiFile.disableEmpty method.
*
* Returns a jQuery collection of all affected elements.
*
* @name reEnableEmpty
* @type jQuery
* @cat Plugins/MultiFile
* @author Diego A. (http://www.fyneworks.com/)
*
* @example $.fn.MultiFile.reEnableEmpty();
* @param String klass (optional) A string specifying the class that was used to mark affected elements - Default: 'mfD'.
*/
reEnableEmpty: function(klass){ klass = (typeof(klass)=='string'?klass:'')||'mfD';
return $('input:file.'+klass).removeClass(klass).each(function(){ this.disabled = false });
},
/**
* This method will intercept other jQuery plugins and disable empty file input elements prior to form submission
*
* @name intercept
* @cat Plugins/MultiFile
* @author Diego A. (http://www.fyneworks.com/)
*
* @example $.fn.MultiFile.intercept();
* @param Array methods (optional) Array of method names to be intercepted
*/
intercepted: {},
intercept: function(methods, context, args){
var method, value; args = args || [];
if(args.constructor.toString().indexOf("Array")<0) args = [ args ];
if(typeof(methods)=='function'){
$.fn.MultiFile.disableEmpty();
value = methods.apply(context || window, args);
//SEE-http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=27
setTimeout(function(){ $.fn.MultiFile.reEnableEmpty() },1000);
return value;
};
if(methods.constructor.toString().indexOf("Array")<0) methods = [methods];
for(var i=0;i<methods.length;i++){
method = methods[i]+''; // make sure that we have a STRING
if(method) (function(method){ // make sure that method is ISOLATED for the interception
$.fn.MultiFile.intercepted[method] = $.fn[method] || function(){};
$.fn[method] = function(){
$.fn.MultiFile.disableEmpty();
value = $.fn.MultiFile.intercepted[method].apply(this, arguments);
//SEE-http://code.google.com/p/jquery-multifile-plugin/issues/detail?id=27
setTimeout(function(){ $.fn.MultiFile.reEnableEmpty() },1000);
return value;
}; // interception
})(method); // MAKE SURE THAT method IS ISOLATED for the interception
};// for each method
} // $.fn.MultiFile.intercept
});
/*--------------------------------------------------------*/
/*
### Default Settings ###
eg.: You can override default control like this:
$.fn.MultiFile.options.accept = 'gif|jpg';
*/
$.fn.MultiFile.options = { //$.extend($.fn.MultiFile, { options: {
accept: '', // accepted file extensions
max: -1, // maximum number of selectable files
// name to use for newly created elements
namePattern: '$name', // same name by default (which creates an array)
// STRING: collection lets you show messages in different languages
STRING: {
remove:'x',
denied:'You cannot select a $ext file.\nTry again...',
file:'$file',
selected:'File selected: $file',
duplicate:'This file has already been selected:\n$file'
},
// name of methods that should be automcatically intercepted so the plugin can disable
// extra file elements that are empty before execution and automatically re-enable them afterwards
autoIntercept: [ 'submit', 'ajaxSubmit', 'ajaxForm', 'validate', 'valid' /* array of methods to intercept */ ],
// error handling function
error: function(s){
/*
ERROR! blockUI is not currently working in IE
if($.blockUI){
$.blockUI({
message: s.replace(/\n/gi,'<br/>'),
css: {
border:'none', padding:'15px', size:'12.0pt',
backgroundColor:'#900', color:'#fff',
opacity:'.8','-webkit-border-radius': '10px','-moz-border-radius': '10px'
}
});
window.setTimeout($.unblockUI, 2000);
}
else//{// save a byte!
*/
alert(s);
//}// save a byte!
}
}; //} });
/*--------------------------------------------------------*/
/*
### Additional Methods ###
Required functionality outside the plugin's scope
*/
// Native input reset method - because this alone doesn't always work: $(element).val('').attr('value', '')[0].value = '';
$.fn.reset = function(){ return this.each(function(){ try{ this.reset(); }catch(e){} }); };
/*--------------------------------------------------------*/
/*
### Default implementation ###
The plugin will attach itself to file inputs
with the class 'multi' when the page loads
*/
$(function(){
//$("input:file.multi").MultiFile();
$("input[type=file].multi").MultiFile();
});
/*# AVOID COLLISIONS #*/
})(jQuery);
/*# AVOID COLLISIONS #*/

View File

@@ -0,0 +1,376 @@
/*
### jQuery Star Rating Plugin v4.11 - 2013-03-14 ###
* Home: http://www.fyneworks.com/jquery/star-rating/
* Code: http://code.google.com/p/jquery-star-rating-plugin/
*
* Licensed under http://en.wikipedia.org/wiki/MIT_License
###
*/
/*# AVOID COLLISIONS #*/
;if(window.jQuery) (function($){
/*# AVOID COLLISIONS #*/
// IE6 Background Image Fix
if ((!$.support.opacity && !$.support.style)) try { document.execCommand("BackgroundImageCache", false, true)} catch(e) { };
// Thanks to http://www.visualjquery.com/rating/rating_redux.html
// plugin initialization
$.fn.rating = function(options){
if(this.length==0) return this; // quick fail
// Handle API methods
if(typeof arguments[0]=='string'){
// Perform API methods on individual elements
if(this.length>1){
var args = arguments;
return this.each(function(){
$.fn.rating.apply($(this), args);
});
};
// Invoke API method handler
$.fn.rating[arguments[0]].apply(this, $.makeArray(arguments).slice(1) || []);
// Quick exit...
return this;
};
// Initialize options for this call
var options = $.extend(
{}/* new object */,
$.fn.rating.options/* default options */,
options || {} /* just-in-time options */
);
// Allow multiple controls with the same name by making each call unique
$.fn.rating.calls++;
// loop through each matched element
this
.not('.star-rating-applied')
.addClass('star-rating-applied')
.each(function(){
// Load control parameters / find context / etc
var control, input = $(this);
var eid = (this.name || 'unnamed-rating').replace(/\[|\]/g, '_').replace(/^\_+|\_+$/g,'');
var context = $(this.form || document.body);
// FIX: http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=23
var raters = context.data('rating');
if(!raters || raters.call!=$.fn.rating.calls) raters = { count:0, call:$.fn.rating.calls };
var rater = raters[eid] || context.data('rating'+eid);
// if rater is available, verify that the control still exists
if(rater) control = rater.data('rating');
if(rater && control)//{// save a byte!
// add star to control if rater is available and the same control still exists
control.count++;
//}// save a byte!
else{
// create new control if first star or control element was removed/replaced
// Initialize options for this rater
control = $.extend(
{}/* new object */,
options || {} /* current call options */,
($.metadata? input.metadata(): ($.meta?input.data():null)) || {}, /* metadata options */
{ count:0, stars: [], inputs: [] }
);
// increment number of rating controls
control.serial = raters.count++;
// create rating element
rater = $('<span class="star-rating-control"/>');
input.before(rater);
// Mark element for initialization (once all stars are ready)
rater.addClass('rating-to-be-drawn');
// Accept readOnly setting from 'disabled' property
if(input.attr('disabled') || input.hasClass('disabled')) control.readOnly = true;
// Accept required setting from class property (class='required')
if(input.hasClass('required')) control.required = true;
// Create 'cancel' button
rater.append(
control.cancel = $('<div class="rating-cancel"><a title="' + control.cancel + '">' + control.cancelValue + '</a></div>')
.on('mouseover',function(){
$(this).rating('drain');
$(this).addClass('star-rating-hover');
//$(this).rating('focus');
})
.on('mouseout',function(){
$(this).rating('draw');
$(this).removeClass('star-rating-hover');
//$(this).rating('blur');
})
.on('click',function(){
$(this).rating('select');
})
.data('rating', control)
);
}; // first element of group
// insert rating star (thanks Jan Fanslau rev125 for blind support https://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=125)
var star = $('<div role="text" aria-label="'+ this.title +'" class="star-rating rater-'+ control.serial +'"><a title="' + (this.title || this.value) + '">' + this.value + '</a></div>');
rater.append(star);
// inherit attributes from input element
if(this.id) star.attr('id', this.id);
if(this.className) star.addClass(this.className);
// Half-stars?
if(control.half) control.split = 2;
// Prepare division control
if(typeof control.split=='number' && control.split>0){
var stw = ($.fn.width ? star.width() : 0) || control.starWidth;
var spi = (control.count % control.split), spw = Math.floor(stw/control.split);
star
// restrict star's width and hide overflow (already in CSS)
.width(spw)
// move the star left by using a negative margin
// this is work-around to IE's stupid box model (position:relative doesn't work)
.find('a').css({ 'margin-left':'-'+ (spi*spw) +'px' })
};
// readOnly?
if(control.readOnly)//{ //save a byte!
// Mark star as readOnly so user can customize display
star.addClass('star-rating-readonly');
//} //save a byte!
else//{ //save a byte!
// Enable hover css effects
star.addClass('star-rating-live')
// Attach mouse events
.on('mouseover',function(){
$(this).rating('fill');
$(this).rating('focus');
})
.on('mouseout',function(){
$(this).rating('draw');
$(this).rating('blur');
})
.on('click',function(){
$(this).rating('select');
})
;
//}; //save a byte!
// set current selection
if(this.checked) control.current = star;
// set current select for links
if(this.nodeName=="A"){
if($(this).hasClass('selected'))
control.current = star;
};
// hide input element
input.hide();
// backward compatibility, form element to plugin
input.on('change.rating',function(event){
if(event.selfTriggered) return false;
$(this).rating('select');
});
// attach reference to star to input element and vice-versa
star.data('rating.input', input.data('rating.star', star));
// store control information in form (or body when form not available)
control.stars[control.stars.length] = star[0];
control.inputs[control.inputs.length] = input[0];
control.rater = raters[eid] = rater;
control.context = context;
input.data('rating', control);
rater.data('rating', control);
star.data('rating', control);
context.data('rating', raters);
context.data('rating'+eid, rater); // required for ajax forms
}); // each element
// Initialize ratings (first draw)
$('.rating-to-be-drawn').rating('draw').removeClass('rating-to-be-drawn');
return this; // don't break the chain...
};
/*--------------------------------------------------------*/
/*
### Core functionality and API ###
*/
$.extend($.fn.rating, {
// Used to append a unique serial number to internal control ID
// each time the plugin is invoked so same name controls can co-exist
calls: 0,
focus: function(){
var control = this.data('rating'); if(!control) return this;
if(!control.focus) return this; // quick fail if not required
// find data for event
var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null );
// focus handler, as requested by focusdigital.co.uk
if(control.focus) control.focus.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]);
}, // $.fn.rating.focus
blur: function(){
var control = this.data('rating'); if(!control) return this;
if(!control.blur) return this; // quick fail if not required
// find data for event
var input = $(this).data('rating.input') || $( this.tagName=='INPUT' ? this : null );
// blur handler, as requested by focusdigital.co.uk
if(control.blur) control.blur.apply(input[0], [input.val(), $('a', input.data('rating.star'))[0]]);
}, // $.fn.rating.blur
fill: function(){ // fill to the current mouse position.
var control = this.data('rating'); if(!control) return this;
// do not execute when control is in read-only mode
if(control.readOnly) return;
// Reset all stars and highlight them up to this element
this.rating('drain');
this.prevAll().addBack().filter('.rater-'+ control.serial).addClass('star-rating-hover');
},// $.fn.rating.fill
drain: function() { // drain all the stars.
var control = this.data('rating'); if(!control) return this;
// do not execute when control is in read-only mode
if(control.readOnly) return;
// Reset all stars
control.rater.children().filter('.rater-'+ control.serial).removeClass('star-rating-on').removeClass('star-rating-hover');
},// $.fn.rating.drain
draw: function(){ // set value and stars to reflect current selection
var control = this.data('rating'); if(!control) return this;
// Clear all stars
this.rating('drain');
// Set control value
var current = $( control.current );//? control.current.data('rating.input') : null );
var starson = current.length ? current.prevAll().addBack().filter('.rater-'+ control.serial) : null;
if(starson) starson.addClass('star-rating-on');
// Show/hide 'cancel' button
control.cancel[control.readOnly || control.required?'hide':'show']();
// Add/remove read-only classes to remove hand pointer
this.siblings()[control.readOnly?'addClass':'removeClass']('star-rating-readonly');
},// $.fn.rating.draw
select: function(value,wantCallBack){ // select a value
var control = this.data('rating'); if(!control) return this;
// do not execute when control is in read-only mode
if(control.readOnly) return;
// clear selection
control.current = null;
// programmatically (based on user input)
if(typeof value!='undefined' || this.length>1){
// select by index (0 based)
if(typeof value=='number')
return $(control.stars[value]).rating('select',undefined,wantCallBack);
// select by literal value (must be passed as a string
if(typeof value=='string'){
//return
$.each(control.stars, function(){
//console.log($(this).data('rating.input'), $(this).data('rating.input').val(), value, $(this).data('rating.input').val()==value?'BINGO!':'');
if($(this).data('rating.input').val()==value) $(this).rating('select',undefined,wantCallBack);
});
// don't break the chain
return this;
};
}
else{
control.current = this[0].tagName=='INPUT' ?
this.data('rating.star') :
(this.is('.rater-'+ control.serial) ? this : null);
};
// Update rating control state
this.data('rating', control);
// Update display
this.rating('draw');
// find current input and its sibblings
var current = $( control.current ? control.current.data('rating.input') : null );
var lastipt = $( control.inputs ).filter(':checked');
var deadipt = $( control.inputs ).not(current);
// check and uncheck elements as required
deadipt.prop('checked',false);//.removeAttr('checked');
current.prop('checked',true);//.attr('checked','checked');
// trigger change on current or last selected input
$(current.length? current : lastipt ).trigger({ type:'change', selfTriggered:true });
// click callback, as requested here: http://plugins.jquery.com/node/1655
if((wantCallBack || wantCallBack == undefined) && control.callback) control.callback.apply(current[0], [current.val(), $('a', control.current)[0]]);// callback event
// don't break the chain
return this;
},// $.fn.rating.select
readOnly: function(toggle, disable){ // make the control read-only (still submits value)
var control = this.data('rating'); if(!control) return this;
// setread-only status
control.readOnly = toggle || toggle==undefined ? true : false;
// enable/disable control value submission
if(disable) $(control.inputs).attr("disabled", "disabled");
else $(control.inputs).removeAttr("disabled");
// Update rating control state
this.data('rating', control);
// Update display
this.rating('draw');
},// $.fn.rating.readOnly
disable: function(){ // make read-only and never submit value
this.rating('readOnly', true, true);
},// $.fn.rating.disable
enable: function(){ // make read/write and submit value
this.rating('readOnly', false, false);
}// $.fn.rating.select
});
/*--------------------------------------------------------*/
/*
### Default Settings ###
eg.: You can override default control like this:
$.fn.rating.options.cancel = 'Clear';
*/
$.fn.rating.options = { //$.extend($.fn.rating, { options: {
cancel: 'Cancel Rating', // advisory title for the 'cancel' link
cancelValue: '', // value to submit when user click the 'cancel' link
split: 0, // split the star into how many parts?
// Width of star image in case the plugin can't work it out. This can happen if
// the jQuery.dimensions plugin is not available OR the image is hidden at installation
starWidth: 16//,
//NB.: These don't need to be pre-defined (can be undefined/null) so let's save some code!
//half: false, // just a shortcut to control.split = 2
//required: false, // disables the 'cancel' button so user can only select one of the specified values
//readOnly: false, // disable rating plugin interaction/ values cannot be.one('change', //focus: function(){}, // executed when stars are focused
//blur: function(){}, // executed when stars are focused
//callback: function(){}, // executed when a star is clicked
}; //} });
/*--------------------------------------------------------*/
// auto-initialize plugin
$(function(){
$('input[type=radio].star').rating();
});
/*# AVOID COLLISIONS #*/
})(jQuery);
/*# AVOID COLLISIONS #*/

View File

@@ -0,0 +1,110 @@
/*
* Async Treeview 0.1 - Lazy-loading extension for Treeview
*
* http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
*
* Copyright (c) 2007 Jörn Zaefferer
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id$
*
*/
;(function($) {
function load(settings, root, child, container) {
function createNode(parent) {
var current = $("<li/>").attr("id", this.id || "").html("<span>" + this.text + "</span>").appendTo(parent);
if (this.classes) {
current.children("span").addClass(this.classes);
}
if (this.expanded) {
current.addClass("open");
}
if (this.hasChildren || this.children && this.children.length) {
var branch = $("<ul/>").appendTo(current);
if (this.hasChildren) {
current.addClass("hasChildren");
createNode.call({
classes: "placeholder",
text: "&nbsp;",
children:[]
}, branch);
}
if (this.children && this.children.length) {
$.each(this.children, createNode, [branch])
}
}
}
$.ajax($.extend(true, {
url: settings.url,
dataType: "json",
data: {
root: root
},
success: function(response) {
child.empty();
$.each(response, createNode, [child]);
$(container).treeview({add: child});
}
}, settings.ajax));
/*
$.getJSON(settings.url, {root: root}, function(response) {
function createNode(parent) {
var current = $("<li/>").attr("id", this.id || "").html("<span>" + this.text + "</span>").appendTo(parent);
if (this.classes) {
current.children("span").addClass(this.classes);
}
if (this.expanded) {
current.addClass("open");
}
if (this.hasChildren || this.children && this.children.length) {
var branch = $("<ul/>").appendTo(current);
if (this.hasChildren) {
current.addClass("hasChildren");
createNode.call({
classes: "placeholder",
text: "&nbsp;",
children:[]
}, branch);
}
if (this.children && this.children.length) {
$.each(this.children, createNode, [branch])
}
}
}
child.empty();
$.each(response, createNode, [child]);
$(container).treeview({add: child});
});
*/
}
var proxied = $.fn.treeview;
$.fn.treeview = function(settings) {
if (!settings.url) {
return proxied.apply(this, arguments);
}
var container = this;
if (!container.children().size())
load(settings, "source", this, container);
var userToggle = settings.toggle;
return proxied.call(this, $.extend({}, settings, {
collapsed: true,
toggle: function() {
var $this = $(this);
if ($this.hasClass("hasChildren")) {
var childList = $this.removeClass("hasChildren").find("ul");
load(settings, this.id, childList, container);
}
if (userToggle) {
userToggle.apply(this, arguments);
}
}
}));
};
})(jQuery);

View File

@@ -0,0 +1,37 @@
(function($) {
var CLASSES = $.treeview.classes;
var proxied = $.fn.treeview;
$.fn.treeview = function(settings) {
settings = $.extend({}, settings);
if (settings.add) {
return this.trigger("add", [settings.add]);
}
if (settings.remove) {
return this.trigger("remove", [settings.remove]);
}
return proxied.apply(this, arguments).bind("add", function(event, branches) {
$(branches).prev()
.removeClass(CLASSES.last)
.removeClass(CLASSES.lastCollapsable)
.removeClass(CLASSES.lastExpandable)
.find(">.hitarea")
.removeClass(CLASSES.lastCollapsableHitarea)
.removeClass(CLASSES.lastExpandableHitarea);
$(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings, $(this).data("toggler"));
}).bind("remove", function(event, branches) {
var prev = $(branches).prev();
var parent = $(branches).parent();
$(branches).remove();
prev.filter(":last-child").addClass(CLASSES.last)
.filter("." + CLASSES.expandable).replaceClass(CLASSES.last, CLASSES.lastExpandable).end()
.find(">.hitarea").replaceClass(CLASSES.expandableHitarea, CLASSES.lastExpandableHitarea).end()
.filter("." + CLASSES.collapsable).replaceClass(CLASSES.last, CLASSES.lastCollapsable).end()
.find(">.hitarea").replaceClass(CLASSES.collapsableHitarea, CLASSES.lastCollapsableHitarea);
if (parent.is(":not(:has(>))") && parent[0] != this) {
parent.parent().removeClass(CLASSES.collapsable).removeClass(CLASSES.expandable);
parent.siblings(".hitarea").andSelf().remove();
}
});
};
})(jQuery);

View File

@@ -0,0 +1,256 @@
/*
* Treeview 1.5pre - jQuery plugin to hide and show branches of a tree
*
* http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
* http://docs.jquery.com/Plugins/Treeview
*
* Copyright (c) 2007 Jörn Zaefferer
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Revision: $Id: jquery.treeview.js 5759 2008-07-01 07:50:28Z joern.zaefferer $
*
*/
;(function($) {
// TODO rewrite as a widget, removing all the extra plugins
$.extend($.fn, {
swapClass: function(c1, c2) {
var c1Elements = this.filter('.' + c1);
this.filter('.' + c2).removeClass(c2).addClass(c1);
c1Elements.removeClass(c1).addClass(c2);
return this;
},
replaceClass: function(c1, c2) {
return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
},
hoverClass: function(className) {
className = className || "hover";
return this.hover(function() {
$(this).addClass(className);
}, function() {
$(this).removeClass(className);
});
},
heightToggle: function(animated, callback) {
animated ?
this.animate({ height: "toggle" }, animated, callback) :
this.each(function(){
jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
if(callback)
callback.apply(this, arguments);
});
},
heightHide: function(animated, callback) {
if (animated) {
this.animate({ height: "hide" }, animated, callback);
} else {
this.hide();
if (callback)
this.each(callback);
}
},
prepareBranches: function(settings) {
if (!settings.prerendered) {
// mark last tree items
this.filter(":last-child:not(ul)").addClass(CLASSES.last);
// collapse whole tree, or only those marked as closed, anyway except those marked as open
this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
}
// return all items with sublists
return this.filter(":has(>ul)");
},
applyClasses: function(settings, toggler) {
// TODO use event delegation
this.filter(":has(>ul):not(:has(>a))").find(">span").unbind("click.treeview").bind("click.treeview", function(event) {
// don't handle click events on children, eg. checkboxes
if ( this == event.target )
toggler.apply($(this).next());
}).add( $("a", this) ).hoverClass();
if (!settings.prerendered) {
// handle closed ones first
this.filter(":has(>ul:hidden)")
.addClass(CLASSES.expandable)
.replaceClass(CLASSES.last, CLASSES.lastExpandable);
// handle open ones
this.not(":has(>ul:hidden)")
.addClass(CLASSES.collapsable)
.replaceClass(CLASSES.last, CLASSES.lastCollapsable);
// create hitarea if not present
var hitarea = this.find("div." + CLASSES.hitarea);
if (!hitarea.length)
hitarea = this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea);
hitarea.removeClass().addClass(CLASSES.hitarea).each(function() {
var classes = "";
$.each($(this).parent().attr("class").split(" "), function() {
classes += this + "-hitarea ";
});
$(this).addClass( classes );
})
}
// apply event to hitarea
this.find("div." + CLASSES.hitarea).click( toggler );
},
treeview: function(settings) {
settings = $.extend({
cookieId: "treeview"
}, settings);
if ( settings.toggle ) {
var callback = settings.toggle;
settings.toggle = function() {
return callback.apply($(this).parent()[0], arguments);
};
}
// factory for treecontroller
function treeController(tree, control) {
// factory for click handlers
function handler(filter) {
return function() {
// reuse toggle event handler, applying the elements to toggle
// start searching for all hitareas
toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
// for plain toggle, no filter is provided, otherwise we need to check the parent element
return filter ? $(this).parent("." + filter).length : true;
}) );
return false;
};
}
// click on first element to collapse tree
$("a:eq(0)", control).click( handler(CLASSES.collapsable) );
// click on second to expand tree
$("a:eq(1)", control).click( handler(CLASSES.expandable) );
// click on third to toggle tree
$("a:eq(2)", control).click( handler() );
}
// handle toggle event
function toggler() {
$(this)
.parent()
// swap classes for hitarea
.find(">.hitarea")
.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
.end()
// swap classes for parent li
.swapClass( CLASSES.collapsable, CLASSES.expandable )
.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
// find child lists
.find( ">ul" )
// toggle them
.heightToggle( settings.animated, settings.toggle );
if ( settings.unique ) {
$(this).parent()
.siblings()
// swap classes for hitarea
.find(">.hitarea")
.replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
.replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
.end()
.replaceClass( CLASSES.collapsable, CLASSES.expandable )
.replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
.find( ">ul" )
.heightHide( settings.animated, settings.toggle );
}
}
this.data("toggler", toggler);
function serialize() {
function binary(arg) {
return arg ? 1 : 0;
}
var data = [];
branches.each(function(i, e) {
data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
});
$.cookie(settings.cookieId, data.join(""), settings.cookieOptions );
}
function deserialize() {
var stored = $.cookie(settings.cookieId);
if ( stored ) {
var data = stored.split("");
branches.each(function(i, e) {
$(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
});
}
}
// add treeview class to activate styles
this.addClass("treeview");
// prepare branches and find all tree items with child lists
var branches = this.find("li").prepareBranches(settings);
switch(settings.persist) {
case "cookie":
var toggleCallback = settings.toggle;
settings.toggle = function() {
serialize();
if (toggleCallback) {
toggleCallback.apply(this, arguments);
}
};
deserialize();
break;
case "location":
var current = this.find("a").filter(function() {
return this.href.toLowerCase() == location.href.toLowerCase();
});
if ( current.length ) {
// TODO update the open/closed classes
var items = current.addClass("selected").parents("ul, li").add( current.next() ).show();
if (settings.prerendered) {
// if prerendered is on, replicate the basic class swapping
items.filter("li")
.swapClass( CLASSES.collapsable, CLASSES.expandable )
.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
.find(">.hitarea")
.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea );
}
}
break;
}
branches.applyClasses(settings, toggler);
// if control option is set, create the treecontroller and show it
if ( settings.control ) {
treeController(this, settings.control);
$(settings.control).show();
}
return this;
}
});
// classes used by the plugin
// need to be styled via external stylesheet, see first example
$.treeview = {};
var CLASSES = ($.treeview.classes = {
open: "open",
closed: "closed",
expandable: "expandable",
expandableHitarea: "expandable-hitarea",
lastExpandableHitarea: "lastExpandable-hitarea",
collapsable: "collapsable",
collapsableHitarea: "collapsable-hitarea",
lastCollapsableHitarea: "lastCollapsable-hitarea",
lastCollapsable: "lastCollapsable",
lastExpandable: "lastExpandable",
last: "last",
hitarea: "hitarea"
});
})(jQuery);

View File

@@ -0,0 +1,52 @@
/**
* jQuery Yii plugin file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2010 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
;(function($) {
$.yii = {
version : '1.0',
submitForm : function (element, url, params) {
var f = $(element).parents('form')[0];
if (!f) {
f = document.createElement('form');
f.style.display = 'none';
element.parentNode.appendChild(f);
f.method = 'POST';
}
if (typeof url == 'string' && url != '') {
f.action = url;
}
if (element.target != null) {
f.target = element.target;
}
var inputs = [];
$.each(params, function(name, value) {
var input = document.createElement("input");
input.setAttribute("type", "hidden");
input.setAttribute("name", name);
input.setAttribute("value", value);
f.appendChild(input);
inputs.push(input);
});
// remember who triggers the form submission
// this is used by jquery.yiiactiveform.js
$(f).data('submitObject', $(element));
$(f).trigger('submit');
$.each(inputs, function() {
f.removeChild(this);
});
}
};
})(jQuery);

View File

@@ -0,0 +1,438 @@
/**
* jQuery yiiactiveform plugin file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2010 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @since 1.1.1
*/
(function ($) {
/*
* returns the value of the CActiveForm input field
* performs additional checks to get proper values for checkbox / radiobutton / checkBoxList / radioButtonList
* @param o object the jQuery object of the input element
*/
var getAFValue = function (o) {
var type,
c = [];
if (!o.length) {
return undefined;
}
if (o[0].tagName.toLowerCase() === 'span') {
o.find(':checked').each(function () {
c.push(this.value);
});
return c.join(',');
}
type = o.attr('type');
if (type === 'checkbox' || type === 'radio') {
return o.filter(':checked').val();
} else {
return o.val();
}
};
/**
* yiiactiveform set function.
* @param options map settings for the active form plugin. Please see {@link CActiveForm::options} for availablel options.
*/
$.fn.yiiactiveform = function (options) {
return this.each(function () {
var settings = $.extend({}, $.fn.yiiactiveform.defaults, options || {}),
$form = $(this);
if (settings.validationUrl === undefined) {
settings.validationUrl = $form.attr('action');
}
$.each(settings.attributes, function (i) {
this.value = getAFValue($form.find('#' + this.inputID));
settings.attributes[i] = $.extend({}, {
validationDelay: settings.validationDelay,
validateOnChange: settings.validateOnChange,
validateOnType: settings.validateOnType,
hideErrorMessage: settings.hideErrorMessage,
inputContainer: settings.inputContainer,
errorCssClass: settings.errorCssClass,
successCssClass: settings.successCssClass,
beforeValidateAttribute: settings.beforeValidateAttribute,
afterValidateAttribute: settings.afterValidateAttribute,
validatingCssClass: settings.validatingCssClass
}, this);
});
$form.data('settings', settings);
settings.submitting = false; // whether it is waiting for ajax submission result
var validate = function (attribute, forceValidate) {
if (forceValidate) {
attribute.status = 2;
}
$.each(settings.attributes, function () {
if (this.value !== getAFValue($form.find('#' + this.inputID))) {
this.status = 2;
forceValidate = true;
}
});
if (!forceValidate) {
return;
}
if (settings.timer !== undefined) {
clearTimeout(settings.timer);
}
settings.timer = setTimeout(function () {
if (settings.submitting || $form.is(':hidden')) {
return;
}
if (attribute.beforeValidateAttribute === undefined || attribute.beforeValidateAttribute($form, attribute)) {
$.each(settings.attributes, function () {
if (this.status === 2) {
this.status = 3;
$.fn.yiiactiveform.getInputContainer(this, $form).addClass(this.validatingCssClass);
}
});
$.fn.yiiactiveform.validate($form, function (data) {
var hasError = false;
$.each(settings.attributes, function () {
if (this.status === 2 || this.status === 3) {
hasError = $.fn.yiiactiveform.updateInput(this, data, $form) || hasError;
}
});
if (attribute.afterValidateAttribute !== undefined) {
attribute.afterValidateAttribute($form, attribute, data, hasError);
}
});
}
}, attribute.validationDelay);
};
$.each(settings.attributes, function (i, attribute) {
if (this.validateOnChange) {
$form.find('#' + this.inputID).change(function () {
validate(attribute, false);
}).blur(function () {
if (attribute.status !== 2 && attribute.status !== 3) {
validate(attribute, !attribute.status);
}
});
}
if (this.validateOnType) {
$form.find('#' + this.inputID).keyup(function () {
if (attribute.value !== getAFValue($(this))) {
validate(attribute, false);
}
});
}
});
if (settings.validateOnSubmit) {
$form.on('mouseup keyup', ':submit', function () {
$form.data('submitObject', $(this));
});
var validated = false;
$form.submit(function () {
if (validated) {
validated = false;
return true;
}
if (settings.timer !== undefined) {
clearTimeout(settings.timer);
}
settings.submitting = true;
if (settings.beforeValidate === undefined || settings.beforeValidate($form)) {
$.fn.yiiactiveform.validate($form, function (data) {
var hasError = false;
$.each(settings.attributes, function () {
hasError = $.fn.yiiactiveform.updateInput(this, data, $form) || hasError;
});
$.fn.yiiactiveform.updateSummary($form, data);
if (settings.afterValidate === undefined || settings.afterValidate($form, data, hasError)) {
if (!hasError) {
validated = true;
var $button = $form.data('submitObject') || $form.find(':submit:first');
// TODO: if the submission is caused by "change" event, it will not work
if ($button.length) {
$button.click();
} else { // no submit button in the form
$form.submit();
}
return;
}
}
settings.submitting = false;
});
} else {
settings.submitting = false;
}
return false;
});
}
/*
* In case of reseting the form we need to reset error messages
* NOTE1: $form.reset - does not exist
* NOTE2: $form.on('reset', ...) does not work
*/
$form.bind('reset', function () {
/*
* because we bind directly to a form reset event, not to a reset button (that could or could not exist),
* when this function is executed form elements values have not been reset yet,
* because of that we use the setTimeout
*/
setTimeout(function () {
$.each(settings.attributes, function () {
this.status = 0;
var $error = $form.find('#' + this.errorID),
$container = $.fn.yiiactiveform.getInputContainer(this, $form);
$container.removeClass(
this.validatingCssClass + ' ' +
this.errorCssClass + ' ' +
this.successCssClass
);
$error.html('').hide();
/*
* without the setTimeout() we would get here the current entered value before the reset instead of the reseted value
*/
this.value = getAFValue($form.find('#' + this.inputID));
});
/*
* If the form is submited (non ajax) with errors, labels and input gets the class 'error'
*/
$form.find('label, :input').each(function () {
$(this).removeClass(settings.errorCss);
});
$('#' + settings.summaryID).hide().find('ul').html('');
//.. set to initial focus on reset
if (settings.focus !== undefined && !window.location.hash) {
$form.find(settings.focus).focus();
}
}, 1);
});
/*
* set to initial focus
*/
if (settings.focus !== undefined && !window.location.hash) {
$form.find(settings.focus).focus();
}
});
};
/**
* Returns the container element of the specified attribute.
* @param attribute object the configuration for a particular attribute.
* @param form the form jQuery object
* @return jQuery the jQuery representation of the container
*/
$.fn.yiiactiveform.getInputContainer = function (attribute, form) {
if (attribute.inputContainer === undefined) {
return form.find('#' + attribute.inputID).closest('div');
} else {
return form.find(attribute.inputContainer).filter(':has("#' + attribute.inputID + '")');
}
};
/**
* updates the error message and the input container for a particular attribute.
* @param attribute object the configuration for a particular attribute.
* @param messages array the json data obtained from the ajax validation request
* @param form the form jQuery object
* @return boolean whether there is a validation error for the specified attribute
*/
$.fn.yiiactiveform.updateInput = function (attribute, messages, form) {
attribute.status = 1;
var $error, $container,
hasError = false,
$el = form.find('#' + attribute.inputID),
errorCss = form.data('settings').errorCss;
if ($el.length) {
hasError = messages !== null && $.isArray(messages[attribute.id]) && messages[attribute.id].length > 0;
$error = form.find('#' + attribute.errorID);
$container = $.fn.yiiactiveform.getInputContainer(attribute, form);
$container.removeClass(
attribute.validatingCssClass + ' ' +
attribute.errorCssClass + ' ' +
attribute.successCssClass
);
$container.find('label, :input').each(function () {
$(this).removeClass(errorCss);
});
if (hasError) {
$error.html(messages[attribute.id][0]);
$container.addClass(attribute.errorCssClass);
} else if (attribute.enableAjaxValidation || attribute.clientValidation) {
$container.addClass(attribute.successCssClass);
}
if (!attribute.hideErrorMessage) {
$error.toggle(hasError);
}
attribute.value = getAFValue($el);
}
return hasError;
};
/**
* updates the error summary, if any.
* @param form jquery the jquery representation of the form
* @param messages array the json data obtained from the ajax validation request
*/
$.fn.yiiactiveform.updateSummary = function (form, messages) {
var settings = $(form).data('settings'),
content = '';
if (settings.summaryID === undefined) {
return;
}
if (messages) {
var summaryAttributes = [];
for (var i in settings.attributes) {
if (settings.attributes[i].summary) {
summaryAttributes.push(settings.attributes[i].id);
}
}
$.each(settings.attributes, function () {
if ($.inArray(this.id, summaryAttributes) !== -1 && $.isArray(messages[this.id])) {
$.each(messages[this.id], function (j, message) {
content = content + '<li>' + message + '</li>';
});
}
});
}
$('#' + settings.summaryID).toggle(content !== '').find('ul').html(content);
};
/**
* Performs the ajax validation request.
* This method is invoked internally to trigger the ajax validation.
* @param form jquery the jquery representation of the form
* @param successCallback function the function to be invoked if the ajax request succeeds
* @param errorCallback function the function to be invoked if the ajax request fails
*/
$.fn.yiiactiveform.validate = function (form, successCallback, errorCallback) {
var $form = $(form),
settings = $form.data('settings'),
needAjaxValidation = false,
messages = {};
$.each(settings.attributes, function () {
var value,
msg = [];
if (this.clientValidation !== undefined && (settings.submitting || this.status === 2 || this.status === 3)) {
value = getAFValue($form.find('#' + this.inputID));
this.clientValidation(value, msg, this);
if (msg.length) {
messages[this.id] = msg;
}
}
if (this.enableAjaxValidation && !msg.length && (settings.submitting || this.status === 2 || this.status === 3)) {
needAjaxValidation = true;
}
});
if (!needAjaxValidation || settings.submitting && !$.isEmptyObject(messages)) {
if (settings.submitting) {
// delay callback so that the form can be submitted without problem
setTimeout(function () {
successCallback(messages);
}, 200);
} else {
successCallback(messages);
}
return;
}
var $button = $form.data('submitObject'),
extData = '&' + settings.ajaxVar + '=' + $form.attr('id');
if ($button && $button.length) {
extData += '&' + $button.attr('name') + '=' + $button.attr('value');
}
$.ajax({
url: settings.validationUrl,
type: $form.attr('method'),
data: $form.serialize() + extData,
dataType: 'json',
success: function (data) {
if (data !== null && typeof data === 'object') {
$.each(settings.attributes, function () {
if (!this.enableAjaxValidation) {
delete data[this.id];
}
});
successCallback($.extend({}, messages, data));
} else {
successCallback(messages);
}
},
error: function () {
if (errorCallback !== undefined) {
errorCallback();
}
}
});
};
/**
* Returns the configuration for the specified form.
* The configuration contains all needed information to perform ajax-based validation.
* @param form jquery the jquery representation of the form
* @return object the configuration for the specified form.
*/
$.fn.yiiactiveform.getSettings = function (form) {
return $(form).data('settings');
};
$.fn.yiiactiveform.defaults = {
ajaxVar: 'ajax',
validationUrl: undefined,
validationDelay: 200,
validateOnSubmit: false,
validateOnChange: true,
validateOnType: false,
hideErrorMessage: false,
inputContainer: undefined,
errorCss: 'error',
errorCssClass: 'error',
successCssClass: 'success',
validatingCssClass: 'validating',
summaryID: undefined,
timer: undefined,
beforeValidateAttribute: undefined, // function (form, attribute) | boolean
afterValidateAttribute: undefined, // function (form, attribute, data, hasError)
beforeValidate: undefined, // function (form) | boolean
afterValidate: undefined, // function (form, data, hasError) | boolean
focus: undefined, // jquery selector that indicates which element to receive input focus initially
/**
* list of attributes to be validated. Each array element is of the following structure:
* {
* id: 'ModelClass_attribute', // the unique attribute ID
* model: 'ModelClass', // the model class name
* name: 'name', // attribute name
* inputID: 'input-tag-id',
* errorID: 'error-tag-id',
* value: undefined,
* status: 0, // 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
* validationDelay: 200,
* validateOnChange: true,
* validateOnType: false,
* hideErrorMessage: false,
* inputContainer: undefined,
* errorCssClass: 'error',
* successCssClass: 'success',
* validatingCssClass: 'validating',
* enableAjaxValidation: true,
* enableClientValidation: true,
* clientValidation: undefined, // function (value, messages, attribute) | client-side validation
* beforeValidateAttribute: undefined, // function (form, attribute) | boolean
* afterValidateAttribute: undefined, // function (form, attribute, data, hasError)
* }
*/
attributes: []
};
})(jQuery);

View File

@@ -0,0 +1,49 @@
/**
* jQuery Yii plugin file.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright 2008-2010 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
;(function($) {
$.extend($.fn, {
yiitab: function() {
function activate(id) {
var pos = id.indexOf("#");
if (pos>=0) {
id = id.substring(pos);
}
var $tab=$(id);
var $container=$tab.parent();
$container.find('>ul a').removeClass('active');
$container.find('>ul a[href="'+id+'"]').addClass('active');
$container.children('div').hide();
$tab.show();
}
this.find('>ul a').click(function(event) {
var href=$(this).attr('href');
var pos=href.indexOf('#');
activate(href);
if(pos==0 || (pos>0 && (window.location.pathname=='' || window.location.pathname==href.substring(0,pos))))
return false;
});
// activate a tab based on the current anchor
var url = decodeURI(window.location);
var pos = url.indexOf("#");
if (pos >= 0) {
var id = url.substring(pos);
if (this.find('>ul a[href="'+id+'"]').length > 0) {
activate(id);
return;
}
}
}
});
})(jQuery);

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,5 @@
/*! jQuery UI - v1.9.2 - 2012-11-23
* http://jqueryui.com
* Includes: jquery.ui.accordion.css
* Copyright 2012 jQuery Foundation and other contributors; Licensed MIT */
.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;zoom:1}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto;zoom:1}

View File

@@ -0,0 +1,5 @@
/*! jQuery UI - v1.9.2 - 2012-11-23
* http://jqueryui.com
* Includes: jquery.ui.autocomplete.css
* Copyright 2012 jQuery Foundation and other contributors; Licensed MIT */
.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}* html .ui-autocomplete{width:1px}

View File

@@ -0,0 +1,5 @@
/*! jQuery UI - v1.9.2 - 2012-11-23
* http://jqueryui.com
* Includes: jquery.ui.button.css
* Copyright 2012 jQuery Foundation and other contributors; Licensed MIT */
.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;cursor:pointer;text-align:center;zoom:1;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:1.4}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}button.ui-button::-moz-focus-inner{border:0;padding:0}

View File

@@ -0,0 +1,5 @@
/*! jQuery UI - v1.9.2 - 2012-11-23
* http://jqueryui.com
* Includes: jquery.ui.core.css
* Copyright 2012 jQuery Foundation and other contributors; Licensed MIT */
.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{zoom:1}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:absolute;top:0;left:0;width:100%;height:100%}

Some files were not shown because too many files have changed in this diff Show More