Added new (clean) yii boilerplate
232
framework/web/CActiveDataProvider.php
Normal 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;
|
||||
}
|
||||
}
|
||||
193
framework/web/CArrayDataProvider.php
Normal 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;
|
||||
}
|
||||
}
|
||||
334
framework/web/CAssetManager.php
Normal 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);
|
||||
}
|
||||
}
|
||||
302
framework/web/CBaseController.php
Normal 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');
|
||||
}
|
||||
}
|
||||
112
framework/web/CCacheHttpSession.php
Normal 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;
|
||||
}
|
||||
}
|
||||
841
framework/web/CClientScript.php
Normal 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;
|
||||
}
|
||||
}
|
||||
1231
framework/web/CController.php
Normal file
238
framework/web/CDataProvider.php
Normal 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;
|
||||
}
|
||||
}
|
||||
155
framework/web/CDataProviderIterator.php
Normal 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;
|
||||
}
|
||||
}
|
||||
298
framework/web/CDbHttpSession.php
Normal 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;
|
||||
}
|
||||
}
|
||||
53
framework/web/CExtController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
78
framework/web/CFormModel.php
Normal 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];
|
||||
}
|
||||
}
|
||||
94
framework/web/CHttpCookie.php
Normal 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;
|
||||
}
|
||||
}
|
||||
1396
framework/web/CHttpRequest.php
Normal file
572
framework/web/CHttpSession.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
91
framework/web/CHttpSessionIterator.php
Normal 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;
|
||||
}
|
||||
}
|
||||
37
framework/web/COutputEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
240
framework/web/CPagination.php
Normal 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
@@ -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);
|
||||
}
|
||||
}
|
||||
142
framework/web/CSqlDataProvider.php
Normal 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
@@ -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);
|
||||
}
|
||||
}
|
||||
130
framework/web/CThemeManager.php
Normal 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,'/');
|
||||
}
|
||||
}
|
||||
275
framework/web/CUploadedFile.php
Normal 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 '';
|
||||
}
|
||||
}
|
||||
850
framework/web/CUrlManager.php
Normal 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 '<ParamName:RegExp>'.
|
||||
*
|
||||
* 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 <ParamName:RegExp> 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;
|
||||
}
|
||||
}
|
||||
547
framework/web/CWebApplication.php
Normal 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();
|
||||
}
|
||||
}
|
||||
202
framework/web/CWebModule.php
Normal 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);
|
||||
}
|
||||
}
|
||||
197
framework/web/CWidgetFactory.php
Normal 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'=>'>>',
|
||||
* 'prevPageLabel'=>'<<',
|
||||
* ),
|
||||
* '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];
|
||||
}
|
||||
}
|
||||
111
framework/web/actions/CAction.php
Normal 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;
|
||||
}
|
||||
}
|
||||
52
framework/web/actions/CInlineAction.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
167
framework/web/actions/CViewAction.php
Normal 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);
|
||||
}
|
||||
}
|
||||
394
framework/web/auth/CAccessControlFilter.php
Normal 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));
|
||||
}
|
||||
}
|
||||
106
framework/web/auth/CAuthAssignment.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
277
framework/web/auth/CAuthItem.php
Normal 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);
|
||||
}
|
||||
}
|
||||
165
framework/web/auth/CAuthManager.php
Normal 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])));
|
||||
}
|
||||
}
|
||||
131
framework/web/auth/CBaseUserIdentity.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
603
framework/web/auth/CDbAuthManager.php
Normal 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;
|
||||
}
|
||||
}
|
||||
506
framework/web/auth/CPhpAuthManager.php
Normal 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");
|
||||
}
|
||||
}
|
||||
81
framework/web/auth/CUserIdentity.php
Normal 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;
|
||||
}
|
||||
}
|
||||
821
framework/web/auth/CWebUser.php
Normal 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;
|
||||
}
|
||||
}
|
||||
42
framework/web/auth/schema-mssql.sql
Normal 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
|
||||
);
|
||||
42
framework/web/auth/schema-mysql.sql
Normal 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;
|
||||
42
framework/web/auth/schema-oci.sql
Normal 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
|
||||
);
|
||||
42
framework/web/auth/schema-pgsql.sql
Normal 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
|
||||
);
|
||||
42
framework/web/auth/schema-sqlite.sql
Normal 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
|
||||
);
|
||||
74
framework/web/filters/CFilter.php
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
135
framework/web/filters/CFilterChain.php
Normal 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);
|
||||
}
|
||||
}
|
||||
209
framework/web/filters/CHttpCacheFilter.php
Normal 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)).'"';
|
||||
}
|
||||
}
|
||||
60
framework/web/filters/CInlineFilter.php
Normal 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);
|
||||
}
|
||||
}
|
||||
635
framework/web/form/CForm.php
Normal 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);
|
||||
}
|
||||
}
|
||||
138
framework/web/form/CFormButtonElement.php
Normal 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);
|
||||
}
|
||||
}
|
||||
167
framework/web/form/CFormElement.php
Normal 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;
|
||||
}
|
||||
}
|
||||
111
framework/web/form/CFormElementCollection.php
Normal 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);
|
||||
}
|
||||
}
|
||||
264
framework/web/form/CFormInputElement.php
Normal 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);
|
||||
}
|
||||
}
|
||||
70
framework/web/form/CFormStringElement.php
Normal 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);
|
||||
}
|
||||
}
|
||||
74
framework/web/helpers/CGoogleApi.php
Normal 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);
|
||||
}
|
||||
}
|
||||
2615
framework/web/helpers/CHtml.php
Normal file
709
framework/web/helpers/CJSON.php
Normal 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);
|
||||
}
|
||||
}
|
||||
127
framework/web/helpers/CJavaScript.php
Normal 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);
|
||||
}
|
||||
}
|
||||
47
framework/web/helpers/CJavaScriptExpression.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* CJavaScriptExpression class file.
|
||||
*
|
||||
* @author Alexander Makarov <sam@rmcreative.ru>
|
||||
* @link http://www.yiiframework.com/
|
||||
* @copyright Copyright © 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;
|
||||
}
|
||||
}
|
||||
81
framework/web/js/packages.php
Normal 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'),
|
||||
),
|
||||
);
|
||||
BIN
framework/web/js/source/autocomplete/indicator.gif
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
48
framework/web/js/source/autocomplete/jquery.autocomplete.css
Normal 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;
|
||||
}
|
||||
116
framework/web/js/source/jquery.ajaxqueue.js
Normal 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);
|
||||
813
framework/web/js/source/jquery.autocomplete.js
Normal 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);
|
||||
1378
framework/web/js/source/jquery.ba-bbq.js
Normal file
18
framework/web/js/source/jquery.ba-bbq.min.js
vendored
Normal 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);
|
||||
39
framework/web/js/source/jquery.bgiframe.js
Normal 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);
|
||||
92
framework/web/js/source/jquery.cookie.js
Normal 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;
|
||||
}
|
||||
};
|
||||
1
framework/web/js/source/jquery.history.js
Normal file
9472
framework/web/js/source/jquery.js
vendored
Normal file
338
framework/web/js/source/jquery.maskedinput.js
Normal 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);
|
||||
7
framework/web/js/source/jquery.maskedinput.min.js
vendored
Normal 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);
|
||||
148
framework/web/js/source/jquery.metadata.js
Normal 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
536
framework/web/js/source/jquery.multifile.js
Normal 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 #*/
|
||||
376
framework/web/js/source/jquery.rating.js
Normal 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 #*/
|
||||
110
framework/web/js/source/jquery.treeview.async.js
Normal 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: " ",
|
||||
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: " ",
|
||||
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);
|
||||
37
framework/web/js/source/jquery.treeview.edit.js
Normal 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);
|
||||
256
framework/web/js/source/jquery.treeview.js
Normal 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);
|
||||
52
framework/web/js/source/jquery.yii.js
Normal 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);
|
||||
438
framework/web/js/source/jquery.yiiactiveform.js
Normal 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);
|
||||
49
framework/web/js/source/jquery.yiitab.js
Normal 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);
|
||||
|
After Width: | Height: | Size: 86 B |
|
After Width: | Height: | Size: 75 B |
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 99 B |
|
After Width: | Height: | Size: 142 B |
|
After Width: | Height: | Size: 137 B |
|
After Width: | Height: | Size: 140 B |
|
After Width: | Height: | Size: 86 B |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
10
framework/web/js/source/jui/css/base/jquery-ui.css
vendored
Normal file
5
framework/web/js/source/jui/css/base/jquery.ui.accordion.css
vendored
Normal 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}
|
||||
5
framework/web/js/source/jui/css/base/jquery.ui.autocomplete.css
vendored
Normal 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}
|
||||
5
framework/web/js/source/jui/css/base/jquery.ui.button.css
vendored
Normal 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}
|
||||
5
framework/web/js/source/jui/css/base/jquery.ui.core.css
vendored
Normal 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%}
|
||||