summaryrefslogtreecommitdiff
path: root/framework/web
diff options
context:
space:
mode:
authorPatrick Seeger <pseeger@ccwn.org>2012-04-13 23:11:05 +0200
committerPatrick Seeger <pseeger@ccwn.org>2012-04-13 23:11:05 +0200
commit341cc4dd9c53ffbfb863e026dd58549c1082c7a7 (patch)
tree1bbbed20313bafb9b063b6b4d894fe580d8b000f /framework/web
yii-framework 1.1.10 hinzugefügtHEADmaster
Diffstat (limited to 'framework/web')
-rw-r--r--framework/web/CActiveDataProvider.php183
-rw-r--r--framework/web/CArrayDataProvider.php162
-rw-r--r--framework/web/CAssetManager.php304
-rw-r--r--framework/web/CBaseController.php303
-rw-r--r--framework/web/CCacheHttpSession.php113
-rw-r--r--framework/web/CClientScript.php756
-rw-r--r--framework/web/CController.php1232
-rw-r--r--framework/web/CDataProvider.php207
-rw-r--r--framework/web/CDbHttpSession.php267
-rw-r--r--framework/web/CExtController.php54
-rw-r--r--framework/web/CFormModel.php79
-rw-r--r--framework/web/CHttpCookie.php63
-rw-r--r--framework/web/CHttpRequest.php1064
-rw-r--r--framework/web/CHttpSession.php572
-rw-r--r--framework/web/CHttpSessionIterator.php92
-rw-r--r--framework/web/COutputEvent.php38
-rw-r--r--framework/web/CPagination.php241
-rw-r--r--framework/web/CSort.php457
-rw-r--r--framework/web/CSqlDataProvider.php132
-rw-r--r--framework/web/CTheme.php141
-rw-r--r--framework/web/CThemeManager.php131
-rw-r--r--framework/web/CUploadedFile.php274
-rw-r--r--framework/web/CUrlManager.php849
-rw-r--r--framework/web/CWebApplication.php537
-rw-r--r--framework/web/CWebModule.php197
-rw-r--r--framework/web/CWidgetFactory.php198
-rw-r--r--framework/web/actions/CAction.php110
-rw-r--r--framework/web/actions/CInlineAction.php53
-rw-r--r--framework/web/actions/CViewAction.php168
-rw-r--r--framework/web/auth/CAccessControlFilter.php342
-rw-r--r--framework/web/auth/CAuthAssignment.php107
-rw-r--r--framework/web/auth/CAuthItem.php277
-rw-r--r--framework/web/auth/CAuthManager.php166
-rw-r--r--framework/web/auth/CBaseUserIdentity.php132
-rw-r--r--framework/web/auth/CDbAuthManager.php600
-rw-r--r--framework/web/auth/CPhpAuthManager.php504
-rw-r--r--framework/web/auth/CUserIdentity.php82
-rw-r--r--framework/web/auth/CWebUser.php796
-rw-r--r--framework/web/auth/schema-mssql.sql42
-rw-r--r--framework/web/auth/schema-mysql.sql42
-rw-r--r--framework/web/auth/schema-oci.sql42
-rw-r--r--framework/web/auth/schema-pgsql.sql42
-rw-r--r--framework/web/auth/schema-sqlite.sql42
-rw-r--r--framework/web/filters/CFilter.php75
-rw-r--r--framework/web/filters/CFilterChain.php136
-rw-r--r--framework/web/filters/CInlineFilter.php61
-rw-r--r--framework/web/form/CForm.php615
-rw-r--r--framework/web/form/CFormButtonElement.php139
-rw-r--r--framework/web/form/CFormElement.php168
-rw-r--r--framework/web/form/CFormElementCollection.php112
-rw-r--r--framework/web/form/CFormInputElement.php255
-rw-r--r--framework/web/form/CFormStringElement.php71
-rw-r--r--framework/web/helpers/CGoogleApi.php71
-rw-r--r--framework/web/helpers/CHtml.php2122
-rw-r--r--framework/web/helpers/CJSON.php704
-rw-r--r--framework/web/helpers/CJavaScript.php120
-rw-r--r--framework/web/js/packages.php74
-rw-r--r--framework/web/js/source/autocomplete/indicator.gifbin0 -> 1553 bytes
-rw-r--r--framework/web/js/source/autocomplete/jquery.autocomplete.css48
-rw-r--r--framework/web/js/source/jquery.ajaxqueue.js116
-rw-r--r--framework/web/js/source/jquery.autocomplete.js813
-rw-r--r--framework/web/js/source/jquery.ba-bbq.js1137
-rw-r--r--framework/web/js/source/jquery.bgiframe.js39
-rw-r--r--framework/web/js/source/jquery.cookie.js92
-rw-r--r--framework/web/js/source/jquery.js9266
-rw-r--r--framework/web/js/source/jquery.maskedinput.js258
-rw-r--r--framework/web/js/source/jquery.maskedinput.min.js7
-rw-r--r--framework/web/js/source/jquery.metadata.js148
-rw-r--r--framework/web/js/source/jquery.min.js4
-rw-r--r--framework/web/js/source/jquery.multifile.js536
-rw-r--r--framework/web/js/source/jquery.rating.js381
-rw-r--r--framework/web/js/source/jquery.treeview.async.js110
-rw-r--r--framework/web/js/source/jquery.treeview.edit.js37
-rw-r--r--framework/web/js/source/jquery.treeview.js256
-rw-r--r--framework/web/js/source/jquery.yii.js53
-rw-r--r--framework/web/js/source/jquery.yiiactiveform.js426
-rw-r--r--framework/web/js/source/jquery.yiitab.js50
-rw-r--r--framework/web/js/source/jui/MIT-LICENSE.txt25
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-bg_flat_0_aaaaaa_40x100.pngbin0 -> 86 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-bg_flat_75_ffffff_40x100.pngbin0 -> 75 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-bg_glass_55_fbf9ee_1x400.pngbin0 -> 144 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-bg_glass_65_ffffff_1x400.pngbin0 -> 99 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-bg_glass_75_dadada_1x400.pngbin0 -> 142 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-bg_glass_75_e6e6e6_1x400.pngbin0 -> 137 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-bg_glass_95_fef1ec_1x400.pngbin0 -> 140 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-bg_highlight-soft_75_cccccc_1x100.pngbin0 -> 86 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-icons_222222_256x240.pngbin0 -> 3800 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-icons_2e83ff_256x240.pngbin0 -> 3800 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-icons_454545_256x240.pngbin0 -> 3800 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-icons_888888_256x240.pngbin0 -> 3800 bytes
-rw-r--r--framework/web/js/source/jui/css/base/images/ui-icons_cd0a0a_256x240.pngbin0 -> 3800 bytes
-rw-r--r--framework/web/js/source/jui/css/base/jquery-ui.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.accordion.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.all.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.autocomplete.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.base.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.button.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.core.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.datepicker.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.dialog.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.progressbar.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.resizable.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.selectable.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.slider.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.tabs.css10
-rw-r--r--framework/web/js/source/jui/css/base/jquery.ui.theme.css12
-rw-r--r--framework/web/js/source/jui/js/jquery-ui-i18n.min.js2
-rw-r--r--framework/web/js/source/jui/js/jquery-ui.min.js15
-rw-r--r--framework/web/js/source/rating/delete.gifbin0 -> 752 bytes
-rw-r--r--framework/web/js/source/rating/jquery.rating.css12
-rw-r--r--framework/web/js/source/rating/star.gifbin0 -> 815 bytes
-rw-r--r--framework/web/js/source/treeview/images/ajax-loader.gifbin0 -> 847 bytes
-rw-r--r--framework/web/js/source/treeview/images/file.gifbin0 -> 110 bytes
-rw-r--r--framework/web/js/source/treeview/images/folder-closed.gifbin0 -> 105 bytes
-rw-r--r--framework/web/js/source/treeview/images/folder.gifbin0 -> 106 bytes
-rw-r--r--framework/web/js/source/treeview/images/minus.gifbin0 -> 837 bytes
-rw-r--r--framework/web/js/source/treeview/images/plus.gifbin0 -> 841 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-black-line.gifbin0 -> 1877 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-black.gifbin0 -> 1216 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-default-line.gifbin0 -> 1993 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-default.gifbin0 -> 1222 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-famfamfam-line.gifbin0 -> 807 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-famfamfam.gifbin0 -> 1280 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-gray-line.gifbin0 -> 1877 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-gray.gifbin0 -> 1230 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-red-line.gifbin0 -> 1877 bytes
-rw-r--r--framework/web/js/source/treeview/images/treeview-red.gifbin0 -> 1230 bytes
-rw-r--r--framework/web/js/source/treeview/jquery.treeview.css74
-rw-r--r--framework/web/js/source/yiitab/jquery.yiitab.css58
-rw-r--r--framework/web/renderers/CPradoViewRenderer.php305
-rw-r--r--framework/web/renderers/CViewRenderer.php97
-rw-r--r--framework/web/services/CWebService.php283
-rw-r--r--framework/web/services/CWebServiceAction.php132
-rw-r--r--framework/web/services/CWsdlGenerator.php419
-rw-r--r--framework/web/widgets/CActiveForm.php787
-rw-r--r--framework/web/widgets/CAutoComplete.php291
-rw-r--r--framework/web/widgets/CClipWidget.php53
-rw-r--r--framework/web/widgets/CContentDecorator.php82
-rw-r--r--framework/web/widgets/CFilterWidget.php75
-rw-r--r--framework/web/widgets/CFlexWidget.php122
-rw-r--r--framework/web/widgets/CHtmlPurifier.php82
-rw-r--r--framework/web/widgets/CInputWidget.php81
-rw-r--r--framework/web/widgets/CMarkdown.php118
-rw-r--r--framework/web/widgets/CMaskedTextField.php113
-rw-r--r--framework/web/widgets/CMultiFileUpload.php142
-rw-r--r--framework/web/widgets/COutputCache.php347
-rw-r--r--framework/web/widgets/COutputProcessor.php77
-rw-r--r--framework/web/widgets/CStarRating.php217
-rw-r--r--framework/web/widgets/CTabView.php212
-rw-r--r--framework/web/widgets/CTextHighlighter.php125
-rw-r--r--framework/web/widgets/CTreeView.php246
-rw-r--r--framework/web/widgets/CWidget.php249
-rw-r--r--framework/web/widgets/captcha/CCaptcha.php171
-rw-r--r--framework/web/widgets/captcha/CCaptchaAction.php272
-rw-r--r--framework/web/widgets/captcha/Duality.ttfbin0 -> 50560 bytes
-rw-r--r--framework/web/widgets/pagers/CBasePager.php135
-rw-r--r--framework/web/widgets/pagers/CLinkPager.php195
-rw-r--r--framework/web/widgets/pagers/CListPager.php89
-rw-r--r--framework/web/widgets/pagers/pager.css67
-rw-r--r--framework/web/widgets/views/flexWidget.php100
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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 '&lt;ParamName:RegExp&gt;'.
+ *
+ * When parsing a URL, a matching rule will extract the named parameters from the path info
+ * and put them into the $_GET variable; when creating a URL, a matching rule will extract
+ * the named parameters from $_GET and put them into the path info part of the created URL.
+ *
+ * If a pattern ends with '/*', it means additional GET parameters may be appended to the path
+ * info part of the URL; otherwise, the GET parameters can only appear in the query string part.
+ *
+ * To specify URL rules, set the {@link setRules rules} property as an array of rules (pattern=>route).
+ * For example,
+ * <pre>
+ * array(
+ * 'articles'=>'article/list',
+ * 'article/<id:\d+>/*'=>'article/read',
+ * )
+ * </pre>
+ * Two rules are specified in the above:
+ * <ul>
+ * <li>The first rule says that if the user requests the URL '/path/to/index.php/articles',
+ * it should be treated as '/path/to/index.php/article/list'; and vice versa applies
+ * when constructing such a URL.</li>
+ * <li>The second rule contains a named parameter 'id' which is specified using
+ * the &lt;ParamName:RegExp&gt; syntax. It says that if the user requests the URL
+ * '/path/to/index.php/article/13', it should be treated as '/path/to/index.php/article/read?id=13';
+ * and vice versa applies when constructing such a URL.</li>
+ * </ul>
+ *
+ * The route part may contain references to named parameters defined in the pattern part.
+ * This allows a rule to be applied to different routes based on matching criteria.
+ * For example,
+ * <pre>
+ * array(
+ * '<_c:(post|comment)>/<id:\d+>/<_a:(create|update|delete)>'=>'<_c>/<_a>',
+ * '<_c:(post|comment)>/<id:\d+>'=>'<_c>/view',
+ * '<_c:(post|comment)>s/*'=>'<_c>/list',
+ * )
+ * </pre>
+ * In the above, we use two named parameters '<_c>' and '<_a>' in the route part. The '<_c>'
+ * parameter matches either 'post' or 'comment', while the '<_a>' parameter matches an action ID.
+ *
+ * Like normal rules, these rules can be used for both parsing and creating URLs.
+ * For example, using the rules above, the URL '/index.php/post/123/create'
+ * would be parsed as the route 'post/create' with GET parameter 'id' being 123.
+ * And given the route 'post/list' and GET parameter 'page' being 2, we should get a URL
+ * '/index.php/posts/page/2'.
+ *
+ * It is also possible to include hostname into the rules for parsing and creating URLs.
+ * One may extract part of the hostname to be a GET parameter.
+ * For example, the URL <code>http://admin.example.com/en/profile</code> may be parsed into GET parameters
+ * <code>user=admin</code> and <code>lang=en</code>. On the other hand, rules with hostname may also be used to
+ * create URLs with parameterized hostnames.
+ *
+ * In order to use parameterized hostnames, simply declare URL rules with host info, e.g.:
+ * <pre>
+ * array(
+ * 'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile',
+ * )
+ * </pre>
+ *
+ * Starting from version 1.1.8, one can write custom URL rule classes and use them for one or several URL rules.
+ * For example,
+ * <pre>
+ * array(
+ * // a standard rule
+ * '<action:(login|logout)>' => 'site/<action>',
+ * // a custom rule using data in DB
+ * array(
+ * 'class' => 'application.components.MyUrlRule',
+ * 'connectionID' => 'db',
+ * ),
+ * )
+ * </pre>
+ * Please note that the custom URL rule class should extend from {@link CBaseUrlRule} and
+ * implement the following two methods,
+ * <ul>
+ * <li>{@link CBaseUrlRule::createUrl()}</li>
+ * <li>{@link CBaseUrlRule::parseUrl()}</li>
+ * </ul>
+ *
+ * CUrlManager is a default application component that may be accessed via
+ * {@link CWebApplication::getUrlManager()}.
+ *
+ * @property string $baseUrl The base URL of the application (the part after host name and before query string).
+ * If {@link showScriptName} is true, it will include the script name part.
+ * Otherwise, it will not, and the ending slashes are stripped off.
+ * @property string $urlFormat The URL format. Defaults to 'path'. Valid values include 'path' and 'get'.
+ * Please refer to the guide for more details about the difference between these two formats.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @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 &copy; 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 &copy; 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 &copy; 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'=>'&gt;&gt;',
+ * 'prevPageLabel'=>'&lt;&lt;',
+ * ),
+ * 'short'=>array(
+ * 'header'=>'',
+ * 'maxButtonCount'=>5,
+ * ),
+ * );
+ * </pre>
+ * In the above, there are two skins. The first one is the default skin which is indexed by the string "default".
+ * Note that {@link CWidget::skin} defaults to "default". Therefore, this is the skin that will be applied
+ * if we do not explicitly specify the {@link CWidget::skin} property.
+ * The second one is named as the "short" skin which will be used only when we set {@link CWidget::skin}
+ * to be "short".
+ *
+ * By default, CWidgetFactory looks for the skin of a widget under the "skins" directory
+ * of the current application's {@link CWebApplication::viewPath} (e.g. protected/views/skins).
+ * If a theme is being used, it will look for the skin under the "skins" directory of
+ * the theme's {@link CTheme::viewPath} (as well as the aforementioned skin directory).
+ * In case the specified skin is not found, a widget will still be created
+ * normally without causing any error.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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('<'=>'&lt;', '>'=>'&gt;'))."</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('<'=>'&lt;', '>'=>'&gt;'))."</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 &copy; 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 &copy; 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
new file mode 100644
index 0000000..085ccae
--- /dev/null
+++ b/framework/web/js/source/autocomplete/indicator.gif
Binary files differ
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: "&nbsp;",
+ children:[]
+ }, branch);
+ }
+ if (this.children && this.children.length) {
+ $.each(this.children, createNode, [branch])
+ }
+ }
+ }
+ $.ajax($.extend(true, {
+ url: settings.url,
+ dataType: "json",
+ data: {
+ root: root
+ },
+ success: function(response) {
+ child.empty();
+ $.each(response, createNode, [child]);
+ $(container).treeview({add: child});
+ }
+ }, settings.ajax));
+ /*
+ $.getJSON(settings.url, {root: root}, function(response) {
+ function createNode(parent) {
+ var current = $("<li/>").attr("id", this.id || "").html("<span>" + this.text + "</span>").appendTo(parent);
+ if (this.classes) {
+ current.children("span").addClass(this.classes);
+ }
+ if (this.expanded) {
+ current.addClass("open");
+ }
+ if (this.hasChildren || this.children && this.children.length) {
+ var branch = $("<ul/>").appendTo(current);
+ if (this.hasChildren) {
+ current.addClass("hasChildren");
+ createNode.call({
+ classes: "placeholder",
+ text: "&nbsp;",
+ children:[]
+ }, branch);
+ }
+ if (this.children && this.children.length) {
+ $.each(this.children, createNode, [branch])
+ }
+ }
+ }
+ child.empty();
+ $.each(response, createNode, [child]);
+ $(container).treeview({add: child});
+ });
+ */
+}
+
+var proxied = $.fn.treeview;
+$.fn.treeview = function(settings) {
+ if (!settings.url) {
+ return proxied.apply(this, arguments);
+ }
+ var container = this;
+ if (!container.children().size())
+ load(settings, "source", this, container);
+ var userToggle = settings.toggle;
+ return proxied.call(this, $.extend({}, settings, {
+ collapsed: true,
+ toggle: function() {
+ var $this = $(this);
+ if ($this.hasClass("hasChildren")) {
+ var childList = $this.removeClass("hasChildren").find("ul");
+ load(settings, this.id, childList, container);
+ }
+ if (userToggle) {
+ userToggle.apply(this, arguments);
+ }
+ }
+ }));
+};
+
+})(jQuery); \ 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 &copy; 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 &copy; 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 &copy; 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
new file mode 100644
index 0000000..b3fe56f
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-bg_flat_0_aaaaaa_40x100.png
Binary files differ
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
new file mode 100644
index 0000000..f63829a
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-bg_flat_75_ffffff_40x100.png
Binary files differ
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
new file mode 100644
index 0000000..e67a294
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_55_fbf9ee_1x400.png
Binary files differ
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
new file mode 100644
index 0000000..6a436ad
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_65_ffffff_1x400.png
Binary files differ
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
new file mode 100644
index 0000000..a3582db
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_75_dadada_1x400.png
Binary files differ
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
new file mode 100644
index 0000000..1bc1520
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_75_e6e6e6_1x400.png
Binary files differ
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
new file mode 100644
index 0000000..6d54032
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-bg_glass_95_fef1ec_1x400.png
Binary files differ
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
new 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
Binary files differ
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
new file mode 100644
index 0000000..e1994df
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-icons_222222_256x240.png
Binary files differ
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
new file mode 100644
index 0000000..42d63db
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-icons_2e83ff_256x240.png
Binary files differ
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
new file mode 100644
index 0000000..f076a41
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-icons_454545_256x240.png
Binary files differ
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
new file mode 100644
index 0000000..b53b730
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-icons_888888_256x240.png
Binary files differ
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
new file mode 100644
index 0000000..3b8d737
--- /dev/null
+++ b/framework/web/js/source/jui/css/base/images/ui-icons_cd0a0a_256x240.png
Binary files differ
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:"&#x3c;السابق",nextText:"Ų§Ł„ŲŖŲ§Ł„ŁŠ&#x3e;",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:"&#x3c;السابق",nextText:"Ų§Ł„ŲŖŲ§Ł„ŁŠ&#x3e;",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:"&#x3c;Geri",nextText:"İrəli&#x3e;",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:"&#x3c;назаГ",nextText:"напреГ&#x3e;",nextBigText:"&#x3e;&#x3e;",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:"&#x3c;",nextText:"&#x3e;",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:"&#x3c;Ant",nextText:"Seg&#x3e;",currentText:"Avui",monthNames:["Gener","Febrer","Mar&ccedil;","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:"&#x3c;Dříve",nextText:"Později&#x3e;",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:"&#x3c;Forrige",nextText:"NƦste&#x3e;",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:"&#x3c;zurück",nextText:"Vor&#x3e;",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:"&lt;Anta",nextText:"Sekv&gt;",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:"&#x3c;Ant",nextText:"Sig&#x3e;",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&eacute;rcoles","Jueves","Viernes","S&aacute;bado"],dayNamesShort:["Dom","Lun","Mar","Mi&eacute;","Juv","Vie","S&aacute;b"],dayNamesMin:["Do","Lu","Ma","Mi","Ju","Vi","S&aacute;"],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:"&#x3c;Aur",nextText:"Hur&#x3e;",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:"&#x3c;Ł‚ŲØŁ„ŁŠ",nextText:"بعدي&#x3e;",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:"&laquo;Edellinen",nextText:"Seuraava&raquo;",currentText:"T&auml;n&auml;&auml;n",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kes&auml;kuu","Hein&auml;kuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kes&auml;","Hein&auml;","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:"&#x3c;Fyrra",nextText:"NƦsta&#x3e;",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:"&#x3c;PrĆ©c",nextText:"Suiv&#x3e;",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:"&#x3c;Ant",nextText:"Seg&#x3e;",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&eacute;rcores","Xoves","Venres","S&aacute;bado"],dayNamesShort:["Dom","Lun","Mar","M&eacute;r","Xov","Ven","S&aacute;b"],dayNamesMin:["Do","Lu","Ma","M&eacute;","Xo","Ve","S&aacute;"],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:"&#x3c;הקודם",nextText:"הבא&#x3e;",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:"&#x3c;",nextText:"&#x3e;",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:"&#x3c;Õ†Õ”Õ­.",nextText:"Õ€Õ”Õ».&#x3e;",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:"&#x3c;mundur",nextText:"maju&#x3e;",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:"&#x3c; Fyrri",nextText:"N&aelig;sti &#x3e;",currentText:"&Iacute; dag",monthNames:["Jan&uacute;ar","Febr&uacute;ar","Mars","Apr&iacute;l","Ma&iacute","J&uacute;n&iacute;","J&uacute;l&iacute;","&Aacute;g&uacute;st","September","Okt&oacute;ber","N&oacute;vember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Ma&iacute;","J&uacute;n","J&uacute;l","&Aacute;g&uacute;","Sep","Okt","N&oacute;v","Des"],dayNames:["Sunnudagur","M&aacute;nudagur","&THORN;ri&eth;judagur","Mi&eth;vikudagur","Fimmtudagur","F&ouml;studagur","Laugardagur"],dayNamesShort:["Sun","M&aacute;n","&THORN;ri","Mi&eth;","Fim","F&ouml;s","Lau"],dayNamesMin:["Su","M&aacute;","&THORN;r","Mi","Fi","F&ouml;","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:"&#x3c;Prec",nextText:"Succ&#x3e;",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&#236","Marted&#236","Mercoled&#236","Gioved&#236","Venerd&#236","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:"&#x3c;前",nextText:"ꬔ&#x3e;",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:"&#x3c;АлГыңғы",nextText:"ŠšŠµŠ»ŠµŃŃ–&#x3e;",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:"&#x3c;Atgal",nextText:"Pirmyn&#x3e;",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:"&#x3C;",nextText:"&#x3E;",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:"&#x3c;Sebelum",nextText:"Selepas&#x3e;",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:"&laquo;Forrige",nextText:"Neste&raquo;",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:"&#x3c;Poprzedni",nextText:"Następny&#x3e;",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:"&#x3c;Anterior",nextText:"Pr&oacute;ximo&#x3e;",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Mar&ccedil;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&ccedil;a-feira","Quarta-feira","Quinta-feira","Sexta-feira","S&aacute;bado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","S&aacute;b"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","S&aacute;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:"&#x3c;Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Mar&ccedil;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&ccedil;a-feira","Quarta-feira","Quinta-feira","Sexta-feira","S&aacute;bado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","S&aacute;b"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","S&aacute;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:"&#x3c;Suandant",nextText:"Precedent&#x3e;",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:"&laquo; Luna precedentă",nextText:"Luna următoare &raquo;",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:"&#x3c;ŠŸŃ€ŠµŠ“",nextText:"ДлеГ&#x3e;",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:"&#x3c;PredchĆ”dzajĆŗci",nextText:"NasledujĆŗci&#x3e;",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:"&lt;Prej&#x161;nji",nextText:"Naslednji&gt;",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","&#x10C;etrtek","Petek","Sobota"],dayNamesShort:["Ned","Pon","Tor","Sre","&#x10C;et","Pet","Sob"],dayNamesMin:["Ne","Po","To","Sr","&#x10C;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:"&#x3c;mbrapa",nextText:"PĆ«rpara&#x3e;",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:"&#x3c;",nextText:"&#x3e;",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:"&#x3c;",nextText:"&#x3e;",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:"&laquo;Fƶrra",nextText:"NƤsta&raquo;",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:"&laquo;&nbsp;ย้อน",nextText:"ถัดไป&nbsp;&raquo;",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:"&#x3c;ŅšŠ°Ń„Š¾",nextText:"Пеш&#x3e;",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:"&#x3c;geri",nextText:"ileri&#x3e",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:"&#x3c;",nextText:"&#x3e;",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:"&#x3c;Trước",nextText:"Tiįŗæp&#x3e;",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:"&#x3c;上月",nextText:"äø‹ęœˆ&#x3e;",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:"&#x3c;上月",nextText:"äø‹ęœˆ&#x3e;",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:"&#x3c;上月",nextText:"äø‹ęœˆ&#x3e;",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)&&times--;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?"&#xa0;":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?"&#xa0;":""));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?"&#xa0;":"")+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||"&#160;",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||"&#160;"))}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&#8230;</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
new file mode 100644
index 0000000..43c6ca8
--- /dev/null
+++ b/framework/web/js/source/rating/delete.gif
Binary files differ
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
new file mode 100644
index 0000000..d0948a7
--- /dev/null
+++ b/framework/web/js/source/rating/star.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/ajax-loader.gif b/framework/web/js/source/treeview/images/ajax-loader.gif
new file mode 100644
index 0000000..bc54585
--- /dev/null
+++ b/framework/web/js/source/treeview/images/ajax-loader.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/file.gif b/framework/web/js/source/treeview/images/file.gif
new file mode 100644
index 0000000..7e62167
--- /dev/null
+++ b/framework/web/js/source/treeview/images/file.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/folder-closed.gif b/framework/web/js/source/treeview/images/folder-closed.gif
new file mode 100644
index 0000000..5411078
--- /dev/null
+++ b/framework/web/js/source/treeview/images/folder-closed.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/folder.gif b/framework/web/js/source/treeview/images/folder.gif
new file mode 100644
index 0000000..2b31631
--- /dev/null
+++ b/framework/web/js/source/treeview/images/folder.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/minus.gif b/framework/web/js/source/treeview/images/minus.gif
new file mode 100644
index 0000000..47fb7b7
--- /dev/null
+++ b/framework/web/js/source/treeview/images/minus.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/plus.gif b/framework/web/js/source/treeview/images/plus.gif
new file mode 100644
index 0000000..6906621
--- /dev/null
+++ b/framework/web/js/source/treeview/images/plus.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-black-line.gif b/framework/web/js/source/treeview/images/treeview-black-line.gif
new file mode 100644
index 0000000..e549687
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-black-line.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-black.gif b/framework/web/js/source/treeview/images/treeview-black.gif
new file mode 100644
index 0000000..d549b9f
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-black.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-default-line.gif b/framework/web/js/source/treeview/images/treeview-default-line.gif
new file mode 100644
index 0000000..37114d3
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-default-line.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-default.gif b/framework/web/js/source/treeview/images/treeview-default.gif
new file mode 100644
index 0000000..a12ac52
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-default.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-famfamfam-line.gif b/framework/web/js/source/treeview/images/treeview-famfamfam-line.gif
new file mode 100644
index 0000000..6e289ce
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-famfamfam-line.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-famfamfam.gif b/framework/web/js/source/treeview/images/treeview-famfamfam.gif
new file mode 100644
index 0000000..0cb178e
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-famfamfam.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-gray-line.gif b/framework/web/js/source/treeview/images/treeview-gray-line.gif
new file mode 100644
index 0000000..3760044
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-gray-line.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-gray.gif b/framework/web/js/source/treeview/images/treeview-gray.gif
new file mode 100644
index 0000000..cfb8a2f
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-gray.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-red-line.gif b/framework/web/js/source/treeview/images/treeview-red-line.gif
new file mode 100644
index 0000000..df9e749
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-red-line.gif
Binary files differ
diff --git a/framework/web/js/source/treeview/images/treeview-red.gif b/framework/web/js/source/treeview/images/treeview-red.gif
new file mode 100644
index 0000000..3bbb3a1
--- /dev/null
+++ b/framework/web/js/source/treeview/images/treeview-red.gif
Binary files differ
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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &lt;strong&gt; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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 &copy; 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
new file mode 100644
index 0000000..581d5ce
--- /dev/null
+++ b/framework/web/widgets/captcha/Duality.ttf
Binary files differ
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 &copy; 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 &copy; 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 &gt;'.
+ */
+ public $nextPageLabel;
+ /**
+ * @var string the text label for the previous page button. Defaults to '&lt; Previous'.
+ */
+ public $prevPageLabel;
+ /**
+ * @var string the text label for the first page button. Defaults to '&lt;&lt; First'.
+ */
+ public $firstPageLabel;
+ /**
+ * @var string the text label for the last page button. Defaults to 'Last &gt;&gt;'.
+ */
+ 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 &gt;');
+ if($this->prevPageLabel===null)
+ $this->prevPageLabel=Yii::t('yii','&lt; Previous');
+ if($this->firstPageLabel===null)
+ $this->firstPageLabel=Yii::t('yii','&lt;&lt; First');
+ if($this->lastPageLabel===null)
+ $this->lastPageLabel=Yii::t('yii','Last &gt;&gt;');
+ 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 &copy; 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 &copy; 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 &copy; 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