diff options
Diffstat (limited to 'framework/web')
160 files changed, 36715 insertions, 0 deletions
diff --git a/framework/web/CActiveDataProvider.php b/framework/web/CActiveDataProvider.php new file mode 100644 index 0000000..ef05376 --- /dev/null +++ b/framework/web/CActiveDataProvider.php @@ -0,0 +1,183 @@ +<?php +/** + * CActiveDataProvider class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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'), + * ), + * 'pagination'=>array( + * 'pageSize'=>20, + * ), + * )); + * // $dataProvider->getData() will return a list of Post objects + * </pre> + * + * @property CDbCriteria $criteria The query criteria. + * @property CSort $sort The sorting object. If this is false, it means the sorting is disabled. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CActiveDataProvider.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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; + + private $_criteria; + + /** + * 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=CActiveRecord::model($this->modelClass); + } + else if($modelClass instanceof CActiveRecord) + { + $this->modelClass=get_class($modelClass); + $this->model=$modelClass; + } + $this->setId($this->modelClass); + 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 mixed $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 sorting object. + * @return CSort the sorting object. If this is false, it means the sorting is disabled. + */ + public function getSort() + { + if(($sort=parent::getSort())!==false) + $sort->modelClass=$this->modelClass; + return $sort; + } + + /** + * 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->getCriteria()); + $this->model->setDbCriteria($baseCriteria); + return $count; + } +} diff --git a/framework/web/CArrayDataProvider.php b/framework/web/CArrayDataProvider.php new file mode 100644 index 0000000..7641f85 --- /dev/null +++ b/framework/web/CArrayDataProvider.php @@ -0,0 +1,162 @@ +<?php +/** + * CArrayDataProvider class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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). + * + * 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> + * @version $Id: CArrayDataProvider.php 3353 2011-07-12 21:10:36Z alexander.makarow $ + * @package system.web + * @since 1.1.4 + */ +class CArrayDataProvider extends CDataProvider +{ + /** + * @var string the name of key field. Defaults to 'id'. If it's set to false, + * keys of $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(); + + /** + * 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(); + foreach($this->rawData as $index=>$data) + $column[$index]=is_object($data) ? $data->$name : $data[$name]; + $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); + } + + /** + * 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; + } +} diff --git a/framework/web/CAssetManager.php b/framework/web/CAssetManager.php new file mode 100644 index 0000000..8245bca --- /dev/null +++ b/framework/web/CAssetManager.php @@ -0,0 +1,304 @@ +<?php +/** + * CAssetManager class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * 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> + * @version $Id: CAssetManager.php 3449 2011-11-20 20:42:45Z alexander.makarow $ + * @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> + * + * @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' files only. This option has no effect if {@link linkAssets} is enabled. + * @since 1.1.6 + **/ + public $excludeFiles=array('.svn'); + /** + * @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 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. + * 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. + * This parameter has been available since version 1.1.2. + * @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=false) + { + if(isset($this->_published[$path])) + return $this->_published[$path]; + else if(($src=realpath($path))!==false) + { + if(is_file($src)) + { + $dir=$this->hash($hashByName ? basename($src) : dirname($src).filemtime($src)); + $fileName=basename($src); + $dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir; + $dstFile=$dstDir.DIRECTORY_SEPARATOR.$fileName; + + if($this->linkAssets) + { + if(!is_file($dstFile)) + { + if(!is_dir($dstDir)) + { + mkdir($dstDir); + @chmod($dstDir, $this->newDirMode); + } + symlink($src,$dstFile); + } + } + else if(@filemtime($dstFile)<@filemtime($src)) + { + if(!is_dir($dstDir)) + { + mkdir($dstDir); + @chmod($dstDir, $this->newDirMode); + } + copy($src,$dstFile); + @chmod($dstFile, $this->newFileMode); + } + + return $this->_published[$path]=$this->getBaseUrl()."/$dir/$fileName"; + } + else if(is_dir($src)) + { + $dir=$this->hash($hashByName ? basename($src) : $src.filemtime($src)); + $dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir; + + if($this->linkAssets) + { + if(!is_dir($dstDir)) + symlink($src,$dstDir); + } + else if(!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; + if(is_file($path)) + return $base . $this->hash($hashByName ? basename($path) : dirname($path).filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); + else + return $base . $this->hash($hashByName ? basename($path) : $path.filemtime($path)); + } + 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) + { + if(is_file($path)) + return $this->getBaseUrl().'/'.$this->hash($hashByName ? basename($path) : dirname($path).filemtime($path)).'/'.basename($path); + else + return $this->getBaseUrl().'/'.$this->hash($hashByName ? basename($path) : $path.filemtime($path)); + } + 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())); + } +} diff --git a/framework/web/CBaseController.php b/framework/web/CBaseController.php new file mode 100644 index 0000000..8e782ff --- /dev/null +++ b/framework/web/CBaseController.php @@ -0,0 +1,303 @@ +<?php +/** + * CBaseController class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * 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> + * @version $Id: CBaseController.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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'); + } +} diff --git a/framework/web/CCacheHttpSession.php b/framework/web/CCacheHttpSession.php new file mode 100644 index 0000000..c7b686e --- /dev/null +++ b/framework/web/CCacheHttpSession.php @@ -0,0 +1,113 @@ +<?php +/** + * CCacheHttpSession class + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * 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> + * @version $Id: CCacheHttpSession.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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; + } +} diff --git a/framework/web/CClientScript.php b/framework/web/CClientScript.php new file mode 100644 index 0000000..a546c6f --- /dev/null +++ b/framework/web/CClientScript.php @@ -0,0 +1,756 @@ +<?php +/** + * CClientScript class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CClientScript.php 3559 2012-02-09 21:12:53Z alexander.makarow $ + * @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 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 JavaScript code blocks (position, key => code) + */ + protected $scripts=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; + + 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 $key=>$scriptFile) + { + if(isset($map[$scriptFile])) + unset($this->scriptFiles[self::POS_BEGIN][$key]); + else + $map[$scriptFile]=true; + } + } + + if(isset($this->scriptFiles[self::POS_END])) + { + foreach($this->scriptFiles[self::POS_END] as $key=>$scriptFile) + { + if(isset($map[$scriptFile])) + 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; + } + else if(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=>$scripts) + { + $jsFiles[$position]=array(); + foreach($scripts as $key=>$script) + { + $name=basename($script); + if(isset($this->scriptMap[$name])) + { + if($this->scriptMap[$name]!==false) + $jsFiles[$position][$this->scriptMap[$name]]=$this->scriptMap[$name]; + } + else if(isset($this->scriptMap['*.js'])) + { + if($this->scriptMap['*.js']!==false) + $jsFiles[$position][$this->scriptMap['*.js']]=$this->scriptMap['*.js']; + } + else + $jsFiles[$position][$key]=$script; + } + } + $this->scriptFiles=$jsFiles; + } + + /** + * 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) + $jsFiles[$url]=$url; + } + $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 $scriptFile) + $html.=CHtml::scriptFile($scriptFile)."\n"; + } + + if(isset($this->scripts[self::POS_HEAD])) + $html.=CHtml::script(implode("\n",$this->scripts[self::POS_HEAD]))."\n"; + } + + 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 $scriptFile) + $html.=CHtml::scriptFile($scriptFile)."\n"; + } + if(isset($this->scripts[self::POS_BEGIN])) + $html.=CHtml::script(implode("\n",$this->scripts[self::POS_BEGIN]))."\n"; + + 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 $scriptFile) + $html.=CHtml::scriptFile($scriptFile)."\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).load(function() {\n".implode("\n",$this->scripts[self::POS_LOAD])."\n});"; + else + $scripts[]=implode("\n",$this->scripts[self::POS_LOAD]); + } + if(!empty($scripts)) + $html.=CHtml::script(implode("\n",$scripts))."\n"; + + 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,'/'); + } + else if(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> + * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + */ + public function registerScriptFile($url,$position=self::POS_HEAD) + { + $this->hasScripts=true; + $this->scriptFiles[$position][$url]=$url; + $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> + * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5). + */ + public function registerScript($id,$script,$position=self::POS_READY) + { + $this->hasScripts=true; + $this->scripts[$position][$id]=$script; + 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> + * Meta tags with same attributes will be rendered more then once if called with different values. + * + * <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') + * @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()) + { + $this->hasScripts=true; + if($name!==null) + $options['name']=$name; + if($httpEquiv!==null) + $options['http-equiv']=$httpEquiv; + $options['content']=$content; + $this->metaTags[serialize($options)]=$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; + } +}
\ No newline at end of file diff --git a/framework/web/CController.php b/framework/web/CController.php new file mode 100644 index 0000000..0ac65bc --- /dev/null +++ b/framework/web/CController.php @@ -0,0 +1,1232 @@ +<?php +/** + * CController class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * CController manages a set of actions which deal with the corresponding user requests. + * + * Through the actions, CController coordinates the data flow between models and views. + * + * When a user requests an action 'XYZ', CController will do one of the following: + * 1. Method-based action: call method 'actionXYZ' if it exists; + * 2. Class-based action: create an instance of class 'XYZ' if the class is found in the action class map + * (specified via {@link actions()}, and execute the action; + * 3. Call {@link missingAction()}, which by default will raise a 404 HTTP exception. + * + * If the user does not specify an action, CController will run the action specified by + * {@link defaultAction}, instead. + * + * CController may be configured to execute filters before and after running actions. + * Filters preprocess/postprocess the user request/response and may quit executing actions + * if needed. They are executed in the order they are specified. If during the execution, + * any of the filters returns true, the rest filters and the action will no longer get executed. + * + * Filters can be individual objects, or methods defined in the controller class. + * They are specified by overriding {@link filters()} method. The following is an example + * of the filter specification: + * <pre> + * array( + * 'accessControl - login', + * 'ajaxOnly + search', + * array( + * 'COutputCache + list', + * 'duration'=>300, + * ), + * ) + * </pre> + * The above example declares three filters: accessControl, ajaxOnly, COutputCache. The first two + * are method-based filters (defined in CController), which refer to filtering methods in the controller class; + * while the last refers to a object-based filter whose class is 'system.web.widgets.COutputCache' and + * the 'duration' property is initialized as 300 (s). + * + * For method-based filters, a method named 'filterXYZ($filterChain)' in the controller class + * will be executed, where 'XYZ' stands for the filter name as specified in {@link filters()}. + * Note, inside the filter method, you must call <code>$filterChain->run()</code> if the action should + * be executed. Otherwise, the filtering process would stop at this filter. + * + * Filters can be specified so that they are executed only when running certain actions. + * For method-based filters, this is done by using '+' and '-' operators in the filter specification. + * The '+' operator means the filter runs only when the specified actions are requested; + * while the '-' operator means the filter runs only when the requested action is not among those actions. + * For object-based filters, the '+' and '-' operators are following the class name. + * + * @property array $actionParams The request parameters to be used for action parameter binding. + * @property CAction $action The action currently being executed, null if no active action. + * @property string $id ID of the controller. + * @property string $uniqueId The controller ID that is prefixed with the module ID (if any). + * @property string $route The route (module ID, controller ID and action ID) of the current request. + * @property CWebModule $module The module that this controller belongs to. It returns null + * if the controller does not belong to any module. + * @property string $viewPath The directory containing the view files for this controller. Defaults to 'protected/views/ControllerID'. + * @property CMap $clips The list of clips. + * @property string $pageTitle The page title. Defaults to the controller name and the action name. + * @property CStack $cachingStack Stack of {@link COutputCache} objects. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CController.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web + * @since 1.0 + */ +class CController extends CBaseController +{ + /** + * Name of the hidden field storing persistent page states. + */ + const STATE_INPUT_NAME='YII_PAGE_STATE'; + + /** + * @var mixed the name of the layout to be applied to this controller's views. + * Defaults to null, meaning the {@link CWebApplication::layout application layout} + * is used. If it is false, no layout will be applied. + * The {@link CWebModule::layout module layout} will be used + * if the controller belongs to a module and this layout property is null. + */ + public $layout; + /** + * @var string the name of the default action. Defaults to 'index'. + */ + public $defaultAction='index'; + + private $_id; + private $_action; + private $_pageTitle; + private $_cachingStack; + private $_clips; + private $_dynamicOutput; + private $_pageStates; + private $_module; + + + /** + * @param string $id id of this controller + * @param CWebModule $module the module that this controller belongs to. + */ + public function __construct($id,$module=null) + { + $this->_id=$id; + $this->_module=$module; + $this->attachBehaviors($this->behaviors()); + } + + /** + * Initializes the controller. + * This method is called by the application before the controller starts to execute. + * You may override this method to perform the needed initialization for the controller. + */ + public function init() + { + } + + /** + * Returns the filter configurations. + * + * By overriding this method, child classes can specify filters to be applied to actions. + * + * This method returns an array of filter specifications. Each array element specify a single filter. + * + * For a method-based filter (called inline filter), it is specified as 'FilterName[ +|- Action1, Action2, ...]', + * where the '+' ('-') operators describe which actions should be (should not be) applied with the filter. + * + * For a class-based filter, it is specified as an array like the following: + * <pre> + * array( + * 'FilterClass[ +|- Action1, Action2, ...]', + * 'name1'=>'value1', + * 'name2'=>'value2', + * ... + * ) + * </pre> + * where the name-value pairs will be used to initialize the properties of the filter. + * + * Note, in order to inherit filters defined in the parent class, a child class needs to + * merge the parent filters with child filters using functions like array_merge(). + * + * @return array a list of filter configurations. + * @see CFilter + */ + public function filters() + { + return array(); + } + + /** + * Returns a list of external action classes. + * Array keys are action IDs, and array values are the corresponding + * action class in dot syntax (e.g. 'edit'=>'application.controllers.article.EditArticle') + * or arrays representing the configuration of the actions, such as the following, + * <pre> + * return array( + * 'action1'=>'path.to.Action1Class', + * 'action2'=>array( + * 'class'=>'path.to.Action2Class', + * 'property1'=>'value1', + * 'property2'=>'value2', + * ), + * ); + * </pre> + * Derived classes may override this method to declare external actions. + * + * Note, in order to inherit actions defined in the parent class, a child class needs to + * merge the parent actions with child actions using functions like array_merge(). + * + * You may import actions from an action provider + * (such as a widget, see {@link CWidget::actions}), like the following: + * <pre> + * return array( + * ...other actions... + * // import actions declared in ProviderClass::actions() + * // the action IDs will be prefixed with 'pro.' + * 'pro.'=>'path.to.ProviderClass', + * // similar as above except that the imported actions are + * // configured with the specified initial property values + * 'pro2.'=>array( + * 'class'=>'path.to.ProviderClass', + * 'action1'=>array( + * 'property1'=>'value1', + * ), + * 'action2'=>array( + * 'property2'=>'value2', + * ), + * ), + * ) + * </pre> + * + * In the above, we differentiate action providers from other action + * declarations by the array keys. For action providers, the array keys + * must contain a dot. As a result, an action ID 'pro2.action1' will + * be resolved as the 'action1' action declared in the 'ProviderClass'. + * + * @return array list of external action classes + * @see createAction + */ + public function actions() + { + return array(); + } + + /** + * Returns a list of behaviors that this controller should behave as. + * The return value should be an array of behavior configurations indexed by + * behavior names. Each behavior configuration can be either a string specifying + * the behavior class or an array of the following structure: + * <pre> + * 'behaviorName'=>array( + * 'class'=>'path.to.BehaviorClass', + * 'property1'=>'value1', + * 'property2'=>'value2', + * ) + * </pre> + * + * Note, the behavior classes must implement {@link IBehavior} or extend from + * {@link CBehavior}. Behaviors declared in this method will be attached + * to the controller when it is instantiated. + * + * For more details about behaviors, see {@link CComponent}. + * @return array the behavior configurations (behavior name=>behavior configuration) + */ + public function behaviors() + { + return array(); + } + + /** + * Returns the access rules for this controller. + * Override this method if you use the {@link filterAccessControl accessControl} filter. + * @return array list of access rules. See {@link CAccessControlFilter} for details about rule specification. + */ + public function accessRules() + { + return array(); + } + + /** + * Runs the named action. + * Filters specified via {@link filters()} will be applied. + * @param string $actionID action ID + * @throws CHttpException if the action does not exist or the action name is not proper. + * @see filters + * @see createAction + * @see runAction + */ + public function run($actionID) + { + if(($action=$this->createAction($actionID))!==null) + { + if(($parent=$this->getModule())===null) + $parent=Yii::app(); + if($parent->beforeControllerAction($this,$action)) + { + $this->runActionWithFilters($action,$this->filters()); + $parent->afterControllerAction($this,$action); + } + } + else + $this->missingAction($actionID); + } + + /** + * Runs an action with the specified filters. + * A filter chain will be created based on the specified filters + * and the action will be executed then. + * @param CAction $action the action to be executed. + * @param array $filters list of filters to be applied to the action. + * @see filters + * @see createAction + * @see runAction + */ + public function runActionWithFilters($action,$filters) + { + if(empty($filters)) + $this->runAction($action); + else + { + $priorAction=$this->_action; + $this->_action=$action; + CFilterChain::create($this,$action,$filters)->run(); + $this->_action=$priorAction; + } + } + + /** + * Runs the action after passing through all filters. + * This method is invoked by {@link runActionWithFilters} after all possible filters have been executed + * and the action starts to run. + * @param CAction $action action to run + */ + public function runAction($action) + { + $priorAction=$this->_action; + $this->_action=$action; + if($this->beforeAction($action)) + { + if($action->runWithParams($this->getActionParams())===false) + $this->invalidActionParams($action); + else + $this->afterAction($action); + } + $this->_action=$priorAction; + } + + /** + * Returns the request parameters that will be used for action parameter binding. + * By default, this method will return $_GET. You may override this method if you + * want to use other request parameters (e.g. $_GET+$_POST). + * @return array the request parameters to be used for action parameter binding + * @since 1.1.7 + */ + public function getActionParams() + { + return $_GET; + } + + /** + * This method is invoked when the request parameters do not satisfy the requirement of the specified action. + * The default implementation will throw a 400 HTTP exception. + * @param CAction $action the action being executed + * @since 1.1.7 + */ + public function invalidActionParams($action) + { + throw new CHttpException(400,Yii::t('yii','Your request is invalid.')); + } + + /** + * Postprocesses the output generated by {@link render()}. + * This method is invoked at the end of {@link render()} and {@link renderText()}. + * If there are registered client scripts, this method will insert them into the output + * at appropriate places. If there are dynamic contents, they will also be inserted. + * This method may also save the persistent page states in hidden fields of + * stateful forms in the page. + * @param string $output the output generated by the current action + * @return string the output that has been processed. + */ + public function processOutput($output) + { + Yii::app()->getClientScript()->render($output); + + // if using page caching, we should delay dynamic output replacement + if($this->_dynamicOutput!==null && $this->isCachingStackEmpty()) + { + $output=$this->processDynamicOutput($output); + $this->_dynamicOutput=null; + } + + if($this->_pageStates===null) + $this->_pageStates=$this->loadPageStates(); + if(!empty($this->_pageStates)) + $this->savePageStates($this->_pageStates,$output); + + return $output; + } + + /** + * Postprocesses the dynamic output. + * This method is internally used. Do not call this method directly. + * @param string $output output to be processed + * @return string the processed output + */ + public function processDynamicOutput($output) + { + if($this->_dynamicOutput) + { + $output=preg_replace_callback('/<###dynamic-(\d+)###>/',array($this,'replaceDynamicOutput'),$output); + } + return $output; + } + + /** + * Replaces the dynamic content placeholders with actual content. + * This is a callback function used internally. + * @param array $matches matches + * @return string the replacement + * @see processOutput + */ + protected function replaceDynamicOutput($matches) + { + $content=$matches[0]; + if(isset($this->_dynamicOutput[$matches[1]])) + { + $content=$this->_dynamicOutput[$matches[1]]; + $this->_dynamicOutput[$matches[1]]=null; + } + return $content; + } + + /** + * Creates the action instance based on the action name. + * The action can be either an inline action or an object. + * The latter is created by looking up the action map specified in {@link actions}. + * @param string $actionID ID of the action. If empty, the {@link defaultAction default action} will be used. + * @return CAction the action instance, null if the action does not exist. + * @see actions + */ + public function createAction($actionID) + { + if($actionID==='') + $actionID=$this->defaultAction; + if(method_exists($this,'action'.$actionID) && strcasecmp($actionID,'s')) // we have actions method + return new CInlineAction($this,$actionID); + else + { + $action=$this->createActionFromMap($this->actions(),$actionID,$actionID); + if($action!==null && !method_exists($action,'run')) + throw new CException(Yii::t('yii', 'Action class {class} must implement the "run" method.', array('{class}'=>get_class($action)))); + return $action; + } + } + + /** + * Creates the action instance based on the action map. + * This method will check to see if the action ID appears in the given + * action map. If so, the corresponding configuration will be used to + * create the action instance. + * @param array $actionMap the action map + * @param string $actionID the action ID that has its prefix stripped off + * @param string $requestActionID the originally requested action ID + * @param array $config the action configuration that should be applied on top of the configuration specified in the map + * @return CAction the action instance, null if the action does not exist. + */ + protected function createActionFromMap($actionMap,$actionID,$requestActionID,$config=array()) + { + if(($pos=strpos($actionID,'.'))===false && isset($actionMap[$actionID])) + { + $baseConfig=is_array($actionMap[$actionID]) ? $actionMap[$actionID] : array('class'=>$actionMap[$actionID]); + return Yii::createComponent(empty($config)?$baseConfig:array_merge($baseConfig,$config),$this,$requestActionID); + } + else if($pos===false) + return null; + + // the action is defined in a provider + $prefix=substr($actionID,0,$pos+1); + if(!isset($actionMap[$prefix])) + return null; + $actionID=(string)substr($actionID,$pos+1); + + $provider=$actionMap[$prefix]; + if(is_string($provider)) + $providerType=$provider; + else if(is_array($provider) && isset($provider['class'])) + { + $providerType=$provider['class']; + if(isset($provider[$actionID])) + { + if(is_string($provider[$actionID])) + $config=array_merge(array('class'=>$provider[$actionID]),$config); + else + $config=array_merge($provider[$actionID],$config); + } + } + else + throw new CException(Yii::t('yii','Object configuration must be an array containing a "class" element.')); + + $class=Yii::import($providerType,true); + $map=call_user_func(array($class,'actions')); + + return $this->createActionFromMap($map,$actionID,$requestActionID,$config); + } + + /** + * Handles the request whose action is not recognized. + * This method is invoked when the controller cannot find the requested action. + * The default implementation simply throws an exception. + * @param string $actionID the missing action name + * @throws CHttpException whenever this method is invoked + */ + public function missingAction($actionID) + { + throw new CHttpException(404,Yii::t('yii','The system is unable to find the requested action "{action}".', + array('{action}'=>$actionID==''?$this->defaultAction:$actionID))); + } + + /** + * @return CAction the action currently being executed, null if no active action. + */ + public function getAction() + { + return $this->_action; + } + + /** + * @param CAction $value the action currently being executed. + */ + public function setAction($value) + { + $this->_action=$value; + } + + /** + * @return string ID of the controller + */ + public function getId() + { + return $this->_id; + } + + /** + * @return string the controller ID that is prefixed with the module ID (if any). + */ + public function getUniqueId() + { + return $this->_module ? $this->_module->getId().'/'.$this->_id : $this->_id; + } + + /** + * @return string the route (module ID, controller ID and action ID) of the current request. + * @since 1.1.0 + */ + public function getRoute() + { + if(($action=$this->getAction())!==null) + return $this->getUniqueId().'/'.$action->getId(); + else + return $this->getUniqueId(); + } + + /** + * @return CWebModule the module that this controller belongs to. It returns null + * if the controller does not belong to any module + */ + public function getModule() + { + return $this->_module; + } + + /** + * Returns the directory containing view files for this controller. + * The default implementation returns 'protected/views/ControllerID'. + * Child classes may override this method to use customized view path. + * If the controller belongs to a module, the default view path + * is the {@link CWebModule::getViewPath module view path} appended with the controller ID. + * @return string the directory containing the view files for this controller. Defaults to 'protected/views/ControllerID'. + */ + public function getViewPath() + { + if(($module=$this->getModule())===null) + $module=Yii::app(); + return $module->getViewPath().DIRECTORY_SEPARATOR.$this->getId(); + } + + /** + * Looks for the view file according to the given view name. + * + * When a theme is currently active, this method will call {@link CTheme::getViewFile} to determine + * which view file should be returned. + * + * Otherwise, this method will return the corresponding view file based on the following criteria: + * <ul> + * <li>absolute view within a module: the view name starts with a single slash '/'. + * In this case, the view will be searched for under the currently active module's view path. + * If there is no active module, the view will be searched for under the application's view path.</li> + * <li>absolute view within the application: the view name starts with double slashes '//'. + * In this case, the view will be searched for under the application's view path. + * This syntax has been available since version 1.1.3.</li> + * <li>aliased view: the view name contains dots and refers to a path alias. + * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views + * cannot be themed because they can refer to a view file located at arbitrary places.</li> + * <li>relative view: otherwise. Relative views will be searched for under the currently active + * controller's view path.</li> + * </ul> + * + * After the view file is identified, this method may further call {@link CApplication::findLocalizedFile} + * to find its localized version if internationalization is needed. + * + * @param string $viewName view name + * @return string the view file path, false if the view file does not exist + * @see resolveViewFile + * @see CApplication::findLocalizedFile + */ + public function getViewFile($viewName) + { + if(($theme=Yii::app()->getTheme())!==null && ($viewFile=$theme->getViewFile($this,$viewName))!==false) + return $viewFile; + $moduleViewPath=$basePath=Yii::app()->getViewPath(); + if(($module=$this->getModule())!==null) + $moduleViewPath=$module->getViewPath(); + return $this->resolveViewFile($viewName,$this->getViewPath(),$basePath,$moduleViewPath); + } + + /** + * Looks for the layout view script based on the layout name. + * + * The layout name can be specified in one of the following ways: + * + * <ul> + * <li>layout is false: returns false, meaning no layout.</li> + * <li>layout is null: the currently active module's layout will be used. If there is no active module, + * the application's layout will be used.</li> + * <li>a regular view name.</li> + * </ul> + * + * The resolution of the view file based on the layout view is similar to that in {@link getViewFile}. + * In particular, the following rules are followed: + * + * Otherwise, this method will return the corresponding view file based on the following criteria: + * <ul> + * <li>When a theme is currently active, this method will call {@link CTheme::getLayoutFile} to determine + * which view file should be returned.</li> + * <li>absolute view within a module: the view name starts with a single slash '/'. + * In this case, the view will be searched for under the currently active module's view path. + * If there is no active module, the view will be searched for under the application's view path.</li> + * <li>absolute view within the application: the view name starts with double slashes '//'. + * In this case, the view will be searched for under the application's view path. + * This syntax has been available since version 1.1.3.</li> + * <li>aliased view: the view name contains dots and refers to a path alias. + * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views + * cannot be themed because they can refer to a view file located at arbitrary places.</li> + * <li>relative view: otherwise. Relative views will be searched for under the currently active + * module's layout path. In case when there is no active module, the view will be searched for + * under the application's layout path.</li> + * </ul> + * + * After the view file is identified, this method may further call {@link CApplication::findLocalizedFile} + * to find its localized version if internationalization is needed. + * + * @param mixed $layoutName layout name + * @return string the view file for the layout. False if the view file cannot be found + */ + public function getLayoutFile($layoutName) + { + if($layoutName===false) + return false; + if(($theme=Yii::app()->getTheme())!==null && ($layoutFile=$theme->getLayoutFile($this,$layoutName))!==false) + return $layoutFile; + + if(empty($layoutName)) + { + $module=$this->getModule(); + while($module!==null) + { + if($module->layout===false) + return false; + if(!empty($module->layout)) + break; + $module=$module->getParentModule(); + } + if($module===null) + $module=Yii::app(); + $layoutName=$module->layout; + } + else if(($module=$this->getModule())===null) + $module=Yii::app(); + + return $this->resolveViewFile($layoutName,$module->getLayoutPath(),Yii::app()->getViewPath(),$module->getViewPath()); + } + + /** + * Finds a view file based on its name. + * The view name can be in one of the following formats: + * <ul> + * <li>absolute view within a module: the view name starts with a single slash '/'. + * In this case, the view will be searched for under the currently active module's view path. + * If there is no active module, the view will be searched for under the application's view path.</li> + * <li>absolute view within the application: the view name starts with double slashes '//'. + * In this case, the view will be searched for under the application's view path. + * This syntax has been available since version 1.1.3.</li> + * <li>aliased view: the view name contains dots and refers to a path alias. + * The view file is determined by calling {@link YiiBase::getPathOfAlias()}. Note that aliased views + * cannot be themed because they can refer to a view file located at arbitrary places.</li> + * <li>relative view: otherwise. Relative views will be searched for under the currently active + * controller's view path.</li> + * </ul> + * For absolute view and relative view, the corresponding view file is a PHP file + * whose name is the same as the view name. The file is located under a specified directory. + * This method will call {@link CApplication::findLocalizedFile} to search for a localized file, if any. + * @param string $viewName the view name + * @param string $viewPath the directory that is used to search for a relative view name + * @param string $basePath the directory that is used to search for an absolute view name under the application + * @param string $moduleViewPath the directory that is used to search for an absolute view name under the current module. + * If this is not set, the application base view path will be used. + * @return mixed the view file path. False if the view file does not exist. + */ + public function resolveViewFile($viewName,$viewPath,$basePath,$moduleViewPath=null) + { + if(empty($viewName)) + return false; + + if($moduleViewPath===null) + $moduleViewPath=$basePath; + + if(($renderer=Yii::app()->getViewRenderer())!==null) + $extension=$renderer->fileExtension; + else + $extension='.php'; + if($viewName[0]==='/') + { + if(strncmp($viewName,'//',2)===0) + $viewFile=$basePath.$viewName; + else + $viewFile=$moduleViewPath.$viewName; + } + else if(strpos($viewName,'.')) + $viewFile=Yii::getPathOfAlias($viewName); + else + $viewFile=$viewPath.DIRECTORY_SEPARATOR.$viewName; + + if(is_file($viewFile.$extension)) + return Yii::app()->findLocalizedFile($viewFile.$extension); + else if($extension!=='.php' && is_file($viewFile.'.php')) + return Yii::app()->findLocalizedFile($viewFile.'.php'); + else + return false; + } + + /** + * Returns the list of clips. + * A clip is a named piece of rendering result that can be + * inserted at different places. + * @return CMap the list of clips + * @see CClipWidget + */ + public function getClips() + { + if($this->_clips!==null) + return $this->_clips; + else + return $this->_clips=new CMap; + } + + /** + * Processes the request using another controller action. + * This is like {@link redirect}, but the user browser's URL remains unchanged. + * In most cases, you should call {@link redirect} instead of this method. + * @param string $route the route of the new controller action. This can be an action ID, or a complete route + * with module ID (optional in the current module), controller ID and action ID. If the former, the action is assumed + * to be located within the current controller. + * @param boolean $exit whether to end the application after this call. Defaults to true. + * @since 1.1.0 + */ + public function forward($route,$exit=true) + { + if(strpos($route,'/')===false) + $this->run($route); + else + { + if($route[0]!=='/' && ($module=$this->getModule())!==null) + $route=$module->getId().'/'.$route; + Yii::app()->runController($route); + } + if($exit) + Yii::app()->end(); + } + + /** + * Renders a view with a layout. + * + * This method first calls {@link renderPartial} to render the view (called content view). + * It then renders the layout view which may embed the content view at appropriate place. + * In the layout view, the content view rendering result can be accessed via variable + * <code>$content</code>. At the end, it calls {@link processOutput} to insert scripts + * and dynamic contents if they are available. + * + * By default, the layout view script is "protected/views/layouts/main.php". + * This may be customized by changing {@link layout}. + * + * @param string $view name of the view to be rendered. See {@link getViewFile} for details + * about how the view script is resolved. + * @param array $data data to be extracted into PHP variables and made available to the view script + * @param boolean $return whether the rendering result should be returned instead of being displayed to end users. + * @return string the rendering result. Null if the rendering result is not required. + * @see renderPartial + * @see getLayoutFile + */ + public function render($view,$data=null,$return=false) + { + if($this->beforeRender($view)) + { + $output=$this->renderPartial($view,$data,true); + if(($layoutFile=$this->getLayoutFile($this->layout))!==false) + $output=$this->renderFile($layoutFile,array('content'=>$output),true); + + $this->afterRender($view,$output); + + $output=$this->processOutput($output); + + if($return) + return $output; + else + echo $output; + } + } + + /** + * This method is invoked at the beginning of {@link render()}. + * You may override this method to do some preprocessing when rendering a view. + * @param string $view the view to be rendered + * @return boolean whether the view should be rendered. + * @since 1.1.5 + */ + protected function beforeRender($view) + { + return true; + } + + /** + * This method is invoked after the specified is rendered by calling {@link render()}. + * Note that this method is invoked BEFORE {@link processOutput()}. + * You may override this method to do some postprocessing for the view rendering. + * @param string $view the view that has been rendered + * @param string $output the rendering result of the view. Note that this parameter is passed + * as a reference. That means you can modify it within this method. + * @since 1.1.5 + */ + protected function afterRender($view, &$output) + { + } + + /** + * Renders a static text string. + * The string will be inserted in the current controller layout and returned back. + * @param string $text the static text string + * @param boolean $return whether the rendering result should be returned instead of being displayed to end users. + * @return string the rendering result. Null if the rendering result is not required. + * @see getLayoutFile + */ + public function renderText($text,$return=false) + { + if(($layoutFile=$this->getLayoutFile($this->layout))!==false) + $text=$this->renderFile($layoutFile,array('content'=>$text),true); + + $text=$this->processOutput($text); + + if($return) + return $text; + else + echo $text; + } + + /** + * Renders a view. + * + * The named view refers to a PHP script (resolved via {@link getViewFile}) + * that is included by this method. If $data is an associative array, + * it will be extracted as PHP variables and made available to the script. + * + * This method differs from {@link render()} in that it does not + * apply a layout to the rendered result. It is thus mostly used + * in rendering a partial view, or an AJAX response. + * + * @param string $view name of the view to be rendered. See {@link getViewFile} for details + * about how the view script is resolved. + * @param array $data data to be extracted into PHP variables and made available to the view script + * @param boolean $return whether the rendering result should be returned instead of being displayed to end users + * @param boolean $processOutput whether the rendering result should be postprocessed using {@link processOutput}. + * @return string the rendering result. Null if the rendering result is not required. + * @throws CException if the view does not exist + * @see getViewFile + * @see processOutput + * @see render + */ + public function renderPartial($view,$data=null,$return=false,$processOutput=false) + { + if(($viewFile=$this->getViewFile($view))!==false) + { + $output=$this->renderFile($viewFile,$data,true); + if($processOutput) + $output=$this->processOutput($output); + if($return) + return $output; + else + echo $output; + } + else + throw new CException(Yii::t('yii','{controller} cannot find the requested view "{view}".', + array('{controller}'=>get_class($this), '{view}'=>$view))); + } + + /** + * Renders a named clip with the supplied parameters. + * This is similar to directly accessing the {@link clips} property. + * The main difference is that it can take an array of named parameters + * which will replace the corresponding placeholders in the clip. + * @param string $name the name of the clip + * @param array $params an array of named parameters (name=>value) that should replace + * their corresponding placeholders in the clip + * @param boolean $return whether to return the clip content or echo it. + * @return mixed either the clip content or null + * @since 1.1.8 + */ + public function renderClip($name,$params=array(),$return=false) + { + $text=isset($this->clips[$name]) ? strtr($this->clips[$name], $params) : ''; + + if($return) + return $text; + else + echo $text; + } + + /** + * Renders dynamic content returned by the specified callback. + * This method is used together with {@link COutputCache}. Dynamic contents + * will always show as their latest state even if the content surrounding them is being cached. + * This is especially useful when caching pages that are mostly static but contain some small + * dynamic regions, such as username or current time. + * We can use this method to render these dynamic regions to ensure they are always up-to-date. + * + * The first parameter to this method should be a valid PHP callback, while the rest parameters + * will be passed to the callback. + * + * Note, the callback and its parameter values will be serialized and saved in cache. + * Make sure they are serializable. + * + * @param callback $callback a PHP callback which returns the needed dynamic content. + * When the callback is specified as a string, it will be first assumed to be a method of the current + * controller class. If the method does not exist, it is assumed to be a global PHP function. + * Note, the callback should return the dynamic content instead of echoing it. + */ + public function renderDynamic($callback) + { + $n=count($this->_dynamicOutput); + echo "<###dynamic-$n###>"; + $params=func_get_args(); + array_shift($params); + $this->renderDynamicInternal($callback,$params); + } + + /** + * This method is internally used. + * @param callback $callback a PHP callback which returns the needed dynamic content. + * @param array $params parameters passed to the PHP callback + * @see renderDynamic + */ + public function renderDynamicInternal($callback,$params) + { + $this->recordCachingAction('','renderDynamicInternal',array($callback,$params)); + if(is_string($callback) && method_exists($this,$callback)) + $callback=array($this,$callback); + $this->_dynamicOutput[]=call_user_func_array($callback,$params); + } + + /** + * Creates a relative URL for the specified action defined in this controller. + * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'. + * If the ControllerID is not present, the current controller ID will be prefixed to the route. + * If the route is empty, it is assumed to be the current action. + * If the controller belongs to a module, the {@link CWebModule::getId module ID} + * will be prefixed to the route. (If you do not want the module ID prefix, the route should start with a slash '/'.) + * @param array $params additional 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. + * @return string the constructed URL + */ + public function createUrl($route,$params=array(),$ampersand='&') + { + if($route==='') + $route=$this->getId().'/'.$this->getAction()->getId(); + else if(strpos($route,'/')===false) + $route=$this->getId().'/'.$route; + if($route[0]!=='/' && ($module=$this->getModule())!==null) + $route=$module->getId().'/'.$route; + return Yii::app()->createUrl(trim($route,'/'),$params,$ampersand); + } + + /** + * Creates an absolute URL for the specified action defined in this controller. + * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'. + * If the ControllerPath is not present, the current controller ID will be prefixed to the route. + * If the route is empty, it is assumed to be the current action. + * @param array $params additional GET parameters (name=>value). Both the name and value will be URL-encoded. + * @param string $schema schema to use (e.g. http, https). If empty, the schema used for the current request will be used. + * @param string $ampersand the token separating name-value pairs in the URL. + * @return string the constructed URL + */ + public function createAbsoluteUrl($route,$params=array(),$schema='',$ampersand='&') + { + $url=$this->createUrl($route,$params,$ampersand); + if(strpos($url,'http')===0) + return $url; + else + return Yii::app()->getRequest()->getHostInfo($schema).$url; + } + + /** + * @return string the page title. Defaults to the controller name and the action name. + */ + public function getPageTitle() + { + if($this->_pageTitle!==null) + return $this->_pageTitle; + else + { + $name=ucfirst(basename($this->getId())); + if($this->getAction()!==null && strcasecmp($this->getAction()->getId(),$this->defaultAction)) + return $this->_pageTitle=Yii::app()->name.' - '.ucfirst($this->getAction()->getId()).' '.$name; + else + return $this->_pageTitle=Yii::app()->name.' - '.$name; + } + } + + /** + * @param string $value the page title. + */ + public function setPageTitle($value) + { + $this->_pageTitle=$value; + } + + /** + * Redirects the browser to the specified URL or route (controller/action). + * @param mixed $url the URL to be redirected to. If the parameter is an array, + * the first element must be a route to a controller action and the rest + * are GET parameters in name-value pairs. + * @param boolean $terminate whether to terminate the current application after calling this method + * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html} + * for details about HTTP status code. + */ + public function redirect($url,$terminate=true,$statusCode=302) + { + if(is_array($url)) + { + $route=isset($url[0]) ? $url[0] : ''; + $url=$this->createUrl($route,array_splice($url,1)); + } + Yii::app()->getRequest()->redirect($url,$terminate,$statusCode); + } + + /** + * Refreshes the current page. + * The effect of this method call is the same as user pressing the + * refresh button on the browser (without post data). + * @param boolean $terminate whether to terminate the current application after calling this method + * @param string $anchor the anchor that should be appended to the redirection URL. + * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it. + */ + public function refresh($terminate=true,$anchor='') + { + $this->redirect(Yii::app()->getRequest()->getUrl().$anchor,$terminate); + } + + /** + * Records a method call when an output cache is in effect. + * When the content is served from the output cache, the recorded + * method will be re-invoked. + * @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 + */ + public function recordCachingAction($context,$method,$params) + { + if($this->_cachingStack) // record only when there is an active output cache + { + foreach($this->_cachingStack as $cache) + $cache->recordAction($context,$method,$params); + } + } + + /** + * @param boolean $createIfNull whether to create a stack if it does not exist yet. Defaults to true. + * @return CStack stack of {@link COutputCache} objects + */ + public function getCachingStack($createIfNull=true) + { + if(!$this->_cachingStack) + $this->_cachingStack=new CStack; + return $this->_cachingStack; + } + + /** + * Returns whether the caching stack is empty. + * @return boolean whether the caching stack is empty. If not empty, it means currently there are + * some output cache in effect. Note, the return result of this method may change when it is + * called in different output regions, depending on the partition of output caches. + */ + public function isCachingStackEmpty() + { + return $this->_cachingStack===null || !$this->_cachingStack->getCount(); + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * You may override this method to do last-minute preparation for the action. + * @param CAction $action the action to be executed. + * @return boolean whether the action should be executed. + */ + protected function beforeAction($action) + { + return true; + } + + /** + * This method is invoked right after an action is executed. + * You may override this method to do some postprocessing for the action. + * @param CAction $action the action just executed. + */ + protected function afterAction($action) + { + } + + /** + * The filter method for 'postOnly' filter. + * This filter reports an error if the applied action is receiving a non-POST request. + * @param CFilterChain $filterChain the filter chain that the filter is on. + * @throws CHttpException if the current request is not a POST request + */ + public function filterPostOnly($filterChain) + { + if(Yii::app()->getRequest()->getIsPostRequest()) + $filterChain->run(); + else + throw new CHttpException(400,Yii::t('yii','Your request is invalid.')); + } + + /** + * The filter method for 'ajaxOnly' filter. + * This filter reports an error if the applied action is receiving a non-AJAX request. + * @param CFilterChain $filterChain the filter chain that the filter is on. + * @throws CHttpException if the current request is not an AJAX request. + */ + public function filterAjaxOnly($filterChain) + { + if(Yii::app()->getRequest()->getIsAjaxRequest()) + $filterChain->run(); + else + throw new CHttpException(400,Yii::t('yii','Your request is invalid.')); + } + + /** + * The filter method for 'accessControl' filter. + * This filter is a wrapper of {@link CAccessControlFilter}. + * To use this filter, you must override {@link accessRules} method. + * @param CFilterChain $filterChain the filter chain that the filter is on. + */ + public function filterAccessControl($filterChain) + { + $filter=new CAccessControlFilter; + $filter->setRules($this->accessRules()); + $filter->filter($filterChain); + } + + /** + * Returns a persistent page state value. + * A page state is a variable that is persistent across POST requests of the same page. + * In order to use persistent page states, the form(s) must be stateful + * which are generated using {@link CHtml::statefulForm}. + * @param string $name the state name + * @param mixed $defaultValue the value to be returned if the named state is not found + * @return mixed the page state value + * @see setPageState + * @see CHtml::statefulForm + */ + public function getPageState($name,$defaultValue=null) + { + if($this->_pageStates===null) + $this->_pageStates=$this->loadPageStates(); + return isset($this->_pageStates[$name])?$this->_pageStates[$name]:$defaultValue; + } + + /** + * Saves a persistent page state value. + * A page state is a variable that is persistent across POST requests of the same page. + * In order to use persistent page states, the form(s) must be stateful + * which are generated using {@link CHtml::statefulForm}. + * @param string $name the state name + * @param mixed $value the page state value + * @param mixed $defaultValue the default page state value. If this is the same as + * the given value, the state will be removed from persistent storage. + * @see getPageState + * @see CHtml::statefulForm + */ + public function setPageState($name,$value,$defaultValue=null) + { + if($this->_pageStates===null) + $this->_pageStates=$this->loadPageStates(); + if($value===$defaultValue) + unset($this->_pageStates[$name]); + else + $this->_pageStates[$name]=$value; + + $params=func_get_args(); + $this->recordCachingAction('','setPageState',$params); + } + + /** + * Removes all page states. + */ + public function clearPageStates() + { + $this->_pageStates=array(); + } + + /** + * Loads page states from a hidden input. + * @return array the loaded page states + */ + protected function loadPageStates() + { + if(!empty($_POST[self::STATE_INPUT_NAME])) + { + if(($data=base64_decode($_POST[self::STATE_INPUT_NAME]))!==false) + { + if(extension_loaded('zlib')) + $data=@gzuncompress($data); + if(($data=Yii::app()->getSecurityManager()->validateData($data))!==false) + return unserialize($data); + } + } + return array(); + } + + /** + * Saves page states as a base64 string. + * @param array $states the states to be saved. + * @param string $output the output to be modified. Note, this is passed by reference. + */ + protected function savePageStates($states,&$output) + { + $data=Yii::app()->getSecurityManager()->hashData(serialize($states)); + if(extension_loaded('zlib')) + $data=gzcompress($data); + $value=base64_encode($data); + $output=str_replace(CHtml::pageStateField(''),CHtml::pageStateField($value),$output); + } +} diff --git a/framework/web/CDataProvider.php b/framework/web/CDataProvider.php new file mode 100644 index 0000000..3fc7f87 --- /dev/null +++ b/framework/web/CDataProvider.php @@ -0,0 +1,207 @@ +<?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> + * @version $Id: CDataProvider.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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. + * @return CPagination the pagination object. If this is false, it means the pagination is disabled. + */ + public function getPagination() + { + if($this->_pagination===null) + { + $this->_pagination=new CPagination; + 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. + */ + public function setPagination($value) + { + if(is_array($value)) + { + $pagination=$this->getPagination(); + foreach($value as $k=>$v) + $pagination->$k=$v; + } + else + $this->_pagination=$value; + } + + /** + * Returns the sort object. + * @return CSort the sorting object. If this is false, it means the sorting is disabled. + */ + public function getSort() + { + if($this->_sort===null) + { + $this->_sort=new CSort; + 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. + */ + public function setSort($value) + { + if(is_array($value)) + { + $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; + } +} diff --git a/framework/web/CDbHttpSession.php b/framework/web/CDbHttpSession.php new file mode 100644 index 0000000..de42d71 --- /dev/null +++ b/framework/web/CDbHttpSession.php @@ -0,0 +1,267 @@ +<?php +/** + * CDbHttpSession class + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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 TEXT + * ) + * </pre> + * + * 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> + * @version $Id: CDbHttpSession.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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 TEXT) + * </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(); + + $sql="SELECT * FROM {$this->sessionTableName} WHERE id=:id"; + $row=$db->createCommand($sql)->bindValue(':id',$oldID)->queryRow(); + if($row!==false) + { + if($deleteOldSession) + { + $sql="UPDATE {$this->sessionTableName} SET id=:newID WHERE id=:oldID"; + $db->createCommand($sql)->bindValue(':newID',$newID)->bindValue(':oldID',$oldID)->execute(); + } + 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(), + )); + } + } + + /** + * 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) + { + $sql=" +CREATE TABLE $tableName +( + id CHAR(32) PRIMARY KEY, + expire INTEGER, + data TEXT +)"; + $db->createCommand($sql)->execute(); + } + + /** + * @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; + else if(($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); + $sql="DELETE FROM {$this->sessionTableName} WHERE expire<".time(); + try + { + $db->createCommand($sql)->execute(); + } + 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) + { + $now=time(); + $sql=" +SELECT data FROM {$this->sessionTableName} +WHERE expire>$now AND id=:id +"; + $data=$this->getDbConnection()->createCommand($sql)->bindValue(':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(); + $sql="SELECT id FROM {$this->sessionTableName} WHERE id=:id"; + if($db->createCommand($sql)->bindValue(':id',$id)->queryScalar()===false) + $sql="INSERT INTO {$this->sessionTableName} (id, data, expire) VALUES (:id, :data, $expire)"; + else + $sql="UPDATE {$this->sessionTableName} SET expire=$expire, data=:data WHERE id=:id"; + $db->createCommand($sql)->bindValue(':id',$id)->bindValue(':data',$data)->execute(); + } + 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) + { + $sql="DELETE FROM {$this->sessionTableName} WHERE id=:id"; + $this->getDbConnection()->createCommand($sql)->bindValue(':id',$id)->execute(); + 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) + { + $sql="DELETE FROM {$this->sessionTableName} WHERE expire<".time(); + $this->getDbConnection()->createCommand($sql)->execute(); + return true; + } +} diff --git a/framework/web/CExtController.php b/framework/web/CExtController.php new file mode 100644 index 0000000..987d1ae --- /dev/null +++ b/framework/web/CExtController.php @@ -0,0 +1,54 @@ +<?php +/** + * CExtController class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * 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> + * @version $Id: CExtController.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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; + } +} diff --git a/framework/web/CFormModel.php b/framework/web/CFormModel.php new file mode 100644 index 0000000..8faff6f --- /dev/null +++ b/framework/web/CFormModel.php @@ -0,0 +1,79 @@ +<?php +/** + * CFormModel class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CFormModel.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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]; + } +}
\ No newline at end of file diff --git a/framework/web/CHttpCookie.php b/framework/web/CHttpCookie.php new file mode 100644 index 0000000..53d2677 --- /dev/null +++ b/framework/web/CHttpCookie.php @@ -0,0 +1,63 @@ +<?php +/** + * CHttpCookie class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * A CHttpCookie instance stores a single cookie, including the cookie name, value, domain, path, expire, and secure. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CHttpCookie.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @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 + */ + public function __construct($name,$value) + { + $this->name=$name; + $this->value=$value; + } +} diff --git a/framework/web/CHttpRequest.php b/framework/web/CHttpRequest.php new file mode 100644 index 0000000..031ad64 --- /dev/null +++ b/framework/web/CHttpRequest.php @@ -0,0 +1,1064 @@ +<?php +/** + * CHttpRequest and CCookieCollection class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * CHttpRequest encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers. + * + * CHttpRequest also manages the cookies sent from and sent to the user. + * By setting {@link enableCookieValidation} to true, + * cookies sent from the user will be validated to see if they are tampered. + * The property {@link getCookies cookies} returns the collection of cookies. + * For more details, see {@link CCookieCollection}. + * + * CHttpRequest is a default application component loaded by {@link CWebApplication}. It can be + * accessed via {@link CWebApplication::getRequest()}. + * + * @property string $url Part of the request URL after the host info. + * @property string $hostInfo Schema and hostname part (with port number if needed) of the request URL (e.g. http://www.yiiframework.com). + * @property string $baseUrl The relative URL for the application. + * @property string $scriptUrl The relative URL of the entry script. + * @property string $pathInfo Part of the request URL that is after the entry script and before the question mark. + * Note, the returned pathinfo is decoded starting from 1.1.4. + * Prior to 1.1.4, whether it is decoded or not depends on the server configuration + * (in most cases it is not decoded). + * @property string $requestUri The request URI portion for the currently requested URL. + * @property string $queryString Part of the request URL that is after the question mark. + * @property boolean $isSecureConnection If the request is sent via secure channel (https). + * @property string $requestType Request type, such as GET, POST, HEAD, PUT, DELETE. + * @property boolean $isPostRequest Whether this is a POST request. + * @property boolean $isDeleteRequest Whether this is a DELETE request. + * @property boolean $isPutRequest Whether this is a PUT request. + * @property boolean $isAjaxRequest Whether this is an AJAX (XMLHttpRequest) request. + * @property string $serverName Server name. + * @property integer $serverPort Server port number. + * @property string $urlReferrer URL referrer, null if not present. + * @property string $userAgent User agent, null if not present. + * @property string $userHostAddress User IP address. + * @property string $userHost User host name, null if cannot be determined. + * @property string $scriptFile Entry script file path (processed w/ realpath()). + * @property array $browser User browser capabilities. + * @property string $acceptTypes User browser accept types, null if not present. + * @property integer $port Port number for insecure requests. + * @property integer $securePort Port number for secure requests. + * @property CCookieCollection $cookies The cookie collection. + * @property string $preferredLanguage The user preferred language. + * @property string $csrfToken The random token for CSRF validation. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CHttpRequest.php 3560 2012-02-10 14:13:00Z mdomba $ + * @package system.web + * @since 1.0 + */ +class CHttpRequest extends CApplicationComponent +{ + /** + * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to false. + */ + public $enableCookieValidation=false; + /** + * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false. + * By setting this property to true, forms submitted to an Yii Web application must be originated + * from the same application. If not, a 400 HTTP exception will be raised. + * Note, this feature requires that the user client accepts cookie. + * You also need to use {@link CHtml::form} or {@link CHtml::statefulForm} to generate + * the needed HTML forms in your pages. + * @see http://seclab.stanford.edu/websec/csrf/csrf.pdf + */ + public $enableCsrfValidation=false; + /** + * @var string the name of the token used to prevent CSRF. Defaults to 'YII_CSRF_TOKEN'. + * This property is effectively only when {@link enableCsrfValidation} is true. + */ + public $csrfTokenName='YII_CSRF_TOKEN'; + /** + * @var array the property values (in name-value pairs) used to initialize the CSRF cookie. + * Any property of {@link CHttpCookie} may be initialized. + * This property is effective only when {@link enableCsrfValidation} is true. + */ + public $csrfCookie; + + private $_requestUri; + private $_pathInfo; + private $_scriptFile; + private $_scriptUrl; + private $_hostInfo; + private $_baseUrl; + private $_cookies; + private $_preferredLanguage; + private $_csrfToken; + private $_deleteParams; + private $_putParams; + + /** + * Initializes the application component. + * This method overrides the parent implementation by preprocessing + * the user request data. + */ + public function init() + { + parent::init(); + $this->normalizeRequest(); + } + + /** + * Normalizes the request data. + * This method strips off slashes in request data if get_magic_quotes_gpc() returns true. + * It also performs CSRF validation if {@link enableCsrfValidation} is true. + */ + protected function normalizeRequest() + { + // normalize request + if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) + { + if(isset($_GET)) + $_GET=$this->stripSlashes($_GET); + if(isset($_POST)) + $_POST=$this->stripSlashes($_POST); + if(isset($_REQUEST)) + $_REQUEST=$this->stripSlashes($_REQUEST); + if(isset($_COOKIE)) + $_COOKIE=$this->stripSlashes($_COOKIE); + } + + if($this->enableCsrfValidation) + Yii::app()->attachEventHandler('onBeginRequest',array($this,'validateCsrfToken')); + } + + + /** + * Strips slashes from input data. + * This method is applied when magic quotes is enabled. + * @param mixed $data input data to be processed + * @return mixed processed data + */ + public function stripSlashes(&$data) + { + return is_array($data)?array_map(array($this,'stripSlashes'),$data):stripslashes($data); + } + + /** + * Returns the named GET or POST parameter value. + * If the GET or POST parameter does not exist, the second parameter to this method will be returned. + * If both GET and POST contains such a named parameter, the GET parameter takes precedence. + * @param string $name the GET parameter name + * @param mixed $defaultValue the default parameter value if the GET parameter does not exist. + * @return mixed the GET parameter value + * @see getQuery + * @see getPost + */ + public function getParam($name,$defaultValue=null) + { + return isset($_GET[$name]) ? $_GET[$name] : (isset($_POST[$name]) ? $_POST[$name] : $defaultValue); + } + + /** + * Returns the named GET parameter value. + * If the GET parameter does not exist, the second parameter to this method will be returned. + * @param string $name the GET parameter name + * @param mixed $defaultValue the default parameter value if the GET parameter does not exist. + * @return mixed the GET parameter value + * @see getPost + * @see getParam + */ + public function getQuery($name,$defaultValue=null) + { + return isset($_GET[$name]) ? $_GET[$name] : $defaultValue; + } + + /** + * Returns the named POST parameter value. + * If the POST parameter does not exist, the second parameter to this method will be returned. + * @param string $name the POST parameter name + * @param mixed $defaultValue the default parameter value if the POST parameter does not exist. + * @return mixed the POST parameter value + * @see getParam + * @see getQuery + */ + public function getPost($name,$defaultValue=null) + { + return isset($_POST[$name]) ? $_POST[$name] : $defaultValue; + } + + /** + * Returns the named DELETE parameter value. + * If the DELETE parameter does not exist or if the current request is not a DELETE request, + * the second parameter to this method will be returned. + * @param string $name the DELETE parameter name + * @param mixed $defaultValue the default parameter value if the DELETE parameter does not exist. + * @return mixed the DELETE parameter value + * @since 1.1.7 + */ + public function getDelete($name,$defaultValue=null) + { + if($this->_deleteParams===null) + $this->_deleteParams=$this->getIsDeleteRequest() ? $this->getRestParams() : array(); + return isset($this->_deleteParams[$name]) ? $this->_deleteParams[$name] : $defaultValue; + } + + /** + * Returns the named PUT parameter value. + * If the PUT parameter does not exist or if the current request is not a PUT request, + * the second parameter to this method will be returned. + * @param string $name the PUT parameter name + * @param mixed $defaultValue the default parameter value if the PUT parameter does not exist. + * @return mixed the PUT parameter value + * @since 1.1.7 + */ + public function getPut($name,$defaultValue=null) + { + if($this->_putParams===null) + $this->_putParams=$this->getIsPutRequest() ? $this->getRestParams() : array(); + return isset($this->_putParams[$name]) ? $this->_putParams[$name] : $defaultValue; + } + + /** + * Returns the PUT or DELETE request parameters. + * @return array the request parameters + * @since 1.1.7 + */ + protected function getRestParams() + { + $result=array(); + if(function_exists('mb_parse_str')) + mb_parse_str(file_get_contents('php://input'), $result); + else + parse_str(file_get_contents('php://input'), $result); + return $result; + } + + /** + * Returns the currently requested URL. + * This is the same as {@link getRequestUri}. + * @return string part of the request URL after the host info. + */ + public function getUrl() + { + return $this->getRequestUri(); + } + + /** + * Returns the schema and host part of the application URL. + * The returned URL does not have an ending slash. + * By default this is determined based on the user request information. + * You may explicitly specify it by setting the {@link setHostInfo hostInfo} property. + * @param string $schema schema to use (e.g. http, https). If empty, the schema used for the current request will be used. + * @return string schema and hostname part (with port number if needed) of the request URL (e.g. http://www.yiiframework.com) + * @see setHostInfo + */ + public function getHostInfo($schema='') + { + if($this->_hostInfo===null) + { + if($secure=$this->getIsSecureConnection()) + $http='https'; + else + $http='http'; + if(isset($_SERVER['HTTP_HOST'])) + $this->_hostInfo=$http.'://'.$_SERVER['HTTP_HOST']; + else + { + $this->_hostInfo=$http.'://'.$_SERVER['SERVER_NAME']; + $port=$secure ? $this->getSecurePort() : $this->getPort(); + if(($port!==80 && !$secure) || ($port!==443 && $secure)) + $this->_hostInfo.=':'.$port; + } + } + if($schema!=='') + { + $secure=$this->getIsSecureConnection(); + if($secure && $schema==='https' || !$secure && $schema==='http') + return $this->_hostInfo; + + $port=$schema==='https' ? $this->getSecurePort() : $this->getPort(); + if($port!==80 && $schema==='http' || $port!==443 && $schema==='https') + $port=':'.$port; + else + $port=''; + + $pos=strpos($this->_hostInfo,':'); + return $schema.substr($this->_hostInfo,$pos,strcspn($this->_hostInfo,':',$pos+1)+1).$port; + } + else + return $this->_hostInfo; + } + + /** + * Sets the schema and host part of the application URL. + * This setter is provided in case the schema and hostname cannot be determined + * on certain Web servers. + * @param string $value the schema and host part of the application URL. + */ + public function setHostInfo($value) + { + $this->_hostInfo=rtrim($value,'/'); + } + + /** + * Returns the relative URL for the application. + * This is similar to {@link getScriptUrl scriptUrl} except that + * it does not have the script file name, and the ending slashes are stripped off. + * @param boolean $absolute whether to return an absolute URL. Defaults to false, meaning returning a relative one. + * @return string the relative URL for the application + * @see setScriptUrl + */ + public function getBaseUrl($absolute=false) + { + if($this->_baseUrl===null) + $this->_baseUrl=rtrim(dirname($this->getScriptUrl()),'\\/'); + return $absolute ? $this->getHostInfo() . $this->_baseUrl : $this->_baseUrl; + } + + /** + * Sets the relative URL for the application. + * By default the URL is determined based on the entry script URL. + * This setter is provided in case you want to change this behavior. + * @param string $value the relative URL for the application + */ + public function setBaseUrl($value) + { + $this->_baseUrl=$value; + } + + /** + * Returns the relative URL of the entry script. + * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework. + * @return string the relative URL of the entry script. + */ + public function getScriptUrl() + { + if($this->_scriptUrl===null) + { + $scriptName=basename($_SERVER['SCRIPT_FILENAME']); + if(basename($_SERVER['SCRIPT_NAME'])===$scriptName) + $this->_scriptUrl=$_SERVER['SCRIPT_NAME']; + else if(basename($_SERVER['PHP_SELF'])===$scriptName) + $this->_scriptUrl=$_SERVER['PHP_SELF']; + else if(isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME'])===$scriptName) + $this->_scriptUrl=$_SERVER['ORIG_SCRIPT_NAME']; + else if(($pos=strpos($_SERVER['PHP_SELF'],'/'.$scriptName))!==false) + $this->_scriptUrl=substr($_SERVER['SCRIPT_NAME'],0,$pos).'/'.$scriptName; + else if(isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'],$_SERVER['DOCUMENT_ROOT'])===0) + $this->_scriptUrl=str_replace('\\','/',str_replace($_SERVER['DOCUMENT_ROOT'],'',$_SERVER['SCRIPT_FILENAME'])); + else + throw new CException(Yii::t('yii','CHttpRequest is unable to determine the entry script URL.')); + } + return $this->_scriptUrl; + } + + /** + * Sets the relative URL for the application entry script. + * This setter is provided in case the entry script URL cannot be determined + * on certain Web servers. + * @param string $value the relative URL for the application entry script. + */ + public function setScriptUrl($value) + { + $this->_scriptUrl='/'.trim($value,'/'); + } + + /** + * Returns the path info of the currently requested URL. + * This refers to the part that is after the entry script and before the question mark. + * The starting and ending slashes are stripped off. + * @return string part of the request URL that is after the entry script and before the question mark. + * Note, the returned pathinfo is decoded starting from 1.1.4. + * Prior to 1.1.4, whether it is decoded or not depends on the server configuration + * (in most cases it is not decoded). + * @throws CException if the request URI cannot be determined due to improper server configuration + */ + public function getPathInfo() + { + if($this->_pathInfo===null) + { + $pathInfo=$this->getRequestUri(); + + if(($pos=strpos($pathInfo,'?'))!==false) + $pathInfo=substr($pathInfo,0,$pos); + + $pathInfo=$this->decodePathInfo($pathInfo); + + $scriptUrl=$this->getScriptUrl(); + $baseUrl=$this->getBaseUrl(); + if(strpos($pathInfo,$scriptUrl)===0) + $pathInfo=substr($pathInfo,strlen($scriptUrl)); + else if($baseUrl==='' || strpos($pathInfo,$baseUrl)===0) + $pathInfo=substr($pathInfo,strlen($baseUrl)); + else if(strpos($_SERVER['PHP_SELF'],$scriptUrl)===0) + $pathInfo=substr($_SERVER['PHP_SELF'],strlen($scriptUrl)); + else + throw new CException(Yii::t('yii','CHttpRequest is unable to determine the path info of the request.')); + + $this->_pathInfo=trim($pathInfo,'/'); + } + return $this->_pathInfo; + } + + /** + * Decodes the path info. + * This method is an improved variant of the native urldecode() function and used in {@link getPathInfo getPathInfo()} to + * decode the path part of the request URI. You may override this method to change the way the path info is being decoded. + * @param string $pathInfo encoded path info + * @return string decoded path info + * @since 1.1.10 + */ + protected function decodePathInfo($pathInfo) + { + $pathInfo = urldecode($pathInfo); + + // is it UTF-8? + // http://w3.org/International/questions/qa-forms-utf-8.html + if(preg_match('%^(?: + [\x09\x0A\x0D\x20-\x7E] # ASCII + | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )*$%xs', $pathInfo)) + { + return $pathInfo; + } + else + { + return utf8_encode($pathInfo); + } + } + + /** + * Returns the request URI portion for the currently requested URL. + * This refers to the portion that is after the {@link hostInfo host info} part. + * It includes the {@link queryString query string} part if any. + * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework. + * @return string the request URI portion for the currently requested URL. + * @throws CException if the request URI cannot be determined due to improper server configuration + */ + public function getRequestUri() + { + if($this->_requestUri===null) + { + if(isset($_SERVER['HTTP_X_REWRITE_URL'])) // IIS + $this->_requestUri=$_SERVER['HTTP_X_REWRITE_URL']; + else if(isset($_SERVER['REQUEST_URI'])) + { + $this->_requestUri=$_SERVER['REQUEST_URI']; + if(!empty($_SERVER['HTTP_HOST'])) + { + if(strpos($this->_requestUri,$_SERVER['HTTP_HOST'])!==false) + $this->_requestUri=preg_replace('/^\w+:\/\/[^\/]+/','',$this->_requestUri); + } + else + $this->_requestUri=preg_replace('/^(http|https):\/\/[^\/]+/i','',$this->_requestUri); + } + else if(isset($_SERVER['ORIG_PATH_INFO'])) // IIS 5.0 CGI + { + $this->_requestUri=$_SERVER['ORIG_PATH_INFO']; + if(!empty($_SERVER['QUERY_STRING'])) + $this->_requestUri.='?'.$_SERVER['QUERY_STRING']; + } + else + throw new CException(Yii::t('yii','CHttpRequest is unable to determine the request URI.')); + } + + return $this->_requestUri; + } + + /** + * Returns part of the request URL that is after the question mark. + * @return string part of the request URL that is after the question mark + */ + public function getQueryString() + { + return isset($_SERVER['QUERY_STRING'])?$_SERVER['QUERY_STRING']:''; + } + + /** + * Return if the request is sent via secure channel (https). + * @return boolean if the request is sent via secure channel (https) + */ + public function getIsSecureConnection() + { + return isset($_SERVER['HTTPS']) && !strcasecmp($_SERVER['HTTPS'],'on'); + } + + /** + * Returns the request type, such as GET, POST, HEAD, PUT, DELETE. + * @return string request type, such as GET, POST, HEAD, PUT, DELETE. + */ + public function getRequestType() + { + return strtoupper(isset($_SERVER['REQUEST_METHOD'])?$_SERVER['REQUEST_METHOD']:'GET'); + } + + /** + * Returns whether this is a POST request. + * @return boolean whether this is a POST request. + */ + public function getIsPostRequest() + { + return isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'POST'); + } + + /** + * Returns whether this is a DELETE request. + * @return boolean whether this is a DELETE request. + * @since 1.1.7 + */ + public function getIsDeleteRequest() + { + return isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'DELETE'); + } + + /** + * Returns whether this is a PUT request. + * @return boolean whether this is a PUT request. + * @since 1.1.7 + */ + public function getIsPutRequest() + { + return isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'PUT'); + } + + /** + * Returns whether this is an AJAX (XMLHttpRequest) request. + * @return boolean whether this is an AJAX (XMLHttpRequest) request. + */ + public function getIsAjaxRequest() + { + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest'; + } + + /** + * Returns the server name. + * @return string server name + */ + public function getServerName() + { + return $_SERVER['SERVER_NAME']; + } + + /** + * Returns the server port number. + * @return integer server port number + */ + public function getServerPort() + { + return $_SERVER['SERVER_PORT']; + } + + /** + * Returns the URL referrer, null if not present + * @return string URL referrer, null if not present + */ + public function getUrlReferrer() + { + return isset($_SERVER['HTTP_REFERER'])?$_SERVER['HTTP_REFERER']:null; + } + + /** + * Returns the user agent, null if not present. + * @return string user agent, null if not present + */ + public function getUserAgent() + { + return isset($_SERVER['HTTP_USER_AGENT'])?$_SERVER['HTTP_USER_AGENT']:null; + } + + /** + * Returns the user IP address. + * @return string user IP address + */ + public function getUserHostAddress() + { + return isset($_SERVER['REMOTE_ADDR'])?$_SERVER['REMOTE_ADDR']:'127.0.0.1'; + } + + /** + * Returns the user host name, null if it cannot be determined. + * @return string user host name, null if cannot be determined + */ + public function getUserHost() + { + return isset($_SERVER['REMOTE_HOST'])?$_SERVER['REMOTE_HOST']:null; + } + + /** + * Returns entry script file path. + * @return string entry script file path (processed w/ realpath()) + */ + public function getScriptFile() + { + if($this->_scriptFile!==null) + return $this->_scriptFile; + else + return $this->_scriptFile=realpath($_SERVER['SCRIPT_FILENAME']); + } + + /** + * Returns information about the capabilities of user browser. + * @param string $userAgent the user agent to be analyzed. Defaults to null, meaning using the + * current User-Agent HTTP header information. + * @return array user browser capabilities. + * @see http://www.php.net/manual/en/function.get-browser.php + */ + public function getBrowser($userAgent=null) + { + return get_browser($userAgent,true); + } + + /** + * Returns user browser accept types, null if not present. + * @return string user browser accept types, null if not present + */ + public function getAcceptTypes() + { + return isset($_SERVER['HTTP_ACCEPT'])?$_SERVER['HTTP_ACCEPT']:null; + } + + private $_port; + + /** + * Returns the port to use for insecure requests. + * Defaults to 80, or the port specified by the server if the current + * request is insecure. + * You may explicitly specify it by setting the {@link setPort port} property. + * @return integer port number for insecure requests. + * @see setPort + * @since 1.1.3 + */ + public function getPort() + { + if($this->_port===null) + $this->_port=!$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : 80; + return $this->_port; + } + + /** + * Sets the port to use for insecure requests. + * This setter is provided in case a custom port is necessary for certain + * server configurations. + * @param integer $value port number. + * @since 1.1.3 + */ + public function setPort($value) + { + $this->_port=(int)$value; + $this->_hostInfo=null; + } + + private $_securePort; + + /** + * Returns the port to use for secure requests. + * Defaults to 443, or the port specified by the server if the current + * request is secure. + * You may explicitly specify it by setting the {@link setSecurePort securePort} property. + * @return integer port number for secure requests. + * @see setSecurePort + * @since 1.1.3 + */ + public function getSecurePort() + { + if($this->_securePort===null) + $this->_securePort=$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int)$_SERVER['SERVER_PORT'] : 443; + return $this->_securePort; + } + + /** + * Sets the port to use for secure requests. + * This setter is provided in case a custom port is necessary for certain + * server configurations. + * @param integer $value port number. + * @since 1.1.3 + */ + public function setSecurePort($value) + { + $this->_securePort=(int)$value; + $this->_hostInfo=null; + } + + /** + * Returns the cookie collection. + * The result can be used like an associative array. Adding {@link CHttpCookie} objects + * to the collection will send the cookies to the client; and removing the objects + * from the collection will delete those cookies on the client. + * @return CCookieCollection the cookie collection. + */ + public function getCookies() + { + if($this->_cookies!==null) + return $this->_cookies; + else + return $this->_cookies=new CCookieCollection($this); + } + + /** + * Redirects the browser to the specified URL. + * @param string $url URL to be redirected to. If the URL is a relative one, the base URL of + * the application will be inserted at the beginning. + * @param boolean $terminate whether to terminate the current application + * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html} + * for details about HTTP status code. + */ + public function redirect($url,$terminate=true,$statusCode=302) + { + if(strpos($url,'/')===0) + $url=$this->getHostInfo().$url; + header('Location: '.$url, true, $statusCode); + if($terminate) + Yii::app()->end(); + } + + /** + * Returns the user preferred language. + * The returned language ID will be canonicalized using {@link CLocale::getCanonicalID}. + * This method returns false if the user does not have language preference. + * @return string the user preferred language. + */ + public function getPreferredLanguage() + { + if($this->_preferredLanguage===null) + { + if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) && ($n=preg_match_all('/([\w\-_]+)\s*(;\s*q\s*=\s*(\d*\.\d*))?/',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches))>0) + { + $languages=array(); + for($i=0;$i<$n;++$i) + $languages[$matches[1][$i]]=empty($matches[3][$i]) ? 1.0 : floatval($matches[3][$i]); + arsort($languages); + foreach($languages as $language=>$pref) + return $this->_preferredLanguage=CLocale::getCanonicalID($language); + } + return $this->_preferredLanguage=false; + } + return $this->_preferredLanguage; + } + + /** + * Sends a file to user. + * @param string $fileName file name + * @param string $content content to be set. + * @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name. + * @param boolean $terminate whether to terminate the current application after calling this method + */ + public function sendFile($fileName,$content,$mimeType=null,$terminate=true) + { + if($mimeType===null) + { + if(($mimeType=CFileHelper::getMimeTypeByExtension($fileName))===null) + $mimeType='text/plain'; + } + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header("Content-type: $mimeType"); + if(ob_get_length()===false) + header('Content-Length: '.(function_exists('mb_strlen') ? mb_strlen($content,'8bit') : strlen($content))); + header("Content-Disposition: attachment; filename=\"$fileName\""); + header('Content-Transfer-Encoding: binary'); + + if($terminate) + { + // clean up the application first because the file downloading could take long time + // which may cause timeout of some resources (such as DB connection) + Yii::app()->end(0,false); + echo $content; + exit(0); + } + else + echo $content; + } + + /** + * Sends existing file to a browser as a download using x-sendfile. + * + * X-Sendfile is a feature allowing a web application to redirect the request for a file to the webserver + * that in turn processes the request, this way eliminating the need to perform tasks like reading the file + * and sending it to the user. When dealing with a lot of files (or very big files) this can lead to a great + * increase in performance as the web application is allowed to terminate earlier while the webserver is + * handling the request. + * + * The request is sent to the server through a special non-standard HTTP-header. + * When the web server encounters the presence of such header it will discard all output and send the file + * specified by that header using web server internals including all optimizations like caching-headers. + * + * As this header directive is non-standard different directives exists for different web servers applications: + * <ul> + * <li>Apache: {@link http://tn123.org/mod_xsendfile X-Sendfile}</li> + * <li>Lighttpd v1.4: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-LIGHTTPD-send-file}</li> + * <li>Lighttpd v1.5: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-Sendfile}</li> + * <li>Nginx: {@link http://wiki.nginx.org/XSendfile X-Accel-Redirect}</li> + * <li>Cherokee: {@link http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile X-Sendfile and X-Accel-Redirect}</li> + * </ul> + * So for this method to work the X-SENDFILE option/module should be enabled by the web server and + * a proper xHeader should be sent. + * + * <b>Note:</b> + * This option allows to download files that are not under web folders, and even files that are otherwise protected (deny from all) like .htaccess + * + * <b>Side effects</b>: + * If this option is disabled by the web server, when this method is called a download configuration dialog + * will open but the downloaded file will have 0 bytes. + * + * <b>Example</b>: + * <pre> + * <?php + * Yii::app()->request->xSendFile('/home/user/Pictures/picture1.jpg',array( + * 'saveName'=>'image1.jpg', + * 'mimeType'=>'image/jpeg', + * 'terminate'=>false, + * )); + * ?> + * </pre> + * @param string $filePath file name with full path + * @param array $options additional options: + * <ul> + * <li>saveName: file name shown to the user, if not set real file name will be used</li> + * <li>mimeType: mime type of the file, if not set it will be guessed automatically based on the file name, if set to null no content-type header will be sent.</li> + * <li>xHeader: appropriate x-sendfile header, defaults to "X-Sendfile"</li> + * <li>terminate: whether to terminate the current application after calling this method, defaults to true</li> + * <li>forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true. (Since version 1.1.9.)</li> + * <li>addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)</li> + * </ul> + */ + public function xSendFile($filePath, $options=array()) + { + if(!isset($options['forceDownload']) || $options['forceDownload']) + $disposition='attachment'; + else + $disposition='inline'; + + if(!isset($options['saveName'])) + $options['saveName']=basename($filePath); + + if(!isset($options['mimeType'])) + { + if(($options['mimeType']=CFileHelper::getMimeTypeByExtension($filePath))===null) + $options['mimeType']='text/plain'; + } + + if(!isset($options['xHeader'])) + $options['xHeader']='X-Sendfile'; + + if($options['mimeType'] !== null) + header('Content-type: '.$options['mimeType']); + header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"'); + if(isset($options['addHeaders'])) + { + foreach($options['addHeaders'] as $header=>$value) + header($header.': '.$value); + } + header(trim($options['xHeader']).': '.$filePath); + + if(!isset($options['terminate']) || $options['terminate']) + Yii::app()->end(); + } + + /** + * Returns the random token used to perform CSRF validation. + * The token will be read from cookie first. If not found, a new token + * will be generated. + * @return string the random token for CSRF validation. + * @see enableCsrfValidation + */ + public function getCsrfToken() + { + if($this->_csrfToken===null) + { + $cookie=$this->getCookies()->itemAt($this->csrfTokenName); + if(!$cookie || ($this->_csrfToken=$cookie->value)==null) + { + $cookie=$this->createCsrfCookie(); + $this->_csrfToken=$cookie->value; + $this->getCookies()->add($cookie->name,$cookie); + } + } + + return $this->_csrfToken; + } + + /** + * Creates a cookie with a randomly generated CSRF token. + * Initial values specified in {@link csrfCookie} will be applied + * to the generated cookie. + * @return CHttpCookie the generated cookie + * @see enableCsrfValidation + */ + protected function createCsrfCookie() + { + $cookie=new CHttpCookie($this->csrfTokenName,sha1(uniqid(mt_rand(),true))); + if(is_array($this->csrfCookie)) + { + foreach($this->csrfCookie as $name=>$value) + $cookie->$name=$value; + } + return $cookie; + } + + /** + * Performs the CSRF validation. + * This is the event handler responding to {@link CApplication::onBeginRequest}. + * The default implementation will compare the CSRF token obtained + * from a cookie and from a POST field. If they are different, a CSRF attack is detected. + * @param CEvent $event event parameter + * @throws CHttpException if the validation fails + */ + public function validateCsrfToken($event) + { + if($this->getIsPostRequest()) + { + // only validate POST requests + $cookies=$this->getCookies(); + if($cookies->contains($this->csrfTokenName) && isset($_POST[$this->csrfTokenName])) + { + $tokenFromCookie=$cookies->itemAt($this->csrfTokenName)->value; + $tokenFromPost=$_POST[$this->csrfTokenName]; + $valid=$tokenFromCookie===$tokenFromPost; + } + else + $valid=false; + if(!$valid) + throw new CHttpException(400,Yii::t('yii','The CSRF token could not be verified.')); + } + } +} + + +/** + * CCookieCollection implements a collection class to store cookies. + * + * You normally access it via {@link CHttpRequest::getCookies()}. + * + * Since CCookieCollection extends from {@link CMap}, it can be used + * like an associative array as follows: + * <pre> + * $cookies[$name]=new CHttpCookie($name,$value); // sends a cookie + * $value=$cookies[$name]->value; // reads a cookie value + * unset($cookies[$name]); // removes a cookie + * </pre> + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CHttpRequest.php 3560 2012-02-10 14:13:00Z mdomba $ + * @package system.web + * @since 1.0 + */ +class CCookieCollection extends CMap +{ + private $_request; + private $_initialized=false; + + /** + * Constructor. + * @param CHttpRequest $request owner of this collection. + */ + public function __construct(CHttpRequest $request) + { + $this->_request=$request; + $this->copyfrom($this->getCookies()); + $this->_initialized=true; + } + + /** + * @return CHttpRequest the request instance + */ + public function getRequest() + { + return $this->_request; + } + + /** + * @return array list of validated cookies + */ + protected function getCookies() + { + $cookies=array(); + if($this->_request->enableCookieValidation) + { + $sm=Yii::app()->getSecurityManager(); + foreach($_COOKIE as $name=>$value) + { + if(is_string($value) && ($value=$sm->validateData($value))!==false) + $cookies[$name]=new CHttpCookie($name,@unserialize($value)); + } + } + else + { + foreach($_COOKIE as $name=>$value) + $cookies[$name]=new CHttpCookie($name,$value); + } + return $cookies; + } + + /** + * Adds a cookie with the specified name. + * This overrides the parent implementation by performing additional + * operations for each newly added CHttpCookie object. + * @param mixed $name Cookie name. + * @param CHttpCookie $cookie Cookie object. + * @throws CException if the item to be inserted is not a CHttpCookie object. + */ + public function add($name,$cookie) + { + if($cookie instanceof CHttpCookie) + { + $this->remove($name); + parent::add($name,$cookie); + if($this->_initialized) + $this->addCookie($cookie); + } + else + throw new CException(Yii::t('yii','CHttpCookieCollection can only hold CHttpCookie objects.')); + } + + /** + * Removes a cookie with the specified name. + * This overrides the parent implementation by performing additional + * cleanup work when removing a CHttpCookie object. + * @param mixed $name Cookie name. + * @return CHttpCookie The removed cookie object. + */ + public function remove($name) + { + if(($cookie=parent::remove($name))!==null) + { + if($this->_initialized) + $this->removeCookie($cookie); + } + return $cookie; + } + + /** + * Sends a cookie. + * @param CHttpCookie $cookie cookie to be sent + */ + protected function addCookie($cookie) + { + $value=$cookie->value; + if($this->_request->enableCookieValidation) + $value=Yii::app()->getSecurityManager()->hashData(serialize($value)); + if(version_compare(PHP_VERSION,'5.2.0','>=')) + setcookie($cookie->name,$value,$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure,$cookie->httpOnly); + else + setcookie($cookie->name,$value,$cookie->expire,$cookie->path,$cookie->domain,$cookie->secure); + } + + /** + * Deletes a cookie. + * @param CHttpCookie $cookie cookie to be deleted + */ + protected function removeCookie($cookie) + { + if(version_compare(PHP_VERSION,'5.2.0','>=')) + setcookie($cookie->name,null,0,$cookie->path,$cookie->domain,$cookie->secure,$cookie->httpOnly); + else + setcookie($cookie->name,null,0,$cookie->path,$cookie->domain,$cookie->secure); + } +} diff --git a/framework/web/CHttpSession.php b/framework/web/CHttpSession.php new file mode 100644 index 0000000..1d6f100 --- /dev/null +++ b/framework/web/CHttpSession.php @@ -0,0 +1,572 @@ +<?php +/** + * CHttpSession class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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 '/tmp'. + * @property array $cookieParams The session cookie parameters. + * @property string $cookieMode How to use cookie to store session ID. Defaults to 'Allow'. + * @property integer $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> + * @version $Id: CHttpSession.php 3511 2011-12-27 00:02:53Z alexander.makarow $ + * @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 '/tmp'. + */ + 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. + * @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'; + else if(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'); + } + else if($value==='allow') + { + ini_set('session.use_cookies','1'); + ini_set('session.use_only_cookies','0'); + } + else if($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 integer 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 (int)ini_get('session.gc_probability'); + } + + /** + * @param integer $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) + { + $value=(int)$value; + if($value>=0 && $value<=100) + { + ini_set('session.gc_probability',$value); + ini_set('session.gc_divisor','100'); + } + else + throw new CException(Yii::t('yii','CHttpSession.gcProbability "{value}" is invalid. It must be an integer 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]); + } +} diff --git a/framework/web/CHttpSessionIterator.php b/framework/web/CHttpSessionIterator.php new file mode 100644 index 0000000..f695c46 --- /dev/null +++ b/framework/web/CHttpSessionIterator.php @@ -0,0 +1,92 @@ +<?php +/** + * CHttpSessionIterator class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CHttpSessionIterator implements an interator for {@link CHttpSession}. + * + * It allows CHttpSession to return a new iterator for traversing the session variables. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CHttpSessionIterator.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @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; + } +} diff --git a/framework/web/COutputEvent.php b/framework/web/COutputEvent.php new file mode 100644 index 0000000..08c2327 --- /dev/null +++ b/framework/web/COutputEvent.php @@ -0,0 +1,38 @@ +<?php +/** + * COutputEvent class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: COutputEvent.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @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; + } +} diff --git a/framework/web/CPagination.php b/framework/web/CPagination.php new file mode 100644 index 0000000..dcb75f3 --- /dev/null +++ b/framework/web/CPagination.php @@ -0,0 +1,241 @@ +<?php +/** + * CPagination class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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 = Post::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> + * @version $Id: CPagination.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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(); + } +}
\ No newline at end of file diff --git a/framework/web/CSort.php b/framework/web/CSort.php new file mode 100644 index 0000000..ecc9b73 --- /dev/null +++ b/framework/web/CSort.php @@ -0,0 +1,457 @@ +<?php +/** + * CSort class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CSort.php 3554 2012-02-08 16:31:30Z alexander.makarow $ + * @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> + * + * 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(); + if(!empty($order)) + { + if(!empty($criteria->order)) + $criteria->order.=', '; + $criteria->order.=$order; + } + } + + /** + * @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() + { + $directions=$this->getDirections(); + if(empty($directions)) + return is_string($this->defaultOrder) ? $this->defaultOrder : ''; + else + { + if($this->modelClass!==null) + $schema=CActiveRecord::model($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; + } + else if($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=CActiveRecord::model($this->modelClass)->getTableAlias(true).'.'.$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]); + } + else if(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']; + } + else if(is_string($definition)) + $attribute=$definition; + if($this->modelClass!==null) + return CActiveRecord::model($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; + else if($this->modelClass!==null) + $attributes=CActiveRecord::model($this->modelClass)->attributeNames(); + else + return false; + foreach($attributes as $name=>$definition) + { + if(is_string($name)) + { + if($name===$attribute) + return $definition; + } + else if($definition==='*') + { + if($this->modelClass!==null && CActiveRecord::model($this->modelClass)->hasAttribute($attribute)) + return $attribute; + } + else if($definition===$attribute) + return $attribute; + } + return false; + } + + /** + * 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); + } +}
\ No newline at end of file diff --git a/framework/web/CSqlDataProvider.php b/framework/web/CSqlDataProvider.php new file mode 100644 index 0000000..bd2c926 --- /dev/null +++ b/framework/web/CSqlDataProvider.php @@ -0,0 +1,132 @@ +<?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> + * @version $Id: CSqlDataProvider.php 2820 2011-01-06 17:15:56Z mdomba $ + * @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 the SQL statement to be used for fetching data rows. + */ + 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 $sql the SQL statement to be used for fetching data rows. + * @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() + { + $sql=$this->sql; + $db=$this->db===null ? Yii::app()->db : $this->db; + $db->active=true; + + if(($sort=$this->getSort())!==false) + { + $order=$sort->getOrderBy(); + if(!empty($order)) + { + if(preg_match('/\s+order\s+by\s+[\w\s,]+$/i',$sql)) + $sql.=', '.$order; + else + $sql.=' ORDER BY '.$order; + } + } + + if(($pagination=$this->getPagination())!==false) + { + $pagination->setItemCount($this->getTotalItemCount()); + $limit=$pagination->getLimit(); + $offset=$pagination->getOffset(); + $sql=$db->getCommandBuilder()->applyLimit($sql,$limit,$offset); + } + + $command=$db->createCommand($sql); + 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(); + foreach($this->getData() as $i=>$data) + $keys[$i]=$data[$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; + } +} diff --git a/framework/web/CTheme.php b/framework/web/CTheme.php new file mode 100644 index 0000000..5bf3520 --- /dev/null +++ b/framework/web/CTheme.php @@ -0,0 +1,141 @@ +<?php +/** + * CTheme class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CTheme.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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(); + } + } + else if($module!==null) + $moduleViewPath.='/'.$module->getId(); + + return $controller->resolveViewFile($layoutName,$moduleViewPath.'/layouts',$basePath,$moduleViewPath); + } +} diff --git a/framework/web/CThemeManager.php b/framework/web/CThemeManager.php new file mode 100644 index 0000000..29004f3 --- /dev/null +++ b/framework/web/CThemeManager.php @@ -0,0 +1,131 @@ +<?php +/** + * CThemeManager class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CThemeManager.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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' && 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,'/'); + } +} diff --git a/framework/web/CUploadedFile.php b/framework/web/CUploadedFile.php new file mode 100644 index 0000000..60f04c0 --- /dev/null +++ b/framework/web/CUploadedFile.php @@ -0,0 +1,274 @@ +<?php +/** + * CUploadedFile class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CUploadedFile.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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 array 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 for the 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 array 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) && 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. + * @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); + else if(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 ''; + } +} diff --git a/framework/web/CUrlManager.php b/framework/web/CUrlManager.php new file mode 100644 index 0000000..ea36977 --- /dev/null +++ b/framework/web/CUrlManager.php @@ -0,0 +1,849 @@ +<?php +/** + * CUrlManager class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CUrlManager.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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 + { + 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 + return new $this->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; + } + else if(($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; + } + else if(isset($_GET[$this->routeVar])) + return $_GET[$this->routeVar]; + else if(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> + * @version $Id: CUrlManager.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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> + * @version $Id: CUrlManager.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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('/'.$value.'/'.$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; + else if(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; + } +} diff --git a/framework/web/CWebApplication.php b/framework/web/CWebApplication.php new file mode 100644 index 0000000..f6b486d --- /dev/null +++ b/framework/web/CWebApplication.php @@ -0,0 +1,537 @@ +<?php +/** + * CWebApplication class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CWebApplication.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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; + + 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(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(); + } +} diff --git a/framework/web/CWebModule.php b/framework/web/CWebModule.php new file mode 100644 index 0000000..101e7f7 --- /dev/null +++ b/framework/web/CWebModule.php @@ -0,0 +1,197 @@ +<?php +/** + * CWebModule class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CWebModule.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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 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); + } +} diff --git a/framework/web/CWidgetFactory.php b/framework/web/CWidgetFactory.php new file mode 100644 index 0000000..9c94b68 --- /dev/null +++ b/framework/web/CWidgetFactory.php @@ -0,0 +1,198 @@ +<?php +/** + * CWidgetFactory class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * 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> + * @version $Id: CWidgetFactory.php 3421 2011-10-20 21:23:38Z alexander.makarow $ + * @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]; + } +}
\ No newline at end of file diff --git a/framework/web/actions/CAction.php b/framework/web/actions/CAction.php new file mode 100644 index 0000000..246f534 --- /dev/null +++ b/framework/web/actions/CAction.php @@ -0,0 +1,110 @@ +<?php +/** + * CAction class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CAction.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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]); + else if(!is_array($params[$name])) + $ps[]=$params[$name]; + else + return false; + } + else if($param->isDefaultValueAvailable()) + $ps[]=$param->getDefaultValue(); + else + return false; + } + $method->invokeArgs($object,$ps); + return true; + } +} diff --git a/framework/web/actions/CInlineAction.php b/framework/web/actions/CInlineAction.php new file mode 100644 index 0000000..18472e4 --- /dev/null +++ b/framework/web/actions/CInlineAction.php @@ -0,0 +1,53 @@ +<?php +/** + * CInlineAction class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * 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> + * @version $Id: CInlineAction.php 3137 2011-03-28 11:08:06Z mdomba $ + * @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(); + } + +} diff --git a/framework/web/actions/CViewAction.php b/framework/web/actions/CViewAction.php new file mode 100644 index 0000000..e43c606 --- /dev/null +++ b/framework/web/actions/CViewAction.php @@ -0,0 +1,168 @@ +<?php +/** + * CViewAction class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CViewAction.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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])) + $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); + } +}
\ No newline at end of file diff --git a/framework/web/auth/CAccessControlFilter.php b/framework/web/auth/CAccessControlFilter.php new file mode 100644 index 0000000..6ab4d2a --- /dev/null +++ b/framework/web/auth/CAccessControlFilter.php @@ -0,0 +1,342 @@ +<?php +/** + * CAccessControlFilter class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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, 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'), + * // 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 + * '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.', + * ) + * </pre> + * + * @property array $rules List of access rules. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CAccessControlFilter.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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') + $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; + else if($allow<0) // denied + { + $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; + else if($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> + * @version $Id: CAccessControlFilter.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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 controler 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. + */ + 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; + + + /** + * 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 CAction $controller the action + * @return boolean whether the rule applies to the action + */ + protected function isControllerMatched($controller) + { + return empty($this->controllers) || in_array(strtolower($controller->getId()),$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; + else if($u==='?' && $user->getIsGuest()) + return true; + else if($u==='@' && !$user->getIsGuest()) + return true; + else if(!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 $role) + { + if($user->checkAccess($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)); + } +} diff --git a/framework/web/auth/CAuthAssignment.php b/framework/web/auth/CAuthAssignment.php new file mode 100644 index 0000000..b841437 --- /dev/null +++ b/framework/web/auth/CAuthAssignment.php @@ -0,0 +1,107 @@ +<?php +/** + * CAuthAssignment class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CAuthAssignment.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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); + } + } +}
\ No newline at end of file diff --git a/framework/web/auth/CAuthItem.php b/framework/web/auth/CAuthItem.php new file mode 100644 index 0000000..7634786 --- /dev/null +++ b/framework/web/auth/CAuthItem.php @@ -0,0 +1,277 @@ +<?php +/** + * CAuthItem class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CAuthItem.php 3442 2011-11-09 02:48:50Z alexander.makarow $ + * @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 description $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 internally used by {@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) + { + $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); + } +} diff --git a/framework/web/auth/CAuthManager.php b/framework/web/auth/CAuthManager.php new file mode 100644 index 0000000..2756afb --- /dev/null +++ b/framework/web/auth/CAuthManager.php @@ -0,0 +1,166 @@ +<?php +/** + * CAuthManager class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CAuthManager.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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]))); + } +} diff --git a/framework/web/auth/CBaseUserIdentity.php b/framework/web/auth/CBaseUserIdentity.php new file mode 100644 index 0000000..c2bf4e3 --- /dev/null +++ b/framework/web/auth/CBaseUserIdentity.php @@ -0,0 +1,132 @@ +<?php +/** + * CBaseUserIdentity class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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 whether $isAuthenticated The authentication is successful. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CBaseUserIdentity.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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 presistent 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 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]); + } +} diff --git a/framework/web/auth/CDbAuthManager.php b/framework/web/auth/CDbAuthManager.php new file mode 100644 index 0000000..5172fcf --- /dev/null +++ b/framework/web/auth/CDbAuthManager.php @@ -0,0 +1,600 @@ +<?php +/** + * CDbAuthManager class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CDbAuthManager.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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. + * @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. + * @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($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); + else if(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); + } + else if($userId===null) + { + $command=$this->db->createCommand() + ->select() + ->from($this->itemTable) + ->where('type=:type', array(':type'=>$type)); + } + else if($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; + else if(($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; + } +} diff --git a/framework/web/auth/CPhpAuthManager.php b/framework/web/auth/CPhpAuthManager.php new file mode 100644 index 0000000..4a62c67 --- /dev/null +++ b/framework/web/auth/CPhpAuthManager.php @@ -0,0 +1,504 @@ +<?php +/** + * CPhpAuthManager class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CPhpAuthManager.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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 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. + * @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($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))); + else if(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; + } + } + else if(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"); + } +} diff --git a/framework/web/auth/CUserIdentity.php b/framework/web/auth/CUserIdentity.php new file mode 100644 index 0000000..560e75a --- /dev/null +++ b/framework/web/auth/CUserIdentity.php @@ -0,0 +1,82 @@ +<?php +/** + * CUserIdentity class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CUserIdentity.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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; + } +} diff --git a/framework/web/auth/CWebUser.php b/framework/web/auth/CWebUser.php new file mode 100644 index 0000000..73e5a63 --- /dev/null +++ b/framework/web/auth/CWebUser.php @@ -0,0 +1,796 @@ +<?php +/** + * CWebUser class file + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CWebUser.php 3515 2011-12-28 12:29:24Z mdomba $ + * @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'; + + /** + * @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 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(); + else if($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)))); + } + + $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->afterLogout(); + } + } + + /** + * @return boolean whether the current application user is a guest. + */ + public function getIsGuest() + { + return $this->getState('__id')===null; + } + + /** + * @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) + { + return $this->getState('__returnUrl', $defaultUrl===null ? Yii::app()->getRequest()->getScriptUrl() : CHtml::normalizeUrl($defaultUrl)); + } + + /** + * @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()); + elseif(isset($this->loginRequiredAjaxResponse)) + { + echo $this->loginRequiredAjaxResponse; + Yii::app()->end(); + } + + 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); + } + else + 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) && ($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) + { + $cookie->expire=time()+$duration; + $request->getCookies()->add($cookie->name,$cookie); + } + $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])) + { + $cookie->expire=time()+$data[2]; + $cookies->add($cookie->name,$cookie); + } + } + } + + /** + * 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(); + $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, + * he will be automatically logged out. + * @since 1.1.7 + */ + protected function updateAuthStatus() + { + if($this->authTimeout!==null && !$this->getIsGuest()) + { + $expires=$this->getState(self::AUTH_TIMEOUT_VAR); + if ($expires!==null && $expires < 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. + * @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. + * @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]; + else + return $this->_access[$operation]=Yii::app()->getAuthManager()->checkAccess($operation,$this->getId(),$params); + } +} diff --git a/framework/web/auth/schema-mssql.sql b/framework/web/auth/schema-mssql.sql new file mode 100644 index 0000000..7c1208b --- /dev/null +++ b/framework/web/auth/schema-mssql.sql @@ -0,0 +1,42 @@ +/** + * Database schema required by CDbAuthManager. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright 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 +); diff --git a/framework/web/auth/schema-mysql.sql b/framework/web/auth/schema-mysql.sql new file mode 100644 index 0000000..8ed5556 --- /dev/null +++ b/framework/web/auth/schema-mysql.sql @@ -0,0 +1,42 @@ +/** + * Database schema required by CDbAuthManager. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright 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; diff --git a/framework/web/auth/schema-oci.sql b/framework/web/auth/schema-oci.sql new file mode 100644 index 0000000..2d597b4 --- /dev/null +++ b/framework/web/auth/schema-oci.sql @@ -0,0 +1,42 @@ +/** + * Database schema required by CDbAuthManager. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright 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 +); diff --git a/framework/web/auth/schema-pgsql.sql b/framework/web/auth/schema-pgsql.sql new file mode 100644 index 0000000..2d597b4 --- /dev/null +++ b/framework/web/auth/schema-pgsql.sql @@ -0,0 +1,42 @@ +/** + * Database schema required by CDbAuthManager. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright 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 +); diff --git a/framework/web/auth/schema-sqlite.sql b/framework/web/auth/schema-sqlite.sql new file mode 100644 index 0000000..8d93272 --- /dev/null +++ b/framework/web/auth/schema-sqlite.sql @@ -0,0 +1,42 @@ +/** + * Database schema required by CDbAuthManager. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright 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 +); diff --git a/framework/web/filters/CFilter.php b/framework/web/filters/CFilter.php new file mode 100644 index 0000000..93b9750 --- /dev/null +++ b/framework/web/filters/CFilter.php @@ -0,0 +1,75 @@ +<?php +/** + * CFilter class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CFilter.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @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) + { + } +}
\ No newline at end of file diff --git a/framework/web/filters/CFilterChain.php b/framework/web/filters/CFilterChain.php new file mode 100644 index 0000000..592b5a7 --- /dev/null +++ b/framework/web/filters/CFilterChain.php @@ -0,0 +1,136 @@ +<?php +/** + * CFilterChain class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * 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> + * @version $Id: CFilterChain.php 3204 2011-05-05 21:36:32Z alexander.makarow $ + * @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); + } + else if(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); + } +}
\ No newline at end of file diff --git a/framework/web/filters/CInlineFilter.php b/framework/web/filters/CInlineFilter.php new file mode 100644 index 0000000..db10f96 --- /dev/null +++ b/framework/web/filters/CInlineFilter.php @@ -0,0 +1,61 @@ +<?php +/** + * CInlineFilter class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CInlineFilter.php 3026 2011-03-06 10:41:56Z haertl.mike $ + * @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); + } +} diff --git a/framework/web/form/CForm.php b/framework/web/form/CForm.php new file mode 100644 index 0000000..e974eaf --- /dev/null +++ b/framework/web/form/CForm.php @@ -0,0 +1,615 @@ +<?php +/** + * CForm class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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. + * + * 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> + * @version $Id: CForm.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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 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); + $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=get_class($this->_model); + if(strcasecmp($this->getRoot()->method,'get')) + { + if(isset($_POST[$class])) + $this->_model->setAttributes($_POST[$class]); + } + else if(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 button 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"; + } + else if($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); + } +} diff --git a/framework/web/form/CFormButtonElement.php b/framework/web/form/CFormButtonElement.php new file mode 100644 index 0000000..36a4d70 --- /dev/null +++ b/framework/web/form/CFormButtonElement.php @@ -0,0 +1,139 @@ +<?php +/** + * CFormButtonElement class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CFormButtonElement.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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; + } + else if($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); + } +} diff --git a/framework/web/form/CFormElement.php b/framework/web/form/CFormElement.php new file mode 100644 index 0000000..c926e95 --- /dev/null +++ b/framework/web/form/CFormElement.php @@ -0,0 +1,168 @@ +<?php +/** + * CFormElement class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CFormElement.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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(); + else if(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; + } +} diff --git a/framework/web/form/CFormElementCollection.php b/framework/web/form/CFormElementCollection.php new file mode 100644 index 0000000..2ccc3f1 --- /dev/null +++ b/framework/web/form/CFormElementCollection.php @@ -0,0 +1,112 @@ +<?php +/** + * CFormElementCollection class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CFormElementCollection.php 3054 2011-03-12 21:30:21Z qiang.xue $ + * @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); + } + else if(!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); + } + } + } + else if($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); + } +} diff --git a/framework/web/form/CFormInputElement.php b/framework/web/form/CFormInputElement.php new file mode 100644 index 0000000..e53804b --- /dev/null +++ b/framework/web/form/CFormInputElement.php @@ -0,0 +1,255 @@ +<?php +/** + * CFormInputElement class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * </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> + * @version $Id: CFormInputElement.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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', + ); + + /** + * @var string the type of this input. This can be a widget class name, a path alias of a widget class name, + * or a 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()->showErrorSummary ? '' : $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); + } +} diff --git a/framework/web/form/CFormStringElement.php b/framework/web/form/CFormStringElement.php new file mode 100644 index 0000000..7d869ea --- /dev/null +++ b/framework/web/form/CFormStringElement.php @@ -0,0 +1,71 @@ +<?php +/** + * CFormStringElement class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * 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> + * @version $Id: CFormStringElement.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @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); + } +} diff --git a/framework/web/helpers/CGoogleApi.php b/framework/web/helpers/CGoogleApi.php new file mode 100644 index 0000000..7bb08fe --- /dev/null +++ b/framework/web/helpers/CGoogleApi.php @@ -0,0 +1,71 @@ +<?php +/** + * CGoogleApi class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CGoogleApi provides helper methods to easily access {@link http://code.google.com/apis/ajax/ Google AJAX APIs}. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CGoogleApi.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.helpers + */ +class CGoogleApi +{ + public static $bootstrapUrl='http://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); + + $js=self::load($name,$version,$options); + $cs->registerScript($name,$js,CClientScript::POS_HEAD); + } +}
\ No newline at end of file diff --git a/framework/web/helpers/CHtml.php b/framework/web/helpers/CHtml.php new file mode 100644 index 0000000..cc265d6 --- /dev/null +++ b/framework/web/helpers/CHtml.php @@ -0,0 +1,2122 @@ +<?php +/** + * CHtml class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * CHtml is a static class that provides a collection of helper methods for creating HTML views. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CHtml.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.helpers + * @since 1.0 + */ +class CHtml +{ + const ID_PREFIX='yt'; + /** + * @var string the CSS class for displaying error summaries (see {@link errorSummary}). + */ + public static $errorSummaryCss='errorSummary'; + /** + * @var string the CSS class for displaying error messages (see {@link error}). + */ + public static $errorMessageCss='errorMessage'; + /** + * @var string the CSS class for highlighting error inputs. Form inputs will be appended + * with this CSS class if they have input errors. + */ + public static $errorCss='error'; + /** + * @var string the CSS class for required labels. Defaults to 'required'. + * @see label + */ + public static $requiredCss='required'; + /** + * @var string the HTML code to be prepended to the required label. + * @see label + */ + public static $beforeRequiredLabel=''; + /** + * @var string the HTML code to be appended to the required label. + * @see label + */ + public static $afterRequiredLabel=' <span class="required">*</span>'; + /** + * @var integer the counter for generating automatic input field names. + */ + public static $count=0; + + /** + * Sets the default style for attaching jQuery event handlers. + * + * If set to true (default), live/delegated style is used. Event handlers + * are attached to the document body and can process events from descendant + * elements that are added to the document at a later time. + * + * If set to false, direct style is used. Event handlers are attached directly + * to the DOM element, that must already exist on the page. Elements injected + * into the page at a later time will not be processed. + * + * You can override this setting for a particular element by setting the htmlOptions live attribute + * (see {@link clientChange}). + * + * For more information about attaching jQuery event handler see {@link http://api.jquery.com/on/} + * @since 1.1.9 + * @see clientChange + */ + public static $liveEvents = true; + + /** + * Encodes special characters into HTML entities. + * The {@link CApplication::charset application charset} will be used for encoding. + * @param string $text data to be encoded + * @return string the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encode($text) + { + return htmlspecialchars($text,ENT_QUOTES,Yii::app()->charset); + } + + /** + * Decodes special HTML entities back to the corresponding characters. + * This is the opposite of {@link encode()}. + * @param string $text data to be decoded + * @return string the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + * @since 1.1.8 + */ + public static function decode($text) + { + return htmlspecialchars_decode($text,ENT_QUOTES); + } + + /** + * Encodes special characters in an array of strings into HTML entities. + * Both the array keys and values will be encoded if needed. + * If a value is an array, this method will also encode it recursively. + * The {@link CApplication::charset application charset} will be used for encoding. + * @param array $data data to be encoded + * @return array the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encodeArray($data) + { + $d=array(); + foreach($data as $key=>$value) + { + if(is_string($key)) + $key=htmlspecialchars($key,ENT_QUOTES,Yii::app()->charset); + if(is_string($value)) + $value=htmlspecialchars($value,ENT_QUOTES,Yii::app()->charset); + else if(is_array($value)) + $value=self::encodeArray($value); + $d[$key]=$value; + } + return $d; + } + + /** + * Generates an HTML element. + * @param string $tag the tag name + * @param array $htmlOptions the element attributes. The values will be HTML-encoded using {@link encode()}. + * If an 'encode' attribute is given and its value is false, + * the rest of the attribute values will NOT be HTML-encoded. + * Since version 1.1.5, attributes whose value is null will not be rendered. + * @param mixed $content the content to be enclosed between open and close element tags. It will not be HTML-encoded. + * If false, it means there is no body content. + * @param boolean $closeTag whether to generate the close tag. + * @return string the generated HTML element tag + */ + public static function tag($tag,$htmlOptions=array(),$content=false,$closeTag=true) + { + $html='<' . $tag . self::renderAttributes($htmlOptions); + if($content===false) + return $closeTag ? $html.' />' : $html.'>'; + else + return $closeTag ? $html.'>'.$content.'</'.$tag.'>' : $html.'>'.$content; + } + + /** + * Generates an open HTML element. + * @param string $tag the tag name + * @param array $htmlOptions the element attributes. The values will be HTML-encoded using {@link encode()}. + * If an 'encode' attribute is given and its value is false, + * the rest of the attribute values will NOT be HTML-encoded. + * Since version 1.1.5, attributes whose value is null will not be rendered. + * @return string the generated HTML element tag + */ + public static function openTag($tag,$htmlOptions=array()) + { + return '<' . $tag . self::renderAttributes($htmlOptions) . '>'; + } + + /** + * Generates a close HTML element. + * @param string $tag the tag name + * @return string the generated HTML element tag + */ + public static function closeTag($tag) + { + return '</'.$tag.'>'; + } + + /** + * Encloses the given string within a CDATA tag. + * @param string $text the string to be enclosed + * @return string the CDATA tag with the enclosed content. + */ + public static function cdata($text) + { + return '<![CDATA[' . $text . ']]>'; + } + + /** + * Generates a meta tag that can be inserted in the head section of HTML page. + * @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') + * @return string the generated meta tag + */ + public static function metaTag($content,$name=null,$httpEquiv=null,$options=array()) + { + if($name!==null) + $options['name']=$name; + if($httpEquiv!==null) + $options['http-equiv']=$httpEquiv; + $options['content']=$content; + return self::tag('meta',$options); + } + + /** + * Generates a link tag that can be inserted in the head section of HTML page. + * Do not confuse this method with {@link link()}. The latter generates a hyperlink. + * @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 string the generated link tag + */ + public static function linkTag($relation=null,$type=null,$href=null,$media=null,$options=array()) + { + if($relation!==null) + $options['rel']=$relation; + if($type!==null) + $options['type']=$type; + if($href!==null) + $options['href']=$href; + if($media!==null) + $options['media']=$media; + return self::tag('link',$options); + } + + /** + * Encloses the given CSS content with a CSS tag. + * @param string $text the CSS content + * @param string $media the media that this CSS should apply to. + * @return string the CSS properly enclosed + */ + public static function css($text,$media='') + { + if($media!=='') + $media=' media="'.$media.'"'; + return "<style type=\"text/css\"{$media}>\n/*<![CDATA[*/\n{$text}\n/*]]>*/\n</style>"; + } + + /** + * Registers a 'refresh' meta tag. + * This method can be invoked anywhere in a view. It will register a 'refresh' + * meta tag with {@link CClientScript} so that the page can be refreshed in + * the specified seconds. + * @param integer $seconds the number of seconds to wait before refreshing the page + * @param string $url the URL to which the page should be redirected to. If empty, it means the current page. + * @since 1.1.1 + */ + public static function refresh($seconds, $url='') + { + $content="$seconds"; + if($url!=='') + $content.=';'.self::normalizeUrl($url); + Yii::app()->clientScript->registerMetaTag($content,null,'refresh'); + } + + /** + * Links to the specified CSS file. + * @param string $url the CSS URL + * @param string $media the media that this CSS should apply to. + * @return string the CSS link. + */ + public static function cssFile($url,$media='') + { + if($media!=='') + $media=' media="'.$media.'"'; + return '<link rel="stylesheet" type="text/css" href="'.self::encode($url).'"'.$media.' />'; + } + + /** + * Encloses the given JavaScript within a script tag. + * @param string $text the JavaScript to be enclosed + * @return string the enclosed JavaScript + */ + public static function script($text) + { + return "<script type=\"text/javascript\">\n/*<![CDATA[*/\n{$text}\n/*]]>*/\n</script>"; + } + + /** + * Includes a JavaScript file. + * @param string $url URL for the JavaScript file + * @return string the JavaScript file tag + */ + public static function scriptFile($url) + { + return '<script type="text/javascript" src="'.self::encode($url).'"></script>'; + } + + /** + * Generates an opening form tag. + * This is a shortcut to {@link beginForm}. + * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.) + * @param string $method form method (e.g. post, get) + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated form tag. + */ + public static function form($action='',$method='post',$htmlOptions=array()) + { + return self::beginForm($action,$method,$htmlOptions); + } + + /** + * Generates an opening form tag. + * Note, only the open tag is generated. A close tag should be placed manually + * at the end of the form. + * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.) + * @param string $method form method (e.g. post, get) + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated form tag. + * @see endForm + */ + public static function beginForm($action='',$method='post',$htmlOptions=array()) + { + $htmlOptions['action']=$url=self::normalizeUrl($action); + $htmlOptions['method']=$method; + $form=self::tag('form',$htmlOptions,false,false); + $hiddens=array(); + if(!strcasecmp($method,'get') && ($pos=strpos($url,'?'))!==false) + { + foreach(explode('&',substr($url,$pos+1)) as $pair) + { + if(($pos=strpos($pair,'='))!==false) + $hiddens[]=self::hiddenField(urldecode(substr($pair,0,$pos)),urldecode(substr($pair,$pos+1)),array('id'=>false)); + } + } + $request=Yii::app()->request; + if($request->enableCsrfValidation && !strcasecmp($method,'post')) + $hiddens[]=self::hiddenField($request->csrfTokenName,$request->getCsrfToken(),array('id'=>false)); + if($hiddens!==array()) + $form.="\n".self::tag('div',array('style'=>'display:none'),implode("\n",$hiddens)); + return $form; + } + + /** + * Generates a closing form tag. + * @return string the generated tag + * @see beginForm + */ + public static function endForm() + { + return '</form>'; + } + + /** + * Generates a stateful form tag. + * A stateful form tag is similar to {@link form} except that it renders an additional + * hidden field for storing persistent page states. You should use this method to generate + * a form tag if you want to access persistent page states when the form is submitted. + * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.) + * @param string $method form method (e.g. post, get) + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated form tag. + */ + public static function statefulForm($action='',$method='post',$htmlOptions=array()) + { + return self::form($action,$method,$htmlOptions)."\n". + self::tag('div',array('style'=>'display:none'),self::pageStateField('')); + } + + /** + * Generates a hidden field for storing persistent page states. + * This method is internally used by {@link statefulForm}. + * @param string $value the persistent page states in serialized format + * @return string the generated hidden field + */ + public static function pageStateField($value) + { + return '<input type="hidden" name="'.CController::STATE_INPUT_NAME.'" value="'.$value.'" />'; + } + + /** + * Generates a hyperlink tag. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag. + * @param mixed $url a URL or an action route that can be used to create a URL. + * See {@link normalizeUrl} for more details about how to specify this parameter. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated hyperlink + * @see normalizeUrl + * @see clientChange + */ + public static function link($text,$url='#',$htmlOptions=array()) + { + if($url!=='') + $htmlOptions['href']=self::normalizeUrl($url); + self::clientChange('click',$htmlOptions); + return self::tag('a',$htmlOptions,$text); + } + + /** + * Generates a mailto link. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag. + * @param string $email email address. If this is empty, the first parameter (link body) will be treated as the email address. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated mailto link + * @see clientChange + */ + public static function mailto($text,$email='',$htmlOptions=array()) + { + if($email==='') + $email=$text; + return self::link($text,'mailto:'.$email,$htmlOptions); + } + + /** + * Generates an image tag. + * @param string $src the image URL + * @param string $alt the alternative text display + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated image tag + */ + public static function image($src,$alt='',$htmlOptions=array()) + { + $htmlOptions['src']=$src; + $htmlOptions['alt']=$alt; + return self::tag('img',$htmlOptions); + } + + /** + * Generates a button. + * @param string $label the button label + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function button($label='button',$htmlOptions=array()) + { + if(!isset($htmlOptions['name'])) + { + if(!array_key_exists('name',$htmlOptions)) + $htmlOptions['name']=self::ID_PREFIX.self::$count++; + } + if(!isset($htmlOptions['type'])) + $htmlOptions['type']='button'; + if(!isset($htmlOptions['value'])) + $htmlOptions['value']=$label; + self::clientChange('click',$htmlOptions); + return self::tag('input',$htmlOptions); + } + + /** + * Generates a button using HTML button tag. + * This method is similar to {@link button} except that it generates a 'button' + * tag instead of 'input' tag. + * @param string $label the button label. Note that this value will be directly inserted in the button element + * without being HTML-encoded. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function htmlButton($label='button',$htmlOptions=array()) + { + if(!isset($htmlOptions['name'])) + $htmlOptions['name']=self::ID_PREFIX.self::$count++; + if(!isset($htmlOptions['type'])) + $htmlOptions['type']='button'; + self::clientChange('click',$htmlOptions); + return self::tag('button',$htmlOptions,$label); + } + + /** + * Generates a submit button. + * @param string $label the button label + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function submitButton($label='submit',$htmlOptions=array()) + { + $htmlOptions['type']='submit'; + return self::button($label,$htmlOptions); + } + + /** + * Generates a reset button. + * @param string $label the button label + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function resetButton($label='reset',$htmlOptions=array()) + { + $htmlOptions['type']='reset'; + return self::button($label,$htmlOptions); + } + + /** + * Generates an image submit button. + * @param string $src the image URL + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function imageButton($src,$htmlOptions=array()) + { + $htmlOptions['src']=$src; + $htmlOptions['type']='image'; + return self::button('submit',$htmlOptions); + } + + /** + * Generates a link submit button. + * @param string $label the button label + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button tag + * @see clientChange + */ + public static function linkButton($label='submit',$htmlOptions=array()) + { + if(!isset($htmlOptions['submit'])) + $htmlOptions['submit']=isset($htmlOptions['href']) ? $htmlOptions['href'] : ''; + return self::link($label,'#',$htmlOptions); + } + + /** + * Generates a label tag. + * @param string $label label text. Note, you should HTML-encode the text if needed. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is false, the 'for' attribute for the label tag will not be rendered. + * @param array $htmlOptions additional HTML attributes. + * The following HTML option is recognized: + * <ul> + * <li>required: if this is set and is true, the label will be styled + * with CSS class 'required' (customizable with CHtml::$requiredCss), + * and be decorated with {@link CHtml::beforeRequiredLabel} and + * {@link CHtml::afterRequiredLabel}.</li> + * </ul> + * @return string the generated label tag + */ + public static function label($label,$for,$htmlOptions=array()) + { + if($for===false) + unset($htmlOptions['for']); + else + $htmlOptions['for']=$for; + if(isset($htmlOptions['required'])) + { + if($htmlOptions['required']) + { + if(isset($htmlOptions['class'])) + $htmlOptions['class'].=' '.self::$requiredCss; + else + $htmlOptions['class']=self::$requiredCss; + $label=self::$beforeRequiredLabel.$label.self::$afterRequiredLabel; + } + unset($htmlOptions['required']); + } + return self::tag('label',$htmlOptions,$label); + } + + /** + * Generates a text field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + */ + public static function textField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('text',$name,$value,$htmlOptions); + } + + /** + * Generates a hidden input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated input field + * @see inputField + */ + public static function hiddenField($name,$value='',$htmlOptions=array()) + { + return self::inputField('hidden',$name,$value,$htmlOptions); + } + + /** + * Generates a password field input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see inputField + */ + public static function passwordField($name,$value='',$htmlOptions=array()) + { + self::clientChange('change',$htmlOptions); + return self::inputField('password',$name,$value,$htmlOptions); + } + + /** + * Generates a file input. + * Note, you have to set the enclosing form's 'enctype' attribute to be 'multipart/form-data'. + * After the form is submitted, the uploaded file information can be obtained via $_FILES[$name] (see + * PHP documentation). + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated input field + * @see inputField + */ + public static function fileField($name,$value='',$htmlOptions=array()) + { + return self::inputField('file',$name,$value,$htmlOptions); + } + + /** + * Generates a text area input. + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated text area + * @see clientChange + * @see inputField + */ + public static function textArea($name,$value='',$htmlOptions=array()) + { + $htmlOptions['name']=$name; + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=self::getIdByName($name); + else if($htmlOptions['id']===false) + unset($htmlOptions['id']); + self::clientChange('change',$htmlOptions); + return self::tag('textarea',$htmlOptions,isset($htmlOptions['encode']) && !$htmlOptions['encode'] ? $value : self::encode($value)); + } + + /** + * Generates a radio button. + * @param string $name the input name + * @param boolean $checked whether the radio button is checked + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify + * the value returned when the radio button is not checked. When set, a hidden field is rendered so that + * when the radio button is not checked, we can still obtain the posted uncheck value. + * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered. + * @return string the generated radio button + * @see clientChange + * @see inputField + */ + public static function radioButton($name,$checked=false,$htmlOptions=array()) + { + if($checked) + $htmlOptions['checked']='checked'; + else + unset($htmlOptions['checked']); + $value=isset($htmlOptions['value']) ? $htmlOptions['value'] : 1; + self::clientChange('click',$htmlOptions); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck=null; + + if($uncheck!==null) + { + // add a hidden field so that if the radio button is not selected, it still submits a value + if(isset($htmlOptions['id']) && $htmlOptions['id']!==false) + $uncheckOptions=array('id'=>self::ID_PREFIX.$htmlOptions['id']); + else + $uncheckOptions=array('id'=>false); + $hidden=self::hiddenField($name,$uncheck,$uncheckOptions); + } + else + $hidden=''; + + // add a hidden field so that if the radio button is not selected, it still submits a value + return $hidden . self::inputField('radio',$name,$value,$htmlOptions); + } + + /** + * Generates a check box. + * @param string $name the input name + * @param boolean $checked whether the check box is checked + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify + * the value returned when the checkbox is not checked. When set, a hidden field is rendered so that + * when the checkbox is not checked, we can still obtain the posted uncheck value. + * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered. + * @return string the generated check box + * @see clientChange + * @see inputField + */ + public static function checkBox($name,$checked=false,$htmlOptions=array()) + { + if($checked) + $htmlOptions['checked']='checked'; + else + unset($htmlOptions['checked']); + $value=isset($htmlOptions['value']) ? $htmlOptions['value'] : 1; + self::clientChange('click',$htmlOptions); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck=null; + + if($uncheck!==null) + { + // add a hidden field so that if the radio button is not selected, it still submits a value + if(isset($htmlOptions['id']) && $htmlOptions['id']!==false) + $uncheckOptions=array('id'=>self::ID_PREFIX.$htmlOptions['id']); + else + $uncheckOptions=array('id'=>false); + $hidden=self::hiddenField($name,$uncheck,$uncheckOptions); + } + else + $hidden=''; + + // add a hidden field so that if the checkbox is not selected, it still submits a value + return $hidden . self::inputField('checkbox',$name,$value,$htmlOptions); + } + + /** + * Generates a drop down list. + * @param string $name the input name + * @param string $select the selected value + * @param array $data data for generating the list options (value=>display). + * You may use {@link listData} to generate this data. + * Please refer to {@link listOptions} on how this data is used to generate the list options. + * Note, the values and labels will be automatically HTML-encoded by this method. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are recognized. See {@link clientChange} and {@link tag} for more details. + * In addition, the following options are also supported specifically for dropdown list: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * </ul> + * @return string the generated drop down list + * @see clientChange + * @see inputField + * @see listData + */ + public static function dropDownList($name,$select,$data,$htmlOptions=array()) + { + $htmlOptions['name']=$name; + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=self::getIdByName($name); + else if($htmlOptions['id']===false) + unset($htmlOptions['id']); + self::clientChange('change',$htmlOptions); + $options="\n".self::listOptions($select,$data,$htmlOptions); + return self::tag('select',$htmlOptions,$options); + } + + /** + * Generates a list box. + * @param string $name the input name + * @param mixed $select the selected value(s). This can be either a string for single selection or an array for multiple selections. + * @param array $data data for generating the list options (value=>display) + * You may use {@link listData} to generate this data. + * Please refer to {@link listOptions} on how this data is used to generate the list options. + * Note, the values and labels will be automatically HTML-encoded by this method. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized. See {@link clientChange} and {@link tag} for more details. + * In addition, the following options are also supported specifically for list box: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * </ul> + * @return string the generated list box + * @see clientChange + * @see inputField + * @see listData + */ + public static function listBox($name,$select,$data,$htmlOptions=array()) + { + if(!isset($htmlOptions['size'])) + $htmlOptions['size']=4; + if(isset($htmlOptions['multiple'])) + { + if(substr($name,-2)!=='[]') + $name.='[]'; + } + return self::dropDownList($name,$select,$data,$htmlOptions); + } + + /** + * Generates a check box list. + * A check box list allows multiple selection, like {@link listBox}. + * As a result, the corresponding POST value is an array. + * @param string $name name of the check box list. You can use this name to retrieve + * the selected value(s) once the form is submitted. + * @param mixed $select selection of the check boxes. This can be either a string + * for single selection or an array for multiple selections. + * @param array $data value-label pairs used to generate the check box list. + * Note, the values will be automatically HTML-encoded, while the labels will not. + * @param array $htmlOptions addtional HTML options. The options will be applied to + * each checkbox input. The following special options are recognized: + * <ul> + * <li>template: string, specifies how each checkbox is rendered. Defaults + * to "{input} {label}", where "{input}" will be replaced by the generated + * check box input tag while "{label}" be replaced by the corresponding check box label.</li> + * <li>separator: string, specifies the string that separates the generated check boxes.</li> + * <li>checkAll: string, specifies the label for the "check all" checkbox. + * If this option is specified, a 'check all' checkbox will be displayed. Clicking on + * this checkbox will cause all checkboxes checked or unchecked.</li> + * <li>checkAllLast: boolean, specifies whether the 'check all' checkbox should be + * displayed at the end of the checkbox list. If this option is not set (default) + * or is false, the 'check all' checkbox will be displayed at the beginning of + * the checkbox list.</li> + * <li>labelOptions: array, specifies the additional HTML attributes to be rendered + * for every label tag in the list.</li> + * </ul> + * @return string the generated check box list + */ + public static function checkBoxList($name,$select,$data,$htmlOptions=array()) + { + $template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}'; + $separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:"<br/>\n"; + unset($htmlOptions['template'],$htmlOptions['separator']); + + if(substr($name,-2)!=='[]') + $name.='[]'; + + if(isset($htmlOptions['checkAll'])) + { + $checkAllLabel=$htmlOptions['checkAll']; + $checkAllLast=isset($htmlOptions['checkAllLast']) && $htmlOptions['checkAllLast']; + } + unset($htmlOptions['checkAll'],$htmlOptions['checkAllLast']); + + $labelOptions=isset($htmlOptions['labelOptions'])?$htmlOptions['labelOptions']:array(); + unset($htmlOptions['labelOptions']); + + $items=array(); + $baseID=self::getIdByName($name); + $id=0; + $checkAll=true; + + foreach($data as $value=>$label) + { + $checked=!is_array($select) && !strcmp($value,$select) || is_array($select) && in_array($value,$select); + $checkAll=$checkAll && $checked; + $htmlOptions['value']=$value; + $htmlOptions['id']=$baseID.'_'.$id++; + $option=self::checkBox($name,$checked,$htmlOptions); + $label=self::label($label,$htmlOptions['id'],$labelOptions); + $items[]=strtr($template,array('{input}'=>$option,'{label}'=>$label)); + } + + if(isset($checkAllLabel)) + { + $htmlOptions['value']=1; + $htmlOptions['id']=$id=$baseID.'_all'; + $option=self::checkBox($id,$checkAll,$htmlOptions); + $label=self::label($checkAllLabel,$id,$labelOptions); + $item=strtr($template,array('{input}'=>$option,'{label}'=>$label)); + if($checkAllLast) + $items[]=$item; + else + array_unshift($items,$item); + $name=strtr($name,array('['=>'\\[',']'=>'\\]')); + $js=<<<EOD +$('#$id').click(function() { + $("input[name='$name']").prop('checked', this.checked); +}); +$("input[name='$name']").click(function() { + $('#$id').prop('checked', !$("input[name='$name']:not(:checked)").length); +}); +$('#$id').prop('checked', !$("input[name='$name']:not(:checked)").length); +EOD; + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('jquery'); + $cs->registerScript($id,$js); + } + + return self::tag('span',array('id'=>$baseID),implode($separator,$items)); + } + + /** + * Generates a radio button list. + * A radio button list is like a {@link checkBoxList check box list}, except that + * it only allows single selection. + * @param string $name name of the radio button list. You can use this name to retrieve + * the selected value(s) once the form is submitted. + * @param string $select selection of the radio buttons. + * @param array $data value-label pairs used to generate the radio button list. + * Note, the values will be automatically HTML-encoded, while the labels will not. + * @param array $htmlOptions addtional HTML options. The options will be applied to + * each radio button input. The following special options are recognized: + * <ul> + * <li>template: string, specifies how each radio button is rendered. Defaults + * to "{input} {label}", where "{input}" will be replaced by the generated + * radio button input tag while "{label}" will be replaced by the corresponding radio button label.</li> + * <li>separator: string, specifies the string that separates the generated radio buttons. Defaults to new line (<br/>).</li> + * <li>labelOptions: array, specifies the additional HTML attributes to be rendered + * for every label tag in the list.</li> + * </ul> + * @return string the generated radio button list + */ + public static function radioButtonList($name,$select,$data,$htmlOptions=array()) + { + $template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}'; + $separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:"<br/>\n"; + unset($htmlOptions['template'],$htmlOptions['separator']); + + $labelOptions=isset($htmlOptions['labelOptions'])?$htmlOptions['labelOptions']:array(); + unset($htmlOptions['labelOptions']); + + $items=array(); + $baseID=self::getIdByName($name); + $id=0; + foreach($data as $value=>$label) + { + $checked=!strcmp($value,$select); + $htmlOptions['value']=$value; + $htmlOptions['id']=$baseID.'_'.$id++; + $option=self::radioButton($name,$checked,$htmlOptions); + $label=self::label($label,$htmlOptions['id'],$labelOptions); + $items[]=strtr($template,array('{input}'=>$option,'{label}'=>$label)); + } + return self::tag('span',array('id'=>$baseID),implode($separator,$items)); + } + + /** + * Generates a link that can initiate AJAX requests. + * @param string $text the link body (it will NOT be HTML-encoded.) + * @param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {@link normalizeUrl} for more details. + * @param array $ajaxOptions AJAX options (see {@link ajax}) + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated link + * @see normalizeUrl + * @see ajax + */ + public static function ajaxLink($text,$url,$ajaxOptions=array(),$htmlOptions=array()) + { + if(!isset($htmlOptions['href'])) + $htmlOptions['href']='#'; + $ajaxOptions['url']=$url; + $htmlOptions['ajax']=$ajaxOptions; + self::clientChange('click',$htmlOptions); + return self::tag('a',$htmlOptions,$text); + } + + /** + * Generates a push button that can initiate AJAX requests. + * @param string $label the button label + * @param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {@link normalizeUrl} for more details. + * @param array $ajaxOptions AJAX options (see {@link ajax}) + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button + */ + public static function ajaxButton($label,$url,$ajaxOptions=array(),$htmlOptions=array()) + { + $ajaxOptions['url']=$url; + $htmlOptions['ajax']=$ajaxOptions; + return self::button($label,$htmlOptions); + } + + /** + * Generates a push button that can submit the current form in POST method. + * @param string $label the button label + * @param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {@link normalizeUrl} for more details. + * @param array $ajaxOptions AJAX options (see {@link ajax}) + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated button + */ + public static function ajaxSubmitButton($label,$url,$ajaxOptions=array(),$htmlOptions=array()) + { + $ajaxOptions['type']='POST'; + $htmlOptions['type']='submit'; + return self::ajaxButton($label,$url,$ajaxOptions,$htmlOptions); + } + + /** + * Generates the JavaScript that initiates an AJAX request. + * @param array $options AJAX options. The valid options are specified in the jQuery ajax documentation. + * The following special options are added for convenience: + * <ul> + * <li>update: string, specifies the selector whose HTML content should be replaced + * by the AJAX request result.</li> + * <li>replace: string, specifies the selector whose target should be replaced + * by the AJAX request result.</li> + * </ul> + * Note, if you specify the 'success' option, the above options will be ignored. + * @return string the generated JavaScript + * @see http://docs.jquery.com/Ajax/jQuery.ajax#options + */ + public static function ajax($options) + { + Yii::app()->getClientScript()->registerCoreScript('jquery'); + if(!isset($options['url'])) + $options['url']='js:location.href'; + else + $options['url']=self::normalizeUrl($options['url']); + if(!isset($options['cache'])) + $options['cache']=false; + if(!isset($options['data']) && isset($options['type'])) + $options['data']='js:jQuery(this).parents("form").serialize()'; + foreach(array('beforeSend','complete','error','success') as $name) + { + if(isset($options[$name]) && strpos($options[$name],'js:')!==0) + $options[$name]='js:'.$options[$name]; + } + if(isset($options['update'])) + { + if(!isset($options['success'])) + $options['success']='js:function(html){jQuery("'.$options['update'].'").html(html)}'; + unset($options['update']); + } + if(isset($options['replace'])) + { + if(!isset($options['success'])) + $options['success']='js:function(html){jQuery("'.$options['replace'].'").replaceWith(html)}'; + unset($options['replace']); + } + return 'jQuery.ajax('.CJavaScript::encode($options).');'; + } + + /** + * Generates the URL for the published assets. + * @param string $path the path of the asset to be published + * @param boolean $hashByName whether the published directory should be named as the hashed basename. + * If false, the name will be the hashed dirname of the path being published. + * Defaults to false. Set true if the path being published is shared among + * different extensions. + * @return string the asset URL + */ + public static function asset($path,$hashByName=false) + { + return Yii::app()->getAssetManager()->publish($path,$hashByName); + } + + /** + * Normalizes the input parameter to be a valid URL. + * + * If the input parameter is an empty string, the currently requested URL will be returned. + * + * If the input parameter is a non-empty string, it is treated as a valid URL and will + * be returned without any change. + * + * If the input parameter is an array, it is treated as a controller route and a list of + * GET parameters, and the {@link CController::createUrl} method will be invoked to + * create a URL. In this case, the first array element refers to the controller route, + * and the rest key-value pairs refer to the additional GET parameters for the URL. + * For example, <code>array('post/list', 'page'=>3)</code> may be used to generate the URL + * <code>/index.php?r=post/list&page=3</code>. + * + * @param mixed $url the parameter to be used to generate a valid URL + * @return string the normalized URL + */ + public static function normalizeUrl($url) + { + if(is_array($url)) + { + if(isset($url[0])) + { + if(($c=Yii::app()->getController())!==null) + $url=$c->createUrl($url[0],array_splice($url,1)); + else + $url=Yii::app()->createUrl($url[0],array_splice($url,1)); + } + else + $url=''; + } + return $url==='' ? Yii::app()->getRequest()->getUrl() : $url; + } + + /** + * Generates an input HTML tag. + * This method generates an input HTML tag based on the given input name and value. + * @param string $type the input type (e.g. 'text', 'radio') + * @param string $name the input name + * @param string $value the input value + * @param array $htmlOptions additional HTML attributes for the HTML tag (see {@link tag}). + * @return string the generated input tag + */ + protected static function inputField($type,$name,$value,$htmlOptions) + { + $htmlOptions['type']=$type; + $htmlOptions['value']=$value; + $htmlOptions['name']=$name; + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=self::getIdByName($name); + else if($htmlOptions['id']===false) + unset($htmlOptions['id']); + return self::tag('input',$htmlOptions); + } + + /** + * Generates a label tag for a model attribute. + * The label text is the attribute label and the label is associated with + * the input for the attribute (see {@link CModel::getAttributeLabel}. + * If the attribute has input error, the label's CSS class will be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. The following special options are recognized: + * <ul> + * <li>required: if this is set and is true, the label will be styled + * with CSS class 'required' (customizable with CHtml::$requiredCss), + * and be decorated with {@link CHtml::beforeRequiredLabel} and + * {@link CHtml::afterRequiredLabel}.</li> + * <li>label: this specifies the label to be displayed. If this is not set, + * {@link CModel::getAttributeLabel} will be called to get the label for display. + * If the label is specified as false, no label will be rendered.</li> + * </ul> + * @return string the generated label tag + */ + public static function activeLabel($model,$attribute,$htmlOptions=array()) + { + if(isset($htmlOptions['for'])) + { + $for=$htmlOptions['for']; + unset($htmlOptions['for']); + } + else + $for=self::getIdByName(self::resolveName($model,$attribute)); + if(isset($htmlOptions['label'])) + { + if(($label=$htmlOptions['label'])===false) + return ''; + unset($htmlOptions['label']); + } + else + $label=$model->getAttributeLabel($attribute); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + return self::label($label,$for,$htmlOptions); + } + + /** + * Generates a label tag for a model attribute. + * This is an enhanced version of {@link activeLabel}. It will render additional + * CSS class and mark when the attribute is required. + * In particular, it calls {@link CModel::isAttributeRequired} to determine + * if the attribute is required. + * If so, it will add a CSS class {@link CHtml::requiredCss} to the label, + * and decorate the label with {@link CHtml::beforeRequiredLabel} and + * {@link CHtml::afterRequiredLabel}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated label tag + */ + public static function activeLabelEx($model,$attribute,$htmlOptions=array()) + { + $realAttribute=$attribute; + self::resolveName($model,$attribute); // strip off square brackets if any + $htmlOptions['required']=$model->isAttributeRequired($attribute); + return self::activeLabel($model,$realAttribute,$htmlOptions); + } + + /** + * Generates a text field input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see activeInputField + */ + public static function activeTextField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + return self::activeInputField('text',$model,$attribute,$htmlOptions); + } + + /** + * Generates a hidden input for a model attribute. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + * @see activeInputField + */ + public static function activeHiddenField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + return self::activeInputField('hidden',$model,$attribute,$htmlOptions); + } + + /** + * Generates a password field input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated input field + * @see clientChange + * @see activeInputField + */ + public static function activePasswordField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + return self::activeInputField('password',$model,$attribute,$htmlOptions); + } + + /** + * Generates a text area input for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @return string the generated text area + * @see clientChange + */ + public static function activeTextArea($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + self::clientChange('change',$htmlOptions); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + $text=self::resolveValue($model,$attribute); + return self::tag('textarea',$htmlOptions,isset($htmlOptions['encode']) && !$htmlOptions['encode'] ? $text : self::encode($text)); + } + + /** + * Generates a file input for a model attribute. + * Note, you have to set the enclosing form's 'enctype' attribute to be 'multipart/form-data'. + * After the form is submitted, the uploaded file information can be obtained via $_FILES (see + * PHP documentation). + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @return string the generated input field + * @see activeInputField + */ + public static function activeFileField($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + return self::hiddenField($htmlOptions['name'],'',$hiddenOptions) + . self::activeInputField('file',$model,$attribute,$htmlOptions); + } + + /** + * Generates a radio button for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * A special option named 'uncheckValue' is available that can be used to specify + * the value returned when the radio button is not checked. By default, this value is '0'. + * Internally, a hidden field is rendered so that when the radio button is not checked, + * we can still obtain the posted uncheck value. + * If 'uncheckValue' is set as NULL, the hidden field will not be rendered. + * @return string the generated radio button + * @see clientChange + * @see activeInputField + */ + public static function activeRadioButton($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + if(!isset($htmlOptions['value'])) + $htmlOptions['value']=1; + if(!isset($htmlOptions['checked']) && self::resolveValue($model,$attribute)==$htmlOptions['value']) + $htmlOptions['checked']='checked'; + self::clientChange('click',$htmlOptions); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck='0'; + + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + $hidden=$uncheck!==null ? self::hiddenField($htmlOptions['name'],$uncheck,$hiddenOptions) : ''; + + // add a hidden field so that if the radio button is not selected, it still submits a value + return $hidden . self::activeInputField('radio',$model,$attribute,$htmlOptions); + } + + /** + * Generates a check box for a model attribute. + * The attribute is assumed to take either true or false value. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * A special option named 'uncheckValue' is available that can be used to specify + * the value returned when the checkbox is not checked. By default, this value is '0'. + * Internally, a hidden field is rendered so that when the checkbox is not checked, + * we can still obtain the posted uncheck value. + * If 'uncheckValue' is set as NULL, the hidden field will not be rendered. + * @return string the generated check box + * @see clientChange + * @see activeInputField + */ + public static function activeCheckBox($model,$attribute,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + if(!isset($htmlOptions['value'])) + $htmlOptions['value']=1; + if(!isset($htmlOptions['checked']) && self::resolveValue($model,$attribute)==$htmlOptions['value']) + $htmlOptions['checked']='checked'; + self::clientChange('click',$htmlOptions); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck='0'; + + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + $hidden=$uncheck!==null ? self::hiddenField($htmlOptions['name'],$uncheck,$hiddenOptions) : ''; + + return $hidden . self::activeInputField('checkbox',$model,$attribute,$htmlOptions); + } + + /** + * Generates a drop down list for a model attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data data for generating the list options (value=>display) + * You may use {@link listData} to generate this data. + * Please refer to {@link listOptions} on how this data is used to generate the list options. + * Note, the values and labels will be automatically HTML-encoded by this method. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are recognized. See {@link clientChange} and {@link tag} for more details. + * In addition, the following options are also supported: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * </ul> + * @return string the generated drop down list + * @see clientChange + * @see listData + */ + public static function activeDropDownList($model,$attribute,$data,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + $selection=self::resolveValue($model,$attribute); + $options="\n".self::listOptions($selection,$data,$htmlOptions); + self::clientChange('change',$htmlOptions); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + if(isset($htmlOptions['multiple'])) + { + if(substr($htmlOptions['name'],-2)!=='[]') + $htmlOptions['name'].='[]'; + } + return self::tag('select',$htmlOptions,$options); + } + + /** + * Generates a list box for a model attribute. + * The model attribute value is used as the selection. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data data for generating the list options (value=>display) + * You may use {@link listData} to generate this data. + * Please refer to {@link listOptions} on how this data is used to generate the list options. + * Note, the values and labels will be automatically HTML-encoded by this method. + * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special + * attributes are recognized. See {@link clientChange} and {@link tag} for more details. + * In addition, the following options are also supported: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * </ul> + * @return string the generated list box + * @see clientChange + * @see listData + */ + public static function activeListBox($model,$attribute,$data,$htmlOptions=array()) + { + if(!isset($htmlOptions['size'])) + $htmlOptions['size']=4; + return self::activeDropDownList($model,$attribute,$data,$htmlOptions); + } + + /** + * Generates a check box list for a model attribute. + * The model attribute value is used as the selection. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * Note that a check box list allows multiple selection, like {@link listBox}. + * As a result, the corresponding POST value is an array. In case no selection + * is made, the corresponding POST value is an empty string. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data value-label pairs used to generate the check box list. + * Note, the values will be automatically HTML-encoded, while the labels will not. + * @param array $htmlOptions addtional HTML options. The options will be applied to + * each checkbox input. The following special options are recognized: + * <ul> + * <li>template: string, specifies how each checkbox is rendered. Defaults + * to "{input} {label}", where "{input}" will be replaced by the generated + * check box input tag while "{label}" will be replaced by the corresponding check box label.</li> + * <li>separator: string, specifies the string that separates the generated check boxes.</li> + * <li>checkAll: string, specifies the label for the "check all" checkbox. + * If this option is specified, a 'check all' checkbox will be displayed. Clicking on + * this checkbox will cause all checkboxes checked or unchecked.</li> + * <li>checkAllLast: boolean, specifies whether the 'check all' checkbox should be + * displayed at the end of the checkbox list. If this option is not set (default) + * or is false, the 'check all' checkbox will be displayed at the beginning of + * the checkbox list.</li> + * <li>encode: boolean, specifies whether to encode HTML-encode tag attributes and values. Defaults to true.</li> + * </ul> + * Since 1.1.7, a special option named 'uncheckValue' is available. It can be used to set the value + * that will be returned when the checkbox is not checked. By default, this value is ''. + * Internally, a hidden field is rendered so when the checkbox is not checked, we can still + * obtain the value. If 'uncheckValue' is set to NULL, there will be no hidden field rendered. + * @return string the generated check box list + * @see checkBoxList + */ + public static function activeCheckBoxList($model,$attribute,$data,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + $selection=self::resolveValue($model,$attribute); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + $name=$htmlOptions['name']; + unset($htmlOptions['name']); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck=''; + + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + $hidden=$uncheck!==null ? self::hiddenField($name,$uncheck,$hiddenOptions) : ''; + + return $hidden . self::checkBoxList($name,$selection,$data,$htmlOptions); + } + + /** + * Generates a radio button list for a model attribute. + * The model attribute value is used as the selection. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data value-label pairs used to generate the radio button list. + * Note, the values will be automatically HTML-encoded, while the labels will not. + * @param array $htmlOptions addtional HTML options. The options will be applied to + * each radio button input. The following special options are recognized: + * <ul> + * <li>template: string, specifies how each radio button is rendered. Defaults + * to "{input} {label}", where "{input}" will be replaced by the generated + * radio button input tag while "{label}" will be replaced by the corresponding radio button label.</li> + * <li>separator: string, specifies the string that separates the generated radio buttons. Defaults to new line (<br/>).</li> + * <li>encode: boolean, specifies whether to encode HTML-encode tag attributes and values. Defaults to true.</li> + * </ul> + * Since version 1.1.7, a special option named 'uncheckValue' is available that can be used to specify the value + * returned when the radio button is not checked. By default, this value is ''. Internally, a hidden field is + * rendered so that when the radio button is not checked, we can still obtain the posted uncheck value. + * If 'uncheckValue' is set as NULL, the hidden field will not be rendered. + * @return string the generated radio button list + * @see radioButtonList + */ + public static function activeRadioButtonList($model,$attribute,$data,$htmlOptions=array()) + { + self::resolveNameID($model,$attribute,$htmlOptions); + $selection=self::resolveValue($model,$attribute); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + $name=$htmlOptions['name']; + unset($htmlOptions['name']); + + if(array_key_exists('uncheckValue',$htmlOptions)) + { + $uncheck=$htmlOptions['uncheckValue']; + unset($htmlOptions['uncheckValue']); + } + else + $uncheck=''; + + $hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false); + $hidden=$uncheck!==null ? self::hiddenField($name,$uncheck,$hiddenOptions) : ''; + + return $hidden . self::radioButtonList($name,$selection,$data,$htmlOptions); + } + + /** + * Displays a summary of validation errors for one or several models. + * @param mixed $model the models whose input errors are to be displayed. This can be either + * a single model or an array of models. + * @param string $header a piece of HTML code that appears in front of the errors + * @param string $footer a piece of HTML code that appears at the end of the errors + * @param array $htmlOptions additional HTML attributes to be rendered in the container div tag. + * A special option named 'firstError' is recognized, which when set true, will + * make the error summary to show only the first error message of each attribute. + * If this is not set or is false, all error messages will be displayed. + * This option has been available since version 1.1.3. + * @return string the error summary. Empty if no errors are found. + * @see CModel::getErrors + * @see errorSummaryCss + */ + public static function errorSummary($model,$header=null,$footer=null,$htmlOptions=array()) + { + $content=''; + if(!is_array($model)) + $model=array($model); + if(isset($htmlOptions['firstError'])) + { + $firstError=$htmlOptions['firstError']; + unset($htmlOptions['firstError']); + } + else + $firstError=false; + foreach($model as $m) + { + foreach($m->getErrors() as $errors) + { + foreach($errors as $error) + { + if($error!='') + $content.="<li>$error</li>\n"; + if($firstError) + break; + } + } + } + if($content!=='') + { + if($header===null) + $header='<p>'.Yii::t('yii','Please fix the following input errors:').'</p>'; + if(!isset($htmlOptions['class'])) + $htmlOptions['class']=self::$errorSummaryCss; + return self::tag('div',$htmlOptions,$header."\n<ul>\n$content</ul>".$footer); + } + else + return ''; + } + + /** + * Displays the first validation error for a model attribute. + * @param CModel $model the data model + * @param string $attribute the attribute name + * @param array $htmlOptions additional HTML attributes to be rendered in the container div tag. + * @return string the error display. Empty if no errors are found. + * @see CModel::getErrors + * @see errorMessageCss + */ + public static function error($model,$attribute,$htmlOptions=array()) + { + self::resolveName($model,$attribute); // turn [a][b]attr into attr + $error=$model->getError($attribute); + if($error!='') + { + if(!isset($htmlOptions['class'])) + $htmlOptions['class']=self::$errorMessageCss; + return self::tag('div',$htmlOptions,$error); + } + else + return ''; + } + + /** + * Generates the data suitable for list-based HTML elements. + * The generated data can be used in {@link dropDownList}, {@link listBox}, {@link checkBoxList}, + * {@link radioButtonList}, and their active-versions (such as {@link activeDropDownList}). + * Note, this method does not HTML-encode the generated data. You may call {@link encodeArray} to + * encode it if needed. + * Please refer to the {@link value} method on how to specify value field, text field and group field. + * @param array $models a list of model objects. This parameter + * can also be an array of associative arrays (e.g. results of {@link CDbCommand::queryAll}). + * @param string $valueField the attribute name for list option values + * @param string $textField the attribute name for list option texts + * @param string $groupField the attribute name for list option group names. If empty, no group will be generated. + * @return array the list data that can be used in {@link dropDownList}, {@link listBox}, etc. + */ + public static function listData($models,$valueField,$textField,$groupField='') + { + $listData=array(); + if($groupField==='') + { + foreach($models as $model) + { + $value=self::value($model,$valueField); + $text=self::value($model,$textField); + $listData[$value]=$text; + } + } + else + { + foreach($models as $model) + { + $group=self::value($model,$groupField); + $value=self::value($model,$valueField); + $text=self::value($model,$textField); + $listData[$group][$value]=$text; + } + } + return $listData; + } + + /** + * Evaluates the value of the specified attribute for the given model. + * The attribute name can be given in a dot syntax. For example, if the attribute + * is "author.firstName", this method will return the value of "$model->author->firstName". + * A default value (passed as the last parameter) will be returned if the attribute does + * not exist or is broken in the middle (e.g. $model->author is null). + * The model can be either an object or an array. If the latter, the attribute is treated + * as a key of the array. For the example of "author.firstName", if would mean the array value + * "$model['author']['firstName']". + * @param mixed $model the model. This can be either an object or an array. + * @param string $attribute the attribute name (use dot to concatenate multiple attributes) + * @param mixed $defaultValue the default value to return when the attribute does not exist + * @return mixed the attribute value + */ + public static function value($model,$attribute,$defaultValue=null) + { + foreach(explode('.',$attribute) as $name) + { + if(is_object($model)) + $model=$model->$name; + else if(is_array($model) && isset($model[$name])) + $model=$model[$name]; + else + return $defaultValue; + } + return $model; + } + + /** + * Generates a valid HTML ID based on name. + * @param string $name name from which to generate HTML ID + * @return string the ID generated based on name. + */ + public static function getIdByName($name) + { + return str_replace(array('[]', '][', '[', ']'), array('', '_', '_', ''), $name); + } + + /** + * Generates input field ID for a model attribute. + * @param CModel $model the data model + * @param string $attribute the attribute + * @return string the generated input field ID + */ + public static function activeId($model,$attribute) + { + return self::getIdByName(self::activeName($model,$attribute)); + } + + /** + * Generates input field name for a model attribute. + * Unlike {@link resolveName}, this method does NOT modify the attribute name. + * @param CModel $model the data model + * @param string $attribute the attribute + * @return string the generated input field name + */ + public static function activeName($model,$attribute) + { + $a=$attribute; // because the attribute name may be changed by resolveName + return self::resolveName($model,$a); + } + + /** + * Generates an input HTML tag for a model attribute. + * This method generates an input HTML tag based on the given data model and attribute. + * If the attribute has input error, the input field's CSS class will + * be appended with {@link errorCss}. + * This enables highlighting the incorrect input. + * @param string $type the input type (e.g. 'text', 'radio') + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes for the HTML tag + * @return string the generated input tag + */ + protected static function activeInputField($type,$model,$attribute,$htmlOptions) + { + $htmlOptions['type']=$type; + if($type==='text' || $type==='password') + { + if(!isset($htmlOptions['maxlength'])) + { + foreach($model->getValidators($attribute) as $validator) + { + if($validator instanceof CStringValidator && $validator->max!==null) + { + $htmlOptions['maxlength']=$validator->max; + break; + } + } + } + else if($htmlOptions['maxlength']===false) + unset($htmlOptions['maxlength']); + } + + if($type==='file') + unset($htmlOptions['value']); + else if(!isset($htmlOptions['value'])) + $htmlOptions['value']=self::resolveValue($model,$attribute); + if($model->hasErrors($attribute)) + self::addErrorCss($htmlOptions); + return self::tag('input',$htmlOptions); + } + + /** + * Generates the list options. + * @param mixed $selection the selected value(s). This can be either a string for single selection or an array for multiple selections. + * @param array $listData the option data (see {@link listData}) + * @param array $htmlOptions additional HTML attributes. The following two special attributes are recognized: + * <ul> + * <li>encode: boolean, specifies whether to encode the values. Defaults to true.</li> + * <li>prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.</li> + * <li>empty: string, specifies the text corresponding to empty selection. Its value is empty. + * The 'empty' option can also be an array of value-label pairs. + * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.</li> + * <li>options: array, specifies additional attributes for each OPTION tag. + * The array keys must be the option values, and the array values are the extra + * OPTION tag attributes in the name-value pairs. For example, + * <pre> + * array( + * 'value1'=>array('disabled'=>true, 'label'=>'value 1'), + * 'value2'=>array('label'=>'value 2'), + * ); + * </pre> + * </li> + * <li>key: string, specifies the name of key attribute of the selection object(s). + * This is used when the selection is represented in terms of objects. In this case, + * the property named by the key option of the objects will be treated as the actual selection value. + * This option defaults to 'primaryKey', meaning using the 'primaryKey' property value of the objects in the selection. + * This option has been available since version 1.1.3.</li> + * </ul> + * @return string the generated list options + */ + public static function listOptions($selection,$listData,&$htmlOptions) + { + $raw=isset($htmlOptions['encode']) && !$htmlOptions['encode']; + $content=''; + if(isset($htmlOptions['prompt'])) + { + $content.='<option value="">'.strtr($htmlOptions['prompt'],array('<'=>'<', '>'=>'>'))."</option>\n"; + unset($htmlOptions['prompt']); + } + if(isset($htmlOptions['empty'])) + { + if(!is_array($htmlOptions['empty'])) + $htmlOptions['empty']=array(''=>$htmlOptions['empty']); + foreach($htmlOptions['empty'] as $value=>$label) + $content.='<option value="'.self::encode($value).'">'.strtr($label,array('<'=>'<', '>'=>'>'))."</option>\n"; + unset($htmlOptions['empty']); + } + + if(isset($htmlOptions['options'])) + { + $options=$htmlOptions['options']; + unset($htmlOptions['options']); + } + else + $options=array(); + + $key=isset($htmlOptions['key']) ? $htmlOptions['key'] : 'primaryKey'; + if(is_array($selection)) + { + foreach($selection as $i=>$item) + { + if(is_object($item)) + $selection[$i]=$item->$key; + } + } + else if(is_object($selection)) + $selection=$selection->$key; + + foreach($listData as $key=>$value) + { + if(is_array($value)) + { + $content.='<optgroup label="'.($raw?$key : self::encode($key))."\">\n"; + $dummy=array('options'=>$options); + if(isset($htmlOptions['encode'])) + $dummy['encode']=$htmlOptions['encode']; + $content.=self::listOptions($selection,$value,$dummy); + $content.='</optgroup>'."\n"; + } + else + { + $attributes=array('value'=>(string)$key, 'encode'=>!$raw); + if(!is_array($selection) && !strcmp($key,$selection) || is_array($selection) && in_array($key,$selection)) + $attributes['selected']='selected'; + if(isset($options[$key])) + $attributes=array_merge($attributes,$options[$key]); + $content.=self::tag('option',$attributes,$raw?(string)$value : self::encode((string)$value))."\n"; + } + } + + unset($htmlOptions['key']); + + return $content; + } + + /** + * Generates the JavaScript with the specified client changes. + * @param string $event event name (without 'on') + * @param array $htmlOptions HTML attributes which may contain the following special attributes + * specifying the client change behaviors: + * <ul> + * <li>submit: string, specifies the URL that the button should submit to. If empty, the current requested URL will be used.</li> + * <li>params: array, name-value pairs that should be submitted together with the form. This is only used when 'submit' option is specified.</li> + * <li>csrf: boolean, whether a CSRF token should be submitted when {@link CHttpRequest::enableCsrfValidation} is true. Defaults to false. + * You may want to set this to be true if there is no enclosing form around this element. + * This option is meaningful only when 'submit' option is set.</li> + * <li>return: boolean, the return value of the javascript. Defaults to false, meaning that the execution of + * javascript would not cause the default behavior of the event.</li> + * <li>confirm: string, specifies the message that should show in a pop-up confirmation dialog.</li> + * <li>ajax: array, specifies the AJAX options (see {@link ajax}).</li> + * <li>live: boolean, whether the event handler should be attached with live/delegate or direct style. If not set, {@link liveEvents} will be used. This option has been available since version 1.1.6.</li> + * </ul> + * This parameter has been available since version 1.1.1. + */ + protected static function clientChange($event,&$htmlOptions) + { + if(!isset($htmlOptions['submit']) && !isset($htmlOptions['confirm']) && !isset($htmlOptions['ajax'])) + return; + + if(isset($htmlOptions['live'])) + { + $live=$htmlOptions['live']; + unset($htmlOptions['live']); + } + else + $live = self::$liveEvents; + + if(isset($htmlOptions['return']) && $htmlOptions['return']) + $return='return true'; + else + $return='return false'; + + if(isset($htmlOptions['on'.$event])) + { + $handler=trim($htmlOptions['on'.$event],';').';'; + unset($htmlOptions['on'.$event]); + } + else + $handler=''; + + if(isset($htmlOptions['id'])) + $id=$htmlOptions['id']; + else + $id=$htmlOptions['id']=isset($htmlOptions['name'])?$htmlOptions['name']:self::ID_PREFIX.self::$count++; + + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('jquery'); + + if(isset($htmlOptions['submit'])) + { + $cs->registerCoreScript('yii'); + $request=Yii::app()->getRequest(); + if($request->enableCsrfValidation && isset($htmlOptions['csrf']) && $htmlOptions['csrf']) + $htmlOptions['params'][$request->csrfTokenName]=$request->getCsrfToken(); + if(isset($htmlOptions['params'])) + $params=CJavaScript::encode($htmlOptions['params']); + else + $params='{}'; + if($htmlOptions['submit']!=='') + $url=CJavaScript::quote(self::normalizeUrl($htmlOptions['submit'])); + else + $url=''; + $handler.="jQuery.yii.submitForm(this,'$url',$params);{$return};"; + } + + if(isset($htmlOptions['ajax'])) + $handler.=self::ajax($htmlOptions['ajax'])."{$return};"; + + if(isset($htmlOptions['confirm'])) + { + $confirm='confirm(\''.CJavaScript::quote($htmlOptions['confirm']).'\')'; + if($handler!=='') + $handler="if($confirm) {".$handler."} else return false;"; + else + $handler="return $confirm;"; + } + + if($live) + $cs->registerScript('Yii.CHtml.#' . $id, "$('body').on('$event','#$id',function(){{$handler}});"); + else + $cs->registerScript('Yii.CHtml.#' . $id, "$('#$id').on('$event', function(){{$handler}});"); + unset($htmlOptions['params'],$htmlOptions['submit'],$htmlOptions['ajax'],$htmlOptions['confirm'],$htmlOptions['return'],$htmlOptions['csrf']); + } + + /** + * Generates input name and ID for a model attribute. + * This method will update the HTML options by setting appropriate 'name' and 'id' attributes. + * This method may also modify the attribute name if the name + * contains square brackets (mainly used in tabular input). + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions the HTML options + */ + public static function resolveNameID($model,&$attribute,&$htmlOptions) + { + if(!isset($htmlOptions['name'])) + $htmlOptions['name']=self::resolveName($model,$attribute); + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=self::getIdByName($htmlOptions['name']); + else if($htmlOptions['id']===false) + unset($htmlOptions['id']); + } + + /** + * Generates input name for a model attribute. + * Note, the attribute name may be modified after calling this method if the name + * contains square brackets (mainly used in tabular input) before the real attribute name. + * @param CModel $model the data model + * @param string $attribute the attribute + * @return string the input name + */ + public static function resolveName($model,&$attribute) + { + if(($pos=strpos($attribute,'['))!==false) + { + if($pos!==0) // e.g. name[a][b] + return get_class($model).'['.substr($attribute,0,$pos).']'.substr($attribute,$pos); + if(($pos=strrpos($attribute,']'))!==false && $pos!==strlen($attribute)-1) // e.g. [a][b]name + { + $sub=substr($attribute,0,$pos+1); + $attribute=substr($attribute,$pos+1); + return get_class($model).$sub.'['.$attribute.']'; + } + if(preg_match('/\](\w+\[.*)$/',$attribute,$matches)) + { + $name=get_class($model).'['.str_replace(']','][',trim(strtr($attribute,array(']['=>']','['=>']')),']')).']'; + $attribute=$matches[1]; + return $name; + } + } + return get_class($model).'['.$attribute.']'; + } + + /** + * Evaluates the attribute value of the model. + * This method can recognize the attribute name written in array format. + * For example, if the attribute name is 'name[a][b]', the value "$model->name['a']['b']" will be returned. + * @param CModel $model the data model + * @param string $attribute the attribute name + * @return mixed the attribute value + * @since 1.1.3 + */ + public static function resolveValue($model,$attribute) + { + if(($pos=strpos($attribute,'['))!==false) + { + if($pos===0) // [a]name[b][c], should ignore [a] + { + if(preg_match('/\](\w+)/',$attribute,$matches)) + $attribute=$matches[1]; + if(($pos=strpos($attribute,'['))===false) + return $model->$attribute; + } + $name=substr($attribute,0,$pos); + $value=$model->$name; + foreach(explode('][',rtrim(substr($attribute,$pos+1),']')) as $id) + { + if(is_array($value) && isset($value[$id])) + $value=$value[$id]; + else + return null; + } + return $value; + } + else + return $model->$attribute; + } + + /** + * Appends {@link errorCss} to the 'class' attribute. + * @param array $htmlOptions HTML options to be modified + */ + protected static function addErrorCss(&$htmlOptions) + { + if(isset($htmlOptions['class'])) + $htmlOptions['class'].=' '.self::$errorCss; + else + $htmlOptions['class']=self::$errorCss; + } + + /** + * Renders the HTML tag attributes. + * Since version 1.1.5, attributes whose value is null will not be rendered. + * Special attributes, such as 'checked', 'disabled', 'readonly', will be rendered + * properly based on their corresponding boolean value. + * @param array $htmlOptions attributes to be rendered + * @return string the rendering result + */ + public static function renderAttributes($htmlOptions) + { + static $specialAttributes=array( + 'checked'=>1, + 'declare'=>1, + 'defer'=>1, + 'disabled'=>1, + 'ismap'=>1, + 'multiple'=>1, + 'nohref'=>1, + 'noresize'=>1, + 'readonly'=>1, + 'selected'=>1, + ); + + if($htmlOptions===array()) + return ''; + + $html=''; + if(isset($htmlOptions['encode'])) + { + $raw=!$htmlOptions['encode']; + unset($htmlOptions['encode']); + } + else + $raw=false; + + if($raw) + { + foreach($htmlOptions as $name=>$value) + { + if(isset($specialAttributes[$name])) + { + if($value) + $html .= ' ' . $name . '="' . $name . '"'; + } + else if($value!==null) + $html .= ' ' . $name . '="' . $value . '"'; + } + } + else + { + foreach($htmlOptions as $name=>$value) + { + if(isset($specialAttributes[$name])) + { + if($value) + $html .= ' ' . $name . '="' . $name . '"'; + } + else if($value!==null) + $html .= ' ' . $name . '="' . self::encode($value) . '"'; + } + } + return $html; + } +} diff --git a/framework/web/helpers/CJSON.php b/framework/web/helpers/CJSON.php new file mode 100644 index 0000000..f329453 --- /dev/null +++ b/framework/web/helpers/CJSON.php @@ -0,0 +1,704 @@ +<?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> + * @version $Id: CJSON.php 3204 2011-05-05 21:36:32Z alexander.makarow $ + * @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')) + return json_decode($str,$useArray); + + $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(); + } + } + + array_push($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'])); + array_push($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 + array_push($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 + array_push($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 + array_push($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 + array_push($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 + array_push($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); + } +} diff --git a/framework/web/helpers/CJavaScript.php b/framework/web/helpers/CJavaScript.php new file mode 100644 index 0000000..7454f17 --- /dev/null +++ b/framework/web/helpers/CJavaScript.php @@ -0,0 +1,120 @@ +<?php +/** + * CJavaScript helper class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CJavaScript is a helper class containing JavaScript-related handling functions. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CJavaScript.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @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. + * + * @param mixed $value PHP variable to be encoded + * @return string the encoded string + */ + public static function encode($value) + { + if(is_string($value)) + { + if(strpos($value,'js:')===0) + return substr($value,3); + else + return "'".self::quote($value)."'"; + } + else if($value===null) + return 'null'; + else if(is_bool($value)) + return $value?'true':'false'; + else if(is_integer($value)) + return "$value"; + else if(is_float($value)) + { + if($value===-INF) + return 'Number.NEGATIVE_INFINITY'; + else if($value===INF) + return 'Number.POSITIVE_INFINITY'; + else + return rtrim(sprintf('%.16F',$value),'0'); // locale-independent representation + } + else if(is_object($value)) + return self::encode(get_object_vars($value)); + else if(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); + return '{'.implode(',',$es).'}'; + } + else + { + foreach($value as $v) + $es[]=self::encode($v); + 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); + } +} diff --git a/framework/web/js/packages.php b/framework/web/js/packages.php new file mode 100644 index 0000000..85156d7 --- /dev/null +++ b/framework/web/js/packages.php @@ -0,0 +1,74 @@ +<?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 Copyright © 2008-2011 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('jquery.ba-bbq.js'), + 'depends'=>array('jquery'), + ), +); diff --git a/framework/web/js/source/autocomplete/indicator.gif b/framework/web/js/source/autocomplete/indicator.gif Binary files differnew file mode 100644 index 0000000..085ccae --- /dev/null +++ b/framework/web/js/source/autocomplete/indicator.gif diff --git a/framework/web/js/source/autocomplete/jquery.autocomplete.css b/framework/web/js/source/autocomplete/jquery.autocomplete.css new file mode 100644 index 0000000..91b6228 --- /dev/null +++ b/framework/web/js/source/autocomplete/jquery.autocomplete.css @@ -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; +} diff --git a/framework/web/js/source/jquery.ajaxqueue.js b/framework/web/js/source/jquery.ajaxqueue.js new file mode 100644 index 0000000..ca42082 --- /dev/null +++ b/framework/web/js/source/jquery.ajaxqueue.js @@ -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);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.autocomplete.js b/framework/web/js/source/jquery.autocomplete.js new file mode 100644 index 0000000..324b104 --- /dev/null +++ b/framework/web/js/source/jquery.autocomplete.js @@ -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);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.ba-bbq.js b/framework/web/js/source/jquery.ba-bbq.js new file mode 100644 index 0000000..7ac71fc --- /dev/null +++ b/framework/web/js/source/jquery.ba-bbq.js @@ -0,0 +1,1137 @@ +/*! + * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/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/ + */ + +// Script: jQuery BBQ: Back Button & Query Library +// +// *Version: 1.2.1, Last updated: 2/17/2010* +// +// Project Home - http://benalman.com/projects/jquery-bbq-plugin/ +// GitHub - http://github.com/cowboy/jquery-bbq/ +// Source - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js +// (Minified) - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (4.0kb) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// These working examples, complete with fully commented code, illustrate a few +// ways in which this plugin can be used. +// +// Basic AJAX - http://benalman.com/code/projects/jquery-bbq/examples/fragment-basic/ +// Advanced AJAX - http://benalman.com/code/projects/jquery-bbq/examples/fragment-advanced/ +// jQuery UI Tabs - http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/ +// Deparam - http://benalman.com/code/projects/jquery-bbq/examples/deparam/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, +// Chrome 4-5, Opera 9.6-10.1. +// Unit Tests - http://benalman.com/code/projects/jquery-bbq/unit/ +// +// About: Release History +// +// 1.2.1 - (2/17/2010) Actually fixed the stale window.location Safari bug from +// <jQuery hashchange event> in BBQ, which was the main reason for the +// previous release! +// 1.2 - (2/16/2010) Integrated <jQuery hashchange event> v1.2, which fixes a +// Safari bug, the event can now be bound before DOM ready, and IE6/7 +// page should no longer scroll when the event is first bound. Also +// added the <jQuery.param.fragment.noEscape> method, and reworked the +// <hashchange event (BBQ)> internal "add" method to be compatible with +// changes made to the jQuery 1.4.2 special events API. +// 1.1.1 - (1/22/2010) Integrated <jQuery hashchange event> v1.1, which fixes an +// obscure IE8 EmulateIE7 meta tag compatibility mode bug. +// 1.1 - (1/9/2010) Broke out the jQuery BBQ event.special <hashchange event> +// functionality into a separate plugin for users who want just the +// basic event & back button support, without all the extra awesomeness +// that BBQ provides. This plugin will be included as part of jQuery BBQ, +// but also be available separately. See <jQuery hashchange event> +// plugin for more information. Also added the <jQuery.bbq.removeState> +// method and added additional <jQuery.deparam> examples. +// 1.0.3 - (12/2/2009) Fixed an issue in IE 6 where location.search and +// location.hash would report incorrectly if the hash contained the ? +// character. Also <jQuery.param.querystring> and <jQuery.param.fragment> +// will no longer parse params out of a URL that doesn't contain ? or #, +// respectively. +// 1.0.2 - (10/10/2009) Fixed an issue in IE 6/7 where the hidden IFRAME caused +// a "This page contains both secure and nonsecure items." warning when +// used on an https:// page. +// 1.0.1 - (10/7/2009) Fixed an issue in IE 8. Since both "IE7" and "IE8 +// Compatibility View" modes erroneously report that the browser +// supports the native window.onhashchange event, a slightly more +// robust test needed to be added. +// 1.0 - (10/2/2009) Initial release + +(function($,window){ + '$:nomunge'; // Used by YUI compressor. + + // Some convenient shortcuts. + var undefined, + aps = Array.prototype.slice, + decode = decodeURIComponent, + + // Method / object references. + jq_param = $.param, + jq_param_fragment, + jq_deparam, + jq_deparam_fragment, + jq_bbq = $.bbq = $.bbq || {}, + jq_bbq_pushState, + jq_bbq_getState, + jq_elemUrlAttr, + jq_event_special = $.event.special, + + // Reused strings. + str_hashchange = 'hashchange', + str_querystring = 'querystring', + str_fragment = 'fragment', + str_elemUrlAttr = 'elemUrlAttr', + str_location = 'location', + str_href = 'href', + str_src = 'src', + + // Reused RegExp. + re_trim_querystring = /^.*\?|#.*$/g, + re_trim_fragment = /^.*\#/, + re_no_escape, + + // Used by jQuery.elemUrlAttr. + elemUrlAttr_cache = {}; + + // A few commonly used bits, broken out to help reduce minified file size. + + function is_string( arg ) { + return typeof arg === 'string'; + }; + + // Why write the same function twice? Let's curry! Mmmm, curry.. + + function curry( func ) { + var args = aps.call( arguments, 1 ); + + return function() { + return func.apply( this, args.concat( aps.call( arguments ) ) ); + }; + }; + + // Get location.hash (or what you'd expect location.hash to be) sans any + // leading #. Thanks for making this necessary, Firefox! + function get_fragment( url ) { + return url.replace( /^[^#]*#?(.*)$/, '$1' ); + }; + + // Get location.search (or what you'd expect location.search to be) sans any + // leading #. Thanks for making this necessary, IE6! + function get_querystring( url ) { + return url.replace( /(?:^[^?#]*\?([^#]*).*$)?.*/, '$1' ); + }; + + // Section: Param (to string) + // + // Method: jQuery.param.querystring + // + // Retrieve the query string from a URL or if no arguments are passed, the + // current window.location. + // + // Usage: + // + // > jQuery.param.querystring( [ url ] ); + // + // Arguments: + // + // url - (String) A URL containing query string params to be parsed. If url + // is not passed, the current window.location is used. + // + // Returns: + // + // (String) The parsed query string, with any leading "?" removed. + // + + // Method: jQuery.param.querystring (build url) + // + // Merge a URL, with or without pre-existing query string params, plus any + // object, params string or URL containing query string params into a new URL. + // + // Usage: + // + // > jQuery.param.querystring( url, params [, merge_mode ] ); + // + // Arguments: + // + // url - (String) A valid URL for params to be merged into. This URL may + // contain a query string and/or fragment (hash). + // params - (String) A params string or URL containing query string params to + // be merged into url. + // params - (Object) A params object to be merged into url. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified, and is as-follows: + // + // * 0: params in the params argument will override any query string + // params in url. + // * 1: any query string params in url will override params in the params + // argument. + // * 2: params argument will completely replace any query string in url. + // + // Returns: + // + // (String) Either a params string with urlencoded data or a URL with a + // urlencoded query string in the format 'a=b&c=d&e=f'. + + // Method: jQuery.param.fragment + // + // Retrieve the fragment (hash) from a URL or if no arguments are passed, the + // current window.location. + // + // Usage: + // + // > jQuery.param.fragment( [ url ] ); + // + // Arguments: + // + // url - (String) A URL containing fragment (hash) params to be parsed. If + // url is not passed, the current window.location is used. + // + // Returns: + // + // (String) The parsed fragment (hash) string, with any leading "#" removed. + + // Method: jQuery.param.fragment (build url) + // + // Merge a URL, with or without pre-existing fragment (hash) params, plus any + // object, params string or URL containing fragment (hash) params into a new + // URL. + // + // Usage: + // + // > jQuery.param.fragment( url, params [, merge_mode ] ); + // + // Arguments: + // + // url - (String) A valid URL for params to be merged into. This URL may + // contain a query string and/or fragment (hash). + // params - (String) A params string or URL containing fragment (hash) params + // to be merged into url. + // params - (Object) A params object to be merged into url. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified, and is as-follows: + // + // * 0: params in the params argument will override any fragment (hash) + // params in url. + // * 1: any fragment (hash) params in url will override params in the + // params argument. + // * 2: params argument will completely replace any query string in url. + // + // Returns: + // + // (String) Either a params string with urlencoded data or a URL with a + // urlencoded fragment (hash) in the format 'a=b&c=d&e=f'. + + function jq_param_sub( is_fragment, get_func, url, params, merge_mode ) { + var result, + qs, + matches, + url_params, + hash; + + if ( params !== undefined ) { + // Build URL by merging params into url string. + + // matches[1] = url part that precedes params, not including trailing ?/# + // matches[2] = params, not including leading ?/# + // matches[3] = if in 'querystring' mode, hash including leading #, otherwise '' + matches = url.match( is_fragment ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/ ); + + // Get the hash if in 'querystring' mode, and it exists. + hash = matches[3] || ''; + + if ( merge_mode === 2 && is_string( params ) ) { + // If merge_mode is 2 and params is a string, merge the fragment / query + // string into the URL wholesale, without converting it into an object. + qs = params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' ); + + } else { + // Convert relevant params in url to object. + url_params = jq_deparam( matches[2] ); + + params = is_string( params ) + + // Convert passed params string into object. + ? jq_deparam[ is_fragment ? str_fragment : str_querystring ]( params ) + + // Passed params object. + : params; + + qs = merge_mode === 2 ? params // passed params replace url params + : merge_mode === 1 ? $.extend( {}, params, url_params ) // url params override passed params + : $.extend( {}, url_params, params ); // passed params override url params + + // Convert params object to a string. + qs = jq_param( qs ); + + // Unescape characters specified via $.param.noEscape. Since only hash- + // history users have requested this feature, it's only enabled for + // fragment-related params strings. + if ( is_fragment ) { + qs = qs.replace( re_no_escape, decode ); + } + } + + // Build URL from the base url, querystring and hash. In 'querystring' + // mode, ? is only added if a query string exists. In 'fragment' mode, # + // is always added. + result = matches[1] + ( is_fragment ? '#' : qs || !matches[1] ? '?' : '' ) + qs + hash; + + } else { + // If URL was passed in, parse params from URL string, otherwise parse + // params from window.location. + result = get_func( url !== undefined ? url : window[ str_location ][ str_href ] ); + } + + return result; + }; + + jq_param[ str_querystring ] = curry( jq_param_sub, 0, get_querystring ); + jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, get_fragment ); + + // Method: jQuery.param.fragment.noEscape + // + // Specify characters that will be left unescaped when fragments are created + // or merged using <jQuery.param.fragment>, or when the fragment is modified + // using <jQuery.bbq.pushState>. This option only applies to serialized data + // object fragments, and not set-as-string fragments. Does not affect the + // query string. Defaults to ",/" (comma, forward slash). + // + // Note that this is considered a purely aesthetic option, and will help to + // create URLs that "look pretty" in the address bar or bookmarks, without + // affecting functionality in any way. That being said, be careful to not + // unescape characters that are used as delimiters or serve a special + // purpose, such as the "#?&=+" (octothorpe, question mark, ampersand, + // equals, plus) characters. + // + // Usage: + // + // > jQuery.param.fragment.noEscape( [ chars ] ); + // + // Arguments: + // + // chars - (String) The characters to not escape in the fragment. If + // unspecified, defaults to empty string (escape all characters). + // + // Returns: + // + // Nothing. + + jq_param_fragment.noEscape = function( chars ) { + chars = chars || ''; + var arr = $.map( chars.split(''), encodeURIComponent ); + re_no_escape = new RegExp( arr.join('|'), 'g' ); + }; + + // A sensible default. These are the characters people seem to complain about + // "uglifying up the URL" the most. + jq_param_fragment.noEscape( ',/' ); + + // Section: Deparam (from string) + // + // Method: jQuery.deparam + // + // Deserialize a params string into an object, optionally coercing numbers, + // booleans, null and undefined values; this method is the counterpart to the + // internal jQuery.param method. + // + // Usage: + // + // > jQuery.deparam( params [, coerce ] ); + // + // Arguments: + // + // params - (String) A params string to be parsed. + // coerce - (Boolean) If true, coerces any numbers or true, false, null, and + // undefined to their actual value. Defaults to false if omitted. + // + // Returns: + // + // (Object) An object representing the deserialized params string. + + $.deparam = jq_deparam = function( params, coerce ) { + var obj = {}, + coerce_types = { 'true': !0, 'false': !1, 'null': null }; + + // Iterate over all name=value pairs. + $.each( params.replace( /\+/g, ' ' ).split( '&' ), function(j,v){ + var param = v.split( '=' ), + key = decode( param[0] ), + val, + cur = obj, + i = 0, + + // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it + // into its component parts. + keys = key.split( '][' ), + keys_last = keys.length - 1; + + // If the first keys part contains [ and the last ends with ], then [] + // are correctly balanced. + if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) { + // Remove the trailing ] from the last keys part. + keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' ); + + // Split first keys part into two parts on the [ and add them back onto + // the beginning of the keys array. + keys = keys.shift().split('[').concat( keys ); + + keys_last = keys.length - 1; + } else { + // Basic 'foo' style key. + keys_last = 0; + } + + // Are we dealing with a name=value pair, or just a name? + if ( param.length === 2 ) { + val = decode( param[1] ); + + // Coerce values. + if ( coerce ) { + val = val && !isNaN(val) ? +val // number + : val === 'undefined' ? undefined // undefined + : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null + : val; // string + } + + if ( keys_last ) { + // Complex key, build deep object structure based on a few rules: + // * The 'cur' pointer starts at the object top-level. + // * [] = array push (n is set to array length), [n] = array if n is + // numeric, otherwise object. + // * If at the last keys part, set the value. + // * For each keys part, if the current level is undefined create an + // object or array based on the type of the next keys part. + // * Move the 'cur' pointer to the next level. + // * Rinse & repeat. + for ( ; i <= keys_last; i++ ) { + key = keys[i] === '' ? cur.length : keys[i]; + cur = cur[key] = i < keys_last + ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] ) + : val; + } + + } else { + // Simple key, even simpler rules, since only scalars and shallow + // arrays are allowed. + + if ( $.isArray( obj[key] ) ) { + // val is already an array, so push on the next value. + obj[key].push( val ); + + } else if ( obj[key] !== undefined ) { + // val isn't an array, but since a second value has been specified, + // convert val into an array. + obj[key] = [ obj[key], val ]; + + } else { + // val is a scalar. + obj[key] = val; + } + } + + } else if ( key ) { + // No value was defined, so set something meaningful. + obj[key] = coerce + ? undefined + : ''; + } + }); + + return obj; + }; + + // Method: jQuery.deparam.querystring + // + // Parse the query string from a URL or the current window.location, + // deserializing it into an object, optionally coercing numbers, booleans, + // null and undefined values. + // + // Usage: + // + // > jQuery.deparam.querystring( [ url ] [, coerce ] ); + // + // Arguments: + // + // url - (String) An optional params string or URL containing query string + // params to be parsed. If url is omitted, the current window.location + // is used. + // coerce - (Boolean) If true, coerces any numbers or true, false, null, and + // undefined to their actual value. Defaults to false if omitted. + // + // Returns: + // + // (Object) An object representing the deserialized params string. + + // Method: jQuery.deparam.fragment + // + // Parse the fragment (hash) from a URL or the current window.location, + // deserializing it into an object, optionally coercing numbers, booleans, + // null and undefined values. + // + // Usage: + // + // > jQuery.deparam.fragment( [ url ] [, coerce ] ); + // + // Arguments: + // + // url - (String) An optional params string or URL containing fragment (hash) + // params to be parsed. If url is omitted, the current window.location + // is used. + // coerce - (Boolean) If true, coerces any numbers or true, false, null, and + // undefined to their actual value. Defaults to false if omitted. + // + // Returns: + // + // (Object) An object representing the deserialized params string. + + function jq_deparam_sub( is_fragment, url_or_params, coerce ) { + if ( url_or_params === undefined || typeof url_or_params === 'boolean' ) { + // url_or_params not specified. + coerce = url_or_params; + url_or_params = jq_param[ is_fragment ? str_fragment : str_querystring ](); + } else { + url_or_params = is_string( url_or_params ) + ? url_or_params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' ) + : url_or_params; + } + + return jq_deparam( url_or_params, coerce ); + }; + + jq_deparam[ str_querystring ] = curry( jq_deparam_sub, 0 ); + jq_deparam[ str_fragment ] = jq_deparam_fragment = curry( jq_deparam_sub, 1 ); + + // Section: Element manipulation + // + // Method: jQuery.elemUrlAttr + // + // Get the internal "Default URL attribute per tag" list, or augment the list + // with additional tag-attribute pairs, in case the defaults are insufficient. + // + // In the <jQuery.fn.querystring> and <jQuery.fn.fragment> methods, this list + // is used to determine which attribute contains the URL to be modified, if + // an "attr" param is not specified. + // + // Default Tag-Attribute List: + // + // a - href + // base - href + // iframe - src + // img - src + // input - src + // form - action + // link - href + // script - src + // + // Usage: + // + // > jQuery.elemUrlAttr( [ tag_attr ] ); + // + // Arguments: + // + // tag_attr - (Object) An object containing a list of tag names and their + // associated default attribute names in the format { tag: 'attr', ... } to + // be merged into the internal tag-attribute list. + // + // Returns: + // + // (Object) An object containing all stored tag-attribute values. + + // Only define function and set defaults if function doesn't already exist, as + // the urlInternal plugin will provide this method as well. + $[ str_elemUrlAttr ] || ($[ str_elemUrlAttr ] = function( obj ) { + return $.extend( elemUrlAttr_cache, obj ); + })({ + a: str_href, + base: str_href, + iframe: str_src, + img: str_src, + input: str_src, + form: 'action', + link: str_href, + script: str_src + }); + + jq_elemUrlAttr = $[ str_elemUrlAttr ]; + + // Method: jQuery.fn.querystring + // + // Update URL attribute in one or more elements, merging the current URL (with + // or without pre-existing query string params) plus any params object or + // string into a new URL, which is then set into that attribute. Like + // <jQuery.param.querystring (build url)>, but for all elements in a jQuery + // collection. + // + // Usage: + // + // > jQuery('selector').querystring( [ attr, ] params [, merge_mode ] ); + // + // Arguments: + // + // attr - (String) Optional name of an attribute that will contain a URL to + // merge params or url into. See <jQuery.elemUrlAttr> for a list of default + // attributes. + // params - (Object) A params object to be merged into the URL attribute. + // params - (String) A URL containing query string params, or params string + // to be merged into the URL attribute. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified, and is as-follows: + // + // * 0: params in the params argument will override any params in attr URL. + // * 1: any params in attr URL will override params in the params argument. + // * 2: params argument will completely replace any query string in attr + // URL. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements, but with modified URL + // attribute values. + + // Method: jQuery.fn.fragment + // + // Update URL attribute in one or more elements, merging the current URL (with + // or without pre-existing fragment/hash params) plus any params object or + // string into a new URL, which is then set into that attribute. Like + // <jQuery.param.fragment (build url)>, but for all elements in a jQuery + // collection. + // + // Usage: + // + // > jQuery('selector').fragment( [ attr, ] params [, merge_mode ] ); + // + // Arguments: + // + // attr - (String) Optional name of an attribute that will contain a URL to + // merge params into. See <jQuery.elemUrlAttr> for a list of default + // attributes. + // params - (Object) A params object to be merged into the URL attribute. + // params - (String) A URL containing fragment (hash) params, or params + // string to be merged into the URL attribute. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified, and is as-follows: + // + // * 0: params in the params argument will override any params in attr URL. + // * 1: any params in attr URL will override params in the params argument. + // * 2: params argument will completely replace any fragment (hash) in attr + // URL. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements, but with modified URL + // attribute values. + + function jq_fn_sub( mode, force_attr, params, merge_mode ) { + if ( !is_string( params ) && typeof params !== 'object' ) { + // force_attr not specified. + merge_mode = params; + params = force_attr; + force_attr = undefined; + } + + return this.each(function(){ + var that = $(this), + + // Get attribute specified, or default specified via $.elemUrlAttr. + attr = force_attr || jq_elemUrlAttr()[ ( this.nodeName || '' ).toLowerCase() ] || '', + + // Get URL value. + url = attr && that.attr( attr ) || ''; + + // Update attribute with new URL. + that.attr( attr, jq_param[ mode ]( url, params, merge_mode ) ); + }); + + }; + + $.fn[ str_querystring ] = curry( jq_fn_sub, str_querystring ); + $.fn[ str_fragment ] = curry( jq_fn_sub, str_fragment ); + + // Section: History, hashchange event + // + // Method: jQuery.bbq.pushState + // + // Adds a 'state' into the browser history at the current position, setting + // location.hash and triggering any bound <hashchange event> callbacks + // (provided the new state is different than the previous state). + // + // If no arguments are passed, an empty state is created, which is just a + // shortcut for jQuery.bbq.pushState( {}, 2 ). + // + // Usage: + // + // > jQuery.bbq.pushState( [ params [, merge_mode ] ] ); + // + // Arguments: + // + // params - (String) A serialized params string or a hash string beginning + // with # to merge into location.hash. + // params - (Object) A params object to merge into location.hash. + // merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not + // specified (unless a hash string beginning with # is specified, in which + // case merge behavior defaults to 2), and is as-follows: + // + // * 0: params in the params argument will override any params in the + // current state. + // * 1: any params in the current state will override params in the params + // argument. + // * 2: params argument will completely replace current state. + // + // Returns: + // + // Nothing. + // + // Additional Notes: + // + // * Setting an empty state may cause the browser to scroll. + // * Unlike the fragment and querystring methods, if a hash string beginning + // with # is specified as the params agrument, merge_mode defaults to 2. + + jq_bbq.pushState = jq_bbq_pushState = function( params, merge_mode ) { + if ( is_string( params ) && /^#/.test( params ) && merge_mode === undefined ) { + // Params string begins with # and merge_mode not specified, so completely + // overwrite window.location.hash. + merge_mode = 2; + } + + var has_args = params !== undefined, + // Merge params into window.location using $.param.fragment. + url = jq_param_fragment( window[ str_location ][ str_href ], + has_args ? params : {}, has_args ? merge_mode : 2 ); + + // Set new window.location.href. If hash is empty, use just # to prevent + // browser from reloading the page. Note that Safari 3 & Chrome barf on + // location.hash = '#'. + window[ str_location ][ str_href ] = url + ( /#/.test( url ) ? '' : '#' ); + }; + + // Method: jQuery.bbq.getState + // + // Retrieves the current 'state' from the browser history, parsing + // location.hash for a specific key or returning an object containing the + // entire state, optionally coercing numbers, booleans, null and undefined + // values. + // + // Usage: + // + // > jQuery.bbq.getState( [ key ] [, coerce ] ); + // + // Arguments: + // + // key - (String) An optional state key for which to return a value. + // coerce - (Boolean) If true, coerces any numbers or true, false, null, and + // undefined to their actual value. Defaults to false. + // + // Returns: + // + // (Anything) If key is passed, returns the value corresponding with that key + // in the location.hash 'state', or undefined. If not, an object + // representing the entire 'state' is returned. + + jq_bbq.getState = jq_bbq_getState = function( key, coerce ) { + return key === undefined || typeof key === 'boolean' + ? jq_deparam_fragment( key ) // 'key' really means 'coerce' here + : jq_deparam_fragment( coerce )[ key ]; + }; + + // Method: jQuery.bbq.removeState + // + // Remove one or more keys from the current browser history 'state', creating + // a new state, setting location.hash and triggering any bound + // <hashchange event> callbacks (provided the new state is different than + // the previous state). + // + // If no arguments are passed, an empty state is created, which is just a + // shortcut for jQuery.bbq.pushState( {}, 2 ). + // + // Usage: + // + // > jQuery.bbq.removeState( [ key [, key ... ] ] ); + // + // Arguments: + // + // key - (String) One or more key values to remove from the current state, + // passed as individual arguments. + // key - (Array) A single array argument that contains a list of key values + // to remove from the current state. + // + // Returns: + // + // Nothing. + // + // Additional Notes: + // + // * Setting an empty state may cause the browser to scroll. + + jq_bbq.removeState = function( arr ) { + var state = {}; + + // If one or more arguments is passed.. + if ( arr !== undefined ) { + + // Get the current state. + state = jq_bbq_getState(); + + // For each passed key, delete the corresponding property from the current + // state. + $.each( $.isArray( arr ) ? arr : arguments, function(i,v){ + delete state[ v ]; + }); + } + + // Set the state, completely overriding any existing state. + jq_bbq_pushState( state, 2 ); + }; + + // Event: hashchange event (BBQ) + // + // Usage in jQuery 1.4 and newer: + // + // In jQuery 1.4 and newer, the event object passed into any hashchange event + // callback is augmented with a copy of the location.hash fragment at the time + // the event was triggered as its event.fragment property. In addition, the + // event.getState method operates on this property (instead of location.hash) + // which allows this fragment-as-a-state to be referenced later, even after + // window.location may have changed. + // + // Note that event.fragment and event.getState are not defined according to + // W3C (or any other) specification, but will still be available whether or + // not the hashchange event exists natively in the browser, because of the + // utility they provide. + // + // The event.fragment property contains the output of <jQuery.param.fragment> + // and the event.getState method is equivalent to the <jQuery.bbq.getState> + // method. + // + // > $(window).bind( 'hashchange', function( event ) { + // > var hash_str = event.fragment, + // > param_obj = event.getState(), + // > param_val = event.getState( 'param_name' ), + // > param_val_coerced = event.getState( 'param_name', true ); + // > ... + // > }); + // + // Usage in jQuery 1.3.2: + // + // In jQuery 1.3.2, the event object cannot to be augmented as in jQuery 1.4+, + // so the fragment state isn't bound to the event object and must instead be + // parsed using the <jQuery.param.fragment> and <jQuery.bbq.getState> methods. + // + // > $(window).bind( 'hashchange', function( event ) { + // > var hash_str = $.param.fragment(), + // > param_obj = $.bbq.getState(), + // > param_val = $.bbq.getState( 'param_name' ), + // > param_val_coerced = $.bbq.getState( 'param_name', true ); + // > ... + // > }); + // + // Additional Notes: + // + // * Due to changes in the special events API, jQuery BBQ v1.2 or newer is + // required to enable the augmented event object in jQuery 1.4.2 and newer. + // * See <jQuery hashchange event> for more detailed information. + + jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], { + + // Augmenting the event object with the .fragment property and .getState + // method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will + // work, but the event won't be augmented) + add: function( handleObj ) { + var old_handler; + + function new_handler(e) { + // e.fragment is set to the value of location.hash (with any leading # + // removed) at the time the event is triggered. + var hash = e[ str_fragment ] = jq_param_fragment(); + + // e.getState() works just like $.bbq.getState(), but uses the + // e.fragment property stored on the event object. + e.getState = function( key, coerce ) { + return key === undefined || typeof key === 'boolean' + ? jq_deparam( hash, key ) // 'key' really means 'coerce' here + : jq_deparam( hash, coerce )[ key ]; + }; + + old_handler.apply( this, arguments ); + }; + + // This may seem a little complicated, but it normalizes the special event + // .add method between jQuery 1.4/1.4.1 and 1.4.2+ + if ( $.isFunction( handleObj ) ) { + // 1.4, 1.4.1 + old_handler = handleObj; + return new_handler; + } else { + // 1.4.2+ + old_handler = handleObj.handler; + handleObj.handler = new_handler; + } + } + + }); + +})(jQuery,this); + +/*! + * jQuery hashchange event - v1.2 - 2/11/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/ + */ + +// Script: jQuery hashchange event +// +// *Version: 1.2, Last updated: 2/11/2010* +// +// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ +// GitHub - http://github.com/cowboy/jquery-hashchange/ +// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js +// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (1.1kb) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// This working example, complete with fully commented code, illustrate one way +// in which this plugin can be used. +// +// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1. +// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ +// +// About: Known issues +// +// While this jQuery hashchange event implementation is quite stable and robust, +// there are a few unfortunate browser bugs surrounding expected hashchange +// event-based behaviors, independent of any JavaScript window.onhashchange +// abstraction. See the following examples for more information: +// +// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ +// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ +// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ +// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ +// +// About: Release History +// +// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin +// from a page on another domain would cause an error in Safari 4. Also, +// IE6/7 Iframe is now inserted after the body (this actually works), +// which prevents the page from scrolling when the event is first bound. +// Event can also now be bound before DOM ready, but it won't be usable +// before then in IE6/7. +// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug +// where browser version is incorrectly reported as 8.0, despite +// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. +// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special +// window.onhashchange functionality into a separate plugin for users +// who want just the basic event & back button support, without all the +// extra awesomeness that BBQ provides. This plugin will be included as +// part of jQuery BBQ, but also be available separately. + +(function($,window,undefined){ + '$:nomunge'; // Used by YUI compressor. + + // Method / object references. + var fake_onhashchange, + jq_event_special = $.event.special, + + // Reused strings. + str_location = 'location', + str_hashchange = 'hashchange', + str_href = 'href', + + // IE6/7 specifically need some special love when it comes to back-button + // support, so let's do a little browser sniffing.. + browser = $.browser, + mode = document.documentMode, + is_old_ie = browser.msie && ( mode === undefined || mode < 8 ), + + // Does the browser support window.onhashchange? Test for IE version, since + // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"! + supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie; + + // Get location.hash (or what you'd expect location.hash to be) sans any + // leading #. Thanks for making this necessary, Firefox! + function get_fragment( url ) { + url = url || window[ str_location ][ str_href ]; + return url.replace( /^[^#]*#?(.*)$/, '$1' ); + }; + + // Property: jQuery.hashchangeDelay + // + // The numeric interval (in milliseconds) at which the <hashchange event> + // polling loop executes. Defaults to 100. + + $[ str_hashchange + 'Delay' ] = 100; + + // Event: hashchange event + // + // Fired when location.hash changes. In browsers that support it, the native + // window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is + // initialized, running every <jQuery.hashchangeDelay> milliseconds to see if + // the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow + // the back button and hash-based history to work. + // + // Usage: + // + // > $(window).bind( 'hashchange', function(e) { + // > var hash = location.hash; + // > ... + // > }); + // + // Additional Notes: + // + // * The polling loop and Iframe are not created until at least one callback + // is actually bound to 'hashchange'. + // * If you need the bound callback(s) to execute immediately, in cases where + // the page 'state' exists on page load (via bookmark or page refresh, for + // example) use $(window).trigger( 'hashchange' ); + // * The event can be bound before DOM ready, but since it won't be usable + // before then in IE6/7 (due to the necessary Iframe), recommended usage is + // to bind it inside a $(document).ready() callback. + + jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], { + + // Called only when the first 'hashchange' event is bound to window. + setup: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to create our own. And we don't want to call this + // until the user binds to the event, just in case they never do, since it + // will create a polling loop and possibly even a hidden Iframe. + $( fake_onhashchange.start ); + }, + + // Called only when the last 'hashchange' event is unbound from window. + teardown: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to stop ours (if possible). + $( fake_onhashchange.stop ); + } + + }); + + // fake_onhashchange does all the work of triggering the window.onhashchange + // event for browsers that don't natively support it, including creating a + // polling loop to watch for hash changes and in IE 6/7 creating a hidden + // Iframe to enable back and forward. + fake_onhashchange = (function(){ + var self = {}, + timeout_id, + iframe, + set_history, + get_history; + + // Initialize. In IE 6/7, creates a hidden Iframe for history handling. + function init(){ + // Most browsers don't need special methods here.. + set_history = get_history = function(val){ return val; }; + + // But IE6/7 do! + if ( is_old_ie ) { + + // Create hidden Iframe after the end of the body to prevent initial + // page load from scrolling unnecessarily. + iframe = $('<iframe src="javascript:0"/>').hide().insertAfter( 'body' )[0].contentWindow; + + // Get history by looking at the hidden Iframe's location.hash. + get_history = function() { + return get_fragment( iframe.document[ str_location ][ str_href ] ); + }; + + // Set a new history item by opening and then closing the Iframe + // document, *then* setting its location.hash. + set_history = function( hash, history_hash ) { + if ( hash !== history_hash ) { + var doc = iframe.document; + doc.open().close(); + doc[ str_location ].hash = '#' + hash; + } + }; + + // Set initial history. + set_history( get_fragment() ); + } + }; + + // Start the polling loop. + self.start = function() { + // Polling loop is already running! + if ( timeout_id ) { return; } + + // Remember the initial hash so it doesn't get triggered immediately. + var last_hash = get_fragment(); + + // Initialize if not yet initialized. + set_history || init(); + + // This polling loop checks every $.hashchangeDelay milliseconds to see if + // location.hash has changed, and triggers the 'hashchange' event on + // window when necessary. + (function loopy(){ + var hash = get_fragment(), + history_hash = get_history( last_hash ); + + if ( hash !== last_hash ) { + set_history( last_hash = hash, history_hash ); + + $(window).trigger( str_hashchange ); + + } else if ( history_hash !== last_hash ) { + window[ str_location ][ str_href ] = window[ str_location ][ str_href ].replace( /#.*/, '' ) + '#' + history_hash; + } + + timeout_id = setTimeout( loopy, $[ str_hashchange + 'Delay' ] ); + })(); + }; + + // Stop the polling loop, but only if an IE6/7 Iframe wasn't created. In + // that case, even if there are no longer any bound event handlers, the + // polling loop is still necessary for back/next to work at all! + self.stop = function() { + if ( !iframe ) { + timeout_id && clearTimeout( timeout_id ); + timeout_id = 0; + } + }; + + return self; + })(); + +})(jQuery,this);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.bgiframe.js b/framework/web/js/source/jquery.bgiframe.js new file mode 100644 index 0000000..5cd38bb --- /dev/null +++ b/framework/web/js/source/jquery.bgiframe.js @@ -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);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.cookie.js b/framework/web/js/source/jquery.cookie.js new file mode 100644 index 0000000..8e8e1d9 --- /dev/null +++ b/framework/web/js/source/jquery.cookie.js @@ -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; + } +};
\ No newline at end of file diff --git a/framework/web/js/source/jquery.js b/framework/web/js/source/jquery.js new file mode 100644 index 0000000..8ccd0ea --- /dev/null +++ b/framework/web/js/source/jquery.js @@ -0,0 +1,9266 @@ +/*! + * jQuery JavaScript Library v1.7.1 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Nov 21 21:11:03 2011 -0500 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.7.1", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + // A crude way of determining if an object is a window + isWindow: function( obj ) { + return obj && typeof obj === "object" && "setInterval" in obj; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + jQuery.access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!memory; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + marginDiv, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + div.innerHTML = ""; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = marginDiv = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + conMarginTop, ptlm, vb, style, html, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; + vb = "visibility:hidden;border:0;"; + style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; + html = "<div " + style + "><div></div></div>" + + "<table " + style + " cellpadding='0' cellspacing='0'>" + + "<tr><td></td></tr></table>"; + + container = document.createElement("div"); + container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Figure out if the W3C box model works as expected + div.innerHTML = ""; + div.style.width = div.style.paddingLeft = "1px"; + jQuery.boxModel = support.boxModel = div.offsetWidth === 2; + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = ""; + div.innerHTML = "<div style='width:4px;'></div>"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + } + + div.style.cssText = ptlm + vb; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + body.removeChild( container ); + div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, attr, name, + data = null; + + if ( typeof key === "undefined" ) { + if ( this.length ) { + data = jQuery.data( this[0] ); + + if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) { + attr = this[0].attributes; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( this[0], name, data[ name ] ); + } + } + jQuery._data( this[0], "parsedAttrs", true ); + } + } + + return data; + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + // Try to fetch any internally stored data first + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + data = dataAttr( this[0], key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + + } else { + return this.each(function() { + var self = jQuery( this ), + args = [ parts[0], value ]; + + self.triggerHandler( "setData" + parts[1] + "!", args ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + parts[1] + "!", args ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise(); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, name, value, true, jQuery.prop ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + + // See #9699 for explanation of this approach (setting first, then removal) + jQuery.attr( elem, name, "" ); + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( rboolean.test( name ) && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /\bhover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Determine handlers that should run if there are delegated events + // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on.call( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context, seed ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set, seed ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + pass = not ^ found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = "<a name='" + id + "'/>"; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = "<a href='#'></a>"; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "<p class='TEST'></p>"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "<div class='test e'></div><div class='test'></div>"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context, seed ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /<tbody/i, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style)/i, + rnocache = /<(?:script|object|embed|option|style)/i, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"), + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/, + wrapMap = { + option: [ 1, "<select multiple='multiple'>", "</select>" ], + legend: [ 1, "<fieldset>", "</fieldset>" ], + thead: [ 1, "<table>", "</table>" ], + tr: [ 2, "<table><tbody>", "</tbody></table>" ], + td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], + col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], + area: [ 1, "<map>", "</map>" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize <link> and <script> tags normally +if ( !jQuery.support.htmlSerialize ) { + wrapMap._default = [ 1, "div<div>", "</div>" ]; +} + +jQuery.fn.extend({ + text: function( text ) { + if ( jQuery.isFunction(text) ) { + return this.each(function(i) { + var self = jQuery( this ); + + self.text( text.call(this, i, self.text()) ); + }); + } + + if ( typeof text !== "object" && text !== undefined ) { + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + } + + return jQuery.text( this ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this ); + }); + } else if ( arguments.length ) { + var set = jQuery.clean( arguments ); + set.push.apply( set, this.toArray() ); + return this.pushStack( set, "before", arguments ); + } + }, + + after: function() { + if ( this[0] && this[0].parentNode ) { + return this.domManip(arguments, false, function( elem ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + } else if ( arguments.length ) { + var set = this.pushStack( this, "after", arguments ); + set.push.apply( set, jQuery.clean(arguments) ); + return set; + } + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + jQuery.cleanData( [ elem ] ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( elem.getElementsByTagName("*") ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + if ( value === undefined ) { + return this[0] && this[0].nodeType === 1 ? + this[0].innerHTML.replace(rinlinejQuery, "") : + null; + + // See if we can take a shortcut and just use innerHTML + } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && + !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { + + value = value.replace(rxhtmlTag, "<$1></$2>"); + + try { + for ( var i = 0, l = this.length; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + if ( this[i].nodeType === 1 ) { + jQuery.cleanData( this[i].getElementsByTagName("*") ); + this[i].innerHTML = value; + } + } + + // If using innerHTML throws an exception, use the fallback method + } catch(e) { + this.empty().append( value ); + } + + } else if ( jQuery.isFunction( value ) ) { + this.each(function(i){ + var self = jQuery( this ); + + self.html( value.call(this, i, self.html()) ); + }); + + } else { + this.empty().append( value ); + } + + return this; + }, + + replaceWith: function( value ) { + if ( this[0] && this[0].parentNode ) { + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this), old = self.html(); + self.replaceWith( value.call( this, i, old ) ); + }); + } + + if ( typeof value !== "string" ) { + value = jQuery( value ).detach(); + } + + return this.each(function() { + var next = this.nextSibling, + parent = this.parentNode; + + jQuery( this ).remove(); + + if ( next ) { + jQuery(next).before( value ); + } else { + jQuery(parent).append( value ); + } + }); + } else { + return this.length ? + this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : + this; + } + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + var results, first, fragment, parent, + value = args[0], + scripts = []; + + // We can't cloneNode fragments that contain checked, in WebKit + if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { + return this.each(function() { + jQuery(this).domManip( args, table, callback, true ); + }); + } + + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + args[0] = value.call(this, i, table ? self.html() : undefined); + self.domManip( args, table, callback ); + }); + } + + if ( this[0] ) { + parent = value && value.parentNode; + + // If we're in a fragment, just use that instead of building a new one + if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { + results = { fragment: parent }; + + } else { + results = jQuery.buildFragment( args, this, scripts ); + } + + fragment = results.fragment; + + if ( fragment.childNodes.length === 1 ) { + first = fragment = fragment.firstChild; + } else { + first = fragment.firstChild; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + + for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { + callback.call( + table ? + root(this[i], first) : + this[i], + // Make sure that we do not leak memory by inadvertently discarding + // the original fragment (which might have attached data) instead of + // using it; in addition, use the original fragment object for the last + // item instead of first because it can end up being emptied incorrectly + // in certain situations (Bug #8070). + // Fragments from the fragment cache must always be cloned and never used + // in place. + results.cacheable || ( l > 1 && i < lastIndex ) ? + jQuery.clone( fragment, true, true ) : + fragment + ); + } + } + + if ( scripts.length ) { + jQuery.each( scripts, evalScript ); + } + } + + return this; + } +}); + +function root( elem, cur ) { + return jQuery.nodeName(elem, "table") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function cloneFixAttributes( src, dest ) { + var nodeName; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + // clearAttributes removes the attributes, which we don't want, + // but also removes the attachEvent events, which we *do* want + if ( dest.clearAttributes ) { + dest.clearAttributes(); + } + + // mergeAttributes, in contrast, only merges back on the + // original attributes, not the events + if ( dest.mergeAttributes ) { + dest.mergeAttributes( src ); + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 fail to clone children inside object elements that use + // the proprietary classid attribute value (rather than the type + // attribute) to identify the type of content to display + if ( nodeName === "object" ) { + dest.outerHTML = src.outerHTML; + + } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + if ( src.checked ) { + dest.defaultChecked = dest.checked = src.checked; + } + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } + + // Event data gets referenced instead of copied if the expando + // gets copied too + dest.removeAttribute( jQuery.expando ); +} + +jQuery.buildFragment = function( args, nodes, scripts ) { + var fragment, cacheable, cacheresults, doc, + first = args[ 0 ]; + + // nodes may contain either an explicit document object, + // a jQuery collection or context object. + // If nodes[0] contains a valid object to assign to doc + if ( nodes && nodes[0] ) { + doc = nodes[0].ownerDocument || nodes[0]; + } + + // Ensure that an attr object doesn't incorrectly stand in as a document object + // Chrome and Firefox seem to allow this to occur and will throw exception + // Fixes #8950 + if ( !doc.createDocumentFragment ) { + doc = document; + } + + // Only cache "small" (1/2 KB) HTML strings that are associated with the main document + // Cloning options loses the selected state, so don't cache them + // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment + // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache + // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 + if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document && + first.charAt(0) === "<" && !rnocache.test( first ) && + (jQuery.support.checkClone || !rchecked.test( first )) && + (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { + + cacheable = true; + + cacheresults = jQuery.fragments[ first ]; + if ( cacheresults && cacheresults !== 1 ) { + fragment = cacheresults; + } + } + + if ( !fragment ) { + fragment = doc.createDocumentFragment(); + jQuery.clean( args, doc, fragment, scripts ); + } + + if ( cacheable ) { + jQuery.fragments[ first ] = cacheresults ? fragment : 1; + } + + return { fragment: fragment, cacheable: cacheable }; +}; + +jQuery.fragments = {}; + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var ret = [], + insert = jQuery( selector ), + parent = this.length === 1 && this[0].parentNode; + + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + insert[ original ]( this[0] ); + return this; + + } else { + for ( var i = 0, l = insert.length; i < l; i++ ) { + var elems = ( i > 0 ? this.clone(true) : this ).get(); + jQuery( insert[i] )[ original ]( elems ); + ret = ret.concat( elems ); + } + + return this.pushStack( ret, name, insert.selector ); + } + }; +}); + +function getAll( elem ) { + if ( typeof elem.getElementsByTagName !== "undefined" ) { + return elem.getElementsByTagName( "*" ); + + } else if ( typeof elem.querySelectorAll !== "undefined" ) { + return elem.querySelectorAll( "*" ); + + } else { + return []; + } +} + +// Used in clean, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( elem.type === "checkbox" || elem.type === "radio" ) { + elem.defaultChecked = elem.checked; + } +} +// Finds all inputs and passes them to fixDefaultChecked +function findInputs( elem ) { + var nodeName = ( elem.nodeName || "" ).toLowerCase(); + if ( nodeName === "input" ) { + fixDefaultChecked( elem ); + // Skip scripts, get other children + } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) { + jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); + } +} + +// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js +function shimCloneNode( elem ) { + var div = document.createElement( "div" ); + safeFragment.appendChild( div ); + + div.innerHTML = elem.outerHTML; + return div.firstChild; +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var srcElements, + destElements, + i, + // IE<=8 does not properly clone detached, unknown element nodes + clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ? + elem.cloneNode( true ) : + shimCloneNode( elem ); + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + // IE copies events bound via attachEvent when using cloneNode. + // Calling detachEvent on the clone will also remove the events + // from the original. In order to get around this, we use some + // proprietary methods to clear the events. Thanks to MooTools + // guys for this hotness. + + cloneFixAttributes( elem, clone ); + + // Using Sizzle here is crazy slow, so we use getElementsByTagName instead + srcElements = getAll( elem ); + destElements = getAll( clone ); + + // Weird iteration because IE will replace the length property + // with an element if you are cloning the body and one of the + // elements on the page has a name or id of "length" + for ( i = 0; srcElements[i]; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + cloneFixAttributes( srcElements[i], destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + cloneCopyEvent( elem, clone ); + + if ( deepDataAndEvents ) { + srcElements = getAll( elem ); + destElements = getAll( clone ); + + for ( i = 0; srcElements[i]; ++i ) { + cloneCopyEvent( srcElements[i], destElements[i] ); + } + } + } + + srcElements = destElements = null; + + // Return the cloned set + return clone; + }, + + clean: function( elems, context, fragment, scripts ) { + var checkScriptType; + + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) { + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + } + + var ret = [], j; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( typeof elem === "number" ) { + elem += ""; + } + + if ( !elem ) { + continue; + } + + // Convert html string into DOM nodes + if ( typeof elem === "string" ) { + if ( !rhtml.test( elem ) ) { + elem = context.createTextNode( elem ); + } else { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(rxhtmlTag, "<$1></$2>"); + + // Trim whitespace, otherwise indexOf won't work as expected + var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(), + wrap = wrapMap[ tag ] || wrapMap._default, + depth = wrap[0], + div = context.createElement("div"); + + // Append wrapper element to unknown element safe doc fragment + if ( context === document ) { + // Use the fragment we've already created for this document + safeFragment.appendChild( div ); + } else { + // Use a fragment created with the owner document + createSafeFragment( context ).appendChild( div ); + } + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( depth-- ) { + div = div.lastChild; + } + + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { + + // String was a <table>, *may* have spurious <tbody> + var hasBody = rtbody.test(elem), + tbody = tag === "table" && !hasBody ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare <thead> or <tfoot> + wrap[1] === "<table>" && !hasBody ? + div.childNodes : + []; + + for ( j = tbody.length - 1; j >= 0 ; --j ) { + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + } + } + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); + } + + elem = div.childNodes; + } + } + + // Resets defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + var len; + if ( !jQuery.support.appendChecked ) { + if ( elem[0] && typeof (len = elem.length) === "number" ) { + for ( j = 0; j < len; j++ ) { + findInputs( elem[j] ); + } + } else { + findInputs( elem ); + } + } + + if ( elem.nodeType ) { + ret.push( elem ); + } else { + ret = jQuery.merge( ret, elem ); + } + } + + if ( fragment ) { + checkScriptType = function( elem ) { + return !elem.type || rscriptType.test( elem.type ); + }; + for ( i = 0; ret[i]; i++ ) { + if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { + scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); + + } else { + if ( ret[i].nodeType === 1 ) { + var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType ); + + ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); + } + fragment.appendChild( ret[i] ); + } + } + } + + return ret; + }, + + cleanData: function( elems ) { + var data, id, + cache = jQuery.cache, + special = jQuery.event.special, + deleteExpando = jQuery.support.deleteExpando; + + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + continue; + } + + id = elem[ jQuery.expando ]; + + if ( id ) { + data = cache[ id ]; + + if ( data && data.events ) { + for ( var type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + + // Null the DOM reference to avoid IE6/7/8 leak (#7054) + if ( data.handle ) { + data.handle.elem = null; + } + } + + if ( deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + delete cache[ id ]; + } + } + } +}); + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + + + + +var ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity=([^)]*)/, + // fixed for IE9, see #8346 + rupper = /([A-Z]|^ms)/g, + rnumpx = /^-?\d+(?:px)?$/i, + rnum = /^-?\d/, + rrelNum = /^([\-+])=([\-+.\de]+)/, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssWidth = [ "Left", "Right" ], + cssHeight = [ "Top", "Bottom" ], + curCSS, + + getComputedStyle, + currentStyle; + +jQuery.fn.css = function( name, value ) { + // Setting 'undefined' is a no-op + if ( arguments.length === 2 && value === undefined ) { + return this; + } + + return jQuery.access( this, name, value, true, function( elem, name, value ) { + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }); +}; + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity", "opacity" ); + return ret === "" ? "1" : ret; + + } else { + return elem.style.opacity; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, origName = jQuery.camelCase( name ), + style = elem.style, hooks = jQuery.cssHooks[ origName ]; + + name = jQuery.cssProps[ origName ] || origName; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra ) { + var ret, hooks; + + // Make sure that we're working with the right name + name = jQuery.camelCase( name ); + hooks = jQuery.cssHooks[ name ]; + name = jQuery.cssProps[ name ] || name; + + // cssFloat needs a special treatment + if ( name === "cssFloat" ) { + name = "float"; + } + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { + return ret; + + // Otherwise, if a way to get the computed value exists, use that + } else if ( curCSS ) { + return curCSS( elem, name ); + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + } +}); + +// DEPRECATED, Use jQuery.css() instead +jQuery.curCSS = jQuery.css; + +jQuery.each(["height", "width"], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + var val; + + if ( computed ) { + if ( elem.offsetWidth !== 0 ) { + return getWH( elem, name, extra ); + } else { + jQuery.swap( elem, cssShow, function() { + val = getWH( elem, name, extra ); + }); + } + + return val; + } + }, + + set: function( elem, value ) { + if ( rnumpx.test( value ) ) { + // ignore negative width and height values #1599 + value = parseFloat( value ); + + if ( value >= 0 ) { + return value + "px"; + } + + } else { + return value; + } + } + }; +}); + +if ( !jQuery.support.opacity ) { + jQuery.cssHooks.opacity = { + get: function( elem, computed ) { + // IE uses filters for opacity + return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? + ( parseFloat( RegExp.$1 ) / 100 ) + "" : + computed ? "1" : ""; + }, + + set: function( elem, value ) { + var style = elem.style, + currentStyle = elem.currentStyle, + opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "", + filter = currentStyle && currentStyle.filter || style.filter || ""; + + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + style.zoom = 1; + + // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 + if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) { + + // Setting style.filter to null, "" & " " still leave "filter:" in the cssText + // if "filter:" is present at all, clearType is disabled, we want to avoid this + // style.removeAttribute is IE Only, but so apparently is this code path... + style.removeAttribute( "filter" ); + + // if there there is no filter style applied in a css rule, we are done + if ( currentStyle && !currentStyle.filter ) { + return; + } + } + + // otherwise, set new filter values + style.filter = ralpha.test( filter ) ? + filter.replace( ralpha, opacity ) : + filter + " " + opacity; + } + }; +} + +jQuery(function() { + // This hook cannot be added until DOM ready because the support test + // for it is not run until after DOM ready + if ( !jQuery.support.reliableMarginRight ) { + jQuery.cssHooks.marginRight = { + get: function( elem, computed ) { + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + // Work around by temporarily setting element display to inline-block + var ret; + jQuery.swap( elem, { "display": "inline-block" }, function() { + if ( computed ) { + ret = curCSS( elem, "margin-right", "marginRight" ); + } else { + ret = elem.style.marginRight; + } + }); + return ret; + } + }; + } +}); + +if ( document.defaultView && document.defaultView.getComputedStyle ) { + getComputedStyle = function( elem, name ) { + var ret, defaultView, computedStyle; + + name = name.replace( rupper, "-$1" ).toLowerCase(); + + if ( (defaultView = elem.ownerDocument.defaultView) && + (computedStyle = defaultView.getComputedStyle( elem, null )) ) { + ret = computedStyle.getPropertyValue( name ); + if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { + ret = jQuery.style( elem, name ); + } + } + + return ret; + }; +} + +if ( document.documentElement.currentStyle ) { + currentStyle = function( elem, name ) { + var left, rsLeft, uncomputed, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret === null && style && (uncomputed = style[ name ]) ) { + ret = uncomputed; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { + + // Remember the original values + left = style.left; + rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ( ret || 0 ); + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +curCSS = getComputedStyle || currentStyle; + +function getWH( elem, name, extra ) { + + // Start with offset property + var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + which = name === "width" ? cssWidth : cssHeight, + i = 0, + len = which.length; + + if ( val > 0 ) { + if ( extra !== "border" ) { + for ( ; i < len; i++ ) { + if ( !extra ) { + val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; + } + if ( extra === "margin" ) { + val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; + } else { + val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; + } + } + } + + return val + "px"; + } + + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, name ); + if ( val < 0 || val == null ) { + val = elem.style[ name ] || 0; + } + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + + // Add padding, border, margin + if ( extra ) { + for ( ; i < len; i++ ) { + val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0; + if ( extra !== "padding" ) { + val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0; + } + if ( extra === "margin" ) { + val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0; + } + } + } + + return val + "px"; +} + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.hidden = function( elem ) { + var width = elem.offsetWidth, + height = elem.offsetHeight; + + return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none"); + }; + + jQuery.expr.filters.visible = function( elem ) { + return !jQuery.expr.filters.hidden( elem ); + }; +} + + + + +var r20 = /%20/g, + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rhash = /#.*$/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL + rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + rquery = /\?/, + rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, + rspacesAjax = /\s+/, + rts = /([?&])_=[^&]*/, + rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, + + // Keep a copy of the old load method + _load = jQuery.fn.load, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Document location + ajaxLocation, + + // Document location segments + ajaxLocParts, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = ["*/"] + ["*"]; + +// #8138, IE may throw an exception when accessing +// a field from window.location if document.domain has been set +try { + ajaxLocation = location.href; +} catch( e ) { + // Use the href attribute of an A element + // since IE will modify it given document.location + ajaxLocation = document.createElement( "a" ); + ajaxLocation.href = ""; + ajaxLocation = ajaxLocation.href; +} + +// Segment location into parts +ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + if ( jQuery.isFunction( func ) ) { + var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), + i = 0, + length = dataTypes.length, + dataType, + list, + placeBefore; + + // For each dataType in the dataTypeExpression + for ( ; i < length; i++ ) { + dataType = dataTypes[ i ]; + // We control if we're asked to add before + // any existing element + placeBefore = /^\+/.test( dataType ); + if ( placeBefore ) { + dataType = dataType.substr( 1 ) || "*"; + } + list = structure[ dataType ] = structure[ dataType ] || []; + // then we add to the structure accordingly + list[ placeBefore ? "unshift" : "push" ]( func ); + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, + dataType /* internal */, inspected /* internal */ ) { + + dataType = dataType || options.dataTypes[ 0 ]; + inspected = inspected || {}; + + inspected[ dataType ] = true; + + var list = structure[ dataType ], + i = 0, + length = list ? list.length : 0, + executeOnly = ( structure === prefilters ), + selection; + + for ( ; i < length && ( executeOnly || !selection ); i++ ) { + selection = list[ i ]( options, originalOptions, jqXHR ); + // If we got redirected to another dataType + // we try there if executing only and not done already + if ( typeof selection === "string" ) { + if ( !executeOnly || inspected[ selection ] ) { + selection = undefined; + } else { + options.dataTypes.unshift( selection ); + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, selection, inspected ); + } + } + } + // If we're only executing or nothing was selected + // we try the catchall dataType if not done already + if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { + selection = inspectPrefiltersOrTransports( + structure, options, originalOptions, jqXHR, "*", inspected ); + } + // unnecessary when only executing (prefilters) + // but it'll be ignored by the caller in that case + return selection; +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } +} + +jQuery.fn.extend({ + load: function( url, params, callback ) { + if ( typeof url !== "string" && _load ) { + return _load.apply( this, arguments ); + + // Don't do a request if no elements are being requested + } else if ( !this.length ) { + return this; + } + + var off = url.indexOf( " " ); + if ( off >= 0 ) { + var selector = url.slice( off, url.length ); + url = url.slice( 0, off ); + } + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) { + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = undefined; + + // Otherwise, build a param string + } else if ( typeof params === "object" ) { + params = jQuery.param( params, jQuery.ajaxSettings.traditional ); + type = "POST"; + } + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + // Complete callback (responseText is used internally) + complete: function( jqXHR, status, responseText ) { + // Store the response as specified by the jqXHR object + responseText = jqXHR.responseText; + // If successful, inject the HTML into all the matched elements + if ( jqXHR.isResolved() ) { + // #4825: Get the actual response in case + // a dataFilter is present in ajaxSettings + jqXHR.done(function( r ) { + responseText = r; + }); + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("<div>") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(responseText.replace(rscript, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + responseText ); + } + + if ( callback ) { + self.each( callback, [ responseText, status, jqXHR ] ); + } + } + }); + + return this; + }, + + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + + serializeArray: function() { + return this.map(function(){ + return this.elements ? jQuery.makeArray( this.elements ) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + ( this.checked || rselectTextarea.test( this.nodeName ) || + rinput.test( this.type ) ); + }) + .map(function( i, elem ){ + var val = jQuery( this ).val(); + + return val == null ? + null : + jQuery.isArray( val ) ? + jQuery.map( val, function( val, i ){ + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }) : + { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ + jQuery.fn[ o ] = function( f ){ + return this.on( o, f ); + }; +}); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return jQuery.ajax({ + type: method, + url: url, + data: data, + success: callback, + dataType: type + }); + }; +}); + +jQuery.extend({ + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + if ( settings ) { + // Building a settings object + ajaxExtend( target, jQuery.ajaxSettings ); + } else { + // Extending ajaxSettings + settings = target; + target = jQuery.ajaxSettings; + } + ajaxExtend( target, settings ); + return target; + }, + + ajaxSettings: { + url: ajaxLocation, + isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + traditional: false, + headers: {}, + */ + + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + text: "text/plain", + json: "application/json, text/javascript", + "*": allTypes + }, + + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText" + }, + + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + context: true, + url: true + } + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + // Callbacks context + callbackContext = s.context || s, + // Context for global events + // It's the callbackContext if one was provided in the options + // and if it's a DOM node or a jQuery collection + globalEventContext = callbackContext !== s && + ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? + jQuery( callbackContext ) : jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // ifModified key + ifModifiedKey, + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + parts, + // The jqXHR state + state = 0, + // To know if global events are to be dispatched + fireGlobals, + // Loop variable + i, + // Fake xhr + jqXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( !state ) { + var lname = name.toLowerCase(); + name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( state === 2 ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match === undefined ? null : match; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( !state ) { + s.mimeType = type; + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + statusText = statusText || "abort"; + if ( transport ) { + transport.abort( statusText ); + } + done( 0, statusText ); + return this; + } + }; + + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status, nativeStatusText, responses, headers ) { + + // Called once + if ( state === 2 ) { + return; + } + + // State is "done" now + state = 2; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + var isSuccess, + success, + error, + statusText = nativeStatusText, + response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, + lastModified, + etag; + + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + + if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { + jQuery.lastModified[ ifModifiedKey ] = lastModified; + } + if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { + jQuery.etag[ ifModifiedKey ] = etag; + } + } + + // If not modified + if ( status === 304 ) { + + statusText = "notmodified"; + isSuccess = true; + + // If we have data + } else { + + try { + success = ajaxConvert( s, response ); + statusText = "success"; + isSuccess = true; + } catch(e) { + // We have a parsererror + statusText = "parsererror"; + error = e; + } + } + } else { + // We extract error from statusText + // then normalize statusText and status for non-aborts + error = statusText; + if ( !statusText || status ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = "" + ( nativeStatusText || statusText ); + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + // Attach deferreds + deferred.promise( jqXHR ); + jqXHR.success = jqXHR.done; + jqXHR.error = jqXHR.fail; + jqXHR.complete = completeDeferred.add; + + // Status-dependent callbacks + jqXHR.statusCode = function( map ) { + if ( map ) { + var tmp; + if ( state < 2 ) { + for ( tmp in map ) { + statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; + } + } else { + tmp = map[ jqXHR.status ]; + jqXHR.then( tmp, tmp ); + } + } + return this; + }; + + // Remove hash character (#7531: and string promotion) + // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) + // We also use the url parameter if available + s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); + + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); + + // Determine if a cross-domain request is in order + if ( s.crossDomain == null ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( parts && + ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || + ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != + ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) + ); + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefiler, stop there + if ( state === 2 ) { + return false; + } + + // We can fire global events as of now if asked to + fireGlobals = s.global; + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Get ifModifiedKey before adding the anti-cache parameter + ifModifiedKey = s.url; + + // Add anti-cache in url if needed + if ( s.cache === false ) { + + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts, "$1_=" + ts ); + + // if nothing was replaced, add timestamp to the end + s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + ifModifiedKey = ifModifiedKey || s.url; + if ( jQuery.lastModified[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); + } + if ( jQuery.etag[ ifModifiedKey ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); + } + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? + s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { + // Abort if not done already + jqXHR.abort(); + return false; + + } + + // Install callbacks on deferreds + for ( i in { success: 1, error: 1, complete: 1 } ) { + jqXHR[ i ]( s[ i ] ); + } + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout( function(){ + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + state = 1; + transport.send( requestHeaders, done ); + } catch (e) { + // Propagate exception as error if not done + if ( state < 2 ) { + done( -1, e ); + // Simply rethrow otherwise + } else { + throw e; + } + } + } + + return jqXHR; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a, traditional ) { + var s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction( value ) ? value() : value; + s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); + }; + + // Set traditional to true for jQuery <= 1.3.2 behavior. + if ( traditional === undefined ) { + traditional = jQuery.ajaxSettings.traditional; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + }); + + } else { + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( var prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ).replace( r20, "+" ); + } +}); + +function buildParams( prefix, obj, traditional, add ) { + if ( jQuery.isArray( obj ) ) { + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + // If array item is non-scalar (array or object), encode its + // numeric index to resolve deserialization ambiguity issues. + // Note that rack (as of 1.0.0) can't currently deserialize + // nested arrays properly, and attempting to do so may cause + // a server error. Possible fixes are to modify rack's + // deserialization algorithm or to provide an option or flag + // to force array serialization to be shallow. + buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); + } + }); + + } else if ( !traditional && obj != null && typeof obj === "object" ) { + // Serialize object item. + for ( var name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + // Serialize scalar item. + add( prefix, obj ); + } +} + +// This is still on the jQuery object... for now +// Want to move this to jQuery.ajax some day +jQuery.extend({ + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {} + +}); + +/* Handles responses to an ajax request: + * - sets all responseXXX fields accordingly + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var contents = s.contents, + dataTypes = s.dataTypes, + responseFields = s.responseFields, + ct, + type, + finalDataType, + firstDataType; + + // Fill responseXXX fields + for ( type in responseFields ) { + if ( type in responses ) { + jqXHR[ responseFields[type] ] = responses[ type ]; + } + } + + // Remove auto dataType and get content-type in the process + while( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +// Chain conversions given the request and the original response +function ajaxConvert( s, response ) { + + // Apply the dataFilter if provided + if ( s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + var dataTypes = s.dataTypes, + converters = {}, + i, + key, + length = dataTypes.length, + tmp, + // Current and previous dataTypes + current = dataTypes[ 0 ], + prev, + // Conversion expression + conversion, + // Conversion function + conv, + // Conversion functions (transitive conversion) + conv1, + conv2; + + // For each dataType in the chain + for ( i = 1; i < length; i++ ) { + + // Create converters map + // with lowercased keys + if ( i === 1 ) { + for ( key in s.converters ) { + if ( typeof key === "string" ) { + converters[ key.toLowerCase() ] = s.converters[ key ]; + } + } + } + + // Get the dataTypes + prev = current; + current = dataTypes[ i ]; + + // If current is auto dataType, update it to prev + if ( current === "*" ) { + current = prev; + // If no auto and dataTypes are actually different + } else if ( prev !== "*" && prev !== current ) { + + // Get the converter + conversion = prev + " " + current; + conv = converters[ conversion ] || converters[ "* " + current ]; + + // If there is no direct converter, search transitively + if ( !conv ) { + conv2 = undefined; + for ( conv1 in converters ) { + tmp = conv1.split( " " ); + if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { + conv2 = converters[ tmp[1] + " " + current ]; + if ( conv2 ) { + conv1 = converters[ conv1 ]; + if ( conv1 === true ) { + conv = conv2; + } else if ( conv2 === true ) { + conv = conv1; + } + break; + } + } + } + } + // If we found no converter, dispatch an error + if ( !( conv || conv2 ) ) { + jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); + } + // If found converter is not an equivalence + if ( conv !== true ) { + // Convert with 1 or 2 converters accordingly + response = conv ? conv( response ) : conv2( conv1(response) ); + } + } + } + return response; +} + + + + +var jsc = jQuery.now(), + jsre = /(\=)\?(&|$)|\?\?/i; + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + return jQuery.expando + "_" + ( jsc++ ); + } +}); + +// Detect, normalize options and install callbacks for jsonp requests +jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { + + var inspectData = s.contentType === "application/x-www-form-urlencoded" && + ( typeof s.data === "string" ); + + if ( s.dataTypes[ 0 ] === "jsonp" || + s.jsonp !== false && ( jsre.test( s.url ) || + inspectData && jsre.test( s.data ) ) ) { + + var responseContainer, + jsonpCallback = s.jsonpCallback = + jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, + previous = window[ jsonpCallback ], + url = s.url, + data = s.data, + replace = "$1" + jsonpCallback + "$2"; + + if ( s.jsonp !== false ) { + url = url.replace( jsre, replace ); + if ( s.url === url ) { + if ( inspectData ) { + data = data.replace( jsre, replace ); + } + if ( s.data === data ) { + // Add callback manually + url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + } + } + } + + s.url = url; + s.data = data; + + // Install callback + window[ jsonpCallback ] = function( response ) { + responseContainer = [ response ]; + }; + + // Clean-up function + jqXHR.always(function() { + // Set callback back to previous value + window[ jsonpCallback ] = previous; + // Call if it was a function and we have a response + if ( responseContainer && jQuery.isFunction( previous ) ) { + window[ jsonpCallback ]( responseContainer[ 0 ] ); + } + }); + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( !responseContainer ) { + jQuery.error( jsonpCallback + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Delegate to script + return "script"; + } +}); + + + + +// Install script dataType +jQuery.ajaxSetup({ + accepts: { + script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /javascript|ecmascript/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +}); + +// Handle cache's special case and global +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; + } +}); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; + + return { + + send: function( _, callback ) { + + script = document.createElement( "script" ); + + script.async = "async"; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _, isAbort ) { + + if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( head && script.parentNode ) { + head.removeChild( script ); + } + + // Dereference the script + script = undefined; + + // Callback if not abort + if ( !isAbort ) { + callback( 200, "success" ); + } + } + }; + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + }, + + abort: function() { + if ( script ) { + script.onload( 0, 1 ); + } + } + }; + } +}); + + + + +var // #5280: Internet Explorer will keep connections alive if we don't abort on unload + xhrOnUnloadAbort = window.ActiveXObject ? function() { + // Abort all pending requests + for ( var key in xhrCallbacks ) { + xhrCallbacks[ key ]( 0, 1 ); + } + } : false, + xhrId = 0, + xhrCallbacks; + +// Functions to create xhrs +function createStandardXHR() { + try { + return new window.XMLHttpRequest(); + } catch( e ) {} +} + +function createActiveXHR() { + try { + return new window.ActiveXObject( "Microsoft.XMLHTTP" ); + } catch( e ) {} +} + +// Create the request object +// (This is still attached to ajaxSettings for backward compatibility) +jQuery.ajaxSettings.xhr = window.ActiveXObject ? + /* Microsoft failed to properly + * implement the XMLHttpRequest in IE7 (can't request local files), + * so we use the ActiveXObject when it is available + * Additionally XMLHttpRequest can be disabled in IE7/IE8 so + * we need a fallback. + */ + function() { + return !this.isLocal && createStandardXHR() || createActiveXHR(); + } : + // For all other browsers, use the standard XMLHttpRequest object + createStandardXHR; + +// Determine support properties +(function( xhr ) { + jQuery.extend( jQuery.support, { + ajax: !!xhr, + cors: !!xhr && ( "withCredentials" in xhr ) + }); +})( jQuery.ajaxSettings.xhr() ); + +// Create transport if the browser can provide an xhr +if ( jQuery.support.ajax ) { + + jQuery.ajaxTransport(function( s ) { + // Cross domain only allowed if supported through XMLHttpRequest + if ( !s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + send: function( headers, complete ) { + + // Get a new xhr + var xhr = s.xhr(), + handle, + i; + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open( s.type, s.url, s.async, s.username, s.password ); + } else { + xhr.open( s.type, s.url, s.async ); + } + + // Apply custom fields if provided + if ( s.xhrFields ) { + for ( i in s.xhrFields ) { + xhr[ i ] = s.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( s.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( s.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !s.crossDomain && !headers["X-Requested-With"] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + } catch( _ ) {} + + // Do send the request + // This may raise an exception which is actually + // handled in jQuery.ajax (so no try/catch here) + xhr.send( ( s.hasContent && s.data ) || null ); + + // Listener + callback = function( _, isAbort ) { + + var status, + statusText, + responseHeaders, + responses, + xml; + + // Firefox throws exceptions when accessing properties + // of an xhr when a network error occured + // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) + try { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = undefined; + + // Do not keep as active anymore + if ( handle ) { + xhr.onreadystatechange = jQuery.noop; + if ( xhrOnUnloadAbort ) { + delete xhrCallbacks[ handle ]; + } + } + + // If it's an abort + if ( isAbort ) { + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + status = xhr.status; + responseHeaders = xhr.getAllResponseHeaders(); + responses = {}; + xml = xhr.responseXML; + + // Construct response list + if ( xml && xml.documentElement /* #4958 */ ) { + responses.xml = xml; + } + responses.text = xhr.responseText; + + // Firefox throws an exception when accessing + // statusText for faulty cross-domain requests + try { + statusText = xhr.statusText; + } catch( e ) { + // We normalize with Webkit giving an empty statusText + statusText = ""; + } + + // Filter status for non standard behaviors + + // If the request is local and we have data: assume a success + // (success with no data won't get notified, that's the best we + // can do given current implementations) + if ( !status && s.isLocal && !s.crossDomain ) { + status = responses.text ? 200 : 404; + // IE - #1450: sometimes returns 1223 when it should be 204 + } else if ( status === 1223 ) { + status = 204; + } + } + } + } catch( firefoxAccessException ) { + if ( !isAbort ) { + complete( -1, firefoxAccessException ); + } + } + + // Call complete if needed + if ( responses ) { + complete( status, statusText, responses, responseHeaders ); + } + }; + + // if we're in sync mode or it's in cache + // and has been retrieved directly (IE6 & IE7) + // we need to manually fire the callback + if ( !s.async || xhr.readyState === 4 ) { + callback(); + } else { + handle = ++xhrId; + if ( xhrOnUnloadAbort ) { + // Create the active xhrs callbacks list if needed + // and attach the unload handler + if ( !xhrCallbacks ) { + xhrCallbacks = {}; + jQuery( window ).unload( xhrOnUnloadAbort ); + } + // Add to list of active xhrs callbacks + xhrCallbacks[ handle ] = callback; + } + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback(0,1); + } + } + }; + } + }); +} + + + + +var elemdisplay = {}, + iframe, iframeDoc, + rfxtypes = /^(?:toggle|show|hide)$/, + rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, + timerId, + fxAttrs = [ + // height animations + [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], + // width animations + [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], + // opacity animations + [ "opacity" ] + ], + fxNow; + +jQuery.fn.extend({ + show: function( speed, easing, callback ) { + var elem, display; + + if ( speed || speed === 0 ) { + return this.animate( genFx("show", 3), speed, easing, callback ); + + } else { + for ( var i = 0, j = this.length; i < j; i++ ) { + elem = this[ i ]; + + if ( elem.style ) { + display = elem.style.display; + + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( display === "" && jQuery.css(elem, "display") === "none" ) { + jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) ); + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + elem = this[ i ]; + + if ( elem.style ) { + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery._data( elem, "olddisplay" ) || ""; + } + } + } + + return this; + } + }, + + hide: function( speed, easing, callback ) { + if ( speed || speed === 0 ) { + return this.animate( genFx("hide", 3), speed, easing, callback); + + } else { + var elem, display, + i = 0, + j = this.length; + + for ( ; i < j; i++ ) { + elem = this[i]; + if ( elem.style ) { + display = jQuery.css( elem, "display" ); + + if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) { + jQuery._data( elem, "olddisplay", display ); + } + } + } + + // Set the display of the elements in a second loop + // to avoid the constant reflow + for ( i = 0; i < j; i++ ) { + if ( this[i].style ) { + this[i].style.display = "none"; + } + } + + return this; + } + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + + toggle: function( fn, fn2, callback ) { + var bool = typeof fn === "boolean"; + + if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { + this._toggle.apply( this, arguments ); + + } else if ( fn == null || bool ) { + this.each(function() { + var state = bool ? fn : jQuery(this).is(":hidden"); + jQuery(this)[ state ? "show" : "hide" ](); + }); + + } else { + this.animate(genFx("toggle", 3), fn, fn2, callback); + } + + return this; + }, + + fadeTo: function( speed, to, easing, callback ) { + return this.filter(":hidden").css("opacity", 0).show().end() + .animate({opacity: to}, speed, easing, callback); + }, + + animate: function( prop, speed, easing, callback ) { + var optall = jQuery.speed( speed, easing, callback ); + + if ( jQuery.isEmptyObject( prop ) ) { + return this.each( optall.complete, [ false ] ); + } + + // Do not change referenced properties as per-property easing will be lost + prop = jQuery.extend( {}, prop ); + + function doAnimation() { + // XXX 'this' does not always have a nodeName when running the + // test suite + + if ( optall.queue === false ) { + jQuery._mark( this ); + } + + var opt = jQuery.extend( {}, optall ), + isElement = this.nodeType === 1, + hidden = isElement && jQuery(this).is(":hidden"), + name, val, p, e, + parts, start, end, unit, + method; + + // will store per property easing and be used to determine when an animation is complete + opt.animatedProperties = {}; + + for ( p in prop ) { + + // property name normalization + name = jQuery.camelCase( p ); + if ( p !== name ) { + prop[ name ] = prop[ p ]; + delete prop[ p ]; + } + + val = prop[ name ]; + + // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default) + if ( jQuery.isArray( val ) ) { + opt.animatedProperties[ name ] = val[ 1 ]; + val = prop[ name ] = val[ 0 ]; + } else { + opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing'; + } + + if ( val === "hide" && hidden || val === "show" && !hidden ) { + return opt.complete.call( this ); + } + + if ( isElement && ( name === "height" || name === "width" ) ) { + // Make sure that nothing sneaks out + // Record all 3 overflow attributes because IE does not + // change the overflow attribute when overflowX and + // overflowY are set to the same value + opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; + + // Set display property to inline-block for height/width + // animations on inline elements that are having width/height animated + if ( jQuery.css( this, "display" ) === "inline" && + jQuery.css( this, "float" ) === "none" ) { + + // inline-level elements accept inline-block; + // block-level elements need to be inline with layout + if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) { + this.style.display = "inline-block"; + + } else { + this.style.zoom = 1; + } + } + } + } + + if ( opt.overflow != null ) { + this.style.overflow = "hidden"; + } + + for ( p in prop ) { + e = new jQuery.fx( this, opt, p ); + val = prop[ p ]; + + if ( rfxtypes.test( val ) ) { + + // Tracks whether to show or hide based on private + // data attached to the element + method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 ); + if ( method ) { + jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" ); + e[ method ](); + } else { + e[ val ](); + } + + } else { + parts = rfxnum.exec( val ); + start = e.cur(); + + if ( parts ) { + end = parseFloat( parts[2] ); + unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); + + // We need to compute starting value + if ( unit !== "px" ) { + jQuery.style( this, p, (end || 1) + unit); + start = ( (end || 1) / e.cur() ) * start; + jQuery.style( this, p, start + unit); + } + + // If a +=/-= token was provided, we're doing a relative animation + if ( parts[1] ) { + end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; + } + + e.custom( start, end, unit ); + + } else { + e.custom( start, val, "" ); + } + } + } + + // For JS strict compliance + return true; + } + + return optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + + stop: function( type, clearQueue, gotoEnd ) { + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each(function() { + var index, + hadTimers = false, + timers = jQuery.timers, + data = jQuery._data( this ); + + // clear marker counters if we know they won't be + if ( !gotoEnd ) { + jQuery._unmark( true, this ); + } + + function stopQueue( elem, data, index ) { + var hooks = data[ index ]; + jQuery.removeData( elem, index, true ); + hooks.stop( gotoEnd ); + } + + if ( type == null ) { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) { + stopQueue( this, data, index ); + } + } + } else if ( data[ index = type + ".run" ] && data[ index ].stop ){ + stopQueue( this, data, index ); + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) { + if ( gotoEnd ) { + + // force the next step to be the last + timers[ index ]( true ); + } else { + timers[ index ].saveState(); + } + hadTimers = true; + timers.splice( index, 1 ); + } + } + + // start the next in the queue if the last step wasn't forced + // timers currently will call their complete callbacks, which will dequeue + // but only if they were gotoEnd + if ( !( gotoEnd && hadTimers ) ) { + jQuery.dequeue( this, type ); + } + }); + } + +}); + +// Animations created synchronously will run synchronously +function createFxNow() { + setTimeout( clearFxNow, 0 ); + return ( fxNow = jQuery.now() ); +} + +function clearFxNow() { + fxNow = undefined; +} + +// Generate parameters to create a standard animation +function genFx( type, num ) { + var obj = {}; + + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() { + obj[ this ] = type; + }); + + return obj; +} + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx( "show", 1 ), + slideUp: genFx( "hide", 1 ), + slideToggle: genFx( "toggle", 1 ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +}); + +jQuery.extend({ + speed: function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default; + + // normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function( noUnmark ) { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } else if ( noUnmark !== false ) { + jQuery._unmark( this ); + } + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum; + } + }, + + timers: [], + + fx: function( elem, options, prop ) { + this.options = options; + this.elem = elem; + this.prop = prop; + + options.orig = options.orig || {}; + } + +}); + +jQuery.fx.prototype = { + // Simple function for setting a style value + update: function() { + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this ); + }, + + // Get the current size + cur: function() { + if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) { + return this.elem[ this.prop ]; + } + + var parsed, + r = jQuery.css( this.elem, this.prop ); + // Empty strings, null, undefined and "auto" are converted to 0, + // complex values such as "rotate(1rad)" are returned as is, + // simple values such as "10px" are parsed to Float. + return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; + }, + + // Start an animation from one number to another + custom: function( from, to, unit ) { + var self = this, + fx = jQuery.fx; + + this.startTime = fxNow || createFxNow(); + this.end = to; + this.now = this.start = from; + this.pos = this.state = 0; + this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); + + function t( gotoEnd ) { + return self.step( gotoEnd ); + } + + t.queue = this.options.queue; + t.elem = this.elem; + t.saveState = function() { + if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) { + jQuery._data( self.elem, "fxshow" + self.prop, self.start ); + } + }; + + if ( t() && jQuery.timers.push(t) && !timerId ) { + timerId = setInterval( fx.tick, fx.interval ); + } + }, + + // Simple 'show' function + show: function() { + var dataShow = jQuery._data( this.elem, "fxshow" + this.prop ); + + // Remember where we started, so that we can go back to it later + this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop ); + this.options.show = true; + + // Begin the animation + // Make sure that we start at a small width/height to avoid any flash of content + if ( dataShow !== undefined ) { + // This show is picking up where a previous hide or show left off + this.custom( this.cur(), dataShow ); + } else { + this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() ); + } + + // Start by showing the element + jQuery( this.elem ).show(); + }, + + // Simple 'hide' function + hide: function() { + // Remember where we started, so that we can go back to it later + this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop ); + this.options.hide = true; + + // Begin the animation + this.custom( this.cur(), 0 ); + }, + + // Each step of an animation + step: function( gotoEnd ) { + var p, n, complete, + t = fxNow || createFxNow(), + done = true, + elem = this.elem, + options = this.options; + + if ( gotoEnd || t >= options.duration + this.startTime ) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + options.animatedProperties[ this.prop ] = true; + + for ( p in options.animatedProperties ) { + if ( options.animatedProperties[ p ] !== true ) { + done = false; + } + } + + if ( done ) { + // Reset the overflow + if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { + + jQuery.each( [ "", "X", "Y" ], function( index, value ) { + elem.style[ "overflow" + value ] = options.overflow[ index ]; + }); + } + + // Hide the element if the "hide" operation was done + if ( options.hide ) { + jQuery( elem ).hide(); + } + + // Reset the properties, if the item has been hidden or shown + if ( options.hide || options.show ) { + for ( p in options.animatedProperties ) { + jQuery.style( elem, p, options.orig[ p ] ); + jQuery.removeData( elem, "fxshow" + p, true ); + // Toggle data is no longer needed + jQuery.removeData( elem, "toggle" + p, true ); + } + } + + // Execute the complete function + // in the event that the complete function throws an exception + // we must ensure it won't be called twice. #5684 + + complete = options.complete; + if ( complete ) { + + options.complete = false; + complete.call( elem ); + } + } + + return false; + + } else { + // classical easing cannot be used with an Infinity duration + if ( options.duration == Infinity ) { + this.now = t; + } else { + n = t - this.startTime; + this.state = n / options.duration; + + // Perform the easing function, defaults to swing + this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration ); + this.now = this.start + ( (this.end - this.start) * this.pos ); + } + // Perform the next step of the animation + this.update(); + } + + return true; + } +}; + +jQuery.extend( jQuery.fx, { + tick: function() { + var timer, + timers = jQuery.timers, + i = 0; + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + }, + + interval: 13, + + stop: function() { + clearInterval( timerId ); + timerId = null; + }, + + speeds: { + slow: 600, + fast: 200, + // Default speed + _default: 400 + }, + + step: { + opacity: function( fx ) { + jQuery.style( fx.elem, "opacity", fx.now ); + }, + + _default: function( fx ) { + if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { + fx.elem.style[ fx.prop ] = fx.now + fx.unit; + } else { + fx.elem[ fx.prop ] = fx.now; + } + } + } +}); + +// Adds width/height step functions +// Do not set anything below 0 +jQuery.each([ "width", "height" ], function( i, prop ) { + jQuery.fx.step[ prop ] = function( fx ) { + jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit ); + }; +}); + +if ( jQuery.expr && jQuery.expr.filters ) { + jQuery.expr.filters.animated = function( elem ) { + return jQuery.grep(jQuery.timers, function( fn ) { + return elem === fn.elem; + }).length; + }; +} + +// Try to restore the default display value of an element +function defaultDisplay( nodeName ) { + + if ( !elemdisplay[ nodeName ] ) { + + var body = document.body, + elem = jQuery( "<" + nodeName + ">" ).appendTo( body ), + display = elem.css( "display" ); + elem.remove(); + + // If the simple way fails, + // get element's real default display by attaching it to a temp iframe + if ( display === "none" || display === "" ) { + // No iframe to use yet, so create it + if ( !iframe ) { + iframe = document.createElement( "iframe" ); + iframe.frameBorder = iframe.width = iframe.height = 0; + } + + body.appendChild( iframe ); + + // Create a cacheable copy of the iframe document on first call. + // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML + // document to it; WebKit & Firefox won't allow reusing the iframe document. + if ( !iframeDoc || !iframe.createElement ) { + iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; + iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" ); + iframeDoc.close(); + } + + elem = iframeDoc.createElement( nodeName ); + + iframeDoc.body.appendChild( elem ); + + display = jQuery.css( elem, "display" ); + body.removeChild( iframe ); + } + + // Store the correct default display + elemdisplay[ nodeName ] = display; + } + + return elemdisplay[ nodeName ]; +} + + + + +var rtable = /^t(?:able|d|h)$/i, + rroot = /^(?:body|html)$/i; + +if ( "getBoundingClientRect" in document.documentElement ) { + jQuery.fn.offset = function( options ) { + var elem = this[0], box; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + try { + box = elem.getBoundingClientRect(); + } catch(e) {} + + var doc = elem.ownerDocument, + docElem = doc.documentElement; + + // Make sure we're not dealing with a disconnected DOM node + if ( !box || !jQuery.contains( docElem, elem ) ) { + return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; + } + + var body = doc.body, + win = getWindow(doc), + clientTop = docElem.clientTop || body.clientTop || 0, + clientLeft = docElem.clientLeft || body.clientLeft || 0, + scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop, + scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft, + top = box.top + scrollTop - clientTop, + left = box.left + scrollLeft - clientLeft; + + return { top: top, left: left }; + }; + +} else { + jQuery.fn.offset = function( options ) { + var elem = this[0]; + + if ( options ) { + return this.each(function( i ) { + jQuery.offset.setOffset( this, options, i ); + }); + } + + if ( !elem || !elem.ownerDocument ) { + return null; + } + + if ( elem === elem.ownerDocument.body ) { + return jQuery.offset.bodyOffset( elem ); + } + + var computedStyle, + offsetParent = elem.offsetParent, + prevOffsetParent = elem, + doc = elem.ownerDocument, + docElem = doc.documentElement, + body = doc.body, + defaultView = doc.defaultView, + prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, + top = elem.offsetTop, + left = elem.offsetLeft; + + while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { + if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { + break; + } + + computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; + top -= elem.scrollTop; + left -= elem.scrollLeft; + + if ( elem === offsetParent ) { + top += elem.offsetTop; + left += elem.offsetLeft; + + if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevOffsetParent = offsetParent; + offsetParent = elem.offsetParent; + } + + if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { + top += parseFloat( computedStyle.borderTopWidth ) || 0; + left += parseFloat( computedStyle.borderLeftWidth ) || 0; + } + + prevComputedStyle = computedStyle; + } + + if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { + top += body.offsetTop; + left += body.offsetLeft; + } + + if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) { + top += Math.max( docElem.scrollTop, body.scrollTop ); + left += Math.max( docElem.scrollLeft, body.scrollLeft ); + } + + return { top: top, left: left }; + }; +} + +jQuery.offset = { + + bodyOffset: function( body ) { + var top = body.offsetTop, + left = body.offsetLeft; + + if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) { + top += parseFloat( jQuery.css(body, "marginTop") ) || 0; + left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; + } + + return { top: top, left: left }; + }, + + setOffset: function( elem, options, i ) { + var position = jQuery.css( elem, "position" ); + + // set position first, in-case top/left are set even on static elem + if ( position === "static" ) { + elem.style.position = "relative"; + } + + var curElem = jQuery( elem ), + curOffset = curElem.offset(), + curCSSTop = jQuery.css( elem, "top" ), + curCSSLeft = jQuery.css( elem, "left" ), + calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, + props = {}, curPosition = {}, curTop, curLeft; + + // need to be able to calculate position if either top or left is auto and position is either absolute or fixed + if ( calculatePosition ) { + curPosition = curElem.position(); + curTop = curPosition.top; + curLeft = curPosition.left; + } else { + curTop = parseFloat( curCSSTop ) || 0; + curLeft = parseFloat( curCSSLeft ) || 0; + } + + if ( jQuery.isFunction( options ) ) { + options = options.call( elem, i, curOffset ); + } + + if ( options.top != null ) { + props.top = ( options.top - curOffset.top ) + curTop; + } + if ( options.left != null ) { + props.left = ( options.left - curOffset.left ) + curLeft; + } + + if ( "using" in options ) { + options.using.call( elem, props ); + } else { + curElem.css( props ); + } + } +}; + + +jQuery.fn.extend({ + + position: function() { + if ( !this[0] ) { + return null; + } + + var elem = this[0], + + // Get *real* offsetParent + offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; + offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; + + // Add offsetParent borders + parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; + parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; + + // Subtract the two offsets + return { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + }, + + offsetParent: function() { + return this.map(function() { + var offsetParent = this.offsetParent || document.body; + while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent; + }); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( ["Left", "Top"], function( i, name ) { + var method = "scroll" + name; + + jQuery.fn[ method ] = function( val ) { + var elem, win; + + if ( val === undefined ) { + elem = this[ 0 ]; + + if ( !elem ) { + return null; + } + + win = getWindow( elem ); + + // Return the scroll offset + return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : + jQuery.support.boxModel && win.document.documentElement[ method ] || + win.document.body[ method ] : + elem[ method ]; + } + + // Set the scroll offset + return this.each(function() { + win = getWindow( this ); + + if ( win ) { + win.scrollTo( + !i ? val : jQuery( win ).scrollLeft(), + i ? val : jQuery( win ).scrollTop() + ); + + } else { + this[ method ] = val; + } + }); + }; +}); + +function getWindow( elem ) { + return jQuery.isWindow( elem ) ? + elem : + elem.nodeType === 9 ? + elem.defaultView || elem.parentWindow : + false; +} + + + + +// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods +jQuery.each([ "Height", "Width" ], function( i, name ) { + + var type = name.toLowerCase(); + + // innerHeight and innerWidth + jQuery.fn[ "inner" + name ] = function() { + var elem = this[0]; + return elem ? + elem.style ? + parseFloat( jQuery.css( elem, type, "padding" ) ) : + this[ type ]() : + null; + }; + + // outerHeight and outerWidth + jQuery.fn[ "outer" + name ] = function( margin ) { + var elem = this[0]; + return elem ? + elem.style ? + parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) : + this[ type ]() : + null; + }; + + jQuery.fn[ type ] = function( size ) { + // Get window width or height + var elem = this[0]; + if ( !elem ) { + return size == null ? null : this; + } + + if ( jQuery.isFunction( size ) ) { + return this.each(function( i ) { + var self = jQuery( this ); + self[ type ]( size.call( this, i, self[ type ]() ) ); + }); + } + + if ( jQuery.isWindow( elem ) ) { + // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode + // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat + var docElemProp = elem.document.documentElement[ "client" + name ], + body = elem.document.body; + return elem.document.compatMode === "CSS1Compat" && docElemProp || + body && body[ "client" + name ] || docElemProp; + + // Get document width or height + } else if ( elem.nodeType === 9 ) { + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + return Math.max( + elem.documentElement["client" + name], + elem.body["scroll" + name], elem.documentElement["scroll" + name], + elem.body["offset" + name], elem.documentElement["offset" + name] + ); + + // Get or set width or height on the element + } else if ( size === undefined ) { + var orig = jQuery.css( elem, type ), + ret = parseFloat( orig ); + + return jQuery.isNumeric( ret ) ? ret : orig; + + // Set the width or height on the element (default to pixels if value is unitless) + } else { + return this.css( type, typeof size === "string" ? size : size + "px" ); + } + }; + +}); + + + + +// Expose jQuery to the global object +window.jQuery = window.$ = jQuery; + +// Expose jQuery as an AMD module, but only for AMD loaders that +// understand the issues with loading multiple versions of jQuery +// in a page that all might call define(). The loader will indicate +// they have special allowances for multiple jQuery versions by +// specifying define.amd.jQuery = true. Register as a named module, +// since jQuery can be concatenated with other files that may use define, +// but not use a proper concatenation script that understands anonymous +// AMD modules. A named AMD is safest and most robust way to register. +// Lowercase jquery is used because AMD module names are derived from +// file names, and jQuery is normally delivered in a lowercase file name. +// Do this after creating the global so that if an AMD module wants to call +// noConflict to hide this version of jQuery, it will work. +if ( typeof define === "function" && define.amd && define.amd.jQuery ) { + define( "jquery", [], function () { return jQuery; } ); +} + + + +})( window ); diff --git a/framework/web/js/source/jquery.maskedinput.js b/framework/web/js/source/jquery.maskedinput.js new file mode 100644 index 0000000..c4742a0 --- /dev/null +++ b/framework/web/js/source/jquery.maskedinput.js @@ -0,0 +1,258 @@ +/* + Masked Input plugin for jQuery + Copyright (c) 2007-2011 Josh Bush (digitalbush.com) + Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) + Version: 1.3 +*/ +(function($) { + var pasteEventName = ($.browser.msie ? 'paste' : 'input') + ".mask"; + var iPhone = (window.orientation != undefined); + + $.mask = { + //Predefined character definitions + definitions: { + '9': "[0-9]", + 'a': "[A-Za-z]", + '*': "[A-Za-z0-9]" + }, + dataName:"rawMaskFn" + }; + + $.fn.extend({ + //Helper Function for Caret positioning + caret: function(begin, end) { + if (this.length == 0) 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) { + var 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) { + var 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) { + if (!mask && this.length > 0) { + var input = $(this[0]); + return input.data($.mask.dataName)(); + } + settings = $.extend({ + placeholder: "_", + completed: null + }, settings); + + var defs = $.mask.definitions; + var tests = []; + var partialPosition = mask.length; + var firstNonMaskPos = null; + var len = mask.length; + + $.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); + var buffer = $.map(mask.split(""), function(c, i) { if (c != '?') return defs[c] ? settings.placeholder : c }); + var 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) { + if(begin<0) + return; + for (var 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) { + for (var i = pos, c = settings.placeholder; i < len; i++) { + if (tests[i]) { + var j = seekNext(i); + var t = buffer[i]; + buffer[i] = c; + if (j < len && tests[j].test(t)) + c = t; + else + break; + } + } + }; + + function keydownEvent(e) { + var k=e.which; + + //backspace, delete, and escape get special treatment + if(k == 8 || k == 46 || (iPhone && k == 127)){ + var 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); + + return false; + } else if (k == 27) {//escape + input.val(focusText); + input.caret(0, checkVal()); + return false; + } + }; + + function keypressEvent(e) { + var k = e.which, + pos = input.caret(); + if (e.ctrlKey || e.altKey || e.metaKey || k<32) {//Ignore + return true; + } else if (k) { + if(pos.end-pos.begin!=0){ + clearBuffer(pos.begin, pos.end); + shiftL(pos.begin, pos.end-1); + } + + var p = seekNext(pos.begin - 1); + if (p < len) { + var c = String.fromCharCode(k); + if (tests[p].test(c)) { + shiftR(p); + buffer[p] = c; + writeBuffer(); + var next = seekNext(p); + input.caret(next); + if (settings.completed && next >= len) + settings.completed.call(input); + } + } + return false; + } + }; + + function clearBuffer(start, end) { + for (var i = start; i < end && i < len; i++) { + if (tests[i]) + buffer[i] = settings.placeholder; + } + }; + + function writeBuffer() { return input.val(buffer.join('')).val(); }; + + function checkVal(allow) { + //try to place characters where they belong + var test = input.val(); + var lastMatch = -1; + for (var i = 0, pos = 0; i < len; i++) { + if (tests[i]) { + buffer[i] = settings.placeholder; + while (pos++ < test.length) { + var 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 && lastMatch + 1 < partialPosition) { + input.val(""); + clearBuffer(0, len); + } else if (allow || lastMatch + 1 >= partialPosition) { + writeBuffer(); + if (!allow) 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() { + focusText = input.val(); + var pos = checkVal(); + writeBuffer(); + var moveCaret=function(){ + if (pos == mask.length) + input.caret(0, pos); + else + input.caret(pos); + }; + ($.browser.msie ? moveCaret:function(){setTimeout(moveCaret,0)})(); + }) + .bind("blur.mask", function() { + checkVal(); + if (input.val() != focusText) + input.change(); + }) + .bind("keydown.mask", keydownEvent) + .bind("keypress.mask", keypressEvent) + .bind(pasteEventName, function() { + setTimeout(function() { input.caret(checkVal(true)); }, 0); + }); + + checkVal(); //Perform initial check for existing values + }); + } + }); +})(jQuery); diff --git a/framework/web/js/source/jquery.maskedinput.min.js b/framework/web/js/source/jquery.maskedinput.min.js new file mode 100644 index 0000000..fdbd520 --- /dev/null +++ b/framework/web/js/source/jquery.maskedinput.min.js @@ -0,0 +1,7 @@ +/* + Masked Input plugin for jQuery + Copyright (c) 2007-2011 Josh Bush (digitalbush.com) + Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) + Version: 1.3 +*/ +(function(a){var b=(a.browser.msie?"paste":"input")+".mask",c=window.orientation!=undefined;a.mask={definitions:{9:"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"},dataName:"rawMaskFn"},a.fn.extend({caret:function(a,b){if(this.length!=0){if(typeof a=="number"){b=typeof b=="number"?b:a;return this.each(function(){if(this.setSelectionRange)this.setSelectionRange(a,b);else if(this.createTextRange){var c=this.createTextRange();c.collapse(!0),c.moveEnd("character",b),c.moveStart("character",a),c.select()}})}if(this[0].setSelectionRange)a=this[0].selectionStart,b=this[0].selectionEnd;else if(document.selection&&document.selection.createRange){var c=document.selection.createRange();a=0-c.duplicate().moveStart("character",-1e5),b=a+c.text.length}return{begin:a,end:b}}},unmask:function(){return this.trigger("unmask")},mask:function(d,e){if(!d&&this.length>0){var f=a(this[0]);return f.data(a.mask.dataName)()}e=a.extend({placeholder:"_",completed:null},e);var g=a.mask.definitions,h=[],i=d.length,j=null,k=d.length;a.each(d.split(""),function(a,b){b=="?"?(k--,i=a):g[b]?(h.push(new RegExp(g[b])),j==null&&(j=h.length-1)):h.push(null)});return this.trigger("unmask").each(function(){function v(a){var b=f.val(),c=-1;for(var d=0,g=0;d<k;d++)if(h[d]){l[d]=e.placeholder;while(g++<b.length){var m=b.charAt(g-1);if(h[d].test(m)){l[d]=m,c=d;break}}if(g>b.length)break}else l[d]==b.charAt(g)&&d!=i&&(g++,c=d);if(!a&&c+1<i)f.val(""),t(0,k);else if(a||c+1>=i)u(),a||f.val(f.val().substring(0,c+1));return i?d:j}function u(){return f.val(l.join("")).val()}function t(a,b){for(var c=a;c<b&&c<k;c++)h[c]&&(l[c]=e.placeholder)}function s(a){var b=a.which,c=f.caret();if(a.ctrlKey||a.altKey||a.metaKey||b<32)return!0;if(b){c.end-c.begin!=0&&(t(c.begin,c.end),p(c.begin,c.end-1));var d=n(c.begin-1);if(d<k){var g=String.fromCharCode(b);if(h[d].test(g)){q(d),l[d]=g,u();var i=n(d);f.caret(i),e.completed&&i>=k&&e.completed.call(f)}}return!1}}function r(a){var b=a.which;if(b==8||b==46||c&&b==127){var d=f.caret(),e=d.begin,g=d.end;g-e==0&&(e=b!=46?o(e):g=n(e-1),g=b==46?n(g):g),t(e,g),p(e,g-1);return!1}if(b==27){f.val(m),f.caret(0,v());return!1}}function q(a){for(var b=a,c=e.placeholder;b<k;b++)if(h[b]){var d=n(b),f=l[b];l[b]=c;if(d<k&&h[d].test(f))c=f;else break}}function p(a,b){if(!(a<0)){for(var c=a,d=n(b);c<k;c++)if(h[c]){if(d<k&&h[c].test(l[d]))l[c]=l[d],l[d]=e.placeholder;else break;d=n(d)}u(),f.caret(Math.max(j,a))}}function o(a){while(--a>=0&&!h[a]);return a}function n(a){while(++a<=k&&!h[a]);return a}var f=a(this),l=a.map(d.split(""),function(a,b){if(a!="?")return g[a]?e.placeholder:a}),m=f.val();f.data(a.mask.dataName,function(){return a.map(l,function(a,b){return h[b]&&a!=e.placeholder?a:null}).join("")}),f.attr("readonly")||f.one("unmask",function(){f.unbind(".mask").removeData(a.mask.dataName)}).bind("focus.mask",function(){m=f.val();var b=v();u();var c=function(){b==d.length?f.caret(0,b):f.caret(b)};(a.browser.msie?c:function(){setTimeout(c,0)})()}).bind("blur.mask",function(){v(),f.val()!=m&&f.change()}).bind("keydown.mask",r).bind("keypress.mask",s).bind(b,function(){setTimeout(function(){f.caret(v(!0))},0)}),v()})}})})(jQuery)
\ No newline at end of file diff --git a/framework/web/js/source/jquery.metadata.js b/framework/web/js/source/jquery.metadata.js new file mode 100644 index 0000000..0978495 --- /dev/null +++ b/framework/web/js/source/jquery.metadata.js @@ -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);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.min.js b/framework/web/js/source/jquery.min.js new file mode 100644 index 0000000..198b3ff --- /dev/null +++ b/framework/web/js/source/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.1 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test("Â ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; +f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() +{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.multifile.js b/framework/web/js/source/jquery.multifile.js new file mode 100644 index 0000000..d123423 --- /dev/null +++ b/framework/web/js/source/jquery.multifile.js @@ -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ó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 #*/ diff --git a/framework/web/js/source/jquery.rating.js b/framework/web/js/source/jquery.rating.js new file mode 100644 index 0000000..20e5058 --- /dev/null +++ b/framework/web/js/source/jquery.rating.js @@ -0,0 +1,381 @@ +/* + ### jQuery Star Rating Plugin v3.13 - 2009-03-26 ### + * Home: http://www.fyneworks.com/jquery/star-rating/ + * Code: http://code.google.com/p/jquery-star-rating-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($){ + + // IE6 Background Image Fix + if ($.browser.msie) 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]; + + // 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 raters + 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')) control.readOnly = true; + + // Create 'cancel' button + rater.append( + control.cancel = $('<div class="rating-cancel"><a title="' + control.cancel + '">' + control.cancelValue + '</a></div>') + .mouseover(function(){ + $(this).rating('drain'); + $(this).addClass('star-rating-hover'); + //$(this).rating('focus'); + }) + .mouseout(function(){ + $(this).rating('draw'); + $(this).removeClass('star-rating-hover'); + //$(this).rating('blur'); + }) + .click(function(){ + $(this).rating('select'); + }) + .data('rating', control) + ); + + } // first element of group + + // insert rating star + var star = $('<div 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 + .mouseover(function(){ + $(this).rating('fill'); + $(this).rating('focus'); + }) + .mouseout(function(){ + $(this).rating('draw'); + $(this).rating('blur'); + }) + .click(function(){ + $(this).rating('select'); + }) + ; + //}; //save a byte! + + // set current selection + if(this.checked) control.current = star; + + // hide input element + input.hide(); + + // backward compatibility, form element to plugin + input.change(function(){ + $(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); + }); // 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().andSelf().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 + if(control.current){ + control.current.data('rating.input').attr('checked','checked'); + control.current.prevAll().andSelf().filter('.rater-'+ control.serial).addClass('star-rating-on'); + } + else + $(control.inputs).removeAttr('checked'); + // 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 + + // ***** MODIFICATION ***** + // Thanks to faivre.thomas - http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=27 + // + // ***** LIST OF MODIFICATION ***** + // ***** added Parameter wantCallBack : false if you don't want a callback. true or undefined if you want postback to be performed at the end of this method' + // ***** recursive calls to this method were like : ... .rating('select') it's now like .rating('select',undefined,wantCallBack); (parameters are set.) + // ***** line which is calling callback + // ***** /LIST OF MODIFICATION ***** + + 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'){ + // 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(){ + if($(this).data('rating.input').val()==value) $(this).rating('select',undefined,wantCallBack); + }); + } + 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 data for event + var input = $( control.current ? control.current.data('rating.input') : null ); + // click callback, as requested here: http://plugins.jquery.com/node/1655 + + // **** MODIFICATION ***** + // Thanks to faivre.thomas - http://code.google.com/p/jquery-star-rating-plugin/issues/detail?id=27 + // + //old line doing the callback : + //if(control.callback) control.callback.apply(input[0], [input.val(), $('a', control.current)[0]]);// callback event + // + //new line doing the callback (if i want :) + if((wantCallBack ||wantCallBack == undefined) && control.callback) control.callback.apply(input[0], [input.val(), $('a', control.current)[0]]);// callback event + //to ensure retro-compatibility, wantCallBack must be considered as true by default + // **** /MODIFICATION ***** + + },// $.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 changed + //focus: function(){}, // executed when stars are focused + //blur: function(){}, // executed when stars are focused + //callback: function(){}, // executed when a star is clicked + }; //} }); + + /*--------------------------------------------------------*/ + + /* + ### Default implementation ### + The plugin will attach itself to file inputs + with the class 'multi' when the page loads + */ + $(function(){ + $('input[type=radio].star').rating(); + }); + + + +/*# AVOID COLLISIONS #*/ +})(jQuery); diff --git a/framework/web/js/source/jquery.treeview.async.js b/framework/web/js/source/jquery.treeview.async.js new file mode 100644 index 0000000..219bd93 --- /dev/null +++ b/framework/web/js/source/jquery.treeview.async.js @@ -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);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.treeview.edit.js b/framework/web/js/source/jquery.treeview.edit.js new file mode 100644 index 0000000..990b924 --- /dev/null +++ b/framework/web/js/source/jquery.treeview.edit.js @@ -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);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.treeview.js b/framework/web/js/source/jquery.treeview.js new file mode 100644 index 0000000..2f5b1eb --- /dev/null +++ b/framework/web/js/source/jquery.treeview.js @@ -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);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.yii.js b/framework/web/js/source/jquery.yii.js new file mode 100644 index 0000000..d4ba0f7 --- /dev/null +++ b/framework/web/js/source/jquery.yii.js @@ -0,0 +1,53 @@ +/** + * jQuery Yii plugin file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2010 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: jquery.yii.js 3053 2011-03-12 21:25:33Z qiang.xue $ + */ + +;(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); diff --git a/framework/web/js/source/jquery.yiiactiveform.js b/framework/web/js/source/jquery.yiiactiveform.js new file mode 100644 index 0000000..63d320d --- /dev/null +++ b/framework/web/js/source/jquery.yiiactiveform.js @@ -0,0 +1,426 @@ +/** + * jQuery yiiactiveform plugin file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2010 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: jquery.yiiactiveform.js 3538 2012-01-14 18:24:30Z mdomba $ + * @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.find(':submit').live('mouseup keyup', function () { + $form.data('submitObject', $(this)); + }); + var validated = false; + $form.submit(function () { + if (validated) { + 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.live('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('error'); + }); + $('#' + 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); + 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 + ); + + 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) { + $.each(settings.attributes, function () { + if ($.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, + 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 + /** + * 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 + * focus : undefined, // jquery selector that indicates which element to receive input focus initially + * 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);
\ No newline at end of file diff --git a/framework/web/js/source/jquery.yiitab.js b/framework/web/js/source/jquery.yiitab.js new file mode 100644 index 0000000..09e11fd --- /dev/null +++ b/framework/web/js/source/jquery.yiitab.js @@ -0,0 +1,50 @@ +/** + * jQuery Yii plugin file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2010 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: jquery.yiitab.js 1827 2010-02-20 00:43:32Z qiang.xue $ + */ + +;(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); diff --git a/framework/web/js/source/jui/MIT-LICENSE.txt b/framework/web/js/source/jui/MIT-LICENSE.txt new file mode 100644 index 0000000..be22680 --- /dev/null +++ b/framework/web/js/source/jui/MIT-LICENSE.txt @@ -0,0 +1,25 @@ +Copyright (c) 2011 Paul Bakaus, http://jqueryui.com/ + +This software consists of voluntary contributions made by many +individuals (AUTHORS.txt, http://jqueryui.com/about) For exact +contribution history, see the revision history and logs, available +at http://jquery-ui.googlecode.com/svn/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/framework/web/js/source/jui/css/base/images/ui-bg_flat_0_aaaaaa_40x100.png b/framework/web/js/source/jui/css/base/images/ui-bg_flat_0_aaaaaa_40x100.png Binary files differnew file mode 100644 index 0000000..b3fe56f --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/framework/web/js/source/jui/css/base/images/ui-bg_flat_75_ffffff_40x100.png b/framework/web/js/source/jui/css/base/images/ui-bg_flat_75_ffffff_40x100.png Binary files differnew file mode 100644 index 0000000..f63829a --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-bg_flat_75_ffffff_40x100.png diff --git a/framework/web/js/source/jui/css/base/images/ui-bg_glass_55_fbf9ee_1x400.png b/framework/web/js/source/jui/css/base/images/ui-bg_glass_55_fbf9ee_1x400.png Binary files differnew file mode 100644 index 0000000..e67a294 --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_55_fbf9ee_1x400.png diff --git a/framework/web/js/source/jui/css/base/images/ui-bg_glass_65_ffffff_1x400.png b/framework/web/js/source/jui/css/base/images/ui-bg_glass_65_ffffff_1x400.png Binary files differnew file mode 100644 index 0000000..6a436ad --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_65_ffffff_1x400.png diff --git a/framework/web/js/source/jui/css/base/images/ui-bg_glass_75_dadada_1x400.png b/framework/web/js/source/jui/css/base/images/ui-bg_glass_75_dadada_1x400.png Binary files differnew file mode 100644 index 0000000..a3582db --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_75_dadada_1x400.png diff --git a/framework/web/js/source/jui/css/base/images/ui-bg_glass_75_e6e6e6_1x400.png b/framework/web/js/source/jui/css/base/images/ui-bg_glass_75_e6e6e6_1x400.png Binary files differnew file mode 100644 index 0000000..1bc1520 --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_75_e6e6e6_1x400.png diff --git a/framework/web/js/source/jui/css/base/images/ui-bg_glass_95_fef1ec_1x400.png b/framework/web/js/source/jui/css/base/images/ui-bg_glass_95_fef1ec_1x400.png Binary files differnew file mode 100644 index 0000000..6d54032 --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_95_fef1ec_1x400.png diff --git a/framework/web/js/source/jui/css/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/framework/web/js/source/jui/css/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png Binary files differnew file mode 100644 index 0000000..c07fd06 --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png diff --git a/framework/web/js/source/jui/css/base/images/ui-icons_222222_256x240.png b/framework/web/js/source/jui/css/base/images/ui-icons_222222_256x240.png Binary files differnew file mode 100644 index 0000000..e1994df --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-icons_222222_256x240.png diff --git a/framework/web/js/source/jui/css/base/images/ui-icons_2e83ff_256x240.png b/framework/web/js/source/jui/css/base/images/ui-icons_2e83ff_256x240.png Binary files differnew file mode 100644 index 0000000..42d63db --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-icons_2e83ff_256x240.png diff --git a/framework/web/js/source/jui/css/base/images/ui-icons_454545_256x240.png b/framework/web/js/source/jui/css/base/images/ui-icons_454545_256x240.png Binary files differnew file mode 100644 index 0000000..f076a41 --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-icons_454545_256x240.png diff --git a/framework/web/js/source/jui/css/base/images/ui-icons_888888_256x240.png b/framework/web/js/source/jui/css/base/images/ui-icons_888888_256x240.png Binary files differnew file mode 100644 index 0000000..b53b730 --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-icons_888888_256x240.png diff --git a/framework/web/js/source/jui/css/base/images/ui-icons_cd0a0a_256x240.png b/framework/web/js/source/jui/css/base/images/ui-icons_cd0a0a_256x240.png Binary files differnew file mode 100644 index 0000000..3b8d737 --- /dev/null +++ b/framework/web/js/source/jui/css/base/images/ui-icons_cd0a0a_256x240.png diff --git a/framework/web/js/source/jui/css/base/jquery-ui.css b/framework/web/js/source/jui/css/base/jquery-ui.css new file mode 100644 index 0000000..2beeb3b --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery-ui.css @@ -0,0 +1,10 @@ +/* + * jQuery UI CSS Framework 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ +.ui-helper-hidden{display:none;}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,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%;}.ui-accordion{width:100%;}.ui-accordion .ui-accordion-header{cursor:pointer;position:relative;margin-top:1px;zoom:1;}.ui-accordion .ui-accordion-li-fix{display:inline;}.ui-accordion .ui-accordion-header-active{border-bottom:0!important;}.ui-accordion .ui-accordion-header a{display:block;font-size:1em;padding:.5em .5em .5em .7em;}.ui-accordion-icons .ui-accordion-header a{padding-left:2.2em;}.ui-accordion .ui-accordion-header .ui-icon{position:absolute;left:.5em;top:50%;margin-top:-8px;}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none;zoom:1;}.ui-accordion .ui-accordion-content-active{display:block;}.ui-autocomplete{position:absolute;cursor:default;}* html .ui-autocomplete{width:1px;}.ui-menu{list-style:none;padding:2px;margin:0;display:block;float:left;}.ui-menu .ui-menu{margin-top:-3px;}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;float:left;clear:left;width:100%;}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:.2em .4em;line-height:1.5;zoom:1;}.ui-menu .ui-menu-item a.ui-state-hover,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px;}.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;text-decoration:none!important;cursor:pointer;text-align:center;zoom:1;overflow:visible;}.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;}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none;}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0;}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em;}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px;}.ui-datepicker .ui-datepicker-prev{left:2px;}.ui-datepicker .ui-datepicker-next{right:2px;}.ui-datepicker .ui-datepicker-prev-hover{left:1px;}.ui-datepicker .ui-datepicker-next-hover{right:1px;}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px;}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center;}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0;}.ui-datepicker select.ui-datepicker-month-year{width:100%;}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%;}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em;}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0;}.ui-datepicker td{border:0;padding:1px;}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none;}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0;}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible;}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left;}.ui-datepicker.ui-datepicker-multi{width:auto;}.ui-datepicker-multi .ui-datepicker-group{float:left;}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em;}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%;}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%;}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%;}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0;}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0;}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left;}.ui-datepicker-row-break{clear:both;width:100%;font-size:0;}.ui-datepicker-rtl{direction:rtl;}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto;}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto;}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto;}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto;}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right;}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left;}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:right;}.ui-datepicker-rtl .ui-datepicker-group{float:right;}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px;}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px;}.ui-datepicker-cover{display:none;display:block;position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px;}.ui-dialog{position:absolute;padding:.2em;width:300px;overflow:hidden;}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative;}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0;}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px;}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px;}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0;}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto;zoom:1;}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em;}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right;}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer;}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px;}.ui-draggable .ui-dialog-titlebar{cursor:move;}.ui-progressbar{height:2em;text-align:left;overflow:hidden;}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%;}.ui-resizable{position:relative;}.ui-resizable-handle{position:absolute;font-size:.1px;z-index:99999;display:block;}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none;}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0;}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0;}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%;}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%;}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px;}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px;}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px;}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px;}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black;}.ui-slider{position:relative;text-align:left;}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0;}.ui-slider-horizontal{height:.8em;}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em;}.ui-slider-horizontal .ui-slider-range{top:0;height:100%;}.ui-slider-horizontal .ui-slider-range-min{left:0;}.ui-slider-horizontal .ui-slider-range-max{right:0;}.ui-slider-vertical{width:.8em;height:100px;}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em;}.ui-slider-vertical .ui-slider-range{left:0;width:100%;}.ui-slider-vertical .ui-slider-range-min{bottom:0;}.ui-slider-vertical .ui-slider-range-max{top:0;}.ui-tabs{position:relative;padding:.2em;zoom:1;}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0;}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:1px;margin:0 .2em 1px 0;border-bottom:0!important;padding:0;white-space:nowrap;}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none;}.ui-tabs .ui-tabs-nav li.ui-tabs-selected{margin-bottom:0;padding-bottom:1px;}.ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{cursor:text;}.ui-tabs .ui-tabs-nav li a,.ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a{cursor:pointer;}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none;}.ui-tabs .ui-tabs-hide{display:none!important;}.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em;}.ui-widget .ui-widget{font-size:1em;}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em;}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222;}.ui-widget-content a{color:#222;}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold;}.ui-widget-header a{color:#222;}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555;}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none;}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121;}.ui-state-hover a,.ui-state-hover a:hover{color:#212121;text-decoration:none;}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121;}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none;}.ui-widget :active{outline:none;}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;color:#363636;}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636;}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a;}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a;}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a;}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold;}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal;}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none;}.ui-icon{width:16px;height:16px;background-image:url(images/ui-icons_222222_256x240.png);}.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png);}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_222222_256x240.png);}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png);}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png);}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png);}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png);}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png);}.ui-icon-carat-1-n{background-position:0 0;}.ui-icon-carat-1-ne{background-position:-16px 0;}.ui-icon-carat-1-e{background-position:-32px 0;}.ui-icon-carat-1-se{background-position:-48px 0;}.ui-icon-carat-1-s{background-position:-64px 0;}.ui-icon-carat-1-sw{background-position:-80px 0;}.ui-icon-carat-1-w{background-position:-96px 0;}.ui-icon-carat-1-nw{background-position:-112px 0;}.ui-icon-carat-2-n-s{background-position:-128px 0;}.ui-icon-carat-2-e-w{background-position:-144px 0;}.ui-icon-triangle-1-n{background-position:0 -16px;}.ui-icon-triangle-1-ne{background-position:-16px -16px;}.ui-icon-triangle-1-e{background-position:-32px -16px;}.ui-icon-triangle-1-se{background-position:-48px -16px;}.ui-icon-triangle-1-s{background-position:-64px -16px;}.ui-icon-triangle-1-sw{background-position:-80px -16px;}.ui-icon-triangle-1-w{background-position:-96px -16px;}.ui-icon-triangle-1-nw{background-position:-112px -16px;}.ui-icon-triangle-2-n-s{background-position:-128px -16px;}.ui-icon-triangle-2-e-w{background-position:-144px -16px;}.ui-icon-arrow-1-n{background-position:0 -32px;}.ui-icon-arrow-1-ne{background-position:-16px -32px;}.ui-icon-arrow-1-e{background-position:-32px -32px;}.ui-icon-arrow-1-se{background-position:-48px -32px;}.ui-icon-arrow-1-s{background-position:-64px -32px;}.ui-icon-arrow-1-sw{background-position:-80px -32px;}.ui-icon-arrow-1-w{background-position:-96px -32px;}.ui-icon-arrow-1-nw{background-position:-112px -32px;}.ui-icon-arrow-2-n-s{background-position:-128px -32px;}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px;}.ui-icon-arrow-2-e-w{background-position:-160px -32px;}.ui-icon-arrow-2-se-nw{background-position:-176px -32px;}.ui-icon-arrowstop-1-n{background-position:-192px -32px;}.ui-icon-arrowstop-1-e{background-position:-208px -32px;}.ui-icon-arrowstop-1-s{background-position:-224px -32px;}.ui-icon-arrowstop-1-w{background-position:-240px -32px;}.ui-icon-arrowthick-1-n{background-position:0 -48px;}.ui-icon-arrowthick-1-ne{background-position:-16px -48px;}.ui-icon-arrowthick-1-e{background-position:-32px -48px;}.ui-icon-arrowthick-1-se{background-position:-48px -48px;}.ui-icon-arrowthick-1-s{background-position:-64px -48px;}.ui-icon-arrowthick-1-sw{background-position:-80px -48px;}.ui-icon-arrowthick-1-w{background-position:-96px -48px;}.ui-icon-arrowthick-1-nw{background-position:-112px -48px;}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px;}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px;}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px;}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px;}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px;}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px;}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px;}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px;}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px;}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px;}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px;}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px;}.ui-icon-arrowreturn-1-w{background-position:-64px -64px;}.ui-icon-arrowreturn-1-n{background-position:-80px -64px;}.ui-icon-arrowreturn-1-e{background-position:-96px -64px;}.ui-icon-arrowreturn-1-s{background-position:-112px -64px;}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px;}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px;}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px;}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px;}.ui-icon-arrow-4{background-position:0 -80px;}.ui-icon-arrow-4-diag{background-position:-16px -80px;}.ui-icon-extlink{background-position:-32px -80px;}.ui-icon-newwin{background-position:-48px -80px;}.ui-icon-refresh{background-position:-64px -80px;}.ui-icon-shuffle{background-position:-80px -80px;}.ui-icon-transfer-e-w{background-position:-96px -80px;}.ui-icon-transferthick-e-w{background-position:-112px -80px;}.ui-icon-folder-collapsed{background-position:0 -96px;}.ui-icon-folder-open{background-position:-16px -96px;}.ui-icon-document{background-position:-32px -96px;}.ui-icon-document-b{background-position:-48px -96px;}.ui-icon-note{background-position:-64px -96px;}.ui-icon-mail-closed{background-position:-80px -96px;}.ui-icon-mail-open{background-position:-96px -96px;}.ui-icon-suitcase{background-position:-112px -96px;}.ui-icon-comment{background-position:-128px -96px;}.ui-icon-person{background-position:-144px -96px;}.ui-icon-print{background-position:-160px -96px;}.ui-icon-trash{background-position:-176px -96px;}.ui-icon-locked{background-position:-192px -96px;}.ui-icon-unlocked{background-position:-208px -96px;}.ui-icon-bookmark{background-position:-224px -96px;}.ui-icon-tag{background-position:-240px -96px;}.ui-icon-home{background-position:0 -112px;}.ui-icon-flag{background-position:-16px -112px;}.ui-icon-calendar{background-position:-32px -112px;}.ui-icon-cart{background-position:-48px -112px;}.ui-icon-pencil{background-position:-64px -112px;}.ui-icon-clock{background-position:-80px -112px;}.ui-icon-disk{background-position:-96px -112px;}.ui-icon-calculator{background-position:-112px -112px;}.ui-icon-zoomin{background-position:-128px -112px;}.ui-icon-zoomout{background-position:-144px -112px;}.ui-icon-search{background-position:-160px -112px;}.ui-icon-wrench{background-position:-176px -112px;}.ui-icon-gear{background-position:-192px -112px;}.ui-icon-heart{background-position:-208px -112px;}.ui-icon-star{background-position:-224px -112px;}.ui-icon-link{background-position:-240px -112px;}.ui-icon-cancel{background-position:0 -128px;}.ui-icon-plus{background-position:-16px -128px;}.ui-icon-plusthick{background-position:-32px -128px;}.ui-icon-minus{background-position:-48px -128px;}.ui-icon-minusthick{background-position:-64px -128px;}.ui-icon-close{background-position:-80px -128px;}.ui-icon-closethick{background-position:-96px -128px;}.ui-icon-key{background-position:-112px -128px;}.ui-icon-lightbulb{background-position:-128px -128px;}.ui-icon-scissors{background-position:-144px -128px;}.ui-icon-clipboard{background-position:-160px -128px;}.ui-icon-copy{background-position:-176px -128px;}.ui-icon-contact{background-position:-192px -128px;}.ui-icon-image{background-position:-208px -128px;}.ui-icon-video{background-position:-224px -128px;}.ui-icon-script{background-position:-240px -128px;}.ui-icon-alert{background-position:0 -144px;}.ui-icon-info{background-position:-16px -144px;}.ui-icon-notice{background-position:-32px -144px;}.ui-icon-help{background-position:-48px -144px;}.ui-icon-check{background-position:-64px -144px;}.ui-icon-bullet{background-position:-80px -144px;}.ui-icon-radio-off{background-position:-96px -144px;}.ui-icon-radio-on{background-position:-112px -144px;}.ui-icon-pin-w{background-position:-128px -144px;}.ui-icon-pin-s{background-position:-144px -144px;}.ui-icon-play{background-position:0 -160px;}.ui-icon-pause{background-position:-16px -160px;}.ui-icon-seek-next{background-position:-32px -160px;}.ui-icon-seek-prev{background-position:-48px -160px;}.ui-icon-seek-end{background-position:-64px -160px;}.ui-icon-seek-start{background-position:-80px -160px;}.ui-icon-seek-first{background-position:-80px -160px;}.ui-icon-stop{background-position:-96px -160px;}.ui-icon-eject{background-position:-112px -160px;}.ui-icon-volume-off{background-position:-128px -160px;}.ui-icon-volume-on{background-position:-144px -160px;}.ui-icon-power{background-position:0 -176px;}.ui-icon-signal-diag{background-position:-16px -176px;}.ui-icon-signal{background-position:-32px -176px;}.ui-icon-battery-0{background-position:-48px -176px;}.ui-icon-battery-1{background-position:-64px -176px;}.ui-icon-battery-2{background-position:-80px -176px;}.ui-icon-battery-3{background-position:-96px -176px;}.ui-icon-circle-plus{background-position:0 -192px;}.ui-icon-circle-minus{background-position:-16px -192px;}.ui-icon-circle-close{background-position:-32px -192px;}.ui-icon-circle-triangle-e{background-position:-48px -192px;}.ui-icon-circle-triangle-s{background-position:-64px -192px;}.ui-icon-circle-triangle-w{background-position:-80px -192px;}.ui-icon-circle-triangle-n{background-position:-96px -192px;}.ui-icon-circle-arrow-e{background-position:-112px -192px;}.ui-icon-circle-arrow-s{background-position:-128px -192px;}.ui-icon-circle-arrow-w{background-position:-144px -192px;}.ui-icon-circle-arrow-n{background-position:-160px -192px;}.ui-icon-circle-zoomin{background-position:-176px -192px;}.ui-icon-circle-zoomout{background-position:-192px -192px;}.ui-icon-circle-check{background-position:-208px -192px;}.ui-icon-circlesmall-plus{background-position:0 -208px;}.ui-icon-circlesmall-minus{background-position:-16px -208px;}.ui-icon-circlesmall-close{background-position:-32px -208px;}.ui-icon-squaresmall-plus{background-position:-48px -208px;}.ui-icon-squaresmall-minus{background-position:-64px -208px;}.ui-icon-squaresmall-close{background-position:-80px -208px;}.ui-icon-grip-dotted-vertical{background-position:0 -224px;}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px;}.ui-icon-grip-solid-vertical{background-position:-32px -224px;}.ui-icon-grip-solid-horizontal{background-position:-48px -224px;}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px;}.ui-icon-grip-diagonal-se{background-position:-80px -224px;}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;-khtml-border-top-left-radius:4px;border-top-left-radius:4px;}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;-khtml-border-top-right-radius:4px;border-top-right-radius:4px;}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;-khtml-border-bottom-left-radius:4px;border-bottom-left-radius:4px;}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;-khtml-border-bottom-right-radius:4px;border-bottom-right-radius:4px;}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);-moz-border-radius:8px;-khtml-border-radius:8px;-webkit-border-radius:8px;border-radius:8px;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.accordion.css b/framework/web/js/source/jui/css/base/jquery.ui.accordion.css new file mode 100644 index 0000000..83ebfa0 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.accordion.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Accordion 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Accordion#theming + */ +.ui-accordion{width:100%;}.ui-accordion .ui-accordion-header{cursor:pointer;position:relative;margin-top:1px;zoom:1;}.ui-accordion .ui-accordion-li-fix{display:inline;}.ui-accordion .ui-accordion-header-active{border-bottom:0!important;}.ui-accordion .ui-accordion-header a{display:block;font-size:1em;padding:.5em .5em .5em .7em;}.ui-accordion-icons .ui-accordion-header a{padding-left:2.2em;}.ui-accordion .ui-accordion-header .ui-icon{position:absolute;left:.5em;top:50%;margin-top:-8px;}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;margin-top:-2px;position:relative;top:1px;margin-bottom:2px;overflow:auto;display:none;zoom:1;}.ui-accordion .ui-accordion-content-active{display:block;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.all.css b/framework/web/js/source/jui/css/base/jquery.ui.all.css new file mode 100644 index 0000000..ab3e6d5 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.all.css @@ -0,0 +1,10 @@ +/* + * jQuery UI CSS Framework 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming + */ +@import "jquery.ui.base.min.css";@import "jquery.ui.theme.min.css";
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.autocomplete.css b/framework/web/js/source/jui/css/base/jquery.ui.autocomplete.css new file mode 100644 index 0000000..bcf6882 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.autocomplete.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Autocomplete 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete{position:absolute;cursor:default;}* html .ui-autocomplete{width:1px;}.ui-menu{list-style:none;padding:2px;margin:0;display:block;float:left;}.ui-menu .ui-menu{margin-top:-3px;}.ui-menu .ui-menu-item{margin:0;padding:0;zoom:1;float:left;clear:left;width:100%;}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:.2em .4em;line-height:1.5;zoom:1;}.ui-menu .ui-menu-item a.ui-state-hover,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.base.css b/framework/web/js/source/jui/css/base/jquery.ui.base.css new file mode 100644 index 0000000..d4bfac4 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.base.css @@ -0,0 +1,10 @@ +/* + * jQuery UI CSS Framework 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming + */ +@import url("jquery.ui.core.min.css");@import url("jquery.ui.accordion.min.css");@import url("jquery.ui.autocomplete.min.css");@import url("jquery.ui.button.min.css");@import url("jquery.ui.datepicker.min.css");@import url("jquery.ui.dialog.min.css");@import url("jquery.ui.progressbar.min.css");@import url("jquery.ui.resizable.min.css");@import url("jquery.ui.selectable.min.css");@import url("jquery.ui.slider.min.css");@import url("jquery.ui.tabs.min.css");
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.button.css b/framework/web/js/source/jui/css/base/jquery.ui.button.css new file mode 100644 index 0000000..b25ffbe --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.button.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Button 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button{display:inline-block;position:relative;padding:0;margin-right:.1em;text-decoration:none!important;cursor:pointer;text-align:center;zoom:1;overflow:visible;}.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;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.core.css b/framework/web/js/source/jui/css/base/jquery.ui.core.css new file mode 100644 index 0000000..ee23d09 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.core.css @@ -0,0 +1,10 @@ +/* + * jQuery UI CSS Framework 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + */ +.ui-helper-hidden{display:none;}.ui-helper-hidden-accessible{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,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%;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.datepicker.css b/framework/web/js/source/jui/css/base/jquery.ui.datepicker.css new file mode 100644 index 0000000..f61ade6 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.datepicker.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Datepicker 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker{width:17em;padding:.2em .2em 0;display:none;}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0;}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em;}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px;}.ui-datepicker .ui-datepicker-prev{left:2px;}.ui-datepicker .ui-datepicker-next{right:2px;}.ui-datepicker .ui-datepicker-prev-hover{left:1px;}.ui-datepicker .ui-datepicker-next-hover{right:1px;}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px;}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center;}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0;}.ui-datepicker select.ui-datepicker-month-year{width:100%;}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%;}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em;}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0;}.ui-datepicker td{border:0;padding:1px;}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none;}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0;}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible;}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left;}.ui-datepicker.ui-datepicker-multi{width:auto;}.ui-datepicker-multi .ui-datepicker-group{float:left;}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em;}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%;}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%;}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%;}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header{border-left-width:0;}.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0;}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left;}.ui-datepicker-row-break{clear:both;width:100%;font-size:0;}.ui-datepicker-rtl{direction:rtl;}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto;}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto;}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto;}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto;}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right;}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left;}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current{float:right;}.ui-datepicker-rtl .ui-datepicker-group{float:right;}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header{border-right-width:0;border-left-width:1px;}.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px;}.ui-datepicker-cover{display:none;display:block;position:absolute;z-index:-1;filter:mask();top:-4px;left:-4px;width:200px;height:200px;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.dialog.css b/framework/web/js/source/jui/css/base/jquery.ui.dialog.css new file mode 100644 index 0000000..dea4ecb --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.dialog.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Dialog 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog{position:absolute;padding:.2em;width:300px;overflow:hidden;}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative;}.ui-dialog .ui-dialog-title{float:left;margin:.1em 16px .1em 0;}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:19px;margin:-10px 0 0 0;padding:1px;height:18px;}.ui-dialog .ui-dialog-titlebar-close span{display:block;margin:1px;}.ui-dialog .ui-dialog-titlebar-close:hover,.ui-dialog .ui-dialog-titlebar-close:focus{padding:0;}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto;zoom:1;}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin:.5em 0 0 0;padding:.3em 1em .5em .4em;}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right;}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer;}.ui-dialog .ui-resizable-se{width:14px;height:14px;right:3px;bottom:3px;}.ui-draggable .ui-dialog-titlebar{cursor:move;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.progressbar.css b/framework/web/js/source/jui/css/base/jquery.ui.progressbar.css new file mode 100644 index 0000000..632e539 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.progressbar.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Progressbar 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar{height:2em;text-align:left;overflow:hidden;}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.resizable.css b/framework/web/js/source/jui/css/base/jquery.ui.resizable.css new file mode 100644 index 0000000..adc7356 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.resizable.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Resizable 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable{position:relative;}.ui-resizable-handle{position:absolute;font-size:.1px;z-index:99999;display:block;}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none;}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0;}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0;}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%;}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%;}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px;}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px;}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px;}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.selectable.css b/framework/web/js/source/jui/css/base/jquery.ui.selectable.css new file mode 100644 index 0000000..e6da804 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.selectable.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Selectable 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.slider.css b/framework/web/js/source/jui/css/base/jquery.ui.slider.css new file mode 100644 index 0000000..8e23cd5 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.slider.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Slider 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider{position:relative;text-align:left;}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0;}.ui-slider-horizontal{height:.8em;}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em;}.ui-slider-horizontal .ui-slider-range{top:0;height:100%;}.ui-slider-horizontal .ui-slider-range-min{left:0;}.ui-slider-horizontal .ui-slider-range-max{right:0;}.ui-slider-vertical{width:.8em;height:100px;}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em;}.ui-slider-vertical .ui-slider-range{left:0;width:100%;}.ui-slider-vertical .ui-slider-range-min{bottom:0;}.ui-slider-vertical .ui-slider-range-max{top:0;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.tabs.css b/framework/web/js/source/jui/css/base/jquery.ui.tabs.css new file mode 100644 index 0000000..1064ea4 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.tabs.css @@ -0,0 +1,10 @@ +/* + * jQuery UI Tabs 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs{position:relative;padding:.2em;zoom:1;}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0;}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:1px;margin:0 .2em 1px 0;border-bottom:0!important;padding:0;white-space:nowrap;}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none;}.ui-tabs .ui-tabs-nav li.ui-tabs-selected{margin-bottom:0;padding-bottom:1px;}.ui-tabs .ui-tabs-nav li.ui-tabs-selected a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-state-processing a{cursor:text;}.ui-tabs .ui-tabs-nav li a,.ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a{cursor:pointer;}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none;}.ui-tabs .ui-tabs-hide{display:none!important;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/css/base/jquery.ui.theme.css b/framework/web/js/source/jui/css/base/jquery.ui.theme.css new file mode 100644 index 0000000..b132f63 --- /dev/null +++ b/framework/web/js/source/jui/css/base/jquery.ui.theme.css @@ -0,0 +1,12 @@ +/* + * jQuery UI CSS Framework 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/ + */ +.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em;}.ui-widget .ui-widget{font-size:1em;}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em;}.ui-widget-content{border:1px solid #aaa;background:#fff url(images/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x;color:#222;}.ui-widget-content a{color:#222;}.ui-widget-header{border:1px solid #aaa;background:#ccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x;color:#222;font-weight:bold;}.ui-widget-header a{color:#222;}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url(images/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#555;}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none;}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url(images/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121;}.ui-state-hover a,.ui-state-hover a:hover{color:#212121;text-decoration:none;}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x;font-weight:normal;color:#212121;}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none;}.ui-widget :active{outline:none;}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url(images/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x;color:#363636;}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636;}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a;}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a;}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a;}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold;}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal;}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none;}.ui-icon{width:16px;height:16px;background-image:url(images/ui-icons_222222_256x240.png);}.ui-widget-content .ui-icon{background-image:url(images/ui-icons_222222_256x240.png);}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_222222_256x240.png);}.ui-state-default .ui-icon{background-image:url(images/ui-icons_888888_256x240.png);}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_454545_256x240.png);}.ui-state-active .ui-icon{background-image:url(images/ui-icons_454545_256x240.png);}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png);}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png);}.ui-icon-carat-1-n{background-position:0 0;}.ui-icon-carat-1-ne{background-position:-16px 0;}.ui-icon-carat-1-e{background-position:-32px 0;}.ui-icon-carat-1-se{background-position:-48px 0;}.ui-icon-carat-1-s{background-position:-64px 0;}.ui-icon-carat-1-sw{background-position:-80px 0;}.ui-icon-carat-1-w{background-position:-96px 0;}.ui-icon-carat-1-nw{background-position:-112px 0;}.ui-icon-carat-2-n-s{background-position:-128px 0;}.ui-icon-carat-2-e-w{background-position:-144px 0;}.ui-icon-triangle-1-n{background-position:0 -16px;}.ui-icon-triangle-1-ne{background-position:-16px -16px;}.ui-icon-triangle-1-e{background-position:-32px -16px;}.ui-icon-triangle-1-se{background-position:-48px -16px;}.ui-icon-triangle-1-s{background-position:-64px -16px;}.ui-icon-triangle-1-sw{background-position:-80px -16px;}.ui-icon-triangle-1-w{background-position:-96px -16px;}.ui-icon-triangle-1-nw{background-position:-112px -16px;}.ui-icon-triangle-2-n-s{background-position:-128px -16px;}.ui-icon-triangle-2-e-w{background-position:-144px -16px;}.ui-icon-arrow-1-n{background-position:0 -32px;}.ui-icon-arrow-1-ne{background-position:-16px -32px;}.ui-icon-arrow-1-e{background-position:-32px -32px;}.ui-icon-arrow-1-se{background-position:-48px -32px;}.ui-icon-arrow-1-s{background-position:-64px -32px;}.ui-icon-arrow-1-sw{background-position:-80px -32px;}.ui-icon-arrow-1-w{background-position:-96px -32px;}.ui-icon-arrow-1-nw{background-position:-112px -32px;}.ui-icon-arrow-2-n-s{background-position:-128px -32px;}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px;}.ui-icon-arrow-2-e-w{background-position:-160px -32px;}.ui-icon-arrow-2-se-nw{background-position:-176px -32px;}.ui-icon-arrowstop-1-n{background-position:-192px -32px;}.ui-icon-arrowstop-1-e{background-position:-208px -32px;}.ui-icon-arrowstop-1-s{background-position:-224px -32px;}.ui-icon-arrowstop-1-w{background-position:-240px -32px;}.ui-icon-arrowthick-1-n{background-position:0 -48px;}.ui-icon-arrowthick-1-ne{background-position:-16px -48px;}.ui-icon-arrowthick-1-e{background-position:-32px -48px;}.ui-icon-arrowthick-1-se{background-position:-48px -48px;}.ui-icon-arrowthick-1-s{background-position:-64px -48px;}.ui-icon-arrowthick-1-sw{background-position:-80px -48px;}.ui-icon-arrowthick-1-w{background-position:-96px -48px;}.ui-icon-arrowthick-1-nw{background-position:-112px -48px;}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px;}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px;}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px;}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px;}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px;}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px;}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px;}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px;}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px;}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px;}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px;}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px;}.ui-icon-arrowreturn-1-w{background-position:-64px -64px;}.ui-icon-arrowreturn-1-n{background-position:-80px -64px;}.ui-icon-arrowreturn-1-e{background-position:-96px -64px;}.ui-icon-arrowreturn-1-s{background-position:-112px -64px;}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px;}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px;}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px;}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px;}.ui-icon-arrow-4{background-position:0 -80px;}.ui-icon-arrow-4-diag{background-position:-16px -80px;}.ui-icon-extlink{background-position:-32px -80px;}.ui-icon-newwin{background-position:-48px -80px;}.ui-icon-refresh{background-position:-64px -80px;}.ui-icon-shuffle{background-position:-80px -80px;}.ui-icon-transfer-e-w{background-position:-96px -80px;}.ui-icon-transferthick-e-w{background-position:-112px -80px;}.ui-icon-folder-collapsed{background-position:0 -96px;}.ui-icon-folder-open{background-position:-16px -96px;}.ui-icon-document{background-position:-32px -96px;}.ui-icon-document-b{background-position:-48px -96px;}.ui-icon-note{background-position:-64px -96px;}.ui-icon-mail-closed{background-position:-80px -96px;}.ui-icon-mail-open{background-position:-96px -96px;}.ui-icon-suitcase{background-position:-112px -96px;}.ui-icon-comment{background-position:-128px -96px;}.ui-icon-person{background-position:-144px -96px;}.ui-icon-print{background-position:-160px -96px;}.ui-icon-trash{background-position:-176px -96px;}.ui-icon-locked{background-position:-192px -96px;}.ui-icon-unlocked{background-position:-208px -96px;}.ui-icon-bookmark{background-position:-224px -96px;}.ui-icon-tag{background-position:-240px -96px;}.ui-icon-home{background-position:0 -112px;}.ui-icon-flag{background-position:-16px -112px;}.ui-icon-calendar{background-position:-32px -112px;}.ui-icon-cart{background-position:-48px -112px;}.ui-icon-pencil{background-position:-64px -112px;}.ui-icon-clock{background-position:-80px -112px;}.ui-icon-disk{background-position:-96px -112px;}.ui-icon-calculator{background-position:-112px -112px;}.ui-icon-zoomin{background-position:-128px -112px;}.ui-icon-zoomout{background-position:-144px -112px;}.ui-icon-search{background-position:-160px -112px;}.ui-icon-wrench{background-position:-176px -112px;}.ui-icon-gear{background-position:-192px -112px;}.ui-icon-heart{background-position:-208px -112px;}.ui-icon-star{background-position:-224px -112px;}.ui-icon-link{background-position:-240px -112px;}.ui-icon-cancel{background-position:0 -128px;}.ui-icon-plus{background-position:-16px -128px;}.ui-icon-plusthick{background-position:-32px -128px;}.ui-icon-minus{background-position:-48px -128px;}.ui-icon-minusthick{background-position:-64px -128px;}.ui-icon-close{background-position:-80px -128px;}.ui-icon-closethick{background-position:-96px -128px;}.ui-icon-key{background-position:-112px -128px;}.ui-icon-lightbulb{background-position:-128px -128px;}.ui-icon-scissors{background-position:-144px -128px;}.ui-icon-clipboard{background-position:-160px -128px;}.ui-icon-copy{background-position:-176px -128px;}.ui-icon-contact{background-position:-192px -128px;}.ui-icon-image{background-position:-208px -128px;}.ui-icon-video{background-position:-224px -128px;}.ui-icon-script{background-position:-240px -128px;}.ui-icon-alert{background-position:0 -144px;}.ui-icon-info{background-position:-16px -144px;}.ui-icon-notice{background-position:-32px -144px;}.ui-icon-help{background-position:-48px -144px;}.ui-icon-check{background-position:-64px -144px;}.ui-icon-bullet{background-position:-80px -144px;}.ui-icon-radio-off{background-position:-96px -144px;}.ui-icon-radio-on{background-position:-112px -144px;}.ui-icon-pin-w{background-position:-128px -144px;}.ui-icon-pin-s{background-position:-144px -144px;}.ui-icon-play{background-position:0 -160px;}.ui-icon-pause{background-position:-16px -160px;}.ui-icon-seek-next{background-position:-32px -160px;}.ui-icon-seek-prev{background-position:-48px -160px;}.ui-icon-seek-end{background-position:-64px -160px;}.ui-icon-seek-start{background-position:-80px -160px;}.ui-icon-seek-first{background-position:-80px -160px;}.ui-icon-stop{background-position:-96px -160px;}.ui-icon-eject{background-position:-112px -160px;}.ui-icon-volume-off{background-position:-128px -160px;}.ui-icon-volume-on{background-position:-144px -160px;}.ui-icon-power{background-position:0 -176px;}.ui-icon-signal-diag{background-position:-16px -176px;}.ui-icon-signal{background-position:-32px -176px;}.ui-icon-battery-0{background-position:-48px -176px;}.ui-icon-battery-1{background-position:-64px -176px;}.ui-icon-battery-2{background-position:-80px -176px;}.ui-icon-battery-3{background-position:-96px -176px;}.ui-icon-circle-plus{background-position:0 -192px;}.ui-icon-circle-minus{background-position:-16px -192px;}.ui-icon-circle-close{background-position:-32px -192px;}.ui-icon-circle-triangle-e{background-position:-48px -192px;}.ui-icon-circle-triangle-s{background-position:-64px -192px;}.ui-icon-circle-triangle-w{background-position:-80px -192px;}.ui-icon-circle-triangle-n{background-position:-96px -192px;}.ui-icon-circle-arrow-e{background-position:-112px -192px;}.ui-icon-circle-arrow-s{background-position:-128px -192px;}.ui-icon-circle-arrow-w{background-position:-144px -192px;}.ui-icon-circle-arrow-n{background-position:-160px -192px;}.ui-icon-circle-zoomin{background-position:-176px -192px;}.ui-icon-circle-zoomout{background-position:-192px -192px;}.ui-icon-circle-check{background-position:-208px -192px;}.ui-icon-circlesmall-plus{background-position:0 -208px;}.ui-icon-circlesmall-minus{background-position:-16px -208px;}.ui-icon-circlesmall-close{background-position:-32px -208px;}.ui-icon-squaresmall-plus{background-position:-48px -208px;}.ui-icon-squaresmall-minus{background-position:-64px -208px;}.ui-icon-squaresmall-close{background-position:-80px -208px;}.ui-icon-grip-dotted-vertical{background-position:0 -224px;}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px;}.ui-icon-grip-solid-vertical{background-position:-32px -224px;}.ui-icon-grip-solid-horizontal{background-position:-48px -224px;}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px;}.ui-icon-grip-diagonal-se{background-position:-80px -224px;}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{-moz-border-radius-topleft:4px;-webkit-border-top-left-radius:4px;-khtml-border-top-left-radius:4px;border-top-left-radius:4px;}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{-moz-border-radius-topright:4px;-webkit-border-top-right-radius:4px;-khtml-border-top-right-radius:4px;border-top-right-radius:4px;}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{-moz-border-radius-bottomleft:4px;-webkit-border-bottom-left-radius:4px;-khtml-border-bottom-left-radius:4px;border-bottom-left-radius:4px;}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{-moz-border-radius-bottomright:4px;-webkit-border-bottom-right-radius:4px;-khtml-border-bottom-right-radius:4px;border-bottom-right-radius:4px;}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);-moz-border-radius:8px;-khtml-border-radius:8px;-webkit-border-radius:8px;border-radius:8px;}
\ No newline at end of file diff --git a/framework/web/js/source/jui/js/jquery-ui-i18n.min.js b/framework/web/js/source/jui/js/jquery-ui-i18n.min.js new file mode 100644 index 0000000..f2b9e6d --- /dev/null +++ b/framework/web/js/source/jui/js/jquery-ui-i18n.min.js @@ -0,0 +1,2 @@ +/* Afrikaans initialisation for the jQuery UI date picker plugin. *//* Written by Renier Pretorius. */jQuery(function(a){a.datepicker.regional.af={closeText:"Selekteer",prevText:"Vorige",nextText:"Volgende",currentText:"Vandag",monthNames:["Januarie","Februarie","Maart","April","Mei","Junie","Julie","Augustus","September","Oktober","November","Desember"],monthNamesShort:["Jan","Feb","Mrt","Apr","Mei","Jun","Jul","Aug","Sep","Okt","Nov","Des"],dayNames:["Sondag","Maandag","Dinsdag","Woensdag","Donderdag","Vrydag","Saterdag"],dayNamesShort:["Son","Maa","Din","Woe","Don","Vry","Sat"],dayNamesMin:["So","Ma","Di","Wo","Do","Vr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.af)}),jQuery(function(a){a.datepicker.regional["ar-DZ"]={closeText:"إغلاق",prevText:"<السابق",nextText:"التالي>",currentText:"اليوم",monthNames:["جانÙÙŠ","ÙÙŠÙØ±ÙŠ","مارس","Ø£ÙØ±ÙŠÙ„","ماي","جوان","جويلية","أوت","سبتمبر","أكتوبر","نوÙمبر","ديسمبر"],monthNamesShort:["1","2","3","4","5","6","7","8","9","10","11","12"],dayNames:["Ø§Ù„Ø£ØØ¯","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesShort:["Ø§Ù„Ø£ØØ¯","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesMin:["Ø§Ù„Ø£ØØ¯","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],weekHeader:"أسبوع",dateFormat:"dd/mm/yy",firstDay:6,isRTL:!0,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["ar-DZ"])}),jQuery(function(a){a.datepicker.regional.ar={closeText:"إغلاق",prevText:"<السابق",nextText:"التالي>",currentText:"اليوم",monthNames:["كانون الثاني","شباط","آذار","نيسان","مايو","ØØ²ÙŠØ±Ø§Ù†","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول"],monthNamesShort:["1","2","3","4","5","6","7","8","9","10","11","12"],dayNames:["Ø§Ù„Ø£ØØ¯","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesShort:["Ø§Ù„Ø£ØØ¯","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],dayNamesMin:["Ø§Ù„Ø£ØØ¯","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت"],weekHeader:"أسبوع",dateFormat:"dd/mm/yy",firstDay:6,isRTL:!0,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.ar)}),jQuery(function(a){a.datepicker.regional.az={closeText:"BaÄŸla",prevText:"<Geri",nextText:"İrÉ™li>",currentText:"Bugün",monthNames:["Yanvar","Fevral","Mart","Aprel","May","İyun","İyul","Avqust","Sentyabr","Oktyabr","Noyabr","Dekabr"],monthNamesShort:["Yan","Fev","Mar","Apr","May","İyun","İyul","Avq","Sen","Okt","Noy","Dek"],dayNames:["Bazar","Bazar ertÉ™si","ÇərÅŸÉ™nbÉ™ axÅŸamı","ÇərÅŸÉ™nbÉ™","CümÉ™ axÅŸamı","CümÉ™","ŞənbÉ™"],dayNamesShort:["B","Be","Ça","Ç","Ca","C","Åž"],dayNamesMin:["B","B","Ç","С","Ç","C","Åž"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.az)}),jQuery(function(a){a.datepicker.regional.bg={closeText:"затвори",prevText:"<назад",nextText:"напред>",nextBigText:">>",currentText:"днеÑ",monthNames:["Януари","Февруари","Март","Ðприл","Май","Юни","Юли","ÐвгуÑÑ‚","Септември","Октомври","Ðоември","Декември"],monthNamesShort:["Яну","Фев","Мар","Ðпр","Май","Юни","Юли","Ðвг","Сеп","Окт","Ðов","Дек"],dayNames:["ÐеделÑ","Понеделник","Вторник","СрÑда","Четвъртък","Петък","Събота"],dayNamesShort:["Ðед","Пон","Вто","СрÑ","Чет","Пет","Съб"],dayNamesMin:["Ðе","По","Ð’Ñ‚","Ср","Че","Пе","Съ"],weekHeader:"Wk",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.bg)}),jQuery(function(a){a.datepicker.regional.bs={closeText:"Zatvori",prevText:"<",nextText:">",currentText:"Danas",monthNames:["Januar","Februar","Mart","April","Maj","Juni","Juli","August","Septembar","Oktobar","Novembar","Decembar"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Nedelja","Ponedeljak","Utorak","Srijeda","ÄŒetvrtak","Petak","Subota"],dayNamesShort:["Ned","Pon","Uto","Sri","ÄŒet","Pet","Sub"],dayNamesMin:["Ne","Po","Ut","Sr","ÄŒe","Pe","Su"],weekHeader:"Wk",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.bs)}),jQuery(function(a){a.datepicker.regional.ca={closeText:"Tancar",prevText:"<Ant",nextText:"Seg>",currentText:"Avui",monthNames:["Gener","Febrer","Març","Abril","Maig","Juny","Juliol","Agost","Setembre","Octubre","Novembre","Desembre"],monthNamesShort:["Gen","Feb","Mar","Abr","Mai","Jun","Jul","Ago","Set","Oct","Nov","Des"],dayNames:["Diumenge","Dilluns","Dimarts","Dimecres","Dijous","Divendres","Dissabte"],dayNamesShort:["Dug","Dln","Dmt","Dmc","Djs","Dvn","Dsb"],dayNamesMin:["Dg","Dl","Dt","Dc","Dj","Dv","Ds"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.ca)}),jQuery(function(a){a.datepicker.regional.cs={closeText:"ZavÅ™Ãt",prevText:"<DÅ™Ãve",nextText:"PozdÄ›ji>",currentText:"NynÃ",monthNames:["leden","únor","bÅ™ezen","duben","kvÄ›ten","Äerven","Äervenec","srpen","zářÃ","Å™Ãjen","listopad","prosinec"],monthNamesShort:["led","úno","bÅ™e","dub","kvÄ›","Äer","Ävc","srp","zář","Å™Ãj","lis","pro"],dayNames:["nedÄ›le","pondÄ›lÃ","úterý","stÅ™eda","Ätvrtek","pátek","sobota"],dayNamesShort:["ne","po","út","st","Ät","pá","so"],dayNamesMin:["ne","po","út","st","Ät","pá","so"],weekHeader:"Týd",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.cs)}),jQuery(function(a){a.datepicker.regional["cy-GB"]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["Ionawr","Chwefror","Mawrth","Ebrill","Mai","Mehefin","Gorffennaf","Awst","Medi","Hydref","Tachwedd","Rhagfyr"],monthNamesShort:["Ion","Chw","Maw","Ebr","Mai","Meh","Gor","Aws","Med","Hyd","Tac","Rha"],dayNames:["Dydd Sul","Dydd Llun","Dydd Mawrth","Dydd Mercher","Dydd Iau","Dydd Gwener","Dydd Sadwrn"],dayNamesShort:["Sul","Llu","Maw","Mer","Iau","Gwe","Sad"],dayNamesMin:["Su","Ll","Ma","Me","Ia","Gw","Sa"],weekHeader:"Wy",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["cy-GB"])}),jQuery(function(a){a.datepicker.regional.da={closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.da)}),jQuery(function(a){a.datepicker.regional.de={closeText:"schließen",prevText:"<zurück",nextText:"Vor>",currentText:"heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"Wo",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.de)}),jQuery(function(a){a.datepicker.regional.el={closeText:"Κλείσιμο",prevText:"Î ÏοηγοÏμενος",nextText:"Επόμενος",currentText:"ΤÏÎχων Μήνας",monthNames:["ΙανουάÏιος","ΦεβÏουάÏιος","ΜάÏτιος","ΑπÏίλιος","Μάιος","ΙοÏνιος","ΙοÏλιος","ΑÏγουστος","ΣεπτÎμβÏιος","ΟκτώβÏιος","ÎοÎμβÏιος","ΔεκÎμβÏιος"],monthNamesShort:["Ιαν","Φεβ","ΜαÏ","ΑπÏ","Μαι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Îοε","Δεκ"],dayNames:["ΚυÏιακή","ΔευτÎÏα","ΤÏίτη","ΤετάÏτη","Î Îμπτη","ΠαÏασκευή","Σάββατο"],dayNamesShort:["ΚυÏ","Δευ","ΤÏι","Τετ","Πεμ","ΠαÏ","Σαβ"],dayNamesMin:["Κυ","Δε","ΤÏ","Τε","Πε","Πα","Σα"],weekHeader:"Εβδ",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.el)}),jQuery(function(a){a.datepicker.regional["en-AU"]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["en-AU"])}),jQuery(function(a){a.datepicker.regional["en-GB"]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["en-GB"])}),jQuery(function(a){a.datepicker.regional["en-NZ"]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["en-NZ"])}),jQuery(function(a){a.datepicker.regional.eo={closeText:"Fermi",prevText:"<Anta",nextText:"Sekv>",currentText:"Nuna",monthNames:["Januaro","Februaro","Marto","Aprilo","Majo","Junio","Julio","AÅgusto","Septembro","Oktobro","Novembro","Decembro"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","AÅg","Sep","Okt","Nov","Dec"],dayNames:["Dimanĉo","Lundo","Mardo","Merkredo","Ä´aÅdo","Vendredo","Sabato"],dayNamesShort:["Dim","Lun","Mar","Mer","Ä´aÅ","Ven","Sab"],dayNamesMin:["Di","Lu","Ma","Me","Ä´a","Ve","Sa"],weekHeader:"Sb",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.eo)}),jQuery(function(a){a.datepicker.regional.es={closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],monthNamesShort:["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],dayNames:["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],dayNamesShort:["Dom","Lun","Mar","Mié","Juv","Vie","Sáb"],dayNamesMin:["Do","Lu","Ma","Mi","Ju","Vi","Sá"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.es)}),jQuery(function(a){a.datepicker.regional.et={closeText:"Sulge",prevText:"Eelnev",nextText:"Järgnev",currentText:"Täna",monthNames:["Jaanuar","Veebruar","Märts","Aprill","Mai","Juuni","Juuli","August","September","Oktoober","November","Detsember"],monthNamesShort:["Jaan","Veebr","Märts","Apr","Mai","Juuni","Juuli","Aug","Sept","Okt","Nov","Dets"],dayNames:["Pühapäev","Esmaspäev","Teisipäev","Kolmapäev","Neljapäev","Reede","Laupäev"],dayNamesShort:["Pühap","Esmasp","Teisip","Kolmap","Neljap","Reede","Laup"],dayNamesMin:["P","E","T","K","N","R","L"],weekHeader:"Sm",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.et)}),jQuery(function(a){a.datepicker.regional.eu={closeText:"Egina",prevText:"<Aur",nextText:"Hur>",currentText:"Gaur",monthNames:["Urtarrila","Otsaila","Martxoa","Apirila","Maiatza","Ekaina","Uztaila","Abuztua","Iraila","Urria","Azaroa","Abendua"],monthNamesShort:["Urt","Ots","Mar","Api","Mai","Eka","Uzt","Abu","Ira","Urr","Aza","Abe"],dayNames:["Igandea","Astelehena","Asteartea","Asteazkena","Osteguna","Ostirala","Larunbata"],dayNamesShort:["Iga","Ast","Ast","Ast","Ost","Ost","Lar"],dayNamesMin:["Ig","As","As","As","Os","Os","La"],weekHeader:"Wk",dateFormat:"yy/mm/dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.eu)}),jQuery(function(a){a.datepicker.regional.fa={closeText:"بستن",prevText:"<قبلي",nextText:"بعدي>",currentText:"امروز",monthNames:["ÙØ±ÙˆØ±Ø¯ÙŠÙ†","ارديبهشت","خرداد","تير","مرداد","شهريور","مهر","آبان","آذر","دي","بهمن","اسÙند"],monthNamesShort:["1","2","3","4","5","6","7","8","9","10","11","12"],dayNames:["يکشنبه","دوشنبه","سه‌شنبه","چهارشنبه","پنجشنبه","جمعه","شنبه"],dayNamesShort:["ÙŠ","د","س","Ú†","Ù¾","ج","Ø´"],dayNamesMin:["ÙŠ","د","س","Ú†","Ù¾","ج","Ø´"],weekHeader:"Ù‡Ù",dateFormat:"yy/mm/dd",firstDay:6,isRTL:!0,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.fa)}),jQuery(function(a){a.datepicker.regional.fi={closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","Su"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.fi)}),jQuery(function(a){a.datepicker.regional.fo={closeText:"Lat aftur",prevText:"<Fyrra",nextText:"Næsta>",currentText:"à dag",monthNames:["Januar","Februar","Mars","AprÃl","Mei","Juni","Juli","August","September","Oktober","November","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Aug","Sep","Okt","Nov","Des"],dayNames:["Sunnudagur","Mánadagur","Týsdagur","Mikudagur","Hósdagur","FrÃggjadagur","Leyardagur"],dayNamesShort:["Sun","Mán","Týs","Mik","Hós","FrÃ","Ley"],dayNamesMin:["Su","Má","Tý","Mi","Hó","Fr","Le"],weekHeader:"Vk",dateFormat:"dd-mm-yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.fo)}),jQuery(function(a){a.datepicker.regional["fr-CH"]={closeText:"Fermer",prevText:"<Préc",nextText:"Suiv>",currentText:"Courant",monthNames:["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"],monthNamesShort:["Jan","Fév","Mar","Avr","Mai","Jun","Jul","Aoû","Sep","Oct","Nov","Déc"],dayNames:["Dimanche","Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi"],dayNamesShort:["Dim","Lun","Mar","Mer","Jeu","Ven","Sam"],dayNamesMin:["Di","Lu","Ma","Me","Je","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["fr-CH"])}),jQuery(function(a){a.datepicker.regional.fr={closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"],monthNamesShort:["Janv.","Févr.","Mars","Avril","Mai","Juin","Juil.","Août","Sept.","Oct.","Nov.","Déc."],dayNames:["Dimanche","Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi"],dayNamesShort:["Dim.","Lun.","Mar.","Mer.","Jeu.","Ven.","Sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.fr)}),jQuery(function(a){a.datepicker.regional.gl={closeText:"Pechar",prevText:"<Ant",nextText:"Seg>",currentText:"Hoxe",monthNames:["Xaneiro","Febreiro","Marzo","Abril","Maio","Xuño","Xullo","Agosto","Setembro","Outubro","Novembro","Decembro"],monthNamesShort:["Xan","Feb","Mar","Abr","Mai","Xuñ","Xul","Ago","Set","Out","Nov","Dec"],dayNames:["Domingo","Luns","Martes","Mércores","Xoves","Venres","Sábado"],dayNamesShort:["Dom","Lun","Mar","Mér","Xov","Ven","Sáb"],dayNamesMin:["Do","Lu","Ma","Mé","Xo","Ve","Sá"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.gl)}),jQuery(function(a){a.datepicker.regional.he={closeText:"סגור",prevText:"<הקוד×",nextText:"הב×>",currentText:"היו×",monthNames:["×™× ×•×ר","פברו×ר","מרץ","×פריל","מ××™","×™×•× ×™","יולי","×וגוסט","ספטמבר","×וקטובר","× ×•×‘×ž×‘×¨","דצמבר"],monthNamesShort:["1","2","3","4","5","6","7","8","9","10","11","12"],dayNames:["ר×שון","×©× ×™","שלישי","רביעי","חמישי","שישי","שבת"],dayNamesShort:["×'","ב'","×’'","ד'","×”'","ו'","שבת"],dayNamesMin:["×'","ב'","×’'","ד'","×”'","ו'","שבת"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!0,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.he)}),jQuery(function(a){a.datepicker.regional.hr={closeText:"Zatvori",prevText:"<",nextText:">",currentText:"Danas",monthNames:["SijeÄanj","VeljaÄa","Ožujak","Travanj","Svibanj","Lipanj","Srpanj","Kolovoz","Rujan","Listopad","Studeni","Prosinac"],monthNamesShort:["Sij","Velj","Ožu","Tra","Svi","Lip","Srp","Kol","Ruj","Lis","Stu","Pro"],dayNames:["Nedjelja","Ponedjeljak","Utorak","Srijeda","ÄŒetvrtak","Petak","Subota"],dayNamesShort:["Ned","Pon","Uto","Sri","ÄŒet","Pet","Sub"],dayNamesMin:["Ne","Po","Ut","Sr","ÄŒe","Pe","Su"],weekHeader:"Tje",dateFormat:"dd.mm.yy.",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.hr)}),jQuery(function(a){a.datepicker.regional.hu={closeText:"bezár",prevText:"vissza",nextText:"elÅ‘re",currentText:"ma",monthNames:["Január","Február","Március","Ãprilis","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ãpr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","HétfÅ‘","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.hu)}),jQuery(function(a){a.datepicker.regional.hy={closeText:"Õ“Õ¡Õ¯Õ¥Õ¬",prevText:"<Õ†Õ¡Õ.",nextText:"Õ€Õ¡Õ».>",currentText:"Ô±ÕµÕ½Ö…Ö€",monthNames:["Õ€Õ¸Ö‚Õ¶Õ¾Õ¡Ö€","Õ“Õ¥Õ¿Ö€Õ¾Õ¡Ö€","Õ„Õ¡Ö€Õ¿","Ô±ÕºÖ€Õ«Õ¬","Õ„Õ¡ÕµÕ«Õ½","Õ€Õ¸Ö‚Õ¶Õ«Õ½","Õ€Õ¸Ö‚Õ¬Õ«Õ½","Õ•Õ£Õ¸Õ½Õ¿Õ¸Õ½","ÕÕ¥ÕºÕ¿Õ¥Õ´Õ¢Õ¥Ö€","Õ€Õ¸Õ¯Õ¿Õ¥Õ´Õ¢Õ¥Ö€","Õ†Õ¸ÕµÕ¥Õ´Õ¢Õ¥Ö€","Ô´Õ¥Õ¯Õ¿Õ¥Õ´Õ¢Õ¥Ö€"],monthNamesShort:["Õ€Õ¸Ö‚Õ¶Õ¾","Õ“Õ¥Õ¿Ö€","Õ„Õ¡Ö€Õ¿","Ô±ÕºÖ€","Õ„Õ¡ÕµÕ«Õ½","Õ€Õ¸Ö‚Õ¶Õ«Õ½","Õ€Õ¸Ö‚Õ¬","Õ•Õ£Õ½","ÕÕ¥Õº","Õ€Õ¸Õ¯","Õ†Õ¸Õµ","Ô´Õ¥Õ¯"],dayNames:["Õ¯Õ«Ö€Õ¡Õ¯Õ«","Õ¥Õ¯Õ¸Ö‚Õ·Õ¡Õ¢Õ©Õ«","Õ¥Ö€Õ¥Ö„Õ·Õ¡Õ¢Õ©Õ«","Õ¹Õ¸Ö€Õ¥Ö„Õ·Õ¡Õ¢Õ©Õ«","Õ°Õ«Õ¶Õ£Õ·Õ¡Õ¢Õ©Õ«","Õ¸Ö‚Ö€Õ¢Õ¡Õ©","Õ·Õ¡Õ¢Õ¡Õ©"],dayNamesShort:["Õ¯Õ«Ö€","Õ¥Ö€Õ¯","Õ¥Ö€Ö„","Õ¹Ö€Ö„","Õ°Õ¶Õ£","Õ¸Ö‚Ö€Õ¢","Õ·Õ¢Õ©"],dayNamesMin:["Õ¯Õ«Ö€","Õ¥Ö€Õ¯","Õ¥Ö€Ö„","Õ¹Ö€Ö„","Õ°Õ¶Õ£","Õ¸Ö‚Ö€Õ¢","Õ·Õ¢Õ©"],weekHeader:"Õ‡Ô²Õ",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.hy)}),jQuery(function(a){a.datepicker.regional.id={closeText:"Tutup",prevText:"<mundur",nextText:"maju>",currentText:"hari ini",monthNames:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","Nopember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agus","Sep","Okt","Nop","Des"],dayNames:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],dayNamesShort:["Min","Sen","Sel","Rab","kam","Jum","Sab"],dayNamesMin:["Mg","Sn","Sl","Rb","Km","jm","Sb"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.id)}),jQuery(function(a){a.datepicker.regional.is={closeText:"Loka",prevText:"< Fyrri",nextText:"Næsti >",currentText:"Í dag",monthNames:["Janúar","Febrúar","Mars","Apríl","Maí","Júní","Júlí","Ágúst","September","Október","Nóvember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Maí","Jún","Júl","Ágú","Sep","Okt","Nóv","Des"],dayNames:["Sunnudagur","Mánudagur","Þriðjudagur","Miðvikudagur","Fimmtudagur","Föstudagur","Laugardagur"],dayNamesShort:["Sun","Mán","Þri","Mið","Fim","Fös","Lau"],dayNamesMin:["Su","Má","Þr","Mi","Fi","Fö","La"],weekHeader:"Vika",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.is)}),jQuery(function(a){a.datepicker.regional.it={closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.it)}),jQuery(function(a){a.datepicker.regional.ja={closeText:"é–‰ã˜ã‚‹",prevText:"<å‰",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","ç«æ›œæ—¥","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["æ—¥","月","ç«","æ°´","木","金","土"],dayNamesMin:["æ—¥","月","ç«","æ°´","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"å¹´"},a.datepicker.setDefaults(a.datepicker.regional.ja)}),jQuery(function(a){a.datepicker.regional.kk={closeText:"Жабу",prevText:"<Ðлдыңғы",nextText:"КелеÑÑ–>",currentText:"Бүгін",monthNames:["Қаңтар","Ðқпан","Ðаурыз","Сәуір","Мамыр","МауÑым","Шілде","Тамыз","Қыркүйек","Қазан","Қараша","ЖелтоқÑан"],monthNamesShort:["Қаң","Ðқп","Ðау","Сәу","Мам","Мау","Шіл","Там","Қыр","Қаз","Қар","Жел"],dayNames:["ЖекÑенбі","ДүйÑенбі","СейÑенбі","СәрÑенбі","БейÑенбі","Жұма","Сенбі"],dayNamesShort:["жкÑ","дÑн","ÑÑн","ÑÑ€Ñ","бÑн","жма","Ñнб"],dayNamesMin:["Жк","ДÑ","СÑ","Ср","БÑ","Жм","Сн"],weekHeader:"Ðе",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.kk)}),jQuery(function(a){a.datepicker.regional.ko={closeText:"닫기",prevText:"ì´ì „달",nextText:"다ìŒë‹¬",currentText:"오늘",monthNames:["1ì›”(JAN)","2ì›”(FEB)","3ì›”(MAR)","4ì›”(APR)","5ì›”(MAY)","6ì›”(JUN)","7ì›”(JUL)","8ì›”(AUG)","9ì›”(SEP)","10ì›”(OCT)","11ì›”(NOV)","12ì›”(DEC)"],monthNamesShort:["1ì›”(JAN)","2ì›”(FEB)","3ì›”(MAR)","4ì›”(APR)","5ì›”(MAY)","6ì›”(JUN)","7ì›”(JUL)","8ì›”(AUG)","9ì›”(SEP)","10ì›”(OCT)","11ì›”(NOV)","12ì›”(DEC)"],dayNames:["ì¼","ì›”","í™”","수","목","금","í† "],dayNamesShort:["ì¼","ì›”","í™”","수","목","금","í† "],dayNamesMin:["ì¼","ì›”","í™”","수","목","금","í† "],weekHeader:"Wk",dateFormat:"yy-mm-dd",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:"ë…„"},a.datepicker.setDefaults(a.datepicker.regional.ko)}),jQuery(function(a){a.datepicker.regional.lb={closeText:"Fäerdeg",prevText:"Zréck",nextText:"Weider",currentText:"Haut",monthNames:["Januar","Februar","Mäerz","Abrëll","Mee","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mäe","Abr","Mee","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonndeg","Méindeg","Dënschdeg","Mëttwoch","Donneschdeg","Freideg","Samschdeg"],dayNamesShort:["Son","Méi","Dën","Mët","Don","Fre","Sam"],dayNamesMin:["So","Mé","Dë","Më","Do","Fr","Sa"],weekHeader:"W",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.lb)}),jQuery(function(a){a.datepicker.regional.lt={closeText:"Uždaryti",prevText:"<Atgal",nextText:"Pirmyn>",currentText:"Å iandien",monthNames:["Sausis","Vasaris","Kovas","Balandis","Gegužė","Birželis","Liepa","RugpjÅ«tis","RugsÄ—jis","Spalis","Lapkritis","Gruodis"],monthNamesShort:["Sau","Vas","Kov","Bal","Geg","Bir","Lie","Rugp","Rugs","Spa","Lap","Gru"],dayNames:["sekmadienis","pirmadienis","antradienis","treÄiadienis","ketvirtadienis","penktadienis","Å¡eÅ¡tadienis"],dayNamesShort:["sek","pir","ant","tre","ket","pen","Å¡eÅ¡"],dayNamesMin:["Se","Pr","An","Tr","Ke","Pe","Å e"],weekHeader:"Wk",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.lt)}),jQuery(function(a){a.datepicker.regional.lv={closeText:"AizvÄ“rt",prevText:"Iepr",nextText:"NÄka",currentText:"Å odien",monthNames:["JanvÄris","FebruÄris","Marts","AprÄ«lis","Maijs","JÅ«nijs","JÅ«lijs","Augusts","Septembris","Oktobris","Novembris","Decembris"],monthNamesShort:["Jan","Feb","Mar","Apr","Mai","JÅ«n","JÅ«l","Aug","Sep","Okt","Nov","Dec"],dayNames:["svÄ“tdiena","pirmdiena","otrdiena","treÅ¡diena","ceturtdiena","piektdiena","sestdiena"],dayNamesShort:["svt","prm","otr","tre","ctr","pkt","sst"],dayNamesMin:["Sv","Pr","Ot","Tr","Ct","Pk","Ss"],weekHeader:"Nav",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.lv)}),jQuery(function(a){a.datepicker.regional.mk={closeText:"Затвори",prevText:"<",nextText:">",currentText:"ДенеÑ",monthNames:["Јануари","Фебруари","Март","Ðприл","Мај","Јуни","Јули","ÐвгуÑÑ‚","Септември","Октомври","Ðоември","Декември"],monthNamesShort:["Јан","Феб","Мар","Ðпр","Мај","Јун","Јул","Ðвг","Сеп","Окт","Ðое","Дек"],dayNames:["Ðедела","Понеделник","Вторник","Среда","Четврток","Петок","Сабота"],dayNamesShort:["Ðед","Пон","Вто","Сре","Чет","Пет","Саб"],dayNamesMin:["Ðе","По","Ð’Ñ‚","Ср","Че","Пе","Са"],weekHeader:"Сед",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.mk)}),jQuery(function(a){a.datepicker.regional.ml={closeText:"à´¶à´°à´¿",prevText:"à´®àµà´¨àµà´¨à´¤àµà´¤àµ†",nextText:"à´…à´Ÿàµà´¤àµà´¤à´¤àµ ",currentText:"ഇനàµà´¨àµ",monthNames:["ജനàµà´µà´°à´¿","ഫെബàµà´°àµà´µà´°à´¿","മാരàµâ€à´šàµà´šàµ","à´à´ªàµà´°à´¿à´²àµâ€","മേയàµ","ജൂണàµâ€","ജൂലൈ","ആഗസàµà´±àµà´±àµ","സെപàµà´±àµà´±à´‚ബരàµâ€","à´’à´•àµà´Ÿàµ‹à´¬à´°àµâ€","നവംബരàµâ€","ഡിസംബരàµâ€"],monthNamesShort:["ജനàµ","ഫെബàµ","മാരàµâ€","à´à´ªàµà´°à´¿","മേയàµ","ജൂണàµâ€","ജൂലാ","ആഗ","സെപàµ","à´’à´•àµà´Ÿàµ‹","നവം","à´¡à´¿à´¸"],dayNames:["ഞായരàµâ€","തിങàµà´•à´³àµâ€","ചൊവàµà´µ","à´¬àµà´§à´¨àµâ€","à´µàµà´¯à´¾à´´à´‚","വെളàµà´³à´¿","ശനി"],dayNamesShort:["ഞായ","തിങàµà´•","ചൊവàµà´µ","à´¬àµà´§","à´µàµà´¯à´¾à´´à´‚","വെളàµà´³à´¿","ശനി"],dayNamesMin:["à´žà´¾","തി","ചൊ","à´¬àµ","à´µàµà´¯à´¾","വെ","à´¶"],weekHeader:"à´†",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.ml)}),jQuery(function(a){a.datepicker.regional.ms={closeText:"Tutup",prevText:"<Sebelum",nextText:"Selepas>",currentText:"hari ini",monthNames:["Januari","Februari","Mac","April","Mei","Jun","Julai","Ogos","September","Oktober","November","Disember"],monthNamesShort:["Jan","Feb","Mac","Apr","Mei","Jun","Jul","Ogo","Sep","Okt","Nov","Dis"],dayNames:["Ahad","Isnin","Selasa","Rabu","Khamis","Jumaat","Sabtu"],dayNamesShort:["Aha","Isn","Sel","Rab","kha","Jum","Sab"],dayNamesMin:["Ah","Is","Se","Ra","Kh","Ju","Sa"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.ms)}),jQuery(function(a){a.datepicker.regional["nl-BE"]={closeText:"Sluiten",prevText:"â†",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["nl-BE"])}),jQuery(function(a){a.datepicker.regional.nl={closeText:"Sluiten",prevText:"â†",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.nl)}),jQuery(function(a){a.datepicker.regional.no={closeText:"Lukk",prevText:"«Forrige",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["søn","man","tir","ons","tor","fre","lør"],dayNames:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],dayNamesMin:["sø","ma","ti","on","to","fr","lø"],weekHeader:"Uke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.no)}),jQuery(function(a){a.datepicker.regional.pl={closeText:"Zamknij",prevText:"<Poprzedni",nextText:"NastÄ™pny>",currentText:"DziÅ›",monthNames:["StyczeÅ„","Luty","Marzec","KwiecieÅ„","Maj","Czerwiec","Lipiec","SierpieÅ„","WrzesieÅ„","Październik","Listopad","GrudzieÅ„"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","PoniedziaÅ‚ek","Wtorek","Åšroda","Czwartek","PiÄ…tek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Åšr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Åšr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.pl)}),jQuery(function(a){a.datepicker.regional["pt-BR"]={closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["pt-BR"])}),jQuery(function(a){a.datepicker.regional.pt={closeText:"Fechar",prevText:"<Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.pt)}),jQuery(function(a){a.datepicker.regional.rm={closeText:"Serrar",prevText:"<Suandant",nextText:"Precedent>",currentText:"Actual",monthNames:["Schaner","Favrer","Mars","Avrigl","Matg","Zercladur","Fanadur","Avust","Settember","October" +,"November","December"],monthNamesShort:["Scha","Fev","Mar","Avr","Matg","Zer","Fan","Avu","Sett","Oct","Nov","Dec"],dayNames:["Dumengia","Glindesdi","Mardi","Mesemna","Gievgia","Venderdi","Sonda"],dayNamesShort:["Dum","Gli","Mar","Mes","Gie","Ven","Som"],dayNamesMin:["Du","Gl","Ma","Me","Gi","Ve","So"],weekHeader:"emna",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.rm)}),jQuery(function(a){a.datepicker.regional.ro={closeText:"ÃŽnchide",prevText:"« Luna precedentă",nextText:"Luna următoare »",currentText:"Azi",monthNames:["Ianuarie","Februarie","Martie","Aprilie","Mai","Iunie","Iulie","August","Septembrie","Octombrie","Noiembrie","Decembrie"],monthNamesShort:["Ian","Feb","Mar","Apr","Mai","Iun","Iul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Duminică","Luni","MarÅ£i","Miercuri","Joi","Vineri","Sâmbătă"],dayNamesShort:["Dum","Lun","Mar","Mie","Joi","Vin","Sâm"],dayNamesMin:["Du","Lu","Ma","Mi","Jo","Vi","Sâ"],weekHeader:"Săpt",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.ro)}),jQuery(function(a){a.datepicker.regional.ru={closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"СегоднÑ",monthNames:["Январь","Февраль","Март","Ðпрель","Май","Июнь","Июль","ÐвгуÑÑ‚","СентÑбрь","ОктÑбрь","ÐоÑбрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Ðпр","Май","Июн","Июл","Ðвг","Сен","Окт","ÐоÑ","Дек"],dayNames:["воÑкреÑенье","понедельник","вторник","Ñреда","четверг","пÑтница","Ñуббота"],dayNamesShort:["вÑк","пнд","втр","Ñрд","чтв","птн","Ñбт"],dayNamesMin:["Ð’Ñ","Пн","Ð’Ñ‚","Ср","Чт","Пт","Сб"],weekHeader:"Ðед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.ru)}),jQuery(function(a){a.datepicker.regional.sk={closeText:"ZavrieÅ¥",prevText:"<Predchádzajúci",nextText:"Nasledujúci>",currentText:"Dnes",monthNames:["Január","Február","Marec","AprÃl","Máj","Jún","Júl","August","September","Október","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Máj","Jún","Júl","Aug","Sep","Okt","Nov","Dec"],dayNames:["Nedeľa","Pondelok","Utorok","Streda","Å tvrtok","Piatok","Sobota"],dayNamesShort:["Ned","Pon","Uto","Str","Å tv","Pia","Sob"],dayNamesMin:["Ne","Po","Ut","St","Å t","Pia","So"],weekHeader:"Ty",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.sk)}),jQuery(function(a){a.datepicker.regional.sl={closeText:"Zapri",prevText:"<Prejšnji",nextText:"Naslednji>",currentText:"Trenutni",monthNames:["Januar","Februar","Marec","April","Maj","Junij","Julij","Avgust","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Avg","Sep","Okt","Nov","Dec"],dayNames:["Nedelja","Ponedeljek","Torek","Sreda","Četrtek","Petek","Sobota"],dayNamesShort:["Ned","Pon","Tor","Sre","Čet","Pet","Sob"],dayNamesMin:["Ne","Po","To","Sr","Če","Pe","So"],weekHeader:"Teden",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.sl)}),jQuery(function(a){a.datepicker.regional.sq={closeText:"mbylle",prevText:"<mbrapa",nextText:"Përpara>",currentText:"sot",monthNames:["Janar","Shkurt","Mars","Prill","Maj","Qershor","Korrik","Gusht","Shtator","Tetor","Nëntor","Dhjetor"],monthNamesShort:["Jan","Shk","Mar","Pri","Maj","Qer","Kor","Gus","Sht","Tet","Nën","Dhj"],dayNames:["E Diel","E Hënë","E Martë","E Mërkurë","E Enjte","E Premte","E Shtune"],dayNamesShort:["Di","Hë","Ma","Më","En","Pr","Sh"],dayNamesMin:["Di","Hë","Ma","Më","En","Pr","Sh"],weekHeader:"Ja",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.sq)}),jQuery(function(a){a.datepicker.regional["sr-SR"]={closeText:"Zatvori",prevText:"<",nextText:">",currentText:"Danas",monthNames:["Januar","Februar","Mart","April","Maj","Jun","Jul","Avgust","Septembar","Oktobar","Novembar","Decembar"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Avg","Sep","Okt","Nov","Dec"],dayNames:["Nedelja","Ponedeljak","Utorak","Sreda","ÄŒetvrtak","Petak","Subota"],dayNamesShort:["Ned","Pon","Uto","Sre","ÄŒet","Pet","Sub"],dayNamesMin:["Ne","Po","Ut","Sr","ÄŒe","Pe","Su"],weekHeader:"Sed",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional["sr-SR"])}),jQuery(function(a){a.datepicker.regional.sr={closeText:"Затвори",prevText:"<",nextText:">",currentText:"ДанаÑ",monthNames:["Јануар","Фебруар","Март","Ðприл","Мај","Јун","Јул","ÐвгуÑÑ‚","Септембар","Октобар","Ðовембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Ðпр","Мај","Јун","Јул","Ðвг","Сеп","Окт","Ðов","Дец"],dayNames:["Ðедеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Ðед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Ðе","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.sr)}),jQuery(function(a){a.datepicker.regional.sv={closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","MÃ¥n","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","MÃ¥ndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","MÃ¥","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.sv)}),jQuery(function(a){a.datepicker.regional.ta={closeText:"மூடà¯",prevText:"à®®à¯à®©à¯à®©à¯ˆà®¯à®¤à¯",nextText:"அடà¯à®¤à¯à®¤à®¤à¯",currentText:"இனà¯à®±à¯",monthNames:["தை","மாசி","பஙà¯à®•à¯à®©à®¿","சிதà¯à®¤à®¿à®°à¯ˆ","வைகாசி","ஆனி","ஆடி","ஆவணி","பà¯à®°à®Ÿà¯à®Ÿà®¾à®šà®¿","à®à®ªà¯à®ªà®šà®¿","காரà¯à®¤à¯à®¤à®¿à®•ை","மாரà¯à®•ழி"],monthNamesShort:["தை","மாசி","பஙà¯","சிதà¯","வைகா","ஆனி","ஆடி","ஆவ","பà¯à®°","à®à®ªà¯","காரà¯","மாரà¯"],dayNames:["ஞாயிறà¯à®±à¯à®•à¯à®•ிழமை","திஙà¯à®•டà¯à®•ிழமை","செவà¯à®µà®¾à®¯à¯à®•à¯à®•ிழமை","பà¯à®¤à®©à¯à®•ிழமை","வியாழகà¯à®•ிழமை","வெளà¯à®³à®¿à®•à¯à®•ிழமை","சனிகà¯à®•ிழமை"],dayNamesShort:["ஞாயிறà¯","திஙà¯à®•ளà¯","செவà¯à®µà®¾à®¯à¯","பà¯à®¤à®©à¯","வியாழனà¯","வெளà¯à®³à®¿","சனி"],dayNamesMin:["ஞா","தி","செ","பà¯","வி","வெ","ச"],weekHeader:"Ðе",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.ta)}),jQuery(function(a){a.datepicker.regional.th={closeText:"ปิด",prevText:"« ย้à¸à¸™",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มà¸à¸£à¸²à¸„ม","à¸à¸¸à¸¡à¸ าพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","à¸à¸£à¸à¸Žà¸²à¸„ม","สิงหาคม","à¸à¸±à¸™à¸¢à¸²à¸¢à¸™","ตุลาคม","พฤศจิà¸à¸²à¸¢à¸™","ธันวาคม"],monthNamesShort:["ม.ค.","à¸.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","à¸.ค.","ส.ค.","à¸.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["à¸à¸²à¸—ิตย์","จันทร์","à¸à¸±à¸‡à¸„าร","พุธ","พฤหัสบดี","ศุà¸à¸£à¹Œ","เสาร์"],dayNamesShort:["à¸à¸².","จ.","à¸.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["à¸à¸².","จ.","à¸.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.th)}),jQuery(function(a){a.datepicker.regional.tj={closeText:"Идома",prevText:"<Қафо",nextText:"Пеш>",currentText:"Имрӯз",monthNames:["Январ","Феврал","Март","Ðпрел","Май","Июн","Июл","ÐвгуÑÑ‚","СентÑбр","ОктÑбр","ÐоÑбр","Декабр"],monthNamesShort:["Янв","Фев","Мар","Ðпр","Май","Июн","Июл","Ðвг","Сен","Окт","ÐоÑ","Дек"],dayNames:["Ñкшанбе","душанбе","Ñешанбе","чоршанбе","панҷшанбе","ҷумъа","шанбе"],dayNamesShort:["Ñкш","душ","Ñеш","чор","пан","ҷум","шан"],dayNamesMin:["Як","Дш","Сш","Чш","Пш","Ҷм","Шн"],weekHeader:"Хф",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.tj)}),jQuery(function(a){a.datepicker.regional.tr={closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Åžubat","Mart","Nisan","Mayıs","Haziran","Temmuz","AÄŸustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Åžub","Mar","Nis","May","Haz","Tem","AÄŸu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","ÇarÅŸamba","PerÅŸembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.tr)}),jQuery(function(a){a.datepicker.regional.uk={closeText:"Закрити",prevText:"<",nextText:">",currentText:"Сьогодні",monthNames:["Січень","Лютий","Березень","Квітень","Травень","Червень","Липень","Серпень","ВереÑень","Жовтень","ЛиÑтопад","Грудень"],monthNamesShort:["Січ","Лют","Бер","Кві","Тра","Чер","Лип","Сер","Вер","Жов","ЛиÑ","Гру"],dayNames:["неділÑ","понеділок","вівторок","Ñереда","четвер","п’ÑтницÑ","Ñубота"],dayNamesShort:["нед","пнд","вів","Ñрд","чтв","птн","Ñбт"],dayNamesMin:["Ðд","Пн","Ð’Ñ‚","Ср","Чт","Пт","Сб"],weekHeader:"Тиж",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.uk)}),jQuery(function(a){a.datepicker.regional.vi={closeText:"Äóng",prevText:"<Trước",nextText:"Tiếp>",currentText:"Hôm nay",monthNames:["Tháng Má»™t","Tháng Hai","Tháng Ba","Tháng Tư","Tháng Năm","Tháng Sáu","Tháng Bảy","Tháng Tám","Tháng ChÃn","Tháng Mưá»i","Tháng Mưá»i Má»™t","Tháng Mưá»i Hai"],monthNamesShort:["Tháng 1","Tháng 2","Tháng 3","Tháng 4","Tháng 5","Tháng 6","Tháng 7","Tháng 8","Tháng 9","Tháng 10","Tháng 11","Tháng 12"],dayNames:["Chá»§ Nháºt","Thứ Hai","Thứ Ba","Thứ Tư","Thứ Năm","Thứ Sáu","Thứ Bảy"],dayNamesShort:["CN","T2","T3","T4","T5","T6","T7"],dayNamesMin:["CN","T2","T3","T4","T5","T6","T7"],weekHeader:"Tu",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},a.datepicker.setDefaults(a.datepicker.regional.vi)}),jQuery(function(a){a.datepicker.regional["zh-CN"]={closeText:"å…³é—",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","å…æœˆ","七月","八月","乿œˆ","åæœˆ","å一月","å二月"],monthNamesShort:["一","二","三","å››","五","å…","七","å…«","ä¹","å","å一","å二"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期å…"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周å…"],dayNamesMin:["æ—¥","一","二","三","å››","五","å…"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"å¹´"},a.datepicker.setDefaults(a.datepicker.regional["zh-CN"])}),jQuery(function(a){a.datepicker.regional["zh-HK"]={closeText:"關閉",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","å…æœˆ","七月","八月","乿œˆ","åæœˆ","å一月","å二月"],monthNamesShort:["一","二","三","å››","五","å…","七","å…«","ä¹","å","å一","å二"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期å…"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周å…"],dayNamesMin:["æ—¥","一","二","三","å››","五","å…"],weekHeader:"周",dateFormat:"dd-mm-yy",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"å¹´"},a.datepicker.setDefaults(a.datepicker.regional["zh-HK"])}),jQuery(function(a){a.datepicker.regional["zh-TW"]={closeText:"關閉",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","å…æœˆ","七月","八月","乿œˆ","åæœˆ","å一月","å二月"],monthNamesShort:["一","二","三","å››","五","å…","七","å…«","ä¹","å","å一","å二"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期å…"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周å…"],dayNamesMin:["æ—¥","一","二","三","å››","五","å…"],weekHeader:"周",dateFormat:"yy/mm/dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"å¹´"},a.datepicker.setDefaults(a.datepicker.regional["zh-TW"])})
\ No newline at end of file diff --git a/framework/web/js/source/jui/js/jquery-ui.min.js b/framework/web/js/source/jui/js/jquery-ui.min.js new file mode 100644 index 0000000..618b8ef --- /dev/null +++ b/framework/web/js/source/jui/js/jquery-ui.min.js @@ -0,0 +1,15 @@ +/*! + * jQuery UI 1.8.17 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI + */(function(a,b){function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;if(!b.href||!g||f.nodeName.toLowerCase()!=="map")return!1;h=a("img[usemap=#"+g+"]")[0];return!!h&&d(h)}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}a.ui=a.ui||{};a.ui.version||(a.extend(a.ui,{version:"1.8.17",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}}),a.fn.extend({propAttr:a.fn.prop||a.fn.attr,_focus:a.fn.focus,focus:function(b,c){return typeof b=="number"?this.each(function(){var d=this;setTimeout(function(){a(d).focus(),c&&c.call(d)},b)}):this._focus.apply(this,arguments)},scrollParent:function(){var b;a.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?b=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(a.curCSS(this,"position",1))&&/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0):b=this.parents().filter(function(){return/(auto|scroll)/.test(a.curCSS(this,"overflow",1)+a.curCSS(this,"overflow-y",1)+a.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!b.length?a(document):b},zIndex:function(c){if(c!==b)return this.css("zIndex",c);if(this.length){var d=a(this[0]),e,f;while(d.length&&d[0]!==document){e=d.css("position");if(e==="absolute"||e==="relative"||e==="fixed"){f=parseInt(d.css("zIndex"),10);if(!isNaN(f)&&f!==0)return f}d=d.parent()}}return 0},disableSelection:function(){return this.bind((a.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),a.each(["Width","Height"],function(c,d){function h(b,c,d,f){a.each(e,function(){c-=parseFloat(a.curCSS(b,"padding"+this,!0))||0,d&&(c-=parseFloat(a.curCSS(b,"border"+this+"Width",!0))||0),f&&(c-=parseFloat(a.curCSS(b,"margin"+this,!0))||0)});return c}var e=d==="Width"?["Left","Right"]:["Top","Bottom"],f=d.toLowerCase(),g={innerWidth:a.fn.innerWidth,innerHeight:a.fn.innerHeight,outerWidth:a.fn.outerWidth,outerHeight:a.fn.outerHeight};a.fn["inner"+d]=function(c){if(c===b)return g["inner"+d].call(this);return this.each(function(){a(this).css(f,h(this,c)+"px")})},a.fn["outer"+d]=function(b,c){if(typeof b!="number")return g["outer"+d].call(this,b);return this.each(function(){a(this).css(f,h(this,b,!0,c)+"px")})}}),a.extend(a.expr[":"],{data:function(b,c,d){return!!a.data(b,d[3])},focusable:function(b){return c(b,!isNaN(a.attr(b,"tabindex")))},tabbable:function(b){var d=a.attr(b,"tabindex"),e=isNaN(d);return(e||d>=0)&&c(b,!e)}}),a(function(){var b=document.body,c=b.appendChild(c=document.createElement("div"));a.extend(c.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0}),a.support.minHeight=c.offsetHeight===100,a.support.selectstart="onselectstart"in c,b.removeChild(c).style.display="none"}),a.extend(a.ui,{plugin:{add:function(b,c,d){var e=a.ui[b].prototype;for(var f in d)e.plugins[f]=e.plugins[f]||[],e.plugins[f].push([c,d[f]])},call:function(a,b,c){var d=a.plugins[b];if(!!d&&!!a.element[0].parentNode)for(var e=0;e<d.length;e++)a.options[d[e][0]]&&d[e][1].apply(a.element,c)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(b,c){if(a(b).css("overflow")==="hidden")return!1;var d=c&&c==="left"?"scrollLeft":"scrollTop",e=!1;if(b[d]>0)return!0;b[d]=1,e=b[d]>0,b[d]=0;return e},isOverAxis:function(a,b,c){return a>b&&a<b+c},isOver:function(b,c,d,e,f,g){return a.ui.isOverAxis(b,d,f)&&a.ui.isOverAxis(c,e,g)}}))})(jQuery),function(a,b){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var d=0,e;(e=b[d])!=null;d++)try{a(e).triggerHandler("remove")}catch(f){}c(b)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(b){}});return d.call(a(this),b,c)})}}a.widget=function(b,c,d){var e=b.split(".")[0],f;b=b.split(".")[1],f=e+"-"+b,d||(d=c,c=a.Widget),a.expr[":"][f]=function(c){return!!a.data(c,b)},a[e]=a[e]||{},a[e][b]=function(a,b){arguments.length&&this._createWidget(a,b)};var g=new c;g.options=a.extend(!0,{},g.options),a[e][b].prototype=a.extend(!0,g,{namespace:e,widgetName:b,widgetEventPrefix:a[e][b].prototype.widgetEventPrefix||b,widgetBaseClass:f},d),a.widget.bridge(b,a[e][b])},a.widget.bridge=function(c,d){a.fn[c]=function(e){var f=typeof e=="string",g=Array.prototype.slice.call(arguments,1),h=this;e=!f&&g.length?a.extend.apply(null,[!0,e].concat(g)):e;if(f&&e.charAt(0)==="_")return h;f?this.each(function(){var d=a.data(this,c),f=d&&a.isFunction(d[e])?d[e].apply(d,g):d;if(f!==d&&f!==b){h=f;return!1}}):this.each(function(){var b=a.data(this,c);b?b.option(e||{})._init():a.data(this,c,new d(e,this))});return h}},a.Widget=function(a,b){arguments.length&&this._createWidget(a,b)},a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:!1},_createWidget:function(b,c){a.data(c,this.widgetName,this),this.element=a(c),this.options=a.extend(!0,{},this.options,this._getCreateOptions(),b);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()}),this._create(),this._trigger("create"),this._init()},_getCreateOptions:function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName),this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled "+"ui-state-disabled")},widget:function(){return this.element},option:function(c,d){var e=c;if(arguments.length===0)return a.extend({},this.options);if(typeof c=="string"){if(d===b)return this.options[c];e={},e[c]=d}this._setOptions(e);return this},_setOptions:function(b){var c=this;a.each(b,function(a,b){c._setOption(a,b)});return this},_setOption:function(a,b){this.options[a]=b,a==="disabled"&&this.widget()[b?"addClass":"removeClass"](this.widgetBaseClass+"-disabled"+" "+"ui-state-disabled").attr("aria-disabled",b);return this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_trigger:function(b,c,d){var e,f,g=this.options[b];d=d||{},c=a.Event(c),c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase(),c.target=this.element[0],f=c.originalEvent;if(f)for(e in f)e in c||(c[e]=f[e]);this.element.trigger(c,d);return!(a.isFunction(g)&&g.call(this.element[0],c,d)===!1||c.isDefaultPrevented())}}}(jQuery),function(a,b){var c=!1;a(document).mouseup(function(a){c=!1}),a.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var b=this;this.element.bind("mousedown."+this.widgetName,function(a){return b._mouseDown(a)}).bind("click."+this.widgetName,function(c){if(!0===a.data(c.target,b.widgetName+".preventClickEvent")){a.removeData(c.target,b.widgetName+".preventClickEvent"),c.stopImmediatePropagation();return!1}}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(b){if(!c){this._mouseStarted&&this._mouseUp(b),this._mouseDownEvent=b;var d=this,e=b.which==1,f=typeof this.options.cancel=="string"&&b.target.nodeName?a(b.target).closest(this.options.cancel).length:!1;if(!e||f||!this._mouseCapture(b))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){d.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(b)&&this._mouseDelayMet(b)){this._mouseStarted=this._mouseStart(b)!==!1;if(!this._mouseStarted){b.preventDefault();return!0}}!0===a.data(b.target,this.widgetName+".preventClickEvent")&&a.removeData(b.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(a){return d._mouseMove(a)},this._mouseUpDelegate=function(a){return d._mouseUp(a)},a(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),b.preventDefault(),c=!0;return!0}},_mouseMove:function(b){if(a.browser.msie&&!(document.documentMode>=9)&&!b.button)return this._mouseUp(b);if(this._mouseStarted){this._mouseDrag(b);return b.preventDefault()}this._mouseDistanceMet(b)&&this._mouseDelayMet(b)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,b)!==!1,this._mouseStarted?this._mouseDrag(b):this._mouseUp(b));return!this._mouseStarted},_mouseUp:function(b){a(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,b.target==this._mouseDownEvent.target&&a.data(b.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(b));return!1},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(a){return this.mouseDelayMet},_mouseStart:function(a){},_mouseDrag:function(a){},_mouseStop:function(a){},_mouseCapture:function(a){return!0}})}(jQuery),function(a,b){a.widget("ui.draggable",a.ui.mouse,{widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1},_create:function(){this.options.helper=="original"&&!/^(?:r|a|f)/.test(this.element.css("position"))&&(this.element[0].style.position="relative"),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._mouseInit()},destroy:function(){if(!!this.element.data("draggable")){this.element.removeData("draggable").unbind(".draggable").removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._mouseDestroy();return this}},_mouseCapture:function(b){var c=this.options;if(this.helper||c.disabled||a(b.target).is(".ui-resizable-handle"))return!1;this.handle=this._getHandle(b);if(!this.handle)return!1;c.iframeFix&&a(c.iframeFix===!0?"iframe":c.iframeFix).each(function(){a('<div class="ui-draggable-iframeFix" style="background: #fff;"></div>').css({width:this.offsetWidth+"px",height:this.offsetHeight+"px",position:"absolute",opacity:"0.001",zIndex:1e3}).css(a(this).offset()).appendTo("body")});return!0},_mouseStart:function(b){var c=this.options;this.helper=this._createHelper(b),this._cacheHelperProportions(),a.ui.ddmanager&&(a.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(),this.offset=this.positionAbs=this.element.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this.position=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,c.cursorAt&&this._adjustOffsetFromHelper(c.cursorAt),c.containment&&this._setContainment();if(this._trigger("start",b)===!1){this._clear();return!1}this._cacheHelperProportions(),a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.helper.addClass("ui-draggable-dragging"),this._mouseDrag(b,!0),a.ui.ddmanager&&a.ui.ddmanager.dragStart(this,b);return!0},_mouseDrag:function(b,c){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute");if(!c){var d=this._uiHash();if(this._trigger("drag",b,d)===!1){this._mouseUp({});return!1}this.position=d.position}if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";a.ui.ddmanager&&a.ui.ddmanager.drag(this,b);return!1},_mouseStop:function(b){var c=!1;a.ui.ddmanager&&!this.options.dropBehaviour&&(c=a.ui.ddmanager.drop(this,b)),this.dropped&&(c=this.dropped,this.dropped=!1);if((!this.element[0]||!this.element[0].parentNode)&&this.options.helper=="original")return!1;if(this.options.revert=="invalid"&&!c||this.options.revert=="valid"&&c||this.options.revert===!0||a.isFunction(this.options.revert)&&this.options.revert.call(this.element,c)){var d=this;a(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){d._trigger("stop",b)!==!1&&d._clear()})}else this._trigger("stop",b)!==!1&&this._clear();return!1},_mouseUp:function(b){this.options.iframeFix===!0&&a("div.ui-draggable-iframeFix").each(function(){this.parentNode.removeChild(this)}),a.ui.ddmanager&&a.ui.ddmanager.dragStop(this,b);return a.ui.mouse.prototype._mouseUp.call(this,b)},cancel:function(){this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear();return this},_getHandle:function(b){var c=!this.options.handle||!a(this.options.handle,this.element).length?!0:!1;a(this.options.handle,this.element).find("*").andSelf().each(function(){this==b.target&&(c=!0)});return c},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b])):c.helper=="clone"?this.element.clone().removeAttr("id"):this.element;d.parents("body").length||d.appendTo(c.appendTo=="parent"?this.element[0].parentNode:c.appendTo),d[0]!=this.element[0]&&!/(fixed|absolute)/.test(d.css("position"))&&d.css("position","absolute");return d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.element.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[b.containment=="document"?0:a(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,b.containment=="document"?0:a(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,(b.containment=="document"?0:a(window).scrollLeft())+a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(b.containment=="document"?0:a(window).scrollTop())+(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)&&b.containment.constructor!=Array){var c=a(b.containment),d=c[0];if(!d)return;var e=c.offset(),f=a(d).css("overflow")!="hidden";this.containment=[(parseInt(a(d).css("borderLeftWidth"),10)||0)+(parseInt(a(d).css("paddingLeft"),10)||0),(parseInt(a(d).css("borderTopWidth"),10)||0)+(parseInt(a(d).css("paddingTop"),10)||0),(f?Math.max(d.scrollWidth,d.offsetWidth):d.offsetWidth)-(parseInt(a(d).css("borderLeftWidth"),10)||0)-(parseInt(a(d).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(f?Math.max(d.scrollHeight,d.offsetHeight):d.offsetHeight)-(parseInt(a(d).css("borderTopWidth"),10)||0)-(parseInt(a(d).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relative_container=c}else b.containment.constructor==Array&&(this.containment=b.containment)},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName),f=b.pageX,g=b.pageY;if(this.originalPosition){var h;if(this.containment){if(this.relative_container){var i=this.relative_container.offset();h=[this.containment[0]+i.left,this.containment[1]+i.top,this.containment[2]+i.left,this.containment[3]+i.top]}else h=this.containment;b.pageX-this.offset.click.left<h[0]&&(f=h[0]+this.offset.click.left),b.pageY-this.offset.click.top<h[1]&&(g=h[1]+this.offset.click.top),b.pageX-this.offset.click.left>h[2]&&(f=h[2]+this.offset.click.left),b.pageY-this.offset.click.top>h[3]&&(g=h[3]+this.offset.click.top)}if(c.grid){var j=c.grid[1]?this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1]:this.originalPageY;g=h?j-this.offset.click.top<h[1]||j-this.offset.click.top>h[3]?j-this.offset.click.top<h[1]?j+c.grid[1]:j-c.grid[1]:j:j;var k=c.grid[0]?this.originalPageX+Math.round((f-this.originalPageX)/c.grid[0])*c.grid[0]:this.originalPageX;f=h?k-this.offset.click.left<h[0]||k-this.offset.click.left>h[2]?k-this.offset.click.left<h[0]?k+c.grid[0]:k-c.grid[0]:k:k}}return{top:g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:d.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&a.browser.version<526&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:d.scrollLeft())}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]!=this.element[0]&&!this.cancelHelperRemoval&&this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1},_trigger:function(b,c,d){d=d||this._uiHash(),a.ui.plugin.call(this,b,[c,d]),b=="drag"&&(this.positionAbs=this._convertPositionTo("absolute"));return a.Widget.prototype._trigger.call(this,b,c,d)},plugins:{},_uiHash:function(a){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),a.extend(a.ui.draggable,{version:"1.8.17"}),a.ui.plugin.add("draggable","connectToSortable",{start:function(b,c){var d=a(this).data("draggable"),e=d.options,f=a.extend({},c,{item:d.element});d.sortables=[],a(e.connectToSortable).each(function(){var c=a.data(this,"sortable");c&&!c.options.disabled&&(d.sortables.push({instance:c,shouldRevert:c.options.revert}),c.refreshPositions(),c._trigger("activate",b,f))})},stop:function(b,c){var d=a(this).data("draggable"),e=a.extend({},c,{item:d.element});a.each(d.sortables,function(){this.instance.isOver?(this.instance.isOver=0,d.cancelHelperRemoval=!0,this.instance.cancelHelperRemoval=!1,this.shouldRevert&&(this.instance.options.revert=!0),this.instance._mouseStop(b),this.instance.options.helper=this.instance.options._helper,d.options.helper=="original"&&this.instance.currentItem.css({top:"auto",left:"auto"})):(this.instance.cancelHelperRemoval=!1,this.instance._trigger("deactivate",b,e))})},drag:function(b,c){var d=a(this).data("draggable"),e=this,f=function(b){var c=this.offset.click.top,d=this.offset.click.left,e=this.positionAbs.top,f=this.positionAbs.left,g=b.height,h=b.width,i=b.top,j=b.left;return a.ui.isOver(e+c,f+d,i,j,g,h)};a.each(d.sortables,function(f){this.instance.positionAbs=d.positionAbs,this.instance.helperProportions=d.helperProportions,this.instance.offset.click=d.offset.click,this.instance._intersectsWith(this.instance.containerCache)?(this.instance.isOver||(this.instance.isOver=1,this.instance.currentItem=a(e).clone().removeAttr("id").appendTo(this.instance.element).data("sortable-item",!0),this.instance.options._helper=this.instance.options.helper,this.instance.options.helper=function(){return c.helper[0]},b.target=this.instance.currentItem[0],this.instance._mouseCapture(b,!0),this.instance._mouseStart(b,!0,!0),this.instance.offset.click.top=d.offset.click.top,this.instance.offset.click.left=d.offset.click.left,this.instance.offset.parent.left-=d.offset.parent.left-this.instance.offset.parent.left,this.instance.offset.parent.top-=d.offset.parent.top-this.instance.offset.parent.top,d._trigger("toSortable",b),d.dropped=this.instance.element,d.currentItem=d.element,this.instance.fromOutside=d),this.instance.currentItem&&this.instance._mouseDrag(b)):this.instance.isOver&&(this.instance.isOver=0,this.instance.cancelHelperRemoval=!0,this.instance.options.revert=!1,this.instance._trigger("out",b,this.instance._uiHash(this.instance)),this.instance._mouseStop(b,!0),this.instance.options.helper=this.instance.options._helper,this.instance.currentItem.remove(),this.instance.placeholder&&this.instance.placeholder.remove(),d._trigger("fromSortable",b),d.dropped=!1)})}}),a.ui.plugin.add("draggable","cursor",{start:function(b,c){var d=a("body"),e=a(this).data("draggable").options;d.css("cursor")&&(e._cursor=d.css("cursor")),d.css("cursor",e.cursor)},stop:function(b,c){var d=a(this).data("draggable").options;d._cursor&&a("body").css("cursor",d._cursor)}}),a.ui.plugin.add("draggable","opacity",{start:function(b,c){var d=a(c.helper),e=a(this).data("draggable").options;d.css("opacity")&&(e._opacity=d.css("opacity")),d.css("opacity",e.opacity)},stop:function(b,c){var d=a(this).data("draggable").options;d._opacity&&a(c.helper).css("opacity",d._opacity)}}),a.ui.plugin.add("draggable","scroll",{start:function(b,c){var d=a(this).data("draggable");d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML"&&(d.overflowOffset=d.scrollParent.offset())},drag:function(b,c){var d=a(this).data("draggable"),e=d.options,f=!1;if(d.scrollParent[0]!=document&&d.scrollParent[0].tagName!="HTML"){if(!e.axis||e.axis!="x")d.overflowOffset.top+d.scrollParent[0].offsetHeight-b.pageY<e.scrollSensitivity?d.scrollParent[0].scrollTop=f=d.scrollParent[0].scrollTop+e.scrollSpeed:b.pageY-d.overflowOffset.top<e.scrollSensitivity&&(d.scrollParent[0].scrollTop=f=d.scrollParent[0].scrollTop-e.scrollSpeed);if(!e.axis||e.axis!="y")d.overflowOffset.left+d.scrollParent[0].offsetWidth-b.pageX<e.scrollSensitivity?d.scrollParent[0].scrollLeft=f=d.scrollParent[0].scrollLeft+e.scrollSpeed:b.pageX-d.overflowOffset.left<e.scrollSensitivity&&(d.scrollParent[0].scrollLeft=f=d.scrollParent[0].scrollLeft-e.scrollSpeed)}else{if(!e.axis||e.axis!="x")b.pageY-a(document).scrollTop()<e.scrollSensitivity?f=a(document).scrollTop(a(document).scrollTop()-e.scrollSpeed):a(window).height()-(b.pageY-a(document).scrollTop())<e.scrollSensitivity&&(f=a(document).scrollTop(a(document).scrollTop()+e.scrollSpeed));if(!e.axis||e.axis!="y")b.pageX-a(document).scrollLeft()<e.scrollSensitivity?f=a(document).scrollLeft(a(document).scrollLeft()-e.scrollSpeed):a(window).width()-(b.pageX-a(document).scrollLeft())<e.scrollSensitivity&&(f=a(document).scrollLeft(a(document).scrollLeft()+e.scrollSpeed))}f!==!1&&a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(d,b)}}),a.ui.plugin.add("draggable","snap",{start:function(b,c){var d=a(this).data("draggable"),e=d.options;d.snapElements=[],a(e.snap.constructor!=String?e.snap.items||":data(draggable)":e.snap).each(function(){var b=a(this),c=b.offset();this!=d.element[0]&&d.snapElements.push({item:this,width:b.outerWidth(),height:b.outerHeight(),top:c.top,left:c.left})})},drag:function(b,c){var d=a(this).data("draggable"),e=d.options,f=e.snapTolerance,g=c.offset.left,h=g+d.helperProportions.width,i=c.offset.top,j=i+d.helperProportions.height;for(var k=d.snapElements.length-1;k>=0;k--){var l=d.snapElements[k].left,m=l+d.snapElements[k].width,n=d.snapElements[k].top,o=n+d.snapElements[k].height;if(!(l-f<g&&g<m+f&&n-f<i&&i<o+f||l-f<g&&g<m+f&&n-f<j&&j<o+f||l-f<h&&h<m+f&&n-f<i&&i<o+f||l-f<h&&h<m+f&&n-f<j&&j<o+f)){d.snapElements[k].snapping&&d.options.snap.release&&d.options.snap.release.call(d.element,b,a.extend(d._uiHash(),{snapItem:d.snapElements[k].item})),d.snapElements[k].snapping=!1;continue}if(e.snapMode!="inner"){var p=Math.abs(n-j)<=f,q=Math.abs(o-i)<=f,r=Math.abs(l-h)<=f,s=Math.abs(m-g)<=f;p&&(c.position.top=d._convertPositionTo("relative",{top:n-d.helperProportions.height,left:0}).top-d.margins.top),q&&(c.position.top=d._convertPositionTo("relative",{top:o,left:0}).top-d.margins.top),r&&(c.position.left=d._convertPositionTo("relative",{top:0,left:l-d.helperProportions.width}).left-d.margins.left),s&&(c.position.left=d._convertPositionTo("relative",{top:0,left:m}).left-d.margins.left)}var t=p||q||r||s;if(e.snapMode!="outer"){var p=Math.abs(n-i)<=f,q=Math.abs(o-j)<=f,r=Math.abs(l-g)<=f,s=Math.abs(m-h)<=f;p&&(c.position.top=d._convertPositionTo("relative",{top:n,left:0}).top-d.margins.top),q&&(c.position.top=d._convertPositionTo("relative",{top:o-d.helperProportions.height,left:0}).top-d.margins.top),r&&(c.position.left=d._convertPositionTo("relative",{top:0,left:l}).left-d.margins.left),s&&(c.position.left=d._convertPositionTo("relative",{top:0,left:m-d.helperProportions.width}).left-d.margins.left)}!d.snapElements[k].snapping&&(p||q||r||s||t)&&d.options.snap.snap&&d.options.snap.snap.call(d.element,b,a.extend(d._uiHash(),{snapItem:d.snapElements[k].item})),d.snapElements[k].snapping=p||q||r||s||t}}}),a.ui.plugin.add("draggable","stack",{start:function(b,c){var d=a(this).data("draggable").options,e=a.makeArray(a(d.stack)).sort(function(b,c){return(parseInt(a(b).css("zIndex"),10)||0)-(parseInt(a(c).css("zIndex"),10)||0)});if(!!e.length){var f=parseInt(e[0].style.zIndex)||0;a(e).each(function(a){this.style.zIndex=f+a}),this[0].style.zIndex=f+e.length}}}),a.ui.plugin.add("draggable","zIndex",{start:function(b,c){var d=a(c.helper),e=a(this).data("draggable").options;d.css("zIndex")&&(e._zIndex=d.css("zIndex")),d.css("zIndex",e.zIndex)},stop:function(b,c){var d=a(this).data("draggable").options;d._zIndex&&a(c.helper).css("zIndex",d._zIndex)}})}(jQuery),function(a,b){a.widget("ui.droppable",{widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect"},_create:function(){var b=this.options,c=b.accept;this.isover=0,this.isout=1,this.accept=a.isFunction(c)?c:function(a){return a.is(c)},this.proportions={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight},a.ui.ddmanager.droppables[b.scope]=a.ui.ddmanager.droppables[b.scope]||[],a.ui.ddmanager.droppables[b.scope].push(this),b.addClasses&&this.element.addClass("ui-droppable")},destroy:function(){var b=a.ui.ddmanager.droppables[this.options.scope];for(var c=0;c<b.length;c++)b[c]==this&&b.splice(c,1);this.element.removeClass("ui-droppable ui-droppable-disabled").removeData("droppable").unbind(".droppable");return this},_setOption:function(b,c){b=="accept"&&(this.accept=a.isFunction(c)?c:function(a){return a.is(c)}),a.Widget.prototype._setOption.apply(this,arguments)},_activate:function(b){var c=a.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),c&&this._trigger("activate",b,this.ui(c))},_deactivate:function(b){var c=a.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),c&&this._trigger("deactivate",b,this.ui(c))},_over:function(b){var c=a.ui.ddmanager.current;!!c&&(c.currentItem||c.element)[0]!=this.element[0]&&this.accept.call(this.element[0],c.currentItem||c.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",b,this.ui(c)))},_out:function(b){var c=a.ui.ddmanager.current;!!c&&(c.currentItem||c.element)[0]!=this.element[0]&&this.accept.call(this.element[0],c.currentItem||c.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",b,this.ui(c)))},_drop:function(b,c){var d=c||a.ui.ddmanager.current;if(!d||(d.currentItem||d.element)[0]==this.element[0])return!1;var e=!1;this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function(){var b=a.data(this,"droppable");if(b.options.greedy&&!b.options.disabled&&b.options.scope==d.options.scope&&b.accept.call(b.element[0],d.currentItem||d.element)&&a.ui.intersect(d,a.extend(b,{offset:b.element.offset()}),b.options.tolerance)){e=!0;return!1}});if(e)return!1;if(this.accept.call(this.element[0],d.currentItem||d.element)){this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",b,this.ui(d));return this.element}return!1},ui:function(a){return{draggable:a.currentItem||a.element,helper:a.helper,position:a.position,offset:a.positionAbs}}}),a.extend(a.ui.droppable,{version:"1.8.17"}),a.ui.intersect=function(b,c,d){if(!c.offset)return!1;var e=(b.positionAbs||b.position.absolute).left,f=e+b.helperProportions.width,g=(b.positionAbs||b.position.absolute).top,h=g+b.helperProportions.height,i=c.offset.left,j=i+c.proportions.width,k=c.offset.top,l=k+c.proportions.height;switch(d){case"fit":return i<=e&&f<=j&&k<=g&&h<=l;case"intersect":return i<e+b.helperProportions.width/2&&f-b.helperProportions.width/2<j&&k<g+b.helperProportions.height/2&&h-b.helperProportions.height/2<l;case"pointer":var m=(b.positionAbs||b.position.absolute).left+(b.clickOffset||b.offset.click).left,n=(b.positionAbs||b.position.absolute).top+(b.clickOffset||b.offset.click).top,o=a.ui.isOver(n,m,k,i,c.proportions.height,c.proportions.width);return o;case"touch":return(g>=k&&g<=l||h>=k&&h<=l||g<k&&h>l)&&(e>=i&&e<=j||f>=i&&f<=j||e<i&&f>j);default:return!1}},a.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(b,c){var d=a.ui.ddmanager.droppables[b.options.scope]||[],e=c?c.type:null,f=(b.currentItem||b.element).find(":data(droppable)").andSelf();droppablesLoop:for(var g=0;g<d.length;g++){if(d[g].options.disabled||b&&!d[g].accept.call(d[g].element[0],b.currentItem||b.element))continue;for(var h=0;h<f.length;h++)if(f[h]==d[g].element[0]){d[g].proportions.height=0;continue droppablesLoop}d[g].visible=d[g].element.css("display")!="none";if(!d[g].visible)continue;e=="mousedown"&&d[g]._activate.call(d[g],c),d[g].offset=d[g].element.offset(),d[g].proportions={width:d[g].element[0].offsetWidth,height:d[g].element +[0].offsetHeight}}},drop:function(b,c){var d=!1;a.each(a.ui.ddmanager.droppables[b.options.scope]||[],function(){!this.options||(!this.options.disabled&&this.visible&&a.ui.intersect(b,this,this.options.tolerance)&&(d=this._drop.call(this,c)||d),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],b.currentItem||b.element)&&(this.isout=1,this.isover=0,this._deactivate.call(this,c)))});return d},dragStart:function(b,c){b.element.parents(":not(body,html)").bind("scroll.droppable",function(){b.options.refreshPositions||a.ui.ddmanager.prepareOffsets(b,c)})},drag:function(b,c){b.options.refreshPositions&&a.ui.ddmanager.prepareOffsets(b,c),a.each(a.ui.ddmanager.droppables[b.options.scope]||[],function(){if(!(this.options.disabled||this.greedyChild||!this.visible)){var d=a.ui.intersect(b,this,this.options.tolerance),e=!d&&this.isover==1?"isout":d&&this.isover==0?"isover":null;if(!e)return;var f;if(this.options.greedy){var g=this.element.parents(":data(droppable):eq(0)");g.length&&(f=a.data(g[0],"droppable"),f.greedyChild=e=="isover"?1:0)}f&&e=="isover"&&(f.isover=0,f.isout=1,f._out.call(f,c)),this[e]=1,this[e=="isout"?"isover":"isout"]=0,this[e=="isover"?"_over":"_out"].call(this,c),f&&e=="isout"&&(f.isout=0,f.isover=1,f._over.call(f,c))}})},dragStop:function(b,c){b.element.parents(":not(body,html)").unbind("scroll.droppable"),b.options.refreshPositions||a.ui.ddmanager.prepareOffsets(b,c)}}}(jQuery),function(a,b){a.widget("ui.resizable",a.ui.mouse,{widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:1e3},_create:function(){var b=this,c=this.options;this.element.addClass("ui-resizable"),a.extend(this,{_aspectRatio:!!c.aspectRatio,aspectRatio:c.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:c.helper||c.ghost||c.animate?c.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(/relative/.test(this.element.css("position"))&&a.browser.opera&&this.element.css({position:"relative",top:"auto",left:"auto"}),this.element.wrap(a('<div class="ui-wrapper" style="overflow: hidden;"></div>').css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("resizable",this.element.data("resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=c.handles||(a(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor==String){this.handles=="all"&&(this.handles="n,e,s,w,se,sw,ne,nw");var d=this.handles.split(",");this.handles={};for(var e=0;e<d.length;e++){var f=a.trim(d[e]),g="ui-resizable-"+f,h=a('<div class="ui-resizable-handle '+g+'"></div>');/sw|se|ne|nw/.test(f)&&h.css({zIndex:++c.zIndex}),"se"==f&&h.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[f]=".ui-resizable-"+f,this.element.append(h)}}this._renderAxis=function(b){b=b||this.element;for(var c in this.handles){this.handles[c].constructor==String&&(this.handles[c]=a(this.handles[c],this.element).show());if(this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)){var d=a(this.handles[c],this.element),e=0;e=/sw|ne|nw|se|n|s/.test(c)?d.outerHeight():d.outerWidth();var f=["padding",/ne|nw|n/.test(c)?"Top":/se|sw|s/.test(c)?"Bottom":/^e$/.test(c)?"Right":"Left"].join("");b.css(f,e),this._proportionallyResize()}if(!a(this.handles[c]).length)continue}},this._renderAxis(this.element),this._handles=a(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){if(!b.resizing){if(this.className)var a=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);b.axis=a&&a[1]?a[1]:"se"}}),c.autoHide&&(this._handles.hide(),a(this.element).addClass("ui-resizable-autohide").hover(function(){c.disabled||(a(this).removeClass("ui-resizable-autohide"),b._handles.show())},function(){c.disabled||b.resizing||(a(this).addClass("ui-resizable-autohide"),b._handles.hide())})),this._mouseInit()},destroy:function(){this._mouseDestroy();var b=function(b){a(b).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};if(this.elementIsWrapper){b(this.element);var c=this.element;c.after(this.originalElement.css({position:c.css("position"),width:c.outerWidth(),height:c.outerHeight(),top:c.css("top"),left:c.css("left")})).remove()}this.originalElement.css("resize",this.originalResizeStyle),b(this.originalElement);return this},_mouseCapture:function(b){var c=!1;for(var d in this.handles)a(this.handles[d])[0]==b.target&&(c=!0);return!this.options.disabled&&c},_mouseStart:function(b){var d=this.options,e=this.element.position(),f=this.element;this.resizing=!0,this.documentScroll={top:a(document).scrollTop(),left:a(document).scrollLeft()},(f.is(".ui-draggable")||/absolute/.test(f.css("position")))&&f.css({position:"absolute",top:e.top,left:e.left}),a.browser.opera&&/relative/.test(f.css("position"))&&f.css({position:"relative",top:"auto",left:"auto"}),this._renderProxy();var g=c(this.helper.css("left")),h=c(this.helper.css("top"));d.containment&&(g+=a(d.containment).scrollLeft()||0,h+=a(d.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:g,top:h},this.size=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalSize=this._helper?{width:f.outerWidth(),height:f.outerHeight()}:{width:f.width(),height:f.height()},this.originalPosition={left:g,top:h},this.sizeDiff={width:f.outerWidth()-f.width(),height:f.outerHeight()-f.height()},this.originalMousePosition={left:b.pageX,top:b.pageY},this.aspectRatio=typeof d.aspectRatio=="number"?d.aspectRatio:this.originalSize.width/this.originalSize.height||1;var i=a(".ui-resizable-"+this.axis).css("cursor");a("body").css("cursor",i=="auto"?this.axis+"-resize":i),f.addClass("ui-resizable-resizing"),this._propagate("start",b);return!0},_mouseDrag:function(b){var c=this.helper,d=this.options,e={},f=this,g=this.originalMousePosition,h=this.axis,i=b.pageX-g.left||0,j=b.pageY-g.top||0,k=this._change[h];if(!k)return!1;var l=k.apply(this,[b,i,j]),m=a.browser.msie&&a.browser.version<7,n=this.sizeDiff;this._updateVirtualBoundaries(b.shiftKey);if(this._aspectRatio||b.shiftKey)l=this._updateRatio(l,b);l=this._respectSize(l,b),this._propagate("resize",b),c.css({top:this.position.top+"px",left:this.position.left+"px",width:this.size.width+"px",height:this.size.height+"px"}),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),this._updateCache(l),this._trigger("resize",b,this.ui());return!1},_mouseStop:function(b){this.resizing=!1;var c=this.options,d=this;if(this._helper){var e=this._proportionallyResizeElements,f=e.length&&/textarea/i.test(e[0].nodeName),g=f&&a.ui.hasScroll(e[0],"left")?0:d.sizeDiff.height,h=f?0:d.sizeDiff.width,i={width:d.helper.width()-h,height:d.helper.height()-g},j=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,k=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;c.animate||this.element.css(a.extend(i,{top:k,left:j})),d.helper.height(d.size.height),d.helper.width(d.size.width),this._helper&&!c.animate&&this._proportionallyResize()}a("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",b),this._helper&&this.helper.remove();return!1},_updateVirtualBoundaries:function(a){var b=this.options,c,e,f,g,h;h={minWidth:d(b.minWidth)?b.minWidth:0,maxWidth:d(b.maxWidth)?b.maxWidth:Infinity,minHeight:d(b.minHeight)?b.minHeight:0,maxHeight:d(b.maxHeight)?b.maxHeight:Infinity};if(this._aspectRatio||a)c=h.minHeight*this.aspectRatio,f=h.minWidth/this.aspectRatio,e=h.maxHeight*this.aspectRatio,g=h.maxWidth/this.aspectRatio,c>h.minWidth&&(h.minWidth=c),f>h.minHeight&&(h.minHeight=f),e<h.maxWidth&&(h.maxWidth=e),g<h.maxHeight&&(h.maxHeight=g);this._vBoundaries=h},_updateCache:function(a){var b=this.options;this.offset=this.helper.offset(),d(a.left)&&(this.position.left=a.left),d(a.top)&&(this.position.top=a.top),d(a.height)&&(this.size.height=a.height),d(a.width)&&(this.size.width=a.width)},_updateRatio:function(a,b){var c=this.options,e=this.position,f=this.size,g=this.axis;d(a.height)?a.width=a.height*this.aspectRatio:d(a.width)&&(a.height=a.width/this.aspectRatio),g=="sw"&&(a.left=e.left+(f.width-a.width),a.top=null),g=="nw"&&(a.top=e.top+(f.height-a.height),a.left=e.left+(f.width-a.width));return a},_respectSize:function(a,b){var c=this.helper,e=this._vBoundaries,f=this._aspectRatio||b.shiftKey,g=this.axis,h=d(a.width)&&e.maxWidth&&e.maxWidth<a.width,i=d(a.height)&&e.maxHeight&&e.maxHeight<a.height,j=d(a.width)&&e.minWidth&&e.minWidth>a.width,k=d(a.height)&&e.minHeight&&e.minHeight>a.height;j&&(a.width=e.minWidth),k&&(a.height=e.minHeight),h&&(a.width=e.maxWidth),i&&(a.height=e.maxHeight);var l=this.originalPosition.left+this.originalSize.width,m=this.position.top+this.size.height,n=/sw|nw|w/.test(g),o=/nw|ne|n/.test(g);j&&n&&(a.left=l-e.minWidth),h&&n&&(a.left=l-e.maxWidth),k&&o&&(a.top=m-e.minHeight),i&&o&&(a.top=m-e.maxHeight);var p=!a.width&&!a.height;p&&!a.left&&a.top?a.top=null:p&&!a.top&&a.left&&(a.left=null);return a},_proportionallyResize:function(){var b=this.options;if(!!this._proportionallyResizeElements.length){var c=this.helper||this.element;for(var d=0;d<this._proportionallyResizeElements.length;d++){var e=this._proportionallyResizeElements[d];if(!this.borderDif){var f=[e.css("borderTopWidth"),e.css("borderRightWidth"),e.css("borderBottomWidth"),e.css("borderLeftWidth")],g=[e.css("paddingTop"),e.css("paddingRight"),e.css("paddingBottom"),e.css("paddingLeft")];this.borderDif=a.map(f,function(a,b){var c=parseInt(a,10)||0,d=parseInt(g[b],10)||0;return c+d})}if(a.browser.msie&&(!!a(c).is(":hidden")||!!a(c).parents(":hidden").length))continue;e.css({height:c.height()-this.borderDif[0]-this.borderDif[2]||0,width:c.width()-this.borderDif[1]-this.borderDif[3]||0})}}},_renderProxy:function(){var b=this.element,c=this.options;this.elementOffset=b.offset();if(this._helper){this.helper=this.helper||a('<div style="overflow:hidden;"></div>');var d=a.browser.msie&&a.browser.version<7,e=d?1:0,f=d?2:-1;this.helper.addClass(this._helper).css({width:this.element.outerWidth()+f,height:this.element.outerHeight()+f,position:"absolute",left:this.elementOffset.left-e+"px",top:this.elementOffset.top-e+"px",zIndex:++c.zIndex}),this.helper.appendTo("body").disableSelection()}else this.helper=this.element},_change:{e:function(a,b,c){return{width:this.originalSize.width+b}},w:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{left:f.left+b,width:e.width-b}},n:function(a,b,c){var d=this.options,e=this.originalSize,f=this.originalPosition;return{top:f.top+c,height:e.height-c}},s:function(a,b,c){return{height:this.originalSize.height+c}},se:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},sw:function(b,c,d){return a.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[b,c,d]))},ne:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[b,c,d]))},nw:function(b,c,d){return a.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[b,c,d]))}},_propagate:function(b,c){a.ui.plugin.call(this,b,[c,this.ui()]),b!="resize"&&this._trigger(b,c,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),a.extend(a.ui.resizable,{version:"1.8.17"}),a.ui.plugin.add("resizable","alsoResize",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=function(b){a(b).each(function(){var b=a(this);b.data("resizable-alsoresize",{width:parseInt(b.width(),10),height:parseInt(b.height(),10),left:parseInt(b.css("left"),10),top:parseInt(b.css("top"),10),position:b.css("position")})})};typeof e.alsoResize=="object"&&!e.alsoResize.parentNode?e.alsoResize.length?(e.alsoResize=e.alsoResize[0],f(e.alsoResize)):a.each(e.alsoResize,function(a){f(a)}):f(e.alsoResize)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.originalSize,g=d.originalPosition,h={height:d.size.height-f.height||0,width:d.size.width-f.width||0,top:d.position.top-g.top||0,left:d.position.left-g.left||0},i=function(b,e){a(b).each(function(){var b=a(this),f=a(this).data("resizable-alsoresize"),g={},i=e&&e.length?e:b.parents(c.originalElement[0]).length?["width","height"]:["width","height","top","left"];a.each(i,function(a,b){var c=(f[b]||0)+(h[b]||0);c&&c>=0&&(g[b]=c||null)}),a.browser.opera&&/relative/.test(b.css("position"))&&(d._revertToRelativePosition=!0,b.css({position:"absolute",top:"auto",left:"auto"})),b.css(g)})};typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?a.each(e.alsoResize,function(a,b){i(a,b)}):i(e.alsoResize)},stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=function(b){a(b).each(function(){var b=a(this);b.css({position:b.data("resizable-alsoresize").position})})};d._revertToRelativePosition&&(d._revertToRelativePosition=!1,typeof e.alsoResize=="object"&&!e.alsoResize.nodeType?a.each(e.alsoResize,function(a){f(a)}):f(e.alsoResize)),a(this).removeData("resizable-alsoresize")}}),a.ui.plugin.add("resizable","animate",{stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d._proportionallyResizeElements,g=f.length&&/textarea/i.test(f[0].nodeName),h=g&&a.ui.hasScroll(f[0],"left")?0:d.sizeDiff.height,i=g?0:d.sizeDiff.width,j={width:d.size.width-i,height:d.size.height-h},k=parseInt(d.element.css("left"),10)+(d.position.left-d.originalPosition.left)||null,l=parseInt(d.element.css("top"),10)+(d.position.top-d.originalPosition.top)||null;d.element.animate(a.extend(j,l&&k?{top:l,left:k}:{}),{duration:e.animateDuration,easing:e.animateEasing,step:function(){var c={width:parseInt(d.element.css("width"),10),height:parseInt(d.element.css("height"),10),top:parseInt(d.element.css("top"),10),left:parseInt(d.element.css("left"),10)};f&&f.length&&a(f[0]).css({width:c.width,height:c.height}),d._updateCache(c),d._propagate("resize",b)}})}}),a.ui.plugin.add("resizable","containment",{start:function(b,d){var e=a(this).data("resizable"),f=e.options,g=e.element,h=f.containment,i=h instanceof a?h.get(0):/parent/.test(h)?g.parent().get(0):h;if(!!i){e.containerElement=a(i);if(/document/.test(h)||h==document)e.containerOffset={left:0,top:0},e.containerPosition={left:0,top:0},e.parentData={element:a(document),left:0,top:0,width:a(document).width(),height:a(document).height()||document.body.parentNode.scrollHeight};else{var j=a(i),k=[];a(["Top","Right","Left","Bottom"]).each(function(a,b){k[a]=c(j.css("padding"+b))}),e.containerOffset=j.offset(),e.containerPosition=j.position(),e.containerSize={height:j.innerHeight()-k[3],width:j.innerWidth()-k[1]};var l=e.containerOffset,m=e.containerSize.height,n=e.containerSize.width,o=a.ui.hasScroll(i,"left")?i.scrollWidth:n,p=a.ui.hasScroll(i)?i.scrollHeight:m;e.parentData={element:i,left:l.left,top:l.top,width:o,height:p}}}},resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.containerSize,g=d.containerOffset,h=d.size,i=d.position,j=d._aspectRatio||b.shiftKey,k={top:0,left:0},l=d.containerElement;l[0]!=document&&/static/.test(l.css("position"))&&(k=g),i.left<(d._helper?g.left:0)&&(d.size.width=d.size.width+(d._helper?d.position.left-g.left:d.position.left-k.left),j&&(d.size.height=d.size.width/e.aspectRatio),d.position.left=e.helper?g.left:0),i.top<(d._helper?g.top:0)&&(d.size.height=d.size.height+(d._helper?d.position.top-g.top:d.position.top),j&&(d.size.width=d.size.height*e.aspectRatio),d.position.top=d._helper?g.top:0),d.offset.left=d.parentData.left+d.position.left,d.offset.top=d.parentData.top+d.position.top;var m=Math.abs((d._helper?d.offset.left-k.left:d.offset.left-k.left)+d.sizeDiff.width),n=Math.abs((d._helper?d.offset.top-k.top:d.offset.top-g.top)+d.sizeDiff.height),o=d.containerElement.get(0)==d.element.parent().get(0),p=/relative|absolute/.test(d.containerElement.css("position"));o&&p&&(m-=d.parentData.left),m+d.size.width>=d.parentData.width&&(d.size.width=d.parentData.width-m,j&&(d.size.height=d.size.width/d.aspectRatio)),n+d.size.height>=d.parentData.height&&(d.size.height=d.parentData.height-n,j&&(d.size.width=d.size.height*d.aspectRatio))},stop:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.position,g=d.containerOffset,h=d.containerPosition,i=d.containerElement,j=a(d.helper),k=j.offset(),l=j.outerWidth()-d.sizeDiff.width,m=j.outerHeight()-d.sizeDiff.height;d._helper&&!e.animate&&/relative/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m}),d._helper&&!e.animate&&/static/.test(i.css("position"))&&a(this).css({left:k.left-h.left-g.left,width:l,height:m})}}),a.ui.plugin.add("resizable","ghost",{start:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size;d.ghost=d.originalElement.clone(),d.ghost.css({opacity:.25,display:"block",position:"relative",height:f.height,width:f.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof e.ghost=="string"?e.ghost:""),d.ghost.appendTo(d.helper)},resize:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.ghost.css({position:"relative",height:d.size.height,width:d.size.width})},stop:function(b,c){var d=a(this).data("resizable"),e=d.options;d.ghost&&d.helper&&d.helper.get(0).removeChild(d.ghost.get(0))}}),a.ui.plugin.add("resizable","grid",{resize:function(b,c){var d=a(this).data("resizable"),e=d.options,f=d.size,g=d.originalSize,h=d.originalPosition,i=d.axis,j=e._aspectRatio||b.shiftKey;e.grid=typeof e.grid=="number"?[e.grid,e.grid]:e.grid;var k=Math.round((f.width-g.width)/(e.grid[0]||1))*(e.grid[0]||1),l=Math.round((f.height-g.height)/(e.grid[1]||1))*(e.grid[1]||1);/^(se|s|e)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l):/^(ne)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l):/^(sw)$/.test(i)?(d.size.width=g.width+k,d.size.height=g.height+l,d.position.left=h.left-k):(d.size.width=g.width+k,d.size.height=g.height+l,d.position.top=h.top-l,d.position.left=h.left-k)}});var c=function(a){return parseInt(a,10)||0},d=function(a){return!isNaN(parseInt(a,10))}}(jQuery),function(a,b){a.widget("ui.selectable",a.ui.mouse,{options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch"},_create:function(){var b=this;this.element.addClass("ui-selectable"),this.dragged=!1;var c;this.refresh=function(){c=a(b.options.filter,b.element[0]),c.addClass("ui-selectee"),c.each(function(){var b=a(this),c=b.offset();a.data(this,"selectable-item",{element:this,$element:b,left:c.left,top:c.top,right:c.left+b.outerWidth(),bottom:c.top+b.outerHeight(),startselected:!1,selected:b.hasClass("ui-selected"),selecting:b.hasClass("ui-selecting"),unselecting:b.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=c.addClass("ui-selectee"),this._mouseInit(),this.helper=a("<div class='ui-selectable-helper'></div>")},destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled").removeData("selectable").unbind(".selectable"),this._mouseDestroy();return this},_mouseStart:function(b){var c=this;this.opos=[b.pageX,b.pageY];if(!this.options.disabled){var d=this.options;this.selectees=a(d.filter,this.element[0]),this._trigger("start",b),a(d.appendTo).append(this.helper),this.helper.css({left:b.clientX,top:b.clientY,width:0,height:0}),d.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var d=a.data(this,"selectable-item");d.startselected=!0,!b.metaKey&&!b.ctrlKey&&(d.$element.removeClass("ui-selected"),d.selected=!1,d.$element.addClass("ui-unselecting"),d.unselecting=!0,c._trigger("unselecting",b,{unselecting:d.element}))}),a(b.target).parents().andSelf().each(function(){var d=a.data(this,"selectable-item");if(d){var e=!b.metaKey&&!b.ctrlKey||!d.$element.hasClass("ui-selected");d.$element.removeClass(e?"ui-unselecting":"ui-selected").addClass(e?"ui-selecting":"ui-unselecting"),d.unselecting=!e,d.selecting=e,d.selected=e,e?c._trigger("selecting",b,{selecting:d.element}):c._trigger("unselecting",b,{unselecting:d.element});return!1}})}},_mouseDrag:function(b){var c=this;this.dragged=!0;if(!this.options.disabled){var d=this.options,e=this.opos[0],f=this.opos[1],g=b.pageX,h=b.pageY;if(e>g){var i=g;g=e,e=i}if(f>h){var i=h;h=f,f=i}this.helper.css({left:e,top:f,width:g-e,height:h-f}),this.selectees.each(function(){var i=a.data(this,"selectable-item");if(!!i&&i.element!=c.element[0]){var j=!1;d.tolerance=="touch"?j=!(i.left>g||i.right<e||i.top>h||i.bottom<f):d.tolerance=="fit"&&(j=i.left>e&&i.right<g&&i.top>f&&i.bottom<h),j?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,c._trigger("selecting",b,{selecting:i.element}))):(i.selecting&&((b.metaKey||b.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),c._trigger("unselecting",b,{unselecting:i.element}))),i.selected&&!b.metaKey&&!b.ctrlKey&&!i.startselected&&(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,c._trigger("unselecting",b,{unselecting:i.element})))}});return!1}},_mouseStop:function(b){var c=this;this.dragged=!1;var d=this.options;a(".ui-unselecting",this.element[0]).each(function(){var d=a.data(this,"selectable-item");d.$element.removeClass("ui-unselecting"),d.unselecting=!1,d.startselected=!1,c._trigger("unselected",b,{unselected:d.element})}),a(".ui-selecting",this.element[0]).each(function(){var d=a.data(this,"selectable-item");d.$element.removeClass("ui-selecting").addClass("ui-selected"),d.selecting=!1,d.selected=!0,d.startselected=!0,c._trigger("selected",b,{selected:d.element})}),this._trigger("stop",b),this.helper.remove();return!1}}),a.extend(a.ui.selectable,{version:"1.8.17"})}(jQuery),function(a,b){a.widget("ui.sortable",a.ui.mouse,{widgetEventPrefix:"sort",options:{appendTo:"parent",axis:!1,connectWith:!1,containment:!1,cursor:"auto",cursorAt:!1,dropOnEmpty:!0,forcePlaceholderSize:!1,forceHelperSize:!1,grid:!1,handle:!1,helper:"original",items:"> *",opacity:!1,placeholder:!1,revert:!1,scroll:!0,scrollSensitivity:20,scrollSpeed:20,scope:"default",tolerance:"intersect",zIndex:1e3},_create:function(){var a=this.options;this.containerCache={},this.element.addClass("ui-sortable"),this.refresh(),this.floating=this.items.length?a.axis==="x"||/left|right/.test(this.items[0].item.css("float"))||/inline|table-cell/.test(this.items[0].item.css("display")):!1,this.offset=this.element.offset(),this._mouseInit()},destroy:function(){this.element.removeClass("ui-sortable ui-sortable-disabled"),this._mouseDestroy();for(var a=this.items.length-1;a>=0;a--)this.items[a].item.removeData(this.widgetName+"-item");return this},_setOption:function(b,c){b==="disabled"?(this.options[b]=c,this.widget()[c?"addClass":"removeClass"]("ui-sortable-disabled")):a.Widget.prototype._setOption.apply(this,arguments)},_mouseCapture:function(b,c){var d=this;if(this.reverting)return!1;if(this.options.disabled||this.options.type=="static")return!1;this._refreshItems(b);var e=null,f=this,g=a(b.target).parents().each(function(){if(a.data(this,d.widgetName+"-item")==f){e=a(this);return!1}});a.data(b.target,d.widgetName+"-item")==f&&(e=a(b.target));if(!e)return!1;if(this.options.handle&&!c){var h=!1;a(this.options.handle,e).find("*").andSelf().each(function(){this==b.target&&(h=!0)});if(!h)return!1}this.currentItem=e,this._removeCurrentsFromItems();return!0},_mouseStart:function(b,c,d){var e=this.options,f=this;this.currentContainer=this,this.refreshPositions(),this.helper=this._createHelper(b),this._cacheHelperProportions(),this._cacheMargins(),this.scrollParent=this.helper.scrollParent(),this.offset=this.currentItem.offset(),this.offset={top:this.offset.top-this.margins.top,left:this.offset.left-this.margins.left},this.helper.css("position","absolute"),this.cssPosition=this.helper.css("position"),a.extend(this.offset,{click:{left:b.pageX-this.offset.left,top:b.pageY-this.offset.top},parent:this._getParentOffset(),relative:this._getRelativeOffset()}),this.originalPosition=this._generatePosition(b),this.originalPageX=b.pageX,this.originalPageY=b.pageY,e.cursorAt&&this._adjustOffsetFromHelper(e.cursorAt),this.domPosition={prev:this.currentItem.prev()[0],parent:this.currentItem.parent()[0]},this.helper[0]!=this.currentItem[0]&&this.currentItem.hide(),this._createPlaceholder(),e.containment&&this._setContainment(),e.cursor&&(a("body").css("cursor")&&(this._storedCursor=a("body").css("cursor")),a("body").css("cursor",e.cursor)),e.opacity&&(this.helper.css("opacity")&&(this._storedOpacity=this.helper.css("opacity")),this.helper.css("opacity",e.opacity)),e.zIndex&&(this.helper.css("zIndex")&&(this._storedZIndex=this.helper.css("zIndex")),this.helper.css("zIndex",e.zIndex)),this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"&&(this.overflowOffset=this.scrollParent.offset()),this._trigger("start",b,this._uiHash()),this._preserveHelperProportions||this._cacheHelperProportions();if(!d)for(var g=this.containers.length-1;g>=0;g--)this.containers[g]._trigger("activate",b,f._uiHash(this));a.ui.ddmanager&&(a.ui.ddmanager.current=this),a.ui.ddmanager&&!e.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b),this.dragging=!0,this.helper.addClass("ui-sortable-helper"),this._mouseDrag(b);return!0},_mouseDrag:function(b){this.position=this._generatePosition(b),this.positionAbs=this._convertPositionTo("absolute"),this.lastPositionAbs||(this.lastPositionAbs=this.positionAbs);if(this.options.scroll){var c=this.options,d=!1;this.scrollParent[0]!=document&&this.scrollParent[0].tagName!="HTML"?(this.overflowOffset.top+this.scrollParent[0].offsetHeight-b.pageY<c.scrollSensitivity?this.scrollParent[0].scrollTop=d=this.scrollParent[0].scrollTop+c.scrollSpeed:b.pageY-this.overflowOffset.top<c.scrollSensitivity&&(this.scrollParent[0].scrollTop=d=this.scrollParent[0].scrollTop-c.scrollSpeed),this.overflowOffset.left+this.scrollParent[0].offsetWidth-b.pageX<c.scrollSensitivity?this.scrollParent[0].scrollLeft=d=this.scrollParent[0].scrollLeft+c.scrollSpeed:b.pageX-this.overflowOffset.left<c.scrollSensitivity&&(this.scrollParent[0].scrollLeft=d=this.scrollParent[0].scrollLeft-c.scrollSpeed)):(b.pageY-a(document).scrollTop()<c.scrollSensitivity?d=a(document).scrollTop(a(document).scrollTop()-c.scrollSpeed):a(window).height()-(b.pageY-a(document).scrollTop())<c.scrollSensitivity&&(d=a(document).scrollTop(a(document).scrollTop()+c.scrollSpeed)),b.pageX-a(document).scrollLeft()<c.scrollSensitivity?d=a(document).scrollLeft(a(document).scrollLeft()-c.scrollSpeed):a(window).width()-(b.pageX-a(document).scrollLeft())<c.scrollSensitivity&&(d=a(document).scrollLeft(a(document).scrollLeft()+c.scrollSpeed))),d!==!1&&a.ui.ddmanager&&!c.dropBehaviour&&a.ui.ddmanager.prepareOffsets(this,b)}this.positionAbs=this._convertPositionTo("absolute");if(!this.options.axis||this.options.axis!="y")this.helper[0].style.left=this.position.left+"px";if(!this.options.axis||this.options.axis!="x")this.helper[0].style.top=this.position.top+"px";for(var e=this.items.length-1;e>=0;e--){var f=this.items[e],g=f.item[0],h=this._intersectsWithPointer(f);if(!h)continue;if(g!=this.currentItem[0]&&this.placeholder[h==1?"next":"prev"]()[0]!=g&&!a.ui.contains(this.placeholder[0],g)&&(this.options.type=="semi-dynamic"?!a.ui.contains(this.element[0],g):!0)){this.direction=h==1?"down":"up";if(this.options.tolerance=="pointer"||this._intersectsWithSides(f))this._rearrange(b,f);else break;this._trigger("change",b,this._uiHash());break}}this._contactContainers(b),a.ui.ddmanager&&a.ui.ddmanager.drag(this,b),this._trigger("sort",b,this._uiHash()),this.lastPositionAbs=this.positionAbs;return!1},_mouseStop:function(b,c){if(!!b){a.ui.ddmanager&&!this.options.dropBehaviour&&a.ui.ddmanager.drop(this,b);if(this.options.revert){var d=this,e=d.placeholder.offset();d.reverting=!0,a(this.helper).animate({left:e.left-this.offset.parent.left-d.margins.left+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollLeft),top:e.top-this.offset.parent.top-d.margins.top+(this.offsetParent[0]==document.body?0:this.offsetParent[0].scrollTop)},parseInt(this.options.revert,10)||500,function(){d._clear(b)})}else this._clear(b,c);return!1}},cancel:function(){var b=this;if(this.dragging){this._mouseUp({target:null}),this.options.helper=="original"?this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"):this.currentItem.show();for(var c=this.containers.length-1;c>=0;c--)this.containers[c]._trigger("deactivate",null,b._uiHash(this)),this.containers[c].containerCache.over&&(this.containers[c]._trigger("out",null,b._uiHash(this)),this.containers[c].containerCache.over=0)}this.placeholder&&(this.placeholder[0].parentNode&&this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.options.helper!="original"&&this.helper&&this.helper[0].parentNode&&this.helper.remove(),a.extend(this,{helper:null,dragging:!1,reverting:!1,_noFinalSort:null}),this.domPosition.prev?a(this.domPosition.prev).after(this.currentItem):a(this.domPosition.parent).prepend(this.currentItem));return this},serialize:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];b=b||{},a(c).each(function(){var c=(a(b.item||this).attr(b.attribute||"id")||"").match(b.expression||/(.+)[-=_](.+)/);c&&d.push((b.key||c[1]+"[]")+"="+(b.key&&b.expression?c[1]:c[2]))}),!d.length&&b.key&&d.push(b.key+"=");return d.join("&")},toArray:function(b){var c=this._getItemsAsjQuery(b&&b.connected),d=[];b=b||{},c.each(function(){d.push(a(b.item||this).attr(b.attribute||"id")||"")});return d},_intersectsWith:function(a){var b=this.positionAbs.left,c=b+this.helperProportions.width,d=this.positionAbs.top,e=d+this.helperProportions.height,f=a.left,g=f+a.width,h=a.top,i=h+a.height,j=this.offset.click.top,k=this.offset.click.left,l=d+j>h&&d+j<i&&b+k>f&&b+k<g;return this.options.tolerance=="pointer"||this.options.forcePointerForContainers||this.options.tolerance!="pointer"&&this.helperProportions[this.floating?"width":"height"]>a[this.floating?"width":"height"]?l:f<b+this.helperProportions.width/2&&c-this.helperProportions.width/2<g&&h<d+this.helperProportions.height/2&&e-this.helperProportions.height/2<i},_intersectsWithPointer:function(b){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,b.top,b.height),d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,b.left,b.width),e=c&&d,f=this._getDragVerticalDirection(),g=this._getDragHorizontalDirection();if(!e)return!1;return this.floating?g&&g=="right"||f=="down"?2:1:f&&(f=="down"?2:1)},_intersectsWithSides:function(b){var c=a.ui.isOverAxis(this.positionAbs.top+this.offset.click.top,b.top+b.height/2,b.height),d=a.ui.isOverAxis(this.positionAbs.left+this.offset.click.left,b.left+b.width/2,b.width),e=this._getDragVerticalDirection(),f=this._getDragHorizontalDirection();return this.floating&&f?f=="right"&&d||f=="left"&&!d:e&&(e=="down"&&c||e=="up"&&!c)},_getDragVerticalDirection:function(){var a=this.positionAbs.top-this.lastPositionAbs.top;return a!=0&&(a>0?"down":"up")},_getDragHorizontalDirection:function(){var a=this.positionAbs.left-this +.lastPositionAbs.left;return a!=0&&(a>0?"right":"left")},refresh:function(a){this._refreshItems(a),this.refreshPositions();return this},_connectWith:function(){var a=this.options;return a.connectWith.constructor==String?[a.connectWith]:a.connectWith},_getItemsAsjQuery:function(b){var c=this,d=[],e=[],f=this._connectWith();if(f&&b)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&e.push([a.isFunction(j.options.items)?j.options.items.call(j.element):a(j.options.items,j.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),j])}}e.push([a.isFunction(this.options.items)?this.options.items.call(this.element,null,{options:this.options,item:this.currentItem}):a(this.options.items,this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"),this]);for(var g=e.length-1;g>=0;g--)e[g][0].each(function(){d.push(this)});return a(d)},_removeCurrentsFromItems:function(){var a=this.currentItem.find(":data("+this.widgetName+"-item)");for(var b=0;b<this.items.length;b++)for(var c=0;c<a.length;c++)a[c]==this.items[b].item[0]&&this.items.splice(b,1)},_refreshItems:function(b){this.items=[],this.containers=[this];var c=this.items,d=this,e=[[a.isFunction(this.options.items)?this.options.items.call(this.element[0],b,{item:this.currentItem}):a(this.options.items,this.element),this]],f=this._connectWith();if(f)for(var g=f.length-1;g>=0;g--){var h=a(f[g]);for(var i=h.length-1;i>=0;i--){var j=a.data(h[i],this.widgetName);j&&j!=this&&!j.options.disabled&&(e.push([a.isFunction(j.options.items)?j.options.items.call(j.element[0],b,{item:this.currentItem}):a(j.options.items,j.element),j]),this.containers.push(j))}}for(var g=e.length-1;g>=0;g--){var k=e[g][1],l=e[g][0];for(var i=0,m=l.length;i<m;i++){var n=a(l[i]);n.data(this.widgetName+"-item",k),c.push({item:n,instance:k,width:0,height:0,left:0,top:0})}}},refreshPositions:function(b){this.offsetParent&&this.helper&&(this.offset.parent=this._getParentOffset());for(var c=this.items.length-1;c>=0;c--){var d=this.items[c];if(d.instance!=this.currentContainer&&this.currentContainer&&d.item[0]!=this.currentItem[0])continue;var e=this.options.toleranceElement?a(this.options.toleranceElement,d.item):d.item;b||(d.width=e.outerWidth(),d.height=e.outerHeight());var f=e.offset();d.left=f.left,d.top=f.top}if(this.options.custom&&this.options.custom.refreshContainers)this.options.custom.refreshContainers.call(this);else for(var c=this.containers.length-1;c>=0;c--){var f=this.containers[c].element.offset();this.containers[c].containerCache.left=f.left,this.containers[c].containerCache.top=f.top,this.containers[c].containerCache.width=this.containers[c].element.outerWidth(),this.containers[c].containerCache.height=this.containers[c].element.outerHeight()}return this},_createPlaceholder:function(b){var c=b||this,d=c.options;if(!d.placeholder||d.placeholder.constructor==String){var e=d.placeholder;d.placeholder={element:function(){var b=a(document.createElement(c.currentItem[0].nodeName)).addClass(e||c.currentItem[0].className+" ui-sortable-placeholder").removeClass("ui-sortable-helper")[0];e||(b.style.visibility="hidden");return b},update:function(a,b){if(!e||!!d.forcePlaceholderSize)b.height()||b.height(c.currentItem.innerHeight()-parseInt(c.currentItem.css("paddingTop")||0,10)-parseInt(c.currentItem.css("paddingBottom")||0,10)),b.width()||b.width(c.currentItem.innerWidth()-parseInt(c.currentItem.css("paddingLeft")||0,10)-parseInt(c.currentItem.css("paddingRight")||0,10))}}}c.placeholder=a(d.placeholder.element.call(c.element,c.currentItem)),c.currentItem.after(c.placeholder),d.placeholder.update(c,c.placeholder)},_contactContainers:function(b){var c=null,d=null;for(var e=this.containers.length-1;e>=0;e--){if(a.ui.contains(this.currentItem[0],this.containers[e].element[0]))continue;if(this._intersectsWith(this.containers[e].containerCache)){if(c&&a.ui.contains(this.containers[e].element[0],c.element[0]))continue;c=this.containers[e],d=e}else this.containers[e].containerCache.over&&(this.containers[e]._trigger("out",b,this._uiHash(this)),this.containers[e].containerCache.over=0)}if(!!c)if(this.containers.length===1)this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1;else if(this.currentContainer!=this.containers[d]){var f=1e4,g=null,h=this.positionAbs[this.containers[d].floating?"left":"top"];for(var i=this.items.length-1;i>=0;i--){if(!a.ui.contains(this.containers[d].element[0],this.items[i].item[0]))continue;var j=this.items[i][this.containers[d].floating?"left":"top"];Math.abs(j-h)<f&&(f=Math.abs(j-h),g=this.items[i])}if(!g&&!this.options.dropOnEmpty)return;this.currentContainer=this.containers[d],g?this._rearrange(b,g,null,!0):this._rearrange(b,null,this.containers[d].element,!0),this._trigger("change",b,this._uiHash()),this.containers[d]._trigger("change",b,this._uiHash(this)),this.options.placeholder.update(this.currentContainer,this.placeholder),this.containers[d]._trigger("over",b,this._uiHash(this)),this.containers[d].containerCache.over=1}},_createHelper:function(b){var c=this.options,d=a.isFunction(c.helper)?a(c.helper.apply(this.element[0],[b,this.currentItem])):c.helper=="clone"?this.currentItem.clone():this.currentItem;d.parents("body").length||a(c.appendTo!="parent"?c.appendTo:this.currentItem[0].parentNode)[0].appendChild(d[0]),d[0]==this.currentItem[0]&&(this._storedCSS={width:this.currentItem[0].style.width,height:this.currentItem[0].style.height,position:this.currentItem.css("position"),top:this.currentItem.css("top"),left:this.currentItem.css("left")}),(d[0].style.width==""||c.forceHelperSize)&&d.width(this.currentItem.width()),(d[0].style.height==""||c.forceHelperSize)&&d.height(this.currentItem.height());return d},_adjustOffsetFromHelper:function(b){typeof b=="string"&&(b=b.split(" ")),a.isArray(b)&&(b={left:+b[0],top:+b[1]||0}),"left"in b&&(this.offset.click.left=b.left+this.margins.left),"right"in b&&(this.offset.click.left=this.helperProportions.width-b.right+this.margins.left),"top"in b&&(this.offset.click.top=b.top+this.margins.top),"bottom"in b&&(this.offset.click.top=this.helperProportions.height-b.bottom+this.margins.top)},_getParentOffset:function(){this.offsetParent=this.helper.offsetParent();var b=this.offsetParent.offset();this.cssPosition=="absolute"&&this.scrollParent[0]!=document&&a.ui.contains(this.scrollParent[0],this.offsetParent[0])&&(b.left+=this.scrollParent.scrollLeft(),b.top+=this.scrollParent.scrollTop());if(this.offsetParent[0]==document.body||this.offsetParent[0].tagName&&this.offsetParent[0].tagName.toLowerCase()=="html"&&a.browser.msie)b={top:0,left:0};return{top:b.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:b.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if(this.cssPosition=="relative"){var a=this.currentItem.position();return{top:a.top-(parseInt(this.helper.css("top"),10)||0)+this.scrollParent.scrollTop(),left:a.left-(parseInt(this.helper.css("left"),10)||0)+this.scrollParent.scrollLeft()}}return{top:0,left:0}},_cacheMargins:function(){this.margins={left:parseInt(this.currentItem.css("marginLeft"),10)||0,top:parseInt(this.currentItem.css("marginTop"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var b=this.options;b.containment=="parent"&&(b.containment=this.helper[0].parentNode);if(b.containment=="document"||b.containment=="window")this.containment=[0-this.offset.relative.left-this.offset.parent.left,0-this.offset.relative.top-this.offset.parent.top,a(b.containment=="document"?document:window).width()-this.helperProportions.width-this.margins.left,(a(b.containment=="document"?document:window).height()||document.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top];if(!/^(document|window|parent)$/.test(b.containment)){var c=a(b.containment)[0],d=a(b.containment).offset(),e=a(c).css("overflow")!="hidden";this.containment=[d.left+(parseInt(a(c).css("borderLeftWidth"),10)||0)+(parseInt(a(c).css("paddingLeft"),10)||0)-this.margins.left,d.top+(parseInt(a(c).css("borderTopWidth"),10)||0)+(parseInt(a(c).css("paddingTop"),10)||0)-this.margins.top,d.left+(e?Math.max(c.scrollWidth,c.offsetWidth):c.offsetWidth)-(parseInt(a(c).css("borderLeftWidth"),10)||0)-(parseInt(a(c).css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left,d.top+(e?Math.max(c.scrollHeight,c.offsetHeight):c.offsetHeight)-(parseInt(a(c).css("borderTopWidth"),10)||0)-(parseInt(a(c).css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top]}},_convertPositionTo:function(b,c){c||(c=this.position);var d=b=="absolute"?1:-1,e=this.options,f=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,g=/(html|body)/i.test(f[0].tagName);return{top:c.top+this.offset.relative.top*d+this.offset.parent.top*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollTop():g?0:f.scrollTop())*d),left:c.left+this.offset.relative.left*d+this.offset.parent.left*d-(a.browser.safari&&this.cssPosition=="fixed"?0:(this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():g?0:f.scrollLeft())*d)}},_generatePosition:function(b){var c=this.options,d=this.cssPosition=="absolute"&&(this.scrollParent[0]==document||!a.ui.contains(this.scrollParent[0],this.offsetParent[0]))?this.offsetParent:this.scrollParent,e=/(html|body)/i.test(d[0].tagName);this.cssPosition=="relative"&&(this.scrollParent[0]==document||this.scrollParent[0]==this.offsetParent[0])&&(this.offset.relative=this._getRelativeOffset());var f=b.pageX,g=b.pageY;if(this.originalPosition){this.containment&&(b.pageX-this.offset.click.left<this.containment[0]&&(f=this.containment[0]+this.offset.click.left),b.pageY-this.offset.click.top<this.containment[1]&&(g=this.containment[1]+this.offset.click.top),b.pageX-this.offset.click.left>this.containment[2]&&(f=this.containment[2]+this.offset.click.left),b.pageY-this.offset.click.top>this.containment[3]&&(g=this.containment[3]+this.offset.click.top));if(c.grid){var h=this.originalPageY+Math.round((g-this.originalPageY)/c.grid[1])*c.grid[1];g=this.containment?h-this.offset.click.top<this.containment[1]||h-this.offset.click.top>this.containment[3]?h-this.offset.click.top<this.containment[1]?h+c.grid[1]:h-c.grid[1]:h:h;var i=this.originalPageX+Math.round((f-this.originalPageX)/c.grid[0])*c.grid[0];f=this.containment?i-this.offset.click.left<this.containment[0]||i-this.offset.click.left>this.containment[2]?i-this.offset.click.left<this.containment[0]?i+c.grid[0]:i-c.grid[0]:i:i}}return{top:g-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollTop():e?0:d.scrollTop()),left:f-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+(a.browser.safari&&this.cssPosition=="fixed"?0:this.cssPosition=="fixed"?-this.scrollParent.scrollLeft():e?0:d.scrollLeft())}},_rearrange:function(a,b,c,d){c?c[0].appendChild(this.placeholder[0]):b.item[0].parentNode.insertBefore(this.placeholder[0],this.direction=="down"?b.item[0]:b.item[0].nextSibling),this.counter=this.counter?++this.counter:1;var e=this,f=this.counter;window.setTimeout(function(){f==e.counter&&e.refreshPositions(!d)},0)},_clear:function(b,c){this.reverting=!1;var d=[],e=this;!this._noFinalSort&&this.currentItem.parent().length&&this.placeholder.before(this.currentItem),this._noFinalSort=null;if(this.helper[0]==this.currentItem[0]){for(var f in this._storedCSS)if(this._storedCSS[f]=="auto"||this._storedCSS[f]=="static")this._storedCSS[f]="";this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper")}else this.currentItem.show();this.fromOutside&&!c&&d.push(function(a){this._trigger("receive",a,this._uiHash(this.fromOutside))}),(this.fromOutside||this.domPosition.prev!=this.currentItem.prev().not(".ui-sortable-helper")[0]||this.domPosition.parent!=this.currentItem.parent()[0])&&!c&&d.push(function(a){this._trigger("update",a,this._uiHash())});if(!a.ui.contains(this.element[0],this.currentItem[0])){c||d.push(function(a){this._trigger("remove",a,this._uiHash())});for(var f=this.containers.length-1;f>=0;f--)a.ui.contains(this.containers[f].element[0],this.currentItem[0])&&!c&&(d.push(function(a){return function(b){a._trigger("receive",b,this._uiHash(this))}}.call(this,this.containers[f])),d.push(function(a){return function(b){a._trigger("update",b,this._uiHash(this))}}.call(this,this.containers[f])))}for(var f=this.containers.length-1;f>=0;f--)c||d.push(function(a){return function(b){a._trigger("deactivate",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over&&(d.push(function(a){return function(b){a._trigger("out",b,this._uiHash(this))}}.call(this,this.containers[f])),this.containers[f].containerCache.over=0);this._storedCursor&&a("body").css("cursor",this._storedCursor),this._storedOpacity&&this.helper.css("opacity",this._storedOpacity),this._storedZIndex&&this.helper.css("zIndex",this._storedZIndex=="auto"?"":this._storedZIndex),this.dragging=!1;if(this.cancelHelperRemoval){if(!c){this._trigger("beforeStop",b,this._uiHash());for(var f=0;f<d.length;f++)d[f].call(this,b);this._trigger("stop",b,this._uiHash())}return!1}c||this._trigger("beforeStop",b,this._uiHash()),this.placeholder[0].parentNode.removeChild(this.placeholder[0]),this.helper[0]!=this.currentItem[0]&&this.helper.remove(),this.helper=null;if(!c){for(var f=0;f<d.length;f++)d[f].call(this,b);this._trigger("stop",b,this._uiHash())}this.fromOutside=!1;return!0},_trigger:function(){a.Widget.prototype._trigger.apply(this,arguments)===!1&&this.cancel()},_uiHash:function(b){var c=b||this;return{helper:c.helper,placeholder:c.placeholder||a([]),position:c.position,originalPosition:c.originalPosition,offset:c.positionAbs,item:c.currentItem,sender:b?b.element:null}}}),a.extend(a.ui.sortable,{version:"1.8.17"})}(jQuery),jQuery.effects||function(a,b){function l(b){if(!b||typeof b=="number"||a.fx.speeds[b])return!0;if(typeof b=="string"&&!a.effects[b])return!0;return!1}function k(b,c,d,e){typeof b=="object"&&(e=c,d=null,c=b,b=c.effect),a.isFunction(c)&&(e=c,d=null,c={});if(typeof c=="number"||a.fx.speeds[c])e=d,d=c,c={};a.isFunction(d)&&(e=d,d=null),c=c||{},d=d||c.duration,d=a.fx.off?0:typeof d=="number"?d:d in a.fx.speeds?a.fx.speeds[d]:a.fx.speeds._default,e=e||c.complete;return[b,c,d,e]}function j(a,b){var c={_:0},d;for(d in b)a[d]!=b[d]&&(c[d]=b[d]);return c}function i(b){var c,d;for(c in b)d=b[c],(d==null||a.isFunction(d)||c in g||/scrollbar/.test(c)||!/color/i.test(c)&&isNaN(parseFloat(d)))&&delete b[c];return b}function h(){var a=document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle,b={},c,d;if(a&&a.length&&a[0]&&a[a[0]]){var e=a.length;while(e--)c=a[e],typeof a[c]=="string"&&(d=c.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()}),b[d]=a[c])}else for(c in a)typeof a[c]=="string"&&(b[c]=a[c]);return b}function d(b,d){var e;do{e=a.curCSS(b,d);if(e!=""&&e!="transparent"||a.nodeName(b,"body"))break;d="backgroundColor"}while(b=b.parentNode);return c(e)}function c(b){var c;if(b&&b.constructor==Array&&b.length==3)return b;if(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))return[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)];if(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))return[parseFloat(c[1])*2.55,parseFloat(c[2])*2.55,parseFloat(c[3])*2.55];if(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))return[parseInt(c[1],16),parseInt(c[2],16),parseInt(c[3],16)];if(c=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))return[parseInt(c[1]+c[1],16),parseInt(c[2]+c[2],16),parseInt(c[3]+c[3],16)];if(c=/rgba\(0, 0, 0, 0\)/.exec(b))return e.transparent;return e[a.trim(b).toLowerCase()]}a.effects={},a.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","borderColor","color","outlineColor"],function(b,e){a.fx.step[e]=function(a){a.colorInit||(a.start=d(a.elem,e),a.end=c(a.end),a.colorInit=!0),a.elem.style[e]="rgb("+Math.max(Math.min(parseInt(a.pos*(a.end[0]-a.start[0])+a.start[0],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[1]-a.start[1])+a.start[1],10),255),0)+","+Math.max(Math.min(parseInt(a.pos*(a.end[2]-a.start[2])+a.start[2],10),255),0)+")"}});var e={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]},f=["add","remove","toggle"],g={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};a.effects.animateClass=function(b,c,d,e){a.isFunction(d)&&(e=d,d=null);return this.queue(function(){var g=a(this),k=g.attr("style")||" ",l=i(h.call(this)),m,n=g.attr("class");a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),m=i(h.call(this)),g.attr("class",n),g.animate(j(l,m),{queue:!1,duration:c,easing:d,complete:function(){a.each(f,function(a,c){b[c]&&g[c+"Class"](b[c])}),typeof g.attr("style")=="object"?(g.attr("style").cssText="",g.attr("style").cssText=k):g.attr("style",k),e&&e.apply(this,arguments),a.dequeue(this)}})})},a.fn.extend({_addClass:a.fn.addClass,addClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{add:b},c,d,e]):this._addClass(b)},_removeClass:a.fn.removeClass,removeClass:function(b,c,d,e){return c?a.effects.animateClass.apply(this,[{remove:b},c,d,e]):this._removeClass(b)},_toggleClass:a.fn.toggleClass,toggleClass:function(c,d,e,f,g){return typeof d=="boolean"||d===b?e?a.effects.animateClass.apply(this,[d?{add:c}:{remove:c},e,f,g]):this._toggleClass(c,d):a.effects.animateClass.apply(this,[{toggle:c},d,e,f])},switchClass:function(b,c,d,e,f){return a.effects.animateClass.apply(this,[{add:c,remove:b},d,e,f])}}),a.extend(a.effects,{version:"1.8.17",save:function(a,b){for(var c=0;c<b.length;c++)b[c]!==null&&a.data("ec.storage."+b[c],a[0].style[b[c]])},restore:function(a,b){for(var c=0;c<b.length;c++)b[c]!==null&&a.css(b[c],a.data("ec.storage."+b[c]))},setMode:function(a,b){b=="toggle"&&(b=a.is(":hidden")?"show":"hide");return b},getBaseline:function(a,b){var c,d;switch(a[0]){case"top":c=0;break;case"middle":c=.5;break;case"bottom":c=1;break;default:c=a[0]/b.height}switch(a[1]){case"left":d=0;break;case"center":d=.5;break;case"right":d=1;break;default:d=a[1]/b.width}return{x:d,y:c}},createWrapper:function(b){if(b.parent().is(".ui-effects-wrapper"))return b.parent();var c={width:b.outerWidth(!0),height:b.outerHeight(!0),"float":b.css("float")},d=a("<div></div>").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),e=document.activeElement;b.wrap(d),(b[0]===e||a.contains(b[0],e))&&a(e).focus(),d=b.parent(),b.css("position")=="static"?(d.css({position:"relative"}),b.css({position:"relative"})):(a.extend(c,{position:b.css("position"),zIndex:b.css("z-index")}),a.each(["top","left","bottom","right"],function(a,d){c[d]=b.css(d),isNaN(parseInt(c[d],10))&&(c[d]="auto")}),b.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"}));return d.css(c).show()},removeWrapper:function(b){var c,d=document.activeElement;if(b.parent().is(".ui-effects-wrapper")){c=b.parent().replaceWith(b),(b[0]===d||a.contains(b[0],d))&&a(d).focus();return c}return b},setTransition:function(b,c,d,e){e=e||{},a.each(c,function(a,c){unit=b.cssUnit(c),unit[0]>0&&(e[c]=unit[0]*d+unit[1])});return e}}),a.fn.extend({effect:function(b,c,d,e){var f=k.apply(this,arguments),g={options:f[1],duration:f[2],callback:f[3]},h=g.options.mode,i=a.effects[b];if(a.fx.off||!i)return h?this[h](g.duration,g.callback):this.each(function(){g.callback&&g.callback.call(this)});return i.call(this,g)},_show:a.fn.show,show:function(a){if(l(a))return this._show.apply(this,arguments);var b=k.apply(this,arguments);b[1].mode="show";return this.effect.apply(this,b)},_hide:a.fn.hide,hide:function(a){if(l(a))return this._hide.apply(this,arguments);var b=k.apply(this,arguments);b[1].mode="hide";return this.effect.apply(this,b)},__toggle:a.fn.toggle,toggle:function(b){if(l(b)||typeof b=="boolean"||a.isFunction(b))return this.__toggle.apply(this,arguments);var c=k.apply(this,arguments);c[1].mode="toggle";return this.effect.apply(this,c)},cssUnit:function(b){var c=this.css(b),d=[];a.each(["em","px","%","pt"],function(a,b){c.indexOf(b)>0&&(d=[parseFloat(c),b])});return d}}),a.easing.jswing=a.easing.swing,a.extend(a.easing,{def:"easeOutQuad",swing:function(b,c,d,e,f){return a.easing[a.easing.def](b,c,d,e,f)},easeInQuad:function(a,b,c,d,e){return d*(b/=e)*b+c},easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c},easeInOutQuad:function(a,b,c,d,e){if((b/=e/2)<1)return d/2*b*b+c;return-d/2*(--b*(b-2)-1)+c},easeInCubic:function(a,b,c,d,e){return d*(b/=e)*b*b+c},easeOutCubic:function(a,b,c,d,e){return d*((b=b/e-1)*b*b+1)+c},easeInOutCubic:function(a,b,c,d,e){if((b/=e/2)<1)return d/2*b*b*b+c;return d/2*((b-=2)*b*b+2)+c},easeInQuart:function(a,b,c,d,e){return d*(b/=e)*b*b*b+c},easeOutQuart:function(a,b,c,d,e){return-d*((b=b/e-1)*b*b*b-1)+c},easeInOutQuart:function(a,b,c,d,e){if((b/=e/2)<1)return d/2*b*b*b*b+c;return-d/2*((b-=2)*b*b*b-2)+c},easeInQuint:function(a,b,c,d,e){return d*(b/=e)*b*b*b*b+c},easeOutQuint:function(a,b,c,d,e){return d*((b=b/e-1)*b*b*b*b+1)+c},easeInOutQuint:function(a,b,c,d,e){if((b/=e/2)<1)return d/2*b*b*b*b*b+c;return d/2*((b-=2)*b*b*b*b+2)+c},easeInSine:function(a,b,c,d,e){return-d*Math.cos(b/e*(Math.PI/2))+d+c},easeOutSine:function(a,b,c,d,e){return d*Math.sin(b/e*(Math.PI/2))+c},easeInOutSine:function(a,b,c,d,e){return-d/2*(Math.cos(Math.PI*b/e)-1)+c},easeInExpo:function(a,b,c,d,e){return b==0?c:d*Math.pow(2,10*(b/e-1))+c},easeOutExpo:function(a,b,c,d,e){return b==e?c+d:d*(-Math.pow(2,-10*b/e)+1)+c},easeInOutExpo:function(a,b,c,d,e){if(b==0)return c;if(b==e)return c+d;if((b/=e/2)<1)return d/2*Math.pow(2,10*(b-1))+c;return d/2*(-Math.pow(2,-10*--b)+2)+c},easeInCirc:function(a,b,c,d,e){return-d*(Math.sqrt(1-(b/=e)*b)-1)+c},easeOutCirc:function(a,b,c,d,e){return d*Math.sqrt(1-(b=b/e-1)*b)+c},easeInOutCirc:function(a,b,c,d,e){if((b/=e/2)<1)return-d/2*(Math.sqrt(1-b*b)-1)+c;return d/2*(Math.sqrt(1-(b-=2)*b)+1)+c},easeInElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e)==1)return c+d;g||(g=e*.3);if(h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);return-(h*Math.pow(2,10*(b-=1))*Math.sin((b*e-f)*2*Math.PI/g))+c},easeOutElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e)==1)return c+d;g||(g=e*.3);if(h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);return h*Math.pow(2,-10*b)*Math.sin((b*e-f)*2*Math.PI/g)+d+c},easeInOutElastic:function(a,b,c,d,e){var f=1.70158,g=0,h=d;if(b==0)return c;if((b/=e/2)==2)return c+d;g||(g=e*.3*1.5);if(h<Math.abs(d)){h=d;var f=g/4}else var f=g/(2*Math.PI)*Math.asin(d/h);if(b<1)return-0.5*h*Math.pow(2,10*(b-=1))*Math.sin((b*e-f)*2*Math.PI/g)+c;return h*Math.pow(2,-10*(b-=1))*Math.sin((b*e-f)*2*Math.PI/g)*.5+d+c},easeInBack:function(a,c,d,e,f,g){g==b&&(g=1.70158);return e*(c/=f)*c*((g+1)*c-g)+d},easeOutBack:function(a,c,d,e,f,g){g==b&&(g=1.70158);return e*((c=c/f-1)*c*((g+1)*c+g)+1)+d},easeInOutBack:function(a,c,d,e,f,g){g==b&&(g=1.70158);if((c/=f/2)<1)return e/2*c*c*(((g*=1.525)+1)*c-g)+d;return e/2*((c-=2)*c*(((g*=1.525)+1)*c+g)+2)+d},easeInBounce:function(b,c,d,e,f){return e-a.easing.easeOutBounce(b,f-c,0,e,f)+d},easeOutBounce:function(a,b,c,d,e){return(b/=e)<1/2.75?d*7.5625*b*b+c:b<2/2.75?d*(7.5625*(b-=1.5/2.75)*b+.75)+c:b<2.5/2.75?d*(7.5625*(b-=2.25/2.75)*b+.9375)+c:d*(7.5625*(b-=2.625/2.75)*b+.984375)+c},easeInOutBounce:function(b,c,d,e,f){if(c<f/2)return a.easing.easeInBounce(b,c*2,0,e,f)*.5+d;return a.easing.easeOutBounce(b,c*2-f,0,e,f)*.5+e*.5+d}})}(jQuery),function(a,b){a.effects.blind=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"vertical";a.effects.save(c,d),c.show();var g=a.effects.createWrapper(c).css({overflow:"hidden"}),h=f=="vertical"?"height":"width",i=f=="vertical"?g.height():g.width();e=="show"&&g.css(h,0);var j={};j[h]=e=="show"?i:0,g.animate(j,b.duration,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}}(jQuery),function(a,b){a.effects.bounce=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"effect"),f=b.options.direction||"up",g=b.options.distance||20,h=b.options.times||5,i=b.duration||250;/show|hide/.test(e)&&d.push("opacity"),a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var j=f=="up"||f=="down"?"top":"left",k=f=="up"||f=="left"?"pos":"neg",g=b.options.distance||(j=="top"?c.outerHeight({margin:!0})/3:c.outerWidth({margin:!0})/3);e=="show"&&c.css("opacity",0).css(j,k=="pos"?-g:g),e=="hide"&&(g=g/(h*2)),e!="hide"&&h--;if(e=="show"){var l={opacity:1};l[j]=(k=="pos"?"+=":"-=")+g,c.animate(l,i/2,b.options.easing),g=g/2,h--}for(var m=0;m<h;m++){var n={},p={};n[j]=(k=="pos"?"-=":"+=")+g,p[j]=(k=="pos"?"+=":"-=")+g,c.animate(n,i/2,b.options.easing).animate(p,i/2,b.options.easing),g=e=="hide"?g*2:g/2}if(e=="hide"){var l={opacity:0};l[j]=(k=="pos"?"-=":"+=")+g,c.animate(l,i/2,b.options.easing,function(){c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)})}else{var n={},p={};n[j]=(k=="pos"?"-=":"+=")+g,p[j]=(k=="pos"?"+=":"-=")+g,c.animate(n,i/2,b.options.easing).animate(p,i/2,b.options.easing,function(){a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)})}c.queue("fx",function(){c.dequeue()}),c.dequeue()})}}(jQuery),function(a,b){a.effects.clip=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","height","width"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"vertical";a.effects.save(c,d),c.show();var g=a.effects.createWrapper(c).css({overflow:"hidden"}),h=c[0].tagName=="IMG"?g:c,i={size:f=="vertical"?"height":"width",position:f=="vertical"?"top":"left"},j=f=="vertical"?h.height():h.width();e=="show"&&(h.css(i.size,0),h.css(i.position,j/2));var k={};k[i.size]=e=="show"?j:0,k[i.position]=e=="show"?0:j/2,h.animate(k,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.drop=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","opacity"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.direction||"left";a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var g=f=="up"||f=="down"?"top":"left",h=f=="up"||f=="left"?"pos":"neg",i=b.options.distance||(g=="top"?c.outerHeight({margin:!0})/2:c.outerWidth({margin:!0})/2);e=="show"&&c.css("opacity",0).css(g,h=="pos"?-i:i);var j={opacity:e=="show"?1:0};j[g]=(e=="show"?h=="pos"?"+=":"-=":h=="pos"?"-=":"+=")+i,c.animate(j,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.explode=function(b){return this.queue(function(){var c=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3,d=b.options.pieces?Math.round(Math.sqrt(b.options.pieces)):3;b.options.mode=b.options.mode=="toggle"?a(this).is(":visible")?"hide":"show":b.options.mode;var e=a(this).show().css("visibility","hidden"),f=e.offset();f.top-=parseInt(e.css("marginTop"),10)||0,f.left-=parseInt(e.css("marginLeft"),10)||0;var g=e.outerWidth(!0),h=e.outerHeight(!0);for(var i=0;i<c;i++)for(var j=0;j<d;j++)e.clone().appendTo("body").wrap("<div></div>").css({position:"absolute",visibility:"visible",left:-j*(g/d),top:-i*(h/c)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/d,height:h/c,left:f.left+j*(g/d)+(b.options.mode=="show"?(j-Math.floor(d/2))*(g/d):0),top:f.top+i*(h/c)+(b.options.mode=="show"?(i-Math.floor(c/2))*(h/c):0),opacity:b.options.mode=="show"?0:1}).animate({left:f.left+j*(g/d)+(b.options.mode=="show"?0:(j-Math.floor(d/2))*(g/d)),top:f.top+i*(h/c)+(b.options.mode=="show"?0:(i-Math.floor(c/2))*(h/c)),opacity:b.options.mode=="show"?1:0},b.duration||500);setTimeout(function(){b.options.mode=="show"?e.css({visibility:"visible"}):e.css({visibility:"visible"}).hide(),b.callback&&b.callback.apply(e[0]),e.dequeue(),a("div.ui-effects-explode").remove()},b.duration||500)})}}(jQuery),function(a,b){a.effects.fade=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide");c.animate({opacity:d},{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.fold=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"hide"),f=b.options.size||15,g=!!b.options.horizFirst,h=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(c,d),c.show();var i=a.effects.createWrapper(c).css({overflow:"hidden"}),j=e=="show"!=g,k=j?["width","height"]:["height","width"],l=j?[i.width(),i.height()]:[i.height(),i.width()],m=/([0-9]+)%/.exec(f);m&&(f=parseInt(m[1],10)/100*l[e=="hide"?0:1]),e=="show"&&i.css(g?{height:0,width:f}:{height:f,width:0});var n={},p={};n[k[0]]=e=="show"?l[0]:f,p[k[1]]=e=="show"?l[1]:0,i.animate(n,h,b.options.easing).animate(p,h,b.options.easing,function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}}(jQuery),function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e=="show"&&!a.support.opacity&&this.style.removeAttribute("filter"),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.pulsate=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"show");times=(b.options.times||5)*2-1,duration=b.duration?b.duration/2:a.fx.speeds._default/2,isVisible=c.is(":visible"),animateTo=0,isVisible||(c.css("opacity",0).show(),animateTo=1),(d=="hide"&&isVisible||d=="show"&&!isVisible)&×--;for(var e=0;e<times;e++)c.animate({opacity:animateTo},duration,b.options.easing),animateTo=(animateTo+1)%2;c.animate({opacity:animateTo},duration,b.options.easing,function(){animateTo==0&&c.hide(),b.callback&&b.callback.apply(this,arguments)}),c.queue("fx",function(){c.dequeue()}).dequeue()})}}(jQuery),function(a,b){a.effects.puff=function(b){return this.queue(function(){var c=a(this),d=a.effects.setMode(c,b.options.mode||"hide"),e=parseInt(b.options.percent,10)||150,f=e/100,g={height:c.height(),width:c.width()};a.extend(b.options,{fade:!0,mode:d,percent:d=="hide"?e:100,from:d=="hide"?g:{height:g.height*f,width:g.width*f}}),c.effect("scale",b.options,b.duration,b.callback),c.dequeue()})},a.effects.scale=function(b){return this.queue(function(){var c=a(this),d=a.extend(!0,{},b.options +),e=a.effects.setMode(c,b.options.mode||"effect"),f=parseInt(b.options.percent,10)||(parseInt(b.options.percent,10)==0?0:e=="hide"?0:100),g=b.options.direction||"both",h=b.options.origin;e!="effect"&&(d.origin=h||["middle","center"],d.restore=!0);var i={height:c.height(),width:c.width()};c.from=b.options.from||(e=="show"?{height:0,width:0}:i);var j={y:g!="horizontal"?f/100:1,x:g!="vertical"?f/100:1};c.to={height:i.height*j.y,width:i.width*j.x},b.options.fade&&(e=="show"&&(c.from.opacity=0,c.to.opacity=1),e=="hide"&&(c.from.opacity=1,c.to.opacity=0)),d.from=c.from,d.to=c.to,d.mode=e,c.effect("size",d,b.duration,b.callback),c.dequeue()})},a.effects.size=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right","width","height","overflow","opacity"],e=["position","top","bottom","left","right","overflow","opacity"],f=["width","height","overflow"],g=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],i=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],j=a.effects.setMode(c,b.options.mode||"effect"),k=b.options.restore||!1,l=b.options.scale||"both",m=b.options.origin,n={height:c.height(),width:c.width()};c.from=b.options.from||n,c.to=b.options.to||n;if(m){var p=a.effects.getBaseline(m,n);c.from.top=(n.height-c.from.height)*p.y,c.from.left=(n.width-c.from.width)*p.x,c.to.top=(n.height-c.to.height)*p.y,c.to.left=(n.width-c.to.width)*p.x}var q={from:{y:c.from.height/n.height,x:c.from.width/n.width},to:{y:c.to.height/n.height,x:c.to.width/n.width}};if(l=="box"||l=="both")q.from.y!=q.to.y&&(d=d.concat(h),c.from=a.effects.setTransition(c,h,q.from.y,c.from),c.to=a.effects.setTransition(c,h,q.to.y,c.to)),q.from.x!=q.to.x&&(d=d.concat(i),c.from=a.effects.setTransition(c,i,q.from.x,c.from),c.to=a.effects.setTransition(c,i,q.to.x,c.to));(l=="content"||l=="both")&&q.from.y!=q.to.y&&(d=d.concat(g),c.from=a.effects.setTransition(c,g,q.from.y,c.from),c.to=a.effects.setTransition(c,g,q.to.y,c.to)),a.effects.save(c,k?d:e),c.show(),a.effects.createWrapper(c),c.css("overflow","hidden").css(c.from);if(l=="content"||l=="both")h=h.concat(["marginTop","marginBottom"]).concat(g),i=i.concat(["marginLeft","marginRight"]),f=d.concat(h).concat(i),c.find("*[width]").each(function(){child=a(this),k&&a.effects.save(child,f);var c={height:child.height(),width:child.width()};child.from={height:c.height*q.from.y,width:c.width*q.from.x},child.to={height:c.height*q.to.y,width:c.width*q.to.x},q.from.y!=q.to.y&&(child.from=a.effects.setTransition(child,h,q.from.y,child.from),child.to=a.effects.setTransition(child,h,q.to.y,child.to)),q.from.x!=q.to.x&&(child.from=a.effects.setTransition(child,i,q.from.x,child.from),child.to=a.effects.setTransition(child,i,q.to.x,child.to)),child.css(child.from),child.animate(child.to,b.duration,b.options.easing,function(){k&&a.effects.restore(child,f)})});c.animate(c.to,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){c.to.opacity===0&&c.css("opacity",c.from.opacity),j=="hide"&&c.hide(),a.effects.restore(c,k?d:e),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.shake=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"effect"),f=b.options.direction||"left",g=b.options.distance||20,h=b.options.times||3,i=b.duration||b.options.duration||140;a.effects.save(c,d),c.show(),a.effects.createWrapper(c);var j=f=="up"||f=="down"?"top":"left",k=f=="up"||f=="left"?"pos":"neg",l={},m={},n={};l[j]=(k=="pos"?"-=":"+=")+g,m[j]=(k=="pos"?"+=":"-=")+g*2,n[j]=(k=="pos"?"-=":"+=")+g*2,c.animate(l,i,b.options.easing);for(var p=1;p<h;p++)c.animate(m,i,b.options.easing).animate(n,i,b.options.easing);c.animate(m,i,b.options.easing).animate(l,i/2,b.options.easing,function(){a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments)}),c.queue("fx",function(){c.dequeue()}),c.dequeue()})}}(jQuery),function(a,b){a.effects.slide=function(b){return this.queue(function(){var c=a(this),d=["position","top","bottom","left","right"],e=a.effects.setMode(c,b.options.mode||"show"),f=b.options.direction||"left";a.effects.save(c,d),c.show(),a.effects.createWrapper(c).css({overflow:"hidden"});var g=f=="up"||f=="down"?"top":"left",h=f=="up"||f=="left"?"pos":"neg",i=b.options.distance||(g=="top"?c.outerHeight({margin:!0}):c.outerWidth({margin:!0}));e=="show"&&c.css(g,h=="pos"?isNaN(i)?"-"+i:-i:i);var j={};j[g]=(e=="show"?h=="pos"?"+=":"-=":h=="pos"?"-=":"+=")+i,c.animate(j,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),a.effects.removeWrapper(c),b.callback&&b.callback.apply(this,arguments),c.dequeue()}})})}}(jQuery),function(a,b){a.effects.transfer=function(b){return this.queue(function(){var c=a(this),d=a(b.options.to),e=d.offset(),f={top:e.top,left:e.left,height:d.innerHeight(),width:d.innerWidth()},g=c.offset(),h=a('<div class="ui-effects-transfer"></div>').appendTo(document.body).addClass(b.options.className).css({top:g.top,left:g.left,height:c.innerHeight(),width:c.innerWidth(),position:"absolute"}).animate(f,b.duration,b.options.easing,function(){h.remove(),b.callback&&b.callback.apply(c[0],arguments),c.dequeue()})})}}(jQuery),function(a,b){a.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:!0,clearStyle:!1,collapsible:!1,event:"click",fillSpace:!1,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix"),b.headers=b.element.find(c.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){c.disabled||a(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){c.disabled||a(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){c.disabled||a(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){c.disabled||a(this).removeClass("ui-state-focus")}),b.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");if(c.navigation){var d=b.element.find("a").filter(c.navigationFilter).eq(0);if(d.length){var e=d.closest(".ui-accordion-header");e.length?b.active=e:b.active=d.closest(".ui-accordion-content").prev()}}b.active=b._findActive(b.active||c.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all").toggleClass("ui-corner-top"),b.active.next().addClass("ui-accordion-content-active"),b._createIcons(),b.resize(),b.element.attr("role","tablist"),b.headers.attr("role","tab").bind("keydown.accordion",function(a){return b._keydown(a)}).next().attr("role","tabpanel"),b.headers.not(b.active||"").attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).next().hide(),b.active.length?b.active.attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}):b.headers.eq(0).attr("tabIndex",0),a.browser.safari||b.headers.find("a").attr("tabIndex",-1),c.event&&b.headers.bind(c.event.split(" ").join(".accordion ")+".accordion",function(a){b._clickHandler.call(b,a,this),a.preventDefault()})},_createIcons:function(){var b=this.options;b.icons&&(a("<span></span>").addClass("ui-icon "+b.icons.header).prependTo(this.headers),this.active.children(".ui-icon").toggleClass(b.icons.header).toggleClass(b.icons.headerSelected),this.element.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.children(".ui-icon").remove(),this.element.removeClass("ui-accordion-icons")},destroy:function(){var b=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("tabIndex"),this.headers.find("a").removeAttr("tabIndex"),this._destroyIcons();var c=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");(b.autoHeight||b.fillHeight)&&c.css("height","");return a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b=="active"&&this.activate(c),b=="icons"&&(this._destroyIcons(),c&&this._createIcons()),b=="disabled"&&this.headers.add(this.headers.next())[c?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(b){if(!(this.options.disabled||b.altKey||b.ctrlKey)){var c=a.ui.keyCode,d=this.headers.length,e=this.headers.index(b.target),f=!1;switch(b.keyCode){case c.RIGHT:case c.DOWN:f=this.headers[(e+1)%d];break;case c.LEFT:case c.UP:f=this.headers[(e-1+d)%d];break;case c.SPACE:case c.ENTER:this._clickHandler({target:b.target},b.target),b.preventDefault()}if(f){a(b.target).attr("tabIndex",-1),a(f).attr("tabIndex",0),f.focus();return!1}return!0}},resize:function(){var b=this.options,c;if(b.fillSpace){if(a.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}c=this.element.parent().height(),a.browser.msie&&this.element.parent().css("overflow",d),this.headers.each(function(){c-=a(this).outerHeight(!0)}),this.headers.next().each(function(){a(this).height(Math.max(0,c-a(this).innerHeight()+a(this).height()))}).css("overflow","auto")}else b.autoHeight&&(c=0,this.headers.next().each(function(){c=Math.max(c,a(this).height("").height())}).height(c));return this},activate:function(a){this.options.active=a;var b=this._findActive(a)[0];this._clickHandler({target:b},b);return this},_findActive:function(b){return b?typeof b=="number"?this.headers.filter(":eq("+b+")"):this.headers.not(this.headers.not(b)):b===!1?a([]):this.headers.filter(":eq(0)")},_clickHandler:function(b,c){var d=this.options;if(!d.disabled){if(!b.target){if(!d.collapsible)return;this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),this.active.next().addClass("ui-accordion-content-active");var e=this.active.next(),f={options:d,newHeader:a([]),oldHeader:d.active,newContent:a([]),oldContent:e},g=this.active=a([]);this._toggle(g,e,f);return}var h=a(b.currentTarget||c),i=h[0]===this.active[0];d.active=d.collapsible&&i?!1:this.headers.index(h);if(this.running||!d.collapsible&&i)return;var j=this.active,g=h.next(),e=this.active.next(),f={options:d,newHeader:i&&d.collapsible?a([]):h,oldHeader:this.active,newContent:i&&d.collapsible?a([]):g,oldContent:e},k=this.headers.index(this.active[0])>this.headers.index(h[0]);this.active=i?a([]):h,this._toggle(g,e,f,i,k),j.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header),i||(h.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected),h.next().addClass("ui-accordion-content-active"));return}},_toggle:function(b,c,d,e,f){var g=this,h=g.options;g.toShow=b,g.toHide=c,g.data=d;var i=function(){if(!!g)return g._completed.apply(g,arguments)};g._trigger("changestart",null,g.data),g.running=c.size()===0?b.size():c.size();if(h.animated){var j={};h.collapsible&&e?j={toShow:a([]),toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace}:j={toShow:b,toHide:c,complete:i,down:f,autoHeight:h.autoHeight||h.fillSpace},h.proxied||(h.proxied=h.animated),h.proxiedDuration||(h.proxiedDuration=h.duration),h.animated=a.isFunction(h.proxied)?h.proxied(j):h.proxied,h.duration=a.isFunction(h.proxiedDuration)?h.proxiedDuration(j):h.proxiedDuration;var k=a.ui.accordion.animations,l=h.duration,m=h.animated;m&&!k[m]&&!a.easing[m]&&(m="slide"),k[m]||(k[m]=function(a){this.slide(a,{easing:m,duration:l||700})}),k[m](j)}else h.collapsible&&e?b.toggle():(c.hide(),b.show()),i(!0);c.prev().attr({"aria-expanded":"false","aria-selected":"false",tabIndex:-1}).blur(),b.prev().attr({"aria-expanded":"true","aria-selected":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;this.running||(this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""}),this.toHide.removeClass("ui-accordion-content-active"),this.toHide.length&&(this.toHide.parent()[0].className=this.toHide.parent()[0].className),this._trigger("change",null,this.data))}}),a.extend(a.ui.accordion,{version:"1.8.17",animations:{slide:function(b,c){b=a.extend({easing:"swing",duration:300},b,c);if(!b.toHide.size())b.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},b);else{if(!b.toShow.size()){b.toHide.animate({height:"hide",paddingTop:"hide",paddingBottom:"hide"},b);return}var d=b.toShow.css("overflow"),e=0,f={},g={},h=["height","paddingTop","paddingBottom"],i,j=b.toShow;i=j[0].style.width,j.width(j.parent().width()-parseFloat(j.css("paddingLeft"))-parseFloat(j.css("paddingRight"))-(parseFloat(j.css("borderLeftWidth"))||0)-(parseFloat(j.css("borderRightWidth"))||0)),a.each(h,function(c,d){g[d]="hide";var e=(""+a.css(b.toShow[0],d)).match(/^([\d+-.]+)(.*)$/);f[d]={value:e[1],unit:e[2]||"px"}}),b.toShow.css({height:0,overflow:"hidden"}).show(),b.toHide.filter(":hidden").each(b.complete).end().filter(":visible").animate(g,{step:function(a,c){c.prop=="height"&&(e=c.end-c.start===0?0:(c.now-c.start)/(c.end-c.start)),b.toShow[0].style[c.prop]=e*f[c.prop].value+f[c.prop].unit},duration:b.duration,easing:b.easing,complete:function(){b.autoHeight||b.toShow.css("height",""),b.toShow.css({width:i,overflow:d}),b.complete()}})}},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1e3:200})}}})}(jQuery),function(a,b){var c=0;a.widget("ui.autocomplete",{options:{appendTo:"body",autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null},pending:0,_create:function(){var b=this,c=this.element[0].ownerDocument,d;this.element.addClass("ui-autocomplete-input").attr("autocomplete","off").attr({role:"textbox","aria-autocomplete":"list","aria-haspopup":"true"}).bind("keydown.autocomplete",function(c){if(!b.options.disabled&&!b.element.propAttr("readOnly")){d=!1;var e=a.ui.keyCode;switch(c.keyCode){case e.PAGE_UP:b._move("previousPage",c);break;case e.PAGE_DOWN:b._move("nextPage",c);break;case e.UP:b._move("previous",c),c.preventDefault();break;case e.DOWN:b._move("next",c),c.preventDefault();break;case e.ENTER:case e.NUMPAD_ENTER:b.menu.active&&(d=!0,c.preventDefault());case e.TAB:if(!b.menu.active)return;b.menu.select(c);break;case e.ESCAPE:b.element.val(b.term),b.close(c);break;default:clearTimeout(b.searching),b.searching=setTimeout(function(){b.term!=b.element.val()&&(b.selectedItem=null,b.search(null,c))},b.options.delay)}}}).bind("keypress.autocomplete",function(a){d&&(d=!1,a.preventDefault())}).bind("focus.autocomplete",function(){b.options.disabled||(b.selectedItem=null,b.previous=b.element.val())}).bind("blur.autocomplete",function(a){b.options.disabled||(clearTimeout(b.searching),b.closing=setTimeout(function(){b.close(a),b._change(a)},150))}),this._initSource(),this.response=function(){return b._response.apply(b,arguments)},this.menu=a("<ul></ul>").addClass("ui-autocomplete").appendTo(a(this.options.appendTo||"body",c)[0]).mousedown(function(c){var d=b.menu.element[0];a(c.target).closest(".ui-menu-item").length||setTimeout(function(){a(document).one("mousedown",function(c){c.target!==b.element[0]&&c.target!==d&&!a.ui.contains(d,c.target)&&b.close()})},1),setTimeout(function(){clearTimeout(b.closing)},13)}).menu({focus:function(a,c){var d=c.item.data("item.autocomplete");!1!==b._trigger("focus",a,{item:d})&&/^key/.test(a.originalEvent.type)&&b.element.val(d.value)},selected:function(a,d){var e=d.item.data("item.autocomplete"),f=b.previous;b.element[0]!==c.activeElement&&(b.element.focus(),b.previous=f,setTimeout(function(){b.previous=f,b.selectedItem=e},1)),!1!==b._trigger("select",a,{item:e})&&b.element.val(e.value),b.term=b.element.val(),b.close(a),b.selectedItem=e},blur:function(a,c){b.menu.element.is(":visible")&&b.element.val()!==b.term&&b.element.val(b.term)}}).zIndex(this.element.zIndex()+1).css({top:0,left:0}).hide().data("menu"),a.fn.bgiframe&&this.menu.element.bgiframe(),b.beforeunloadHandler=function(){b.element.removeAttr("autocomplete")},a(window).bind("beforeunload",b.beforeunloadHandler)},destroy:function(){this.element.removeClass("ui-autocomplete-input").removeAttr("autocomplete").removeAttr("role").removeAttr("aria-autocomplete").removeAttr("aria-haspopup"),this.menu.element.remove(),a(window).unbind("beforeunload",this.beforeunloadHandler),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments),b==="source"&&this._initSource(),b==="appendTo"&&this.menu.element.appendTo(a(c||"body",this.element[0].ownerDocument)[0]),b==="disabled"&&c&&this.xhr&&this.xhr.abort()},_initSource:function(){var b=this,d,e;a.isArray(this.options.source)?(d=this.options.source,this.source=function(b,c){c(a.ui.autocomplete.filter(d,b.term))}):typeof this.options.source=="string"?(e=this.options.source,this.source=function(d,f){b.xhr&&b.xhr.abort(),b.xhr=a.ajax({url:e,data:d,dataType:"json",autocompleteRequest:++c,success:function(a,b){this.autocompleteRequest===c&&f(a)},error:function(){this.autocompleteRequest===c&&f([])}})}):this.source=this.options.source},search:function(a,b){a=a!=null?a:this.element.val(),this.term=this.element.val();if(a.length<this.options.minLength)return this.close(b);clearTimeout(this.closing);if(this._trigger("search",b)!==!1)return this._search(a)},_search:function(a){this.pending++,this.element.addClass("ui-autocomplete-loading"),this.source({term:a},this.response)},_response:function(a){!this.options.disabled&&a&&a.length?(a=this._normalize(a),this._suggest(a),this._trigger("open")):this.close(),this.pending--,this.pending||this.element.removeClass("ui-autocomplete-loading")},close:function(a){clearTimeout(this.closing),this.menu.element.is(":visible")&&(this.menu.element.hide(),this.menu.deactivate(),this._trigger("close",a))},_change:function(a){this.previous!==this.element.val()&&this._trigger("change",a,{item:this.selectedItem})},_normalize:function(b){if(b.length&&b[0].label&&b[0].value)return b;return a.map(b,function(b){if(typeof b=="string")return{label:b,value:b};return a.extend({label:b.label||b.value,value:b.value||b.label},b)})},_suggest:function(b){var c=this.menu.element.empty().zIndex(this.element.zIndex()+1);this._renderMenu(c,b),this.menu.deactivate(),this.menu.refresh(),c.show(),this._resizeMenu(),c.position(a.extend({of:this.element},this.options.position)),this.options.autoFocus&&this.menu.next(new a.Event("mouseover"))},_resizeMenu:function(){var a=this.menu.element;a.outerWidth(Math.max(a.width("").outerWidth()+1,this.element.outerWidth()))},_renderMenu:function(b,c){var d=this;a.each(c,function(a,c){d._renderItem(b,c)})},_renderItem:function(b,c){return a("<li></li>").data("item.autocomplete",c).append(a("<a></a>").text(c.label)).appendTo(b)},_move:function(a,b){if(!this.menu.element.is(":visible"))this.search(null,b);else{if(this.menu.first()&&/^previous/.test(a)||this.menu.last()&&/^next/.test(a)){this.element.val(this.term),this.menu.deactivate();return}this.menu[a](b)}},widget:function(){return this.menu.element}}),a.extend(a.ui.autocomplete,{escapeRegex:function(a){return a.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&")},filter:function(b,c){var d=new RegExp(a.ui.autocomplete.escapeRegex(c),"i");return a.grep(b,function(a){return d.test(a.label||a.value||a)})}})}(jQuery),function(a){a.widget("ui.menu",{_create:function(){var b=this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role:"listbox","aria-activedescendant":"ui-active-menuitem"}).click(function(c){!a(c.target).closest(".ui-menu-item a").length||(c.preventDefault(),b.select(c))}),this.refresh()},refresh:function(){var b=this,c=this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role","menuitem");c.children("a").addClass("ui-corner-all").attr("tabindex",-1).mouseenter(function(c){b.activate(c,a(this).parent())}).mouseleave(function(){b.deactivate()})},activate:function(a,b){this.deactivate();if(this.hasScroll()){var c=b.offset().top-this.element.offset().top,d=this.element.scrollTop(),e=this.element.height();c<0?this.element.scrollTop(d+c):c>=e&&this.element.scrollTop(d+c-e+b.height())}this.active=b.eq(0).children("a").addClass("ui-state-hover").attr("id","ui-active-menuitem").end(),this._trigger("focus",a,{item:b})},deactivate:function(){!this.active||(this.active.children("a").removeClass("ui-state-hover").removeAttr("id"),this._trigger("blur"),this.active=null)},next:function(a){this.move("next",".ui-menu-item:first",a)},previous:function(a){this.move("prev",".ui-menu-item:last",a)},first:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},last:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},move:function(a,b,c){if(!this.active)this.activate(c,this.element.children(b));else{var d=this.active[a+"All"](".ui-menu-item").eq(0);d.length?this.activate(c,d):this.activate(c,this.element.children(b))}},nextPage:function(b){if(this.hasScroll()){if(!this.active||this.last()){this.activate(b,this.element.children(".ui-menu-item:first"));return}var c=this.active.offset().top,d=this.element.height(),e=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c-d+a(this).height();return b<10&&b>-10});e.length||(e=this.element.children(".ui-menu-item:last")),this.activate(b,e)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.last()?":first":":last"))},previousPage:function(b){if(this.hasScroll()){if(!this.active||this.first()){this.activate(b,this.element.children(".ui-menu-item:last"));return}var c=this.active.offset().top,d=this.element.height();result=this.element.children(".ui-menu-item").filter(function(){var b=a(this).offset().top-c+d-a(this).height();return b<10&&b>-10}),result.length||(result=this.element.children(".ui-menu-item:first")),this.activate(b,result)}else this.activate(b,this.element.children(".ui-menu-item").filter(!this.active||this.first()?":last":":first"))},hasScroll:function(){return this.element.height()<this.element[a.fn.prop?"prop":"attr"]("scrollHeight")},select:function(a){this._trigger("selected",a,{item:this.active})}})}(jQuery),function(a,b){var c,d,e,f,g="ui-button ui-widget ui-state-default ui-corner-all",h="ui-state-hover ui-state-active ",i="ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",j=function(){var b=a(this).find(":ui-button");setTimeout(function(){b.button("refresh")},1)},k=function(b){var c=b.name,d=b.form,e=a([]);c&&(d?e=a(d).find("[name='"+c+"']"):e=a("[name='"+c+"']",b.ownerDocument).filter(function(){return!this.form}));return e};a.widget("ui.button",{options:{disabled:null,text:!0,label:null,icons:{primary:null,secondary:null}},_create:function(){this.element.closest("form").unbind("reset.button").bind("reset.button",j),typeof this.options.disabled!="boolean"&&(this.options.disabled=this.element.propAttr("disabled")),this._determineButtonType(),this.hasTitle=!!this.buttonElement.attr("title");var b=this,h=this.options,i=this.type==="checkbox"||this.type==="radio",l="ui-state-hover"+(i?"":" ui-state-active"),m="ui-state-focus";h.label===null&&(h.label=this.buttonElement.html()),this.element.is(":disabled")&&(h.disabled=!0),this.buttonElement.addClass(g).attr("role","button").bind("mouseenter.button",function(){h.disabled||(a(this).addClass("ui-state-hover"),this===c&&a(this).addClass("ui-state-active"))}).bind("mouseleave.button",function(){h.disabled||a(this).removeClass(l)}).bind("click.button",function(a){h.disabled&&(a.preventDefault(),a.stopImmediatePropagation())}),this.element.bind("focus.button",function(){b.buttonElement.addClass(m)}).bind("blur.button",function(){b.buttonElement.removeClass(m)}),i&&(this.element.bind("change.button",function(){f||b.refresh()}),this.buttonElement.bind("mousedown.button",function(a){h.disabled||(f=!1,d=a.pageX,e=a.pageY)}).bind("mouseup.button",function(a){!h.disabled&&(d!==a.pageX||e!==a.pageY)&&(f=!0)})),this.type==="checkbox"?this.buttonElement.bind("click.button",function(){if(h.disabled||f)return!1;a(this).toggleClass("ui-state-active"),b.buttonElement.attr("aria-pressed",b.element[0].checked)}):this.type==="radio"?this.buttonElement.bind("click.button",function(){if(h.disabled||f)return!1;a(this).addClass("ui-state-active"),b.buttonElement.attr("aria-pressed","true");var c=b.element[0];k(c).not(c).map(function(){return a(this).button("widget")[0]}).removeClass("ui-state-active").attr("aria-pressed","false")}):(this.buttonElement.bind("mousedown.button",function(){if(h.disabled)return!1;a(this).addClass("ui-state-active"),c=this,a(document).one("mouseup",function(){c=null})}).bind("mouseup.button",function(){if(h.disabled)return!1;a(this).removeClass("ui-state-active")}).bind("keydown.button",function(b){if(h.disabled)return!1;(b.keyCode==a.ui.keyCode.SPACE||b.keyCode==a.ui.keyCode.ENTER)&&a(this).addClass("ui-state-active")}).bind("keyup.button",function(){a(this).removeClass("ui-state-active")}),this.buttonElement.is("a")&&this.buttonElement.keyup(function(b){b.keyCode===a.ui.keyCode.SPACE&&a(this).click()})),this._setOption("disabled",h.disabled),this._resetButton()},_determineButtonType:function(){this.element.is(":checkbox")?this.type="checkbox":this.element.is(":radio")?this.type="radio":this.element.is("input")?this.type="input":this.type="button";if(this.type==="checkbox"||this.type==="radio"){var a=this.element.parents().filter(":last"),b="label[for='"+this.element.attr("id")+"']";this.buttonElement=a.find(b),this.buttonElement.length||(a=a.length?a.siblings():this.element.siblings(),this.buttonElement=a.filter(b),this.buttonElement.length||(this.buttonElement=a.find(b))),this.element.addClass("ui-helper-hidden-accessible");var c=this.element.is(":checked");c&&this.buttonElement.addClass("ui-state-active"),this.buttonElement.attr("aria-pressed",c)}else this.buttonElement=this.element},widget:function(){return this.buttonElement},destroy:function(){this.element.removeClass("ui-helper-hidden-accessible"),this.buttonElement.removeClass(g+" "+h+" "+i).removeAttr("role").removeAttr("aria-pressed").html(this.buttonElement.find(".ui-button-text").html()),this.hasTitle||this.buttonElement.removeAttr("title"),a.Widget.prototype.destroy.call(this)},_setOption:function(b,c){a.Widget.prototype._setOption.apply(this,arguments);b==="disabled"?c?this.element.propAttr("disabled",!0):this.element.propAttr("disabled",!1):this._resetButton()},refresh:function(){var b=this.element.is(":disabled");b!==this.options.disabled&&this._setOption("disabled",b),this.type==="radio"?k(this.element[0]).each(function(){a(this).is(":checked")?a(this).button("widget").addClass("ui-state-active").attr("aria-pressed","true"):a(this).button("widget").removeClass("ui-state-active").attr("aria-pressed","false")}):this.type==="checkbox"&&(this.element.is(":checked")?this.buttonElement.addClass("ui-state-active").attr("aria-pressed","true"):this.buttonElement.removeClass("ui-state-active").attr("aria-pressed","false"))},_resetButton:function(){if(this.type==="input")this.options.label&&this.element.val(this.options.label);else{var b=this.buttonElement.removeClass(i),c=a("<span></span>",this.element[0].ownerDocument).addClass("ui-button-text").html(this.options.label).appendTo(b.empty()).text(),d=this.options.icons,e=d.primary&&d.secondary,f=[];d.primary||d.secondary?(this.options.text&&f.push("ui-button-text-icon"+(e?"s":d.primary?"-primary":"-secondary")),d.primary&&b.prepend("<span class='ui-button-icon-primary ui-icon "+d.primary+"'></span>"),d.secondary&&b.append("<span class='ui-button-icon-secondary ui-icon "+d.secondary+"'></span>"),this.options.text||(f.push(e?"ui-button-icons-only":"ui-button-icon-only"),this.hasTitle||b.attr("title",c))):f.push("ui-button-text-only"),b.addClass(f.join(" "))}}}),a.widget("ui.buttonset",{options:{items:":button, :submit, :reset, :checkbox, :radio, a, :data(button)"},_create:function(){this.element.addClass("ui-buttonset")},_init:function(){this.refresh()},_setOption:function(b,c){b==="disabled"&&this.buttons.button("option",b,c),a.Widget.prototype._setOption.apply(this,arguments)},refresh:function(){var b=this.element.css("direction")==="rtl";this.buttons=this.element.find(this.options.items).filter(":ui-button").button("refresh").end().not(":ui-button").button().end().map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-all ui-corner-left ui-corner-right").filter(":first").addClass(b?"ui-corner-right":"ui-corner-left").end().filter(":last").addClass(b?"ui-corner-left":"ui-corner-right").end().end()},destroy:function(){this.element.removeClass("ui-buttonset"),this.buttons.map(function(){return a(this).button("widget")[0]}).removeClass("ui-corner-left ui-corner-right").end().button("destroy"),a.Widget.prototype.destroy.call(this)}})}(jQuery),function($,undefined){function isArray(a){return a&&($.browser.safari&&typeof a=="object"&&a.length||a.constructor&&a.constructor.toString().match(/\Array\(\)/))}function extendRemove(a,b){$.extend(a,b);for(var c in b)if(b[c]==null||b[c]==undefined)a[c]=b[c];return a}function bindHover(a){var b="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return a.bind("mouseout",function(a){var c=$(a.target).closest(b);!c.length||c.removeClass("ui-state-hover ui-datepicker-prev-hover ui-datepicker-next-hover")}).bind("mouseover",function(c){var d=$(c.target).closest(b);!$.datepicker._isDisabledDatepicker(instActive.inline?a.parent()[0]:instActive.input[0])&&!!d.length&&(d.parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),d.addClass("ui-state-hover"),d.hasClass("ui-datepicker-prev")&&d.addClass("ui-datepicker-prev-hover"),d.hasClass("ui-datepicker-next")&&d.addClass("ui-datepicker-next-hover"))})}function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},$.extend(this._defaults,this.regional[""]),this.dpDiv=bindHover($('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}$.extend($.ui,{datepicker:{version:"1.8.17"}});var PROP_NAME="datepicker" +,dpuuid=(new Date).getTime(),instActive;$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",maxRows:4,log:function(){this.debug&&console.log.apply("",arguments)},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(a){extendRemove(this._defaults,a||{});return this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase(),inline=nodeName=="div"||nodeName=="span";target.id||(this.uuid+=1,target.id="dp"+this.uuid);var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{}),nodeName=="input"?this._connectDatepicker(target,inst):inline&&this._inlineDatepicker(target,inst)},_newInst:function(a,b){var c=a[0].id.replace(/([^A-Za-z0-9_-])/g,"\\\\$1");return{id:c,input:a,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:b,dpDiv:b?bindHover($('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>')):this.dpDiv}},_connectDatepicker:function(a,b){var c=$(a);b.append=$([]),b.trigger=$([]);c.hasClass(this.markerClassName)||(this._attachments(c,b),c.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),this._autoSize(b),$.data(a,PROP_NAME,b),b.settings.disabled&&this._disableDatepicker(a))},_attachments:function(a,b){var c=this._get(b,"appendText"),d=this._get(b,"isRTL");b.append&&b.append.remove(),c&&(b.append=$('<span class="'+this._appendClass+'">'+c+"</span>"),a[d?"before":"after"](b.append)),a.unbind("focus",this._showDatepicker),b.trigger&&b.trigger.remove();var e=this._get(b,"showOn");(e=="focus"||e=="both")&&a.focus(this._showDatepicker);if(e=="button"||e=="both"){var f=this._get(b,"buttonText"),g=this._get(b,"buttonImage");b.trigger=$(this._get(b,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:g,alt:f,title:f}):$('<button type="button"></button>').addClass(this._triggerClass).html(g==""?f:$("<img/>").attr({src:g,alt:f,title:f}))),a[d?"before":"after"](b.trigger),b.trigger.click(function(){$.datepicker._datepickerShowing&&$.datepicker._lastInput==a[0]?$.datepicker._hideDatepicker():$.datepicker._showDatepicker(a[0]);return!1})}},_autoSize:function(a){if(this._get(a,"autoSize")&&!a.inline){var b=new Date(2009,11,20),c=this._get(a,"dateFormat");if(c.match(/[DM]/)){var d=function(a){var b=0,c=0;for(var d=0;d<a.length;d++)a[d].length>b&&(b=a[d].length,c=d);return c};b.setMonth(d(this._get(a,c.match(/MM/)?"monthNames":"monthNamesShort"))),b.setDate(d(this._get(a,c.match(/DD/)?"dayNames":"dayNamesShort"))+20-b.getDay())}a.input.attr("size",this._formatDate(a,b).length)}},_inlineDatepicker:function(a,b){var c=$(a);c.hasClass(this.markerClassName)||(c.addClass(this.markerClassName).append(b.dpDiv).bind("setData.datepicker",function(a,c,d){b.settings[c]=d}).bind("getData.datepicker",function(a,c){return this._get(b,c)}),$.data(a,PROP_NAME,b),this._setDate(b,this._getDefaultDate(b),!0),this._updateDatepicker(b),this._updateAlternate(b),b.settings.disabled&&this._disableDatepicker(a),b.dpDiv.css("display","block"))},_dialogDatepicker:function(a,b,c,d,e){var f=this._dialogInst;if(!f){this.uuid+=1;var g="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+g+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>'),this._dialogInput.keydown(this._doKeyDown),$("body").append(this._dialogInput),f=this._dialogInst=this._newInst(this._dialogInput,!1),f.settings={},$.data(this._dialogInput[0],PROP_NAME,f)}extendRemove(f.settings,d||{}),b=b&&b.constructor==Date?this._formatDate(f,b):b,this._dialogInput.val(b),this._pos=e?e.length?e:[e.pageX,e.pageY]:null;if(!this._pos){var h=document.documentElement.clientWidth,i=document.documentElement.clientHeight,j=document.documentElement.scrollLeft||document.body.scrollLeft,k=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[h/2-100+j,i/2-150+k]}this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),f.settings.onSelect=c,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),$.blockUI&&$.blockUI(this.dpDiv),$.data(this._dialogInput[0],PROP_NAME,f);return this},_destroyDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!!b.hasClass(this.markerClassName)){var d=a.nodeName.toLowerCase();$.removeData(a,PROP_NAME),d=="input"?(c.append.remove(),c.trigger.remove(),b.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):(d=="div"||d=="span")&&b.removeClass(this.markerClassName).empty()}},_enableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!!b.hasClass(this.markerClassName)){var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!1,c.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().removeClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").removeAttr("disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b})}},_disableDatepicker:function(a){var b=$(a),c=$.data(a,PROP_NAME);if(!!b.hasClass(this.markerClassName)){var d=a.nodeName.toLowerCase();if(d=="input")a.disabled=!0,c.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"});else if(d=="div"||d=="span"){var e=b.children("."+this._inlineClass);e.children().addClass("ui-state-disabled"),e.find("select.ui-datepicker-month, select.ui-datepicker-year").attr("disabled","disabled")}this._disabledInputs=$.map(this._disabledInputs,function(b){return b==a?null:b}),this._disabledInputs[this._disabledInputs.length]=a}},_isDisabledDatepicker:function(a){if(!a)return!1;for(var b=0;b<this._disabledInputs.length;b++)if(this._disabledInputs[b]==a)return!0;return!1},_getInst:function(a){try{return $.data(a,PROP_NAME)}catch(b){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(a,b,c){var d=this._getInst(a);if(arguments.length==2&&typeof b=="string")return b=="defaults"?$.extend({},$.datepicker._defaults):d?b=="all"?$.extend({},d.settings):this._get(d,b):null;var e=b||{};typeof b=="string"&&(e={},e[b]=c);if(d){this._curInst==d&&this._hideDatepicker();var f=this._getDateDatepicker(a,!0),g=this._getMinMaxDate(d,"min"),h=this._getMinMaxDate(d,"max");extendRemove(d.settings,e),g!==null&&e.dateFormat!==undefined&&e.minDate===undefined&&(d.settings.minDate=this._formatDate(d,g)),h!==null&&e.dateFormat!==undefined&&e.maxDate===undefined&&(d.settings.maxDate=this._formatDate(d,h)),this._attachments($(a),d),this._autoSize(d),this._setDate(d,f),this._updateAlternate(d),this._updateDatepicker(d)}},_changeDatepicker:function(a,b,c){this._optionDatepicker(a,b,c)},_refreshDatepicker:function(a){var b=this._getInst(a);b&&this._updateDatepicker(b)},_setDateDatepicker:function(a,b){var c=this._getInst(a);c&&(this._setDate(c,b),this._updateDatepicker(c),this._updateAlternate(c))},_getDateDatepicker:function(a,b){var c=this._getInst(a);c&&!c.inline&&this._setDateFromField(c,b);return c?this._getDate(c):null},_doKeyDown:function(a){var b=$.datepicker._getInst(a.target),c=!0,d=b.dpDiv.is(".ui-datepicker-rtl");b._keyEvent=!0;if($.datepicker._datepickerShowing)switch(a.keyCode){case 9:$.datepicker._hideDatepicker(),c=!1;break;case 13:var e=$("td."+$.datepicker._dayOverClass+":not(."+$.datepicker._currentClass+")",b.dpDiv);e[0]&&$.datepicker._selectDay(a.target,b.selectedMonth,b.selectedYear,e[0]);var f=$.datepicker._get(b,"onSelect");if(f){var g=$.datepicker._formatDate(b);f.apply(b.input?b.input[0]:null,[g,b])}else $.datepicker._hideDatepicker();return!1;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 34:$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 35:(a.ctrlKey||a.metaKey)&&$.datepicker._clearDate(a.target),c=a.ctrlKey||a.metaKey;break;case 36:(a.ctrlKey||a.metaKey)&&$.datepicker._gotoToday(a.target),c=a.ctrlKey||a.metaKey;break;case 37:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?1:-1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?-$.datepicker._get(b,"stepBigMonths"):-$.datepicker._get(b,"stepMonths"),"M");break;case 38:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,-7,"D"),c=a.ctrlKey||a.metaKey;break;case 39:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,d?-1:1,"D"),c=a.ctrlKey||a.metaKey,a.originalEvent.altKey&&$.datepicker._adjustDate(a.target,a.ctrlKey?+$.datepicker._get(b,"stepBigMonths"):+$.datepicker._get(b,"stepMonths"),"M");break;case 40:(a.ctrlKey||a.metaKey)&&$.datepicker._adjustDate(a.target,7,"D"),c=a.ctrlKey||a.metaKey;break;default:c=!1}else a.keyCode==36&&a.ctrlKey?$.datepicker._showDatepicker(this):c=!1;c&&(a.preventDefault(),a.stopPropagation())},_doKeyPress:function(a){var b=$.datepicker._getInst(a.target);if($.datepicker._get(b,"constrainInput")){var c=$.datepicker._possibleChars($.datepicker._get(b,"dateFormat")),d=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);return a.ctrlKey||a.metaKey||d<" "||!c||c.indexOf(d)>-1}},_doKeyUp:function(a){var b=$.datepicker._getInst(a.target);if(b.input.val()!=b.lastVal)try{var c=$.datepicker.parseDate($.datepicker._get(b,"dateFormat"),b.input?b.input.val():null,$.datepicker._getFormatConfig(b));c&&($.datepicker._setDateFromField(b),$.datepicker._updateAlternate(b),$.datepicker._updateDatepicker(b))}catch(a){$.datepicker.log(a)}return!0},_showDatepicker:function(a){a=a.target||a,a.nodeName.toLowerCase()!="input"&&(a=$("input",a.parentNode)[0]);if(!$.datepicker._isDisabledDatepicker(a)&&$.datepicker._lastInput!=a){var b=$.datepicker._getInst(a);$.datepicker._curInst&&$.datepicker._curInst!=b&&($.datepicker._curInst.dpDiv.stop(!0,!0),b&&$.datepicker._datepickerShowing&&$.datepicker._hideDatepicker($.datepicker._curInst.input[0]));var c=$.datepicker._get(b,"beforeShow"),d=c?c.apply(a,[a,b]):{};if(d===!1)return;extendRemove(b.settings,d),b.lastVal=null,$.datepicker._lastInput=a,$.datepicker._setDateFromField(b),$.datepicker._inDialog&&(a.value=""),$.datepicker._pos||($.datepicker._pos=$.datepicker._findPos(a),$.datepicker._pos[1]+=a.offsetHeight);var e=!1;$(a).parents().each(function(){e|=$(this).css("position")=="fixed";return!e}),e&&$.browser.opera&&($.datepicker._pos[0]-=document.documentElement.scrollLeft,$.datepicker._pos[1]-=document.documentElement.scrollTop);var f={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null,b.dpDiv.empty(),b.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),$.datepicker._updateDatepicker(b),f=$.datepicker._checkOffset(b,f,e),b.dpDiv.css({position:$.datepicker._inDialog&&$.blockUI?"static":e?"fixed":"absolute",display:"none",left:f.left+"px",top:f.top+"px"});if(!b.inline){var g=$.datepicker._get(b,"showAnim"),h=$.datepicker._get(b,"duration"),i=function(){var a=b.dpDiv.find("iframe.ui-datepicker-cover");if(!!a.length){var c=$.datepicker._getBorders(b.dpDiv);a.css({left:-c[0],top:-c[1],width:b.dpDiv.outerWidth(),height:b.dpDiv.outerHeight()})}};b.dpDiv.zIndex($(a).zIndex()+1),$.datepicker._datepickerShowing=!0,$.effects&&$.effects[g]?b.dpDiv.show(g,$.datepicker._get(b,"showOptions"),h,i):b.dpDiv[g||"show"](g?h:null,i),(!g||!h)&&i(),b.input.is(":visible")&&!b.input.is(":disabled")&&b.input.focus(),$.datepicker._curInst=b}}},_updateDatepicker:function(a){var b=this;b.maxRows=4;var c=$.datepicker._getBorders(a.dpDiv);instActive=a,a.dpDiv.empty().append(this._generateHTML(a));var d=a.dpDiv.find("iframe.ui-datepicker-cover");!d.length||d.css({left:-c[0],top:-c[1],width:a.dpDiv.outerWidth(),height:a.dpDiv.outerHeight()}),a.dpDiv.find("."+this._dayOverClass+" a").mouseover();var e=this._getNumberOfMonths(a),f=e[1],g=17;a.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),f>1&&a.dpDiv.addClass("ui-datepicker-multi-"+f).css("width",g*f+"em"),a.dpDiv[(e[0]!=1||e[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi"),a.dpDiv[(this._get(a,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),a==$.datepicker._curInst&&$.datepicker._datepickerShowing&&a.input&&a.input.is(":visible")&&!a.input.is(":disabled")&&a.input[0]!=document.activeElement&&a.input.focus();if(a.yearshtml){var h=a.yearshtml;setTimeout(function(){h===a.yearshtml&&a.yearshtml&&a.dpDiv.find("select.ui-datepicker-year:first").replaceWith(a.yearshtml),h=a.yearshtml=null},0)}},_getBorders:function(a){var b=function(a){return{thin:1,medium:2,thick:3}[a]||a};return[parseFloat(b(a.css("border-left-width"))),parseFloat(b(a.css("border-top-width")))]},_checkOffset:function(a,b,c){var d=a.dpDiv.outerWidth(),e=a.dpDiv.outerHeight(),f=a.input?a.input.outerWidth():0,g=a.input?a.input.outerHeight():0,h=document.documentElement.clientWidth+$(document).scrollLeft(),i=document.documentElement.clientHeight+$(document).scrollTop();b.left-=this._get(a,"isRTL")?d-f:0,b.left-=c&&b.left==a.input.offset().left?$(document).scrollLeft():0,b.top-=c&&b.top==a.input.offset().top+g?$(document).scrollTop():0,b.left-=Math.min(b.left,b.left+d>h&&h>d?Math.abs(b.left+d-h):0),b.top-=Math.min(b.top,b.top+e>i&&i>e?Math.abs(e+g):0);return b},_findPos:function(a){var b=this._getInst(a),c=this._get(b,"isRTL");while(a&&(a.type=="hidden"||a.nodeType!=1||$.expr.filters.hidden(a)))a=a[c?"previousSibling":"nextSibling"];var d=$(a).offset();return[d.left,d.top]},_hideDatepicker:function(a){var b=this._curInst;if(!(!b||a&&b!=$.data(a,PROP_NAME))&&this._datepickerShowing){var c=this._get(b,"showAnim"),d=this._get(b,"duration"),e=this,f=function(){$.datepicker._tidyDialog(b),e._curInst=null};$.effects&&$.effects[c]?b.dpDiv.hide(c,$.datepicker._get(b,"showOptions"),d,f):b.dpDiv[c=="slideDown"?"slideUp":c=="fadeIn"?"fadeOut":"hide"](c?d:null,f),c||f(),this._datepickerShowing=!1;var g=this._get(b,"onClose");g&&g.apply(b.input?b.input[0]:null,[b.input?b.input.val():"",b]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),$.blockUI&&($.unblockUI(),$("body").append(this.dpDiv))),this._inDialog=!1}},_tidyDialog:function(a){a.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(a){if(!!$.datepicker._curInst){var b=$(a.target),c=$.datepicker._getInst(b[0]);(b[0].id!=$.datepicker._mainDivId&&b.parents("#"+$.datepicker._mainDivId).length==0&&!b.hasClass($.datepicker.markerClassName)&&!b.hasClass($.datepicker._triggerClass)&&$.datepicker._datepickerShowing&&(!$.datepicker._inDialog||!$.blockUI)||b.hasClass($.datepicker.markerClassName)&&$.datepicker._curInst!=c)&&$.datepicker._hideDatepicker()}},_adjustDate:function(a,b,c){var d=$(a),e=this._getInst(d[0]);this._isDisabledDatepicker(d[0])||(this._adjustInstDate(e,b+(c=="M"?this._get(e,"showCurrentAtPos"):0),c),this._updateDatepicker(e))},_gotoToday:function(a){var b=$(a),c=this._getInst(b[0]);if(this._get(c,"gotoCurrent")&&c.currentDay)c.selectedDay=c.currentDay,c.drawMonth=c.selectedMonth=c.currentMonth,c.drawYear=c.selectedYear=c.currentYear;else{var d=new Date;c.selectedDay=d.getDate(),c.drawMonth=c.selectedMonth=d.getMonth(),c.drawYear=c.selectedYear=d.getFullYear()}this._notifyChange(c),this._adjustDate(b)},_selectMonthYear:function(a,b,c){var d=$(a),e=this._getInst(d[0]);e["selected"+(c=="M"?"Month":"Year")]=e["draw"+(c=="M"?"Month":"Year")]=parseInt(b.options[b.selectedIndex].value,10),this._notifyChange(e),this._adjustDate(d)},_selectDay:function(a,b,c,d){var e=$(a);if(!$(d).hasClass(this._unselectableClass)&&!this._isDisabledDatepicker(e[0])){var f=this._getInst(e[0]);f.selectedDay=f.currentDay=$("a",d).html(),f.selectedMonth=f.currentMonth=b,f.selectedYear=f.currentYear=c,this._selectDate(a,this._formatDate(f,f.currentDay,f.currentMonth,f.currentYear))}},_clearDate:function(a){var b=$(a),c=this._getInst(b[0]);this._selectDate(b,"")},_selectDate:function(a,b){var c=$(a),d=this._getInst(c[0]);b=b!=null?b:this._formatDate(d),d.input&&d.input.val(b),this._updateAlternate(d);var e=this._get(d,"onSelect");e?e.apply(d.input?d.input[0]:null,[b,d]):d.input&&d.input.trigger("change"),d.inline?this._updateDatepicker(d):(this._hideDatepicker(),this._lastInput=d.input[0],typeof d.input[0]!="object"&&d.input.focus(),this._lastInput=null)},_updateAlternate:function(a){var b=this._get(a,"altField");if(b){var c=this._get(a,"altFormat")||this._get(a,"dateFormat"),d=this._getDate(a),e=this.formatDate(c,d,this._getFormatConfig(a));$(b).each(function(){$(this).val(e)})}},noWeekends:function(a){var b=a.getDay();return[b>0&&b<6,""]},iso8601Week:function(a){var b=new Date(a.getTime());b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();b.setMonth(0),b.setDate(1);return Math.floor(Math.round((c-b)/864e5)/7)+1},parseDate:function(a,b,c){if(a==null||b==null)throw"Invalid arguments";b=typeof b=="object"?b.toString():b+"";if(b=="")return null;var d=(c?c.shortYearCutoff:null)||this._defaults.shortYearCutoff;d=typeof d!="string"?d:(new Date).getFullYear()%100+parseInt(d,10);var e=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,f=(c?c.dayNames:null)||this._defaults.dayNames,g=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,h=(c?c.monthNames:null)||this._defaults.monthNames,i=-1,j=-1,k=-1,l=-1,m=!1,n=function(b){var c=s+1<a.length&&a.charAt(s+1)==b;c&&s++;return c},o=function(a){var c=n(a),d=a=="@"?14:a=="!"?20:a=="y"&&c?4:a=="o"?3:2,e=new RegExp("^\\d{1,"+d+"}"),f=b.substring(r).match(e);if(!f)throw"Missing number at position "+r;r+=f[0].length;return parseInt(f[0],10)},p=function(a,c,d){var e=$.map(n(a)?d:c,function(a,b){return[[b,a]]}).sort(function(a,b){return-(a[1].length-b[1].length)}),f=-1;$.each(e,function(a,c){var d=c[1];if(b.substr(r,d.length).toLowerCase()==d.toLowerCase()){f=c[0],r+=d.length;return!1}});if(f!=-1)return f+1;throw"Unknown name at position "+r},q=function(){if(b.charAt(r)!=a.charAt(s))throw"Unexpected literal at position "+r;r++},r=0;for(var s=0;s<a.length;s++)if(m)a.charAt(s)=="'"&&!n("'")?m=!1:q();else switch(a.charAt(s)){case"d":k=o("d");break;case"D":p("D",e,f);break;case"o":l=o("o");break;case"m":j=o("m");break;case"M":j=p("M",g,h);break;case"y":i=o("y");break;case"@":var t=new Date(o("@"));i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"!":var t=new Date((o("!")-this._ticksTo1970)/1e4);i=t.getFullYear(),j=t.getMonth()+1,k=t.getDate();break;case"'":n("'")?q():m=!0;break;default:q()}if(r<b.length)throw"Extra/unparsed characters found in date: "+b.substring(r);i==-1?i=(new Date).getFullYear():i<100&&(i+=(new Date).getFullYear()-(new Date).getFullYear()%100+(i<=d?0:-100));if(l>-1){j=1,k=l;for(;;){var u=this._getDaysInMonth(i,j-1);if(k<=u)break;j++,k-=u}}var t=this._daylightSavingAdjust(new Date(i,j-1,k));if(t.getFullYear()!=i||t.getMonth()+1!=j||t.getDate()!=k)throw"Invalid date";return t},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925))*24*60*60*1e7,formatDate:function(a,b,c){if(!b)return"";var d=(c?c.dayNamesShort:null)||this._defaults.dayNamesShort,e=(c?c.dayNames:null)||this._defaults.dayNames,f=(c?c.monthNamesShort:null)||this._defaults.monthNamesShort,g=(c?c.monthNames:null)||this._defaults.monthNames,h=function(b){var c=m+1<a.length&&a.charAt(m+1)==b;c&&m++;return c},i=function(a,b,c){var d=""+b;if(h(a))while(d.length<c)d="0"+d;return d},j=function(a,b,c,d){return h(a)?d[b]:c[b]},k="",l=!1;if(b)for(var m=0;m<a.length;m++)if(l)a.charAt(m)=="'"&&!h("'")?l=!1:k+=a.charAt(m);else switch(a.charAt(m)){case"d":k+=i("d",b.getDate(),2);break;case"D":k+=j("D",b.getDay(),d,e);break;case"o":k+=i("o",Math.round(((new Date(b.getFullYear(),b.getMonth(),b.getDate())).getTime()-(new Date(b.getFullYear(),0,0)).getTime())/864e5),3);break;case"m":k+=i("m",b.getMonth()+1,2);break;case"M":k+=j("M",b.getMonth(),f,g);break;case"y":k+=h("y")?b.getFullYear():(b.getYear()%100<10?"0":"")+b.getYear()%100;break;case"@":k+=b.getTime();break;case"!":k+=b.getTime()*1e4+this._ticksTo1970;break;case"'":h("'")?k+="'":l=!0;break;default:k+=a.charAt(m)}return k},_possibleChars:function(a){var b="",c=!1,d=function(b){var c=e+1<a.length&&a.charAt(e+1)==b;c&&e++;return c};for(var e=0;e<a.length;e++)if(c)a.charAt(e)=="'"&&!d("'")?c=!1:b+=a.charAt(e);else switch(a.charAt(e)){case"d":case"m":case"y":case"@":b+="0123456789";break;case"D":case"M":return null;case"'":d("'")?b+="'":c=!0;break;default:b+=a.charAt(e)}return b},_get:function(a,b){return a.settings[b]!==undefined?a.settings[b]:this._defaults[b]},_setDateFromField:function(a,b){if(a.input.val()!=a.lastVal){var c=this._get(a,"dateFormat"),d=a.lastVal=a.input?a.input.val():null,e,f;e=f=this._getDefaultDate(a);var g=this._getFormatConfig(a);try{e=this.parseDate(c,d,g)||f}catch(h){this.log(h),d=b?"":d}a.selectedDay=e.getDate(),a.drawMonth=a.selectedMonth=e.getMonth(),a.drawYear=a.selectedYear=e.getFullYear(),a.currentDay=d?e.getDate():0,a.currentMonth=d?e.getMonth():0,a.currentYear=d?e.getFullYear():0,this._adjustInstDate(a)}},_getDefaultDate:function(a){return this._restrictMinMax(a,this._determineDate(a,this._get(a,"defaultDate"),new Date))},_determineDate:function(a,b,c){var d=function(a){var b=new Date;b.setDate(b.getDate()+a);return b},e=function(b){try{return $.datepicker.parseDate($.datepicker._get(a,"dateFormat"),b,$.datepicker._getFormatConfig(a))}catch(c){}var d=(b.toLowerCase().match(/^c/)?$.datepicker._getDate(a):null)||new Date,e=d.getFullYear(),f=d.getMonth(),g=d.getDate(),h=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,i=h.exec(b);while(i){switch(i[2]||"d"){case"d":case"D":g+=parseInt(i[1],10);break;case"w":case"W":g+=parseInt(i[1],10)*7;break;case"m":case"M":f+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f));break;case"y":case"Y":e+=parseInt(i[1],10),g=Math.min(g,$.datepicker._getDaysInMonth(e,f))}i=h.exec(b)}return new Date(e,f,g)},f=b==null||b===""?c:typeof b=="string"?e(b):typeof b=="number"?isNaN(b)?c:d(b):new Date(b.getTime());f=f&&f.toString()=="Invalid Date"?c:f,f&&(f.setHours(0),f.setMinutes(0),f.setSeconds(0),f.setMilliseconds(0));return this._daylightSavingAdjust(f)},_daylightSavingAdjust:function(a){if(!a)return null;a.setHours(a.getHours()>12?a.getHours()+2:0);return a},_setDate:function(a,b,c){var d=!b,e=a.selectedMonth,f=a.selectedYear,g=this._restrictMinMax(a,this._determineDate(a,b,new Date));a.selectedDay=a.currentDay=g.getDate(),a.drawMonth=a.selectedMonth=a.currentMonth=g.getMonth(),a.drawYear=a.selectedYear=a.currentYear=g.getFullYear(),(e!=a.selectedMonth||f!=a.selectedYear)&&!c&&this._notifyChange(a),this._adjustInstDate(a),a.input&&a.input.val(d?"":this._formatDate(a))},_getDate:function(a){var b=!a.currentYear||a.input&&a.input.val()==""?null:this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return b},_generateHTML:function(a){var b=new Date;b=this._daylightSavingAdjust(new Date(b.getFullYear(),b.getMonth(),b.getDate()));var c=this._get(a,"isRTL"),d=this._get(a,"showButtonPanel"),e=this._get(a,"hideIfNoPrevNext"),f=this._get(a,"navigationAsDateFormat"),g=this._getNumberOfMonths(a),h=this._get(a,"showCurrentAtPos"),i=this._get(a,"stepMonths"),j=g[0]!=1||g[1]!=1,k=this._daylightSavingAdjust(a.currentDay?new Date(a.currentYear,a.currentMonth,a.currentDay):new Date(9999,9,9)),l=this._getMinMaxDate(a,"min"),m=this._getMinMaxDate(a,"max"),n=a.drawMonth-h,o=a.drawYear;n<0&&(n+=12,o--);if(m){var p=this._daylightSavingAdjust(new Date(m.getFullYear(),m.getMonth()-g[0]*g[1]+1,m.getDate()));p=l&&p<l?l:p;while(this._daylightSavingAdjust(new Date(o,n,1))>p)n--,n<0&&(n=11,o--)}a.drawMonth=n,a.drawYear=o;var q=this._get(a,"prevText");q=f?this.formatDate(q,this._daylightSavingAdjust(new Date(o,n-i,1)),this._getFormatConfig(a)):q;var r=this._canAdjustMonth(a,-1,o,n)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', -"+i+", 'M');\""+' title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>":e?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+q+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"e":"w")+'">'+q+"</span></a>",s=this._get(a,"nextText");s=f?this.formatDate(s,this._daylightSavingAdjust(new Date(o,n+i,1)),this._getFormatConfig(a)):s;var t=this._canAdjustMonth(a,1,o,n)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+a.id+"', +"+i+", 'M');\""+' title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>":e?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+s+'"><span class="ui-icon ui-icon-circle-triangle-'+(c?"w":"e")+'">'+s+"</span></a>",u=this._get(a,"currentText"),v=this._get(a,"gotoCurrent")&&a.currentDay?k:b;u=f?this.formatDate(u,v,this._getFormatConfig(a)):u;var w=a.inline?"":'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+dpuuid+'.datepicker._hideDatepicker();">'+this._get(a,"closeText")+"</button>",x=d?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(c?w:"")+(this._isInRange(a,v)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._gotoToday('#"+a.id+"');\""+">"+u+"</button>":"")+(c?"":w)+"</div>":"",y=parseInt(this._get(a,"firstDay"),10);y=isNaN(y)?0:y;var z=this._get(a,"showWeek"),A=this._get(a,"dayNames"),B=this._get(a,"dayNamesShort"),C=this._get(a,"dayNamesMin"),D=this._get(a,"monthNames"),E=this._get(a,"monthNamesShort"),F=this._get(a,"beforeShowDay"),G=this._get(a,"showOtherMonths"),H=this._get(a,"selectOtherMonths"),I=this._get(a,"calculateWeek")||this.iso8601Week,J=this._getDefaultDate(a),K="";for(var L=0;L<g[0];L++){var M="";this.maxRows=4;for(var N=0;N<g[1];N++){var O=this._daylightSavingAdjust(new Date(o,n,a.selectedDay)),P=" ui-corner-all",Q="";if(j){Q+='<div class="ui-datepicker-group';if(g[1]>1)switch(N){case 0:Q+=" ui-datepicker-group-first",P=" ui-corner-"+(c?"right":"left");break;case g[1]-1:Q+=" ui-datepicker-group-last",P=" ui-corner-"+(c?"left":"right");break;default:Q+=" ui-datepicker-group-middle",P=""}Q+='">'}Q+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+P+'">'+(/all|left/.test(P)&&L==0?c?t:r:"")+(/all|right/.test(P)&&L==0?c?r:t:"")+this._generateMonthYearHeader(a,n,o,l,m,L>0||N>0,D,E)+'</div><table class="ui-datepicker-calendar"><thead>'+"<tr>";var R=z?'<th class="ui-datepicker-week-col">'+this._get(a,"weekHeader")+"</th>":"";for(var S=0;S<7;S++){var T=(S+y)%7;R+="<th"+((S+y+6)%7>=5?' class="ui-datepicker-week-end"':"")+">"+'<span title="'+A[T]+'">'+C[T]+"</span></th>"}Q+=R+"</tr></thead><tbody>";var U=this._getDaysInMonth(o,n);o==a.selectedYear&&n==a.selectedMonth&&(a.selectedDay=Math.min(a.selectedDay,U));var V=(this._getFirstDayOfMonth(o,n)-y+7)%7,W=Math.ceil((V+U)/7),X=j?this.maxRows>W?this.maxRows:W:W;this.maxRows=X;var Y=this._daylightSavingAdjust(new Date(o,n,1-V));for(var Z=0;Z<X;Z++){Q+="<tr>";var _=z?'<td class="ui-datepicker-week-col">'+this._get(a,"calculateWeek")(Y)+"</td>":"";for(var S=0;S<7;S++){var ba=F?F.apply(a.input?a.input[0]:null,[Y]):[!0,""],bb=Y.getMonth()!=n,bc=bb&&!H||!ba[0]||l&&Y<l||m&&Y>m;_+='<td class="'+((S+y+6)%7>=5?" ui-datepicker-week-end":"")+(bb?" ui-datepicker-other-month":"")+(Y.getTime()==O.getTime()&&n==a.selectedMonth&&a._keyEvent||J.getTime()==Y.getTime()&&J.getTime()==O.getTime()?" "+this._dayOverClass:"")+(bc?" "+this._unselectableClass+" ui-state-disabled":"")+(bb&&!G?"":" "+ba[1]+(Y.getTime()==k.getTime()?" "+this._currentClass:"")+(Y.getTime()==b.getTime()?" ui-datepicker-today":""))+'"'+((!bb||G)&&ba[2]?' title="'+ba[2]+'"':"")+(bc?"":' onclick="DP_jQuery_'+dpuuid+".datepicker._selectDay('#"+a.id+"',"+Y.getMonth()+","+Y.getFullYear()+', this);return false;"')+">"+(bb&&!G?" ":bc?'<span class="ui-state-default">'+Y.getDate()+"</span>":'<a class="ui-state-default'+(Y.getTime()==b.getTime()?" ui-state-highlight":"")+(Y.getTime()==k.getTime()?" ui-state-active":"")+(bb?" ui-priority-secondary":"")+'" href="#">'+Y.getDate()+"</a>")+"</td>",Y.setDate(Y.getDate()+1),Y=this._daylightSavingAdjust(Y)}Q+=_+"</tr>"}n++,n>11&&(n=0,o++),Q+="</tbody></table>"+(j?"</div>"+(g[0]>0&&N==g[1]-1?'<div class="ui-datepicker-row-break"></div>':""):""),M+=Q}K+=M}K+=x+($.browser.msie&&parseInt($.browser.version,10)<7&&!a.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':""),a._keyEvent=!1;return K},_generateMonthYearHeader:function(a,b,c,d,e,f,g,h){var i=this._get(a,"changeMonth"),j=this._get(a,"changeYear"),k=this._get(a,"showMonthAfterYear"),l='<div class="ui-datepicker-title">',m="";if(f||!i)m+='<span class="ui-datepicker-month">'+g[b]+"</span>";else{var n=d&&d.getFullYear()==c,o=e&&e.getFullYear()==c;m+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'M');\" "+">";for(var p=0;p<12;p++)(!n||p>=d.getMonth())&&(!o||p<=e.getMonth())&&(m+='<option value="'+p+'"'+(p==b?' selected="selected"':"")+">"+h[p]+"</option>");m+="</select>"}k||(l+=m+(f||!i||!j?" ":""));if(!a.yearshtml){a.yearshtml="";if(f||!j)l+='<span class="ui-datepicker-year">'+c+"</span>";else{var q=this._get(a,"yearRange").split(":"),r=(new Date).getFullYear(),s=function(a){var b=a.match(/c[+-].*/)?c+parseInt(a.substring(1),10):a.match(/[+-].*/)?r+parseInt(a,10):parseInt(a,10);return isNaN(b)?r:b},t=s(q[0]),u=Math.max(t,s(q[1]||""));t=d?Math.max(t,d.getFullYear()):t,u=e?Math.min(u,e.getFullYear()):u,a.yearshtml+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+a.id+"', this, 'Y');\" "+">";for(;t<=u;t++)a.yearshtml+='<option value="'+t+'"'+(t==c?' selected="selected"':"")+">"+t+"</option>";a.yearshtml+="</select>",l+=a.yearshtml,a.yearshtml=null}}l+=this._get(a,"yearSuffix"),k&&(l+=(f||!i||!j?" ":"")+m),l+="</div>";return l},_adjustInstDate:function(a,b,c){var d=a.drawYear+(c=="Y"?b:0),e=a.drawMonth+(c=="M"?b:0),f=Math.min(a.selectedDay,this._getDaysInMonth(d,e))+(c=="D"?b:0),g=this._restrictMinMax(a,this._daylightSavingAdjust(new Date(d,e,f)));a.selectedDay=g.getDate(),a.drawMonth=a.selectedMonth=g.getMonth(),a.drawYear=a.selectedYear=g.getFullYear(),(c=="M"||c=="Y")&&this._notifyChange(a)},_restrictMinMax:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max"),e=c&&b<c?c:b;e=d&&e>d?d:e;return e},_notifyChange:function(a){var b=this._get(a,"onChangeMonthYear");b&&b.apply(a.input?a.input[0]:null,[a.selectedYear,a.selectedMonth+1,a])},_getNumberOfMonths:function(a){var b=this._get(a,"numberOfMonths");return b==null?[1,1]:typeof b=="number"?[1,b]:b},_getMinMaxDate:function(a,b){return this._determineDate(a,this._get(a,b+"Date"),null)},_getDaysInMonth:function(a,b){return 32-this._daylightSavingAdjust(new Date(a,b,32)).getDate()},_getFirstDayOfMonth:function(a,b){return(new Date(a,b,1)).getDay()},_canAdjustMonth:function(a,b,c,d){var e=this._getNumberOfMonths(a),f=this._daylightSavingAdjust(new Date(c,d+(b<0?b:e[0]*e[1]),1));b<0&&f.setDate(this._getDaysInMonth(f.getFullYear(),f.getMonth()));return this._isInRange(a,f)},_isInRange:function(a,b){var c=this._getMinMaxDate(a,"min"),d=this._getMinMaxDate(a,"max");return(!c||b.getTime()>=c.getTime())&&(!d||b.getTime()<=d.getTime())},_getFormatConfig:function(a){var b=this._get(a,"shortYearCutoff");b=typeof b!="string"?b:(new Date).getFullYear()%100+parseInt(b,10);return{shortYearCutoff:b,dayNamesShort:this._get(a,"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,d){b||(a.currentDay=a.selectedDay,a.currentMonth=a.selectedMonth,a.currentYear=a.selectedYear);var e=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(d,c,b)):this._daylightSavingAdjust(new Date +(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),e,this._getFormatConfig(a))}}),$.fn.datepicker=function(a){if(!this.length)return this;$.datepicker.initialized||($(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv),$.datepicker.initialized=!0);var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return $.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return $.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this[0]].concat(b));return this.each(function(){typeof a=="string"?$.datepicker["_"+a+"Datepicker"].apply($.datepicker,[this].concat(b)):$.datepicker._attachDatepicker(this,a)})},$.datepicker=new Datepicker,$.datepicker.initialized=!1,$.datepicker.uuid=(new Date).getTime(),$.datepicker.version="1.8.17",window["DP_jQuery_"+dpuuid]=$}(jQuery),function(a,b){var c="ui-dialog ui-widget ui-widget-content ui-corner-all ",d={buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},e={maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},f=a.attrFn||{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0,click:!0};a.widget("ui.dialog",{options:{autoOpen:!0,buttons:{},closeOnEscape:!0,closeText:"close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:!1,maxWidth:!1,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",collision:"fit",using:function(b){var c=a(this).css(b).offset().top;c<0&&a(this).css("top",b.top-c)}},resizable:!0,show:null,stack:!0,title:"",width:300,zIndex:1e3},_create:function(){this.originalTitle=this.element.attr("title"),typeof this.originalTitle!="string"&&(this.originalTitle=""),this.options.title=this.options.title||this.originalTitle;var b=this,d=b.options,e=d.title||" ",f=a.ui.dialog.getTitleId(b.element),g=(b.uiDialog=a("<div></div>")).appendTo(document.body).hide().addClass(c+d.dialogClass).css({zIndex:d.zIndex}).attr("tabIndex",-1).css("outline",0).keydown(function(c){d.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}).attr({role:"dialog","aria-labelledby":f}).mousedown(function(a){b.moveToTop(!1,a)}),h=b.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(g),i=(b.uiDialogTitlebar=a("<div></div>")).addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(g),j=a('<a href="#"></a>').addClass("ui-dialog-titlebar-close ui-corner-all").attr("role","button").hover(function(){j.addClass("ui-state-hover")},function(){j.removeClass("ui-state-hover")}).focus(function(){j.addClass("ui-state-focus")}).blur(function(){j.removeClass("ui-state-focus")}).click(function(a){b.close(a);return!1}).appendTo(i),k=(b.uiDialogTitlebarCloseText=a("<span></span>")).addClass("ui-icon ui-icon-closethick").text(d.closeText).appendTo(j),l=a("<span></span>").addClass("ui-dialog-title").attr("id",f).html(e).prependTo(i);a.isFunction(d.beforeclose)&&!a.isFunction(d.beforeClose)&&(d.beforeClose=d.beforeclose),i.find("*").add(i).disableSelection(),d.draggable&&a.fn.draggable&&b._makeDraggable(),d.resizable&&a.fn.resizable&&b._makeResizable(),b._createButtons(d.buttons),b._isOpen=!1,a.fn.bgiframe&&g.bgiframe()},_init:function(){this.options.autoOpen&&this.open()},destroy:function(){var a=this;a.overlay&&a.overlay.destroy(),a.uiDialog.hide(),a.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),a.uiDialog.remove(),a.originalTitle&&a.element.attr("title",a.originalTitle);return a},widget:function(){return this.uiDialog},close:function(b){var c=this,d,e;if(!1!==c._trigger("beforeClose",b)){c.overlay&&c.overlay.destroy(),c.uiDialog.unbind("keypress.ui-dialog"),c._isOpen=!1,c.options.hide?c.uiDialog.hide(c.options.hide,function(){c._trigger("close",b)}):(c.uiDialog.hide(),c._trigger("close",b)),a.ui.dialog.overlay.resize(),c.options.modal&&(d=0,a(".ui-dialog").each(function(){this!==c.uiDialog[0]&&(e=a(this).css("z-index"),isNaN(e)||(d=Math.max(d,e)))}),a.ui.dialog.maxZ=d);return c}},isOpen:function(){return this._isOpen},moveToTop:function(b,c){var d=this,e=d.options,f;if(e.modal&&!b||!e.stack&&!e.modal)return d._trigger("focus",c);e.zIndex>a.ui.dialog.maxZ&&(a.ui.dialog.maxZ=e.zIndex),d.overlay&&(a.ui.dialog.maxZ+=1,d.overlay.$el.css("z-index",a.ui.dialog.overlay.maxZ=a.ui.dialog.maxZ)),f={scrollTop:d.element.scrollTop(),scrollLeft:d.element.scrollLeft()},a.ui.dialog.maxZ+=1,d.uiDialog.css("z-index",a.ui.dialog.maxZ),d.element.attr(f),d._trigger("focus",c);return d},open:function(){if(!this._isOpen){var b=this,c=b.options,d=b.uiDialog;b.overlay=c.modal?new a.ui.dialog.overlay(b):null,b._size(),b._position(c.position),d.show(c.show),b.moveToTop(!0),c.modal&&d.bind("keydown.ui-dialog",function(b){if(b.keyCode===a.ui.keyCode.TAB){var c=a(":tabbable",this),d=c.filter(":first"),e=c.filter(":last");if(b.target===e[0]&&!b.shiftKey){d.focus(1);return!1}if(b.target===d[0]&&b.shiftKey){e.focus(1);return!1}}}),a(b.element.find(":tabbable").get().concat(d.find(".ui-dialog-buttonpane :tabbable").get().concat(d.get()))).eq(0).focus(),b._isOpen=!0,b._trigger("open");return b}},_createButtons:function(b){var c=this,d=!1,e=a("<div></div>").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),g=a("<div></div>").addClass("ui-dialog-buttonset").appendTo(e);c.uiDialog.find(".ui-dialog-buttonpane").remove(),typeof b=="object"&&b!==null&&a.each(b,function(){return!(d=!0)}),d&&(a.each(b,function(b,d){d=a.isFunction(d)?{click:d,text:b}:d;var e=a('<button type="button"></button>').click(function(){d.click.apply(c.element[0],arguments)}).appendTo(g);a.each(d,function(a,b){a!=="click"&&(a in f?e[a](b):e.attr(a,b))}),a.fn.button&&e.button()}),e.appendTo(c.uiDialog))},_makeDraggable:function(){function f(a){return{position:a.position,offset:a.offset}}var b=this,c=b.options,d=a(document),e;b.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(d,g){e=c.height==="auto"?"auto":a(this).height(),a(this).height(a(this).height()).addClass("ui-dialog-dragging"),b._trigger("dragStart",d,f(g))},drag:function(a,c){b._trigger("drag",a,f(c))},stop:function(g,h){c.position=[h.position.left-d.scrollLeft(),h.position.top-d.scrollTop()],a(this).removeClass("ui-dialog-dragging").height(e),b._trigger("dragStop",g,f(h)),a.ui.dialog.overlay.resize()}})},_makeResizable:function(c){function h(a){return{originalPosition:a.originalPosition,originalSize:a.originalSize,position:a.position,size:a.size}}c=c===b?this.options.resizable:c;var d=this,e=d.options,f=d.uiDialog.css("position"),g=typeof c=="string"?c:"n,e,s,w,se,sw,ne,nw";d.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:d.element,maxWidth:e.maxWidth,maxHeight:e.maxHeight,minWidth:e.minWidth,minHeight:d._minHeight(),handles:g,start:function(b,c){a(this).addClass("ui-dialog-resizing"),d._trigger("resizeStart",b,h(c))},resize:function(a,b){d._trigger("resize",a,h(b))},stop:function(b,c){a(this).removeClass("ui-dialog-resizing"),e.height=a(this).height(),e.width=a(this).width(),d._trigger("resizeStop",b,h(c)),a.ui.dialog.overlay.resize()}}).css("position",f).find(".ui-resizable-se").addClass("ui-icon ui-icon-grip-diagonal-se")},_minHeight:function(){var a=this.options;return a.height==="auto"?a.minHeight:Math.min(a.minHeight,a.height)},_position:function(b){var c=[],d=[0,0],e;if(b){if(typeof b=="string"||typeof b=="object"&&"0"in b)c=b.split?b.split(" "):[b[0],b[1]],c.length===1&&(c[1]=c[0]),a.each(["left","top"],function(a,b){+c[a]===c[a]&&(d[a]=c[a],c[a]=b)}),b={my:c.join(" "),at:c.join(" "),offset:d.join(" ")};b=a.extend({},a.ui.dialog.prototype.options.position,b)}else b=a.ui.dialog.prototype.options.position;e=this.uiDialog.is(":visible"),e||this.uiDialog.show(),this.uiDialog.css({top:0,left:0}).position(a.extend({of:window},b)),e||this.uiDialog.hide()},_setOptions:function(b){var c=this,f={},g=!1;a.each(b,function(a,b){c._setOption(a,b),a in d&&(g=!0),a in e&&(f[a]=b)}),g&&this._size(),this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option",f)},_setOption:function(b,d){var e=this,f=e.uiDialog;switch(b){case"beforeclose":b="beforeClose";break;case"buttons":e._createButtons(d);break;case"closeText":e.uiDialogTitlebarCloseText.text(""+d);break;case"dialogClass":f.removeClass(e.options.dialogClass).addClass(c+d);break;case"disabled":d?f.addClass("ui-dialog-disabled"):f.removeClass("ui-dialog-disabled");break;case"draggable":var g=f.is(":data(draggable)");g&&!d&&f.draggable("destroy"),!g&&d&&e._makeDraggable();break;case"position":e._position(d);break;case"resizable":var h=f.is(":data(resizable)");h&&!d&&f.resizable("destroy"),h&&typeof d=="string"&&f.resizable("option","handles",d),!h&&d!==!1&&e._makeResizable(d);break;case"title":a(".ui-dialog-title",e.uiDialogTitlebar).html(""+(d||" "))}a.Widget.prototype._setOption.apply(e,arguments)},_size:function(){var b=this.options,c,d,e=this.uiDialog.is(":visible");this.element.show().css({width:"auto",minHeight:0,height:0}),b.minWidth>b.width&&(b.width=b.minWidth),c=this.uiDialog.css({height:"auto",width:b.width}).height(),d=Math.max(0,b.minHeight-c);if(b.height==="auto")if(a.support.minHeight)this.element.css({minHeight:d,height:"auto"});else{this.uiDialog.show();var f=this.element.css("height","auto").height();e||this.uiDialog.hide(),this.element.height(Math.max(f,d))}else this.element.height(Math.max(b.height-c,0));this.uiDialog.is(":data(resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())}}),a.extend(a.ui.dialog,{version:"1.8.17",uuid:0,maxZ:0,getTitleId:function(a){var b=a.attr("id");b||(this.uuid+=1,b=this.uuid);return"ui-dialog-title-"+b},overlay:function(b){this.$el=a.ui.dialog.overlay.create(b)}}),a.extend(a.ui.dialog.overlay,{instances:[],oldInstances:[],maxZ:0,events:a.map("focus,mousedown,mouseup,keydown,keypress,click".split(","),function(a){return a+".dialog-overlay"}).join(" "),create:function(b){this.instances.length===0&&(setTimeout(function(){a.ui.dialog.overlay.instances.length&&a(document).bind(a.ui.dialog.overlay.events,function(b){if(a(b.target).zIndex()<a.ui.dialog.overlay.maxZ)return!1})},1),a(document).bind("keydown.dialog-overlay",function(c){b.options.closeOnEscape&&!c.isDefaultPrevented()&&c.keyCode&&c.keyCode===a.ui.keyCode.ESCAPE&&(b.close(c),c.preventDefault())}),a(window).bind("resize.dialog-overlay",a.ui.dialog.overlay.resize));var c=(this.oldInstances.pop()||a("<div></div>").addClass("ui-widget-overlay")).appendTo(document.body).css({width:this.width(),height:this.height()});a.fn.bgiframe&&c.bgiframe(),this.instances.push(c);return c},destroy:function(b){var c=a.inArray(b,this.instances);c!=-1&&this.oldInstances.push(this.instances.splice(c,1)[0]),this.instances.length===0&&a([document,window]).unbind(".dialog-overlay"),b.remove();var d=0;a.each(this.instances,function(){d=Math.max(d,this.css("z-index"))}),this.maxZ=d},height:function(){var b,c;if(a.browser.msie&&a.browser.version<7){b=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),c=Math.max(document.documentElement.offsetHeight,document.body.offsetHeight);return b<c?a(window).height()+"px":b+"px"}return a(document).height()+"px"},width:function(){var b,c;if(a.browser.msie){b=Math.max(document.documentElement.scrollWidth,document.body.scrollWidth),c=Math.max(document.documentElement.offsetWidth,document.body.offsetWidth);return b<c?a(window).width()+"px":b+"px"}return a(document).width()+"px"},resize:function(){var b=a([]);a.each(a.ui.dialog.overlay.instances,function(){b=b.add(this)}),b.css({width:0,height:0}).css({width:a.ui.dialog.overlay.width(),height:a.ui.dialog.overlay.height()})}}),a.extend(a.ui.dialog.overlay.prototype,{destroy:function(){a.ui.dialog.overlay.destroy(this.$el)}})}(jQuery),function(a,b){a.ui=a.ui||{};var c=/left|center|right/,d=/top|center|bottom/,e="center",f={},g=a.fn.position,h=a.fn.offset;a.fn.position=function(b){if(!b||!b.of)return g.apply(this,arguments);b=a.extend({},b);var h=a(b.of),i=h[0],j=(b.collision||"flip").split(" "),k=b.offset?b.offset.split(" "):[0,0],l,m,n;i.nodeType===9?(l=h.width(),m=h.height(),n={top:0,left:0}):i.setTimeout?(l=h.width(),m=h.height(),n={top:h.scrollTop(),left:h.scrollLeft()}):i.preventDefault?(b.at="left top",l=m=0,n={top:b.of.pageY,left:b.of.pageX}):(l=h.outerWidth(),m=h.outerHeight(),n=h.offset()),a.each(["my","at"],function(){var a=(b[this]||"").split(" ");a.length===1&&(a=c.test(a[0])?a.concat([e]):d.test(a[0])?[e].concat(a):[e,e]),a[0]=c.test(a[0])?a[0]:e,a[1]=d.test(a[1])?a[1]:e,b[this]=a}),j.length===1&&(j[1]=j[0]),k[0]=parseInt(k[0],10)||0,k.length===1&&(k[1]=k[0]),k[1]=parseInt(k[1],10)||0,b.at[0]==="right"?n.left+=l:b.at[0]===e&&(n.left+=l/2),b.at[1]==="bottom"?n.top+=m:b.at[1]===e&&(n.top+=m/2),n.left+=k[0],n.top+=k[1];return this.each(function(){var c=a(this),d=c.outerWidth(),g=c.outerHeight(),h=parseInt(a.curCSS(this,"marginLeft",!0))||0,i=parseInt(a.curCSS(this,"marginTop",!0))||0,o=d+h+(parseInt(a.curCSS(this,"marginRight",!0))||0),p=g+i+(parseInt(a.curCSS(this,"marginBottom",!0))||0),q=a.extend({},n),r;b.my[0]==="right"?q.left-=d:b.my[0]===e&&(q.left-=d/2),b.my[1]==="bottom"?q.top-=g:b.my[1]===e&&(q.top-=g/2),f.fractions||(q.left=Math.round(q.left),q.top=Math.round(q.top)),r={left:q.left-h,top:q.top-i},a.each(["left","top"],function(c,e){a.ui.position[j[c]]&&a.ui.position[j[c]][e](q,{targetWidth:l,targetHeight:m,elemWidth:d,elemHeight:g,collisionPosition:r,collisionWidth:o,collisionHeight:p,offset:k,my:b.my,at:b.at})}),a.fn.bgiframe&&c.bgiframe(),c.offset(a.extend(q,{using:b.using}))})},a.ui.position={fit:{left:function(b,c){var d=a(window),e=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft();b.left=e>0?b.left-e:Math.max(b.left-c.collisionPosition.left,b.left)},top:function(b,c){var d=a(window),e=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop();b.top=e>0?b.top-e:Math.max(b.top-c.collisionPosition.top,b.top)}},flip:{left:function(b,c){if(c.at[0]!==e){var d=a(window),f=c.collisionPosition.left+c.collisionWidth-d.width()-d.scrollLeft(),g=c.my[0]==="left"?-c.elemWidth:c.my[0]==="right"?c.elemWidth:0,h=c.at[0]==="left"?c.targetWidth:-c.targetWidth,i=-2*c.offset[0];b.left+=c.collisionPosition.left<0?g+h+i:f>0?g+h+i:0}},top:function(b,c){if(c.at[1]!==e){var d=a(window),f=c.collisionPosition.top+c.collisionHeight-d.height()-d.scrollTop(),g=c.my[1]==="top"?-c.elemHeight:c.my[1]==="bottom"?c.elemHeight:0,h=c.at[1]==="top"?c.targetHeight:-c.targetHeight,i=-2*c.offset[1];b.top+=c.collisionPosition.top<0?g+h+i:f>0?g+h+i:0}}}},a.offset.setOffset||(a.offset.setOffset=function(b,c){/static/.test(a.curCSS(b,"position"))&&(b.style.position="relative");var d=a(b),e=d.offset(),f=parseInt(a.curCSS(b,"top",!0),10)||0,g=parseInt(a.curCSS(b,"left",!0),10)||0,h={top:c.top-e.top+f,left:c.left-e.left+g};"using"in c?c.using.call(b,h):d.css(h)},a.fn.offset=function(b){var c=this[0];if(!c||!c.ownerDocument)return null;if(b)return this.each(function(){a.offset.setOffset(this,b)});return h.call(this)}),function(){var b=document.getElementsByTagName("body")[0],c=document.createElement("div"),d,e,g,h,i;d=document.createElement(b?"div":"body"),g={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},b&&jQuery.extend(g,{position:"absolute",left:"-1000px",top:"-1000px"});for(var j in g)d.style[j]=g[j];d.appendChild(c),e=b||document.documentElement,e.insertBefore(d,e.firstChild),c.style.cssText="position: absolute; left: 10.7432222px; top: 10.432325px; height: 30px; width: 201px;",h=a(c).offset(function(a,b){return b}).offset(),d.innerHTML="",e.removeChild(d),i=h.top+h.left+(b?2e3:0),f.fractions=i>21&&i<22}()}(jQuery),function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove(),a.Widget.prototype.destroy.apply(this,arguments)},value:function(a){if(a===b)return this._value();this._setOption("value",a);return this},_setOption:function(b,c){b==="value"&&(this.options.value=c,this._refreshValue(),this._value()===this.options.max&&this._trigger("complete")),a.Widget.prototype._setOption.apply(this,arguments)},_value:function(){var a=this.options.value;typeof a!="number"&&(a=0);return Math.min(this.options.max,Math.max(this.min,a))},_percentage:function(){return 100*this._value()/this.options.max},_refreshValue:function(){var a=this.value(),b=this._percentage();this.oldValue!==a&&(this.oldValue=a,this._trigger("change")),this.valueDiv.toggle(a>this.min).toggleClass("ui-corner-right",a===this.options.max).width(b.toFixed(0)+"%"),this.element.attr("aria-valuenow",a)}}),a.extend(a.ui.progressbar,{version:"1.8.17"})}(jQuery),function(a,b){var c=5;a.widget("ui.slider",a.ui.mouse,{widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var b=this,d=this.options,e=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),f="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",g=d.values&&d.values.length||1,h=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0,this._handleIndex=null,this._detectOrientation(),this._mouseInit(),this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget"+" ui-widget-content"+" ui-corner-all"+(d.disabled?" ui-slider-disabled ui-disabled":"")),this.range=a([]),d.range&&(d.range===!0&&(d.values||(d.values=[this._valueMin(),this._valueMin()]),d.values.length&&d.values.length!==2&&(d.values=[d.values[0],d.values[0]])),this.range=a("<div></div>").appendTo(this.element).addClass("ui-slider-range ui-widget-header"+(d.range==="min"||d.range==="max"?" ui-slider-range-"+d.range:"")));for(var i=e.length;i<g;i+=1)h.push(f);this.handles=e.add(a(h.join("")).appendTo(b.element)),this.handle=this.handles.eq(0),this.handles.add(this.range).filter("a").click(function(a){a.preventDefault()}).hover(function(){d.disabled||a(this).addClass("ui-state-hover")},function(){a(this).removeClass("ui-state-hover")}).focus(function(){d.disabled?a(this).blur():(a(".ui-slider .ui-state-focus").removeClass("ui-state-focus"),a(this).addClass("ui-state-focus"))}).blur(function(){a(this).removeClass("ui-state-focus")}),this.handles.each(function(b){a(this).data("index.ui-slider-handle",b)}),this.handles.keydown(function(d){var e=!0,f=a(this).data("index.ui-slider-handle"),g,h,i,j;if(!b.options.disabled){switch(d.keyCode){case a.ui.keyCode.HOME:case a.ui.keyCode.END:case a.ui.keyCode.PAGE_UP:case a.ui.keyCode.PAGE_DOWN:case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:e=!1;if(!b._keySliding){b._keySliding=!0,a(this).addClass("ui-state-active"),g=b._start(d,f);if(g===!1)return}}j=b.options.step,b.options.values&&b.options.values.length?h=i=b.values(f):h=i=b.value();switch(d.keyCode){case a.ui.keyCode.HOME:i=b._valueMin();break;case a.ui.keyCode.END:i=b._valueMax();break;case a.ui.keyCode.PAGE_UP:i=b._trimAlignValue(h+(b._valueMax()-b._valueMin())/c);break;case a.ui.keyCode.PAGE_DOWN:i=b._trimAlignValue(h-(b._valueMax()-b._valueMin())/c);break;case a.ui.keyCode.UP:case a.ui.keyCode.RIGHT:if(h===b._valueMax())return;i=b._trimAlignValue(h+j);break;case a.ui.keyCode.DOWN:case a.ui.keyCode.LEFT:if(h===b._valueMin())return;i=b._trimAlignValue(h-j)}b._slide(d,f,i);return e}}).keyup(function(c){var d=a(this).data("index.ui-slider-handle");b._keySliding&&(b._keySliding=!1,b._stop(c,d),b._change(c,d),a(this).removeClass("ui-state-active"))}),this._refreshValue(),this._animateOff=!1},destroy:function(){this.handles.remove(),this.range.remove(),this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider"),this._mouseDestroy();return this},_mouseCapture:function(b){var c=this.options,d,e,f,g,h,i,j,k,l;if(c.disabled)return!1;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()},this.elementOffset=this.element.offset(),d={x:b.pageX,y:b.pageY},e=this._normValueFromMouse(d),f=this._valueMax()-this._valueMin()+1,h=this,this.handles.each(function(b){var c=Math.abs(e-h.values(b));f>c&&(f=c,g=a(this),i=b)}),c.range===!0&&this.values(1)===c.min&&(i+=1,g=a(this.handles[i])),j=this._start(b,i);if(j===!1)return!1;this._mouseSliding=!0,h._handleIndex=i,g.addClass("ui-state-active").focus(),k=g.offset(),l=!a(b.target).parents().andSelf().is(".ui-slider-handle"),this._clickOffset=l?{left:0,top:0}:{left:b.pageX-k.left-g.width()/2,top:b.pageY-k.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)},this.handles.hasClass("ui-state-hover")||this._slide(b,i,e),this._animateOff=!0;return!0},_mouseStart:function(a){return!0},_mouseDrag:function(a){var b={x:a.pageX,y:a.pageY},c=this._normValueFromMouse(b);this._slide(a,this._handleIndex,c);return!1},_mouseStop:function(a){this.handles.removeClass("ui-state-active"),this._mouseSliding=!1,this._stop(a,this._handleIndex),this._change(a,this._handleIndex),this._handleIndex=null,this._clickOffset=null,this._animateOff=!1;return!1},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b,c,d,e,f;this.orientation==="horizontal"?(b=this.elementSize.width,c=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)):(b=this.elementSize.height,c=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)),d=c/b,d>1&&(d=1),d<0&&(d=0),this.orientation==="vertical"&&(d=1-d),e=this._valueMax()-this._valueMin(),f=this._valueMin()+d*e;return this._trimAlignValue(f)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values());return this._trigger("start",a,c)},_slide:function(a,b,c){var d,e,f;this.options.values&&this.options.values.length?(d=this.values(b?0:1),this.options.values.length===2&&this.options.range===!0&&(b===0&&c>d||b===1&&c<d)&&(c=d),c!==this.values(b)&&(e=this.values(),e[b]=c,f=this._trigger("slide",a,{handle:this.handles[b],value:c,values:e}),d=this.values(b?0:1),f!==!1&&this.values(b,c,!0))):c!==this.value()&&(f=this._trigger("slide",a,{handle:this.handles[b],value:c}),f!==!1&&this.value(c))},_stop:function(a,b){var c={handle:this.handles[b],value:this.value()};this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("stop",a,c)},_change:function(a,b){if(!this._keySliding&&!this._mouseSliding){var c={handle:this.handles[b],value:this.value()};this.options.values&&this.options.values.length&&(c.value=this.values(b),c.values=this.values()),this._trigger("change",a,c)}},value:function(a){if(arguments.length)this.options.value=this._trimAlignValue(a),this._refreshValue(),this._change(null,0);else return this._value()},values:function(b,c){var d,e,f;if(arguments.length>1)this.options.values[b]=this._trimAlignValue(c),this._refreshValue(),this._change(null,b);else{if(!arguments.length)return this._values();if(!a.isArray(arguments[0]))return this.options.values&&this.options.values.length?this._values(b):this.value();d=this.options.values,e=arguments[0];for(f=0;f<d.length;f+=1)d[f]=this._trimAlignValue(e[f]),this._change(null,f);this._refreshValue()}},_setOption:function(b,c){var d,e=0;a.isArray(this.options.values)&&(e=this.options.values.length),a.Widget.prototype._setOption.apply(this,arguments);switch(b){case"disabled":c?(this.handles.filter(".ui-state-focus").blur(),this.handles.removeClass("ui-state-hover"),this.handles.propAttr("disabled",!0),this.element.addClass("ui-disabled")):(this.handles.propAttr("disabled",!1),this.element.removeClass("ui-disabled"));break;case"orientation":this._detectOrientation(),this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation),this._refreshValue();break;case"value":this._animateOff=!0,this._refreshValue(),this._change(null,0),this._animateOff=!1;break;case"values":this._animateOff=!0,this._refreshValue();for(d=0;d<e;d+=1)this._change(null,d);this._animateOff=!1}},_value:function(){var a=this.options.value;a=this._trimAlignValue(a);return a},_values:function(a){var b,c,d;if(arguments.length){b=this.options.values[a],b=this._trimAlignValue(b);return b}c=this.options.values.slice();for(d=0;d<c.length;d+=1)c[d]=this._trimAlignValue(c[d]);return c},_trimAlignValue:function(a){if(a<=this._valueMin())return this._valueMin();if(a>=this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=(a-this._valueMin())%b,d=a-c;Math.abs(c)*2>=b&&(d+=c>0?b:-b);return parseFloat(d.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var b=this.options.range,c=this.options,d=this,e=this._animateOff?!1:c.animate,f,g={},h,i,j,k;this.options.values&&this.options.values.length?this.handles.each(function(b,i){f=(d.values(b)-d._valueMin())/(d._valueMax()-d._valueMin())*100,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",a(this).stop(1,1)[e?"animate":"css"](g,c.animate),d.options.range===!0&&(d.orientation==="horizontal"?(b===0&&d.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({width:f-h+"%"},{queue:!1,duration:c.animate})):(b===0&&d.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},c.animate),b===1&&d.range[e?"animate":"css"]({height:f-h+"%"},{queue:!1,duration:c.animate}))),h=f}):(i=this.value(),j=this._valueMin(),k=this._valueMax(),f=k!==j?(i-j)/(k-j)*100:0,g[d.orientation==="horizontal"?"left":"bottom"]=f+"%",this.handle.stop(1,1)[e?"animate":"css"](g,c.animate),b==="min"&&this.orientation==="horizontal"&&this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},c.animate),b==="max"&&this.orientation==="horizontal"&&this.range[e?"animate":"css"]({width:100-f+"%"},{queue:!1,duration:c.animate}),b==="min"&&this.orientation==="vertical"&&this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},c.animate),b==="max"&&this.orientation==="vertical"&&this.range[e?"animate":"css"]({height:100-f+"%"},{queue:!1,duration:c.animate}))}}),a.extend(a.ui.slider,{version:"1.8.17"})}(jQuery),function(a,b){function f(){return++d}function e(){return++c}var c=0,d=0;a.widget("ui.tabs",{options:{add:null,ajaxOptions:null,cache:!1,cookie:null,collapsible:!1,disable:null,disabled:[],enable:null,event:"click",fx:null,idPrefix:"ui-tabs-",load:null,panelTemplate:"<div></div>",remove:null,select:null,show:null,spinner:"<em>Loading…</em>",tabTemplate:"<li><a href='#{href}'><span>#{label}</span></a></li>"},_create:function(){this._tabify(!0)},_setOption:function(a,b){if(a=="selected"){if(this.options.collapsible&&b==this.options.selected)return;this.select(b)}else this.options[a]=b,this._tabify()},_tabId:function(a){return a.title&&a.title.replace(/\s/g,"_").replace(/[^\w\u00c0-\uFFFF-]/g,"")||this.options.idPrefix+e()},_sanitizeSelector:function(a){return a.replace(/:/g,"\\:")},_cookie:function(){var b=this.cookie||(this.cookie=this.options.cookie.name||"ui-tabs-"+f());return a.cookie.apply(null,[b].concat(a.makeArray(arguments)))},_ui:function(a,b){return{tab:a,panel:b,index:this.anchors.index(a)}},_cleanup:function(){this.lis.filter(".ui-state-processing").removeClass("ui-state-processing").find("span:data(label.tabs)").each(function(){var b=a(this);b.html(b.data("label.tabs")).removeData("label.tabs")})},_tabify:function(c){function m(b,c){b.css("display",""),!a.support.opacity&&c.opacity&&b[0].style.removeAttribute("filter")}var d=this,e=this.options,f=/^#.+/;this.list=this.element.find("ol,ul").eq(0),this.lis=a(" > li:has(a[href])",this.list),this.anchors=this.lis.map(function(){return a("a",this)[0]}),this.panels=a([]),this.anchors.each(function(b,c){var g=a(c).attr("href"),h=g.split("#")[0],i;h&&(h===location.toString().split("#")[0]||(i=a("base")[0])&&h===i.href)&&(g=c.hash,c.href=g);if(f.test(g))d.panels=d.panels.add(d.element.find(d._sanitizeSelector(g)));else if(g&&g!=="#"){a.data(c,"href.tabs",g),a.data(c,"load.tabs",g.replace(/#.*$/,""));var j=d._tabId(c);c.href="#"+j;var k=d.element.find("#"+j);k.length||(k=a(e.panelTemplate).attr("id",j).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").insertAfter(d.panels[b-1]||d.list),k.data("destroy.tabs",!0)),d.panels=d.panels.add(k)}else e.disabled.push(b)}),c?(this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all"),this.list.addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.lis.addClass("ui-state-default ui-corner-top"),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom"),e.selected===b?(location.hash&&this.anchors.each(function(a,b){if(b.hash==location.hash){e.selected=a;return!1}}),typeof e.selected!="number"&&e.cookie&&(e.selected=parseInt(d._cookie(),10)),typeof e.selected!="number"&&this.lis.filter(".ui-tabs-selected").length&&(e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected"))),e.selected=e.selected||(this.lis.length?0:-1)):e.selected===null&&(e.selected=-1),e.selected=e.selected>=0&&this.anchors[e.selected]||e.selected<0?e.selected:0,e.disabled=a.unique(e.disabled.concat(a.map(this.lis.filter(".ui-state-disabled"),function(a,b){return d.lis.index(a)}))).sort(),a.inArray(e.selected,e.disabled)!=-1&&e.disabled.splice(a.inArray(e.selected,e.disabled),1),this.panels.addClass("ui-tabs-hide"),this.lis.removeClass("ui-tabs-selected ui-state-active"),e.selected>=0&&this.anchors.length&&(d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash)).removeClass("ui-tabs-hide"),this.lis.eq(e.selected).addClass("ui-tabs-selected ui-state-active"),d.element.queue("tabs",function(){d._trigger("show",null,d._ui(d.anchors[e.selected],d.element.find(d._sanitizeSelector(d.anchors[e.selected].hash))[0]))}),this.load(e.selected)),a(window).bind("unload",function(){d.lis.add(d.anchors).unbind(".tabs"),d.lis=d.anchors=d.panels=null})):e.selected=this.lis.index(this.lis.filter(".ui-tabs-selected")),this.element[e.collapsible?"addClass":"removeClass"]("ui-tabs-collapsible"),e.cookie&&this._cookie(e.selected,e.cookie);for(var g=0,h;h=this.lis[g];g++)a(h)[a.inArray(g,e.disabled)!=-1&&!a(h).hasClass("ui-tabs-selected")?"addClass":"removeClass"]("ui-state-disabled");e.cache===!1&&this.anchors.removeData("cache.tabs"),this.lis.add(this.anchors).unbind(".tabs");if(e.event!=="mouseover"){var i=function(a,b){b.is(":not(.ui-state-disabled)")&&b.addClass("ui-state-"+a)},j=function(a,b){b.removeClass("ui-state-"+a)};this.lis.bind("mouseover.tabs",function(){i("hover",a(this))}),this.lis.bind("mouseout.tabs",function(){j("hover",a(this))}),this.anchors.bind("focus.tabs",function(){i("focus",a(this).closest("li"))}),this.anchors.bind("blur.tabs",function(){j("focus",a(this).closest("li"))})}var k,l;e.fx&&(a.isArray(e.fx)?(k=e.fx[0],l=e.fx[1]):k=l=e.fx);var n=l?function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.hide().removeClass("ui-tabs-hide").animate(l,l.duration||"normal",function(){m(c,l),d._trigger("show",null,d._ui(b,c[0]))})}:function(b,c){a(b).closest("li").addClass("ui-tabs-selected ui-state-active"),c.removeClass("ui-tabs-hide"),d._trigger("show",null,d._ui(b,c[0]))},o=k?function(a,b){b.animate(k,k.duration||"normal",function(){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),m(b,k),d.element.dequeue("tabs")})}:function(a,b,c){d.lis.removeClass("ui-tabs-selected ui-state-active"),b.addClass("ui-tabs-hide"),d.element.dequeue("tabs")};this.anchors.bind(e.event+".tabs",function(){var b=this,c=a(b).closest("li"),f=d.panels.filter(":not(.ui-tabs-hide)"),g=d.element.find(d._sanitizeSelector(b.hash));if(c.hasClass("ui-tabs-selected")&&!e.collapsible||c.hasClass("ui-state-disabled")||c.hasClass("ui-state-processing" +)||d.panels.filter(":animated").length||d._trigger("select",null,d._ui(this,g[0]))===!1){this.blur();return!1}e.selected=d.anchors.index(this),d.abort();if(e.collapsible){if(c.hasClass("ui-tabs-selected")){e.selected=-1,e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){o(b,f)}).dequeue("tabs"),this.blur();return!1}if(!f.length){e.cookie&&d._cookie(e.selected,e.cookie),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this)),this.blur();return!1}}e.cookie&&d._cookie(e.selected,e.cookie);if(g.length)f.length&&d.element.queue("tabs",function(){o(b,f)}),d.element.queue("tabs",function(){n(b,g)}),d.load(d.anchors.index(this));else throw"jQuery UI Tabs: Mismatching fragment identifier.";a.browser.msie&&this.blur()}),this.anchors.bind("click.tabs",function(){return!1})},_getIndex:function(a){typeof a=="string"&&(a=this.anchors.index(this.anchors.filter("[href$="+a+"]")));return a},destroy:function(){var b=this.options;this.abort(),this.element.unbind(".tabs").removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible").removeData("tabs"),this.list.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all"),this.anchors.each(function(){var b=a.data(this,"href.tabs");b&&(this.href=b);var c=a(this).unbind(".tabs");a.each(["href","load","cache"],function(a,b){c.removeData(b+".tabs")})}),this.lis.unbind(".tabs").add(this.panels).each(function(){a.data(this,"destroy.tabs")?a(this).remove():a(this).removeClass(["ui-state-default","ui-corner-top","ui-tabs-selected","ui-state-active","ui-state-hover","ui-state-focus","ui-state-disabled","ui-tabs-panel","ui-widget-content","ui-corner-bottom","ui-tabs-hide"].join(" "))}),b.cookie&&this._cookie(null,b.cookie);return this},add:function(c,d,e){e===b&&(e=this.anchors.length);var f=this,g=this.options,h=a(g.tabTemplate.replace(/#\{href\}/g,c).replace(/#\{label\}/g,d)),i=c.indexOf("#")?this._tabId(a("a",h)[0]):c.replace("#","");h.addClass("ui-state-default ui-corner-top").data("destroy.tabs",!0);var j=f.element.find("#"+i);j.length||(j=a(g.panelTemplate).attr("id",i).data("destroy.tabs",!0)),j.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"),e>=this.lis.length?(h.appendTo(this.list),j.appendTo(this.list[0].parentNode)):(h.insertBefore(this.lis[e]),j.insertBefore(this.panels[e])),g.disabled=a.map(g.disabled,function(a,b){return a>=e?++a:a}),this._tabify(),this.anchors.length==1&&(g.selected=0,h.addClass("ui-tabs-selected ui-state-active"),j.removeClass("ui-tabs-hide"),this.element.queue("tabs",function(){f._trigger("show",null,f._ui(f.anchors[0],f.panels[0]))}),this.load(0)),this._trigger("add",null,this._ui(this.anchors[e],this.panels[e]));return this},remove:function(b){b=this._getIndex(b);var c=this.options,d=this.lis.eq(b).remove(),e=this.panels.eq(b).remove();d.hasClass("ui-tabs-selected")&&this.anchors.length>1&&this.select(b+(b+1<this.anchors.length?1:-1)),c.disabled=a.map(a.grep(c.disabled,function(a,c){return a!=b}),function(a,c){return a>=b?--a:a}),this._tabify(),this._trigger("remove",null,this._ui(d.find("a")[0],e[0]));return this},enable:function(b){b=this._getIndex(b);var c=this.options;if(a.inArray(b,c.disabled)!=-1){this.lis.eq(b).removeClass("ui-state-disabled"),c.disabled=a.grep(c.disabled,function(a,c){return a!=b}),this._trigger("enable",null,this._ui(this.anchors[b],this.panels[b]));return this}},disable:function(a){a=this._getIndex(a);var b=this,c=this.options;a!=c.selected&&(this.lis.eq(a).addClass("ui-state-disabled"),c.disabled.push(a),c.disabled.sort(),this._trigger("disable",null,this._ui(this.anchors[a],this.panels[a])));return this},select:function(a){a=this._getIndex(a);if(a==-1)if(this.options.collapsible&&this.options.selected!=-1)a=this.options.selected;else return this;this.anchors.eq(a).trigger(this.options.event+".tabs");return this},load:function(b){b=this._getIndex(b);var c=this,d=this.options,e=this.anchors.eq(b)[0],f=a.data(e,"load.tabs");this.abort();if(!f||this.element.queue("tabs").length!==0&&a.data(e,"cache.tabs"))this.element.dequeue("tabs");else{this.lis.eq(b).addClass("ui-state-processing");if(d.spinner){var g=a("span",e);g.data("label.tabs",g.html()).html(d.spinner)}this.xhr=a.ajax(a.extend({},d.ajaxOptions,{url:f,success:function(f,g){c.element.find(c._sanitizeSelector(e.hash)).html(f),c._cleanup(),d.cache&&a.data(e,"cache.tabs",!0),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.success(f,g)}catch(h){}},error:function(a,f,g){c._cleanup(),c._trigger("load",null,c._ui(c.anchors[b],c.panels[b]));try{d.ajaxOptions.error(a,f,b,e)}catch(g){}}})),c.element.dequeue("tabs");return this}},abort:function(){this.element.queue([]),this.panels.stop(!1,!0),this.element.queue("tabs",this.element.queue("tabs").splice(-2,2)),this.xhr&&(this.xhr.abort(),delete this.xhr),this._cleanup();return this},url:function(a,b){this.anchors.eq(a).removeData("cache.tabs").data("load.tabs",b);return this},length:function(){return this.anchors.length}}),a.extend(a.ui.tabs,{version:"1.8.17"}),a.extend(a.ui.tabs.prototype,{rotation:null,rotate:function(a,b){var c=this,d=this.options,e=c._rotate||(c._rotate=function(b){clearTimeout(c.rotation),c.rotation=setTimeout(function(){var a=d.selected;c.select(++a<c.anchors.length?a:0)},a),b&&b.stopPropagation()}),f=c._unrotate||(c._unrotate=b?function(a){t=d.selected,e()}:function(a){a.clientX&&c.rotate(null)});a?(this.element.bind("tabsshow",e),this.anchors.bind(d.event+".tabs",f),e()):(clearTimeout(c.rotation),this.element.unbind("tabsshow",e),this.anchors.unbind(d.event+".tabs",f),delete this._rotate,delete this._unrotate);return this}})}(jQuery)
\ No newline at end of file diff --git a/framework/web/js/source/rating/delete.gif b/framework/web/js/source/rating/delete.gif Binary files differnew file mode 100644 index 0000000..43c6ca8 --- /dev/null +++ b/framework/web/js/source/rating/delete.gif diff --git a/framework/web/js/source/rating/jquery.rating.css b/framework/web/js/source/rating/jquery.rating.css new file mode 100644 index 0000000..45042e8 --- /dev/null +++ b/framework/web/js/source/rating/jquery.rating.css @@ -0,0 +1,12 @@ +/* jQuery.Rating Plugin CSS - http://www.fyneworks.com/jquery/star-rating/ */ +div.rating-cancel,div.star-rating{float:left;width:17px;height:15px;text-indent:-999em;cursor:pointer;display:block;background:transparent;overflow:hidden} +div.rating-cancel,div.rating-cancel a{background:url(delete.gif) no-repeat 0 -16px} +div.star-rating,div.star-rating a{background:url(star.gif) no-repeat 0 0px} +div.rating-cancel a,div.star-rating a{display:block;width:16px;height:100%;background-position:0 0px;border:0} +div.star-rating-on a{background-position:0 -16px!important} +div.star-rating-hover a{background-position:0 -32px} +/* Read Only CSS */ +div.star-rating-readonly a{cursor:default !important} +/* Partial Star CSS */ +div.star-rating{background:transparent!important;overflow:hidden!important} +/* END jQuery.Rating Plugin CSS */
\ No newline at end of file diff --git a/framework/web/js/source/rating/star.gif b/framework/web/js/source/rating/star.gif Binary files differnew file mode 100644 index 0000000..d0948a7 --- /dev/null +++ b/framework/web/js/source/rating/star.gif diff --git a/framework/web/js/source/treeview/images/ajax-loader.gif b/framework/web/js/source/treeview/images/ajax-loader.gif Binary files differnew file mode 100644 index 0000000..bc54585 --- /dev/null +++ b/framework/web/js/source/treeview/images/ajax-loader.gif diff --git a/framework/web/js/source/treeview/images/file.gif b/framework/web/js/source/treeview/images/file.gif Binary files differnew file mode 100644 index 0000000..7e62167 --- /dev/null +++ b/framework/web/js/source/treeview/images/file.gif diff --git a/framework/web/js/source/treeview/images/folder-closed.gif b/framework/web/js/source/treeview/images/folder-closed.gif Binary files differnew file mode 100644 index 0000000..5411078 --- /dev/null +++ b/framework/web/js/source/treeview/images/folder-closed.gif diff --git a/framework/web/js/source/treeview/images/folder.gif b/framework/web/js/source/treeview/images/folder.gif Binary files differnew file mode 100644 index 0000000..2b31631 --- /dev/null +++ b/framework/web/js/source/treeview/images/folder.gif diff --git a/framework/web/js/source/treeview/images/minus.gif b/framework/web/js/source/treeview/images/minus.gif Binary files differnew file mode 100644 index 0000000..47fb7b7 --- /dev/null +++ b/framework/web/js/source/treeview/images/minus.gif diff --git a/framework/web/js/source/treeview/images/plus.gif b/framework/web/js/source/treeview/images/plus.gif Binary files differnew file mode 100644 index 0000000..6906621 --- /dev/null +++ b/framework/web/js/source/treeview/images/plus.gif diff --git a/framework/web/js/source/treeview/images/treeview-black-line.gif b/framework/web/js/source/treeview/images/treeview-black-line.gif Binary files differnew file mode 100644 index 0000000..e549687 --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-black-line.gif diff --git a/framework/web/js/source/treeview/images/treeview-black.gif b/framework/web/js/source/treeview/images/treeview-black.gif Binary files differnew file mode 100644 index 0000000..d549b9f --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-black.gif diff --git a/framework/web/js/source/treeview/images/treeview-default-line.gif b/framework/web/js/source/treeview/images/treeview-default-line.gif Binary files differnew file mode 100644 index 0000000..37114d3 --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-default-line.gif diff --git a/framework/web/js/source/treeview/images/treeview-default.gif b/framework/web/js/source/treeview/images/treeview-default.gif Binary files differnew file mode 100644 index 0000000..a12ac52 --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-default.gif diff --git a/framework/web/js/source/treeview/images/treeview-famfamfam-line.gif b/framework/web/js/source/treeview/images/treeview-famfamfam-line.gif Binary files differnew file mode 100644 index 0000000..6e289ce --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-famfamfam-line.gif diff --git a/framework/web/js/source/treeview/images/treeview-famfamfam.gif b/framework/web/js/source/treeview/images/treeview-famfamfam.gif Binary files differnew file mode 100644 index 0000000..0cb178e --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-famfamfam.gif diff --git a/framework/web/js/source/treeview/images/treeview-gray-line.gif b/framework/web/js/source/treeview/images/treeview-gray-line.gif Binary files differnew file mode 100644 index 0000000..3760044 --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-gray-line.gif diff --git a/framework/web/js/source/treeview/images/treeview-gray.gif b/framework/web/js/source/treeview/images/treeview-gray.gif Binary files differnew file mode 100644 index 0000000..cfb8a2f --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-gray.gif diff --git a/framework/web/js/source/treeview/images/treeview-red-line.gif b/framework/web/js/source/treeview/images/treeview-red-line.gif Binary files differnew file mode 100644 index 0000000..df9e749 --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-red-line.gif diff --git a/framework/web/js/source/treeview/images/treeview-red.gif b/framework/web/js/source/treeview/images/treeview-red.gif Binary files differnew file mode 100644 index 0000000..3bbb3a1 --- /dev/null +++ b/framework/web/js/source/treeview/images/treeview-red.gif diff --git a/framework/web/js/source/treeview/jquery.treeview.css b/framework/web/js/source/treeview/jquery.treeview.css new file mode 100644 index 0000000..5d3200e --- /dev/null +++ b/framework/web/js/source/treeview/jquery.treeview.css @@ -0,0 +1,74 @@ +.treeview, .treeview ul { + padding: 0; + margin: 0; + list-style: none; +} + +.treeview ul { + background-color: white; + margin-top: 4px; +} + +.treeview .hitarea { + background: url(images/treeview-default.gif) -64px -25px no-repeat; + height: 16px; + width: 16px; + margin-left: -16px; + float: left; + cursor: pointer; +} +/* fix for IE6 */ +* html .hitarea { + display: inline; + float:none; +} + +.treeview li { + margin: 0; + padding: 3px 0pt 3px 16px; +} + +.treeview a.selected { + background-color: #eee; +} + +#treecontrol { margin: 1em 0; display: none; } + +.treeview .hover { color: red; cursor: pointer; } + +.treeview li { background: url(images/treeview-default-line.gif) 0 0 no-repeat; } +.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; } + +.treeview .expandable-hitarea { background-position: -80px -3px; } + +.treeview li.last { background-position: 0 -1766px } +.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(images/treeview-default.gif); } +.treeview li.lastCollapsable { background-position: 0 -111px } +.treeview li.lastExpandable { background-position: -32px -67px } + +.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; } + +.treeview-red li { background-image: url(images/treeview-red-line.gif); } +.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(images/treeview-red.gif); } + +.treeview-black li { background-image: url(images/treeview-black-line.gif); } +.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(images/treeview-black.gif); } + +.treeview-gray li { background-image: url(images/treeview-gray-line.gif); } +.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(images/treeview-gray.gif); } + +.treeview-famfamfam li { background-image: url(images/treeview-famfamfam-line.gif); } +.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(images/treeview-famfamfam.gif); } + +.treeview .placeholder { + background: url(images/ajax-loader.gif) 0 0 no-repeat; + height: 16px; + width: 16px; + display: block; +} + +.filetree li { padding: 3px 0 2px 16px; } +.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; } +.filetree span.folder { background: url(images/folder.gif) 0 0 no-repeat; } +.filetree li.expandable span.folder { background: url(images/folder-closed.gif) 0 0 no-repeat; } +.filetree span.file { background: url(images/file.gif) 0 0 no-repeat; } diff --git a/framework/web/js/source/yiitab/jquery.yiitab.css b/framework/web/js/source/yiitab/jquery.yiitab.css new file mode 100644 index 0000000..abbb2e4 --- /dev/null +++ b/framework/web/js/source/yiitab/jquery.yiitab.css @@ -0,0 +1,58 @@ +.yiiTab ul.tabs +{ + padding: 2px 0; + margin: 0; + border-bottom: 1px solid #4F81BD; + font: bold 12px Verdana, sans-serif; +} + +.yiiTab ul.tabs li +{ + list-style: none; + margin: 0; + display: inline; +} + +.yiiTab ul.tabs a +{ + -moz-border-radius-topleft:5px; + -moz-border-radius-topright:5px; + padding: 2px 0.5em; + margin: 0 0 0 3px; + border: 1px solid #4F81BD; + border-bottom: none; + background: #d3dfee; + text-decoration: none; +} + +.yiiTab ul.tabs a:link +{ + color: #667; +} + +.yiiTab ul.tabs a:visited +{ + color: #667; +} + +.yiiTab ul.tabs a:hover +{ + color: #000; + background: #E6F2FF; + border-color: #227; +} + +.yiiTab ul.tabs a.active +{ + background: white; + border-bottom: 1px solid white; +} + +.yiiTab div.view +{ + border-left: 1px solid #4F81BD; + border-right: 1px solid #4F81BD; + border-bottom: 1px solid #4F81BD; + padding: 8px; + margin: 0; +}
\ No newline at end of file diff --git a/framework/web/renderers/CPradoViewRenderer.php b/framework/web/renderers/CPradoViewRenderer.php new file mode 100644 index 0000000..343a78b --- /dev/null +++ b/framework/web/renderers/CPradoViewRenderer.php @@ -0,0 +1,305 @@ +<?php +/** + * CPradoViewRenderer class file. + * + * @author Steve Heyns http://customgothic.com/ + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CPradoViewRenderer implements a view renderer that allows users to use a template syntax similar to PRADO templates. + * + * To use CPradoViewRenderer, configure it as an application component named "viewRenderer" in the application configuration: + * <pre> + * array( + * 'components'=>array( + * ...... + * 'viewRenderer'=>array( + * 'class'=>'CPradoViewRenderer', + * ), + * ), + * ) + * </pre> + * + * CPradoViewRenderer allows you to write view files with the following syntax: + * <pre> + * // PHP tags: + * <%= expression %> + * // <?php echo expression ?> + * <% statement %> + * // <?php statement ?></li> + * + * // component tags: + * <com:WigetClass name1="value1" name2='value2' name3={value3} > + * // <?php $this->beginWidget('WigetClass', + * // array('name1'=>"value1", 'name2'=>'value2', 'name3'=>value3)); ?> + * </com:WigetClass > + * // <?php $this->endWidget('WigetClass'); ?> + * <com:WigetClass name1="value1" name2='value2' name3={value3} /> + * // <?php $this->widget('WigetClass', + * // array('name1'=>"value1", 'name2'=>'value2', 'name3'=>value3)); ?> + * + * // cache tags: + * <cache:fragmentID name1="value1" name2='value2' name3={value3} > + * // <?php if($this->beginCache('fragmentID', + * // array('name1'=>"value1", 'name2'=>'value2', 'name3'=>value3))): ?> + * </cache:fragmentID > + * // <?php $this->endCache('fragmentID'); endif; ?> + * + * // clip tags: + * <clip:clipID > + * // <?php $this->beginClip('clipID'); ?> + * </clip:clipID > + * // <?php $this->endClip('clipID'); ?> + * + * // comment tags: + * <!--- comments ---> + * // the whole tag will be stripped off + * </pre> + * + * @author Steve Heyns http://customgothic.com/ + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CPradoViewRenderer.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @package system.web.renderers + * @since 1.0 + */ +class CPradoViewRenderer extends CViewRenderer +{ + private $_input; + private $_output; + private $_sourceFile; + + /** + * Parses the source view file and saves the results as another file. + * This method is required by the parent class. + * @param string $sourceFile the source view file path + * @param string $viewFile the resulting view file path + */ + protected function generateViewFile($sourceFile,$viewFile) + { + static $regexRules=array( + '<%=?\s*(.*?)\s*%>', // PHP statements or expressions + '<\/?(com|cache|clip):([\w\.]+)\s*((?:\s*\w+\s*=\s*\'.*?(?<!\\\\)\'|\s*\w+\s*=\s*".*?(?<!\\\\)"|\s*\w+\s*=\s*\{.*?\})*)\s*\/?>', // component tags + '<!---.*?--->', // template comments + ); + $this->_sourceFile=$sourceFile; + $this->_input=file_get_contents($sourceFile); + $n=preg_match_all('/'.implode('|',$regexRules).'/msS',$this->_input,$matches,PREG_SET_ORDER|PREG_OFFSET_CAPTURE); + $textStart=0; + $this->_output="<?php /* source file: $sourceFile */ ?>\n"; + for($i=0;$i<$n;++$i) + { + $match=&$matches[$i]; + $str=$match[0][0]; + $matchStart=$match[0][1]; + $matchEnd=$matchStart+strlen($str)-1; + + if($matchStart>$textStart) + $this->_output.=substr($this->_input,$textStart,$matchStart-$textStart); + $textStart=$matchEnd+1; + + if(strpos($str,'<com:')===0) // opening component tag + { + $type=$match[3][0]; + if($str[strlen($str)-2]!=='/') // open tag + $this->_output.=$this->processBeginWidget($type,$match[4][0],$match[2][1]); + else + $this->_output.=$this->processWidget($type,$match[4][0],$match[2][1]); + } + else if(strpos($str,'</com:')===0) // closing component tag + $this->_output.=$this->processEndWidget($match[3][0],$match[2][1]); + else if(strpos($str,'<cache:')===0) // opening cache tag + { + $id=$match[3][0]; + if($str[strlen($str)-2]!=='/') // open tag + $this->_output.=$this->processBeginCache($id,$match[4][0],$match[2][1]); + else + $this->_output.=$this->processCache($id,$match[4][0],$match[2][1]); + } + else if(strpos($str,'</cache:')===0) // closing cache tag + $this->_output.=$this->processEndCache($match[3][0],$match[2][1]); + else if(strpos($str,'<clip:')===0) // opening clip tag + { + $id=$match[3][0]; + if($str[strlen($str)-2]!=='/') // open tag + $this->_output.=$this->processBeginClip($id,$match[4][0],$match[2][1]); + else + $this->_output.=$this->processClip($id,$match[4][0],$match[2][1]); + } + else if(strpos($str,'</clip:')===0) // closing clip tag + $this->_output.=$this->processEndClip($match[3][0],$match[2][1]); + else if(strpos($str,'<%=')===0) // expression + $this->_output.=$this->processExpression($match[1][0],$match[1][1]); + else if(strpos($str,'<%')===0) // statement + $this->_output.=$this->processStatement($match[1][0],$match[1][1]); + } + if($textStart<strlen($this->_input)) + $this->_output.=substr($this->_input,$textStart); + + file_put_contents($viewFile,$this->_output); + } + + /* + * @param string $type type + * @param string $attributes attributes + * @param string $offset offset + */ + private function processWidget($type,$attributes,$offset) + { + $attrs=$this->processAttributes($attributes); + if(empty($attrs)) + return $this->generatePhpCode("\$this->widget('$type');",$offset); + else + return $this->generatePhpCode("\$this->widget('$type', array($attrs));",$offset); + } + + /* + * @param string $type type + * @param string $attributes attributes + * @param string $offset offset + */ + private function processBeginWidget($type,$attributes,$offset) + { + $attrs=$this->processAttributes($attributes); + if(empty($attrs)) + return $this->generatePhpCode("\$this->beginWidget('$type');",$offset); + else + return $this->generatePhpCode("\$this->beginWidget('$type', array($attrs));",$offset); + } + + /* + * @param string $type type + * @param string $offset offset + */ + private function processEndWidget($type,$offset) + { + return $this->generatePhpCode("\$this->endWidget('$type');",$offset); + } + + /* + * @param string $id id + * @param string $attributes attributes + * @param string $offset offset + */ + private function processCache($id,$attributes,$offset) + { + return $this->processBeginCache($id,$attributes,$offset) . $this->processEndCache($id,$offset); + } + + /* + * @param string $id id + * @param string $attributes attributes + * @param string $offset offset + */ + private function processBeginCache($id,$attributes,$offset) + { + $attrs=$this->processAttributes($attributes); + if(empty($attrs)) + return $this->generatePhpCode("if(\$this->beginCache('$id')):",$offset); + else + return $this->generatePhpCode("if(\$this->beginCache('$id', array($attrs))):",$offset); + } + + /* + * @param string $id id + * @param string $offset offset + */ + private function processEndCache($id,$offset) + { + return $this->generatePhpCode("\$this->endCache('$id'); endif;",$offset); + } + + /* + * @param string $id id + * @param string $attributes attributes + * @param string $offset offset + */ + private function processClip($id,$attributes,$offset) + { + return $this->processBeginClip($id,$attributes,$offset) . $this->processEndClip($id,$offset); + } + + /* + * @param string $id id + * @param string $attributes attributes + * @param string $offset offset + */ + private function processBeginClip($id,$attributes,$offset) + { + $attrs=$this->processAttributes($attributes); + if(empty($attrs)) + return $this->generatePhpCode("\$this->beginClip('$id');",$offset); + else + return $this->generatePhpCode("\$this->beginClip('$id', array($attrs));",$offset); + } + + /* + * @param string $id id + * @param string $offset offset + */ + private function processEndClip($id,$offset) + { + return $this->generatePhpCode("\$this->endClip('$id');",$offset); + } + + /* + * @param string $expression expression + * @param string $offset offset + */ + private function processExpression($expression,$offset) + { + return $this->generatePhpCode('echo '.$expression,$offset); + } + + /* + * @param string $statement statement + * @param string $offset offset + */ + private function processStatement($statement,$offset) + { + return $this->generatePhpCode($statement,$offset); + } + + /* + * @param string $code code + * @param string $offset offset + */ + private function generatePhpCode($code,$offset) + { + $line=$this->getLineNumber($offset); + $code=str_replace('__FILE__',var_export($this->_sourceFile,true),$code); + return "<?php /* line $line */ $code ?>"; + } + + /* + * @param string $str str + */ + private function processAttributes($str) + { + static $pattern='/(\w+)\s*=\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)"|\{.*?\})/msS'; + $attributes=array(); + $n=preg_match_all($pattern,$str,$matches,PREG_SET_ORDER); + for($i=0;$i<$n;++$i) + { + $match=&$matches[$i]; + $name=$match[1]; + $value=$match[2]; + if($value[0]==='{') + $attributes[]="'$name'=>".str_replace('__FILE__',$this->_sourceFile,substr($value,1,-1)); + else + $attributes[]="'$name'=>$value"; + } + return implode(', ',$attributes); + } + + /* + * @param string $offset offset + */ + private function getLineNumber($offset) + { + return count(explode("\n",substr($this->_input,0,$offset))); + } +} diff --git a/framework/web/renderers/CViewRenderer.php b/framework/web/renderers/CViewRenderer.php new file mode 100644 index 0000000..093c3bb --- /dev/null +++ b/framework/web/renderers/CViewRenderer.php @@ -0,0 +1,97 @@ +<?php +/** + * CViewRenderer class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CViewRenderer is the base class for view renderer classes. + * + * A view renderer is an application component that renders views written + * in a customized syntax. + * + * Once installing a view renderer as a 'viewRenderer' application component, + * the normal view rendering process will be intercepted by the renderer. + * The renderer will first parse the source view file and then render the + * the resulting view file. + * + * Parsing results are saved as temporary files that may be stored + * under the application runtime directory or together with the source view file. + * + * @author Steve Heyns http://customgothic.com/ + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CViewRenderer.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.renderers + * @since 1.0 + */ +abstract class CViewRenderer extends CApplicationComponent implements IViewRenderer +{ + /** + * @var boolean whether to store the parsing results in the application's + * runtime directory. Defaults to true. If false, the parsing results will + * be saved as files under the same directory as the source view files and the + * file names will be the source file names appended with letter 'c'. + */ + public $useRuntimePath=true; + /** + * @var integer the chmod permission for temporary directories and files + * generated during parsing. Defaults to 0755 (owner rwx, group rx and others rx). + */ + public $filePermission=0755; + /** + * @var string the extension name of the view file. Defaults to '.php'. + */ + public $fileExtension='.php'; + + /** + * Parses the source view file and saves the results as another file. + * @param string $sourceFile the source view file path + * @param string $viewFile the resulting view file path + */ + abstract protected function generateViewFile($sourceFile,$viewFile); + + /** + * Renders a view file. + * This method is required by {@link IViewRenderer}. + * @param CBaseController $context the controller or widget who is rendering the view file. + * @param string $sourceFile the view file path + * @param mixed $data the data to be passed to the view + * @param boolean $return whether the rendering result should be returned + * @return mixed the rendering result, or null if the rendering result is not needed. + */ + public function renderFile($context,$sourceFile,$data,$return) + { + if(!is_file($sourceFile) || ($file=realpath($sourceFile))===false) + throw new CException(Yii::t('yii','View file "{file}" does not exist.',array('{file}'=>$sourceFile))); + $viewFile=$this->getViewFile($sourceFile); + if(@filemtime($sourceFile)>@filemtime($viewFile)) + { + $this->generateViewFile($sourceFile,$viewFile); + @chmod($viewFile,$this->filePermission); + } + return $context->renderInternal($viewFile,$data,$return); + } + + /** + * Generates the resulting view file path. + * @param string $file source view file path + * @return string resulting view file path + */ + protected function getViewFile($file) + { + if($this->useRuntimePath) + { + $crc=sprintf('%x', crc32(get_class($this).Yii::getVersion().dirname($file))); + $viewFile=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.$crc.DIRECTORY_SEPARATOR.basename($file); + if(!is_file($viewFile)) + @mkdir(dirname($viewFile),$this->filePermission,true); + return $viewFile; + } + else + return $file.'c'; + } +} diff --git a/framework/web/services/CWebService.php b/framework/web/services/CWebService.php new file mode 100644 index 0000000..c1de152 --- /dev/null +++ b/framework/web/services/CWebService.php @@ -0,0 +1,283 @@ +<?php +/** + * CWebService class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CWebService encapsulates SoapServer and provides a WSDL-based web service. + * + * PHP SOAP extension is required. + * + * CWebService makes use of {@link CWsdlGenerator} and can generate the WSDL + * on-the-fly without requiring you to write complex WSDL. + * + * To generate the WSDL based on doc comment blocks in the service provider class, + * call {@link generateWsdl} or {@link renderWsdl}. To process the web service + * requests, call {@link run}. + * + * @property string $methodName The currently requested method name. Empty if no method is being requested. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CWebService.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.services + * @since 1.0 + */ +class CWebService extends CComponent +{ + const SOAP_ERROR=1001; + /** + * @var string|object the web service provider class or object. + * If specified as a class name, it can be a path alias. + */ + public $provider; + /** + * @var string the URL for WSDL. This is required by {@link run()}. + */ + public $wsdlUrl; + /** + * @var string the URL for the Web service. This is required by {@link generateWsdl()} and {@link renderWsdl()}. + */ + public $serviceUrl; + /** + * @var integer number of seconds that the generated WSDL can remain valid in cache. Defaults to 0, meaning no caching. + */ + public $wsdlCacheDuration=0; + /** + * @var string the ID of the cache application component that is used to cache the generated WSDL. + * Defaults to 'cache' which refers to the primary cache application component. + * Set this property to false if you want to disable caching WSDL. + */ + public $cacheID='cache'; + /** + * @var string encoding of the Web service. Defaults to 'UTF-8'. + */ + public $encoding='UTF-8'; + /** + * @var array a list of classes that are declared as complex types in WSDL. + * This should be an array with WSDL types as keys and names of PHP classes as values. + * A PHP class can also be specified as a path alias. + * @see http://www.php.net/manual/en/function.soap-soapserver-construct.php + */ + public $classMap=array(); + /** + * @var string actor of the SOAP service. Defaults to null, meaning not set. + */ + public $actor; + /** + * @var string SOAP version (e.g. '1.1' or '1.2'). Defaults to null, meaning not set. + */ + public $soapVersion; + /** + * @var integer the persistence mode of the SOAP server. + * @see http://www.php.net/manual/en/function.soap-soapserver-setpersistence.php + */ + public $persistence; + + private $_method; + + + /** + * Constructor. + * @param mixed $provider the web service provider class name or object + * @param string $wsdlUrl the URL for WSDL. This is required by {@link run()}. + * @param string $serviceUrl the URL for the Web service. This is required by {@link generateWsdl()} and {@link renderWsdl()}. + */ + public function __construct($provider,$wsdlUrl,$serviceUrl) + { + $this->provider=$provider; + $this->wsdlUrl=$wsdlUrl; + $this->serviceUrl=$serviceUrl; + } + + /** + * The PHP error handler. + * @param CErrorEvent $event the PHP error event + */ + public function handleError($event) + { + $event->handled=true; + $message=$event->message; + if(YII_DEBUG) + { + $trace=debug_backtrace(); + if(isset($trace[2]) && isset($trace[2]['file']) && isset($trace[2]['line'])) + $message.=' ('.$trace[2]['file'].':'.$trace[2]['line'].')'; + } + throw new CException($message,self::SOAP_ERROR); + } + + /** + * Generates and displays the WSDL as defined by the provider. + * @see generateWsdl + */ + public function renderWsdl() + { + $wsdl=$this->generateWsdl(); + header('Content-Type: text/xml;charset='.$this->encoding); + header('Content-Length: '.(function_exists('mb_strlen') ? mb_strlen($wsdl,'8bit') : strlen($wsdl))); + echo $wsdl; + } + + /** + * Generates the WSDL as defined by the provider. + * The cached version may be used if the WSDL is found valid in cache. + * @return string the generated WSDL + * @see wsdlCacheDuration + */ + public function generateWsdl() + { + $providerClass=is_object($this->provider) ? get_class($this->provider) : Yii::import($this->provider,true); + if($this->wsdlCacheDuration>0 && $this->cacheID!==false && ($cache=Yii::app()->getComponent($this->cacheID))!==null) + { + $key='Yii.CWebService.'.$providerClass.$this->serviceUrl.$this->encoding; + if(($wsdl=$cache->get($key))!==false) + return $wsdl; + } + $generator=new CWsdlGenerator; + $wsdl=$generator->generateWsdl($providerClass,$this->serviceUrl,$this->encoding); + if(isset($key)) + $cache->set($key,$wsdl,$this->wsdlCacheDuration); + return $wsdl; + } + + /** + * Handles the web service request. + */ + public function run() + { + header('Content-Type: text/xml;charset='.$this->encoding); + if(YII_DEBUG) + ini_set("soap.wsdl_cache_enabled",0); + $server=new SoapServer($this->wsdlUrl,$this->getOptions()); + Yii::app()->attachEventHandler('onError',array($this,'handleError')); + try + { + if($this->persistence!==null) + $server->setPersistence($this->persistence); + if(is_string($this->provider)) + $provider=Yii::createComponent($this->provider); + else + $provider=$this->provider; + + if(method_exists($server,'setObject')) + $server->setObject($provider); + else + $server->setClass('CSoapObjectWrapper',$provider); + + if($provider instanceof IWebServiceProvider) + { + if($provider->beforeWebMethod($this)) + { + $server->handle(); + $provider->afterWebMethod($this); + } + } + else + $server->handle(); + } + catch(Exception $e) + { + if($e->getCode()!==self::SOAP_ERROR) // non-PHP error + { + // only log for non-PHP-error case because application's error handler already logs it + // php <5.2 doesn't support string conversion auto-magically + Yii::log($e->__toString(),CLogger::LEVEL_ERROR,'application'); + } + $message=$e->getMessage(); + if(YII_DEBUG) + $message.=' ('.$e->getFile().':'.$e->getLine().")\n".$e->getTraceAsString(); + + // We need to end application explicitly because of + // http://bugs.php.net/bug.php?id=49513 + Yii::app()->onEndRequest(new CEvent($this)); + $server->fault(get_class($e),$message); + exit(1); + } + } + + /** + * @return string the currently requested method name. Empty if no method is being requested. + */ + public function getMethodName() + { + if($this->_method===null) + { + if(isset($HTTP_RAW_POST_DATA)) + $request=$HTTP_RAW_POST_DATA; + else + $request=file_get_contents('php://input'); + if(preg_match('/<.*?:Body[^>]*>\s*<.*?:(\w+)/mi',$request,$matches)) + $this->_method=$matches[1]; + else + $this->_method=''; + } + return $this->_method; + } + + /** + * @return array options for creating SoapServer instance + * @see http://www.php.net/manual/en/function.soap-soapserver-construct.php + */ + protected function getOptions() + { + $options=array(); + if($this->soapVersion==='1.1') + $options['soap_version']=SOAP_1_1; + else if($this->soapVersion==='1.2') + $options['soap_version']=SOAP_1_2; + if($this->actor!==null) + $options['actor']=$this->actor; + $options['encoding']=$this->encoding; + foreach($this->classMap as $type=>$className) + { + $className=Yii::import($className,true); + if(is_int($type)) + $type=$className; + $options['classmap'][$type]=$className; + } + return $options; + } +} + + +/** + * CSoapObjectWrapper is a wrapper class internally used when SoapServer::setObject() is not defined. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CWebService.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.services + */ +class CSoapObjectWrapper +{ + /** + * @var object the service provider + */ + public $object=null; + + /** + * Constructor. + * @param object $object the service provider + */ + public function __construct($object) + { + $this->object=$object; + } + + /** + * PHP __call magic method. + * This method calls the service provider to execute the actual logic. + * @param string $name method name + * @param array $arguments method arguments + * @return mixed method return value + */ + public function __call($name,$arguments) + { + return call_user_func_array(array($this->object,$name),$arguments); + } +} + diff --git a/framework/web/services/CWebServiceAction.php b/framework/web/services/CWebServiceAction.php new file mode 100644 index 0000000..d4dfbc9 --- /dev/null +++ b/framework/web/services/CWebServiceAction.php @@ -0,0 +1,132 @@ +<?php +/** + * CWebServiceAction class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CWebServiceAction implements an action that provides Web services. + * + * CWebServiceAction serves for two purposes. On the one hand, it displays + * the WSDL content specifying the Web service APIs. On the other hand, it + * invokes the requested Web service API. A GET parameter named <code>ws</code> + * is used to differentiate these two aspects: the existence of the GET parameter + * indicates performing the latter action. + * + * By default, CWebServiceAction will use the current controller as + * the Web service provider. See {@link CWsdlGenerator} on how to declare + * methods that can be remotely invoked. + * + * Note, PHP SOAP extension is required for this action. + * + * @property CWebService $service The Web service instance. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CWebServiceAction.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.web.services + * @since 1.0 + */ +class CWebServiceAction extends CAction +{ + /** + * @var mixed the Web service provider object or class name. + * If specified as a class name, it can be a path alias. + * Defaults to null, meaning the current controller is used as the service provider. + * If the provider implements the interface {@link IWebServiceProvider}, + * it will be able to intercept the remote method invocation and perform + * additional tasks (e.g. authentication, logging). + */ + public $provider; + /** + * @var string the URL for the Web service. Defaults to null, meaning + * the URL for this action is used to provide Web services. + * In this case, a GET parameter named {@link serviceVar} will be used to + * deteremine whether the current request is for WSDL or Web service. + */ + public $serviceUrl; + /** + * @var string the URL for WSDL. Defaults to null, meaning + * the URL for this action is used to serve WSDL document. + */ + public $wsdlUrl; + /** + * @var string the name of the GET parameter that differentiates a WSDL request + * from a Web service request. If this GET parameter exists, the request is considered + * as a Web service request; otherwise, it is a WSDL request. Defaults to 'ws'. + */ + public $serviceVar='ws'; + /** + * @var array a list of PHP classes that are declared as complex types in WSDL. + * This should be an array with WSDL types as keys and names of PHP classes as values. + * A PHP class can also be specified as a path alias. + * @see http://www.php.net/manual/en/soapclient.soapclient.php + */ + public $classMap; + /** + * @var array the initial property values for the {@link CWebService} object. + * The array keys are property names of {@link CWebService} and the array values + * are the corresponding property initial values. + */ + public $serviceOptions=array(); + + private $_service; + + + /** + * Runs the action. + * If the GET parameter {@link serviceVar} exists, the action handle the remote method invocation. + * If not, the action will serve WSDL content; + */ + public function run() + { + $hostInfo=Yii::app()->getRequest()->getHostInfo(); + $controller=$this->getController(); + if(($serviceUrl=$this->serviceUrl)===null) + $serviceUrl=$hostInfo.$controller->createUrl($this->getId(),array($this->serviceVar=>1)); + if(($wsdlUrl=$this->wsdlUrl)===null) + $wsdlUrl=$hostInfo.$controller->createUrl($this->getId()); + if(($provider=$this->provider)===null) + $provider=$controller; + + $this->_service=$this->createWebService($provider,$wsdlUrl,$serviceUrl); + + if(is_array($this->classMap)) + $this->_service->classMap=$this->classMap; + + foreach($this->serviceOptions as $name=>$value) + $this->_service->$name=$value; + + if(isset($_GET[$this->serviceVar])) + $this->_service->run(); + else + $this->_service->renderWsdl(); + + Yii::app()->end(); + } + + /** + * Returns the Web service instance currently being used. + * @return CWebService the Web service instance + */ + public function getService() + { + return $this->_service; + } + + /** + * Creates a {@link CWebService} instance. + * You may override this method to customize the created instance. + * @param mixed $provider the web service provider class name or object + * @param string $wsdlUrl the URL for WSDL. + * @param string $serviceUrl the URL for the Web service. + * @return CWebService the Web service instance + */ + protected function createWebService($provider,$wsdlUrl,$serviceUrl) + { + return new CWebService($provider,$wsdlUrl,$serviceUrl); + } +}
\ No newline at end of file diff --git a/framework/web/services/CWsdlGenerator.php b/framework/web/services/CWsdlGenerator.php new file mode 100644 index 0000000..db0419e --- /dev/null +++ b/framework/web/services/CWsdlGenerator.php @@ -0,0 +1,419 @@ +<?php +/** + * CWsdlGenerator class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CWsdlGenerator generates the WSDL for a given service class. + * + * The WSDL generation is based on the doc comments found in the service class file. + * In particular, it recognizes the '@soap' tag in the comment and extracts + * API method and type definitions. + * + * In a service class, a remote invokable method must be a public method with a doc + * comment block containing the '@soap' tag. In the doc comment, the type and name + * of every input parameter and the type of the return value should be declared using + * the standard phpdoc format. + * + * CWsdlGenerator recognizes the following primitive types (case-sensitive) in + * the parameter and return type declarations: + * <ul> + * <li>str/string: maps to xsd:string;</li> + * <li>int/integer: maps to xsd:int;</li> + * <li>float/double: maps to xsd:float;</li> + * <li>bool/boolean: maps to xsd:boolean;</li> + * <li>date: maps to xsd:date;</li> + * <li>time: maps to xsd:time;</li> + * <li>datetime: maps to xsd:dateTime;</li> + * <li>array: maps to xsd:string;</li> + * <li>object: maps to xsd:struct;</li> + * <li>mixed: maps to xsd:anyType.</li> + * </ul> + * + * If a type is not a primitive type, it is considered as a class type, and + * CWsdlGenerator will look for its property declarations. Only public properties + * are considered, and they each must be associated with a doc comment block containg + * the '@soap' tag. The doc comment block should declare the type of the property. + * + * CWsdlGenerator recognizes the array type with the following format: + * <pre> + * typeName[]: maps to tns:typeNameArray + * </pre> + * + * The following is an example declaring a remote invokable method: + * <pre> + * / ** + * * A foo method. + * * @param string name of something + * * @param string value of something + * * @return string[] some array + * * @soap + * * / + * public function foo($name,$value) {...} + * </pre> + * + * And the following is an example declaring a class with remote accessible properties: + * <pre> + * class Foo { + * / ** + * * @var string name of foo + * * @soap + * * / + * public $name; + * / ** + * * @var Member[] members of foo + * * @soap + * * / + * public $members; + * } + * </pre> + * In the above, the 'members' property is an array of 'Member' objects. Since 'Member' is not + * a primitive type, CWsdlGenerator will look further to find the definition of 'Member'. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CWsdlGenerator.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @package system.web.services + * @since 1.0 + */ +class CWsdlGenerator extends CComponent +{ + /** + * @var string the namespace to be used in the generated WSDL. + * If not set, it defaults to the name of the class that WSDL is generated upon. + */ + public $namespace; + /** + * @var string the name of the generated WSDL. + * If not set, it defaults to "urn:{$className}wsdl". + */ + public $serviceName; + + private $_operations; + private $_types; + private $_messages; + + /** + * Generates the WSDL for the given class. + * @param string $className class name + * @param string $serviceUrl Web service URL + * @param string $encoding encoding of the WSDL. Defaults to 'UTF-8'. + * @return string the generated WSDL + */ + public function generateWsdl($className, $serviceUrl, $encoding='UTF-8') + { + $this->_operations=array(); + $this->_types=array(); + $this->_messages=array(); + if($this->serviceName===null) + $this->serviceName=$className; + if($this->namespace===null) + $this->namespace="urn:{$className}wsdl"; + + $reflection=new ReflectionClass($className); + foreach($reflection->getMethods() as $method) + { + if($method->isPublic()) + $this->processMethod($method); + } + + return $this->buildDOM($serviceUrl,$encoding)->saveXML(); + } + + /* + * @param ReflectionMethod $method method + */ + private function processMethod($method) + { + $comment=$method->getDocComment(); + if(strpos($comment,'@soap')===false) + return; + + $methodName=$method->getName(); + $comment=preg_replace('/^\s*\**(\s*?$|\s*)/m','',$comment); + $params=$method->getParameters(); + $message=array(); + $n=preg_match_all('/^@param\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches); + if($n>count($params)) + $n=count($params); + for($i=0;$i<$n;++$i) + $message[$params[$i]->getName()]=array($this->processType($matches[1][$i]), trim($matches[3][$i])); // name => type, doc + + $this->_messages[$methodName.'Request']=$message; + + if(preg_match('/^@return\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/im',$comment,$matches)) + $return=array($this->processType($matches[1]),trim($matches[2])); // type, doc + else + $return=null; + $this->_messages[$methodName.'Response']=array('return'=>$return); + + if(preg_match('/^\/\*+\s*([^@]*?)\n@/s',$comment,$matches)) + $doc=trim($matches[1]); + else + $doc=''; + $this->_operations[$methodName]=$doc; + } + + /* + * @param string $type PHP variable type + */ + private function processType($type) + { + static $typeMap=array( + 'string'=>'xsd:string', + 'str'=>'xsd:string', + 'int'=>'xsd:int', + 'integer'=>'xsd:integer', + 'float'=>'xsd:float', + 'double'=>'xsd:float', + 'bool'=>'xsd:boolean', + 'boolean'=>'xsd:boolean', + 'date'=>'xsd:date', + 'time'=>'xsd:time', + 'datetime'=>'xsd:dateTime', + 'array'=>'soap-enc:Array', + 'object'=>'xsd:struct', + 'mixed'=>'xsd:anyType', + ); + if(isset($typeMap[$type])) + return $typeMap[$type]; + else if(isset($this->_types[$type])) + return is_array($this->_types[$type]) ? 'tns:'.$type : $this->_types[$type]; + else if(($pos=strpos($type,'[]'))!==false) // if it is an array + { + $type=substr($type,0,$pos); + if(isset($typeMap[$type])) + $this->_types[$type.'[]']='xsd:'.$type.'Array'; + else + { + $this->_types[$type.'[]']='tns:'.$type.'Array'; + $this->processType($type); + } + return $this->_types[$type.'[]']; + } + else // class type + { + $type=Yii::import($type,true); + $this->_types[$type]=array(); + $class=new ReflectionClass($type); + foreach($class->getProperties() as $property) + { + $comment=$property->getDocComment(); + if($property->isPublic() && strpos($comment,'@soap')!==false) + { + if(preg_match('/@var\s+([\w\.]+(\[\s*\])?)\s*?(.*)$/mi',$comment,$matches)) + $this->_types[$type][$property->getName()]=array($this->processType($matches[1]),trim($matches[3])); // name => type, doc + } + } + return 'tns:'.$type; + } + } + + /* + * @param string $serviceUrl Web service URL + * @param string $encoding encoding of the WSDL. Defaults to 'UTF-8'. + */ + private function buildDOM($serviceUrl,$encoding) + { + $xml="<?xml version=\"1.0\" encoding=\"$encoding\"?> +<definitions name=\"{$this->serviceName}\" targetNamespace=\"{$this->namespace}\" + xmlns=\"http://schemas.xmlsoap.org/wsdl/\" + xmlns:tns=\"{$this->namespace}\" + xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\" + xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" + xmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\" + xmlns:soap-enc=\"http://schemas.xmlsoap.org/soap/encoding/\"></definitions>"; + + $dom=new DOMDocument(); + $dom->loadXml($xml); + $this->addTypes($dom); + + $this->addMessages($dom); + $this->addPortTypes($dom); + $this->addBindings($dom); + $this->addService($dom,$serviceUrl); + + return $dom; + } + + /* + * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree + */ + private function addTypes($dom) + { + if($this->_types===array()) + return; + $types=$dom->createElement('wsdl:types'); + $schema=$dom->createElement('xsd:schema'); + $schema->setAttribute('targetNamespace',$this->namespace); + foreach($this->_types as $phpType=>$xmlType) + { + if(is_string($xmlType) && strrpos($xmlType,'Array')!==strlen($xmlType)-5) + continue; // simple type + $complexType=$dom->createElement('xsd:complexType'); + if(is_string($xmlType)) + { + if(($pos=strpos($xmlType,'tns:'))!==false) + $complexType->setAttribute('name',substr($xmlType,4)); + else + $complexType->setAttribute('name',$xmlType); + $complexContent=$dom->createElement('xsd:complexContent'); + $restriction=$dom->createElement('xsd:restriction'); + $restriction->setAttribute('base','soap-enc:Array'); + $attribute=$dom->createElement('xsd:attribute'); + $attribute->setAttribute('ref','soap-enc:arrayType'); + $attribute->setAttribute('wsdl:arrayType',substr($xmlType,0,strlen($xmlType)-5).'[]'); + $restriction->appendChild($attribute); + $complexContent->appendChild($restriction); + $complexType->appendChild($complexContent); + } + else if(is_array($xmlType)) + { + $complexType->setAttribute('name',$phpType); + $all=$dom->createElement('xsd:all'); + foreach($xmlType as $name=>$type) + { + $element=$dom->createElement('xsd:element'); + $element->setAttribute('name',$name); + $element->setAttribute('type',$type[0]); + $all->appendChild($element); + } + $complexType->appendChild($all); + } + $schema->appendChild($complexType); + $types->appendChild($schema); + } + + $dom->documentElement->appendChild($types); + } + + /* + * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree + */ + private function addMessages($dom) + { + foreach($this->_messages as $name=>$message) + { + $element=$dom->createElement('wsdl:message'); + $element->setAttribute('name',$name); + foreach($this->_messages[$name] as $partName=>$part) + { + if(is_array($part)) + { + $partElement=$dom->createElement('wsdl:part'); + $partElement->setAttribute('name',$partName); + $partElement->setAttribute('type',$part[0]); + $element->appendChild($partElement); + } + } + $dom->documentElement->appendChild($element); + } + } + + /* + * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree + */ + private function addPortTypes($dom) + { + $portType=$dom->createElement('wsdl:portType'); + $portType->setAttribute('name',$this->serviceName.'PortType'); + $dom->documentElement->appendChild($portType); + foreach($this->_operations as $name=>$doc) + $portType->appendChild($this->createPortElement($dom,$name,$doc)); + } + + /* + * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree + * @param string $name method name + * @param string $doc doc + */ + private function createPortElement($dom,$name,$doc) + { + $operation=$dom->createElement('wsdl:operation'); + $operation->setAttribute('name',$name); + + $input = $dom->createElement('wsdl:input'); + $input->setAttribute('message', 'tns:'.$name.'Request'); + $output = $dom->createElement('wsdl:output'); + $output->setAttribute('message', 'tns:'.$name.'Response'); + + $operation->appendChild($dom->createElement('wsdl:documentation',$doc)); + $operation->appendChild($input); + $operation->appendChild($output); + + return $operation; + } + + /* + * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree + */ + private function addBindings($dom) + { + $binding=$dom->createElement('wsdl:binding'); + $binding->setAttribute('name',$this->serviceName.'Binding'); + $binding->setAttribute('type','tns:'.$this->serviceName.'PortType'); + + $soapBinding=$dom->createElement('soap:binding'); + $soapBinding->setAttribute('style','rpc'); + $soapBinding->setAttribute('transport','http://schemas.xmlsoap.org/soap/http'); + $binding->appendChild($soapBinding); + + $dom->documentElement->appendChild($binding); + + foreach($this->_operations as $name=>$doc) + $binding->appendChild($this->createOperationElement($dom,$name)); + } + + /* + * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree + * @param string $name method name + */ + private function createOperationElement($dom,$name) + { + $operation=$dom->createElement('wsdl:operation'); + $operation->setAttribute('name', $name); + $soapOperation = $dom->createElement('soap:operation'); + $soapOperation->setAttribute('soapAction', $this->namespace.'#'.$name); + $soapOperation->setAttribute('style','rpc'); + + $input = $dom->createElement('wsdl:input'); + $output = $dom->createElement('wsdl:output'); + + $soapBody = $dom->createElement('soap:body'); + $soapBody->setAttribute('use', 'encoded'); + $soapBody->setAttribute('namespace', $this->namespace); + $soapBody->setAttribute('encodingStyle', 'http://schemas.xmlsoap.org/soap/encoding/'); + $input->appendChild($soapBody); + $output->appendChild(clone $soapBody); + + $operation->appendChild($soapOperation); + $operation->appendChild($input); + $operation->appendChild($output); + + return $operation; + } + + /* + * @param DOMDocument $dom Represents an entire HTML or XML document; serves as the root of the document tree + * @param string $serviceUrl Web service URL + */ + private function addService($dom,$serviceUrl) + { + $service=$dom->createElement('wsdl:service'); + $service->setAttribute('name', $this->serviceName.'Service'); + + $port=$dom->createElement('wsdl:port'); + $port->setAttribute('name', $this->serviceName.'Port'); + $port->setAttribute('binding', 'tns:'.$this->serviceName.'Binding'); + + $soapAddress=$dom->createElement('soap:address'); + $soapAddress->setAttribute('location',$serviceUrl); + $port->appendChild($soapAddress); + $service->appendChild($port); + $dom->documentElement->appendChild($service); + } +} diff --git a/framework/web/widgets/CActiveForm.php b/framework/web/widgets/CActiveForm.php new file mode 100644 index 0000000..76a49b7 --- /dev/null +++ b/framework/web/widgets/CActiveForm.php @@ -0,0 +1,787 @@ +<?php +/** + * CActiveForm class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CActiveForm provides a set of methods that can help to simplify the creation + * of complex and interactive HTML forms that are associated with data models. + * + * The 'beginWidget' and 'endWidget' call of CActiveForm widget will render + * the open and close form tags. Most other methods of CActiveForm are wrappers + * of the corresponding 'active' methods in {@link CHtml}. Calling them in between + * the 'beginWidget' and 'endWidget' calls will render text labels, input fields, + * etc. For example, calling {@link CActiveForm::textField} + * would generate an input field for a specified model attribute. + * + * What makes CActiveForm extremely useful is its support for data validation. + * CActiveForm supports data validation at three levels: + * <ul> + * <li>server-side validation: the validation is performed at server side after + * the whole page containing the form is submitted. If there is any validation error, + * CActiveForm will render the error in the page back to user.</li> + * <li>AJAX-based validation: when the user enters data into an input field, + * an AJAX request is triggered which requires server-side validation. The validation + * result is sent back in AJAX response and the input field changes its appearance + * accordingly.</li> + * <li>client-side validation (available since version 1.1.7): + * when the user enters data into an input field, + * validation is performed on the client side using JavaScript. No server contact + * will be made, which reduces the workload on the server.</li> + * </ul> + * + * All these validations share the same set of validation rules declared in + * the associated model class. CActiveForm is designed in such a way that + * all these validations will lead to the same user interface changes and error + * message content. + * + * To ensure data validity, server-side validation is always performed. + * By setting {@link enableAjaxValidation} to true, one can enable AJAX-based validation; + * and by setting {@link enableClientValidation} to true, one can enable client-side validation. + * Note that in order to make the latter two validations work, the user's browser + * must has its JavaScript enabled. If not, only the server-side validation will + * be performed. + * + * The AJAX-based validation and client-side validation may be used together + * or separately. For example, in a user registration form, one may use AJAX-based + * validation to check if the user has picked a unique username, and use client-side + * validation to ensure all required fields are entered with data. + * Because the AJAX-based validation may bring extra workload on the server, + * if possible, one should mainly use client-side validation. + * + * The AJAX-based validation has a few limitations. First, it does not work + * with file upload fields. Second, it should not be used to perform validations that + * may cause server-side state changes. Third, it is not designed + * to work with tabular data input for the moment. + * + * Support for client-side validation varies for different validators. A validator + * will support client-side validation only if it implements {@link CValidator::clientValidateAttribute} + * and has its {@link CValidator::enableClientValidation} property set true. + * At this moment, the following core validators support client-side validation: + * <ul> + * <li>{@link CBooleanValidator}</li> + * <li>{@link CCaptchaValidator}</li> + * <li>{@link CCompareValidator}</li> + * <li>{@link CEmailValidator}</li> + * <li>{@link CNumberValidator}</li> + * <li>{@link CRangeValidator}</li> + * <li>{@link CRegularExpressionValidator}</li> + * <li>{@link CRequiredValidator}</li> + * <li>{@link CStringValidator}</li> + * <li>{@link CUrlValidator}</li> + * </ul> + * + * CActiveForm relies on CSS to customize the appearance of input fields + * which are in different validation states. In particular, each input field + * may be one of the four states: initial (not validated), + * validating, error and success. To differentiate these states, CActiveForm + * automatically assigns different CSS classes for the last three states + * to the HTML element containing the input field. + * By default, these CSS classes are named as 'validating', 'error' and 'success', + * respectively. We may customize these CSS classes by configuring the + * {@link clientOptions} property or specifying in the {@link error} method. + * + * The following is a piece of sample view code showing how to use CActiveForm: + * + * <pre> + * <?php $form = $this->beginWidget('CActiveForm', array( + * 'id'=>'user-form', + * 'enableAjaxValidation'=>true, + * 'enableClientValidation'=>true, + * 'focus'=>array($model,'firstName'), + * )); ?> + * + * <?php echo $form->errorSummary($model); ?> + * + * <div class="row"> + * <?php echo $form->labelEx($model,'firstName'); ?> + * <?php echo $form->textField($model,'firstName'); ?> + * <?php echo $form->error($model,'firstName'); ?> + * </div> + * <div class="row"> + * <?php echo $form->labelEx($model,'lastName'); ?> + * <?php echo $form->textField($model,'lastName'); ?> + * <?php echo $form->error($model,'lastName'); ?> + * </div> + * + * <?php $this->endWidget(); ?> + * </pre> + * + * To respond to the AJAX validation requests, we need the following class code: + * <pre> + * public function actionCreate() + * { + * $model=new User; + * $this->performAjaxValidation($model); + * if(isset($_POST['User'])) + * { + * $model->attributes=$_POST['User']; + * if($model->save()) + * $this->redirect('index'); + * } + * $this->render('create',array('model'=>$model)); + * } + * + * protected function performAjaxValidation($model) + * { + * if(isset($_POST['ajax']) && $_POST['ajax']==='user-form') + * { + * echo CActiveForm::validate($model); + * Yii::app()->end(); + * } + * } + * </pre> + * + * In the above code, if we do not enable the AJAX-based validation, we can remove + * the <code>performAjaxValidation</code> method and its invocation. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CActiveForm.php 3542 2012-01-17 12:46:49Z mdomba $ + * @package system.web.widgets + * @since 1.1.1 + */ +class CActiveForm extends CWidget +{ + /** + * @var mixed the form action URL (see {@link CHtml::normalizeUrl} for details about this parameter). + * If not set, the current page URL is used. + */ + public $action=''; + /** + * @var string the form submission method. This should be either 'post' or 'get'. + * Defaults to 'post'. + */ + public $method='post'; + /** + * @var boolean whether to generate a stateful form (See {@link CHtml::statefulForm}). Defaults to false. + */ + public $stateful=false; + /** + * @var string the CSS class name for error messages. Defaults to 'errorMessage'. + * Individual {@link error} call may override this value by specifying the 'class' HTML option. + */ + public $errorMessageCssClass='errorMessage'; + /** + * @var array additional HTML attributes that should be rendered for the form tag. + */ + public $htmlOptions=array(); + /** + * @var array the options to be passed to the javascript validation plugin. + * The following options are supported: + * <ul> + * <li>ajaxVar: string, the name of the parameter indicating the request is an AJAX request. + * When the AJAX validation is triggered, a parameter named as this property will be sent + * together with the other form data to the server. The parameter value is the form ID. + * The server side can then detect who triggers the AJAX validation and react accordingly. + * Defaults to 'ajax'.</li> + * <li>validationUrl: string, the URL that performs the AJAX validations. + * If not set, it will take the value of {@link action}.</li> + * <li>validationDelay: integer, the number of milliseconds that an AJAX validation should be + * delayed after an input is changed. A value 0 means the validation will be triggered immediately + * when an input is changed. A value greater than 0 means changing several inputs may only + * trigger a single validation if they happen fast enough, which may help reduce the server load. + * Defaults to 200 (0.2 second).</li> + * <li>validateOnSubmit: boolean, whether to perform AJAX validation when the form is being submitted. + * If there are any validation errors, the form submission will be stopped. + * Defaults to false.</li> + * <li>validateOnChange: boolean, whether to trigger an AJAX validation + * each time when an input's value is changed. You may want to turn this off + * if it causes too much performance impact, because each AJAX validation request + * will submit the data of the whole form. Defaults to true.</li> + * <li>validateOnType: boolean, whether to trigger an AJAX validation each time when the user + * presses a key. When setting this property to be true, you should tune up the 'validationDelay' + * option to avoid triggering too many AJAX validations. Defaults to false.</li> + * <li>hideErrorMessage: boolean, whether to hide the error message even if there is an error. + * Defaults to false, which means the error message will show up whenever the input has an error.</li> + * <li>inputContainer: string, the jQuery selector for the HTML element containing the input field. + * During the validation process, CActiveForm will set different CSS class for the container element + * to indicate the state change. If not set, it means the closest 'div' element that contains the input field.</li> + * <li>errorCssClass: string, the CSS class to be assigned to the container whose associated input + * has AJAX validation error. Defaults to 'error'.</li> + * <li>successCssClass: string, the CSS class to be assigned to the container whose associated input + * passes AJAX validation without any error. Defaults to 'success'.</li> + * <li>validatingCssClass: string, the CSS class to be assigned to the container whose associated input + * is currently being validated via AJAX. Defaults to 'validating'.</li> + * <li>errorMessageCssClass: string, the CSS class assigned to the error messages returned + * by AJAX validations. Defaults to 'errorMessage'.</li> + * <li>beforeValidate: function, the function that will be invoked before performing ajax-based validation + * triggered by form submission action (available only when validateOnSubmit is set true). + * The expected function signature should be <code>beforeValidate(form) {...}</code>, where 'form' is + * the jquery representation of the form object. If the return value of this function is NOT true, the validation + * will be cancelled. + * + * Note that because this option refers to a js function, you should prefix the value with 'js:' to prevent it + * from being encoded as a string. This option has been available since version 1.1.3.</li> + * <li>afterValidate: function, the function that will be invoked after performing ajax-based validation + * triggered by form submission action (available only when validateOnSubmit is set true). + * The expected function signature should be <code>afterValidate(form, data, hasError) {...}</code>, where 'form' is + * the jquery representation of the form object; 'data' is the JSON response from the server-side validation; 'hasError' + * is a boolean value indicating whether there is any validation error. If the return value of this function is NOT true, + * the normal form submission will be cancelled. + * + * Note that because this option refers to a js function, you should prefix the value with 'js:' to prevent it + * from being encoded as a string. This option has been available since version 1.1.3.</li> + * <li>beforeValidateAttribute: function, the function that will be invoked before performing ajax-based validation + * triggered by a single attribute input change. The expected function signature should be + * <code>beforeValidateAttribute(form, attribute) {...}</code>, where 'form' is the jquery representation of the form object + * and 'attribute' refers to the js options for the triggering attribute (see {@link error}). + * If the return value of this function is NOT true, the validation will be cancelled. + * + * Note that because this option refers to a js function, you should prefix the value with 'js:' to prevent it + * from being encoded as a string. This option has been available since version 1.1.3.</li> + * <li>afterValidateAttribute: function, the function that will be invoked after performing ajax-based validation + * triggered by a single attribute input change. The expected function signature should be + * <code>afterValidateAttribute(form, attribute, data, hasError) {...}</code>, where 'form' is the jquery + * representation of the form object; 'attribute' refers to the js options for the triggering attribute (see {@link error}); + * 'data' is the JSON response from the server-side validation; 'hasError' is a boolean value indicating whether + * there is any validation error. + * + * Note that because this option refers to a js function, you should prefix the value with 'js:' to prevent it + * from being encoded as a string. This option has been available since version 1.1.3.</li> + * </ul> + * + * Some of the above options may be overridden in individual calls of {@link error()}. + * They include: validationDelay, validateOnChange, validateOnType, hideErrorMessage, + * inputContainer, errorCssClass, successCssClass, validatingCssClass, beforeValidateAttribute, afterValidateAttribute. + */ + public $clientOptions=array(); + /** + * @var boolean whether to enable data validation via AJAX. Defaults to false. + * When this property is set true, you should respond to the AJAX validation request on the server side as shown below: + * <pre> + * public function actionCreate() + * { + * $model=new User; + * if(isset($_POST['ajax']) && $_POST['ajax']==='user-form') + * { + * echo CActiveForm::validate($model); + * Yii::app()->end(); + * } + * ...... + * } + * </pre> + */ + public $enableAjaxValidation=false; + /** + * @var boolean whether to enable client-side data validation. Defaults to false. + * + * When this property is set true, client-side validation will be performed by validators + * that support it (see {@link CValidator::enableClientValidation} and {@link CValidator::clientValidateAttribute}). + * + * @see error + * @since 1.1.7 + */ + public $enableClientValidation=false; + + /** + * @var mixed form element to get initial input focus on page load. + * + * Defaults to null meaning no input field has a focus. + * If set as array, first element should be model and second element should be the attribute. + * If set as string any jQuery selector can be used + * + * Example - set input focus on page load to: + * <ul> + * <li>'focus'=>array($model,'username') - $model->username input filed</li> + * <li>'focus'=>'#'.CHtml::activeId($model,'username') - $model->username input field</li> + * <li>'focus'=>'#LoginForm_username' - input field with ID LoginForm_username</li> + * <li>'focus'=>'input[type="text"]:first' - first input element of type text</li> + * <li>'focus'=>'input:visible:enabled:first' - first visible and enabled input element</li> + * <li>'focus'=>'input:text[value=""]:first' - first empty input</li> + * </ul> + * + * @since 1.1.4 + */ + public $focus; + /** + * @var array the javascript options for model attributes (input ID => options) + * @see error + * @since 1.1.7 + */ + protected $attributes=array(); + /** + * @var string the ID of the container element for error summary + * @see errorSummary + * @since 1.1.7 + */ + protected $summaryID; + + /** + * Initializes the widget. + * This renders the form open tag. + */ + public function init() + { + if(!isset($this->htmlOptions['id'])) + $this->htmlOptions['id']=$this->id; + if($this->stateful) + echo CHtml::statefulForm($this->action, $this->method, $this->htmlOptions); + else + echo CHtml::beginForm($this->action, $this->method, $this->htmlOptions); + } + + /** + * Runs the widget. + * This registers the necessary javascript code and renders the form close tag. + */ + public function run() + { + if(is_array($this->focus)) + $this->focus="#".CHtml::activeId($this->focus[0],$this->focus[1]); + + echo CHtml::endForm(); + $cs=Yii::app()->clientScript; + if(!$this->enableAjaxValidation && !$this->enableClientValidation || empty($this->attributes)) + { + if($this->focus!==null) + { + $cs->registerCoreScript('jquery'); + $cs->registerScript('CActiveForm#focus'," + if(!window.location.hash) + $('".$this->focus."').focus(); + "); + } + return; + } + + $options=$this->clientOptions; + if(isset($this->clientOptions['validationUrl']) && is_array($this->clientOptions['validationUrl'])) + $options['validationUrl']=CHtml::normalizeUrl($this->clientOptions['validationUrl']); + + $options['attributes']=array_values($this->attributes); + + if($this->summaryID!==null) + $options['summaryID']=$this->summaryID; + + if($this->focus!==null) + $options['focus']=$this->focus; + + $options=CJavaScript::encode($options); + $cs->registerCoreScript('yiiactiveform'); + $id=$this->id; + $cs->registerScript(__CLASS__.'#'.$id,"\$('#$id').yiiactiveform($options);"); + } + + /** + * Displays the first validation error for a model attribute. + * This is similar to {@link CHtml::error} except that it registers the model attribute + * so that if its value is changed by users, an AJAX validation may be triggered. + * @param CModel $model the data model + * @param string $attribute the attribute name + * @param array $htmlOptions additional HTML attributes to be rendered in the container div tag. + * Besides all those options available in {@link CHtml::error}, the following options are recognized in addition: + * <ul> + * <li>validationDelay</li> + * <li>validateOnChange</li> + * <li>validateOnType</li> + * <li>hideErrorMessage</li> + * <li>inputContainer</li> + * <li>errorCssClass</li> + * <li>successCssClass</li> + * <li>validatingCssClass</li> + * <li>beforeValidateAttribute</li> + * <li>afterValidateAttribute</li> + * </ul> + * These options override the corresponding options as declared in {@link options} for this + * particular model attribute. For more details about these options, please refer to {@link clientOptions}. + * Note that these options are only used when {@link enableAjaxValidation} or {@link enableClientValidation} + * is set true. + * + * When client-side validation is enabled, an option named "clientValidation" is also recognized. + * This option should take a piece of JavaScript code to perform client-side validation. In the code, + * the variables are predefined: + * <ul> + * <li>value: the current input value associated with this attribute.</li> + * <li>messages: an array that may be appended with new error messages for the attribute.</li> + * <li>attribute: a data structure keeping all client-side options for the attribute</li> + * </ul> + * @param boolean $enableAjaxValidation whether to enable AJAX validation for the specified attribute. + * Note that in order to enable AJAX validation, both {@link enableAjaxValidation} and this parameter + * must be true. + * @param boolean $enableClientValidation whether to enable client-side validation for the specified attribute. + * Note that in order to enable client-side validation, both {@link enableClientValidation} and this parameter + * must be true. This parameter has been available since version 1.1.7. + * @return string the validation result (error display or success message). + * @see CHtml::error + */ + public function error($model,$attribute,$htmlOptions=array(),$enableAjaxValidation=true,$enableClientValidation=true) + { + if(!$this->enableAjaxValidation) + $enableAjaxValidation=false; + if(!$this->enableClientValidation) + $enableClientValidation=false; + + if(!isset($htmlOptions['class'])) + $htmlOptions['class']=$this->errorMessageCssClass; + + if(!$enableAjaxValidation && !$enableClientValidation) + return CHtml::error($model,$attribute,$htmlOptions); + + $id=CHtml::activeId($model,$attribute); + $inputID=isset($htmlOptions['inputID']) ? $htmlOptions['inputID'] : $id; + unset($htmlOptions['inputID']); + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=$inputID.'_em_'; + + $option=array( + 'id'=>$id, + 'inputID'=>$inputID, + 'errorID'=>$htmlOptions['id'], + 'model'=>get_class($model), + 'name'=>$attribute, + 'enableAjaxValidation'=>$enableAjaxValidation, + ); + + $optionNames=array( + 'validationDelay', + 'validateOnChange', + 'validateOnType', + 'hideErrorMessage', + 'inputContainer', + 'errorCssClass', + 'successCssClass', + 'validatingCssClass', + 'beforeValidateAttribute', + 'afterValidateAttribute', + ); + foreach($optionNames as $name) + { + if(isset($htmlOptions[$name])) + { + $option[$name]=$htmlOptions[$name]; + unset($htmlOptions[$name]); + } + } + if($model instanceof CActiveRecord && !$model->isNewRecord) + $option['status']=1; + + if($enableClientValidation) + { + $validators=isset($htmlOptions['clientValidation']) ? array($htmlOptions['clientValidation']) : array(); + + $attributeName = $attribute; + if(($pos=strrpos($attribute,']'))!==false && $pos!==strlen($attribute)-1) // e.g. [a]name + { + $attributeName=substr($attribute,$pos+1); + } + + foreach($model->getValidators($attributeName) as $validator) + { + if($validator->enableClientValidation) + { + if(($js=$validator->clientValidateAttribute($model,$attributeName))!='') + $validators[]=$js; + } + } + if($validators!==array()) + $option['clientValidation']="js:function(value, messages, attribute) {\n".implode("\n",$validators)."\n}"; + } + + $html=CHtml::error($model,$attribute,$htmlOptions); + if($html==='') + { + if(isset($htmlOptions['style'])) + $htmlOptions['style']=rtrim($htmlOptions['style'],';').';display:none'; + else + $htmlOptions['style']='display:none'; + $html=CHtml::tag('div',$htmlOptions,''); + } + + $this->attributes[$inputID]=$option; + return $html; + } + + /** + * Displays a summary of validation errors for one or several models. + * This method is very similar to {@link CHtml::errorSummary} except that it also works + * when AJAX validation is performed. + * @param mixed $models the models whose input errors are to be displayed. This can be either + * a single model or an array of models. + * @param string $header a piece of HTML code that appears in front of the errors + * @param string $footer a piece of HTML code that appears at the end of the errors + * @param array $htmlOptions additional HTML attributes to be rendered in the container div tag. + * @return string the error summary. Empty if no errors are found. + * @see CHtml::errorSummary + */ + public function errorSummary($models,$header=null,$footer=null,$htmlOptions=array()) + { + if(!$this->enableAjaxValidation && !$this->enableClientValidation) + return CHtml::errorSummary($models,$header,$footer,$htmlOptions); + + if(!isset($htmlOptions['id'])) + $htmlOptions['id']=$this->id.'_es_'; + $html=CHtml::errorSummary($models,$header,$footer,$htmlOptions); + if($html==='') + { + if($header===null) + $header='<p>'.Yii::t('yii','Please fix the following input errors:').'</p>'; + if(!isset($htmlOptions['class'])) + $htmlOptions['class']=CHtml::$errorSummaryCss; + $htmlOptions['style']=isset($htmlOptions['style']) ? rtrim($htmlOptions['style'],';').';display:none' : 'display:none'; + $html=CHtml::tag('div',$htmlOptions,$header."\n<ul><li>dummy</li></ul>".$footer); + } + + $this->summaryID=$htmlOptions['id']; + return $html; + } + + /** + * Renders an HTML label for a model attribute. + * This method is a wrapper of {@link CHtml::activeLabel}. + * Please check {@link CHtml::activeLabel} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated label tag + */ + public function label($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeLabel($model,$attribute,$htmlOptions); + } + + /** + * Renders an HTML label for a model attribute. + * This method is a wrapper of {@link CHtml::activeLabelEx}. + * Please check {@link CHtml::activeLabelEx} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated label tag + */ + public function labelEx($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeLabelEx($model,$attribute,$htmlOptions); + } + + /** + * Renders a text field for a model attribute. + * This method is a wrapper of {@link CHtml::activeTextField}. + * Please check {@link CHtml::activeTextField} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + */ + public function textField($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeTextField($model,$attribute,$htmlOptions); + } + + /** + * Renders a hidden field for a model attribute. + * This method is a wrapper of {@link CHtml::activeHiddenField}. + * Please check {@link CHtml::activeHiddenField} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + */ + public function hiddenField($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeHiddenField($model,$attribute,$htmlOptions); + } + + /** + * Renders a password field for a model attribute. + * This method is a wrapper of {@link CHtml::activePasswordField}. + * Please check {@link CHtml::activePasswordField} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated input field + */ + public function passwordField($model,$attribute,$htmlOptions=array()) + { + return CHtml::activePasswordField($model,$attribute,$htmlOptions); + } + + /** + * Renders a text area for a model attribute. + * This method is a wrapper of {@link CHtml::activeTextArea}. + * Please check {@link CHtml::activeTextArea} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated text area + */ + public function textArea($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeTextArea($model,$attribute,$htmlOptions); + } + + /** + * Renders a file field for a model attribute. + * This method is a wrapper of {@link CHtml::activeFileField}. + * Please check {@link CHtml::activeFileField} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes + * @return string the generated input field + */ + public function fileField($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeFileField($model,$attribute,$htmlOptions); + } + + /** + * Renders a radio button for a model attribute. + * This method is a wrapper of {@link CHtml::activeRadioButton}. + * Please check {@link CHtml::activeRadioButton} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated radio button + */ + public function radioButton($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeRadioButton($model,$attribute,$htmlOptions); + } + + /** + * Renders a checkbox for a model attribute. + * This method is a wrapper of {@link CHtml::activeCheckBox}. + * Please check {@link CHtml::activeCheckBox} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $htmlOptions additional HTML attributes. + * @return string the generated check box + */ + public function checkBox($model,$attribute,$htmlOptions=array()) + { + return CHtml::activeCheckBox($model,$attribute,$htmlOptions); + } + + /** + * Renders a dropdown list for a model attribute. + * This method is a wrapper of {@link CHtml::activeDropDownList}. + * Please check {@link CHtml::activeDropDownList} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data data for generating the list options (value=>display) + * @param array $htmlOptions additional HTML attributes. + * @return string the generated drop down list + */ + public function dropDownList($model,$attribute,$data,$htmlOptions=array()) + { + return CHtml::activeDropDownList($model,$attribute,$data,$htmlOptions); + } + + /** + * Renders a list box for a model attribute. + * This method is a wrapper of {@link CHtml::activeListBox}. + * Please check {@link CHtml::activeListBox} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data data for generating the list options (value=>display) + * @param array $htmlOptions additional HTML attributes. + * @return string the generated list box + */ + public function listBox($model,$attribute,$data,$htmlOptions=array()) + { + return CHtml::activeListBox($model,$attribute,$data,$htmlOptions); + } + + /** + * Renders a checkbox list for a model attribute. + * This method is a wrapper of {@link CHtml::activeCheckBoxList}. + * Please check {@link CHtml::activeCheckBoxList} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data value-label pairs used to generate the check box list. + * @param array $htmlOptions addtional HTML options. + * @return string the generated check box list + */ + public function checkBoxList($model,$attribute,$data,$htmlOptions=array()) + { + return CHtml::activeCheckBoxList($model,$attribute,$data,$htmlOptions); + } + + /** + * Renders a radio button list for a model attribute. + * This method is a wrapper of {@link CHtml::activeRadioButtonList}. + * Please check {@link CHtml::activeRadioButtonList} for detailed information + * about the parameters for this method. + * @param CModel $model the data model + * @param string $attribute the attribute + * @param array $data value-label pairs used to generate the radio button list. + * @param array $htmlOptions addtional HTML options. + * @return string the generated radio button list + */ + public function radioButtonList($model,$attribute,$data,$htmlOptions=array()) + { + return CHtml::activeRadioButtonList($model,$attribute,$data,$htmlOptions); + } + + /** + * Validates one or several models and returns the results in JSON format. + * This is a helper method that simplifies the way of writing AJAX validation code. + * @param mixed $models a single model instance or an array of models. + * @param array $attributes list of attributes that should be validated. Defaults to null, + * meaning any attribute listed in the applicable validation rules of the models should be + * validated. If this parameter is given as a list of attributes, only + * the listed attributes will be validated. + * @param boolean $loadInput whether to load the data from $_POST array in this method. + * If this is true, the model will be populated from <code>$_POST[ModelClass]</code>. + * @return string the JSON representation of the validation error messages. + */ + public static function validate($models, $attributes=null, $loadInput=true) + { + $result=array(); + if(!is_array($models)) + $models=array($models); + foreach($models as $model) + { + if($loadInput && isset($_POST[get_class($model)])) + $model->attributes=$_POST[get_class($model)]; + $model->validate($attributes); + foreach($model->getErrors() as $attribute=>$errors) + $result[CHtml::activeId($model,$attribute)]=$errors; + } + return function_exists('json_encode') ? json_encode($result) : CJSON::encode($result); + } + + /** + * Validates an array of model instances and returns the results in JSON format. + * This is a helper method that simplifies the way of writing AJAX validation code for tabular input. + * @param mixed $models an array of model instances. + * @param array $attributes list of attributes that should be validated. Defaults to null, + * meaning any attribute listed in the applicable validation rules of the models should be + * validated. If this parameter is given as a list of attributes, only + * the listed attributes will be validated. + * @param boolean $loadInput whether to load the data from $_POST array in this method. + * If this is true, the model will be populated from <code>$_POST[ModelClass][$i]</code>. + * @return string the JSON representation of the validation error messages. + */ + public static function validateTabular($models, $attributes=null, $loadInput=true) + { + $result=array(); + if(!is_array($models)) + $models=array($models); + foreach($models as $i=>$model) + { + if($loadInput && isset($_POST[get_class($model)][$i])) + $model->attributes=$_POST[get_class($model)][$i]; + $model->validate($attributes); + foreach($model->getErrors() as $attribute=>$errors) + $result[CHtml::activeId($model,'['.$i.']'.$attribute)]=$errors; + } + return function_exists('json_encode') ? json_encode($result) : CJSON::encode($result); + } +}
\ No newline at end of file diff --git a/framework/web/widgets/CAutoComplete.php b/framework/web/widgets/CAutoComplete.php new file mode 100644 index 0000000..8803109 --- /dev/null +++ b/framework/web/widgets/CAutoComplete.php @@ -0,0 +1,291 @@ +<?php +/** + * CAutoComplete class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CAutoComplete generates an auto-complete text field. + * + * CAutoComplete is based on the {@link http://plugins.jquery.com/project/autocompletex jQuery Autocomplete}. + * + * This class is deprecated since Yii 1.1.3. Consider using CJuiAutoComplete. + * There is {@link http://www.learningjquery.com/2010/06/autocomplete-migration-guide a good migration guide from the author of both JavaScript solutions}. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CAutoComplete.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + * @deprecated in 1.1.3 + */ +class CAutoComplete extends CInputWidget +{ + /** + * @var boolean whether to show the autocomplete using a text area. Defaults to false, + * meaning a text field is used. + */ + public $textArea=false; + /** + * @var array data that would be saved as client-side data to provide candidate selections. + * Each array element can be string or an associative array. + * The {@link url} property will be ignored if this property is set. + * @see url + */ + public $data; + /** + * @var string|array the URL that can return the candidate selections. + * A 'q' GET parameter will be sent with the URL which contains what the user has entered so far. + * If the URL is given as an array, it is considered as a route to a controller action and will + * be used to generate a URL using {@link CController::createUrl}; + * If the URL is an empty string, the currently requested URL is used. + * This property will be ignored if {@link data} is set. + * @see data + */ + public $url=''; + /** + * @var mixed the CSS file used for the widget. Defaults to null, meaning + * using the default CSS file included together with the widget. + * If false, no CSS file will be used. Otherwise, the specified CSS file + * will be included when using this widget. + */ + public $cssFile; + /** + * @var integer the minimum number of characters a user has to type before + * the autocompleter activates. Defaults to 1. + */ + public $minChars; + /** + * @var integer the delay in milliseconds the autocompleter waits after + * a keystroke to activate itself. Defaults to 400. + */ + public $delay; + /** + * @var integer the number of backend query results to store in cache. + * If set to 1 (the current result), no caching will happen. Must be >= 1. Defaults to 10. + */ + public $cacheLength; + /** + * @var boolean whether or not the autocompleter can use a cache for more + * specific queries. This means that all matches of "foot" are a subset + * of all matches for "foo". Usually this is true, and using this options + * decreases server load and increases performance. Only useful with + * cacheLength settings bigger than one, like 10. Defaults to true. + */ + public $matchSubset; + /** + * @var boolean whether or not the comparison is case sensitive. Important + * only if you use caching. Defaults to false. + */ + public $matchCase; + /** + * @var boolean whether or not the comparison looks inside + * (i.e. does "ba" match "foo bar") the search results. Important only if + * you use caching. Don't mix with autofill. Defaults to false. + */ + public $matchContains; + /** + * @var boolean if set to true, the autocompleter will only allow results that + * are presented by the backend. Note that illegal values result in an empty + * input box. Defaults to false. + */ + public $mustMatch; + /** + * @var boolean if this is set to true, the first autocomplete value will + * be automatically selected on tab/return, even if it has not been handpicked + * by keyboard or mouse action. If there is a handpicked (highlighted) result, + * that result will take precedence. Defaults to true. + */ + public $selectFirst; + /** + * @var array extra parameters for the backend. If you were to specify + * array('bar'=>4), the autocompleter would call the backend with a GET + * parameter 'bar' 4. The param can be a function that is called to calculate + * the param before each request. + */ + public $extraParams; + /** + * @var string a javascript function that provides advanced markup for an item. + * For each row of results, this function will be called. The returned value will + * be displayed inside an LI element in the results list. Autocompleter will + * provide 4 parameters: the results row, the position of the row in the list of + * results (starting at 1), the number of items in the list of results and the search term. + * The default behavior assumes that a single row contains a single value. + */ + public $formatItem; + /** + * @var string a javascript function that can be used to limit the data that autocomplete + * searches for matches. For example, there may be items you want displayed to the user, + * but don't want included in the data that's searched. The function is called with the same arguments + * as {@link formatItem}. Defaults to formatItem. + */ + public $formatMatch; + /** + * @var string a javascript function that provides the formatting for the value to be + * put into the input field. Again three arguments: Data, position (starting with one) and + * total number of data. The default behavior assumes either plain data to use as result + * or uses the same value as provided by formatItem. + */ + public $formatResult; + /** + * @var boolean whether to allow more than one autocompleted-value to enter. Defaults to false. + */ + public $multiple; + /** + * @var string seperator to put between values when using multiple option. Defaults to ", ". + */ + public $multipleSeparator; + /** + * @var integer specify a custom width for the select box. Defaults to the width of the input element. + */ + public $width; + /** + * @var boolean fill the textinput while still selecting a value, replacing the value + * if more is typed or something else is selected. Defaults to false. + */ + public $autoFill; + /** + * @var integer limit the number of items in the select box. Is also sent as + * a "limit" parameter with a remote request. Defaults to 10. + */ + public $max; + /** + * @var boolean|string Whether and how to highlight matches in the select box. + * Set to false to disable. Set to a javascript function to customize. + * The function gets the value as the first argument and the search term as the + * second and must return the formatted value. Defaults to Wraps the search term in a <strong> element. + */ + public $highlight; + /** + * @var boolean whether to scroll when more results than configured via scrollHeight are available. Defaults to true. + */ + public $scroll; + /** + * @var integer height of scrolled autocomplete control in pixels. Defaults to 180. + */ + public $scrollHeight; + /** + * @var string the CSS class for the input element. Defaults to "ac_input". + */ + public $inputClass; + /** + * @var string the CSS class for the dropdown list. Defaults to "ac_results". + */ + public $resultsClass; + /** + * @var string the CSS class used when the data is being loaded from backend. Defaults to "ac_loading". + */ + public $loadingClass; + /** + * @var array additional options that can be passed to the constructor of the autocomplete js object. + * This allows you to override existing functions of the autocomplete js class (e.g. the parse() function) + * + * If you want to provide JavaScript native code, you have to prefix the string with js: otherwise it will + * be enclosed by quotes. + */ + public $options=array(); + /** + * @var string the chain of method calls that would be appended at the end of the autocomplete constructor. + * For example, ".result(function(...){})" would cause the specified js function to execute + * when the user selects an option. + */ + public $methodChain; + + /** + * Initializes the widget. + * This method registers all needed client scripts and renders + * the autocomplete input. + */ + public function init() + { + list($name,$id)=$this->resolveNameID(); + if(isset($this->htmlOptions['id'])) + $id=$this->htmlOptions['id']; + else + $this->htmlOptions['id']=$id; + if(isset($this->htmlOptions['name'])) + $name=$this->htmlOptions['name']; + + $this->registerClientScript(); + + if($this->hasModel()) + { + $field=$this->textArea ? 'activeTextArea' : 'activeTextField'; + echo CHtml::$field($this->model,$this->attribute,$this->htmlOptions); + } + else + { + $field=$this->textArea ? 'textArea' : 'textField'; + echo CHtml::$field($name,$this->value,$this->htmlOptions); + } + } + + /** + * Registers the needed CSS and JavaScript. + */ + public function registerClientScript() + { + $id=$this->htmlOptions['id']; + + $acOptions=$this->getClientOptions(); + $options=$acOptions===array()?'{}' : CJavaScript::encode($acOptions); + + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('autocomplete'); + if($this->data!==null) + $data=CJavaScript::encode($this->data); + else + { + $url=CHtml::normalizeUrl($this->url); + $data='"'.$url.'"'; + } + $cs->registerScript('Yii.CAutoComplete#'.$id,"jQuery(\"#{$id}\").legacyautocomplete($data,{$options}){$this->methodChain};"); + + if($this->cssFile!==false) + self::registerCssFile($this->cssFile); + } + + /** + * Registers the needed CSS file. + * @param string $url the CSS URL. If null, a default CSS URL will be used. + */ + public static function registerCssFile($url=null) + { + $cs=Yii::app()->getClientScript(); + if($url===null) + $url=$cs->getCoreScriptUrl().'/autocomplete/jquery.autocomplete.css'; + $cs->registerCssFile($url); + } + + /** + * @return array the javascript options + */ + protected function getClientOptions() + { + static $properties=array( + 'minChars', 'delay', 'cacheLength', 'matchSubset', + 'matchCase', 'matchContains', 'mustMatch', 'selectFirst', + 'extraParams', 'multiple', 'multipleSeparator', 'width', + 'autoFill', 'max', 'scroll', 'scrollHeight', 'inputClass', + 'formatItem', 'formatMatch', 'formatResult', 'highlight', + 'resultsClass', 'loadingClass'); + static $functions=array('formatItem', 'formatMatch', 'formatResult', 'highlight'); + + $options=$this->options; + foreach($properties as $property) + { + if($this->$property!==null) + $options[$property]=$this->$property; + } + foreach($functions as $func) + { + if(is_string($this->$func) && strncmp($this->$func,'js:',3)) + $options[$func]='js:'.$this->$func; + } + + return $options; + } +} diff --git a/framework/web/widgets/CClipWidget.php b/framework/web/widgets/CClipWidget.php new file mode 100644 index 0000000..d963947 --- /dev/null +++ b/framework/web/widgets/CClipWidget.php @@ -0,0 +1,53 @@ +<?php +/** + * CClipWidget class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CClipWidget records its content and makes it available elsewhere. + * + * Content rendered between its {@link init()} and {@link run()} calls are saved + * as a clip in the controller. The clip is named after the widget ID. + * + * See {@link CBaseController::beginClip} and {@link CBaseController::endClip} + * for a shortcut usage of CClipWidget. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CClipWidget.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @package system.web.widgets + * @since 1.0 + */ +class CClipWidget extends CWidget +{ + /** + * @var boolean whether to render the clip content in place. Defaults to false, + * meaning the captured clip will not be displayed. + */ + public $renderClip=false; + + /** + * Starts recording a clip. + */ + public function init() + { + ob_start(); + ob_implicit_flush(false); + } + + /** + * Ends recording a clip. + * This method stops output buffering and saves the rendering result as a named clip in the controller. + */ + public function run() + { + $clip=ob_get_clean(); + if($this->renderClip) + echo $clip; + $this->getController()->getClips()->add($this->getId(),$clip); + } +}
\ No newline at end of file diff --git a/framework/web/widgets/CContentDecorator.php b/framework/web/widgets/CContentDecorator.php new file mode 100644 index 0000000..9a3cff9 --- /dev/null +++ b/framework/web/widgets/CContentDecorator.php @@ -0,0 +1,82 @@ +<?php +/** + * CContentDecorator class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CContentDecorator decorates the content it encloses with the specified view. + * + * CContentDecorator is mostly used to implement nested layouts, i.e., a layout + * is embedded within another layout. {@link CBaseController} defines a pair of + * convenient methods to use CContentDecorator: + * <pre> + * $this->beginContent('path/to/view'); + * // ... content to be decorated + * $this->endContent(); + * </pre> + * + * The property {@link view} specifies the name of the view that is used to + * decorate the content. In the view, the content being decorated may be + * accessed with variable <code>$content</code>. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CContentDecorator.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CContentDecorator extends COutputProcessor +{ + /** + * @var mixed the name of the view that will be used to decorate the captured content. + * If this property is null (default value), 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}. + */ + public $view; + /** + * @var array the variables (name=>value) to be extracted and made available in the decorative view. + */ + public $data=array(); + + /** + * Processes the captured output. + * This method decorates the output with the specified {@link view}. + * @param string $output the captured output to be processed + */ + public function processOutput($output) + { + $output=$this->decorate($output); + parent::processOutput($output); + } + + /** + * Decorates the content by rendering a view and embedding the content in it. + * The content being embedded can be accessed in the view using variable <code>$content</code> + * The decorated content will be displayed directly. + * @param string $content the content to be decorated + * @return string the decorated content + */ + protected function decorate($content) + { + $owner=$this->getOwner(); + if($this->view===null) + $viewFile=Yii::app()->getController()->getLayoutFile(null); + else + $viewFile=$owner->getViewFile($this->view); + if($viewFile!==false) + { + $data=$this->data; + $data['content']=$content; + return $owner->renderFile($viewFile,$data,true); + } + else + return $content; + } +} diff --git a/framework/web/widgets/CFilterWidget.php b/framework/web/widgets/CFilterWidget.php new file mode 100644 index 0000000..42cb8d4 --- /dev/null +++ b/framework/web/widgets/CFilterWidget.php @@ -0,0 +1,75 @@ +<?php +/** + * CFilterWidget class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CFilterWidget is the base class for widgets that can also be used as filters. + * + * Derived classes may need to override the following methods: + * <ul> + * <li>{@link CWidget::init()} : called when this is object is used as a widget and needs initialization.</li> + * <li>{@link CWidget::run()} : called when this is object is used as a widget.</li> + * <li>{@link filter()} : the filtering method called when this object is used as an action filter.</li> + * </ul> + * + * CFilterWidget provides all properties and methods of {@link CWidget} and {@link CFilter}. + * + * @property boolean $isFilter Whether this widget is used as a filter. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CFilterWidget.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CFilterWidget extends CWidget implements IFilter +{ + /** + * @var boolean whether to stop the action execution when this widget is used as a filter. + * This property should be changed only in {@link CWidget::init} method. + * Defaults to false, meaning the action should be executed. + */ + public $stopAction=false; + + private $_isFilter; + + /** + * Constructor. + * @param CBaseController $owner owner/creator of this widget. It could be either a widget or a controller. + */ + public function __construct($owner=null) + { + parent::__construct($owner); + $this->_isFilter=($owner===null); + } + + /** + * @return boolean whether this widget is used as a filter. + */ + public function getIsFilter() + { + return $this->_isFilter; + } + + /** + * Performs the filtering. + * The default implementation simply calls {@link init()}, + * {@link CFilterChain::run()} and {@link run()} in order + * Derived classes may want to override this method to change this behavior. + * @param CFilterChain $filterChain the filter chain that the filter is on. + */ + public function filter($filterChain) + { + $this->init(); + if(!$this->stopAction) + { + $filterChain->run(); + $this->run(); + } + } +}
\ No newline at end of file diff --git a/framework/web/widgets/CFlexWidget.php b/framework/web/widgets/CFlexWidget.php new file mode 100644 index 0000000..f6ff982 --- /dev/null +++ b/framework/web/widgets/CFlexWidget.php @@ -0,0 +1,122 @@ +<?php +/** + * CFlexWidget class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CFlexWidget embeds a Flex 3.x application into a page. + * + * To use CFlexWidget, set {@link name} to be the Flex application name + * (without the .swf suffix), and set {@link baseUrl} to be URL (without the ending slash) + * of the directory containing the SWF file of the Flex application. + * + * @property string $flashVarsAsString The flash parameter string. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CFlexWidget.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CFlexWidget extends CWidget +{ + /** + * @var string name of the Flex application. + * This should be the SWF file name without the ".swf" suffix. + */ + public $name; + /** + * @var string the base URL of the Flex application. + * This refers to the URL of the directory containing the SWF file. + */ + public $baseUrl; + /** + * @var string width of the application region. Defaults to 450. + */ + public $width='100%'; + /** + * @var string height of the application region. Defaults to 300. + */ + public $height='100%'; + /** + * @var string quality of the animation. Defaults to 'high'. + */ + public $quality='high'; + /** + * @var string background color of the application region. Defaults to '#FFFFFF', meaning white. + */ + public $bgColor='#FFFFFF'; + /** + * @var string align of the application region. Defaults to 'middle'. + */ + public $align='middle'; + /** + * @var string the access method of the script. Defaults to 'sameDomain'. + */ + public $allowScriptAccess='sameDomain'; + /** + * @var boolean whether to allow running the Flash in full screen mode. Defaults to false. + * @since 1.1.1 + */ + public $allowFullScreen=false; + /** + * @var string the HTML content to be displayed if Flash player is not installed. + */ + public $altHtmlContent; + /** + * @var boolean whether history should be enabled. Defaults to true. + */ + public $enableHistory=true; + /** + * @var array parameters to be passed to the Flex application. + */ + public $flashVars=array(); + + /** + * Renders the widget. + */ + public function run() + { + if(empty($this->name)) + throw new CException(Yii::t('yii','CFlexWidget.name cannot be empty.')); + if(empty($this->baseUrl)) + throw new CException(Yii::t('yii','CFlexWidget.baseUrl cannot be empty.')); + if($this->altHtmlContent===null) + $this->altHtmlContent=Yii::t('yii','This content requires the <a href="http://www.adobe.com/go/getflash/">Adobe Flash Player</a>.'); + + $this->registerClientScript(); + + $this->render('flexWidget'); + } + + /** + * Registers the needed CSS and JavaScript. + */ + public function registerClientScript() + { + $cs=Yii::app()->getClientScript(); + $cs->registerScriptFile($this->baseUrl.'/AC_OETags.js'); + + if($this->enableHistory) + { + $cs->registerCssFile($this->baseUrl.'/history/history.css'); + $cs->registerScriptFile($this->baseUrl.'/history/history.js'); + } + } + + /** + * Generates the properly quoted flash parameter string. + * @return string the flash parameter string. + */ + public function getFlashVarsAsString() + { + $params=array(); + foreach($this->flashVars as $k=>$v) + $params[]=urlencode($k).'='.urlencode($v); + return CJavaScript::quote(implode('&',$params)); + } +}
\ No newline at end of file diff --git a/framework/web/widgets/CHtmlPurifier.php b/framework/web/widgets/CHtmlPurifier.php new file mode 100644 index 0000000..c0f13fe --- /dev/null +++ b/framework/web/widgets/CHtmlPurifier.php @@ -0,0 +1,82 @@ +<?php +/** + * CHtmlPurifier class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +if(!class_exists('HTMLPurifier_Bootstrap',false)) +{ + require_once(Yii::getPathOfAlias('system.vendors.htmlpurifier').DIRECTORY_SEPARATOR.'HTMLPurifier.standalone.php'); + HTMLPurifier_Bootstrap::registerAutoload(); +} + +/** + * CHtmlPurifier is wrapper of {@link http://htmlpurifier.org HTML Purifier}. + * + * CHtmlPurifier removes all malicious code (better known as XSS) with a thoroughly audited, + * secure yet permissive whitelist. It will also make sure the resulting code + * is standard-compliant. + * + * CHtmlPurifier can be used as either a widget or a controller filter. + * + * Note: since HTML Purifier is a big package, its performance is not very good. + * You should consider either caching the purification result or purifying the user input + * before saving to database. + * + * Usage as a class: + * <pre> + * $p = new CHtmlPurifier(); + * $p->options = array('URI.AllowedSchemes'=>array( + * 'http' => true, + * 'https' => true, + * )); + * $text = $p->purify($text); + * </pre> + * + * Usage as validation rule: + * <pre> + * array('text','filter','filter'=>array($obj=new CHtmlPurifier(),'purify')), + * </pre> + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CHtmlPurifier.php 3541 2012-01-17 07:27:46Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CHtmlPurifier extends COutputProcessor +{ + /** + * @var mixed the options to be passed to HTML Purifier instance. + * This can be a HTMLPurifier_Config object, an array of directives (Namespace.Directive => Value) + * or the filename of an ini file. + * @see http://htmlpurifier.org/live/configdoc/plain.html + */ + public $options=null; + + /** + * Processes the captured output. + * This method purifies the output using {@link http://htmlpurifier.org HTML Purifier}. + * @param string $output the captured output to be processed + */ + public function processOutput($output) + { + $output=$this->purify($output); + parent::processOutput($output); + } + + /** + * Purifies the HTML content by removing malicious code. + * @param string $content the content to be purified. + * @return string the purified content + */ + public function purify($content) + { + $purifier=new HTMLPurifier($this->options); + $purifier->config->set('Cache.SerializerPath',Yii::app()->getRuntimePath()); + return $purifier->purify($content); + } +} diff --git a/framework/web/widgets/CInputWidget.php b/framework/web/widgets/CInputWidget.php new file mode 100644 index 0000000..f08fa82 --- /dev/null +++ b/framework/web/widgets/CInputWidget.php @@ -0,0 +1,81 @@ +<?php +/** + * CInputWidget class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CInputWidget is the base class for widgets that collect user inputs. + * + * CInputWidget declares properties common among input widgets. An input widget + * can be associated with a data model and an attribute, or a name and a value. + * If the former, the name and the value will be generated automatically. + * Child classes may use {@link resolveNameID} and {@link hasModel}. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CInputWidget.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +abstract class CInputWidget extends CWidget +{ + /** + * @var CModel the data model associated with this widget. + */ + public $model; + /** + * @var string the attribute associated with this widget. + * The name can contain square brackets (e.g. 'name[1]') which is used to collect tabular data input. + */ + public $attribute; + /** + * @var string the input name. This must be set if {@link model} is not set. + */ + public $name; + /** + * @var string the input value + */ + public $value; + /** + * @var array additional HTML options to be rendered in the input tag + */ + public $htmlOptions=array(); + + + /** + * @return array the name and the ID of the input. + */ + protected function resolveNameID() + { + if($this->name!==null) + $name=$this->name; + else if(isset($this->htmlOptions['name'])) + $name=$this->htmlOptions['name']; + else if($this->hasModel()) + $name=CHtml::activeName($this->model,$this->attribute); + else + throw new CException(Yii::t('yii','{class} must specify "model" and "attribute" or "name" property values.',array('{class}'=>get_class($this)))); + + if(($id=$this->getId(false))===null) + { + if(isset($this->htmlOptions['id'])) + $id=$this->htmlOptions['id']; + else + $id=CHtml::getIdByName($name); + } + + return array($name,$id); + } + + /** + * @return boolean whether this widget is associated with a data model. + */ + protected function hasModel() + { + return $this->model instanceof CModel && $this->attribute!==null; + } +}
\ No newline at end of file diff --git a/framework/web/widgets/CMarkdown.php b/framework/web/widgets/CMarkdown.php new file mode 100644 index 0000000..d2ce142 --- /dev/null +++ b/framework/web/widgets/CMarkdown.php @@ -0,0 +1,118 @@ +<?php +/** + * CMarkdown class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CMarkdown converts the captured content from markdown syntax to HTML code. + * + * CMarkdown can be used as either a widget or a filter. It is a wrapper of {@link CMarkdownParser}. + * CMarkdown adds an additional option {@link purifyOutput} which can be set true + * so that the converted HTML code is purified before being displayed. + * + * For details about the markdown syntax, please check the following: + * <ul> + * <li>{@link http://daringfireball.net/projects/markdown/syntax official markdown syntax}</li> + * <li>{@link http://michelf.com/projects/php-markdown/extra/ markdown extra syntax}</li> + * <li>{@link CMarkdownParser markdown with syntax highlighting}</li> + * </ul> + * + * @property CMarkdownParser $markdownParser The parser instance. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CMarkdown.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CMarkdown extends COutputProcessor +{ + /** + * @var mixed the CSS file used for the widget. Defaults to null, meaning + * using the default CSS file included together with the widget. + * If false, no CSS file will be used. Otherwise, the specified CSS file + * will be included when using this widget. + */ + public $cssFile; + /** + * @var boolean whether to use {@link CHtmlPurifier} to purify the generated HTML code. Defaults to false. + */ + public $purifyOutput=false; + + private $_parser; + + /** + * Processes the captured output. + * This method converts the content in markdown syntax to HTML code. + * If {@link purifyOutput} is true, the HTML code will also be purified. + * @param string $output the captured output to be processed + * @see convert + */ + public function processOutput($output) + { + $output=$this->transform($output); + if($this->purifyOutput) + { + $purifier=new CHtmlPurifier; + $output=$purifier->purify($output); + } + parent::processOutput($output); + } + + /** + * Converts the content in markdown syntax to HTML code. + * This method uses {@link CMarkdownParser} to do the conversion. + * @param string $output the content to be converted + * @return string the converted content + */ + public function transform($output) + { + $this->registerClientScript(); + return $this->getMarkdownParser()->transform($output); + } + + /** + * Registers the needed CSS and JavaScript. + */ + public function registerClientScript() + { + if($this->cssFile!==false) + self::registerCssFile($this->cssFile); + } + + /** + * Registers the needed CSS file. + * @param string $url the CSS URL. If null, a default CSS URL will be used. + */ + public static function registerCssFile($url=null) + { + CTextHighlighter::registerCssFile($url); + } + + /** + * Returns the markdown parser instance. + * This method calls {@link createMarkdownParser} to create the parser instance. + * Call this method multipe times will only return the same instance. + * @return CMarkdownParser the parser instance + */ + public function getMarkdownParser() + { + if($this->_parser===null) + $this->_parser=$this->createMarkdownParser(); + return $this->_parser; + } + + /** + * Creates a markdown parser. + * By default, this method creates a {@link CMarkdownParser} instance. + * @return CMarkdownParser the markdown parser. + */ + protected function createMarkdownParser() + { + return new CMarkdownParser; + } +} diff --git a/framework/web/widgets/CMaskedTextField.php b/framework/web/widgets/CMaskedTextField.php new file mode 100644 index 0000000..f7bf40f --- /dev/null +++ b/framework/web/widgets/CMaskedTextField.php @@ -0,0 +1,113 @@ +<?php +/** + * CMaskedTextField class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CMaskedTextField generates a masked text field. + * + * CMaskedTextField is similar to {@link CHtml::textField} except that + * an input mask will be used to help users enter properly formatted data. + * The masked text field is implemented based on the jQuery masked input plugin + * (see {@link http://digitalbush.com/projects/masked-input-plugin}). + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CMaskedTextField.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CMaskedTextField extends CInputWidget +{ + /** + * @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined: + * <ul> + * <li>a: represents an alpha character (A-Z,a-z).</li> + * <li>9: represents a numeric character (0-9).</li> + * <li>*: represents an alphanumeric character (A-Z,a-z,0-9).</li> + * <li>?: anything listed after '?' within the mask is considered optional user input.</li> + * </ul> + * Additional characters can be defined by specifying the {@link charMap} property. + */ + public $mask; + /** + * @var array the mapping between mask characters and the corresponding patterns. + * For example, array('~'=>'[+-]') specifies that the '~' character expects '+' or '-' input. + * Defaults to null, meaning using the map as described in {@link mask}. + */ + public $charMap; + /** + * @var string the character prompting for user input. Defaults to underscore '_'. + */ + public $placeholder; + /** + * @var string a JavaScript function callback that will be invoked when user finishes the input. + */ + public $completed; + + /** + * Executes the widget. + * This method registers all needed client scripts and renders + * the text field. + */ + public function run() + { + if($this->mask=='') + throw new CException(Yii::t('yii','Property CMaskedTextField.mask cannot be empty.')); + + list($name,$id)=$this->resolveNameID(); + if(isset($this->htmlOptions['id'])) + $id=$this->htmlOptions['id']; + else + $this->htmlOptions['id']=$id; + if(isset($this->htmlOptions['name'])) + $name=$this->htmlOptions['name']; + + $this->registerClientScript(); + + if($this->hasModel()) + echo CHtml::activeTextField($this->model,$this->attribute,$this->htmlOptions); + else + echo CHtml::textField($name,$this->value,$this->htmlOptions); + } + + /** + * Registers the needed CSS and JavaScript. + */ + public function registerClientScript() + { + $id=$this->htmlOptions['id']; + $miOptions=$this->getClientOptions(); + $options=$miOptions!==array() ? ','.CJavaScript::encode($miOptions) : ''; + $js=''; + if(is_array($this->charMap)) + $js.='jQuery.mask.definitions='.CJavaScript::encode($this->charMap).";\n"; + $js.="jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});"; + + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('maskedinput'); + $cs->registerScript('Yii.CMaskedTextField#'.$id,$js); + } + + /** + * @return array the options for the text field + */ + protected function getClientOptions() + { + $options=array(); + if($this->placeholder!==null) + $options['placeholder']=$this->placeholder; + if(is_string($this->completed)) + { + if(strncmp($this->completed,'js:',3)) + $options['completed']='js:'.$this->completed; + else + $options['completed']=$this->completed; + } + return $options; + } +}
\ No newline at end of file diff --git a/framework/web/widgets/CMultiFileUpload.php b/framework/web/widgets/CMultiFileUpload.php new file mode 100644 index 0000000..60ef23e --- /dev/null +++ b/framework/web/widgets/CMultiFileUpload.php @@ -0,0 +1,142 @@ +<?php +/** + * CMultiFileUpload class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CMultiFileUpload generates a file input that can allow uploading multiple files at a time. + * + * This is based on the {@link http://www.fyneworks.com/jquery/multiple-file-upload/ jQuery Multi File Upload plugin}. + * The uploaded file information can be accessed via $_FILES[widget-name], which gives an array of the uploaded + * files. Note, you have to set the enclosing form's 'enctype' attribute to be 'multipart/form-data'. + * + * Example: + * <pre> + * <?php + * $this->widget('CMultiFileUpload', array( + * 'model'=>$model, + * 'attribute'=>'files', + * 'accept'=>'jpg|gif', + * 'options'=>array( + * 'onFileSelect'=>'function(e, v, m){ alert("onFileSelect - "+v) }', + * 'afterFileSelect'=>'function(e, v, m){ alert("afterFileSelect - "+v) }', + * 'onFileAppend'=>'function(e, v, m){ alert("onFileAppend - "+v) }', + * 'afterFileAppend'=>'function(e, v, m){ alert("afterFileAppend - "+v) }', + * 'onFileRemove'=>'function(e, v, m){ alert("onFileRemove - "+v) }', + * 'afterFileRemove'=>'function(e, v, m){ alert("afterFileRemove - "+v) }', + * ), + * )); + * ?> + * </pre> + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CMultiFileUpload.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CMultiFileUpload extends CInputWidget +{ + /** + * @var string the file types that are allowed (eg "gif|jpg"). + * Note, the server side still needs to check if the uploaded files have allowed types. + */ + public $accept; + /** + * @var integer the maximum number of files that can be uploaded. If -1, it means no limits. Defaults to -1. + */ + public $max=-1; + /** + * @var string the label for the remove button. Defaults to "Remove". + */ + public $remove; + /** + * @var string message that is displayed when a file type is not allowed. + */ + public $denied; + /** + * @var string message that is displayed when a file is selected. + */ + public $selected; + /** + * @var string message that is displayed when a file appears twice. + */ + public $duplicate; + /** + * @var string the message template for displaying the uploaded file name + * @since 1.1.3 + */ + public $file; + /** + * @var array additional options that can be passed to the constructor of the multifile js object. + * @since 1.1.7 + */ + public $options=array(); + + + /** + * Runs the widget. + * This method registers all needed client scripts and renders + * the multiple file uploader. + */ + public function run() + { + list($name,$id)=$this->resolveNameID(); + if(substr($name,-2)!=='[]') + $name.='[]'; + if(isset($this->htmlOptions['id'])) + $id=$this->htmlOptions['id']; + else + $this->htmlOptions['id']=$id; + $this->registerClientScript(); + echo CHtml::fileField($name,'',$this->htmlOptions); + } + + /** + * Registers the needed CSS and JavaScript. + */ + public function registerClientScript() + { + $id=$this->htmlOptions['id']; + + $options=$this->getClientOptions(); + $options=$options===array()? '' : CJavaScript::encode($options); + + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('multifile'); + $cs->registerScript('Yii.CMultiFileUpload#'.$id,"jQuery(\"#{$id}\").MultiFile({$options});"); + } + + /** + * @return array the javascript options + */ + protected function getClientOptions() + { + $options=$this->options; + foreach(array('onFileRemove','afterFileRemove','onFileAppend','afterFileAppend','onFileSelect','afterFileSelect') as $event) + { + if(isset($options[$event]) && strpos($options[$event],'js:')!==0) + $options[$event]='js:'.$options[$event]; + } + + if($this->accept!==null) + $options['accept']=$this->accept; + if($this->max>0) + $options['max']=$this->max; + + $messages=array(); + foreach(array('remove','denied','selected','duplicate','file') as $messageName) + { + if($this->$messageName!==null) + $messages[$messageName]=$this->$messageName; + } + if($messages!==array()) + $options['STRING']=$messages; + + return $options; + } +} diff --git a/framework/web/widgets/COutputCache.php b/framework/web/widgets/COutputCache.php new file mode 100644 index 0000000..1878ba2 --- /dev/null +++ b/framework/web/widgets/COutputCache.php @@ -0,0 +1,347 @@ +<?php +/** + * COutputCache class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * COutputCache enables caching the output generated by an action or a view fragment. + * + * If the output to be displayed is found valid in cache, the cached + * version will be displayed instead, which saves the time for generating + * the original output. + * + * Since COutputCache extends from {@link CFilterWidget}, it can be used + * as either a filter (for action caching) or a widget (for fragment caching). + * For the latter, the shortcuts {@link CBaseController::beginCache()} and {@link CBaseController::endCache()} + * are often used instead, like the following in a view file: + * <pre> + * if($this->beginCache('cacheName',array('property1'=>'value1',...)) + * { + * // ... display the content to be cached here + * $this->endCache(); + * } + * </pre> + * + * COutputCache must work with a cache application component specified via {@link cacheID}. + * If the cache application component is not available, COutputCache will be disabled. + * + * The validity of the cached content is determined based on two factors: + * the {@link duration} and the cache {@link dependency}. + * The former specifies the number of seconds that the data can remain + * valid in cache (defaults to 60s), while the latter specifies conditions + * that the cached data depends on. If a dependency changes, + * (e.g. relevant data in DB are updated), the cached data will be invalidated. + * For more details about cache dependency, see {@link CCacheDependency}. + * + * Sometimes, it is necessary to turn off output caching only for certain request types. + * For example, we only want to cache a form when it is initially requested; + * any subsequent display of the form should not be cached because it contains user input. + * We can set {@link requestTypes} to be <code>array('GET')</code> to accomplish this task. + * + * The content fetched from cache may be variated with respect to + * some parameters. COutputCache supports four kinds of variations: + * <ul> + * <li>{@link varyByRoute}: this specifies whether the cached content + * should be varied with the requested route (controller and action)</li> + * <li>{@link varyByParam}: this specifies a list of GET parameter names + * and uses the corresponding values to determine the version of the cached content.</li> + * <li>{@link varyBySession}: this specifies whether the cached content + * should be varied with the user session.</li> + * <li>{@link varyByExpression}: this specifies whether the cached content + * should be varied with the result of the specified PHP expression.</li> + * </ul> + * For more advanced variation, override {@link getBaseCacheKey()} method. + * + * @property boolean $isContentCached Whether the content can be found from cache. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: COutputCache.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class COutputCache extends CFilterWidget +{ + /** + * Prefix to the keys for storing cached data + */ + const CACHE_KEY_PREFIX='Yii.COutputCache.'; + + /** + * @var integer number of seconds that the data can remain in cache. Defaults to 60 seconds. + * If it is 0, existing cached content would be removed from the cache. + * If it is a negative value, the cache will be disabled (any existing cached content will + * remain in the cache.) + * + * Note, if cache dependency changes or cache space is limited, + * the data may be purged out of cache earlier. + */ + public $duration=60; + /** + * @var boolean whether the content being cached should be differentiated according to route. + * A route consists of the requested controller ID and action ID. + * Defaults to true. + */ + public $varyByRoute=true; + /** + * @var boolean whether the content being cached should be differentiated according to user sessions. Defaults to false. + */ + public $varyBySession=false; + /** + * @var array list of GET parameters that should participate in cache key calculation. + * By setting this property, the output cache will use different cached data + * for each different set of GET parameter values. + */ + public $varyByParam; + /** + * @var string a PHP expression whose result is used in the cache key calculation. + * By setting this property, the output cache will use different cached data + * for each different expression result. + * 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($cache) { ... } + * </pre> + * where $cache refers to the output cache component. + */ + public $varyByExpression; + /** + * @var array list of request types (e.g. GET, POST) for which the cache should be enabled only. + * Defaults to null, meaning all request types. + */ + public $requestTypes; + /** + * @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.) + */ + public $cacheID='cache'; + /** + * @var mixed the dependency that the cached content depends on. + * This can be either an object implementing {@link ICacheDependency} interface or an array + * specifying the configuration of the dependency object. For example, + * <pre> + * array( + * 'class'=>'CDbCacheDependency', + * 'sql'=>'SELECT MAX(lastModified) FROM Post', + * ) + * </pre> + * would make the output cache depends on the last modified time of all posts. + * If any post has its modification time changed, the cached content would be invalidated. + */ + public $dependency; + + private $_key; + private $_cache; + private $_contentCached; + private $_content; + private $_actions; + + /** + * Performs filtering before the action is executed. + * This method is meant to be overridden by child classes if begin-filtering is needed. + * @param CFilterChain $filterChain list of filters being applied to an action + * @return boolean whether the filtering process should stop after this filter. Defaults to false. + */ + public function filter($filterChain) + { + if(!$this->getIsContentCached()) + $filterChain->run(); + $this->run(); + } + + /** + * Marks the start of content to be cached. + * Content displayed after this method call and before {@link endCache()} + * will be captured and saved in cache. + * This method does nothing if valid content is already found in cache. + */ + public function init() + { + if($this->getIsContentCached()) + $this->replayActions(); + else if($this->_cache!==null) + { + $this->getController()->getCachingStack()->push($this); + ob_start(); + ob_implicit_flush(false); + } + } + + /** + * Marks the end of content to be cached. + * Content displayed before this method call and after {@link init()} + * will be captured and saved in cache. + * This method does nothing if valid content is already found in cache. + */ + public function run() + { + if($this->getIsContentCached()) + { + if($this->getController()->isCachingStackEmpty()) + echo $this->getController()->processDynamicOutput($this->_content); + else + echo $this->_content; + } + else if($this->_cache!==null) + { + $this->_content=ob_get_clean(); + $this->getController()->getCachingStack()->pop(); + $data=array($this->_content,$this->_actions); + if(is_array($this->dependency)) + $this->dependency=Yii::createComponent($this->dependency); + $this->_cache->set($this->getCacheKey(),$data,$this->duration,$this->dependency); + + if($this->getController()->isCachingStackEmpty()) + echo $this->getController()->processDynamicOutput($this->_content); + else + echo $this->_content; + } + } + + /** + * @return boolean whether the content can be found from cache + */ + public function getIsContentCached() + { + if($this->_contentCached!==null) + return $this->_contentCached; + else + return $this->_contentCached=$this->checkContentCache(); + } + + /** + * Looks for content in cache. + * @return boolean whether the content is found in cache. + */ + protected function checkContentCache() + { + if((empty($this->requestTypes) || in_array(Yii::app()->getRequest()->getRequestType(),$this->requestTypes)) + && ($this->_cache=$this->getCache())!==null) + { + if($this->duration>0 && ($data=$this->_cache->get($this->getCacheKey()))!==false) + { + $this->_content=$data[0]; + $this->_actions=$data[1]; + return true; + } + if($this->duration==0) + $this->_cache->delete($this->getCacheKey()); + if($this->duration<=0) + $this->_cache=null; + } + return false; + } + + /** + * @return ICache the cache used for caching the content. + */ + protected function getCache() + { + return Yii::app()->getComponent($this->cacheID); + } + + /** + * Caclulates the base cache key. + * The calculated key will be further variated in {@link getCacheKey}. + * Derived classes may override this method if more variations are needed. + * @return string basic cache key without variations + */ + protected function getBaseCacheKey() + { + return self::CACHE_KEY_PREFIX.$this->getId().'.'; + } + + /** + * Calculates the cache key. + * The key is calculated based on {@link getBaseCacheKey} and other factors, including + * {@link varyByRoute}, {@link varyByParam} and {@link varyBySession}. + * @return string cache key + */ + protected function getCacheKey() + { + if($this->_key!==null) + return $this->_key; + else + { + $key=$this->getBaseCacheKey().'.'; + if($this->varyByRoute) + { + $controller=$this->getController(); + $key.=$controller->getUniqueId().'/'; + if(($action=$controller->getAction())!==null) + $key.=$action->getId(); + } + $key.='.'; + + if($this->varyBySession) + $key.=Yii::app()->getSession()->getSessionID(); + $key.='.'; + + if(is_array($this->varyByParam) && isset($this->varyByParam[0])) + { + $params=array(); + foreach($this->varyByParam as $name) + { + if(isset($_GET[$name])) + $params[$name]=$_GET[$name]; + else + $params[$name]=''; + } + $key.=serialize($params); + } + $key.='.'; + + if($this->varyByExpression!==null) + $key.=$this->evaluateExpression($this->varyByExpression); + $key.='.'; + + return $this->_key=$key; + } + } + + /** + * Records a method call when this output cache is in effect. + * When the content is served from the output cache, the recorded + * method will be re-invoked. + * @param string $context a property name of the controller. The property should refer to an object + * whose method is being recorded. If empty it means the controller itself. + * @param string $method the method name + * @param array $params parameters passed to the method + */ + public function recordAction($context,$method,$params) + { + $this->_actions[]=array($context,$method,$params); + } + + /** + * Replays the recorded method calls. + */ + protected function replayActions() + { + if(empty($this->_actions)) + return; + $controller=$this->getController(); + $cs=Yii::app()->getClientScript(); + foreach($this->_actions as $action) + { + if($action[0]==='clientScript') + $object=$cs; + else if($action[0]==='') + $object=$controller; + else + $object=$controller->{$action[0]}; + if(method_exists($object,$action[1])) + call_user_func_array(array($object,$action[1]),$action[2]); + else if($action[0]==='' && function_exists($action[1])) + call_user_func_array($action[1],$action[2]); + else + throw new CException(Yii::t('yii','Unable to replay the action "{object}.{method}". The method does not exist.', + array('object'=>$action[0], + 'method'=>$action[1]))); + } + } +} diff --git a/framework/web/widgets/COutputProcessor.php b/framework/web/widgets/COutputProcessor.php new file mode 100644 index 0000000..1fc8ffd --- /dev/null +++ b/framework/web/widgets/COutputProcessor.php @@ -0,0 +1,77 @@ +<?php +/** + * COutputProcessor class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * COutputProcessor transforms the content into a different format. + * + * COutputProcessor captures the output generated by an action or a view fragment + * and passes it to its {@link onProcessOutput} event handlers for further processing. + * + * The event handler may process the output and store it back to the {@link COutputEvent::output} + * property. By setting the {@link CEvent::handled handled} property of the event parameter + * to true, the output will not be echoed anymore. Otherwise (by default), the output will be echoed. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: COutputProcessor.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @package system.web.widgets + * @since 1.0 + */ +class COutputProcessor extends CFilterWidget +{ + /** + * Initializes the widget. + * This method starts the output buffering. + */ + public function init() + { + ob_start(); + ob_implicit_flush(false); + } + + /** + * Executes the widget. + * This method stops output buffering and processes the captured output. + */ + public function run() + { + $output=ob_get_clean(); + $this->processOutput($output); + } + + /** + * Processes the captured output. + * + * The default implementation raises an {@link onProcessOutput} event. + * If the event is not handled by any event handler, the output will be echoed. + * + * @param string $output the captured output to be processed + */ + public function processOutput($output) + { + if($this->hasEventHandler('onProcessOutput')) + { + $event=new COutputEvent($this,$output); + $this->onProcessOutput($event); + if(!$event->handled) + echo $output; + } + else + echo $output; + } + + /** + * Raised when the output has been captured. + * @param COutputEvent $event event parameter + */ + public function onProcessOutput($event) + { + $this->raiseEvent('onProcessOutput',$event); + } +} diff --git a/framework/web/widgets/CStarRating.php b/framework/web/widgets/CStarRating.php new file mode 100644 index 0000000..5a50c8e --- /dev/null +++ b/framework/web/widgets/CStarRating.php @@ -0,0 +1,217 @@ +<?php +/** + * CStarRating class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CStarRating displays a star rating control that can collect user rating input. + * + * CStarRating is based on {@link http://www.fyneworks.com/jquery/star-rating/ jQuery Star Rating Plugin}. + * It displays a list of stars indicating the rating values. Users can toggle these stars + * to indicate their rating input. On the server side, when the rating input is submitted, + * the value can be retrieved in the same way as working with a normal HTML input. + * For example, using + * <pre> + * $this->widget('CStarRating',array('name'=>'rating')); + * </pre> + * we can retrieve the rating value via <code>$_POST['rating']</code>. + * + * CStarRating allows customization of its appearance. It also supports empty rating as well as read-only rating. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CStarRating.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CStarRating extends CInputWidget +{ + /** + * @var integer the number of stars. Defaults to 5. + */ + public $starCount=5; + /** + * @var mixed the minimum rating allowed. This can be either an integer or a float value. Defaults to 1. + */ + public $minRating=1; + /** + * @var mixed the maximum rating allowed. This can be either an integer or a float value. Defaults to 10. + */ + public $maxRating=10; + /** + * @var mixed the step size of rating. This is the minimum difference between two rating values. Defaults to 1. + */ + public $ratingStepSize=1; + /** + * @var mixed the CSS file used for the widget. Defaults to null, meaning + * using the default CSS file included together with the widget. + * If false, no CSS file will be used. Otherwise, the specified CSS file + * will be included when using this widget. + */ + public $cssFile; + /** + * @var array the titles associated with the rating options. The keys are ratings and the values are the corresponding titles. + * Defaults to null, meaning using the rating value as the title. + */ + public $titles; + /** + * @var string the hint text for the reset button. Defaults to null, meaning using the system-defined text (which is 'Cancel Rating'). + */ + public $resetText; + /** + * @var string the value taken when the rating is cleared. Defaults to null, meaning using the system-defined value (which is ''). + */ + public $resetValue; + /** + * @var boolean whether the rating value can be empty (not set). Defaults to true. + * When this is true, a reset button will be displayed in front of stars. + */ + public $allowEmpty; + /** + * @var integer the width of star image. Defaults to null, meaning using the system-defined value (which is 16). + */ + public $starWidth; + /** + * @var boolean whether the rating value is read-only or not. Defaults to false. + * When this is true, the rating cannot be changed. + */ + public $readOnly; + /** + * @var string Callback when the stars are focused. + */ + public $focus; + /** + * @var string Callback when the stars are not focused. + */ + public $blur; + /** + * @var string Callback when the stars are clicked. + */ + public $callback; + + + /** + * Executes the widget. + * This method registers all needed client scripts and renders + * the text field. + */ + public function run() + { + list($name,$id)=$this->resolveNameID(); + if(isset($this->htmlOptions['id'])) + $id=$this->htmlOptions['id']; + else + $this->htmlOptions['id']=$id; + if(isset($this->htmlOptions['name'])) + $name=$this->htmlOptions['name']; + + $this->registerClientScript($id); + + echo CHtml::openTag('span',$this->htmlOptions)."\n"; + $this->renderStars($id,$name); + echo "</span>"; + } + + /** + * Registers the necessary javascript and css scripts. + * @param string $id the ID of the container + */ + public function registerClientScript($id) + { + $jsOptions=$this->getClientOptions(); + $jsOptions=empty($jsOptions) ? '' : CJavaScript::encode($jsOptions); + $js="jQuery('#{$id} > input').rating({$jsOptions});"; + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('rating'); + $cs->registerScript('Yii.CStarRating#'.$id,$js); + + if($this->cssFile!==false) + self::registerCssFile($this->cssFile); + } + + /** + * Registers the needed CSS file. + * @param string $url the CSS URL. If null, a default CSS URL will be used. + */ + public static function registerCssFile($url=null) + { + $cs=Yii::app()->getClientScript(); + if($url===null) + $url=$cs->getCoreScriptUrl().'/rating/jquery.rating.css'; + $cs->registerCssFile($url); + } + + /** + * Renders the stars. + * @param string $id the ID of the container + * @param string $name the name of the input + */ + protected function renderStars($id,$name) + { + $inputCount=(int)(($this->maxRating-$this->minRating)/$this->ratingStepSize+1); + $starSplit=(int)($inputCount/$this->starCount); + if($this->hasModel()) + { + $attr=$this->attribute; + CHtml::resolveName($this->model,$attr); + $selection=$this->model->$attr; + } + else + $selection=$this->value; + $options=$starSplit>1 ? array('class'=>"{split:{$starSplit}}") : array(); + for($value=$this->minRating, $i=0;$i<$inputCount; ++$i, $value+=$this->ratingStepSize) + { + $options['id']=$id.'_'.$i; + $options['value']=$value; + if(isset($this->titles[$value])) + $options['title']=$this->titles[$value]; + else + unset($options['title']); + echo CHtml::radioButton($name,!strcmp($value,$selection),$options) . "\n"; + } + } + + /** + * @return array the javascript options for the star rating + */ + protected function getClientOptions() + { + $options=array(); + if($this->resetText!==null) + $options['cancel']=$this->resetText; + if($this->resetValue!==null) + $options['cancelValue']=$this->resetValue; + if($this->allowEmpty===false) + $options['required']=true; + if($this->starWidth!==null) + $options['starWidth']=$this->starWidth; + if($this->readOnly===true) + $options['readOnly']=true; + if($this->focus!==null) + { + if(strncmp($this->focus,'js:',3)) + $options['focus']='js:'.$this->focus; + else + $options['focus']=$this->focus; + } + if($this->blur!==null) + { + if(strncmp($this->blur,'js:',3)) + $options['blur']='js:'.$this->blur; + else + $options['blur']=$this->blur; + } + if($this->callback!==null) + { + if(strncmp($this->callback,'js:',3)) + $options['callback']='js:'.$this->callback; + else + $options['callback']=$this->callback; + } + return $options; + } +}
\ No newline at end of file diff --git a/framework/web/widgets/CTabView.php b/framework/web/widgets/CTabView.php new file mode 100644 index 0000000..89b3e03 --- /dev/null +++ b/framework/web/widgets/CTabView.php @@ -0,0 +1,212 @@ +<?php +/** + * CTabView class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CTabView displays contents in multiple tabs. + * + * At any time, only one tab is visible. Users can click on the tab header + * to switch to see another tab of content. + * + * JavaScript is used to control the tab switching. If JavaScript is disabled, + * CTabView still manages to display the content in a semantically appropriate way. + * + * To specify contents and their tab structure, configure the {@link tabs} property. + * The {@link tabs} property takes an array with tab ID being mapped tab definition. + * Each tab definition is an array of the following structure: + * <ul> + * <li>title: the tab title.</li> + * <li>content: the content to be displayed in the tab.</li> + * <li>view: the name of the view to be displayed in this tab. + * The view will be rendered using the current controller's + * {@link CController::renderPartial} method. + * When both 'content' and 'view' are specified, 'content' will take precedence. + * </li> + * <li>url: a URL that the user browser will be redirected to when clicking on this tab.</li> + * <li>data: array (name=>value), this will be passed to the view when 'view' is specified.</li> + * </ul> + * + * For example, the {@link tabs} property can be configured as follows, + * <pre> + * array( + * 'tab1'=>array( + * 'title'=>'tab 1 title', + * 'view'=>'view1', + * 'data'=>array('model'=>$model), + * ), + * 'tab2'=>array( + * 'title'=>'tab 2 title', + * 'url'=>'http://www.yiiframework.com/', + * ), + * ) + * </pre> + * + * By default, the first tab will be activated. To activate a different tab + * when the page is initially loaded, set {@link activeTab} to be the ID of the desired tab. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CTabView.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CTabView extends CWidget +{ + /** + * Default CSS class for the tab container + */ + const CSS_CLASS='yiiTab'; + + /** + * @var mixed the CSS file used for the widget. Defaults to null, meaning + * using the default CSS file included together with the widget. + * If false, no CSS file will be used. Otherwise, the specified CSS file + * will be included when using this widget. + */ + public $cssFile; + /** + * @var string the ID of the tab that should be activated when the page is initially loaded. + * If not set, the first tab will be activated. + */ + public $activeTab; + /** + * @var array the data that will be passed to the partial view rendered by each tab. + */ + public $viewData; + /** + * @var array additional HTML options to be rendered in the container tag. + */ + public $htmlOptions; + /** + * @var array tab definitions. The array keys are the IDs, + * and the array values are the corresponding tab contents. + * Each array value must be an array with the following elements: + * <ul> + * <li>title: the tab title. You need to make sure this is HTML-encoded.</li> + * <li>content: the content to be displayed in the tab.</li> + * <li>view: the name of the view to be displayed in this tab. + * The view will be rendered using the current controller's + * {@link CController::renderPartial} method. + * When both 'content' and 'view' are specified, 'content' will take precedence. + * </li> + * <li>url: a URL that the user browser will be redirected to when clicking on this tab.</li> + * <li>data: array (name=>value), this will be passed to the view when 'view' is specified. + * This option is available since version 1.1.1.</li> + * </ul> + * <pre> + * array( + * 'tab1'=>array( + * 'title'=>'tab 1 title', + * 'view'=>'view1', + * ), + * 'tab2'=>array( + * 'title'=>'tab 2 title', + * 'url'=>'http://www.yiiframework.com/', + * ), + * ) + * </pre> + */ + public $tabs=array(); + + /** + * Runs the widget. + */ + public function run() + { + if(empty($this->tabs)) + return; + + if($this->activeTab===null || !isset($this->tabs[$this->activeTab])) + { + reset($this->tabs); + list($this->activeTab, )=each($this->tabs); + } + + $htmlOptions=$this->htmlOptions; + $htmlOptions['id']=$this->getId(); + if(!isset($htmlOptions['class'])) + $htmlOptions['class']=self::CSS_CLASS; + + $this->registerClientScript(); + + echo CHtml::openTag('div',$htmlOptions)."\n"; + $this->renderHeader(); + $this->renderBody(); + echo CHtml::closeTag('div'); + } + + /** + * Registers the needed CSS and JavaScript. + */ + public function registerClientScript() + { + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('yiitab'); + $id=$this->getId(); + $cs->registerScript('Yii.CTabView#'.$id,"jQuery(\"#{$id}\").yiitab();"); + + if($this->cssFile!==false) + self::registerCssFile($this->cssFile); + } + + /** + * Registers the needed CSS file. + * @param string $url the CSS URL. If null, a default CSS URL will be used. + */ + public static function registerCssFile($url=null) + { + $cs=Yii::app()->getClientScript(); + if($url===null) + $url=$cs->getCoreScriptUrl().'/yiitab/jquery.yiitab.css'; + $cs->registerCssFile($url,'screen'); + } + + /** + * Renders the header part. + */ + protected function renderHeader() + { + echo "<ul class=\"tabs\">\n"; + foreach($this->tabs as $id=>$tab) + { + $title=isset($tab['title'])?$tab['title']:'undefined'; + $active=$id===$this->activeTab?' class="active"' : ''; + $url=isset($tab['url'])?$tab['url']:"#{$id}"; + echo "<li><a href=\"{$url}\"{$active}>{$title}</a></li>\n"; + } + echo "</ul>\n"; + } + + /** + * Renders the body part. + */ + protected function renderBody() + { + foreach($this->tabs as $id=>$tab) + { + $inactive=$id!==$this->activeTab?' style="display:none"' : ''; + echo "<div class=\"view\" id=\"{$id}\"{$inactive}>\n"; + if(isset($tab['content'])) + echo $tab['content']; + else if(isset($tab['view'])) + { + if(isset($tab['data'])) + { + if(is_array($this->viewData)) + $data=array_merge($this->viewData, $tab['data']); + else + $data=$tab['data']; + } + else + $data=$this->viewData; + $this->getController()->renderPartial($tab['view'], $data); + } + echo "</div><!-- {$id} -->\n"; + } + } +} diff --git a/framework/web/widgets/CTextHighlighter.php b/framework/web/widgets/CTextHighlighter.php new file mode 100644 index 0000000..b2a48a2 --- /dev/null +++ b/framework/web/widgets/CTextHighlighter.php @@ -0,0 +1,125 @@ +<?php +/** + * CTextHighlighter class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +require_once(Yii::getPathOfAlias('system.vendors.TextHighlighter.Text.Highlighter').'.php'); +require_once(Yii::getPathOfAlias('system.vendors.TextHighlighter.Text.Highlighter.Renderer.Html').'.php'); + +/** + * CTextHighlighter does syntax highlighting for its body content. + * + * The language of the syntax to be applied is specified via {@link language} property. + * Currently, CTextHighlighter supports the following languages: + * ABAP, CPP, CSS, DIFF, DTD, HTML, JAVA, JAVASCRIPT, MYSQL, PERL, + * PHP, PYTHON, RUBY, SQL, XML. By setting {@link showLineNumbers} + * to true, the highlighted result may be shown with line numbers. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CTextHighlighter.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CTextHighlighter extends COutputProcessor +{ + /** + * @var string the language whose syntax is to be used for highlighting. + * Valid values are those file names (without suffix) that are contained + * in 'vendors/TextHighlighter/Text/Highlighter'. Currently, the following + * languages are supported: + * ABAP, CPP, CSS, DIFF, DTD, HTML, JAVA, JAVASCRIPT, + * MYSQL, PERL, PHP, PYTHON, RUBY, SQL, XML + * If a language is not supported, it will be displayed as plain text. + * Language names are case-insensitive. + */ + public $language; + /** + * @var boolean whether to show line numbers in the highlighted result. Defaults to false. + * @see lineNumberStyle + */ + public $showLineNumbers=false; + /** + * @var string the style of line number display. It can be either 'list' or 'table'. Defaults to 'list'. + * @see showLineNumbers + */ + public $lineNumberStyle='list'; + /** + * @var integer tab size. Defaults to 4. + */ + public $tabSize=4; + /** + * @var mixed the CSS file used for the widget. Defaults to null, meaning + * using the default CSS file included together with the widget. + * If false, no CSS file will be used. Otherwise, the specified CSS file + * will be included when using this widget. + */ + public $cssFile; + /** + * @var array the HTML attributes to be applied to the container element. + * The highlighted content is contained in a DIV element. + */ + public $containerOptions=array(); + + + /** + * Processes the captured output. + * This method highlights the output according to the syntax of the specified {@link language}. + * @param string $output the captured output to be processed + */ + public function processOutput($output) + { + $output=$this->highlight($output); + parent::processOutput($output); + } + + /** + * Highlights the content by the syntax of the specified language. + * @param string $content the content to be highlighted. + * @return string the highlighted content + */ + public function highlight($content) + { + $this->registerClientScript(); + + $options['use_language']=true; + $options['tabsize']=$this->tabSize; + if($this->showLineNumbers) + $options['numbers']=($this->lineNumberStyle==='list')?HL_NUMBERS_LI:HL_NUMBERS_TABLE; + + $highlighter=empty($this->language)?false:Text_Highlighter::factory($this->language); + if($highlighter===false) + $o='<pre>'.CHtml::encode($content).'</pre>'; + else + { + $highlighter->setRenderer(new Text_Highlighter_Renderer_Html($options)); + $o=preg_replace('/<span\s+[^>]*>(\s*)<\/span>/','\1',$highlighter->highlight($content)); + } + + return CHtml::tag('div',$this->containerOptions,$o); + } + + /** + * Registers the needed CSS and JavaScript. + */ + public function registerClientScript() + { + if($this->cssFile!==false) + self::registerCssFile($this->cssFile); + } + + /** + * Registers the needed CSS file. + * @param string $url the CSS URL. If null, a default CSS URL will be used. + */ + public static function registerCssFile($url=null) + { + if($url===null) + $url=CHtml::asset(Yii::getPathOfAlias('system.vendors.TextHighlighter.highlight').'.css'); + Yii::app()->getClientScript()->registerCssFile($url); + } +} diff --git a/framework/web/widgets/CTreeView.php b/framework/web/widgets/CTreeView.php new file mode 100644 index 0000000..a10d5a7 --- /dev/null +++ b/framework/web/widgets/CTreeView.php @@ -0,0 +1,246 @@ +<?php +/** + * CTreeView class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CTreeView displays a tree view of hierarchical data. + * + * It encapsulates the excellent tree view plugin for jQuery + * ({@link http://bassistance.de/jquery-plugins/jquery-plugin-treeview/}). + * + * To use CTreeView, simply sets {@link data} to the data that you want + * to present and you are there. + * + * CTreeView also supports dynamic data loading via AJAX. To do so, set + * {@link url} to be the URL that can serve the tree view data upon request. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CTreeView.php 3144 2011-03-30 07:03:48Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CTreeView extends CWidget +{ + /** + * @var array the data that can be used to generate the tree view content. + * Each array element corresponds to a tree view node with the following structure: + * <ul> + * <li>text: string, required, the HTML text associated with this node.</li> + * <li>expanded: boolean, optional, whether the tree view node is expanded.</li> + * <li>id: string, optional, the ID identifying the node. This is used + * in dynamic loading of tree view (see {@link url}).</li> + * <li>hasChildren: boolean, optional, defaults to false, whether clicking on this + * node should trigger dynamic loading of more tree view nodes from server. + * The {@link url} property must be set in order to make this effective.</li> + * <li>children: array, optional, child nodes of this node.</li> + * <li>htmlOptions: array, additional HTML attributes (see {@link CHtml::tag}). + * This option has been available since version 1.1.7.</li> + * </ul> + * Note, anything enclosed between the beginWidget and endWidget calls will + * also be treated as tree view content, which appends to the content generated + * from this data. + */ + public $data; + /** + * @var mixed the CSS file used for the widget. Defaults to null, meaning + * using the default CSS file included together with the widget. + * If false, no CSS file will be used. Otherwise, the specified CSS file + * will be included when using this widget. + */ + public $cssFile; + /** + * @var string|array the URL to which the treeview can be dynamically loaded (in AJAX). + * See {@link CHtml::normalizeUrl} for possible URL formats. + * Setting this property will enable the dynamic treeview loading. + * When the page is displayed, the browser will request this URL with a GET parameter + * named 'root' whose value is 'source'. The server script should then generate the + * needed tree view data corresponding to the root of the tree (see {@link saveDataAsJson}.) + * When a node has a CSS class 'hasChildren', then expanding this node will also + * cause a dynamic loading of its child nodes. In this case, the value of the 'root' GET parameter + * is the 'id' property of the node. + */ + public $url; + /** + * @var string|integer animation speed. This can be one of the three predefined speeds + * ("slow", "normal", or "fast") or the number of milliseconds to run the animation (e.g. 1000). + * If not set, no animation is used. + */ + public $animated; + /** + * @var boolean whether the tree should start with all branches collapsed. Defaults to false. + */ + public $collapsed; + /** + * @var string container for a tree-control, allowing the user to expand, collapse and toggle all branches with one click. + * In the container, clicking on the first hyperlink will collapse the tree; + * the second hyperlink will expand the tree; while the third hyperlink will toggle the tree. + * The property should be a valid jQuery selector (e.g. '#treecontrol' where 'treecontrol' is + * the ID of the 'div' element containing the hyperlinks.) + */ + public $control; + /** + * @var boolean set to allow only one branch on one level to be open (closing siblings which opening). + * Defaults to false. + */ + public $unique; + /** + * @var string Callback when toggling a branch. Arguments: "this" refers to the UL that was shown or hidden + */ + public $toggle; + /** + * @var string Persist the tree state in cookies or the page location. If set to "location", looks for + * the anchor that matches location.href and activates that part of the treeview it. + * Great for href-based state-saving. If set to "cookie", saves the state of the tree on + * each click to a cookie and restores that state on page load. + */ + public $persist; + /** + * @var string The cookie name to use when persisting via persist:"cookie". Defaults to 'treeview'. + */ + public $cookieId; + /** + * @var boolean Set to skip rendering of classes and hitarea divs, assuming that is done by the serverside. Defaults to false. + */ + public $prerendered; + /** + * @var array additional options that can be passed to the constructor of the treeview js object. + */ + public $options=array(); + /** + * @var array additional HTML attributes that will be rendered in the UL tag. + * The default tree view CSS has defined the following CSS classes which can be enabled + * by specifying the 'class' option here: + * <ul> + * <li>treeview-black</li> + * <li>treeview-gray</li> + * <li>treeview-red</li> + * <li>treeview-famfamfam</li> + * <li>filetree</li> + * </ul> + */ + public $htmlOptions; + + + /** + * Initializes the widget. + * This method registers all needed client scripts and renders + * the tree view content. + */ + public function init() + { + if(isset($this->htmlOptions['id'])) + $id=$this->htmlOptions['id']; + else + $id=$this->htmlOptions['id']=$this->getId(); + if($this->url!==null) + $this->url=CHtml::normalizeUrl($this->url); + $cs=Yii::app()->getClientScript(); + $cs->registerCoreScript('treeview'); + $options=$this->getClientOptions(); + $options=$options===array()?'{}' : CJavaScript::encode($options); + $cs->registerScript('Yii.CTreeView#'.$id,"jQuery(\"#{$id}\").treeview($options);"); + if($this->cssFile===null) + $cs->registerCssFile($cs->getCoreScriptUrl().'/treeview/jquery.treeview.css'); + else if($this->cssFile!==false) + $cs->registerCssFile($this->cssFile); + + echo CHtml::tag('ul',$this->htmlOptions,false,false)."\n"; + echo self::saveDataAsHtml($this->data); + } + + /** + * Ends running the widget. + */ + public function run() + { + echo "</ul>"; + } + + /** + * @return array the javascript options + */ + protected function getClientOptions() + { + $options=$this->options; + foreach(array('url','animated','collapsed','control','unique','toggle','persist','cookieId','prerendered') as $name) + { + if($this->$name!==null) + $options[$name]=$this->$name; + } + return $options; + } + + /** + * Generates tree view nodes in HTML from the data array. + * @param array $data the data for the tree view (see {@link data} for possible data structure). + * @return string the generated HTML for the tree view + */ + public static function saveDataAsHtml($data) + { + $html=''; + if(is_array($data)) + { + foreach($data as $node) + { + if(!isset($node['text'])) + continue; + + if(isset($node['expanded'])) + $css=$node['expanded'] ? 'open' : 'closed'; + else + $css=''; + + if(isset($node['hasChildren']) && $node['hasChildren']) + { + if($css!=='') + $css.=' '; + $css.='hasChildren'; + } + + $options=isset($node['htmlOptions']) ? $node['htmlOptions'] : array(); + if($css!=='') + { + if(isset($options['class'])) + $options['class'].=' '.$css; + else + $options['class']=$css; + } + + if(isset($node['id'])) + $options['id']=$node['id']; + + $html.=CHtml::tag('li',$options,$node['text'],false); + if(!empty($node['children'])) + { + $html.="\n<ul>\n"; + $html.=self::saveDataAsHtml($node['children']); + $html.="</ul>\n"; + } + $html.=CHtml::closeTag('li')."\n"; + } + } + return $html; + } + + /** + * Saves tree view data in JSON format. + * This method is typically used in dynamic tree view loading + * when the server code needs to send to the client the dynamic + * tree view data. + * @param array $data the data for the tree view (see {@link data} for possible data structure). + * @return string the JSON representation of the data + */ + public static function saveDataAsJson($data) + { + if(empty($data)) + return '[]'; + else + return CJavaScript::jsonEncode($data); + } +} diff --git a/framework/web/widgets/CWidget.php b/framework/web/widgets/CWidget.php new file mode 100644 index 0000000..2964f67 --- /dev/null +++ b/framework/web/widgets/CWidget.php @@ -0,0 +1,249 @@ +<?php +/** + * CWidget class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CWidget is the base class for widgets. + * + * A widget is a self-contained component that may generate presentation + * based on model data. It can be viewed as a micro-controller that embeds + * into the controller-managed views. + * + * Compared with {@link CController controller}, a widget has neither actions nor filters. + * + * Usage is described at {@link CBaseController} and {@link CBaseController::widget}. + * + * @property CBaseController $owner Owner/creator of this widget. It could be either a widget or a controller. + * @property string $id Id of the widget. + * @property CController $controller The controller that this widget belongs to. + * @property string $viewPath The directory containing the view files for this widget. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CWidget.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets + * @since 1.0 + */ +class CWidget extends CBaseController +{ + /** + * @var string the prefix to the IDs of the {@link actions}. + * When a widget is declared an action provider in {@link CController::actions}, + * a prefix can be specified to differentiate its action IDs from others. + * The same prefix should then also be used to configure this property + * when the widget is used in a view of the controller. + */ + public $actionPrefix; + /** + * @var mixed the name of the skin to be used by this widget. Defaults to 'default'. + * If this is set as false, no skin will be applied to this widget. + * @see CWidgetFactory + * @since 1.1 + */ + public $skin='default'; + + /** + * @var array view paths for different types of widgets + */ + private static $_viewPaths; + /** + * @var integer the counter for generating implicit IDs. + */ + private static $_counter=0; + /** + * @var string id of the widget. + */ + private $_id; + /** + * @var CBaseController owner/creator of this widget. It could be either a widget or a controller. + */ + private $_owner; + + /** + * Returns a list of actions that are used by this widget. + * The structure of this method's return value is similar to + * that returned by {@link CController::actions}. + * + * When a widget uses several actions, you can declare these actions using + * this method. The widget will then become an action provider, and the actions + * can be easily imported into a controller. + * + * Note, when creating URLs referring to the actions listed in this method, + * make sure the action IDs are prefixed with {@link actionPrefix}. + * + * @return array + * + * @see actionPrefix + * @see CController::actions + */ + public static function actions() + { + return array(); + } + + /** + * Constructor. + * @param CBaseController $owner owner/creator of this widget. It could be either a widget or a controller. + */ + public function __construct($owner=null) + { + $this->_owner=$owner===null?Yii::app()->getController():$owner; + } + + /** + * Returns the owner/creator of this widget. + * @return CBaseController owner/creator of this widget. It could be either a widget or a controller. + */ + public function getOwner() + { + return $this->_owner; + } + + /** + * Returns the ID of the widget or generates a new one if requested. + * @param boolean $autoGenerate whether to generate an ID if it is not set previously + * @return string id of the widget. + */ + public function getId($autoGenerate=true) + { + if($this->_id!==null) + return $this->_id; + else if($autoGenerate) + return $this->_id='yw'.self::$_counter++; + } + + /** + * Sets the ID of the widget. + * @param string $value id of the widget. + */ + public function setId($value) + { + $this->_id=$value; + } + + /** + * Returns the controller that this widget belongs to. + * @return CController the controller that this widget belongs to. + */ + public function getController() + { + if($this->_owner instanceof CController) + return $this->_owner; + else + return Yii::app()->getController(); + } + + /** + * Initializes the widget. + * This method is called by {@link CBaseController::createWidget} + * and {@link CBaseController::beginWidget} after the widget's + * properties have been initialized. + */ + public function init() + { + } + + /** + * Executes the widget. + * This method is called by {@link CBaseController::endWidget}. + */ + public function run() + { + } + + /** + * Returns the directory containing the view files for this widget. + * The default implementation returns the 'views' subdirectory of the directory containing the widget class file. + * If $checkTheme is set true, the directory "ThemeID/views/ClassName" will be returned when it exists. + * @param boolean $checkTheme whether to check if the theme contains a view path for the widget. + * @return string the directory containing the view files for this widget. + */ + public function getViewPath($checkTheme=false) + { + $className=get_class($this); + if(isset(self::$_viewPaths[$className])) + return self::$_viewPaths[$className]; + else + { + if($checkTheme && ($theme=Yii::app()->getTheme())!==null) + { + $path=$theme->getViewPath().DIRECTORY_SEPARATOR; + if(strpos($className,'\\')!==false) // namespaced class + $path.=str_replace('\\','_',ltrim($className,'\\')); + else + $path.=$className; + if(is_dir($path)) + return self::$_viewPaths[$className]=$path; + } + + $class=new ReflectionClass($className); + return self::$_viewPaths[$className]=dirname($class->getFileName()).DIRECTORY_SEPARATOR.'views'; + } + } + + /** + * Looks for the view script file according to the view name. + * This method will look for the view under the widget's {@link getViewPath viewPath}. + * The view script file is named as "ViewName.php". A localized view file + * may be returned if internationalization is needed. See {@link CApplication::findLocalizedFile} + * for more details. + * The view name can also refer to a path alias if it contains dot characters. + * @param string $viewName name of the view (without file extension) + * @return string the view file path. False if the view file does not exist + * @see CApplication::findLocalizedFile + */ + public function getViewFile($viewName) + { + if(($renderer=Yii::app()->getViewRenderer())!==null) + $extension=$renderer->fileExtension; + else + $extension='.php'; + if(strpos($viewName,'.')) // a path alias + $viewFile=Yii::getPathOfAlias($viewName); + else + { + $viewFile=$this->getViewPath(true).DIRECTORY_SEPARATOR.$viewName; + if(is_file($viewFile.$extension)) + return Yii::app()->findLocalizedFile($viewFile.$extension); + else if($extension!=='.php' && is_file($viewFile.'.php')) + return Yii::app()->findLocalizedFile($viewFile.'.php'); + $viewFile=$this->getViewPath(false).DIRECTORY_SEPARATOR.$viewName; + } + + if(is_file($viewFile.$extension)) + return Yii::app()->findLocalizedFile($viewFile.$extension); + else if($extension!=='.php' && is_file($viewFile.'.php')) + return Yii::app()->findLocalizedFile($viewFile.'.php'); + else + return false; + } + + /** + * Renders a view. + * + * The named view refers to a PHP script (resolved via {@link getViewFile}) + * that is included by this method. If $data is an associative array, + * it will be extracted as PHP variables and made available to the script. + * + * @param string $view name of the view to be rendered. See {@link getViewFile} for details + * about how the view script is resolved. + * @param array $data data to be extracted into PHP variables and made available to the view script + * @param boolean $return whether the rendering result should be returned instead of being displayed to end users + * @return string the rendering result. Null if the rendering result is not required. + * @throws CException if the view does not exist + * @see getViewFile + */ + public function render($view,$data=null,$return=false) + { + if(($viewFile=$this->getViewFile($view))!==false) + return $this->renderFile($viewFile,$data,$return); + else + throw new CException(Yii::t('yii','{widget} cannot find the view "{view}".', + array('{widget}'=>get_class($this), '{view}'=>$view))); + } +}
\ No newline at end of file diff --git a/framework/web/widgets/captcha/CCaptcha.php b/framework/web/widgets/captcha/CCaptcha.php new file mode 100644 index 0000000..ea8366c --- /dev/null +++ b/framework/web/widgets/captcha/CCaptcha.php @@ -0,0 +1,171 @@ +<?php +/** + * CCaptcha class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CCaptcha renders a CAPTCHA image element. + * + * CCaptcha is used together with {@link CCaptchaAction} to provide {@link http://en.wikipedia.org/wiki/Captcha CAPTCHA} + * - a way of preventing site spam. + * + * The image element rendered by CCaptcha will display a CAPTCHA image generated + * by an action of class {@link CCaptchaAction} belonging to the current controller. + * By default, the action ID should be 'captcha', which can be changed by setting {@link captchaAction}. + * + * CCaptcha may also render a button next to the CAPTCHA image. Clicking on the button + * will change the CAPTCHA image to be a new one in an AJAX way. + * + * If {@link clickableImage} is set true, clicking on the CAPTCHA image + * will refresh the CAPTCHA. + * + * A {@link CCaptchaValidator} may be used to validate that the user enters + * a verification code matching the code displayed in the CAPTCHA image. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CCaptcha.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets.captcha + * @since 1.0 + */ +class CCaptcha extends CWidget +{ + /** + * @var string the ID of the action that should provide CAPTCHA image. Defaults to 'captcha', + * meaning the 'captcha' action of the current controller. This property may also + * be in the format of 'ControllerID/ActionID'. Underneath, this property is used + * by {@link CController::createUrl} to create the URL that would serve the CAPTCHA image. + * The action has to be of {@link CCaptchaAction}. + */ + public $captchaAction='captcha'; + /** + * @var boolean whether to display a button next to the CAPTCHA image. Clicking on the button + * will cause the CAPTCHA image to be changed to a new one. Defaults to true. + */ + public $showRefreshButton=true; + /** + * @var boolean whether to allow clicking on the CAPTCHA image to refresh the CAPTCHA letters. + * Defaults to false. Hint: you may want to set {@link showRefreshButton} to false if you set + * this property to be true because they serve for the same purpose. + * To enhance accessibility, you may set {@link imageOptions} to provide hints to end-users that + * the image is clickable. + */ + public $clickableImage=false; + /** + * @var string the label for the refresh button. Defaults to 'Get a new code'. + */ + public $buttonLabel; + /** + * @var string the type of the refresh button. This should be either 'link' or 'button'. + * The former refers to hyperlink button while the latter a normal push button. + * Defaults to 'link'. + */ + public $buttonType='link'; + /** + * @var array HTML attributes to be applied to the rendered image element. + */ + public $imageOptions=array(); + /** + * @var array HTML attributes to be applied to the rendered refresh button element. + */ + public $buttonOptions=array(); + + + /** + * Renders the widget. + */ + public function run() + { + if(self::checkRequirements()) + { + $this->renderImage(); + $this->registerClientScript(); + } + else + throw new CException(Yii::t('yii','GD and FreeType PHP extensions are required.')); + } + + /** + * Renders the CAPTCHA image. + */ + protected function renderImage() + { + if(!isset($this->imageOptions['id'])) + $this->imageOptions['id']=$this->getId(); + + $url=$this->getController()->createUrl($this->captchaAction,array('v'=>uniqid())); + $alt=isset($this->imageOptions['alt'])?$this->imageOptions['alt']:''; + echo CHtml::image($url,$alt,$this->imageOptions); + } + + /** + * Registers the needed client scripts. + */ + public function registerClientScript() + { + $cs=Yii::app()->clientScript; + $id=$this->imageOptions['id']; + $url=$this->getController()->createUrl($this->captchaAction,array(CCaptchaAction::REFRESH_GET_VAR=>true)); + + $js=""; + if($this->showRefreshButton) + { + $cs->registerScript('Yii.CCaptcha#'.$id,'dummy'); + $label=$this->buttonLabel===null?Yii::t('yii','Get a new code'):$this->buttonLabel; + $options=$this->buttonOptions; + if(isset($options['id'])) + $buttonID=$options['id']; + else + $buttonID=$options['id']=$id.'_button'; + if($this->buttonType==='button') + $html=CHtml::button($label, $options); + else + $html=CHtml::link($label, $url, $options); + $js="jQuery('#$id').after(".CJSON::encode($html).");"; + $selector="#$buttonID"; + } + + if($this->clickableImage) + $selector=isset($selector) ? "$selector, #$id" : "#$id"; + + if(!isset($selector)) + return; + + $js.=" +jQuery('$selector').live('click',function(){ + jQuery.ajax({ + url: ".CJSON::encode($url).", + dataType: 'json', + cache: false, + success: function(data) { + jQuery('#$id').attr('src', data['url']); + jQuery('body').data('{$this->captchaAction}.hash', [data['hash1'], data['hash2']]); + } + }); + return false; +}); +"; + $cs->registerScript('Yii.CCaptcha#'.$id,$js); + } + + /** + * Checks if GD with FreeType support is loaded. + * @return boolean true if GD with FreeType support is loaded, otherwise false + * @since 1.1.5 + */ + public static function checkRequirements() + { + if (extension_loaded('gd')) + { + $gdinfo=gd_info(); + if( $gdinfo['FreeType Support']) + return true; + } + return false; + } +} + diff --git a/framework/web/widgets/captcha/CCaptchaAction.php b/framework/web/widgets/captcha/CCaptchaAction.php new file mode 100644 index 0000000..05ec194 --- /dev/null +++ b/framework/web/widgets/captcha/CCaptchaAction.php @@ -0,0 +1,272 @@ +<?php + +/** + * CCaptchaAction class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CCaptchaAction renders a CAPTCHA image. + * + * CCaptchaAction is used together with {@link CCaptcha} and {@link CCaptchaValidator} + * to provide the {@link http://en.wikipedia.org/wiki/Captcha CAPTCHA} feature. + * + * You must configure properties of CCaptchaAction to customize the appearance of + * the generated image. + * + * Note, CCaptchaAction requires PHP GD2 extension. + * + * Using CAPTCHA involves the following steps: + * <ol> + * <li>Override {@link CController::actions()} and register an action of class CCaptchaAction with ID 'captcha'.</li> + * <li>In the form model, declare an attribute to store user-entered verification code, and declare the attribute + * to be validated by the 'captcha' validator.</li> + * <li>In the controller view, insert a {@link CCaptcha} widget in the form.</li> + * </ol> + * + * @property string $verifyCode The verification code. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CCaptchaAction.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets.captcha + * @since 1.0 + */ +class CCaptchaAction extends CAction +{ + /** + * The name of the GET parameter indicating whether the CAPTCHA image should be regenerated. + */ + const REFRESH_GET_VAR='refresh'; + /** + * Prefix to the session variable name used by the action. + */ + const SESSION_VAR_PREFIX='Yii.CCaptchaAction.'; + /** + * @var integer how many times should the same CAPTCHA be displayed. Defaults to 3. + * A value less than or equal to 0 means the test is unlimited (available since version 1.1.2). + */ + public $testLimit = 3; + /** + * @var integer the width of the generated CAPTCHA image. Defaults to 120. + */ + public $width = 120; + /** + * @var integer the height of the generated CAPTCHA image. Defaults to 50. + */ + public $height = 50; + /** + * @var integer padding around the text. Defaults to 2. + */ + public $padding = 2; + /** + * @var integer the background color. For example, 0x55FF00. + * Defaults to 0xFFFFFF, meaning white color. + */ + public $backColor = 0xFFFFFF; + /** + * @var integer the font color. For example, 0x55FF00. Defaults to 0x2040A0 (blue color). + */ + public $foreColor = 0x2040A0; + /** + * @var boolean whether to use transparent background. Defaults to false. + */ + public $transparent = false; + /** + * @var integer the minimum length for randomly generated word. Defaults to 6. + */ + public $minLength = 6; + /** + * @var integer the maximum length for randomly generated word. Defaults to 7. + */ + public $maxLength = 7; + /** + * @var integer the offset between characters. Defaults to -2. You can adjust this property + * in order to decrease or increase the readability of the captcha. + * @since 1.1.7 + **/ + public $offset = -2; + /** + * @var string the TrueType font file. Defaults to Duality.ttf which is provided + * with the Yii release. + */ + public $fontFile; + /** + * @var string the fixed verification code. When this is property is set, + * {@link getVerifyCode} will always return this value. + * This is mainly used in automated tests where we want to be able to reproduce + * the same verification code each time we run the tests. + * Defaults to null, meaning the verification code will be randomly generated. + * @since 1.1.4 + */ + public $fixedVerifyCode; + + /** + * Runs the action. + */ + public function run() + { + if(isset($_GET[self::REFRESH_GET_VAR])) // AJAX request for regenerating code + { + $code=$this->getVerifyCode(true); + echo CJSON::encode(array( + 'hash1'=>$this->generateValidationHash($code), + 'hash2'=>$this->generateValidationHash(strtolower($code)), + // we add a random 'v' parameter so that FireFox can refresh the image + // when src attribute of image tag is changed + 'url'=>$this->getController()->createUrl($this->getId(),array('v' => uniqid())), + )); + } + else + $this->renderImage($this->getVerifyCode()); + Yii::app()->end(); + } + + /** + * Generates a hash code that can be used for client side validation. + * @param string $code the CAPTCHA code + * @return string a hash code generated from the CAPTCHA code + * @since 1.1.7 + */ + public function generateValidationHash($code) + { + for($h=0,$i=strlen($code)-1;$i>=0;--$i) + $h+=ord($code[$i]); + return $h; + } + + /** + * Gets the verification code. + * @param boolean $regenerate whether the verification code should be regenerated. + * @return string the verification code. + */ + public function getVerifyCode($regenerate=false) + { + if($this->fixedVerifyCode !== null) + return $this->fixedVerifyCode; + + $session = Yii::app()->session; + $session->open(); + $name = $this->getSessionKey(); + if($session[$name] === null || $regenerate) + { + $session[$name] = $this->generateVerifyCode(); + $session[$name . 'count'] = 1; + } + return $session[$name]; + } + + /** + * Validates the input to see if it matches the generated code. + * @param string $input user input + * @param boolean $caseSensitive whether the comparison should be case-sensitive + * @return boolean whether the input is valid + */ + public function validate($input,$caseSensitive) + { + $code = $this->getVerifyCode(); + $valid = $caseSensitive ? ($input === $code) : !strcasecmp($input,$code); + $session = Yii::app()->session; + $session->open(); + $name = $this->getSessionKey() . 'count'; + $session[$name] = $session[$name] + 1; + if($session[$name] > $this->testLimit && $this->testLimit > 0) + $this->getVerifyCode(true); + return $valid; + } + + /** + * Generates a new verification code. + * @return string the generated verification code + */ + protected function generateVerifyCode() + { + if($this->minLength < 3) + $this->minLength = 3; + if($this->maxLength > 20) + $this->maxLength = 20; + if($this->minLength > $this->maxLength) + $this->maxLength = $this->minLength; + $length = mt_rand($this->minLength,$this->maxLength); + + $letters = 'bcdfghjklmnpqrstvwxyz'; + $vowels = 'aeiou'; + $code = ''; + for($i = 0; $i < $length; ++$i) + { + if($i % 2 && mt_rand(0,10) > 2 || !($i % 2) && mt_rand(0,10) > 9) + $code.=$vowels[mt_rand(0,4)]; + else + $code.=$letters[mt_rand(0,20)]; + } + + return $code; + } + + /** + * Returns the session variable name used to store verification code. + * @return string the session variable name + */ + protected function getSessionKey() + { + return self::SESSION_VAR_PREFIX . Yii::app()->getId() . '.' . $this->getController()->getUniqueId() . '.' . $this->getId(); + } + + /** + * Renders the CAPTCHA image based on the code. + * @param string $code the verification code + * @return string image content + */ + protected function renderImage($code) + { + $image = imagecreatetruecolor($this->width,$this->height); + + $backColor = imagecolorallocate($image, + (int)($this->backColor % 0x1000000 / 0x10000), + (int)($this->backColor % 0x10000 / 0x100), + $this->backColor % 0x100); + imagefilledrectangle($image,0,0,$this->width,$this->height,$backColor); + imagecolordeallocate($image,$backColor); + + if($this->transparent) + imagecolortransparent($image,$backColor); + + $foreColor = imagecolorallocate($image, + (int)($this->foreColor % 0x1000000 / 0x10000), + (int)($this->foreColor % 0x10000 / 0x100), + $this->foreColor % 0x100); + + if($this->fontFile === null) + $this->fontFile = dirname(__FILE__) . '/Duality.ttf'; + + $length = strlen($code); + $box = imagettfbbox(30,0,$this->fontFile,$code); + $w = $box[4] - $box[0] + $this->offset * ($length - 1); + $h = $box[1] - $box[5]; + $scale = min(($this->width - $this->padding * 2) / $w,($this->height - $this->padding * 2) / $h); + $x = 10; + $y = round($this->height * 27 / 40); + for($i = 0; $i < $length; ++$i) + { + $fontSize = (int)(rand(26,32) * $scale * 0.8); + $angle = rand(-10,10); + $letter = $code[$i]; + $box = imagettftext($image,$fontSize,$angle,$x,$y,$foreColor,$this->fontFile,$letter); + $x = $box[2] + $this->offset; + } + + imagecolordeallocate($image,$foreColor); + + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Transfer-Encoding: binary'); + header("Content-type: image/png"); + imagepng($image); + imagedestroy($image); + } + +}
\ No newline at end of file diff --git a/framework/web/widgets/captcha/Duality.ttf b/framework/web/widgets/captcha/Duality.ttf Binary files differnew file mode 100644 index 0000000..581d5ce --- /dev/null +++ b/framework/web/widgets/captcha/Duality.ttf diff --git a/framework/web/widgets/pagers/CBasePager.php b/framework/web/widgets/pagers/CBasePager.php new file mode 100644 index 0000000..62ee4a2 --- /dev/null +++ b/framework/web/widgets/pagers/CBasePager.php @@ -0,0 +1,135 @@ +<?php +/** + * CBasePager class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CBasePager is the base class for all pagers. + * + * It provides the calculation of page count and maintains the current page. + * + * @property CPagination $pages The pagination information. + * @property integer $pageSize Number of items in each page. + * @property integer $itemCount Total number of items. + * @property integer $pageCount Number of pages. + * @property integer $currentPage The zero-based index of the current page. Defaults to 0. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CBasePager.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.web.widgets.pagers + * @since 1.0 + */ +abstract class CBasePager extends CWidget +{ + private $_pages; + + /** + * Returns the pagination information used by this pager. + * @return CPagination the pagination information + */ + public function getPages() + { + if($this->_pages===null) + $this->_pages=$this->createPages(); + return $this->_pages; + } + + /** + * Sets the pagination information used by this pager. + * @param CPagination $pages the pagination information + */ + public function setPages($pages) + { + $this->_pages=$pages; + } + + /** + * Creates the default pagination. + * This is called by {@link getPages} when the pagination is not set before. + * @return CPagination the default pagination instance. + */ + protected function createPages() + { + return new CPagination; + } + + /** + * @return integer number of items in each page. + * @see CPagination::getPageSize + */ + public function getPageSize() + { + return $this->getPages()->getPageSize(); + } + + /** + * @param integer $value number of items in each page + * @see CPagination::setPageSize + */ + public function setPageSize($value) + { + $this->getPages()->setPageSize($value); + } + + /** + * @return integer total number of items. + * @see CPagination::getItemCount + */ + public function getItemCount() + { + return $this->getPages()->getItemCount(); + } + + /** + * @param integer $value total number of items. + * @see CPagination::setItemCount + */ + public function setItemCount($value) + { + $this->getPages()->setItemCount($value); + } + + /** + * @return integer number of pages + * @see CPagination::getPageCount + */ + public function getPageCount() + { + return $this->getPages()->getPageCount(); + } + + /** + * @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. + * @see CPagination::getCurrentPage + */ + public function getCurrentPage($recalculate=true) + { + return $this->getPages()->getCurrentPage($recalculate); + } + + /** + * @param integer $value the zero-based index of the current page. + * @see CPagination::setCurrentPage + */ + public function setCurrentPage($value) + { + $this->getPages()->setCurrentPage($value); + } + + /** + * Creates the URL suitable for pagination. + * @param integer $page the page that the URL should point to. + * @return string the created URL + * @see CPagination::createPageUrl + */ + protected function createPageUrl($page) + { + return $this->getPages()->createPageUrl($this->getController(),$page); + } +} diff --git a/framework/web/widgets/pagers/CLinkPager.php b/framework/web/widgets/pagers/CLinkPager.php new file mode 100644 index 0000000..921dcda --- /dev/null +++ b/framework/web/widgets/pagers/CLinkPager.php @@ -0,0 +1,195 @@ +<?php +/** + * CLinkPager class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CLinkPager displays a list of hyperlinks that lead to different pages of target. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CLinkPager.php 3515 2011-12-28 12:29:24Z mdomba $ + * @package system.web.widgets.pagers + * @since 1.0 + */ +class CLinkPager extends CBasePager +{ + const CSS_FIRST_PAGE='first'; + const CSS_LAST_PAGE='last'; + const CSS_PREVIOUS_PAGE='previous'; + const CSS_NEXT_PAGE='next'; + const CSS_INTERNAL_PAGE='page'; + const CSS_HIDDEN_PAGE='hidden'; + const CSS_SELECTED_PAGE='selected'; + + /** + * @var integer maximum number of page buttons that can be displayed. Defaults to 10. + */ + public $maxButtonCount=10; + /** + * @var string the text label for the next page button. Defaults to 'Next >'. + */ + public $nextPageLabel; + /** + * @var string the text label for the previous page button. Defaults to '< Previous'. + */ + public $prevPageLabel; + /** + * @var string the text label for the first page button. Defaults to '<< First'. + */ + public $firstPageLabel; + /** + * @var string the text label for the last page button. Defaults to 'Last >>'. + */ + public $lastPageLabel; + /** + * @var string the text shown before page buttons. Defaults to 'Go to page: '. + */ + public $header; + /** + * @var string the text shown after page buttons. + */ + public $footer=''; + /** + * @var mixed the CSS file used for the widget. Defaults to null, meaning + * using the default CSS file included together with the widget. + * If false, no CSS file will be used. Otherwise, the specified CSS file + * will be included when using this widget. + */ + public $cssFile; + /** + * @var array HTML attributes for the pager container tag. + */ + public $htmlOptions=array(); + + /** + * Initializes the pager by setting some default property values. + */ + public function init() + { + if($this->nextPageLabel===null) + $this->nextPageLabel=Yii::t('yii','Next >'); + if($this->prevPageLabel===null) + $this->prevPageLabel=Yii::t('yii','< Previous'); + if($this->firstPageLabel===null) + $this->firstPageLabel=Yii::t('yii','<< First'); + if($this->lastPageLabel===null) + $this->lastPageLabel=Yii::t('yii','Last >>'); + if($this->header===null) + $this->header=Yii::t('yii','Go to page: '); + + if(!isset($this->htmlOptions['id'])) + $this->htmlOptions['id']=$this->getId(); + if(!isset($this->htmlOptions['class'])) + $this->htmlOptions['class']='yiiPager'; + } + + /** + * Executes the widget. + * This overrides the parent implementation by displaying the generated page buttons. + */ + public function run() + { + $this->registerClientScript(); + $buttons=$this->createPageButtons(); + if(empty($buttons)) + return; + echo $this->header; + echo CHtml::tag('ul',$this->htmlOptions,implode("\n",$buttons)); + echo $this->footer; + } + + /** + * Creates the page buttons. + * @return array a list of page buttons (in HTML code). + */ + protected function createPageButtons() + { + if(($pageCount=$this->getPageCount())<=1) + return array(); + + list($beginPage,$endPage)=$this->getPageRange(); + $currentPage=$this->getCurrentPage(false); // currentPage is calculated in getPageRange() + $buttons=array(); + + // first page + $buttons[]=$this->createPageButton($this->firstPageLabel,0,self::CSS_FIRST_PAGE,$currentPage<=0,false); + + // prev page + if(($page=$currentPage-1)<0) + $page=0; + $buttons[]=$this->createPageButton($this->prevPageLabel,$page,self::CSS_PREVIOUS_PAGE,$currentPage<=0,false); + + // internal pages + for($i=$beginPage;$i<=$endPage;++$i) + $buttons[]=$this->createPageButton($i+1,$i,self::CSS_INTERNAL_PAGE,false,$i==$currentPage); + + // next page + if(($page=$currentPage+1)>=$pageCount-1) + $page=$pageCount-1; + $buttons[]=$this->createPageButton($this->nextPageLabel,$page,self::CSS_NEXT_PAGE,$currentPage>=$pageCount-1,false); + + // last page + $buttons[]=$this->createPageButton($this->lastPageLabel,$pageCount-1,self::CSS_LAST_PAGE,$currentPage>=$pageCount-1,false); + + return $buttons; + } + + /** + * Creates a page button. + * You may override this method to customize the page buttons. + * @param string $label the text label for the button + * @param integer $page the page number + * @param string $class the CSS class for the page button. This could be 'page', 'first', 'last', 'next' or 'previous'. + * @param boolean $hidden whether this page button is visible + * @param boolean $selected whether this page button is selected + * @return string the generated button + */ + protected function createPageButton($label,$page,$class,$hidden,$selected) + { + if($hidden || $selected) + $class.=' '.($hidden ? self::CSS_HIDDEN_PAGE : self::CSS_SELECTED_PAGE); + return '<li class="'.$class.'">'.CHtml::link($label,$this->createPageUrl($page)).'</li>'; + } + + /** + * @return array the begin and end pages that need to be displayed. + */ + protected function getPageRange() + { + $currentPage=$this->getCurrentPage(); + $pageCount=$this->getPageCount(); + + $beginPage=max(0, $currentPage-(int)($this->maxButtonCount/2)); + if(($endPage=$beginPage+$this->maxButtonCount-1)>=$pageCount) + { + $endPage=$pageCount-1; + $beginPage=max(0,$endPage-$this->maxButtonCount+1); + } + return array($beginPage,$endPage); + } + + /** + * Registers the needed client scripts (mainly CSS file). + */ + public function registerClientScript() + { + if($this->cssFile!==false) + self::registerCssFile($this->cssFile); + } + + /** + * Registers the needed CSS file. + * @param string $url the CSS URL. If null, a default CSS URL will be used. + */ + public static function registerCssFile($url=null) + { + if($url===null) + $url=CHtml::asset(Yii::getPathOfAlias('system.web.widgets.pagers.pager').'.css'); + Yii::app()->getClientScript()->registerCssFile($url); + } +} diff --git a/framework/web/widgets/pagers/CListPager.php b/framework/web/widgets/pagers/CListPager.php new file mode 100644 index 0000000..0ab1f3e --- /dev/null +++ b/framework/web/widgets/pagers/CListPager.php @@ -0,0 +1,89 @@ +<?php +/** + * CListPager class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + + +/** + * CListPager displays a dropdown list that contains options leading to different pages of target. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CListPager.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @package system.web.widgets.pagers + * @since 1.0 + */ +class CListPager extends CBasePager +{ + /** + * @var string the text shown before page buttons. Defaults to 'Go to page: '. + */ + public $header; + /** + * @var string the text shown after page buttons. + */ + public $footer; + /** + * @var string the text displayed as a prompt option in the dropdown list. Defaults to null, meaning no prompt. + */ + public $promptText; + /** + * @var string the format string used to generate page selection text. + * The sprintf function will be used to perform the formatting. + */ + public $pageTextFormat; + /** + * @var array HTML attributes for the enclosing 'div' tag. + */ + public $htmlOptions=array(); + + /** + * Initializes the pager by setting some default property values. + */ + public function init() + { + if($this->header===null) + $this->header=Yii::t('yii','Go to page: '); + if(!isset($this->htmlOptions['id'])) + $this->htmlOptions['id']=$this->getId(); + if($this->promptText!==null) + $this->htmlOptions['prompt']=$this->promptText; + if(!isset($this->htmlOptions['onchange'])) + $this->htmlOptions['onchange']="if(this.value!='') {window.location=this.value;};"; + } + + /** + * Executes the widget. + * This overrides the parent implementation by displaying the generated page buttons. + */ + public function run() + { + if(($pageCount=$this->getPageCount())<=1) + return; + $pages=array(); + for($i=0;$i<$pageCount;++$i) + $pages[$this->createPageUrl($i)]=$this->generatePageText($i); + $selection=$this->createPageUrl($this->getCurrentPage()); + echo $this->header; + echo CHtml::dropDownList($this->getId(),$selection,$pages,$this->htmlOptions); + echo $this->footer; + } + + /** + * Generates the list option for the specified page number. + * You may override this method to customize the option display. + * @param integer $page zero-based page number + * @return string the list option for the page number + */ + protected function generatePageText($page) + { + if($this->pageTextFormat!==null) + return sprintf($this->pageTextFormat,$page+1); + else + return $page+1; + } +}
\ No newline at end of file diff --git a/framework/web/widgets/pagers/pager.css b/framework/web/widgets/pagers/pager.css new file mode 100644 index 0000000..1c802cc --- /dev/null +++ b/framework/web/widgets/pagers/pager.css @@ -0,0 +1,67 @@ +/** + * CSS styles for CLinkPager. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2010 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: pager.css 1678 2010-01-07 21:02:00Z qiang.xue $ + * @since 1.0 + */ + +ul.yiiPager +{ + font-size:11px; + border:0; + margin:0; + padding:0; + line-height:100%; + display:inline; +} + +ul.yiiPager li +{ + display:inline; +} + +ul.yiiPager a:link, +ul.yiiPager a:visited +{ + border:solid 1px #9aafe5; + font-weight:bold; + color:#0e509e; + padding:1px 6px; + text-decoration:none; +} + +ul.yiiPager .page a +{ + font-weight:normal; +} + +ul.yiiPager a:hover +{ + border:solid 1px #0e509e; +} + +ul.yiiPager .selected a +{ + background:#2e6ab1; + color:#FFFFFF; + font-weight:bold; +} + +ul.yiiPager .hidden a +{ + border:solid 1px #DEDEDE; + color:#888888; +} + +/** + * Hide first and last buttons by default. + */ +ul.yiiPager .first, +ul.yiiPager .last +{ + display:none; +}
\ No newline at end of file diff --git a/framework/web/widgets/views/flexWidget.php b/framework/web/widgets/views/flexWidget.php new file mode 100644 index 0000000..9ee3d43 --- /dev/null +++ b/framework/web/widgets/views/flexWidget.php @@ -0,0 +1,100 @@ +<?php +/** + * The view file for CFlexWidget. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @version $Id: flexWidget.php 2799 2011-01-01 19:31:13Z qiang.xue $ + * @package system.web.widgets.views + * @since 1.0 + */ +?> +<script type="text/javascript"> +/*<![CDATA[*/ +// Version check for the Flash Player that has the ability to start Player Product Install (6.0r65) +var hasProductInstall = DetectFlashVer(6, 0, 65); + +// Version check based upon the values defined in globals +var hasRequestedVersion = DetectFlashVer(9, 0, 0); + +// Check to see if a player with Flash Product Install is available and the version does not meet the requirements for playback +if ( hasProductInstall && !hasRequestedVersion ) { + // MMdoctitle is the stored document.title value used by the installation process to close the window that started the process + // This is necessary in order to close browser windows that are still utilizing the older version of the player after installation has completed + // DO NOT MODIFY THE FOLLOWING FOUR LINES + // Location visited after installation is complete if installation is required + var MMPlayerType = (isIE == true) ? "ActiveX" : "PlugIn"; + var MMredirectURL = window.location; + document.title = document.title.slice(0, 47) + " - Flash Player Installation"; + var MMdoctitle = document.title; + + AC_FL_RunContent( + "src", "<?php echo $this->baseUrl ?>/playerProductInstall", + "FlashVars", "MMredirectURL="+MMredirectURL+'&MMplayerType='+MMPlayerType+'&MMdoctitle='+MMdoctitle+"", + "width", "<?php echo $this->width; ?>", + "height", "<?php echo $this->height; ?>", + "align", "<?php echo $this->align; ?>", + "id", "<?php echo $this->name; ?>", + "quality", "<?php echo $this->quality; ?>", + "bgcolor", "<?php echo $this->bgColor; ?>", + "name", "<?php echo $this->name; ?>", + "allowScriptAccess","<?php echo $this->allowScriptAccess ?>", + "allowFullScreen","<?php echo $this->allowFullScreen ?>", + "type", "application/x-shockwave-flash", + "pluginspage", "http://www.adobe.com/go/getflashplayer" + ); +} else if (hasRequestedVersion) { + // if we've detected an acceptable version + // embed the Flash Content SWF when all tests are passed + AC_FL_RunContent( + "src", "<?php echo $this->baseUrl ?>/<?php echo $this->name ?>", + "width", "<?php echo $this->width ?>", + "height", "<?php echo $this->height ?>", + "align", "<?php echo $this->align ?>", + "id", "<?php echo $this->name ?>", + "quality", "<?php echo $this->quality ?>", + "bgcolor", "<?php echo $this->bgColor ?>", + "name", "<?php echo $this->name ?>", + "flashvars","<?php echo $this->flashVarsAsString; ?>", + "allowScriptAccess","<?php echo $this->allowScriptAccess ?>", + "allowFullScreen","<?php echo $this->allowFullScreen ?>", + "type", "application/x-shockwave-flash", + "pluginspage", "http://www.adobe.com/go/getflashplayer" + ); +} else { // flash is too old or we can't detect the plugin + var alternateContent = '<?php echo CJavaScript::quote($this->altHtmlContent); ?>'; + document.write(alternateContent); // insert non-flash content +} +/*]]>*/ +</script> +<noscript> + <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + id="<?php echo $this->name ?>" + width="<?php echo $this->width ?>" + height="<?php echo $this->height ?>" + codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab"> + <param name="movie" value="<?php echo $this->baseUrl ?>/<?php echo $this->name ?>.swf" /> + <param name="quality" value="<?php echo $this->quality ?>" /> + <param name="bgcolor" value="<?php echo $this->bgColor ?>" /> + <param name="flashVars" value="<?php echo $this->flashVarsAsString ?>" /> + <param name="allowScriptAccess" value="<?php echo $this->allowScriptAccess ?>" /> + <param name="allowFullScreen" value="<?php echo $this->allowFullScreen ?>" /> + <embed src="<?php echo $this->baseUrl ?>/<?php echo $this->name ?>.swf" + quality="<?php echo $this->quality ?>" + bgcolor="<?php echo $this->bgColor ?>" + width="<?php echo $this->width ?>" + height="<?php echo $this->height ?>" + name="<?php echo $this->name ?>" + align="<?php echo $this->align ?>" + play="true" + loop="false" + quality="<?php echo $this->quality ?>" + allowScriptAccess="<?php echo $this->allowScriptAccess ?>" + allowFullScreen="<?php echo $this->allowFullScreen ?>" + type="application/x-shockwave-flash" + pluginspage="http://www.adobe.com/go/getflashplayer"> + </embed> + </object> +</noscript>
\ No newline at end of file |
