diff --git a/framework/YiiBase.php b/framework/YiiBase.php
index e4486dc..0069436 100644
--- a/framework/YiiBase.php
+++ b/framework/YiiBase.php
@@ -80,7 +80,7 @@ class YiiBase
*/
public static function getVersion()
{
- return '1.1.14';
+ return '1.1.16';
}
/**
@@ -299,7 +299,8 @@ public static function import($alias,$forceInclude=false)
if(($pos=strrpos($alias,'.'))===false) // a simple class name
{
- if($forceInclude && self::autoload($alias))
+ // try to autoload the class with an autoloader if $forceInclude is true
+ if($forceInclude && (Yii::autoload($alias,true) || class_exists($alias,true)))
self::$_imports[$alias]=$alias;
return $alias;
}
@@ -392,15 +393,19 @@ public static function setPathOfAlias($alias,$path)
* Class autoload loader.
* This method is provided to be invoked within an __autoload() magic method.
* @param string $className class name
+ * @param bool $classMapOnly whether to load classes via classmap only
* @return boolean whether the class has been loaded successfully
+ * @throws CException When class name does not match class file in debug mode.
*/
- public static function autoload($className)
+ public static function autoload($className,$classMapOnly=false)
{
// use include so that the error PHP file may appear
if(isset(self::$classMap[$className]))
include(self::$classMap[$className]);
elseif(isset(self::$_coreClasses[$className]))
include(YII_PATH.self::$_coreClasses[$className]);
+ elseif($classMapOnly)
+ return false;
else
{
// include class file relying on include_path
@@ -709,6 +714,9 @@ public static function registerAutoloader($callback, $append=false)
'CDbExpression' => '/db/schema/CDbExpression.php',
'CDbSchema' => '/db/schema/CDbSchema.php',
'CDbTableSchema' => '/db/schema/CDbTableSchema.php',
+ 'CCubridColumnSchema' => '/db/schema/cubrid/CCubridColumnSchema.php',
+ 'CCubridSchema' => '/db/schema/cubrid/CCubridSchema.php',
+ 'CCubridTableSchema' => '/db/schema/cubrid/CCubridTableSchema.php',
'CMssqlColumnSchema' => '/db/schema/mssql/CMssqlColumnSchema.php',
'CMssqlCommandBuilder' => '/db/schema/mssql/CMssqlCommandBuilder.php',
'CMssqlPdoAdapter' => '/db/schema/mssql/CMssqlPdoAdapter.php',
@@ -750,6 +758,7 @@ public static function registerAutoloader($callback, $append=false)
'CLogRouter' => '/logging/CLogRouter.php',
'CLogger' => '/logging/CLogger.php',
'CProfileLogRoute' => '/logging/CProfileLogRoute.php',
+ 'CSysLogRoute' => '/logging/CSysLogRoute.php',
'CWebLogRoute' => '/logging/CWebLogRoute.php',
'CDateTimeParser' => '/utils/CDateTimeParser.php',
'CFileHelper' => '/utils/CFileHelper.php',
diff --git a/framework/base/CApplication.php b/framework/base/CApplication.php
index 0219478..bb2e409 100644
--- a/framework/base/CApplication.php
+++ b/framework/base/CApplication.php
@@ -42,7 +42,7 @@
* CApplication will undergo the following lifecycles when processing a user request:
*
*
load application configuration;
- *
set up class autoloader and error handling;
+ *
set up error handling;
*
load static application components;
*
{@link onBeginRequest}: preprocess the user request;
*
{@link processRequest}: process the user request;
@@ -97,6 +97,10 @@ abstract class CApplication extends CModule
* the language that the messages and view files are in. Defaults to 'en_us' (US English).
*/
public $sourceLanguage='en_us';
+ /**
+ * @var string the class used to get locale data. Defaults to 'CLocale'.
+ */
+ public $localeClass='CLocale';
private $_id;
private $_basePath;
@@ -395,11 +399,11 @@ public function findLocalizedFile($srcFile,$srcLanguage=null,$language=null)
/**
* Returns the locale instance.
* @param string $localeID the locale ID (e.g. en_US). If null, the {@link getLanguage application language ID} will be used.
- * @return CLocale the locale instance
+ * @return an instance of CLocale
*/
public function getLocale($localeID=null)
{
- return CLocale::getInstance($localeID===null?$this->getLanguage():$localeID);
+ return call_user_func_array(array($this->localeClass, 'getInstance'),array($localeID===null?$this->getLanguage():$localeID));
}
/**
@@ -409,7 +413,10 @@ public function getLocale($localeID=null)
*/
public function getLocaleDataPath()
{
- return CLocale::$dataPath===null ? Yii::getPathOfAlias('system.i18n.data') : CLocale::$dataPath;
+ $vars=get_class_vars($this->localeClass);
+ if(empty($vars['dataPath']))
+ return Yii::getPathOfAlias('system.i18n.data');
+ return $vars['dataPath'];
}
/**
@@ -419,7 +426,8 @@ public function getLocaleDataPath()
*/
public function setLocaleDataPath($value)
{
- CLocale::$dataPath=$value;
+ $property=new ReflectionProperty($this->localeClass,'dataPath');
+ $property->setValue($value);
}
/**
@@ -937,7 +945,7 @@ public function displayException($exception)
}
/**
- * Initializes the class autoloader and error handlers.
+ * Initializes the error handlers.
*/
protected function initSystemHandlers()
{
diff --git a/framework/base/CComponent.php b/framework/base/CComponent.php
index 103be35..13d81f5 100644
--- a/framework/base/CComponent.php
+++ b/framework/base/CComponent.php
@@ -261,7 +261,7 @@ public function __call($name,$parameters)
return call_user_func_array(array($object,$name),$parameters);
}
}
- if(class_exists('Closure', false) && $this->canGetProperty($name) && $this->$name instanceof Closure)
+ if(class_exists('Closure', false) && ($this->canGetProperty($name) || property_exists($this, $name)) && $this->$name instanceof Closure)
return call_user_func_array($this->$name, $parameters);
throw new CException(Yii::t('yii','{class} and its behaviors do not have a method or closure named "{name}".',
array('{class}'=>get_class($this), '{name}'=>$name)));
@@ -499,7 +499,7 @@ public function getEventHandlers($name)
* $component->getEventHandlers($eventName)->add($eventHandler);
*
*
- * Using {@link getEventHandlers}, one can also specify the excution order
+ * Using {@link getEventHandlers}, one can also specify the execution order
* of multiple handlers attaching to the same event. For example:
*
* $component->getEventHandlers($eventName)->insertAt(0,$eventHandler);
diff --git a/framework/base/CErrorHandler.php b/framework/base/CErrorHandler.php
index 3ece1b2..f6b0ca5 100644
--- a/framework/base/CErrorHandler.php
+++ b/framework/base/CErrorHandler.php
@@ -48,6 +48,7 @@
* {@link CApplication::getErrorHandler()}.
*
* @property array $error The error details. Null if there is no error.
+ * @property Exception|null $exception exception instance. Null if there is no exception.
*
* @author Qiang Xue
* @package system.base
@@ -82,6 +83,7 @@ class CErrorHandler extends CApplicationComponent
public $errorAction;
private $_error;
+ private $_exception;
/**
* Handles the exception/error event.
@@ -150,6 +152,15 @@ public function getError()
return $this->_error;
}
+ /**
+ * Returns the instance of the exception that is currently being handled.
+ * @return Exception|null exception instance. Null if there is no exception.
+ */
+ public function getException()
+ {
+ return $this->_exception;
+ }
+
/**
* Handles the exception.
* @param Exception $exception the exception captured
@@ -186,6 +197,7 @@ protected function handleException($exception)
unset($trace[$i]['object']);
}
+ $this->_exception=$exception;
$this->_error=$data=array(
'code'=>($exception instanceof CHttpException)?$exception->statusCode:500,
'type'=>get_class($exception),
@@ -198,17 +210,12 @@ protected function handleException($exception)
);
if(!headers_sent())
- header("HTTP/1.0 {$data['code']} ".$this->getHttpHeader($data['code'], get_class($exception)));
-
- if($exception instanceof CHttpException || !YII_DEBUG)
- $this->render('error',$data);
- else
{
- if($this->isAjaxRequest())
- $app->displayException($exception);
- else
- $this->render('exception',$data);
+ $httpVersion=Yii::app()->request->getHttpVersion();
+ header("HTTP/$httpVersion {$data['code']} ".$this->getHttpHeader($data['code'], get_class($exception)));
}
+
+ $this->renderException();
}
else
$app->displayException($exception);
@@ -270,7 +277,8 @@ protected function handleError($event)
default:
$type = 'PHP error';
}
- $this->_error=$data=array(
+ $this->_exception=null;
+ $this->_error=array(
'code'=>500,
'type'=>$type,
'message'=>$event->message,
@@ -280,13 +288,12 @@ protected function handleError($event)
'traces'=>$trace,
);
if(!headers_sent())
- header("HTTP/1.0 500 Internal Server Error");
- if($this->isAjaxRequest())
- $app->displayError($event->code,$event->message,$event->file,$event->line);
- elseif(YII_DEBUG)
- $this->render('exception',$data);
- else
- $this->render('error',$data);
+ {
+ $httpVersion=Yii::app()->request->getHttpVersion();
+ header("HTTP/$httpVersion 500 Internal Server Error");
+ }
+
+ $this->renderError();
}
else
$app->displayError($event->code,$event->message,$event->file,$event->line);
@@ -327,15 +334,47 @@ protected function getExactTrace($exception)
*/
protected function render($view,$data)
{
- if($view==='error' && $this->errorAction!==null)
+ $data['version']=$this->getVersionInfo();
+ $data['time']=time();
+ $data['admin']=$this->adminInfo;
+ include($this->getViewFile($view,$data['code']));
+ }
+
+ /**
+ * Renders the exception information.
+ * This method will display information from current {@link error} value.
+ */
+ protected function renderException()
+ {
+ $exception=$this->getException();
+ if($exception instanceof CHttpException || !YII_DEBUG)
+ $this->renderError();
+ else
+ {
+ if($this->isAjaxRequest())
+ Yii::app()->displayException($exception);
+ else
+ $this->render('exception',$this->getError());
+ }
+ }
+
+ /**
+ * Renders the current error information.
+ * This method will display information from current {@link error} value.
+ */
+ protected function renderError()
+ {
+ if($this->errorAction!==null)
Yii::app()->runController($this->errorAction);
else
{
- // additional information to be passed to view
- $data['version']=$this->getVersionInfo();
- $data['time']=time();
- $data['admin']=$this->adminInfo;
- include($this->getViewFile($view,$data['code']));
+ $data=$this->getError();
+ if($this->isAjaxRequest())
+ Yii::app()->displayError($data['code'],$data['message'],$data['file'],$data['line']);
+ elseif(YII_DEBUG)
+ $this->render('exception',$data);
+ else
+ $this->render('error',$data);
}
}
diff --git a/framework/base/CModel.php b/framework/base/CModel.php
index 4eb2589..3bdef04 100644
--- a/framework/base/CModel.php
+++ b/framework/base/CModel.php
@@ -64,7 +64,7 @@ abstract public function attributeNames();
*
except: this specifies the scenarios when the validation rule should not be performed.
* Separate different scenarios with commas. Please see {@link scenario} for more details about this option.
*
additional parameters are used to initialize the corresponding validator properties.
- * Please refer to individal validator class API for possible properties.
+ * Please refer to individual validator class API for possible properties.
*
*
* The following are some examples:
diff --git a/framework/base/CModule.php b/framework/base/CModule.php
index 30b3fbe..b81b2bd 100644
--- a/framework/base/CModule.php
+++ b/framework/base/CModule.php
@@ -332,8 +332,13 @@ public function getModules()
* You may also enable or disable a module by specifying the 'enabled' option in the configuration.
*
* @param array $modules module configurations.
+ * @param boolean $merge whether to merge the new module configuration
+ * with the existing one. Defaults to true, meaning the previously registered
+ * module configuration with the same ID will be merged with the new configuration.
+ * If set to false, the existing configuration will be replaced completely.
+ * This parameter is available since 1.1.16.
*/
- public function setModules($modules)
+ public function setModules($modules,$merge=true)
{
foreach($modules as $id=>$module)
{
@@ -342,16 +347,18 @@ public function setModules($modules)
$id=$module;
$module=array();
}
- if(!isset($module['class']))
- {
- Yii::setPathOfAlias($id,$this->getModulePath().DIRECTORY_SEPARATOR.$id);
- $module['class']=$id.'.'.ucfirst($id).'Module';
- }
-
- if(isset($this->_moduleConfig[$id]))
+ if(isset($this->_moduleConfig[$id]) && $merge)
$this->_moduleConfig[$id]=CMap::mergeArray($this->_moduleConfig[$id],$module);
else
+ {
+ if(!isset($module['class']))
+ {
+ if (Yii::getPathOfAlias($id)===false)
+ Yii::setPathOfAlias($id,$this->getModulePath().DIRECTORY_SEPARATOR.$id);
+ $module['class']=$id.'.'.ucfirst($id).'Module';
+ }
$this->_moduleConfig[$id]=$module;
+ }
}
}
diff --git a/framework/base/CSecurityManager.php b/framework/base/CSecurityManager.php
index e4c4640..a285ceb 100644
--- a/framework/base/CSecurityManager.php
+++ b/framework/base/CSecurityManager.php
@@ -48,6 +48,20 @@ class CSecurityManager extends CApplicationComponent
const STATE_VALIDATION_KEY='Yii.CSecurityManager.validationkey';
const STATE_ENCRYPTION_KEY='Yii.CSecurityManager.encryptionkey';
+ /**
+ * @var array known minimum lengths per encryption algorithm
+ */
+ protected static $encryptionKeyMinimumLengths=array(
+ 'blowfish'=>4,
+ 'arcfour'=>5,
+ 'rc2'=>5,
+ );
+
+ /**
+ * @var boolean if encryption key should be validated
+ */
+ public $validateEncryptionKey=true;
+
/**
* @var string the name of the hashing algorithm to be used by {@link computeHMAC}.
* See {@link http://php.net/manual/en/function.hash-algos.php hash-algos} for the list of possible
@@ -62,12 +76,20 @@ class CSecurityManager extends CApplicationComponent
* This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}.
*
* This property can also be configured as an array. In this case, the array elements will be passed in order
- * as parameters to mcrypt_module_open. For example, array('rijndael-256', '', 'ofb', '').
+ * as parameters to mcrypt_module_open. For example, array('rijndael-128', '', 'ofb', '').
+ *
+ * Defaults to AES
+ *
+ * Note: MCRYPT_RIJNDAEL_192 and MCRYPT_RIJNDAEL_256 are *not* AES-192 and AES-256. The numbers of the MCRYPT_RIJNDAEL
+ * constants refer to the block size, whereas the numbers of the AES variants refer to the key length. AES is Rijndael
+ * with a block size of 128 bits and a key length of 128 bits, 192 bits or 256 bits. So to use AES in Mcrypt, you need
+ * MCRYPT_RIJNDAEL_128 and a key with 16 bytes (AES-128), 24 bytes (AES-192) or 32 bytes (AES-256). The other two
+ * Rijndael variants in Mcrypt should be avoided, because they're not standardized and have been analyzed much less
+ * than AES.
*
- * Defaults to 'des', meaning using DES crypt algorithm.
* @since 1.1.3
*/
- public $cryptAlgorithm='des';
+ public $cryptAlgorithm='rijndael-128';
private $_validationKey;
private $_encryptionKey;
@@ -158,10 +180,8 @@ public function getEncryptionKey()
*/
public function setEncryptionKey($value)
{
- if(!empty($value))
- $this->_encryptionKey=$value;
- else
- throw new CException(Yii::t('yii','CSecurityManager.encryptionKey cannot be empty.'));
+ $this->validateEncryptionKey($value);
+ $this->_encryptionKey=$value;
}
/**
@@ -191,12 +211,14 @@ public function setValidation($value)
* @param string $data data to be encrypted.
* @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
* @return string the encrypted data
- * @throws CException if PHP Mcrypt extension is not loaded
+ * @throws CException if PHP Mcrypt extension is not loaded or key is invalid
*/
public function encrypt($data,$key=null)
{
+ if($key===null)
+ $key=$this->getEncryptionKey();
+ $this->validateEncryptionKey($key);
$module=$this->openCryptModule();
- $key=$this->substr($key===null ? md5($this->getEncryptionKey()) : $key,0,mcrypt_enc_get_key_size($module));
srand();
$iv=mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
mcrypt_generic_init($module,$key,$iv);
@@ -211,12 +233,14 @@ public function encrypt($data,$key=null)
* @param string $data data to be decrypted.
* @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
* @return string the decrypted data
- * @throws CException if PHP Mcrypt extension is not loaded
+ * @throws CException if PHP Mcrypt extension is not loaded or key is invalid
*/
public function decrypt($data,$key=null)
{
+ if($key===null)
+ $key=$this->getEncryptionKey();
+ $this->validateEncryptionKey($key);
$module=$this->openCryptModule();
- $key=$this->substr($key===null ? md5($this->getEncryptionKey()) : $key,0,mcrypt_enc_get_key_size($module));
$ivSize=mcrypt_enc_get_iv_size($module);
$iv=$this->substr($data,0,$ivSize);
mcrypt_generic_init($module,$key,$iv);
@@ -271,12 +295,15 @@ public function hashData($data,$key=null)
*/
public function validateData($data,$key=null)
{
+ if (!is_string($data))
+ return false;
+
$len=$this->strlen($this->computeHMAC('test'));
if($this->strlen($data)>=$len)
{
$hmac=$this->substr($data,0,$len);
$data2=$this->substr($data,$len,$this->strlen($data));
- return $hmac===$this->computeHMAC($data2,$key)?$data2:false;
+ return $this->compareString($hmac,$this->computeHMAC($data2,$key))?$data2:false;
}
else
return false;
@@ -489,4 +516,97 @@ private function substr($string,$start,$length)
{
return $this->_mbstring ? mb_substr($string,$start,$length,'8bit') : substr($string,$start,$length);
}
+
+ /**
+ * Checks if a key is valid for {@link cryptAlgorithm}.
+ * @param string $key the key to check
+ * @return boolean the validation result
+ * @throws CException if the supported key lengths of the cipher are unknown
+ */
+ protected function validateEncryptionKey($key)
+ {
+ if(is_string($key))
+ {
+ $supportedKeyLengths=mcrypt_module_get_supported_key_sizes($this->cryptAlgorithm);
+
+ if($supportedKeyLengths)
+ {
+ if(!in_array($this->strlen($key),$supportedKeyLengths)) {
+ throw new CException(Yii::t('yii','Encryption key length can be {keyLengths}',array('{keyLengths}'=>implode(',',$supportedKeyLengths).'.')));
+ }
+ }
+ elseif(isset(self::$encryptionKeyMinimumLengths[$this->cryptAlgorithm]))
+ {
+ $minLength=self::$encryptionKeyMinimumLengths[$this->cryptAlgorithm];
+ $maxLength=mcrypt_module_get_algo_key_size($this->cryptAlgorithm);
+ if($this->strlen($key)<$minLength || $this->strlen($key)>$maxLength)
+ throw new CException(Yii::t('yii','Encryption key length must be between {minLength} and {maxLength}.',array('{minLength}'=>$minLength,'{maxLength}'=>$maxLength)));
+ }
+ else
+ throw new CException(Yii::t('yii','Failed to validate key. Supported key lengths of cipher not known.'));
+ }
+ else
+ throw new CException(Yii::t('yii','Encryption key should be a string.'));
+ }
+
+ /**
+ * Decrypts legacy ciphertext which was produced by the old, broken implementation of encrypt().
+ * @deprecated use only to convert data encrypted prior to 1.1.16
+ * @param string $data data to be decrypted.
+ * @param string $key the decryption key. This defaults to null, meaning the key should be loaded from persistent storage.
+ * @param string|array $cipher the algorithm to be used
+ * @return string the decrypted data
+ * @throws CException if PHP Mcrypt extension is not loaded
+ * @throws CException if the key is missing
+ */
+ public function legacyDecrypt($data,$key=null,$cipher='des')
+ {
+ if (!$key)
+ {
+ $key=Yii::app()->getGlobalState(self::STATE_ENCRYPTION_KEY);
+ if(!$key)
+ throw new CException(Yii::t('yii','No encryption key specified.'));
+ }
+
+ if(extension_loaded('mcrypt'))
+ {
+ if(is_array($cipher))
+ $module=@call_user_func_array('mcrypt_module_open',$cipher);
+ else
+ $module=@mcrypt_module_open($cipher,'', MCRYPT_MODE_CBC,'');
+
+ if($module===false)
+ throw new CException(Yii::t('yii','Failed to initialize the mcrypt module.'));
+ }
+ else
+ throw new CException(Yii::t('yii','CSecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.'));
+
+ $derivedKey=$this->substr(md5($key),0,mcrypt_enc_get_key_size($module));
+ $ivSize=mcrypt_enc_get_iv_size($module);
+ $iv=$this->substr($data,0,$ivSize);
+ mcrypt_generic_init($module,$derivedKey,$iv);
+ $decrypted=mdecrypt_generic($module,$this->substr($data,$ivSize,$this->strlen($data)));
+ mcrypt_generic_deinit($module);
+ mcrypt_module_close($module);
+ return rtrim($decrypted,"\0");
+ }
+
+ /**
+ * Performs string comparison using timing attack resistant approach.
+ * @see http://codereview.stackexchange.com/questions/13512
+ * @param string $expected string to compare.
+ * @param string $actual user-supplied string.
+ * @return boolean whether strings are equal.
+ */
+ public function compareString($expected,$actual)
+ {
+ $expected.="\0";
+ $actual.="\0";
+ $expectedLength=$this->strlen($expected);
+ $actualLength=$this->strlen($actual);
+ $diff=$expectedLength-$actualLength;
+ for($i=0;$i<$actualLength;$i++)
+ $diff|=(ord($actual[$i])^ord($expected[$i%$expectedLength]));
+ return $diff===0;
+ }
}
diff --git a/framework/base/interfaces.php b/framework/base/interfaces.php
index d879d07..052992c 100644
--- a/framework/base/interfaces.php
+++ b/framework/base/interfaces.php
@@ -350,7 +350,7 @@ public function checkAccess($itemName,$userId,$params=array());
* 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
+ * Authorization items form a hierarchy. Higher level items inherit 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).
diff --git a/framework/caching/CApcCache.php b/framework/caching/CApcCache.php
index ffc3235..0fbbb1f 100644
--- a/framework/caching/CApcCache.php
+++ b/framework/caching/CApcCache.php
@@ -103,6 +103,9 @@ protected function deleteValue($key)
*/
protected function flushValues()
{
+ if(extension_loaded('apcu'))
+ return apc_clear_cache();
+
return apc_clear_cache('user');
}
}
diff --git a/framework/caching/CFileCache.php b/framework/caching/CFileCache.php
index f8ed789..330e74d 100644
--- a/framework/caching/CFileCache.php
+++ b/framework/caching/CFileCache.php
@@ -30,10 +30,24 @@ class CFileCache extends CCache
* using 'protected/runtime/cache' as the directory.
*/
public $cachePath;
+ /**
+ * @var integer the permission to be set for directory to store cache files
+ * 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.16
+ */
+ public $cachePathMode=0777;
/**
* @var string cache file suffix. Defaults to '.bin'.
*/
public $cacheFileSuffix='.bin';
+ /**
+ * @var integer the permission to be set for new cache 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.16
+ */
+ public $cacheFileMode=0666;
/**
* @var integer the level of sub-directories to store cache files. Defaults to 0,
* meaning no sub-directories. If the system has huge number of cache files (e.g. 10K+),
@@ -64,7 +78,10 @@ public function init()
if($this->cachePath===null)
$this->cachePath=Yii::app()->getRuntimePath().DIRECTORY_SEPARATOR.'cache';
if(!is_dir($this->cachePath))
- mkdir($this->cachePath,0777,true);
+ {
+ mkdir($this->cachePath,$this->cachePathMode,true);
+ chmod($this->cachePath,$this->cachePathMode);
+ }
}
/**
@@ -142,10 +159,14 @@ protected function setValue($key,$value,$expire)
$cacheFile=$this->getCacheFile($key);
if($this->directoryLevel>0)
- @mkdir(dirname($cacheFile),0777,true);
+ {
+ $cacheDir=dirname($cacheFile);
+ @mkdir($cacheDir,$this->cachePathMode,true);
+ @chmod($cacheDir,$this->cachePathMode);
+ }
if(@file_put_contents($cacheFile,$this->embedExpiry ? $expire.$value : $value,LOCK_EX)!==false)
{
- @chmod($cacheFile,0777);
+ @chmod($cacheFile,$this->cacheFileMode);
return $this->embedExpiry ? true : @touch($cacheFile,$expire);
}
else
diff --git a/framework/caching/CRedisCache.php b/framework/caching/CRedisCache.php
index 844d850..56b347a 100644
--- a/framework/caching/CRedisCache.php
+++ b/framework/caching/CRedisCache.php
@@ -108,7 +108,7 @@ protected function connect()
*
* See {@link http://redis.io/topics/protocol redis protocol description}
* for details on the mentioned reply types.
- * @trows CException for commands that return {@link http://redis.io/topics/protocol#error-reply error reply}.
+ * @throws CException for commands that return {@link http://redis.io/topics/protocol#error-reply error reply}.
*/
public function executeCommand($name,$params=array())
{
diff --git a/framework/cli/commands/MessageCommand.php b/framework/cli/commands/MessageCommand.php
index d4d7be6..d1e0e2c 100644
--- a/framework/cli/commands/MessageCommand.php
+++ b/framework/cli/commands/MessageCommand.php
@@ -61,6 +61,9 @@ public function getHelp()
instead of being enclosed between a pair of '@@' marks.
- sort: sort messages by key when merging, regardless of their translation
state (new, obsolete, translated.)
+ - fileHeader: A boolean indicating whether the file should contain a default
+ comment that explains the message file or a string representing
+ some PHP code or comment to add before the return tag in the message file.
EOD;
}
@@ -98,6 +101,9 @@ public function run($args)
if(!isset($sort))
$sort = false;
+ if(!isset($fileHeader))
+ $fileHeader = true;
+
$options=array();
if(isset($fileTypes))
$options['fileTypes']=$fileTypes;
@@ -117,7 +123,7 @@ public function run($args)
foreach($messages as $category=>$msgs)
{
$msgs=array_values(array_unique($msgs));
- $this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort);
+ $this->generateMessageFile($msgs,$dir.DIRECTORY_SEPARATOR.$category.'.php',$overwrite,$removeOld,$sort,$fileHeader);
}
}
}
@@ -147,7 +153,7 @@ protected function extractMessages($fileName,$translator)
return $messages;
}
- protected function generateMessageFile($messages,$fileName,$overwrite,$removeOld,$sort)
+ protected function generateMessageFile($messages,$fileName,$overwrite,$removeOld,$sort,$fileHeader)
{
echo "Saving messages to $fileName...";
if(is_file($fileName))
@@ -201,8 +207,8 @@ protected function generateMessageFile($messages,$fileName,$overwrite,$removeOld
echo "saved.\n";
}
$array=str_replace("\r",'',var_export($merged,true));
- $content=<<usageError('Please specify which version, timestamp or datetime to migrate to.');
+
+ if((string)(int)$args[0]==$args[0])
+ return $this->migrateToTime($args[0]);
+ elseif(($time=strtotime($args[0]))!==false)
+ return $this->migrateToTime($time);
+ else
+ return $this->migrateToVersion($args[0]);
+ }
+
+ private function migrateToTime($time)
+ {
+ $data=$this->getDbConnection()->createCommand()
+ ->select('version,apply_time')
+ ->from($this->migrationTable)
+ ->where('apply_time<=:time',array(':time'=>$time))
+ ->order('apply_time DESC')
+ ->limit(1)
+ ->queryRow();
+
+ if($data===false)
+ {
+ echo "Error: Unable to find a version before ".date('Y-m-d H:i:s',$time).".\n";
+ return 1;
+ }
else
- $this->usageError('Please specify which version to migrate to.');
+ {
+ echo "Found version ".$data['version']." applied at ".date('Y-m-d H:i:s',$data['apply_time']).", it is before ".date('Y-m-d H:i:s',$time).".\n";
+ return $this->migrateToVersion(substr($data['version'],1,13));
+ }
+ }
+ private function migrateToVersion($version)
+ {
$originalVersion=$version;
if(preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/',$version,$matches))
$version='m'.$matches[1];
@@ -466,7 +496,7 @@ protected function createMigrationHistoryTable()
$db=$this->getDbConnection();
echo 'Creating migration history table "'.$this->migrationTable.'"...';
$db->createCommand()->createTable($this->migrationTable,array(
- 'version'=>'string NOT NULL PRIMARY KEY',
+ 'version'=>'varchar(180) NOT NULL PRIMARY KEY',
'apply_time'=>'integer',
));
$db->createCommand()->insert($this->migrationTable,array(
@@ -530,6 +560,16 @@ public function getHelp()
* yiic migrate to 101129_185401
Migrates up or down to version 101129_185401.
+ * yiic migrate to 1392447720
+ Migrates to the given UNIX timestamp. This means that all the versions
+ applied after the specified timestamp will be reverted. Versions applied
+ before won't be touched.
+
+ * yiic migrate to "2014-02-15 13:00:50"
+ Migrates to the given datetime parseable by the strtotime() function.
+ This means that all the versions applied after the specified datetime
+ will be reverted. Versions applied before won't be touched.
+
* yiic migrate mark 101129_185401
Modifies the migration history up or down to version 101129_185401.
No actual migration will be performed.
diff --git a/framework/cli/commands/ShellCommand.php b/framework/cli/commands/ShellCommand.php
index 3aed3f1..5855f26 100644
--- a/framework/cli/commands/ShellCommand.php
+++ b/framework/cli/commands/ShellCommand.php
@@ -104,11 +104,8 @@ protected function runShell()
// disable E_NOTICE so that the shell is more friendly
error_reporting(E_ALL ^ E_NOTICE);
- $_runner_=new CConsoleCommandRunner;
- $_runner_->addCommands(dirname(__FILE__).'/shell');
- $_runner_->addCommands(Yii::getPathOfAlias('application.commands.shell'));
- if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false)
- $_runner_->addCommands($_path_);
+ $_runner_=$this->createCommandRunner();
+ $this->addCommands($_runner_);
$_commands_=$_runner_->commands;
$log=Yii::app()->log;
@@ -139,6 +136,29 @@ protected function runShell()
}
}
}
+
+ /**
+ * Creates a commands runner
+ * @return CConsoleCommandRunner
+ * @since 1.1.16
+ */
+ protected function createCommandRunner()
+ {
+ return new CConsoleCommandRunner;
+ }
+
+ /**
+ * Adds commands to runner
+ * @param CConsoleCommandRunner $runner
+ * @since 1.1.16
+ */
+ protected function addCommands(CConsoleCommandRunner $runner)
+ {
+ $runner->addCommands(Yii::getPathOfAlias('system.cli.commands.shell'));
+ $runner->addCommands(Yii::getPathOfAlias('application.commands.shell'));
+ if(($_path_=@getenv('YIIC_SHELL_COMMAND_PATH'))!==false)
+ $runner->addCommands($_path_);
+ }
}
class ShellException extends CException
diff --git a/framework/cli/views/shell/crud/view.php b/framework/cli/views/shell/crud/view.php
index 692b328..fc32d93 100644
--- a/framework/cli/views/shell/crud/view.php
+++ b/framework/cli/views/shell/crud/view.php
@@ -21,7 +21,7 @@
array('label'=>'List ', 'url'=>array('index')),
array('label'=>'Create ', 'url'=>array('create')),
array('label'=>'Update ', 'url'=>array('update', 'id'=>$model->)),
- array('label'=>'Delete ', 'url'=>'#', 'linkOptions'=>array('submit'=>array('delete','id'=>$model->),'confirm'=>'Are you sure you want to delete this item?')),
+ array('label'=>'Delete ', 'url'=>'#', 'linkOptions'=>array('submit'=>array('delete','id'=>$model->),'confirm'=>Yii::t('zii','Are you sure you want to delete this item?'))),
array('label'=>'Manage ', 'url'=>array('admin')),
);
?>
diff --git a/framework/cli/views/webapp/protected/config/console.php b/framework/cli/views/webapp/protected/config/console.php
index 346a976..dd3b3bc 100644
--- a/framework/cli/views/webapp/protected/config/console.php
+++ b/framework/cli/views/webapp/protected/config/console.php
@@ -11,19 +11,10 @@
// application components
'components'=>array(
- 'db'=>array(
- 'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
- ),
- // uncomment the following to use a MySQL database
- /*
- 'db'=>array(
- 'connectionString' => 'mysql:host=localhost;dbname=testdrive',
- 'emulatePrepare' => true,
- 'username' => 'root',
- 'password' => '',
- 'charset' => 'utf8',
- ),
- */
+
+ // database settings are configured in database.php
+ 'db'=>require(dirname(__FILE__).'/database.php'),
+
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
@@ -33,5 +24,6 @@
),
),
),
+
),
-);
\ No newline at end of file
+);
diff --git a/framework/cli/views/webapp/protected/config/database.php b/framework/cli/views/webapp/protected/config/database.php
new file mode 100644
index 0000000..02f9aad
--- /dev/null
+++ b/framework/cli/views/webapp/protected/config/database.php
@@ -0,0 +1,14 @@
+ 'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
+ // uncomment the following lines to use a MySQL database
+ /*
+ 'connectionString' => 'mysql:host=localhost;dbname=testdrive',
+ 'emulatePrepare' => true,
+ 'username' => 'root',
+ 'password' => '',
+ 'charset' => 'utf8',
+ */
+);
\ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/config/main.php b/framework/cli/views/webapp/protected/config/main.php
index 6919c74..052c5ec 100644
--- a/framework/cli/views/webapp/protected/config/main.php
+++ b/framework/cli/views/webapp/protected/config/main.php
@@ -32,10 +32,12 @@
// application components
'components'=>array(
+
'user'=>array(
// enable cookie-based authentication
'allowAutoLogin'=>true,
),
+
// uncomment the following to enable URLs in path-format
/*
'urlManager'=>array(
@@ -47,23 +49,15 @@
),
),
*/
- 'db'=>array(
- 'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/testdrive.db',
- ),
- // uncomment the following to use a MySQL database
- /*
- 'db'=>array(
- 'connectionString' => 'mysql:host=localhost;dbname=testdrive',
- 'emulatePrepare' => true,
- 'username' => 'root',
- 'password' => '',
- 'charset' => 'utf8',
- ),
- */
+
+ // database settings are configured in database.php
+ 'db'=>require(dirname(__FILE__).'/database.php'),
+
'errorHandler'=>array(
// use 'site/error' action to display errors
'errorAction'=>'site/error',
),
+
'log'=>array(
'class'=>'CLogRouter',
'routes'=>array(
@@ -79,6 +73,7 @@
*/
),
),
+
),
// application-level parameters that can be accessed
@@ -87,4 +82,4 @@
// this is used in contact page
'adminEmail'=>'webmaster@example.com',
),
-);
\ No newline at end of file
+);
diff --git a/framework/cli/views/webapp/protected/runtime/git-gitignore b/framework/cli/views/webapp/protected/runtime/git-gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/framework/cli/views/webapp/protected/runtime/git-gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/framework/cli/views/webapp/protected/runtime/hg-hgkeep b/framework/cli/views/webapp/protected/runtime/hg-hgkeep
new file mode 100644
index 0000000..e69de29
diff --git a/framework/cli/views/webapp/protected/views/layouts/main.php b/framework/cli/views/webapp/protected/views/layouts/main.php
index daf905e..a8533ae 100644
--- a/framework/cli/views/webapp/protected/views/layouts/main.php
+++ b/framework/cli/views/webapp/protected/views/layouts/main.php
@@ -1,19 +1,19 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
pageTitle); ?>
diff --git a/framework/console/CConsoleApplication.php b/framework/console/CConsoleApplication.php
index 078696a..04700ee 100644
--- a/framework/console/CConsoleApplication.php
+++ b/framework/console/CConsoleApplication.php
@@ -75,7 +75,7 @@ class CConsoleApplication extends CApplication
protected function init()
{
parent::init();
- if(!isset($_SERVER['argv'])) // || strncasecmp(php_sapi_name(),'cli',3))
+ if(empty($_SERVER['argv']))
die('This script must be run from the command line.');
$this->_runner=$this->createCommandRunner();
$this->_runner->commands=$this->commandMap;
diff --git a/framework/console/CConsoleCommand.php b/framework/console/CConsoleCommand.php
index 34e2945..d29c4a0 100644
--- a/framework/console/CConsoleCommand.php
+++ b/framework/console/CConsoleCommand.php
@@ -362,8 +362,8 @@ public function copyFiles($fileList)
$overwriteAll=false;
foreach($fileList as $name=>$file)
{
- $source=strtr($file['source'],'/\\',DIRECTORY_SEPARATOR);
- $target=strtr($file['target'],'/\\',DIRECTORY_SEPARATOR);
+ $source=strtr($file['source'],'/\\',DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR);
+ $target=strtr($file['target'],'/\\',DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR);
$callback=isset($file['callback']) ? $file['callback'] : null;
$params=isset($file['params']) ? $file['params'] : null;
diff --git a/framework/db/CDbCommand.php b/framework/db/CDbCommand.php
index 04d2c19..3bd1d85 100644
--- a/framework/db/CDbCommand.php
+++ b/framework/db/CDbCommand.php
@@ -122,7 +122,7 @@ public function __sleep()
/**
* Set the default fetch mode for this statement
* @param mixed $mode fetch mode
- * @return CDbCommand
+ * @return static
* @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
* @since 1.1.7
*/
@@ -138,7 +138,7 @@ public function setFetchMode($mode)
* This method is mainly used when a command object is being reused
* multiple times for building different queries.
* Calling this method will clean up all internal states of the command object.
- * @return CDbCommand this command instance
+ * @return static this command instance
* @since 1.1.6
*/
public function reset()
@@ -165,7 +165,7 @@ public function getText()
* Specifies the SQL statement to be executed.
* Any previous execution will be terminated or cancel.
* @param string $value the SQL statement to be executed
- * @return CDbCommand this command instance
+ * @return static this command instance
*/
public function setText($value)
{
@@ -239,7 +239,7 @@ public function cancel()
* @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
* @param integer $length length of the data type
* @param mixed $driverOptions the driver-specific options (this is available since version 1.1.6)
- * @return CDbCommand the current command being executed
+ * @return static the current command being executed
* @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php
*/
public function bindParam($name, &$value, $dataType=null, $length=null, $driverOptions=null)
@@ -265,7 +265,7 @@ public function bindParam($name, &$value, $dataType=null, $length=null, $driverO
* placeholders, this will be the 1-indexed position of the parameter.
* @param mixed $value The value to bind to the parameter
* @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
- * @return CDbCommand the current command being executed
+ * @return static the current command being executed
* @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php
*/
public function bindValue($name, $value, $dataType=null)
@@ -286,7 +286,7 @@ public function bindValue($name, $value, $dataType=null)
* @param array $values the values to be bound. This must be given in terms of an associative
* array with array keys being the parameter names, and array values the corresponding parameter values.
* For example, array(':name'=>'John', ':age'=>25).
- * @return CDbCommand the current command being executed
+ * @return static the current command being executed
* @since 1.1.5
*/
public function bindValues($values)
@@ -489,7 +489,7 @@ private function queryInternal($method,$mode,$params=array())
&& ($cache=Yii::app()->getComponent($this->_connection->queryCacheID))!==null)
{
$this->_connection->queryCachingCount--;
- $cacheKey='yii:dbquery'.$this->_connection->connectionString.':'.$this->_connection->username;
+ $cacheKey='yii:dbquery'.':'.$method.':'.$this->_connection->connectionString.':'.$this->_connection->username;
$cacheKey.=':'.$this->getText().':'.serialize(array_merge($this->_paramLog,$params));
if(($result=$cache->get($cacheKey))!==false)
{
@@ -562,8 +562,6 @@ public function buildQuery($query)
if(!empty($query['from']))
$sql.="\nFROM ".$query['from'];
- else
- throw new CDbException(Yii::t('yii','The DB query must contain the "from" portion.'));
if(!empty($query['join']))
$sql.="\n".(is_array($query['join']) ? implode("\n",$query['join']) : $query['join']);
@@ -600,7 +598,7 @@ public function buildQuery($query)
* (which means the column contains a DB expression).
* @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
* in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. This parameter is supported since version 1.1.8.
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function select($columns='*', $option='')
@@ -692,7 +690,7 @@ public function setDistinct($value)
* Table names can contain schema prefixes (e.g. 'public.tbl_user') and/or table aliases (e.g. 'tbl_user u').
* The method will automatically quote the table names unless it contains some parenthesis
* (which means the table is given as a sub-query or DB expression).
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function from($tables)
@@ -774,7 +772,7 @@ public function setFrom($value)
*
* @param mixed $conditions the conditions that should be put in the WHERE part.
* @param array $params the parameters (name=>value) to be bound to the query
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function where($conditions, $params=array())
@@ -795,7 +793,7 @@ public function where($conditions, $params=array())
*
* @param mixed $conditions the conditions that should be appended to the WHERE part.
* @param array $params the parameters (name=>value) to be bound to the query.
- * @return CDbCommand the command object itself.
+ * @return static the command object itself.
* @since 1.1.13
*/
public function andWhere($conditions,$params=array())
@@ -819,7 +817,7 @@ public function andWhere($conditions,$params=array())
*
* @param mixed $conditions the conditions that should be appended to the WHERE part.
* @param array $params the parameters (name=>value) to be bound to the query.
- * @return CDbCommand the command object itself.
+ * @return static the command object itself.
* @since 1.1.13
*/
public function orWhere($conditions,$params=array())
@@ -875,7 +873,7 @@ public function join($table, $conditions, $params=array())
/**
* Returns the join part in the query.
* @return mixed the join part in the query. This can be an array representing
- * multiple join fragments, or a string representing a single jojin fragment.
+ * multiple join fragments, or a string representing a single join fragment.
* Each join fragment will contain the proper join operator (e.g. LEFT JOIN).
* @since 1.1.6
*/
@@ -960,13 +958,43 @@ public function naturalJoin($table)
return $this->joinInternal('natural join', $table);
}
+ /**
+ * Appends a NATURAL LEFT JOIN part to the query.
+ * Note that not all DBMS support NATURAL LEFT JOIN.
+ * @param string $table the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ * @return CDbCommand the command object itself
+ * @since 1.1.16
+ */
+ public function naturalLeftJoin($table)
+ {
+ return $this->joinInternal('natural left join', $table);
+ }
+
+ /**
+ * Appends a NATURAL RIGHT JOIN part to the query.
+ * Note that not all DBMS support NATURAL RIGHT JOIN.
+ * @param string $table the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ * @return CDbCommand the command object itself
+ * @since 1.1.16
+ */
+ public function naturalRightJoin($table)
+ {
+ return $this->joinInternal('natural right join', $table);
+ }
+
/**
* Sets the GROUP BY part of the query.
* @param mixed $columns the columns to be grouped by.
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
* The method will automatically quote the column names unless a column contains some parenthesis
* (which means the column contains a DB expression).
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function group($columns)
@@ -1015,7 +1043,7 @@ public function setGroup($value)
* @param mixed $conditions the conditions to be put after HAVING.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function having($conditions, $params=array())
@@ -1060,7 +1088,7 @@ public function setHaving($value)
* $criteria->order('(1)');
*
*
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function order($columns)
@@ -1113,7 +1141,7 @@ public function setOrder($value)
* Sets the LIMIT part of the query.
* @param integer $limit the limit
* @param integer $offset the offset
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function limit($limit, $offset=null)
@@ -1148,7 +1176,7 @@ public function setLimit($value)
/**
* Sets the OFFSET part of the query.
* @param integer $offset the offset
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function offset($offset)
@@ -1181,7 +1209,7 @@ public function setOffset($value)
/**
* Appends a SQL statement using UNION operator.
* @param string $sql the SQL statement to be appended using UNION
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
public function union($sql)
@@ -1422,9 +1450,9 @@ public function alterColumn($table, $column, $type)
* The method will properly quote the table and column names.
* @param string $name the name of the foreign key constraint.
* @param string $table the table that the foreign key constraint will be added to.
- * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
+ * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or pass as an array of column names.
* @param string $refTable the table that the foreign key references to.
- * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
+ * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or pass as an array of column names.
* @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @return integer number of rows affected by the execution.
@@ -1451,15 +1479,15 @@ public function dropForeignKey($name, $table)
* Builds and executes a SQL statement for creating a new index.
* @param string $name the name of the index. The name will be properly quoted by the method.
* @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
- * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them
- * by commas. The column names will be properly quoted by the method.
+ * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
+ * by commas or pass as an array of column names. Each column name will be properly quoted by the method, unless a parenthesis is found in the name.
* @param boolean $unique whether to add UNIQUE constraint on the created index.
* @return integer number of rows affected by the execution.
* @since 1.1.6
*/
- public function createIndex($name, $table, $column, $unique=false)
+ public function createIndex($name, $table, $columns, $unique=false)
{
- return $this->setText($this->getConnection()->getSchema()->createIndex($name, $table, $column, $unique))->execute();
+ return $this->setText($this->getConnection()->getSchema()->createIndex($name, $table, $columns, $unique))->execute();
}
/**
@@ -1556,7 +1584,7 @@ private function processConditions($conditions)
* @param mixed $conditions the join condition that should appear in the ON part.
* Please refer to {@link where} on how to specify conditions.
* @param array $params the parameters (name=>value) to be bound to the query
- * @return CDbCommand the command object itself
+ * @return static the command object itself
* @since 1.1.6
*/
private function joinInternal($type, $table, $conditions='', $params=array())
@@ -1587,7 +1615,8 @@ private function joinInternal($type, $table, $conditions='', $params=array())
* Builds a SQL statement for creating a primary key constraint.
* @param string $name the name of the primary key constraint to be created. The name will be properly quoted by the method.
* @param string $table the table who will be inheriting the primary key. The name will be properly quoted by the method.
- * @param string $columns the column/s where the primary key will be effected. The name will be properly quoted by the method.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ * Array value can be passed since 1.1.14.
* @return integer number of rows affected by the execution.
* @since 1.1.13
*/
diff --git a/framework/db/CDbConnection.php b/framework/db/CDbConnection.php
index 4390ca7..3fd88d8 100644
--- a/framework/db/CDbConnection.php
+++ b/framework/db/CDbConnection.php
@@ -78,6 +78,20 @@
* )
*
*
+ * Use the {@link driverName} property if you want to force the DB connection to use a particular driver
+ * by the given name, disregarding of what was set in the {@link connectionString} property. This might
+ * be useful when working with ODBC connections. Sample code:
+ *
+ *
+ *
* @property boolean $active Whether the DB connection is established.
* @property PDO $pdoInstance The PDO instance, null if the connection is not established yet.
* @property CDbTransaction $currentTransaction The currently active transaction. Null if no active transaction.
@@ -88,7 +102,8 @@
* @property mixed $nullConversion How the null and empty strings are converted.
* @property boolean $autoCommit Whether creating or updating a DB record will be automatically committed.
* @property boolean $persistent Whether the connection is persistent or not.
- * @property string $driverName Name of the DB driver.
+ * @property string $driverName Name of the DB driver. This property is read-write since 1.1.16.
+ * Before 1.1.15 it was read-only.
* @property string $clientVersion The version information of the DB driver.
* @property string $connectionStatus The status of the connection.
* @property boolean $prefetch Whether the connection performs data prefetching.
@@ -185,7 +200,7 @@ class CDbConnection extends CApplicationComponent
public $autoConnect=true;
/**
* @var string the charset used for database connection. The property is only used
- * for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset
+ * for MySQL, MariaDB and PostgreSQL databases. Defaults to null, meaning using default charset
* as specified by the database.
*
* Note that if you're using GBK or BIG5 then it's highly recommended to
@@ -233,9 +248,10 @@ class CDbConnection extends CApplicationComponent
* @since 1.1.6
*/
public $driverMap=array(
+ 'cubrid'=>'CCubridSchema', // CUBRID
'pgsql'=>'CPgsqlSchema', // PostgreSQL
'mysqli'=>'CMysqlSchema', // MySQL
- 'mysql'=>'CMysqlSchema', // MySQL
+ 'mysql'=>'CMysqlSchema', // MySQL,MariaDB
'sqlite'=>'CSqliteSchema', // sqlite 3
'sqlite2'=>'CSqliteSchema', // sqlite 2
'mssql'=>'CMssqlSchema', // Mssql driver on windows hosts
@@ -250,6 +266,7 @@ class CDbConnection extends CApplicationComponent
*/
public $pdoClass = 'PDO';
+ private $_driverName;
private $_attributes=array();
private $_active=false;
private $_pdo;
@@ -347,7 +364,7 @@ public function setActive($value)
* the query results into cache.
* @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1,
* meaning that the next SQL query will be cached.
- * @return CDbConnection the connection instance itself.
+ * @return static the connection instance itself.
* @since 1.1.7
*/
public function cache($duration, $dependency=null, $queryCount=1)
@@ -413,9 +430,8 @@ protected function close()
protected function createPdoInstance()
{
$pdoClass=$this->pdoClass;
- if(($pos=strpos($this->connectionString,':'))!==false)
+ if(($driver=$this->getDriverName())!==null)
{
- $driver=strtolower(substr($this->connectionString,0,$pos));
if($driver==='mssql' || $driver==='dblib')
$pdoClass='CMssqlPdoAdapter';
elseif($driver==='sqlsrv')
@@ -437,7 +453,7 @@ protected function createPdoInstance()
/**
* Initializes the open db connection.
* This method is invoked right after the db connection is established.
- * The default implementation is to set the charset for MySQL and PostgreSQL database connections.
+ * The default implementation is to set the charset for MySQL, MariaDB and PostgreSQL database connections.
* @param PDO $pdo the PDO instance
*/
protected function initConnection($pdo)
@@ -687,14 +703,29 @@ public function setPersistent($value)
}
/**
- * Returns the name of the DB driver
- * @return string name of the DB driver
+ * Returns the name of the DB driver.
+ * @return string name of the DB driver.
*/
public function getDriverName()
{
- if(($pos=strpos($this->connectionString, ':'))!==false)
- return strtolower(substr($this->connectionString, 0, $pos));
- // return $this->getAttribute(PDO::ATTR_DRIVER_NAME);
+ if($this->_driverName!==null)
+ return $this->_driverName;
+ elseif(($pos=strpos($this->connectionString,':'))!==false)
+ return $this->_driverName=strtolower(substr($this->connectionString,0,$pos));
+ //return $this->getAttribute(PDO::ATTR_DRIVER_NAME);
+ }
+
+ /**
+ * Changes the name of the DB driver. Overrides value extracted from the {@link connectionString},
+ * which is behavior by default.
+ * @param string $driverName to be set. Valid values are the keys from the {@link driverMap} property.
+ * @see getDriverName
+ * @see driverName
+ * @since 1.1.16
+ */
+ public function setDriverName($driverName)
+ {
+ $this->_driverName=strtolower($driverName);
}
/**
diff --git a/framework/db/CDbMigration.php b/framework/db/CDbMigration.php
index 9849060..05d6077 100644
--- a/framework/db/CDbMigration.php
+++ b/framework/db/CDbMigration.php
@@ -179,6 +179,23 @@ public function insert($table, $columns)
echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n";
}
+ /**
+ * Creates and executes an INSERT SQL statement with multiple data.
+ * The method will properly escape the column names, and bind the values to be inserted.
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $data an array of various column data (name=>value) to be inserted into the table.
+ * @since 1.1.16
+ */
+ public function insertMultiple($table, $data)
+ {
+ echo " > insert into $table ...";
+ $time=microtime(true);
+ $builder=$this->getDbConnection()->getSchema()->getCommandBuilder();
+ $command=$builder->createMultipleInsertCommand($table,$data);
+ $command->execute();
+ echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n";
+ }
+
/**
* Creates and executes an UPDATE SQL statement.
* The method will properly escape the column names and bind the values to be updated.
@@ -335,15 +352,16 @@ public function alterColumn($table, $column, $type)
* The method will properly quote the table and column names.
* @param string $name the name of the foreign key constraint.
* @param string $table the table that the foreign key constraint will be added to.
- * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
+ * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or pass as an array of column names.
* @param string $refTable the table that the foreign key references to.
- * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
+ * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or pass as an array of column names.
* @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
*/
public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete=null, $update=null)
{
- echo " > add foreign key $name: $table ($columns) references $refTable ($refColumns) ...";
+ echo " > add foreign key $name: $table (".(is_array($columns) ? implode(',', $columns) : $columns).
+ ") references $refTable (".(is_array($refColumns) ? implode(',', $refColumns) : $refColumns).") ...";
$time=microtime(true);
$this->getDbConnection()->createCommand()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);
echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n";
@@ -366,15 +384,15 @@ public function dropForeignKey($name, $table)
* Builds and executes a SQL statement for creating a new index.
* @param string $name the name of the index. The name will be properly quoted by the method.
* @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
- * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them
- * by commas. The column names will be properly quoted by the method.
+ * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
+ * by commas or pass as an array of column names. Each column name will be properly quoted by the method, unless a parenthesis is found in the name.
* @param boolean $unique whether to add UNIQUE constraint on the created index.
*/
- public function createIndex($name, $table, $column, $unique=false)
+ public function createIndex($name, $table, $columns, $unique=false)
{
- echo " > create".($unique ? ' unique':'')." index $name on $table ($column) ...";
+ echo " > create".($unique ? ' unique':'')." index $name on $table (".(is_array($columns) ? implode(',', $columns) : $columns).") ...";
$time=microtime(true);
- $this->getDbConnection()->createCommand()->createIndex($name, $table, $column, $unique);
+ $this->getDbConnection()->createCommand()->createIndex($name, $table, $columns, $unique);
echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n";
}
@@ -408,12 +426,13 @@ public function refreshTableSchema($table)
* Builds and executes a SQL statement for creating a primary key, supports composite primary keys.
* @param string $name name of the primary key constraint to add
* @param string $table name of the table to add primary key to
- * @param string $columns name of the column to utilise as primary key. If there are multiple columns, separate them with commas.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ * Array value can be passed since 1.1.14.
* @since 1.1.13
*/
public function addPrimaryKey($name,$table,$columns)
{
- echo " > alter table $table add constraint $name primary key ($columns) ...";
+ echo " > alter table $table add constraint $name primary key (".(is_array($columns) ? implode(',', $columns) : $columns).") ...";
$time=microtime(true);
$this->getDbConnection()->createCommand()->addPrimaryKey($name,$table,$columns);
echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n";
diff --git a/framework/db/ar/CActiveFinder.php b/framework/db/ar/CActiveFinder.php
index a93fd3b..58a3936 100644
--- a/framework/db/ar/CActiveFinder.php
+++ b/framework/db/ar/CActiveFinder.php
@@ -246,6 +246,9 @@ private function buildJoinTree($parent,$with,$options=null)
if(!empty($options['scopes']))
$scopes=array_merge($scopes,(array)$options['scopes']); // no need for complex merging
+ if(!empty($options['joinOptions']))
+ $relation->joinOptions=$options['joinOptions'];
+
$model->resetScope(false);
$criteria=$model->getDbCriteria();
$criteria->scopes=$scopes;
@@ -482,7 +485,6 @@ public function lazyFind($baseRecord)
$query=new CJoinQuery($child);
$query->selects=array($child->getColumnSelect($child->relation->select));
$query->conditions=array(
- $child->relation->condition,
$child->relation->on,
);
$query->groups[]=$child->relation->group;
@@ -537,6 +539,9 @@ private function applyLazyCondition($query,$record)
$parent=$this->_parent;
if($this->relation instanceof CManyManyRelation)
{
+ $query->conditions=array(
+ $this->relation->condition,
+ );
$joinTableName=$this->relation->getJunctionTableName();
if(($joinTable=$schema->getTable($joinTableName))===null)
throw new CDbException(Yii::t('yii','The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.',
@@ -742,7 +747,7 @@ public function count($criteria=null)
else
{
$select=is_array($criteria->select) ? implode(',',$criteria->select) : $criteria->select;
- if($select!=='*' && !strncasecmp($select,'count',5))
+ if($select!=='*' && preg_match('/^count\s*\(/',trim($select)))
$query->selects=array($select);
elseif(is_string($this->_table->primaryKey))
{
@@ -965,9 +970,9 @@ public function getColumnSelect($select='*')
$columns[]=$prefix.$schema->quoteColumnName($this->_table->primaryKey).' AS '.$schema->quoteColumnName($this->_pkAlias);
elseif(is_array($this->_pkAlias))
{
- foreach($this->_table->primaryKey as $name)
- if(!isset($selected[$name]))
- $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($this->_pkAlias[$name]);
+ foreach($this->_pkAlias as $name=>$alias)
+ if(!isset($selected[$alias]))
+ $columns[]=$prefix.$schema->quoteColumnName($name).' AS '.$schema->quoteColumnName($alias);
}
}
@@ -1113,7 +1118,12 @@ private function joinOneMany($fke,$fks,$pke,$parent)
}
if(!empty($this->relation->on))
$joins[]=$this->relation->on;
- return $this->relation->joinType . ' ' . $this->getTableNameWithAlias() . ' ON (' . implode(') AND (',$joins).')';
+
+ if(!empty($this->relation->joinOptions) && is_string($this->relation->joinOptions))
+ return $this->relation->joinType.' '.$this->getTableNameWithAlias().' '.$this->relation->joinOptions.
+ ' ON ('.implode(') AND (',$joins).')';
+ else
+ return $this->relation->joinType.' '.$this->getTableNameWithAlias().' ON ('.implode(') AND (',$joins).')';
}
/**
@@ -1181,8 +1191,20 @@ private function joinManyMany($joinTable,$fks,$parent)
if($parentCondition!==array() && $childCondition!==array())
{
$join=$this->relation->joinType.' '.$joinTable->rawName.' '.$joinAlias;
+
+ if(is_array($this->relation->joinOptions) && isset($this->relation->joinOptions[0]) &&
+ is_string($this->relation->joinOptions[0]))
+ $join.=' '.$this->relation->joinOptions[0];
+ elseif(!empty($this->relation->joinOptions) && is_string($this->relation->joinOptions))
+ $join.=' '.$this->relation->joinOptions;
+
$join.=' ON ('.implode(') AND (',$parentCondition).')';
$join.=' '.$this->relation->joinType.' '.$this->getTableNameWithAlias();
+
+ if(is_array($this->relation->joinOptions) && isset($this->relation->joinOptions[1]) &&
+ is_string($this->relation->joinOptions[1]))
+ $join.=' '.$this->relation->joinOptions[1];
+
$join.=' ON ('.implode(') AND (',$childCondition).')';
if(!empty($this->relation->on))
$join.=' AND ('.$this->relation->on.')';
@@ -1315,7 +1337,7 @@ public function join($element)
public function createCommand($builder)
{
$sql=($this->distinct ? 'SELECT DISTINCT ':'SELECT ') . implode(', ',$this->selects);
- $sql.=' FROM ' . implode(' ',$this->joins);
+ $sql.=' FROM ' . implode(' ',array_unique($this->joins));
$conditions=array();
foreach($this->conditions as $condition)
@@ -1501,9 +1523,10 @@ private function queryOneMany()
$record->addRelatedRecord($relation->name,isset($stats[$pk])?$stats[$pk]:$relation->defaultValue,false);
}
- /*
+ /**
* @param string $joinTableName jointablename
* @param string $keys keys
+ * @throws CDbException
*/
private function queryManyMany($joinTableName,$keys)
{
diff --git a/framework/db/ar/CActiveRecord.php b/framework/db/ar/CActiveRecord.php
index c40c5b8..fe9f125 100644
--- a/framework/db/ar/CActiveRecord.php
+++ b/framework/db/ar/CActiveRecord.php
@@ -105,7 +105,7 @@ public function init()
* the query results into cache.
* @param integer $queryCount number of SQL queries that need to be cached after calling this method. Defaults to 1,
* meaning that the next SQL query will be cached.
- * @return CActiveRecord the active record instance itself.
+ * @return static the active record instance itself.
* @since 1.1.7
*/
public function cache($duration, $dependency=null, $queryCount=1)
@@ -357,7 +357,7 @@ public function defaultScope()
* Resets all scopes and criterias applied.
*
* @param boolean $resetDefault including default scope. This parameter available since 1.1.12
- * @return CActiveRecord
+ * @return static the AR instance itself
* @since 1.1.2
*/
public function resetScope($resetDefault=true)
@@ -384,7 +384,7 @@ public function resetScope($resetDefault=true)
*
*
* @param string $className active record class name.
- * @return CActiveRecord active record model instance.
+ * @return static active record model instance.
*/
public static function model($className=__CLASS__)
{
@@ -435,7 +435,10 @@ public function refreshMetaData()
*/
public function tableName()
{
- return get_class($this);
+ $tableName = get_class($this);
+ if(($pos=strrpos($tableName,'\\')) !== false)
+ return substr($tableName,$pos+1);
+ return $tableName;
}
/**
@@ -1072,7 +1075,7 @@ public function insert($attributes=null)
{
Yii::trace(get_class($this).'.insert()','system.db.ar.CActiveRecord');
$builder=$this->getCommandBuilder();
- $table=$this->getMetaData()->tableSchema;
+ $table=$this->getTableSchema();
$command=$builder->createInsertCommand($table,$this->getAttributes($attributes));
if($command->execute())
{
@@ -1146,7 +1149,9 @@ public function update($attributes=null)
* or an attribute value indexed by its name. If the latter, the record's
* attribute will be changed accordingly before saving.
* @throws CDbException if the record is new
- * @return boolean whether the update is successful
+ * @return boolean whether the update is successful. Note that false is also returned if the saving
+ * was successfull but no attributes had changed and the database driver returns 0 for the number
+ * of updated records.
*/
public function saveAttributes($attributes)
{
@@ -1272,7 +1277,7 @@ public function equals($record)
*/
public function getPrimaryKey()
{
- $table=$this->getMetaData()->tableSchema;
+ $table=$this->getTableSchema();
if(is_string($table->primaryKey))
return $this->{$table->primaryKey};
elseif(is_array($table->primaryKey))
@@ -1296,7 +1301,7 @@ public function getPrimaryKey()
public function setPrimaryKey($value)
{
$this->_pk=$this->getPrimaryKey();
- $table=$this->getMetaData()->tableSchema;
+ $table=$this->getTableSchema();
if(is_string($table->primaryKey))
$this->{$table->primaryKey}=$value;
elseif(is_array($table->primaryKey))
@@ -1347,7 +1352,7 @@ protected function query($criteria,$all=false)
{
if(!$all)
$criteria->limit=1;
- $command=$this->getCommandBuilder()->createFindCommand($this->getTableSchema(),$criteria,$this->getTableAlias());
+ $command=$this->getCommandBuilder()->createFindCommand($this->getTableSchema(),$criteria);
return $all ? $this->populateRecords($command->queryAll(), true, $criteria->index) : $this->populateRecord($command->queryRow());
}
else
@@ -1447,7 +1452,7 @@ public function setTableAlias($alias)
* @param array $params parameters to be bound to an SQL statement.
* This is only used when the first parameter is a string (query condition).
* In other cases, please use {@link CDbCriteria::params} to set parameters.
- * @return CActiveRecord the record found. Null if no record is found.
+ * @return static the record found. Null if no record is found.
*/
public function find($condition='',$params=array())
{
@@ -1461,7 +1466,7 @@ public function find($condition='',$params=array())
* See {@link find()} for detailed explanation about $condition and $params.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
- * @return CActiveRecord[] list of active records satisfying the specified condition. An empty array is returned if none is found.
+ * @return static[] list of active records satisfying the specified condition. An empty array is returned if none is found.
*/
public function findAll($condition='',$params=array())
{
@@ -1476,7 +1481,7 @@ public function findAll($condition='',$params=array())
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
- * @return CActiveRecord the record found. Null if none is found.
+ * @return static the record found. Null if none is found.
*/
public function findByPk($pk,$condition='',$params=array())
{
@@ -1492,7 +1497,7 @@ public function findByPk($pk,$condition='',$params=array())
* @param mixed $pk primary key value(s). Use array for multiple primary keys. For composite key, each key value must be an array (column name=>column value).
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
- * @return CActiveRecord[] the records found. An empty array is returned if none is found.
+ * @return static[] the records found. An empty array is returned if none is found.
*/
public function findAllByPk($pk,$condition='',$params=array())
{
@@ -1509,7 +1514,7 @@ public function findAllByPk($pk,$condition='',$params=array())
* An attribute value can be an array which will be used to generate an IN condition.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
- * @return CActiveRecord the record found. Null if none is found.
+ * @return static the record found. Null if none is found.
*/
public function findByAttributes($attributes,$condition='',$params=array())
{
@@ -1526,7 +1531,7 @@ public function findByAttributes($attributes,$condition='',$params=array())
* An attribute value can be an array which will be used to generate an IN condition.
* @param mixed $condition query condition or criteria.
* @param array $params parameters to be bound to an SQL statement.
- * @return CActiveRecord[] the records found. An empty array is returned if none is found.
+ * @return static[] the records found. An empty array is returned if none is found.
*/
public function findAllByAttributes($attributes,$condition='',$params=array())
{
@@ -1540,7 +1545,7 @@ public function findAllByAttributes($attributes,$condition='',$params=array())
* Finds a single active record with the specified SQL statement.
* @param string $sql the SQL statement
* @param array $params parameters to be bound to the SQL statement
- * @return CActiveRecord the record found. Null if none is found.
+ * @return static the record found. Null if none is found.
*/
public function findBySql($sql,$params=array())
{
@@ -1563,7 +1568,7 @@ public function findBySql($sql,$params=array())
* Finds all active records using the specified SQL statement.
* @param string $sql the SQL statement
* @param array $params parameters to be bound to the SQL statement
- * @return CActiveRecord[] the records found. An empty array is returned if none is found.
+ * @return static[] the records found. An empty array is returned if none is found.
*/
public function findAllBySql($sql,$params=array())
{
@@ -1592,8 +1597,8 @@ public function findAllBySql($sql,$params=array())
public function count($condition='',$params=array())
{
Yii::trace(get_class($this).'.count()','system.db.ar.CActiveRecord');
- $builder=$this->getCommandBuilder();
$this->beforeCount();
+ $builder=$this->getCommandBuilder();
$criteria=$builder->createCriteria($condition,$params);
$this->applyScopes($criteria);
@@ -1700,7 +1705,7 @@ public function exists($condition='',$params=array())
* ))->findAll();
*
*
- * @return CActiveRecord the AR object itself.
+ * @return static the AR object itself.
*/
public function with()
{
@@ -1719,7 +1724,7 @@ public function with()
* Sets {@link CDbCriteria::together} property to be true.
* This is only used in relational AR query. Please refer to {@link CDbCriteria::together}
* for more details.
- * @return CActiveRecord the AR object itself
+ * @return static the AR object itself
* @since 1.1.4
*/
public function together()
@@ -1842,7 +1847,7 @@ public function deleteAllByAttributes($attributes,$condition='',$params=array())
* This method is internally used by the find methods.
* @param array $attributes attribute values (column name=>column value)
* @param boolean $callAfterFind whether to call {@link afterFind} after the record is populated.
- * @return CActiveRecord the newly created active record. The class of the object is the same as the model class.
+ * @return static the newly created active record. The class of the object is the same as the model class.
* Null is returned if the input data is false.
*/
public function populateRecord($attributes,$callAfterFind=true)
@@ -1877,7 +1882,7 @@ public function populateRecord($attributes,$callAfterFind=true)
* @param boolean $callAfterFind whether to call {@link afterFind} after each record is populated.
* @param string $index the name of the attribute whose value will be used as indexes of the query result array.
* If null, it means the array will be indexed by zero-based integers.
- * @return CActiveRecord[] list of active records.
+ * @return static[] list of active records.
*/
public function populateRecords($data,$callAfterFind=true,$index=null)
{
@@ -1903,7 +1908,7 @@ public function populateRecords($data,$callAfterFind=true,$index=null)
* For example, by creating a record based on the value of a column,
* you may implement the so-called single-table inheritance mapping.
* @param array $attributes list of attribute values for the active records.
- * @return CActiveRecord the active record
+ * @return static the active record
*/
protected function instantiate($attributes)
{
@@ -1971,6 +1976,14 @@ class CBaseActiveRelation extends CComponent
* @since 1.1.3
*/
public $join='';
+ /**
+ * @var string|array property for setting post-JOIN operations such as USE INDEX.
+ * String typed value can be used with JOINs for HAS_MANY and MANY_MANY relations, while array typed
+ * value designed to be used only with MANY_MANY relations. First array element will be used for junction
+ * table JOIN and second array element will be used for target table JOIN.
+ * @since 1.1.16
+ */
+ public $joinOptions='';
/**
* @var string HAVING clause. For {@link CActiveRelation} descendant classes, column names
* referenced in this property should be disambiguated with prefix 'relationName.'.
@@ -2082,6 +2095,16 @@ class CStatRelation extends CBaseActiveRelation
* receive a statistical query result. Defaults to 0.
*/
public $defaultValue=0;
+ /**
+ * @var mixed scopes to apply
+ * Can be set to the one of the following:
+ *
+ * @since 1.1.16
+ */
+ public $scopes;
/**
* Merges this relation with a criteria specified dynamically.
@@ -2361,9 +2384,10 @@ public function __construct($model)
if(($table=$model->getDbConnection()->getSchema()->getTable($tableName))===null)
throw new CDbException(Yii::t('yii','The table "{table}" for active record class "{class}" cannot be found in the database.',
array('{class}'=>$this->_modelClassName,'{table}'=>$tableName)));
- if($table->primaryKey===null)
+
+ if(($modelPk=$model->primaryKey())!==null || $table->primaryKey===null)
{
- $table->primaryKey=$model->primaryKey();
+ $table->primaryKey=$modelPk;
if(is_string($table->primaryKey) && isset($table->columns[$table->primaryKey]))
$table->columns[$table->primaryKey]->isPrimaryKey=true;
elseif(is_array($table->primaryKey))
diff --git a/framework/db/ar/CActiveRecordBehavior.php b/framework/db/ar/CActiveRecordBehavior.php
index 373d619..443d15b 100644
--- a/framework/db/ar/CActiveRecordBehavior.php
+++ b/framework/db/ar/CActiveRecordBehavior.php
@@ -54,7 +54,7 @@ protected function beforeSave($event)
* Responds to {@link CActiveRecord::onAfterSave} event.
* Override this method and make it public if you want to handle the corresponding event
* of the {@link CBehavior::owner owner}.
- * @param CModelEvent $event event parameter
+ * @param CEvent $event event parameter
*/
protected function afterSave($event)
{
diff --git a/framework/db/schema/CDbCommandBuilder.php b/framework/db/schema/CDbCommandBuilder.php
index 603b912..7f5afab 100644
--- a/framework/db/schema/CDbCommandBuilder.php
+++ b/framework/db/schema/CDbCommandBuilder.php
@@ -273,9 +273,12 @@ public function createMultipleInsertCommand($table,array $data)
* If a key is not a valid column name, the corresponding value will be ignored.
* @param array $templates templates for the SQL parts.
* @return CDbCommand multiple insert command
+ * @throws CDbException if $data is empty.
*/
protected function composeMultipleInsertCommand($table,array $data,array $templates=array())
{
+ if (empty($data))
+ throw new CDbException(Yii::t('yii','Can not generate multiple insert command with empty data set.'));
$templates=array_merge(
array(
'main'=>'INSERT INTO {{tableName}} ({{columnInsertNames}}) VALUES {{rowInsertValues}}',
@@ -288,7 +291,7 @@ protected function composeMultipleInsertCommand($table,array $data,array $templa
$templates
);
$this->ensureTable($table);
- $tableName=$this->getDbConnection()->quoteTableName($table->name);
+ $tableName=$table->rawName;
$params=array();
$columnInsertNames=array();
$rowInsertValues=array();
@@ -499,7 +502,7 @@ public function applyOrder($sql,$orderBy)
/**
* Alters the SQL to apply LIMIT and OFFSET.
- * Default implementation is applicable for PostgreSQL, MySQL and SQLite.
+ * Default implementation is applicable for PostgreSQL, MySQL, MariaDB and SQLite.
* @param string $sql SQL query string without LIMIT and OFFSET.
* @param integer $limit maximum number of rows, -1 to ignore limit.
* @param integer $offset row offset, -1 to ignore offset.
@@ -736,11 +739,11 @@ public function createSearchCondition($table,$columns,$keywords,$prefix=null,$ca
$condition=array();
foreach($keywords as $keyword)
{
- $keyword='%'.strtr($keyword,array('%'=>'\%', '_'=>'\_')).'%';
+ $keyword='%'.strtr($keyword,array('%'=>'\%', '_'=>'\_', '\\'=>'\\\\')).'%';
if($caseSensitive)
- $condition[]=$prefix.$column->rawName.' LIKE '.$this->_connection->quoteValue('%'.$keyword.'%');
+ $condition[]=$prefix.$column->rawName.' LIKE '.$this->_connection->quoteValue($keyword);
else
- $condition[]='LOWER('.$prefix.$column->rawName.') LIKE LOWER('.$this->_connection->quoteValue('%'.$keyword.'%').')';
+ $condition[]='LOWER('.$prefix.$column->rawName.') LIKE LOWER('.$this->_connection->quoteValue($keyword).')';
}
$conditions[]=implode(' AND ',$condition);
}
diff --git a/framework/db/schema/CDbCriteria.php b/framework/db/schema/CDbCriteria.php
index 95061c4..86fb1b6 100644
--- a/framework/db/schema/CDbCriteria.php
+++ b/framework/db/schema/CDbCriteria.php
@@ -213,7 +213,7 @@ public function __wakeup()
* After calling this method, the {@link condition} property will be modified.
* @param mixed $condition the new condition. It can be either a string or an array of strings.
* @param string $operator the operator to join different conditions. Defaults to 'AND'.
- * @return CDbCriteria the criteria object itself
+ * @return static the criteria object itself
*/
public function addCondition($condition,$operator='AND')
{
@@ -246,7 +246,7 @@ public function addCondition($condition,$operator='AND')
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
* @param string $like the LIKE operator. Defaults to 'LIKE'. You may also set this to be 'NOT LIKE'.
- * @return CDbCriteria the criteria object itself
+ * @return static the criteria object itself
*/
public function addSearchCondition($column,$keyword,$escape=true,$operator='AND',$like='LIKE')
{
@@ -269,7 +269,7 @@ public function addSearchCondition($column,$keyword,$escape=true,$operator='AND'
* @param array $values list of values that the column value should be in
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
- * @return CDbCriteria the criteria object itself
+ * @return static the criteria object itself
*/
public function addInCondition($column,$values,$operator='AND')
{
@@ -309,7 +309,7 @@ public function addInCondition($column,$values,$operator='AND')
* @param array $values list of values that the column value should not be in
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
- * @return CDbCriteria the criteria object itself
+ * @return static the criteria object itself
* @since 1.1.1
*/
public function addNotInCondition($column,$values,$operator='AND')
@@ -349,7 +349,7 @@ public function addNotInCondition($column,$values,$operator='AND')
* @param string $columnOperator the operator to concatenate multiple column matching condition. Defaults to 'AND'.
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
- * @return CDbCriteria the criteria object itself
+ * @return static the criteria object itself
*/
public function addColumnCondition($columns,$columnOperator='AND',$operator='AND')
{
@@ -408,7 +408,7 @@ public function addColumnCondition($columns,$columnOperator='AND',$operator='AND
* and _ (matches a single character) will be escaped, and the value will be surrounded with a %
* character on both ends. When this parameter is false, the value will be directly used for
* matching without any change.
- * @return CDbCriteria the criteria object itself
+ * @return static the criteria object itself
* @since 1.1.1
*/
public function compare($column, $value, $partialMatch=false, $operator='AND', $escape=true)
@@ -462,7 +462,7 @@ public function compare($column, $value, $partialMatch=false, $operator='AND', $
* @param string $valueEnd the ending value to end the between search.
* @param string $operator the operator used to concatenate the new condition with the existing one.
* Defaults to 'AND'.
- * @return CDbCriteria the criteria object itself
+ * @return static the criteria object itself
* @since 1.1.2
*/
public function addBetweenCondition($column,$valueStart,$valueEnd,$operator='AND')
@@ -520,7 +520,7 @@ public function mergeWith($criteria,$operator='AND')
if($this->params!==$criteria->params)
$this->params=array_merge($this->params,$criteria->params);
- if($criteria->limit>0)
+ if($criteria->limit>=0)
$this->limit=$criteria->limit;
if($criteria->offset>=0)
diff --git a/framework/db/schema/CDbSchema.php b/framework/db/schema/CDbSchema.php
index 8ea61ca..18d3f0e 100644
--- a/framework/db/schema/CDbSchema.php
+++ b/framework/db/schema/CDbSchema.php
@@ -316,10 +316,11 @@ protected function findTableNames($schema='')
* These abstract column types are supported (using MySQL as example to explain the corresponding
* physical types):
*
- *
pk: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
+ *
pk and bigpk: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY" or "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
*
string: string type, will be converted into "varchar(255)"
*
text: a long string type, will be converted into "text"
*
integer: integer type, will be converted into "int(11)"
+ *
bigint: integer type, will be converted into "bigint(20)"
*
boolean: boolean type, will be converted into "tinyint(1)"
*
float: float number type, will be converted into "float"
*
decimal: decimal number type, will be converted into "decimal"
@@ -367,7 +368,7 @@ public function getColumnType($type)
* @return string the SQL statement for creating a new DB table.
* @since 1.1.6
*/
- public function createTable($table, $columns, $options=null)
+ public function createTable($table,$columns,$options=null)
{
$cols=array();
foreach($columns as $name=>$type)
@@ -388,7 +389,7 @@ public function createTable($table, $columns, $options=null)
* @return string the SQL statement for renaming a DB table.
* @since 1.1.6
*/
- public function renameTable($table, $newName)
+ public function renameTable($table,$newName)
{
return 'RENAME TABLE ' . $this->quoteTableName($table) . ' TO ' . $this->quoteTableName($newName);
}
@@ -425,7 +426,7 @@ public function truncateTable($table)
* @return string the SQL statement for adding a new column.
* @since 1.1.6
*/
- public function addColumn($table, $column, $type)
+ public function addColumn($table,$column,$type)
{
return 'ALTER TABLE ' . $this->quoteTableName($table)
. ' ADD ' . $this->quoteColumnName($column) . ' '
@@ -439,7 +440,7 @@ public function addColumn($table, $column, $type)
* @return string the SQL statement for dropping a DB column.
* @since 1.1.6
*/
- public function dropColumn($table, $column)
+ public function dropColumn($table,$column)
{
return "ALTER TABLE ".$this->quoteTableName($table)
." DROP COLUMN ".$this->quoteColumnName($column);
@@ -453,7 +454,7 @@ public function dropColumn($table, $column)
* @return string the SQL statement for renaming a DB column.
* @since 1.1.6
*/
- public function renameColumn($table, $name, $newName)
+ public function renameColumn($table,$name,$newName)
{
return "ALTER TABLE ".$this->quoteTableName($table)
. " RENAME COLUMN ".$this->quoteColumnName($name)
@@ -470,7 +471,7 @@ public function renameColumn($table, $name, $newName)
* @return string the SQL statement for changing the definition of a column.
* @since 1.1.6
*/
- public function alterColumn($table, $column, $type)
+ public function alterColumn($table,$column,$type)
{
return 'ALTER TABLE ' . $this->quoteTableName($table) . ' CHANGE '
. $this->quoteColumnName($column) . ' '
@@ -483,27 +484,29 @@ public function alterColumn($table, $column, $type)
* The method will properly quote the table and column names.
* @param string $name the name of the foreign key constraint.
* @param string $table the table that the foreign key constraint will be added to.
- * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
+ * @param string|array $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or pass as an array of column names.
* @param string $refTable the table that the foreign key references to.
- * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
+ * @param string|array $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or pass as an array of column names.
* @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
* @return string the SQL statement for adding a foreign key constraint to an existing table.
* @since 1.1.6
*/
- public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete=null, $update=null)
+ public function addForeignKey($name,$table,$columns,$refTable,$refColumns,$delete=null,$update=null)
{
- $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY);
+ if(is_string($columns))
+ $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY);
foreach($columns as $i=>$col)
$columns[$i]=$this->quoteColumnName($col);
- $refColumns=preg_split('/\s*,\s*/',$refColumns,-1,PREG_SPLIT_NO_EMPTY);
+ if(is_string($refColumns))
+ $refColumns=preg_split('/\s*,\s*/',$refColumns,-1,PREG_SPLIT_NO_EMPTY);
foreach($refColumns as $i=>$col)
$refColumns[$i]=$this->quoteColumnName($col);
$sql='ALTER TABLE '.$this->quoteTableName($table)
.' ADD CONSTRAINT '.$this->quoteColumnName($name)
- .' FOREIGN KEY ('.implode(', ', $columns).')'
+ .' FOREIGN KEY ('.implode(', ',$columns).')'
.' REFERENCES '.$this->quoteTableName($refTable)
- .' ('.implode(', ', $refColumns).')';
+ .' ('.implode(', ',$refColumns).')';
if($delete!==null)
$sql.=' ON DELETE '.$delete;
if($update!==null)
@@ -518,7 +521,7 @@ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $
* @return string the SQL statement for dropping a foreign key constraint.
* @since 1.1.6
*/
- public function dropForeignKey($name, $table)
+ public function dropForeignKey($name,$table)
{
return 'ALTER TABLE '.$this->quoteTableName($table)
.' DROP CONSTRAINT '.$this->quoteColumnName($name);
@@ -528,16 +531,17 @@ public function dropForeignKey($name, $table)
* Builds a SQL statement for creating a new index.
* @param string $name the name of the index. The name will be properly quoted by the method.
* @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
- * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them
- * by commas. Each column name will be properly quoted by the method, unless a parenthesis is found in the name.
+ * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
+ * by commas or pass as an array of column names. Each column name will be properly quoted by the method, unless a parenthesis is found in the name.
* @param boolean $unique whether to add UNIQUE constraint on the created index.
* @return string the SQL statement for creating a new index.
* @since 1.1.6
*/
- public function createIndex($name, $table, $column, $unique=false)
+ public function createIndex($name,$table,$columns,$unique=false)
{
$cols=array();
- $columns=preg_split('/\s*,\s*/',$column,-1,PREG_SPLIT_NO_EMPTY);
+ if(is_string($columns))
+ $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY);
foreach($columns as $col)
{
if(strpos($col,'(')!==false)
@@ -557,7 +561,7 @@ public function createIndex($name, $table, $column, $unique=false)
* @return string the SQL statement for dropping an index.
* @since 1.1.6
*/
- public function dropIndex($name, $table)
+ public function dropIndex($name,$table)
{
return 'DROP INDEX '.$this->quoteTableName($name).' ON '.$this->quoteTableName($table);
}
@@ -579,7 +583,7 @@ public function addPrimaryKey($name,$table,$columns)
$columns[$i]=$this->quoteColumnName($col);
return 'ALTER TABLE ' . $this->quoteTableName($table) . ' ADD CONSTRAINT '
. $this->quoteColumnName($name) . ' PRIMARY KEY ('
- . implode(', ', $columns). ' )';
+ . implode(', ',$columns). ' )';
}
/**
diff --git a/framework/db/schema/cubrid/CCubridColumnSchema.php b/framework/db/schema/cubrid/CCubridColumnSchema.php
new file mode 100644
index 0000000..c0c5e40
--- /dev/null
+++ b/framework/db/schema/cubrid/CCubridColumnSchema.php
@@ -0,0 +1,50 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright 2008-2013 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CCubridColumnSchema class describes the column meta data of a CUBRID table.
+ *
+ * @author Esen Sagynov
+ * @package system.db.schema.cubrid
+ * @since 1.1.16
+ */
+class CCubridColumnSchema extends CDbColumnSchema
+{
+ /**
+ * Extracts the PHP type from DB type.
+ * @param string $dbType DB type
+ */
+ protected function extractType($dbType)
+ {
+ if(preg_match('/(FLO|REA|DOU|NUM|DEC)/',$dbType))
+ $this->type='double';
+ // The following "bool" and 'boolean" are for future compatibility.
+ // As of CUBRID 9.0, they are not supported.
+ elseif(strpos($dbType,'BOOL')!==false)
+ $this->type='boolean';
+ elseif(preg_match('/(INT|BIT|SMA|SHO|NUM)/',$dbType))
+ $this->type='integer';
+ else
+ $this->type='string';
+ }
+
+ /**
+ * Extracts the default value for the column.
+ * The value is typecasted to correct PHP type.
+ * @param mixed $defaultValue the default value obtained from metadata
+ */
+ protected function extractDefault($defaultValue)
+ {
+ if($this->dbType==='TIMESTAMP' && $defaultValue==='CURRENT_TIMESTAMP')
+ $this->defaultValue=null;
+ else
+ parent::extractDefault($defaultValue);
+ }
+}
diff --git a/framework/db/schema/cubrid/CCubridSchema.php b/framework/db/schema/cubrid/CCubridSchema.php
new file mode 100644
index 0000000..8b112ca
--- /dev/null
+++ b/framework/db/schema/cubrid/CCubridSchema.php
@@ -0,0 +1,244 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright 2008-2013 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CCubridSchema is the class for retrieving metadata information from a CUBRID database (version 8.4.0 and later).
+ *
+ * @author Esen Sagynov
+ * @package system.db.schema.cubrid
+ * @since 1.1.16
+ */
+class CCubridSchema extends CDbSchema
+{
+ public $columnTypes=array(
+ 'pk' => 'INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ // same as STRING or CHARACTER VARYING
+ 'string' => 'VARCHAR(255)',
+ 'text' => 'VARCHAR(65535)',
+ 'integer' => 'INTEGER',
+ 'float' => 'NUMERIC',
+ 'real' => 'NUMERIC',
+ 'decimal' => 'NUMERIC',
+ 'datetime' => 'DATETIME',
+ 'timestamp' => 'TIMESTAMP',
+ 'time' => 'TIME',
+ 'date' => 'DATE',
+ 'binary' => 'BIT VARYING',
+ 'bool' => 'SHORT',
+ 'boolean' => 'SHORT',
+ 'money' => 'NUMERIC(19,4)',
+ );
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name does not schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return '`'.$name.'`';
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name does not contain prefix.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return '`'.$name.'`';
+ }
+
+ /**
+ * Compares two table names.
+ * The table names can be either quoted or unquoted. This method
+ * will consider both cases.
+ * @param string $name1 table name 1
+ * @param string $name2 table name 2
+ * @return boolean whether the two table names refer to the same table.
+ */
+ public function compareTableNames($name1,$name2)
+ {
+ return parent::compareTableNames(strtolower($name1),strtolower($name2));
+ }
+
+ /**
+ * Resets the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param CDbTableSchema $table the table schema whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ */
+ public function resetSequence($table,$value=null)
+ {
+ if($table->sequenceName!==null)
+ {
+ if($value===null)
+ $value=$this->getDbConnection()->createCommand("SELECT MAX(`{$table->primaryKey}`) FROM {$table->rawName}")->queryScalar()+1;
+ else
+ $value=(int)$value;
+ $this->getDbConnection()->createCommand("ALTER TABLE {$table->rawName} AUTO_INCREMENT=$value")->execute();
+ }
+ }
+
+ /**
+ * Creates a table instance representing the metadata for the named table.
+ * @param string $name table name
+ * @return CCubridTableSchema driver dependent table metadata. Null if the table does not exist.
+ */
+ protected function loadTable($name)
+ {
+ $table=new CCubridTableSchema;
+ $this->resolveTableNames($table,$name);
+
+ if($this->findColumns($table))
+ {
+ $this->findPrimaryKeys($table);
+ $this->findConstraints($table);
+ return $table;
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Generates various kinds of table names.
+ * @param CCubridTableSchema $table the table instance
+ * @param string $name the unquoted table name
+ */
+ protected function resolveTableNames($table,$name)
+ {
+ $parts=explode('.',str_replace('`','',$name));
+ if(isset($parts[1]))
+ {
+ $table->schemaName=$parts[0];
+ $table->name=$parts[1];
+ $table->rawName=$this->quoteTableName($table->schemaName).'.'.$this->quoteTableName($table->name);
+ }
+ else
+ {
+ $table->name=$parts[0];
+ $table->rawName=$this->quoteTableName($table->name);
+ }
+ }
+
+ /**
+ * Collects the table column metadata.
+ * @param CCubridTableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ */
+ protected function findColumns($table)
+ {
+ // it may be good to use CUBRID PHP API to retrieve column info.
+ $sql='SHOW COLUMNS FROM '.$table->rawName;
+ try
+ {
+ $columns=$this->getDbConnection()->createCommand($sql)->queryAll();
+ }
+ catch(Exception $e)
+ {
+ return false;
+ }
+ foreach($columns as $column)
+ {
+ $c=$this->createColumn($column);
+ $table->columns[$c->name]=$c;
+ }
+ return true;
+ }
+
+ /**
+ * Creates a table column.
+ * @param array $column column metadata
+ * @return CDbColumnSchema normalized column metadata
+ */
+ protected function createColumn($column)
+ {
+ $c=new CCubridColumnSchema;
+ $c->name=$column['Field'];
+ $c->rawName=$this->quoteColumnName($c->name);
+ $c->allowNull=$column['Null']==='YES';
+ $c->isPrimaryKey=strpos($column['Key'],'PRI')!==false;
+ $c->isForeignKey=false;
+ $c->init($column['Type'],$column['Default']);
+ $c->autoIncrement=strpos(strtolower($column['Extra']),'auto_increment')!==false;
+
+ return $c;
+ }
+
+ /**
+ * @return float server version.
+ */
+ protected function getServerVersion()
+ {
+ $version=$this->getDbConnection()->getAttribute(PDO::ATTR_SERVER_VERSION);
+ $digits=array();
+ preg_match('/(\d+)\.(\d+)\.(\d+).(\d+)/', $version, $digits);
+ return floatval($digits[1].'.'.$digits[2].$digits[3].'.'.$digits[4]);
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param CCubridTableSchema $table the table metadata
+ */
+ protected function findConstraints($table)
+ {
+ $schemas=$this->getDbConnection()->getPdoInstance()->cubrid_schema(PDO::CUBRID_SCH_IMPORTED_KEYS,$table->name);
+
+ foreach($schemas as $schema)
+ {
+ $table->foreignKeys[$schema["FKCOLUMN_NAME"]]=array($schema["PKTABLE_NAME"],$schema["PKCOLUMN_NAME"]);
+ if(isset($table->columns[$schema["FKCOLUMN_NAME"]]))
+ $table->columns[$schema["FKCOLUMN_NAME"]]->isForeignKey=true;
+ }
+ }
+
+ /**
+ * Collects the primary key column details for the given table.
+ * @param CCubridTableSchema $table the table metadata
+ */
+ protected function findPrimaryKeys($table)
+ {
+ $pks=$this->getDbConnection()->getPdoInstance()->cubrid_schema(PDO::CUBRID_SCH_PRIMARY_KEY,$table->name);
+
+ foreach($pks as $pk)
+ {
+ $c = $table->columns[$pk['ATTR_NAME']];
+ $c->isPrimaryKey = true;
+
+ if($table->primaryKey===null)
+ $table->primaryKey=$c->name;
+ elseif(is_string($table->primaryKey))
+ $table->primaryKey=array($table->primaryKey,$c->name);
+ else
+ $table->primaryKey[]=$c->name;
+ if($c->autoIncrement)
+ $table->sequenceName='';
+ }
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * If not empty, the returned table names will be prefixed with the schema name.
+ * @return array all table names in the database.
+ */
+ protected function findTableNames($schema='')
+ {
+ // CUBRID does not allow to look into another database from within another connection.
+ // If necessary user has to establish a connection to that particular database and
+ // query to show all tables. For this reason if a user executes this funtion
+ // we will return all table names of the currently connected database.
+ return $this->getDbConnection()->createCommand('SHOW TABLES')->queryColumn();
+ }
+}
diff --git a/framework/db/schema/cubrid/CCubridTableSchema.php b/framework/db/schema/cubrid/CCubridTableSchema.php
new file mode 100644
index 0000000..4e83431
--- /dev/null
+++ b/framework/db/schema/cubrid/CCubridTableSchema.php
@@ -0,0 +1,25 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright 2008-2013 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CCubridTableSchema represents the metadata for a CUBRID database table.
+ *
+ * @author Esen Sagynov
+ * @package system.db.schema.cubrid
+ * @since 1.1.16
+ */
+class CCubridTableSchema extends CDbTableSchema
+{
+ /**
+ * @var string name of the schema (database) that this table belongs to.
+ * Defaults to null, meaning no schema (or the current database).
+ */
+ public $schemaName;
+}
diff --git a/framework/db/schema/mssql/CMssqlCommandBuilder.php b/framework/db/schema/mssql/CMssqlCommandBuilder.php
index 56aedfd..aef2fd4 100644
--- a/framework/db/schema/mssql/CMssqlCommandBuilder.php
+++ b/framework/db/schema/mssql/CMssqlCommandBuilder.php
@@ -203,9 +203,9 @@ protected function rewriteLimitOffsetSql($sql, $limit, $offset)
$fetch = $limit+$offset;
$sql = preg_replace('/^([\s(])*SELECT( DISTINCT)?(?!\s*TOP\s*\()/i',"\\1SELECT\\2 TOP $fetch", $sql);
$ordering = $this->findOrdering($sql);
- $orginalOrdering = $this->joinOrdering($ordering, '[__outer__]');
+ $originalOrdering = $this->joinOrdering($ordering, '[__outer__]');
$reverseOrdering = $this->joinOrdering($this->reverseDirection($ordering), '[__inner__]');
- $sql = "SELECT * FROM (SELECT TOP {$limit} * FROM ($sql) as [__inner__] {$reverseOrdering}) as [__outer__] {$orginalOrdering}";
+ $sql = "SELECT * FROM (SELECT TOP {$limit} * FROM ($sql) as [__inner__] {$reverseOrdering}) as [__outer__] {$originalOrdering}";
return $sql;
}
diff --git a/framework/db/schema/mssql/CMssqlSchema.php b/framework/db/schema/mssql/CMssqlSchema.php
index 32f87e9..da2983f 100644
--- a/framework/db/schema/mssql/CMssqlSchema.php
+++ b/framework/db/schema/mssql/CMssqlSchema.php
@@ -26,9 +26,11 @@ class CMssqlSchema extends CDbSchema
*/
public $columnTypes=array(
'pk' => 'int IDENTITY PRIMARY KEY',
+ 'bigpk' => 'bigint IDENTITY PRIMARY KEY',
'string' => 'varchar(255)',
'text' => 'text',
'integer' => 'int',
+ 'bigint' => 'bigint',
'float' => 'float',
'decimal' => 'decimal',
'datetime' => 'datetime',
diff --git a/framework/db/schema/mysql/CMysqlCommandBuilder.php b/framework/db/schema/mysql/CMysqlCommandBuilder.php
index 539a648..5254075 100644
--- a/framework/db/schema/mysql/CMysqlCommandBuilder.php
+++ b/framework/db/schema/mysql/CMysqlCommandBuilder.php
@@ -40,4 +40,19 @@ public function applyJoin($sql,$join)
else
return $sql.' '.$join;
}
+
+ /**
+ * Alters the SQL to apply LIMIT and OFFSET.
+ * @param string $sql SQL query string without LIMIT and OFFSET.
+ * @param integer $limit maximum number of rows, -1 to ignore limit.
+ * @param integer $offset row offset, -1 to ignore offset.
+ * @return string SQL with LIMIT and OFFSET
+ */
+ public function applyLimit($sql,$limit,$offset)
+ {
+ // Ugly, but this is how MySQL recommends doing it: https://dev.mysql.com/doc/refman/5.0/en/select.html
+ if($limit<=0 && $offset>0)
+ $limit=PHP_INT_MAX;
+ return parent::applyLimit($sql,$limit,$offset);
+ }
}
diff --git a/framework/db/schema/mysql/CMysqlSchema.php b/framework/db/schema/mysql/CMysqlSchema.php
index eb1049d..3d4150e 100644
--- a/framework/db/schema/mysql/CMysqlSchema.php
+++ b/framework/db/schema/mysql/CMysqlSchema.php
@@ -23,9 +23,11 @@ class CMysqlSchema extends CDbSchema
*/
public $columnTypes=array(
'pk' => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ 'bigpk' => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',
'string' => 'varchar(255)',
'text' => 'text',
'integer' => 'int(11)',
+ 'bigint' => 'bigint(20)',
'float' => 'float',
'decimal' => 'decimal',
'datetime' => 'datetime',
diff --git a/framework/db/schema/oci/COciCommandBuilder.php b/framework/db/schema/oci/COciCommandBuilder.php
index 89013a7..0814431 100644
--- a/framework/db/schema/oci/COciCommandBuilder.php
+++ b/framework/db/schema/oci/COciCommandBuilder.php
@@ -33,7 +33,6 @@ public function getLastInsertID($table)
/**
* Alters the SQL to apply LIMIT and OFFSET.
- * Default implementation is applicable for PostgreSQL, MySQL and SQLite.
* @param string $sql SQL query string without LIMIT and OFFSET.
* @param integer $limit maximum number of rows, -1 to ignore limit.
* @param integer $offset row offset, -1 to ignore offset.
diff --git a/framework/db/schema/oci/COciSchema.php b/framework/db/schema/oci/COciSchema.php
index 353bbcc..7f3bb5c 100644
--- a/framework/db/schema/oci/COciSchema.php
+++ b/framework/db/schema/oci/COciSchema.php
@@ -26,9 +26,11 @@ class COciSchema extends CDbSchema
*/
public $columnTypes=array(
'pk' => 'NUMBER(10) NOT NULL PRIMARY KEY',
+ 'bigpk' => 'NUMBER(20) NOT NULL PRIMARY KEY',
'string' => 'VARCHAR2(255)',
'text' => 'CLOB',
'integer' => 'NUMBER(10)',
+ 'bigint' => 'NUMBER(20)',
'float' => 'NUMBER',
'decimal' => 'NUMBER',
'datetime' => 'TIMESTAMP',
@@ -184,7 +186,7 @@ protected function findColumns($table)
com.comments as column_comment
FROM ALL_TAB_COLUMNS A
inner join ALL_OBJECTS B ON b.owner = a.owner and ltrim(B.OBJECT_NAME) = ltrim(A.TABLE_NAME)
-LEFT JOIN user_col_comments com ON (A.table_name = com.table_name AND A.column_name = com.column_name)
+LEFT JOIN all_col_comments com ON (A.owner = com.owner AND A.table_name = com.table_name AND A.column_name = com.column_name)
WHERE
a.owner = '{$schemaName}'
and (b.object_type = 'TABLE' or b.object_type = 'VIEW')
diff --git a/framework/db/schema/pgsql/CPgsqlColumnSchema.php b/framework/db/schema/pgsql/CPgsqlColumnSchema.php
index 50bd728..a9ac8a2 100644
--- a/framework/db/schema/pgsql/CPgsqlColumnSchema.php
+++ b/framework/db/schema/pgsql/CPgsqlColumnSchema.php
@@ -35,6 +35,28 @@ protected function extractType($dbType)
$this->type='string';
}
+ /**
+ * Extracts size, precision and scale information from column's DB type.
+ * @param string $dbType the column's DB type
+ */
+ protected function extractLimit($dbType)
+ {
+ if(strpos($dbType,'('))
+ {
+ if (preg_match('/^time.*\((.*)\)/',$dbType,$matches))
+ {
+ $this->precision=(int)$matches[1];
+ }
+ elseif (preg_match('/\((.*)\)/',$dbType,$matches))
+ {
+ $values=explode(',',$matches[1]);
+ $this->size=$this->precision=(int)$values[0];
+ if(isset($values[1]))
+ $this->scale=(int)$values[1];
+ }
+ }
+ }
+
/**
* Extracts the default value for the column.
* The value is typecasted to correct PHP type.
@@ -50,8 +72,8 @@ protected function extractDefault($defaultValue)
$this->defaultValue=null;
elseif(preg_match('/^\'(.*)\'::/',$defaultValue,$matches))
$this->defaultValue=$this->typecast(str_replace("''","'",$matches[1]));
- elseif(preg_match('/^-?\d+(\.\d*)?$/',$defaultValue,$matches))
- $this->defaultValue=$this->typecast($defaultValue);
+ elseif(preg_match('/^(-?\d+(\.\d*)?)(::.*)?$/',$defaultValue,$matches))
+ $this->defaultValue=$this->typecast($matches[1]);
// else is null
}
}
diff --git a/framework/db/schema/pgsql/CPgsqlSchema.php b/framework/db/schema/pgsql/CPgsqlSchema.php
index f77373d..0c874e6 100644
--- a/framework/db/schema/pgsql/CPgsqlSchema.php
+++ b/framework/db/schema/pgsql/CPgsqlSchema.php
@@ -25,9 +25,11 @@ class CPgsqlSchema extends CDbSchema
*/
public $columnTypes=array(
'pk' => 'serial NOT NULL PRIMARY KEY',
+ 'bigpk' => 'bigserial NOT NULL PRIMARY KEY',
'string' => 'character varying (255)',
'text' => 'text',
'integer' => 'integer',
+ 'bigint' => 'bigint',
'float' => 'double precision',
'decimal' => 'numeric',
'datetime' => 'timestamp',
@@ -391,7 +393,7 @@ public function addColumn($table, $column, $type)
$type=$this->getColumnType($type);
$sql='ALTER TABLE ' . $this->quoteTableName($table)
. ' ADD COLUMN ' . $this->quoteColumnName($column) . ' '
- . $this->getColumnType($type);
+ . $type;
return $sql;
}
@@ -413,6 +415,42 @@ public function alterColumn($table, $column, $type)
return $sql;
}
+ /**
+ * Builds a SQL statement for creating a new index.
+ * @param string $name the name of the index. The name will be properly quoted by the method.
+ * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
+ * @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
+ * by commas. Each column name will be properly quoted by the method, unless a parenthesis is found in the name.
+ * @param boolean $unique whether to add UNIQUE constraint on the created index.
+ * @return string the SQL statement for creating a new index.
+ * @since 1.1.6
+ */
+ public function createIndex($name, $table, $columns, $unique=false)
+ {
+ $cols=array();
+ if (is_string($columns))
+ $columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY);
+ foreach($columns as $col)
+ {
+ if(strpos($col,'(')!==false)
+ $cols[]=$col;
+ else
+ $cols[]=$this->quoteColumnName($col);
+ }
+ if ($unique)
+ {
+ return 'ALTER TABLE ONLY '
+ . $this->quoteTableName($table).' ADD CONSTRAINT '
+ . $this->quoteTableName($name).' UNIQUE ('.implode(', ',$cols).')';
+ }
+ else
+ {
+ return 'CREATE INDEX '
+ . $this->quoteTableName($name).' ON '
+ . $this->quoteTableName($table).' ('.implode(', ',$cols).')';
+ }
+ }
+
/**
* Builds a SQL statement for dropping an index.
* @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
diff --git a/framework/db/schema/sqlite/CSqliteSchema.php b/framework/db/schema/sqlite/CSqliteSchema.php
index a2d263a..0308a6f 100644
--- a/framework/db/schema/sqlite/CSqliteSchema.php
+++ b/framework/db/schema/sqlite/CSqliteSchema.php
@@ -23,9 +23,11 @@ class CSqliteSchema extends CDbSchema
*/
public $columnTypes=array(
'pk' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
+ 'bigpk' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
'string' => 'varchar(255)',
'text' => 'text',
'integer' => 'integer',
+ 'bigint' => 'integer',
'float' => 'float',
'decimal' => 'decimal',
'datetime' => 'datetime',
diff --git a/framework/gii/CCodeGenerator.php b/framework/gii/CCodeGenerator.php
index 182ce37..d011b4a 100644
--- a/framework/gii/CCodeGenerator.php
+++ b/framework/gii/CCodeGenerator.php
@@ -103,7 +103,7 @@ public function actionDiff()
if(isset($_GET['id']) && isset($model->files[$_GET['id']]))
{
$file=$model->files[$_GET['id']];
- if(!in_array($file->type,array('php', 'txt','js','css')))
+ if(!in_array($file->type,array('php', 'txt','js','css','sql')))
$diff=false;
elseif($file->operation===CCodeFile::OP_OVERWRITE)
$diff=TextDiff::compare(file_get_contents($file->path), $file->content);
diff --git a/framework/gii/assets/js/tooltip.js b/framework/gii/assets/js/tooltip.js
index 13c7ba1..de1c90d 100644
--- a/framework/gii/assets/js/tooltip.js
+++ b/framework/gii/assets/js/tooltip.js
@@ -4,6 +4,7 @@
* https://github.com/jquerytools/jquerytools/commit/4f3f3f14e83b0ff276a795e9f45400930904adff#src/tooltip/tooltip.js
* 2. Original `$.fn.tooltip` has been changed to `$.fn.tooltip2` to prevent conflict between jQuery UI Tooltip and
* jQuery Tools Tooltip.
+ * 3. Fixed compatibility for jQuery 1.9+ (.browser is deprecated)
*
* @license
* jQuery Tools @VERSION Tooltip - UI essentials
@@ -75,7 +76,7 @@
fade: [
function(done) {
var conf = this.getConf();
- if (!$.browser.msie || conf.fadeIE) {
+ if (navigator.userAgent.match(/MSIE/i) === null || conf.fadeIE) {
this.getTip().fadeTo(conf.fadeInSpeed, conf.opacity, done);
}
else {
@@ -85,7 +86,7 @@
},
function(done) {
var conf = this.getConf();
- if (!$.browser.msie || conf.fadeIE) {
+ if (navigator.userAgent.match(/MSIE/i) === null || conf.fadeIE) {
this.getTip().fadeOut(conf.fadeOutSpeed, done);
}
else {
diff --git a/framework/gii/components/Pear/Text/Diff.php b/framework/gii/components/Pear/Text/Diff.php
index 205b8a7..4b30968 100644
--- a/framework/gii/components/Pear/Text/Diff.php
+++ b/framework/gii/components/Pear/Text/Diff.php
@@ -294,7 +294,7 @@ class Text_MappedDiff extends Text_Diff {
/**
* Computes a diff between sequences of strings.
*
- * This can be used to compute things like case-insensitve diffs, or diffs
+ * This can be used to compute things like case-insensitive diffs, or diffs
* which ignore changes in white-space.
*
* @param array $from_lines An array of strings.
diff --git a/framework/gii/components/Pear/Text/Diff/Mapped.php b/framework/gii/components/Pear/Text/Diff/Mapped.php
index 8403759..7f67184 100644
--- a/framework/gii/components/Pear/Text/Diff/Mapped.php
+++ b/framework/gii/components/Pear/Text/Diff/Mapped.php
@@ -15,7 +15,7 @@ class Text_Diff_Mapped extends Text_Diff {
/**
* Computes a diff between sequences of strings.
*
- * This can be used to compute things like case-insensitve diffs, or diffs
+ * This can be used to compute things like case-insensitive diffs, or diffs
* which ignore changes in white-space.
*
* @param array $from_lines An array of strings.
diff --git a/framework/gii/generators/controller/ControllerCode.php b/framework/gii/generators/controller/ControllerCode.php
index cb67eee..712a97c 100644
--- a/framework/gii/generators/controller/ControllerCode.php
+++ b/framework/gii/generators/controller/ControllerCode.php
@@ -13,7 +13,7 @@ public function rules()
array('controller, baseClass', 'required'),
array('controller', 'match', 'pattern'=>'/^\w+[\w+\\/]*$/', 'message'=>'{attribute} should only contain word characters and slashes.'),
array('actions', 'match', 'pattern'=>'/^\w+[\w\s,]*$/', 'message'=>'{attribute} should only contain word characters, spaces and commas.'),
- array('baseClass', 'match', 'pattern'=>'/^[a-zA-Z_][\w\\\\]*$/', 'message'=>'{attribute} should only contain word characters and backslashes.'),
+ array('baseClass', 'match', 'pattern'=>'/^[a-zA-Z_\\\\][\w\\\\]*$/', 'message'=>'{attribute} should only contain word characters and backslashes.'),
array('baseClass', 'validateReservedWord', 'skipOnError'=>true),
array('baseClass, actions', 'sticky'),
));
diff --git a/framework/gii/generators/crud/CrudCode.php b/framework/gii/generators/crud/CrudCode.php
index 9e252b7..9197db0 100644
--- a/framework/gii/generators/crud/CrudCode.php
+++ b/framework/gii/generators/crud/CrudCode.php
@@ -16,7 +16,7 @@ public function rules()
array('model, controller, baseControllerClass', 'required'),
array('model', 'match', 'pattern'=>'/^\w+[\w+\\.]*$/', 'message'=>'{attribute} should only contain word characters and dots.'),
array('controller', 'match', 'pattern'=>'/^\w+[\w+\\/]*$/', 'message'=>'{attribute} should only contain word characters and slashes.'),
- array('baseControllerClass', 'match', 'pattern'=>'/^[a-zA-Z_][\w\\\\]*$/', 'message'=>'{attribute} should only contain word characters and backslashes.'),
+ array('baseControllerClass', 'match', 'pattern'=>'/^[a-zA-Z_\\\\][\w\\\\]*$/', 'message'=>'{attribute} should only contain word characters and backslashes.'),
array('baseControllerClass', 'validateReservedWord', 'skipOnError'=>true),
array('model', 'validateModel'),
array('baseControllerClass', 'sticky'),
diff --git a/framework/gii/generators/model/ModelCode.php b/framework/gii/generators/model/ModelCode.php
index 31e795b..5b08c4c 100644
--- a/framework/gii/generators/model/ModelCode.php
+++ b/framework/gii/generators/model/ModelCode.php
@@ -26,7 +26,7 @@ public function rules()
array('connectionId', 'validateConnectionId', 'skipOnError'=>true),
array('tableName', 'validateTableName', 'skipOnError'=>true),
array('tablePrefix, modelClass', 'match', 'pattern'=>'/^[a-zA-Z_]\w*$/', 'message'=>'{attribute} should only contain word characters.'),
- array('baseClass', 'match', 'pattern'=>'/^[a-zA-Z_][\w\\\\]*$/', 'message'=>'{attribute} should only contain word characters and backslashes.'),
+ array('baseClass', 'match', 'pattern'=>'/^[a-zA-Z_\\\\][\w\\\\]*$/', 'message'=>'{attribute} should only contain word characters and backslashes.'),
array('modelPath', 'validateModelPath', 'skipOnError'=>true),
array('baseClass, modelClass', 'validateReservedWord', 'skipOnError'=>true),
array('baseClass', 'validateBaseClass', 'skipOnError'=>true),
@@ -186,7 +186,7 @@ public function validateBaseClass($attribute,$params)
if(!is_string($class) || !$this->classExists($class))
$this->addError('baseClass', "Class '{$this->baseClass}' does not exist or has syntax error.");
elseif($class!=='CActiveRecord' && !is_subclass_of($class,'CActiveRecord'))
- $this->addError('baseClass', "'{$this->model}' must extend from CActiveRecord.");
+ $this->addError('baseClass', "'{$this->baseClass}' must extend from CActiveRecord.");
}
public function getTableSchema($tableName)
@@ -363,7 +363,7 @@ protected function generateRelations()
* Checks if the given table is a "many to many" pivot table.
* Their PK has 2 fields, and both of those fields are also FK to other separate tables.
* @param CDbTableSchema table to inspect
- * @return boolean true if table matches description of helpter table.
+ * @return boolean true if table matches description of helper table.
*/
protected function isRelationTable($table)
{
diff --git a/framework/gii/generators/model/templates/default/model.php b/framework/gii/generators/model/templates/default/model.php
index 7e4b148..4c60554 100644
--- a/framework/gii/generators/model/templates/default/model.php
+++ b/framework/gii/generators/model/templates/default/model.php
@@ -98,7 +98,7 @@ public function attributeLabels()
{
return array(
$label): ?>
- '$label',\n"; ?>
+ '".str_replace("'","\'",$label)."',\n"; ?>
);
}
diff --git a/framework/gii/generators/module/ModuleCode.php b/framework/gii/generators/module/ModuleCode.php
index b9465f8..2a83144 100644
--- a/framework/gii/generators/module/ModuleCode.php
+++ b/framework/gii/generators/module/ModuleCode.php
@@ -67,7 +67,7 @@ public function prepare()
{
if(CFileHelper::getExtension($file)==='php')
$content=$this->render($file);
- elseif(basename($file)==='.yii') // an empty directory
+ elseif(basename($file)==='.gitkeep') // an empty directory
{
$file=dirname($file);
$content=null;
diff --git a/framework/gii/generators/module/templates/default/components/.gitkeep b/framework/gii/generators/module/templates/default/components/.gitkeep
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/framework/gii/generators/module/templates/default/components/.gitkeep
@@ -0,0 +1 @@
+*
diff --git a/framework/gii/generators/module/templates/default/messages/.gitkeep b/framework/gii/generators/module/templates/default/messages/.gitkeep
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/framework/gii/generators/module/templates/default/messages/.gitkeep
@@ -0,0 +1 @@
+*
diff --git a/framework/gii/generators/module/templates/default/models/.gitkeep b/framework/gii/generators/module/templates/default/models/.gitkeep
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/framework/gii/generators/module/templates/default/models/.gitkeep
@@ -0,0 +1 @@
+*
diff --git a/framework/gii/generators/module/templates/default/views/layouts/.gitkeep b/framework/gii/generators/module/templates/default/views/layouts/.gitkeep
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/framework/gii/generators/module/templates/default/views/layouts/.gitkeep
@@ -0,0 +1 @@
+*
diff --git a/framework/gii/views/common/code.php b/framework/gii/views/common/code.php
index b981005..46a5cfc 100644
--- a/framework/gii/views/common/code.php
+++ b/framework/gii/views/common/code.php
@@ -5,7 +5,7 @@
highlight_string($file->content);
echo '';
}
-elseif(in_array($file->type,array('txt','js','css')))
+elseif(in_array($file->type,array('txt','js','css','sql')))
{
echo '
traverseSymlinks: boolean, whether symlinks to the directories should be traversed too.
+ * Defaults to `false`, meaning that the content of the symlinked directory would not be deleted.
+ * Only symlink would be removed in that default case.
+ *
+ * Note, options parameter is available since 1.1.16
* @since 1.1.14
*/
- public static function removeDirectory($directory)
+ public static function removeDirectory($directory,$options=array())
{
+ if(!isset($options['traverseSymlinks']))
+ $options['traverseSymlinks']=false;
$items=glob($directory.DIRECTORY_SEPARATOR.'{,.}*',GLOB_MARK | GLOB_BRACE);
foreach($items as $item)
{
if(basename($item)=='.' || basename($item)=='..')
continue;
if(substr($item,-1)==DIRECTORY_SEPARATOR)
- self::removeDirectory($item);
+ {
+ if(!$options['traverseSymlinks'] && is_link(rtrim($item,DIRECTORY_SEPARATOR)))
+ unlink(rtrim($item,DIRECTORY_SEPARATOR));
+ else
+ self::removeDirectory($item,$options);
+ }
else
unlink($item);
}
- if(is_dir($directory))
- rmdir($directory);
+ if(is_dir($directory=rtrim($directory,'\\/')))
+ {
+ if(is_link($directory))
+ unlink($directory);
+ else
+ rmdir($directory);
+ }
}
/**
@@ -100,6 +119,7 @@ public static function removeDirectory($directory)
* Level 0 means searching for only the files DIRECTLY under the directory;
* level N means searching for those directories that are within N levels.
*
+ *
absolutePaths: boolean, whether to return absolute paths or relative ones, defaults to true.
*
* @return array files found under the directory. The file list is sorted.
*/
@@ -108,8 +128,9 @@ public static function findFiles($dir,$options=array())
$fileTypes=array();
$exclude=array();
$level=-1;
+ $absolutePaths=true;
extract($options);
- $list=self::findFilesRecursive($dir,'',$fileTypes,$exclude,$level);
+ $list=self::findFilesRecursive($dir,'',$fileTypes,$exclude,$level,$absolutePaths);
sort($list);
return $list;
}
@@ -136,9 +157,11 @@ public static function findFiles($dir,$options=array())
protected static function copyDirectoryRecursive($src,$dst,$base,$fileTypes,$exclude,$level,$options)
{
if(!is_dir($dst))
- self::mkdir($dst,$options,false);
+ self::createDirectory($dst,isset($options['newDirMode'])?$options['newDirMode']:null,false);
$folder=opendir($src);
+ if($folder===false)
+ throw new Exception('Unable to open directory: ' . $src);
while(($file=readdir($folder))!==false)
{
if($file==='.' || $file==='..')
@@ -174,24 +197,28 @@ protected static function copyDirectoryRecursive($src,$dst,$base,$fileTypes,$exc
* Level -1 means searching for all directories and files under the directory;
* Level 0 means searching for only the files DIRECTLY under the directory;
* level N means searching for those directories that are within N levels.
+ * @param boolean $absolutePaths whether to return absolute paths or relative ones
* @return array files found under the directory.
*/
- protected static function findFilesRecursive($dir,$base,$fileTypes,$exclude,$level)
+ protected static function findFilesRecursive($dir,$base,$fileTypes,$exclude,$level,$absolutePaths)
{
$list=array();
- $handle=opendir($dir);
+ $handle=opendir($dir.$base);
+ if($handle===false)
+ throw new Exception('Unable to open directory: ' . $dir);
while(($file=readdir($handle))!==false)
{
if($file==='.' || $file==='..')
continue;
- $path=$dir.DIRECTORY_SEPARATOR.$file;
- $isFile=is_file($path);
+ $path=substr($base.DIRECTORY_SEPARATOR.$file,1);
+ $fullPath=$dir.DIRECTORY_SEPARATOR.$path;
+ $isFile=is_file($fullPath);
if(self::validatePath($base,$file,$isFile,$fileTypes,$exclude))
{
if($isFile)
- $list[]=$path;
+ $list[]=$absolutePaths?$fullPath:$path;
elseif($level)
- $list=array_merge($list,self::findFilesRecursive($path,$base.'/'.$file,$fileTypes,$exclude,$level-1));
+ $list=array_merge($list,self::findFilesRecursive($dir,$base.'/'.$file,$fileTypes,$exclude,$level-1,$absolutePaths));
}
}
closedir($handle);
@@ -219,7 +246,7 @@ protected static function validatePath($base,$file,$isFile,$fileTypes,$exclude)
}
if(!$isFile || empty($fileTypes))
return true;
- if(($type=pathinfo($file,PATHINFO_EXTENSION))!=='')
+ if(($type=self::getExtension($file))!=='')
return in_array($type,$fileTypes);
else
return false;
@@ -276,7 +303,7 @@ public static function getMimeTypeByExtension($file,$magicFile=null)
$extensions=require(Yii::getPathOfAlias('system.utils.mimeTypes').'.php');
elseif($magicFile!==null && !isset($customExtensions[$magicFile]))
$customExtensions[$magicFile]=require($magicFile);
- if(($ext=pathinfo($file,PATHINFO_EXTENSION))!=='')
+ if(($ext=self::getExtension($file))!=='')
{
$ext=strtolower($ext);
if($magicFile===null && isset($extensions[$ext]))
@@ -287,23 +314,50 @@ public static function getMimeTypeByExtension($file,$magicFile=null)
return null;
}
+ /**
+ * Determines the file extension name based on its MIME type.
+ * This method will use a local map between MIME type and extension name.
+ * @param string $file the file name.
+ * @param string $magicFile the path of the file that contains all available extension information.
+ * If this is not set, the default 'system.utils.fileExtensions' file will be used.
+ * This parameter has been available since version 1.1.16.
+ * @return string extension name. Null is returned if the extension cannot be determined.
+ */
+ public static function getExtensionByMimeType($file,$magicFile=null)
+ {
+ static $mimeTypes,$customMimeTypes=array();
+ if($magicFile===null && $mimeTypes===null)
+ $mimeTypes=require(Yii::getPathOfAlias('system.utils.fileExtensions').'.php');
+ elseif($magicFile!==null && !isset($customMimeTypes[$magicFile]))
+ $customMimeTypes[$magicFile]=require($magicFile);
+ if(($mime=self::getMimeType($file))!==null)
+ {
+ $mime=strtolower($mime);
+ if($magicFile===null && isset($mimeTypes[$mime]))
+ return $mimeTypes[$mime];
+ elseif($magicFile!==null && isset($customMimeTypes[$magicFile][$mime]))
+ return $customMimeTypes[$magicFile][$mime];
+ }
+ return null;
+ }
+
/**
* Shared environment safe version of mkdir. Supports recursive creation.
* For avoidance of umask side-effects chmod is used.
*
* @param string $dst path to be created
- * @param array $options newDirMode element used, must contain access bitmask
+ * @param integer $mode the permission to be set for newly created directories, if not set - 0777 will be used
* @param boolean $recursive whether to create directory structure recursive if parent dirs do not exist
* @return boolean result of mkdir
* @see mkdir
*/
- private static function mkdir($dst,array $options,$recursive)
+ public static function createDirectory($dst,$mode=null,$recursive=false)
{
+ if($mode===null)
+ $mode=0777;
$prevDir=dirname($dst);
if($recursive && !is_dir($dst) && !is_dir($prevDir))
- self::mkdir(dirname($dst),$options,true);
-
- $mode=isset($options['newDirMode']) ? $options['newDirMode'] : 0777;
+ self::createDirectory(dirname($dst),$mode,true);
$res=mkdir($dst, $mode);
@chmod($dst,$mode);
return $res;
diff --git a/framework/utils/CFormatter.php b/framework/utils/CFormatter.php
index f072c36..4fbebb6 100644
--- a/framework/utils/CFormatter.php
+++ b/framework/utils/CFormatter.php
@@ -234,7 +234,10 @@ protected function normalizeDateValue($time)
else
return strtotime($time);
}
- return (int)$time;
+ elseif (class_exists('DateTime', false) && $time instanceof DateTime)
+ return $time->getTimestamp();
+ else
+ return (int)$time;
}
/**
diff --git a/framework/utils/CLocalizedFormatter.php b/framework/utils/CLocalizedFormatter.php
index a2323f1..b9df0bc 100644
--- a/framework/utils/CLocalizedFormatter.php
+++ b/framework/utils/CLocalizedFormatter.php
@@ -14,7 +14,7 @@
* It provides the same functionality as {@link CFormatter}, but overrides all the settings for
* {@link booleanFormat}, {@link datetimeFormat} and {@link numberFormat} with the values for the
* current locale. Because of this you are not able to configure these properties for CLocalizedFormatter directly.
- * Date and time format can be adjsuted by setting {@link dateFormat} and {@link timeFormat}.
+ * Date and time format can be adjusted by setting {@link dateFormat} and {@link timeFormat}.
*
* It uses {@link CApplication::locale} by default but you can set a custom locale by using {@link setLocale}-method.
*
diff --git a/framework/utils/CPasswordHelper.php b/framework/utils/CPasswordHelper.php
index 1949470..0ad581e 100644
--- a/framework/utils/CPasswordHelper.php
+++ b/framework/utils/CPasswordHelper.php
@@ -33,7 +33,7 @@
*
{@link attributes}: array, list of attributes to be validated;
- *
{@link message}: string, the customized error message. The message
- * may contain placeholders that will be replaced with the actual content.
- * For example, the "{attribute}" placeholder will be replaced with the label
- * of the problematic attribute. Different validators may define additional
- * placeholders.
- *
{@link on}: string, in which scenario should the validator be in effect.
- * This is used to match the 'on' parameter supplied when calling {@link CModel::validate}.
- *
- *
* When using {@link createValidator} to create a validator, the following aliases
* are recognized as the corresponding built-in validator classes:
*
@@ -141,7 +129,7 @@ abstract protected function validateAttribute($object,$attribute);
public static function createValidator($name,$object,$attributes,$params=array())
{
if(is_string($attributes))
- $attributes=preg_split('/[\s,]+/',$attributes,-1,PREG_SPLIT_NO_EMPTY);
+ $attributes=preg_split('/\s*,\s*/',$attributes,-1,PREG_SPLIT_NO_EMPTY);
if(isset($params['on']))
{
diff --git a/framework/vendors/README.html b/framework/vendors/README.html
index 4162ec9..a0e5fd5 100644
--- a/framework/vendors/README.html
+++ b/framework/vendors/README.html
@@ -19,12 +19,12 @@
diff --git a/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php b/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php
index 86b4c88..233fed9 100644
--- a/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php
+++ b/framework/vendors/htmlpurifier/HTMLPurifier.standalone.php
@@ -7,7 +7,7 @@
* primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
* FILE, changes will be overwritten the next time the script is run.
*
- * @version 4.5.0
+ * @version 4.6.0
*
* @warning
* You must *not* include any other HTML Purifier files before this file,
@@ -39,7 +39,7 @@
*/
/*
- HTML Purifier 4.5.0 - Standards Compliant HTML Filtering
+ HTML Purifier 4.6.0 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or
@@ -74,66 +74,97 @@
class HTMLPurifier
{
- /** Version of HTML Purifier */
- public $version = '4.5.0';
+ /**
+ * Version of HTML Purifier.
+ * @type string
+ */
+ public $version = '4.6.0';
- /** Constant with version of HTML Purifier */
- const VERSION = '4.5.0';
+ /**
+ * Constant with version of HTML Purifier.
+ */
+ const VERSION = '4.6.0';
- /** Global configuration object */
+ /**
+ * Global configuration object.
+ * @type HTMLPurifier_Config
+ */
public $config;
- /** Array of extra HTMLPurifier_Filter objects to run on HTML, for backwards compatibility */
+ /**
+ * Array of extra filter objects to run on HTML,
+ * for backwards compatibility.
+ * @type HTMLPurifier_Filter[]
+ */
private $filters = array();
- /** Single instance of HTML Purifier */
+ /**
+ * Single instance of HTML Purifier.
+ * @type HTMLPurifier
+ */
private static $instance;
- protected $strategy, $generator;
+ /**
+ * @type HTMLPurifier_Strategy_Core
+ */
+ protected $strategy;
+
+ /**
+ * @type HTMLPurifier_Generator
+ */
+ protected $generator;
/**
- * Resultant HTMLPurifier_Context of last run purification. Is an array
- * of contexts if the last called method was purifyArray().
+ * Resultant context of last run purification.
+ * Is an array of contexts if the last called method was purifyArray().
+ * @type HTMLPurifier_Context
*/
public $context;
/**
* Initializes the purifier.
- * @param $config Optional HTMLPurifier_Config object for all instances of
- * the purifier, if omitted, a default configuration is
- * supplied (which can be overridden on a per-use basis).
+ *
+ * @param HTMLPurifier_Config $config Optional HTMLPurifier_Config object
+ * for all instances of the purifier, if omitted, a default
+ * configuration is supplied (which can be overridden on a
+ * per-use basis).
* The parameter can also be any type that
* HTMLPurifier_Config::create() supports.
*/
- public function __construct($config = null) {
-
+ public function __construct($config = null)
+ {
$this->config = HTMLPurifier_Config::create($config);
-
- $this->strategy = new HTMLPurifier_Strategy_Core();
-
+ $this->strategy = new HTMLPurifier_Strategy_Core();
}
/**
* Adds a filter to process the output. First come first serve
- * @param $filter HTMLPurifier_Filter object
+ *
+ * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object
*/
- public function addFilter($filter) {
- trigger_error('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom', E_USER_WARNING);
+ public function addFilter($filter)
+ {
+ trigger_error(
+ 'HTMLPurifier->addFilter() is deprecated, use configuration directives' .
+ ' in the Filter namespace or Filter.Custom',
+ E_USER_WARNING
+ );
$this->filters[] = $filter;
}
/**
* Filters an HTML snippet/document to be XSS-free and standards-compliant.
*
- * @param $html String of HTML to purify
- * @param $config HTMLPurifier_Config object for this operation, if omitted,
- * defaults to the config object specified during this
+ * @param string $html String of HTML to purify
+ * @param HTMLPurifier_Config $config Config object for this operation,
+ * if omitted, defaults to the config object specified during this
* object's construction. The parameter can also be any type
* that HTMLPurifier_Config::create() supports.
- * @return Purified HTML
+ *
+ * @return string Purified HTML
*/
- public function purify($html, $config = null) {
-
+ public function purify($html, $config = null)
+ {
// :TODO: make the config merge in, instead of replace
$config = $config ? HTMLPurifier_Config::create($config) : $this->config;
@@ -171,8 +202,12 @@ public function purify($html, $config = null) {
unset($filter_flags['Custom']);
$filters = array();
foreach ($filter_flags as $filter => $flag) {
- if (!$flag) continue;
- if (strpos($filter, '.') !== false) continue;
+ if (!$flag) {
+ continue;
+ }
+ if (strpos($filter, '.') !== false) {
+ continue;
+ }
$class = "HTMLPurifier_Filter_$filter";
$filters[] = new $class;
}
@@ -195,9 +230,12 @@ public function purify($html, $config = null) {
// list of un-purified tokens
$lexer->tokenizeHTML(
// un-purified HTML
- $html, $config, $context
+ $html,
+ $config,
+ $context
),
- $config, $context
+ $config,
+ $context
)
);
@@ -212,11 +250,15 @@ public function purify($html, $config = null) {
/**
* Filters an array of HTML snippets
- * @param $config Optional HTMLPurifier_Config object for this operation.
+ *
+ * @param string[] $array_of_html Array of html snippets
+ * @param HTMLPurifier_Config $config Optional config object for this operation.
* See HTMLPurifier::purify() for more details.
- * @return Array of purified HTML
+ *
+ * @return string[] Array of purified HTML
*/
- public function purifyArray($array_of_html, $config = null) {
+ public function purifyArray($array_of_html, $config = null)
+ {
$context_array = array();
foreach ($array_of_html as $key => $html) {
$array_of_html[$key] = $this->purify($html, $config);
@@ -228,11 +270,16 @@ public function purifyArray($array_of_html, $config = null) {
/**
* Singleton for enforcing just one HTML Purifier in your system
- * @param $prototype Optional prototype HTMLPurifier instance to
- * overload singleton with, or HTMLPurifier_Config
- * instance to configure the generated version with.
+ *
+ * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
+ * HTMLPurifier instance to overload singleton with,
+ * or HTMLPurifier_Config instance to configure the
+ * generated version with.
+ *
+ * @return HTMLPurifier
*/
- public static function instance($prototype = null) {
+ public static function instance($prototype = null)
+ {
if (!self::$instance || $prototype) {
if ($prototype instanceof HTMLPurifier) {
self::$instance = $prototype;
@@ -246,18 +293,98 @@ public static function instance($prototype = null) {
}
/**
+ * Singleton for enforcing just one HTML Purifier in your system
+ *
+ * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype
+ * HTMLPurifier instance to overload singleton with,
+ * or HTMLPurifier_Config instance to configure the
+ * generated version with.
+ *
+ * @return HTMLPurifier
* @note Backwards compatibility, see instance()
*/
- public static function getInstance($prototype = null) {
+ public static function getInstance($prototype = null)
+ {
return HTMLPurifier::instance($prototype);
}
-
}
+/**
+ * Converts a stream of HTMLPurifier_Token into an HTMLPurifier_Node,
+ * and back again.
+ *
+ * @note This transformation is not an equivalence. We mutate the input
+ * token stream to make it so; see all [MUT] markers in code.
+ */
+class HTMLPurifier_Arborize
+{
+ public static function arborize($tokens, $config, $context) {
+ $definition = $config->getHTMLDefinition();
+ $parent = new HTMLPurifier_Token_Start($definition->info_parent);
+ $stack = array($parent->toNode());
+ foreach ($tokens as $token) {
+ $token->skip = null; // [MUT]
+ $token->carryover = null; // [MUT]
+ if ($token instanceof HTMLPurifier_Token_End) {
+ $token->start = null; // [MUT]
+ $r = array_pop($stack);
+ assert($r->name === $token->name);
+ assert(empty($token->attr));
+ $r->endCol = $token->col;
+ $r->endLine = $token->line;
+ $r->endArmor = $token->armor;
+ continue;
+ }
+ $node = $token->toNode();
+ $stack[count($stack)-1]->children[] = $node;
+ if ($token instanceof HTMLPurifier_Token_Start) {
+ $stack[] = $node;
+ }
+ }
+ assert(count($stack) == 1);
+ return $stack[0];
+ }
+
+ public static function flatten($node, $config, $context) {
+ $level = 0;
+ $nodes = array($level => new HTMLPurifier_Queue(array($node)));
+ $closingTokens = array();
+ $tokens = array();
+ do {
+ while (!$nodes[$level]->isEmpty()) {
+ $node = $nodes[$level]->shift(); // FIFO
+ list($start, $end) = $node->toTokenPair();
+ if ($level > 0) {
+ $tokens[] = $start;
+ }
+ if ($end !== NULL) {
+ $closingTokens[$level][] = $end;
+ }
+ if ($node instanceof HTMLPurifier_Node_Element) {
+ $level++;
+ $nodes[$level] = new HTMLPurifier_Queue();
+ foreach ($node->children as $childNode) {
+ $nodes[$level]->push($childNode);
+ }
+ }
+ }
+ $level--;
+ if ($level && isset($closingTokens[$level])) {
+ while ($token = array_pop($closingTokens[$level])) {
+ $tokens[] = $token;
+ }
+ }
+ } while ($level > 0);
+ return $tokens;
+ }
+}
+
+
+
/**
* Defines common attribute collections that modules reference
*/
@@ -266,7 +393,8 @@ class HTMLPurifier_AttrCollections
{
/**
- * Associative array of attribute collections, indexed by name
+ * Associative array of attribute collections, indexed by name.
+ * @type array
*/
public $info = array();
@@ -274,10 +402,11 @@ class HTMLPurifier_AttrCollections
* Performs all expansions on internal data for use by other inclusions
* It also collects all attribute collection extensions from
* modules
- * @param $attr_types HTMLPurifier_AttrTypes instance
- * @param $modules Hash array of HTMLPurifier_HTMLModule members
+ * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
+ * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
*/
- public function __construct($attr_types, $modules) {
+ public function __construct($attr_types, $modules)
+ {
// load extensions from the modules
foreach ($modules as $module) {
foreach ($module->attr_collections as $coll_i => $coll) {
@@ -288,7 +417,9 @@ public function __construct($attr_types, $modules) {
if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
// merge in includes
$this->info[$coll_i][$attr_i] = array_merge(
- $this->info[$coll_i][$attr_i], $attr);
+ $this->info[$coll_i][$attr_i],
+ $attr
+ );
continue;
}
$this->info[$coll_i][$attr_i] = $attr;
@@ -307,20 +438,29 @@ public function __construct($attr_types, $modules) {
/**
* Takes a reference to an attribute associative array and performs
* all inclusions specified by the zero index.
- * @param &$attr Reference to attribute array
+ * @param array &$attr Reference to attribute array
*/
- public function performInclusions(&$attr) {
- if (!isset($attr[0])) return;
+ public function performInclusions(&$attr)
+ {
+ if (!isset($attr[0])) {
+ return;
+ }
$merge = $attr[0];
$seen = array(); // recursion guard
// loop through all the inclusions
for ($i = 0; isset($merge[$i]); $i++) {
- if (isset($seen[$merge[$i]])) continue;
+ if (isset($seen[$merge[$i]])) {
+ continue;
+ }
$seen[$merge[$i]] = true;
// foreach attribute of the inclusion, copy it over
- if (!isset($this->info[$merge[$i]])) continue;
+ if (!isset($this->info[$merge[$i]])) {
+ continue;
+ }
foreach ($this->info[$merge[$i]] as $key => $value) {
- if (isset($attr[$key])) continue; // also catches more inclusions
+ if (isset($attr[$key])) {
+ continue;
+ } // also catches more inclusions
$attr[$key] = $value;
}
if (isset($this->info[$merge[$i]][0])) {
@@ -334,20 +474,24 @@ public function performInclusions(&$attr) {
/**
* Expands all string identifiers in an attribute array by replacing
* them with the appropriate values inside HTMLPurifier_AttrTypes
- * @param &$attr Reference to attribute array
- * @param $attr_types HTMLPurifier_AttrTypes instance
+ * @param array &$attr Reference to attribute array
+ * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
*/
- public function expandIdentifiers(&$attr, $attr_types) {
-
+ public function expandIdentifiers(&$attr, $attr_types)
+ {
// because foreach will process new elements we add, make sure we
// skip duplicates
$processed = array();
foreach ($attr as $def_i => $def) {
// skip inclusions
- if ($def_i === 0) continue;
+ if ($def_i === 0) {
+ continue;
+ }
- if (isset($processed[$def_i])) continue;
+ if (isset($processed[$def_i])) {
+ continue;
+ }
// determine whether or not attribute is required
if ($required = (strpos($def_i, '*') !== false)) {
@@ -378,9 +522,7 @@ public function expandIdentifiers(&$attr, $attr_types) {
unset($attr[$def_i]);
}
}
-
}
-
}
@@ -401,23 +543,25 @@ abstract class HTMLPurifier_AttrDef
{
/**
- * Tells us whether or not an HTML attribute is minimized. Has no
- * meaning in other contexts.
+ * Tells us whether or not an HTML attribute is minimized.
+ * Has no meaning in other contexts.
+ * @type bool
*/
public $minimized = false;
/**
- * Tells us whether or not an HTML attribute is required. Has no
- * meaning in other contexts
+ * Tells us whether or not an HTML attribute is required.
+ * Has no meaning in other contexts
+ * @type bool
*/
public $required = false;
/**
* Validates and cleans passed string according to a definition.
*
- * @param $string String to be validated and cleaned.
- * @param $config Mandatory HTMLPurifier_Config object.
- * @param $context Mandatory HTMLPurifier_AttrContext object.
+ * @param string $string String to be validated and cleaned.
+ * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
+ * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object.
*/
abstract public function validate($string, $config, $context);
@@ -442,7 +586,8 @@ abstract public function validate($string, $config, $context);
* parsing XML, thus, this behavior may still be correct. We
* assume that newlines have been normalized.
*/
- public function parseCDATA($string) {
+ public function parseCDATA($string)
+ {
$string = trim($string);
$string = str_replace(array("\n", "\t", "\r"), ' ', $string);
return $string;
@@ -450,10 +595,11 @@ public function parseCDATA($string) {
/**
* Factory method for creating this class from a string.
- * @param $string String construction info
- * @return Created AttrDef object corresponding to $string
+ * @param string $string String construction info
+ * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string
*/
- public function make($string) {
+ public function make($string)
+ {
// default implementation, return a flyweight of this object.
// If $string has an effect on the returned object (i.e. you
// need to overload this method), it is best
@@ -464,16 +610,20 @@ public function make($string) {
/**
* Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
* properly. THIS IS A HACK!
+ * @param string $string a CSS colour definition
+ * @return string
*/
- protected function mungeRgb($string) {
+ protected function mungeRgb($string)
+ {
return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
}
/**
- * Parses a possibly escaped CSS string and returns the "pure"
+ * Parses a possibly escaped CSS string and returns the "pure"
* version of it.
*/
- protected function expandCSSEscape($string) {
+ protected function expandCSSEscape($string)
+ {
// flexibly parse it
$ret = '';
for ($i = 0, $c = strlen($string); $i < $c; $i++) {
@@ -486,25 +636,32 @@ protected function expandCSSEscape($string) {
if (ctype_xdigit($string[$i])) {
$code = $string[$i];
for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
- if (!ctype_xdigit($string[$i])) break;
+ if (!ctype_xdigit($string[$i])) {
+ break;
+ }
$code .= $string[$i];
}
// We have to be extremely careful when adding
// new characters, to make sure we're not breaking
// the encoding.
$char = HTMLPurifier_Encoder::unichr(hexdec($code));
- if (HTMLPurifier_Encoder::cleanUTF8($char) === '') continue;
+ if (HTMLPurifier_Encoder::cleanUTF8($char) === '') {
+ continue;
+ }
$ret .= $char;
- if ($i < $c && trim($string[$i]) !== '') $i--;
+ if ($i < $c && trim($string[$i]) !== '') {
+ $i--;
+ }
+ continue;
+ }
+ if ($string[$i] === "\n") {
continue;
}
- if ($string[$i] === "\n") continue;
}
$ret .= $string[$i];
}
return $ret;
}
-
}
@@ -531,37 +688,41 @@ abstract class HTMLPurifier_AttrTransform
/**
* Abstract: makes changes to the attributes dependent on multiple values.
*
- * @param $attr Assoc array of attributes, usually from
+ * @param array $attr Assoc array of attributes, usually from
* HTMLPurifier_Token_Tag::$attr
- * @param $config Mandatory HTMLPurifier_Config object.
- * @param $context Mandatory HTMLPurifier_Context object
- * @returns Processed attribute array.
+ * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
+ * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
+ * @return array Processed attribute array.
*/
abstract public function transform($attr, $config, $context);
/**
* Prepends CSS properties to the style attribute, creating the
* attribute if it doesn't exist.
- * @param $attr Attribute array to process (passed by reference)
- * @param $css CSS to prepend
+ * @param array &$attr Attribute array to process (passed by reference)
+ * @param string $css CSS to prepend
*/
- public function prependCSS(&$attr, $css) {
+ public function prependCSS(&$attr, $css)
+ {
$attr['style'] = isset($attr['style']) ? $attr['style'] : '';
$attr['style'] = $css . $attr['style'];
}
/**
* Retrieves and removes an attribute
- * @param $attr Attribute array to process (passed by reference)
- * @param $key Key of attribute to confiscate
+ * @param array &$attr Attribute array to process (passed by reference)
+ * @param mixed $key Key of attribute to confiscate
+ * @return mixed
*/
- public function confiscateAttr(&$attr, $key) {
- if (!isset($attr[$key])) return null;
+ public function confiscateAttr(&$attr, $key)
+ {
+ if (!isset($attr[$key])) {
+ return null;
+ }
$value = $attr[$key];
unset($attr[$key]);
return $value;
}
-
}
@@ -574,7 +735,8 @@ public function confiscateAttr(&$attr, $key) {
class HTMLPurifier_AttrTypes
{
/**
- * Lookup array of attribute string identifiers to concrete implementations
+ * Lookup array of attribute string identifiers to concrete implementations.
+ * @type HTMLPurifier_AttrDef[]
*/
protected $info = array();
@@ -582,7 +744,8 @@ class HTMLPurifier_AttrTypes
* Constructs the info array, supplying default implementations for attribute
* types.
*/
- public function __construct() {
+ public function __construct()
+ {
// XXX This is kind of poor, since we don't actually /clone/
// instances; instead, we use the supplied make() attribute. So,
// the underlying class must know how to deal with arguments.
@@ -622,36 +785,39 @@ public function __construct() {
$this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
}
- private static function makeEnum($in) {
+ private static function makeEnum($in)
+ {
return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
}
/**
* Retrieves a type
- * @param $type String type name
- * @return Object AttrDef for type
+ * @param string $type String type name
+ * @return HTMLPurifier_AttrDef Object AttrDef for type
*/
- public function get($type) {
-
+ public function get($type)
+ {
// determine if there is any extra info tacked on
- if (strpos($type, '#') !== false) list($type, $string) = explode('#', $type, 2);
- else $string = '';
+ if (strpos($type, '#') !== false) {
+ list($type, $string) = explode('#', $type, 2);
+ } else {
+ $string = '';
+ }
if (!isset($this->info[$type])) {
trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
return;
}
-
return $this->info[$type]->make($string);
-
}
/**
* Sets a new implementation for a type
- * @param $type String type name
- * @param $impl Object AttrDef for type
+ * @param string $type String type name
+ * @param HTMLPurifier_AttrDef $impl Object AttrDef for type
*/
- public function set($type, $impl) {
+ public function set($type, $impl)
+ {
$this->info[$type] = $impl;
}
}
@@ -669,17 +835,14 @@ class HTMLPurifier_AttrValidator
{
/**
- * Validates the attributes of a token, returning a modified token
+ * Validates the attributes of a token, mutating it as necessary.
* that has valid tokens
- * @param $token Reference to token to validate. We require a reference
- * because the operation this class performs on the token are
- * not atomic, so the context CurrentToken to be updated
- * throughout
- * @param $config Instance of HTMLPurifier_Config
- * @param $context Instance of HTMLPurifier_Context
+ * @param HTMLPurifier_Token $token Token to validate.
+ * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
+ * @param HTMLPurifier_Context $context Instance of HTMLPurifier_Context
*/
- public function validateToken(&$token, &$config, $context) {
-
+ public function validateToken($token, $config, $context)
+ {
$definition = $config->getHTMLDefinition();
$e =& $context->get('ErrorCollector', true);
@@ -692,12 +855,15 @@ public function validateToken(&$token, &$config, $context) {
// initialize CurrentToken if necessary
$current_token =& $context->get('CurrentToken', true);
- if (!$current_token) $context->register('CurrentToken', $token);
+ if (!$current_token) {
+ $context->register('CurrentToken', $token);
+ }
- if (
- !$token instanceof HTMLPurifier_Token_Start &&
+ if (!$token instanceof HTMLPurifier_Token_Start &&
!$token instanceof HTMLPurifier_Token_Empty
- ) return $token;
+ ) {
+ return;
+ }
// create alias to global definition array, see also $defs
// DEFINITION CALL
@@ -711,7 +877,9 @@ public function validateToken(&$token, &$config, $context) {
foreach ($definition->info_attr_transform_pre as $transform) {
$attr = $transform->transform($o = $attr, $config, $context);
if ($e) {
- if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ if ($attr != $o) {
+ $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ }
}
}
@@ -720,7 +888,9 @@ public function validateToken(&$token, &$config, $context) {
foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
$attr = $transform->transform($o = $attr, $config, $context);
if ($e) {
- if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ if ($attr != $o) {
+ $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ }
}
}
@@ -737,7 +907,7 @@ public function validateToken(&$token, &$config, $context) {
foreach ($attr as $attr_key => $value) {
// call the definition
- if ( isset($defs[$attr_key]) ) {
+ if (isset($defs[$attr_key])) {
// there is a local definition defined
if ($defs[$attr_key] === false) {
// We've explicitly been told not to allow this element.
@@ -749,15 +919,19 @@ public function validateToken(&$token, &$config, $context) {
} else {
// validate according to the element's definition
$result = $defs[$attr_key]->validate(
- $value, $config, $context
- );
+ $value,
+ $config,
+ $context
+ );
}
- } elseif ( isset($d_defs[$attr_key]) ) {
+ } elseif (isset($d_defs[$attr_key])) {
// there is a global definition defined, validate according
// to the global definition
$result = $d_defs[$attr_key]->validate(
- $value, $config, $context
- );
+ $value,
+ $config,
+ $context
+ );
} else {
// system never heard of the attribute? DELETE!
$result = false;
@@ -767,7 +941,9 @@ public function validateToken(&$token, &$config, $context) {
if ($result === false || $result === null) {
// this is a generic error message that should replaced
// with more specific ones when possible
- if ($e) $e->send(E_ERROR, 'AttrValidator: Attribute removed');
+ if ($e) {
+ $e->send(E_ERROR, 'AttrValidator: Attribute removed');
+ }
// remove the attribute
unset($attr[$attr_key]);
@@ -797,7 +973,9 @@ public function validateToken(&$token, &$config, $context) {
foreach ($definition->info_attr_transform_post as $transform) {
$attr = $transform->transform($o = $attr, $config, $context);
if ($e) {
- if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ if ($attr != $o) {
+ $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ }
}
}
@@ -805,14 +983,18 @@ public function validateToken(&$token, &$config, $context) {
foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
$attr = $transform->transform($o = $attr, $config, $context);
if ($e) {
- if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ if ($attr != $o) {
+ $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ }
}
}
$token->attr = $attr;
// destroy CurrentToken if we made it ourselves
- if (!$current_token) $context->destroy('CurrentToken');
+ if (!$current_token) {
+ $context->destroy('CurrentToken');
+ }
}
@@ -856,11 +1038,15 @@ class HTMLPurifier_Bootstrap
/**
* Autoload function for HTML Purifier
- * @param $class Class to load
+ * @param string $class Class to load
+ * @return bool
*/
- public static function autoload($class) {
+ public static function autoload($class)
+ {
$file = HTMLPurifier_Bootstrap::getPath($class);
- if (!$file) return false;
+ if (!$file) {
+ return false;
+ }
// Technically speaking, it should be ok and more efficient to
// just do 'require', but Antonio Parraga reports that with
// Zend extensions such as Zend debugger and APC, this invariant
@@ -872,9 +1058,14 @@ public static function autoload($class) {
/**
* Returns the path for a specific class.
+ * @param string $class Class path to get
+ * @return string
*/
- public static function getPath($class) {
- if (strncmp('HTMLPurifier', $class, 12) !== 0) return false;
+ public static function getPath($class)
+ {
+ if (strncmp('HTMLPurifier', $class, 12) !== 0) {
+ return false;
+ }
// Custom implementations
if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
$code = str_replace('_', '-', substr($class, 22));
@@ -882,16 +1073,19 @@ public static function getPath($class) {
} else {
$file = str_replace('_', '/', $class) . '.php';
}
- if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) return false;
+ if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
+ return false;
+ }
return $file;
}
/**
* "Pre-registers" our autoloader on the SPL stack.
*/
- public static function registerAutoload() {
+ public static function registerAutoload()
+ {
$autoload = array('HTMLPurifier_Bootstrap', 'autoload');
- if ( ($funcs = spl_autoload_functions()) === false ) {
+ if (($funcs = spl_autoload_functions()) === false) {
spl_autoload_register($autoload);
} elseif (function_exists('spl_autoload_unregister')) {
if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
@@ -907,27 +1101,30 @@ public static function registerAutoload() {
// places where we need to error out
$reflector = new ReflectionMethod($func[0], $func[1]);
if (!$reflector->isStatic()) {
- throw new Exception('
- HTML Purifier autoloader registrar is not compatible
+ throw new Exception(
+ 'HTML Purifier autoloader registrar is not compatible
with non-static object methods due to PHP Bug #44144;
Please do not use HTMLPurifier.autoload.php (or any
file that includes this file); instead, place the code:
spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
- after your own autoloaders.
- ');
+ after your own autoloaders.'
+ );
}
// Suprisingly, spl_autoload_register supports the
// Class::staticMethod callback format, although call_user_func doesn't
- if ($compat) $func = implode('::', $func);
+ if ($compat) {
+ $func = implode('::', $func);
+ }
}
spl_autoload_unregister($func);
}
spl_autoload_register($autoload);
- foreach ($funcs as $func) spl_autoload_register($func);
+ foreach ($funcs as $func) {
+ spl_autoload_register($func);
+ }
}
}
}
-
}
@@ -943,6 +1140,7 @@ abstract class HTMLPurifier_Definition
/**
* Has setup() been called yet?
+ * @type bool
*/
public $setup = false;
@@ -954,31 +1152,35 @@ abstract class HTMLPurifier_Definition
* is used and any writes to the raw definition object are short
* circuited. See enduser-customize.html for the high-level
* picture.
+ * @type bool
*/
public $optimized = null;
/**
* What type of definition is it?
+ * @type string
*/
public $type;
/**
* Sets up the definition object into the final form, something
* not done by the constructor
- * @param $config HTMLPurifier_Config instance
+ * @param HTMLPurifier_Config $config
*/
abstract protected function doSetup($config);
/**
* Setup function that aborts if already setup
- * @param $config HTMLPurifier_Config instance
+ * @param HTMLPurifier_Config $config
*/
- public function setup($config) {
- if ($this->setup) return;
+ public function setup($config)
+ {
+ if ($this->setup) {
+ return;
+ }
$this->setup = true;
$this->doSetup($config);
}
-
}
@@ -996,35 +1198,59 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
/**
* Assoc array of attribute name to definition object.
+ * @type HTMLPurifier_AttrDef[]
*/
public $info = array();
/**
* Constructs the info array. The meat of this class.
+ * @param HTMLPurifier_Config $config
*/
- protected function doSetup($config) {
-
+ protected function doSetup($config)
+ {
$this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
- array('left', 'right', 'center', 'justify'), false);
+ array('left', 'right', 'center', 'justify'),
+ false
+ );
$border_style =
- $this->info['border-bottom-style'] =
- $this->info['border-right-style'] =
- $this->info['border-left-style'] =
- $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
- array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double',
- 'groove', 'ridge', 'inset', 'outset'), false);
+ $this->info['border-bottom-style'] =
+ $this->info['border-right-style'] =
+ $this->info['border-left-style'] =
+ $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
+ array(
+ 'none',
+ 'hidden',
+ 'dotted',
+ 'dashed',
+ 'solid',
+ 'double',
+ 'groove',
+ 'ridge',
+ 'inset',
+ 'outset'
+ ),
+ false
+ );
$this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
$this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
- array('none', 'left', 'right', 'both'), false);
+ array('none', 'left', 'right', 'both'),
+ false
+ );
$this->info['float'] = new HTMLPurifier_AttrDef_Enum(
- array('none', 'left', 'right'), false);
+ array('none', 'left', 'right'),
+ false
+ );
$this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
- array('normal', 'italic', 'oblique'), false);
+ array('normal', 'italic', 'oblique'),
+ false
+ );
$this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
- array('normal', 'small-caps'), false);
+ array('normal', 'small-caps'),
+ false
+ );
$uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
array(
@@ -1034,16 +1260,31 @@ protected function doSetup($config) {
);
$this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
- array('inside', 'outside'), false);
+ array('inside', 'outside'),
+ false
+ );
$this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
- array('disc', 'circle', 'square', 'decimal', 'lower-roman',
- 'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false);
+ array(
+ 'disc',
+ 'circle',
+ 'square',
+ 'decimal',
+ 'lower-roman',
+ 'upper-roman',
+ 'lower-alpha',
+ 'upper-alpha',
+ 'none'
+ ),
+ false
+ );
$this->info['list-style-image'] = $uri_or_none;
$this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
$this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
- array('capitalize', 'uppercase', 'lowercase', 'none'), false);
+ array('capitalize', 'uppercase', 'lowercase', 'none'),
+ false
+ );
$this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
$this->info['background-image'] = $uri_or_none;
@@ -1056,104 +1297,137 @@ protected function doSetup($config) {
$this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
$border_color =
- $this->info['border-top-color'] =
- $this->info['border-bottom-color'] =
- $this->info['border-left-color'] =
- $this->info['border-right-color'] =
- $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_Enum(array('transparent')),
- new HTMLPurifier_AttrDef_CSS_Color()
- ));
+ $this->info['border-top-color'] =
+ $this->info['border-bottom-color'] =
+ $this->info['border-left-color'] =
+ $this->info['border-right-color'] =
+ $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Enum(array('transparent')),
+ new HTMLPurifier_AttrDef_CSS_Color()
+ )
+ );
$this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
$this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
$border_width =
- $this->info['border-top-width'] =
- $this->info['border-bottom-width'] =
- $this->info['border-left-width'] =
- $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
- new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
- ));
+ $this->info['border-top-width'] =
+ $this->info['border-bottom-width'] =
+ $this->info['border-left-width'] =
+ $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
+ new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
+ )
+ );
$this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
- $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_Enum(array('normal')),
- new HTMLPurifier_AttrDef_CSS_Length()
- ));
-
- $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_Enum(array('normal')),
- new HTMLPurifier_AttrDef_CSS_Length()
- ));
-
- $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small',
- 'small', 'medium', 'large', 'x-large', 'xx-large',
- 'larger', 'smaller')),
- new HTMLPurifier_AttrDef_CSS_Percentage(),
- new HTMLPurifier_AttrDef_CSS_Length()
- ));
-
- $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_Enum(array('normal')),
- new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
- new HTMLPurifier_AttrDef_CSS_Length('0'),
- new HTMLPurifier_AttrDef_CSS_Percentage(true)
- ));
+ $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Enum(array('normal')),
+ new HTMLPurifier_AttrDef_CSS_Length()
+ )
+ );
+
+ $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Enum(array('normal')),
+ new HTMLPurifier_AttrDef_CSS_Length()
+ )
+ );
+
+ $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Enum(
+ array(
+ 'xx-small',
+ 'x-small',
+ 'small',
+ 'medium',
+ 'large',
+ 'x-large',
+ 'xx-large',
+ 'larger',
+ 'smaller'
+ )
+ ),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_CSS_Length()
+ )
+ );
+
+ $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Enum(array('normal')),
+ new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true)
+ )
+ );
$margin =
- $this->info['margin-top'] =
- $this->info['margin-bottom'] =
- $this->info['margin-left'] =
- $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_CSS_Length(),
- new HTMLPurifier_AttrDef_CSS_Percentage(),
- new HTMLPurifier_AttrDef_Enum(array('auto'))
- ));
+ $this->info['margin-top'] =
+ $this->info['margin-bottom'] =
+ $this->info['margin-left'] =
+ $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_Enum(array('auto'))
+ )
+ );
$this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
// non-negative
$padding =
- $this->info['padding-top'] =
- $this->info['padding-bottom'] =
- $this->info['padding-left'] =
- $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_CSS_Length('0'),
- new HTMLPurifier_AttrDef_CSS_Percentage(true)
- ));
+ $this->info['padding-top'] =
+ $this->info['padding-bottom'] =
+ $this->info['padding-left'] =
+ $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true)
+ )
+ );
$this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
- $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_CSS_Length(),
- new HTMLPurifier_AttrDef_CSS_Percentage()
- ));
+ $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage()
+ )
+ );
- $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_CSS_Length('0'),
- new HTMLPurifier_AttrDef_CSS_Percentage(true),
- new HTMLPurifier_AttrDef_Enum(array('auto'))
- ));
+ $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true),
+ new HTMLPurifier_AttrDef_Enum(array('auto'))
+ )
+ );
$max = $config->get('CSS.MaxImgLength');
$this->info['width'] =
$this->info['height'] =
$max === null ?
- $trusted_wh :
- new HTMLPurifier_AttrDef_Switch('img',
- // For img tags:
- new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_CSS_Length('0', $max),
- new HTMLPurifier_AttrDef_Enum(array('auto'))
- )),
- // For everyone else:
- $trusted_wh
- );
+ $trusted_wh :
+ new HTMLPurifier_AttrDef_Switch(
+ 'img',
+ // For img tags:
+ new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Length('0', $max),
+ new HTMLPurifier_AttrDef_Enum(array('auto'))
+ )
+ ),
+ // For everyone else:
+ $trusted_wh
+ );
$this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
@@ -1161,8 +1435,23 @@ protected function doSetup($config) {
// this could use specialized code
$this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
- array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300',
- '400', '500', '600', '700', '800', '900'), false);
+ array(
+ 'normal',
+ 'bold',
+ 'bolder',
+ 'lighter',
+ '100',
+ '200',
+ '300',
+ '400',
+ '500',
+ '600',
+ '700',
+ '800',
+ '900'
+ ),
+ false
+ );
// MUST be called after other font properties, as it references
// a CSSDefinition object
@@ -1175,27 +1464,44 @@ protected function doSetup($config) {
$this->info['border-left'] =
$this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
- $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array(
- 'collapse', 'separate'));
+ $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
+ array('collapse', 'separate')
+ );
- $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array(
- 'top', 'bottom'));
+ $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
+ array('top', 'bottom')
+ );
- $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array(
- 'auto', 'fixed'));
+ $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
+ array('auto', 'fixed')
+ );
- $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super',
- 'top', 'text-top', 'middle', 'bottom', 'text-bottom')),
- new HTMLPurifier_AttrDef_CSS_Length(),
- new HTMLPurifier_AttrDef_CSS_Percentage()
- ));
+ $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Enum(
+ array(
+ 'baseline',
+ 'sub',
+ 'super',
+ 'top',
+ 'text-top',
+ 'middle',
+ 'bottom',
+ 'text-bottom'
+ )
+ ),
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage()
+ )
+ );
$this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
// These CSS properties don't work on many browsers, but we live
// in THE FUTURE!
- $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line'));
+ $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
+ array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line')
+ );
if ($config->get('CSS.Proprietary')) {
$this->doSetupProprietary($config);
@@ -1218,76 +1524,119 @@ protected function doSetup($config) {
$this->setupConfigStuff($config);
}
- protected function doSetupProprietary($config) {
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ protected function doSetupProprietary($config)
+ {
// Internet Explorer only scrollbar colors
- $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
- $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
// technically not proprietary, but CSS3, and no one supports it
- $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
- $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
- $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+ $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+ $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+ $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
// only opacity, for now
$this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
// more CSS3
$this->info['page-break-after'] =
- $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(array('auto','always','avoid','left','right'));
- $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto','avoid'));
+ $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
+ array(
+ 'auto',
+ 'always',
+ 'avoid',
+ 'left',
+ 'right'
+ )
+ );
+ $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid'));
}
- protected function doSetupTricky($config) {
- $this->info['display'] = new HTMLPurifier_AttrDef_Enum(array(
- 'inline', 'block', 'list-item', 'run-in', 'compact',
- 'marker', 'table', 'inline-block', 'inline-table', 'table-row-group',
- 'table-header-group', 'table-footer-group', 'table-row',
- 'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none'
- ));
- $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array(
- 'visible', 'hidden', 'collapse'
- ));
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ protected function doSetupTricky($config)
+ {
+ $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
+ array(
+ 'inline',
+ 'block',
+ 'list-item',
+ 'run-in',
+ 'compact',
+ 'marker',
+ 'table',
+ 'inline-block',
+ 'inline-table',
+ 'table-row-group',
+ 'table-header-group',
+ 'table-footer-group',
+ 'table-row',
+ 'table-column-group',
+ 'table-column',
+ 'table-cell',
+ 'table-caption',
+ 'none'
+ )
+ );
+ $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
+ array('visible', 'hidden', 'collapse')
+ );
$this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
}
- protected function doSetupTrusted($config) {
- $this->info['position'] = new HTMLPurifier_AttrDef_Enum(array(
- 'static', 'relative', 'absolute', 'fixed'
- ));
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ protected function doSetupTrusted($config)
+ {
+ $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
+ array('static', 'relative', 'absolute', 'fixed')
+ );
$this->info['top'] =
$this->info['left'] =
$this->info['right'] =
- $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_CSS_Length(),
- new HTMLPurifier_AttrDef_CSS_Percentage(),
- new HTMLPurifier_AttrDef_Enum(array('auto')),
- ));
- $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
- new HTMLPurifier_AttrDef_Integer(),
- new HTMLPurifier_AttrDef_Enum(array('auto')),
- ));
+ $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_Enum(array('auto')),
+ )
+ );
+ $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ array(
+ new HTMLPurifier_AttrDef_Integer(),
+ new HTMLPurifier_AttrDef_Enum(array('auto')),
+ )
+ );
}
/**
* Performs extra config-based processing. Based off of
* HTMLPurifier_HTMLDefinition.
+ * @param HTMLPurifier_Config $config
* @todo Refactor duplicate elements into common class (probably using
* composition, not inheritance).
*/
- protected function setupConfigStuff($config) {
-
+ protected function setupConfigStuff($config)
+ {
// setup allowed elements
- $support = "(for information on implementing this, see the ".
- "support forums) ";
+ $support = "(for information on implementing this, see the " .
+ "support forums) ";
$allowed_properties = $config->get('CSS.AllowedProperties');
if ($allowed_properties !== null) {
foreach ($this->info as $name => $d) {
- if(!isset($allowed_properties[$name])) unset($this->info[$name]);
+ if (!isset($allowed_properties[$name])) {
+ unset($this->info[$name]);
+ }
unset($allowed_properties[$name]);
}
// emit errors
@@ -1306,7 +1655,6 @@ protected function setupConfigStuff($config) {
}
}
}
-
}
}
@@ -1315,48 +1663,52 @@ protected function setupConfigStuff($config) {
/**
- * Defines allowed child nodes and validates tokens against it.
+ * Defines allowed child nodes and validates nodes against it.
*/
abstract class HTMLPurifier_ChildDef
{
/**
* Type of child definition, usually right-most part of class name lowercase.
* Used occasionally in terms of context.
+ * @type string
*/
public $type;
/**
- * Bool that indicates whether or not an empty array of children is okay
+ * Indicates whether or not an empty array of children is okay.
*
* This is necessary for redundant checking when changes affecting
* a child node may cause a parent node to now be disallowed.
+ * @type bool
*/
public $allow_empty;
/**
- * Lookup array of all elements that this definition could possibly allow
+ * Lookup array of all elements that this definition could possibly allow.
+ * @type array
*/
public $elements = array();
/**
* Get lookup of tag names that should not close this element automatically.
* All other elements will do so.
+ * @param HTMLPurifier_Config $config HTMLPurifier_Config object
+ * @return array
*/
- public function getAllowedElements($config) {
+ public function getAllowedElements($config)
+ {
return $this->elements;
}
/**
* Validates nodes according to definition and returns modification.
*
- * @param $tokens_of_children Array of HTMLPurifier_Token
- * @param $config HTMLPurifier_Config object
- * @param $context HTMLPurifier_Context object
- * @return bool true to leave nodes as is
- * @return bool false to remove parent node
- * @return array of replacement child tokens
- */
- abstract public function validateChildren($tokens_of_children, $config, $context);
+ * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
+ * @param HTMLPurifier_Config $config HTMLPurifier_Config object
+ * @param HTMLPurifier_Context $context HTMLPurifier_Context object
+ * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
+ */
+ abstract public function validateChildren($children, $config, $context);
}
@@ -1382,78 +1734,92 @@ class HTMLPurifier_Config
/**
* HTML Purifier's version
+ * @type string
*/
- public $version = '4.5.0';
+ public $version = '4.6.0';
/**
- * Bool indicator whether or not to automatically finalize
- * the object if a read operation is done
+ * Whether or not to automatically finalize
+ * the object if a read operation is done.
+ * @type bool
*/
public $autoFinalize = true;
// protected member variables
/**
- * Namespace indexed array of serials for specific namespaces (see
- * getSerial() for more info).
+ * Namespace indexed array of serials for specific namespaces.
+ * @see getSerial() for more info.
+ * @type string[]
*/
protected $serials = array();
/**
- * Serial for entire configuration object
+ * Serial for entire configuration object.
+ * @type string
*/
protected $serial;
/**
- * Parser for variables
+ * Parser for variables.
+ * @type HTMLPurifier_VarParser_Flexible
*/
protected $parser = null;
/**
- * Reference HTMLPurifier_ConfigSchema for value checking
+ * Reference HTMLPurifier_ConfigSchema for value checking.
+ * @type HTMLPurifier_ConfigSchema
* @note This is public for introspective purposes. Please don't
* abuse!
*/
public $def;
/**
- * Indexed array of definitions
+ * Indexed array of definitions.
+ * @type HTMLPurifier_Definition[]
*/
protected $definitions;
/**
- * Bool indicator whether or not config is finalized
+ * Whether or not config is finalized.
+ * @type bool
*/
protected $finalized = false;
/**
* Property list containing configuration directives.
+ * @type array
*/
protected $plist;
/**
- * Whether or not a set is taking place due to an
- * alias lookup.
+ * Whether or not a set is taking place due to an alias lookup.
+ * @type bool
*/
private $aliasMode;
/**
- * Set to false if you do not want line and file numbers in errors
+ * Set to false if you do not want line and file numbers in errors.
* (useful when unit testing). This will also compress some errors
* and exceptions.
+ * @type bool
*/
public $chatty = true;
/**
* Current lock; only gets to this namespace are allowed.
+ * @type string
*/
private $lock;
/**
- * @param $definition HTMLPurifier_ConfigSchema that defines what directives
- * are allowed.
+ * Constructor
+ * @param HTMLPurifier_ConfigSchema $definition ConfigSchema that defines
+ * what directives are allowed.
+ * @param HTMLPurifier_PropertyList $parent
*/
- public function __construct($definition, $parent = null) {
+ public function __construct($definition, $parent = null)
+ {
$parent = $parent ? $parent : $definition->defaultPlist;
$this->plist = new HTMLPurifier_PropertyList($parent);
$this->def = $definition; // keep a copy around for checking
@@ -1466,10 +1832,11 @@ public function __construct($definition, $parent = null) {
* object. Can be: a HTMLPurifier_Config() object,
* an array of directives based on loadArray(),
* or a string filename of an ini file.
- * @param HTMLPurifier_ConfigSchema Schema object
- * @return Configured HTMLPurifier_Config object
+ * @param HTMLPurifier_ConfigSchema $schema Schema object
+ * @return HTMLPurifier_Config Configured object
*/
- public static function create($config, $schema = null) {
+ public static function create($config, $schema = null)
+ {
if ($config instanceof HTMLPurifier_Config) {
// pass-through
return $config;
@@ -1479,57 +1846,79 @@ public static function create($config, $schema = null) {
} else {
$ret = new HTMLPurifier_Config($schema);
}
- if (is_string($config)) $ret->loadIni($config);
- elseif (is_array($config)) $ret->loadArray($config);
+ if (is_string($config)) {
+ $ret->loadIni($config);
+ } elseif (is_array($config)) $ret->loadArray($config);
return $ret;
}
/**
* Creates a new config object that inherits from a previous one.
- * @param HTMLPurifier_Config $config Configuration object to inherit
- * from.
+ * @param HTMLPurifier_Config $config Configuration object to inherit from.
* @return HTMLPurifier_Config object with $config as its parent.
*/
- public static function inherit(HTMLPurifier_Config $config) {
+ public static function inherit(HTMLPurifier_Config $config)
+ {
return new HTMLPurifier_Config($config->def, $config->plist);
}
/**
* Convenience constructor that creates a default configuration object.
- * @return Default HTMLPurifier_Config object.
+ * @return HTMLPurifier_Config default object.
*/
- public static function createDefault() {
+ public static function createDefault()
+ {
$definition = HTMLPurifier_ConfigSchema::instance();
$config = new HTMLPurifier_Config($definition);
return $config;
}
/**
- * Retreives a value from the configuration.
- * @param $key String key
+ * Retrieves a value from the configuration.
+ *
+ * @param string $key String key
+ * @param mixed $a
+ *
+ * @return mixed
*/
- public function get($key, $a = null) {
+ public function get($key, $a = null)
+ {
if ($a !== null) {
- $this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING);
+ $this->triggerError(
+ "Using deprecated API: use \$config->get('$key.$a') instead",
+ E_USER_WARNING
+ );
$key = "$key.$a";
}
- if (!$this->finalized) $this->autoFinalize();
+ if (!$this->finalized) {
+ $this->autoFinalize();
+ }
if (!isset($this->def->info[$key])) {
// can't add % due to SimpleTest bug
- $this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
- E_USER_WARNING);
+ $this->triggerError(
+ 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
+ E_USER_WARNING
+ );
return;
}
if (isset($this->def->info[$key]->isAlias)) {
$d = $this->def->info[$key];
- $this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key,
- E_USER_ERROR);
+ $this->triggerError(
+ 'Cannot get value from aliased directive, use real name ' . $d->key,
+ E_USER_ERROR
+ );
return;
}
if ($this->lock) {
list($ns) = explode('.', $key);
if ($ns !== $this->lock) {
- $this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR);
+ $this->triggerError(
+ 'Cannot get value of namespace ' . $ns . ' when lock for ' .
+ $this->lock .
+ ' is active, this probably indicates a Definition setup method ' .
+ 'is accessing directives that are not within its namespace',
+ E_USER_ERROR
+ );
return;
}
}
@@ -1537,15 +1926,24 @@ public function get($key, $a = null) {
}
/**
- * Retreives an array of directives to values from a given namespace
- * @param $namespace String namespace
+ * Retrieves an array of directives to values from a given namespace
+ *
+ * @param string $namespace String namespace
+ *
+ * @return array
*/
- public function getBatch($namespace) {
- if (!$this->finalized) $this->autoFinalize();
+ public function getBatch($namespace)
+ {
+ if (!$this->finalized) {
+ $this->autoFinalize();
+ }
$full = $this->getAll();
if (!isset($full[$namespace])) {
- $this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace),
- E_USER_WARNING);
+ $this->triggerError(
+ 'Cannot retrieve undefined namespace ' .
+ htmlspecialchars($namespace),
+ E_USER_WARNING
+ );
return;
}
return $full[$namespace];
@@ -1554,11 +1952,15 @@ public function getBatch($namespace) {
/**
* Returns a SHA-1 signature of a segment of the configuration object
* that uniquely identifies that particular configuration
+ *
+ * @param string $namespace Namespace to get serial for
+ *
+ * @return string
* @note Revision is handled specially and is removed from the batch
* before processing!
- * @param $namespace Namespace to get serial for
*/
- public function getBatchSerial($namespace) {
+ public function getBatchSerial($namespace)
+ {
if (empty($this->serials[$namespace])) {
$batch = $this->getBatch($namespace);
unset($batch['DefinitionRev']);
@@ -1570,8 +1972,11 @@ public function getBatchSerial($namespace) {
/**
* Returns a SHA-1 signature for the entire configuration object
* that uniquely identifies that particular configuration
+ *
+ * @return string
*/
- public function getSerial() {
+ public function getSerial()
+ {
if (empty($this->serial)) {
$this->serial = sha1(serialize($this->getAll()));
}
@@ -1580,10 +1985,14 @@ public function getSerial() {
/**
* Retrieves all directives, organized by namespace
+ *
* @warning This is a pretty inefficient function, avoid if you can
*/
- public function getAll() {
- if (!$this->finalized) $this->autoFinalize();
+ public function getAll()
+ {
+ if (!$this->finalized) {
+ $this->autoFinalize();
+ }
$ret = array();
foreach ($this->plist->squash() as $name => $value) {
list($ns, $key) = explode('.', $name, 2);
@@ -1594,10 +2003,13 @@ public function getAll() {
/**
* Sets a value to configuration.
- * @param $key String key
- * @param $value Mixed value
+ *
+ * @param string $key key
+ * @param mixed $value value
+ * @param mixed $a
*/
- public function set($key, $value, $a = null) {
+ public function set($key, $value, $a = null)
+ {
if (strpos($key, '.') === false) {
$namespace = $key;
$directive = $value;
@@ -1607,18 +2019,25 @@ public function set($key, $value, $a = null) {
} else {
list($namespace) = explode('.', $key);
}
- if ($this->isFinalized('Cannot set directive after finalization')) return;
+ if ($this->isFinalized('Cannot set directive after finalization')) {
+ return;
+ }
if (!isset($this->def->info[$key])) {
- $this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
- E_USER_WARNING);
+ $this->triggerError(
+ 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
+ E_USER_WARNING
+ );
return;
}
$def = $this->def->info[$key];
if (isset($def->isAlias)) {
if ($this->aliasMode) {
- $this->triggerError('Double-aliases not allowed, please fix '.
- 'ConfigSchema bug with' . $key, E_USER_ERROR);
+ $this->triggerError(
+ 'Double-aliases not allowed, please fix '.
+ 'ConfigSchema bug with' . $key,
+ E_USER_ERROR
+ );
return;
}
$this->aliasMode = true;
@@ -1642,7 +2061,11 @@ public function set($key, $value, $a = null) {
try {
$value = $this->parser->parse($value, $type, $allow_null);
} catch (HTMLPurifier_VarParserException $e) {
- $this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING);
+ $this->triggerError(
+ 'Value for ' . $key . ' is of invalid type, should be ' .
+ HTMLPurifier_VarParser::getTypeName($type),
+ E_USER_WARNING
+ );
return;
}
if (is_string($value) && is_object($def)) {
@@ -1652,8 +2075,11 @@ public function set($key, $value, $a = null) {
}
// check to see if the value is allowed
if (isset($def->allowed) && !isset($def->allowed[$value])) {
- $this->triggerError('Value not supported, valid values are: ' .
- $this->_listify($def->allowed), E_USER_WARNING);
+ $this->triggerError(
+ 'Value not supported, valid values are: ' .
+ $this->_listify($def->allowed),
+ E_USER_WARNING
+ );
return;
}
}
@@ -1671,63 +2097,83 @@ public function set($key, $value, $a = null) {
/**
* Convenience function for error reporting
+ *
+ * @param array $lookup
+ *
+ * @return string
*/
- private function _listify($lookup) {
+ private function _listify($lookup)
+ {
$list = array();
- foreach ($lookup as $name => $b) $list[] = $name;
+ foreach ($lookup as $name => $b) {
+ $list[] = $name;
+ }
return implode(', ', $list);
}
/**
* Retrieves object reference to the HTML definition.
- * @param $raw Return a copy that has not been setup yet. Must be
+ *
+ * @param bool $raw Return a copy that has not been setup yet. Must be
* called before it's been setup, otherwise won't work.
- * @param $optimized If true, this method may return null, to
+ * @param bool $optimized If true, this method may return null, to
* indicate that a cached version of the modified
* definition object is available and no further edits
* are necessary. Consider using
* maybeGetRawHTMLDefinition, which is more explicitly
* named, instead.
+ *
+ * @return HTMLPurifier_HTMLDefinition
*/
- public function getHTMLDefinition($raw = false, $optimized = false) {
+ public function getHTMLDefinition($raw = false, $optimized = false)
+ {
return $this->getDefinition('HTML', $raw, $optimized);
}
/**
* Retrieves object reference to the CSS definition
- * @param $raw Return a copy that has not been setup yet. Must be
+ *
+ * @param bool $raw Return a copy that has not been setup yet. Must be
* called before it's been setup, otherwise won't work.
- * @param $optimized If true, this method may return null, to
+ * @param bool $optimized If true, this method may return null, to
* indicate that a cached version of the modified
* definition object is available and no further edits
* are necessary. Consider using
* maybeGetRawCSSDefinition, which is more explicitly
* named, instead.
+ *
+ * @return HTMLPurifier_CSSDefinition
*/
- public function getCSSDefinition($raw = false, $optimized = false) {
+ public function getCSSDefinition($raw = false, $optimized = false)
+ {
return $this->getDefinition('CSS', $raw, $optimized);
}
/**
* Retrieves object reference to the URI definition
- * @param $raw Return a copy that has not been setup yet. Must be
+ *
+ * @param bool $raw Return a copy that has not been setup yet. Must be
* called before it's been setup, otherwise won't work.
- * @param $optimized If true, this method may return null, to
+ * @param bool $optimized If true, this method may return null, to
* indicate that a cached version of the modified
* definition object is available and no further edits
* are necessary. Consider using
* maybeGetRawURIDefinition, which is more explicitly
* named, instead.
+ *
+ * @return HTMLPurifier_URIDefinition
*/
- public function getURIDefinition($raw = false, $optimized = false) {
+ public function getURIDefinition($raw = false, $optimized = false)
+ {
return $this->getDefinition('URI', $raw, $optimized);
}
/**
* Retrieves a definition
- * @param $type Type of definition: HTML, CSS, etc
- * @param $raw Whether or not definition should be returned raw
- * @param $optimized Only has an effect when $raw is true. Whether
+ *
+ * @param string $type Type of definition: HTML, CSS, etc
+ * @param bool $raw Whether or not definition should be returned raw
+ * @param bool $optimized Only has an effect when $raw is true. Whether
* or not to return null if the result is already present in
* the cache. This is off by default for backwards
* compatibility reasons, but you need to do things this
@@ -1735,12 +2181,18 @@ public function getURIDefinition($raw = false, $optimized = false) {
* Check out enduser-customize.html for more details.
* We probably won't ever change this default, as much as the
* maybe semantics is the "right thing to do."
+ *
+ * @throws HTMLPurifier_Exception
+ * @return HTMLPurifier_Definition
*/
- public function getDefinition($type, $raw = false, $optimized = false) {
+ public function getDefinition($type, $raw = false, $optimized = false)
+ {
if ($optimized && !$raw) {
throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
}
- if (!$this->finalized) $this->autoFinalize();
+ if (!$this->finalized) {
+ $this->autoFinalize();
+ }
// temporarily suspend locks, so we can handle recursive definition calls
$lock = $this->lock;
$this->lock = null;
@@ -1758,7 +2210,9 @@ public function getDefinition($type, $raw = false, $optimized = false) {
return $def;
} else {
$def->setup($this);
- if ($def->optimized) $cache->add($def, $this);
+ if ($def->optimized) {
+ $cache->add($def, $this);
+ }
return $def;
}
}
@@ -1787,23 +2241,36 @@ public function getDefinition($type, $raw = false, $optimized = false) {
if ($optimized) {
if (is_null($this->get($type . '.DefinitionID'))) {
// fatally error out if definition ID not set
- throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID");
+ throw new HTMLPurifier_Exception(
+ "Cannot retrieve raw version without specifying %$type.DefinitionID"
+ );
}
}
if (!empty($this->definitions[$type])) {
$def = $this->definitions[$type];
if ($def->setup && !$optimized) {
- $extra = $this->chatty ? " (try moving this code block earlier in your initialization)" : "";
- throw new HTMLPurifier_Exception("Cannot retrieve raw definition after it has already been setup" . $extra);
+ $extra = $this->chatty ?
+ " (try moving this code block earlier in your initialization)" :
+ "";
+ throw new HTMLPurifier_Exception(
+ "Cannot retrieve raw definition after it has already been setup" .
+ $extra
+ );
}
if ($def->optimized === null) {
$extra = $this->chatty ? " (try flushing your cache)" : "";
- throw new HTMLPurifier_Exception("Optimization status of definition is unknown" . $extra);
+ throw new HTMLPurifier_Exception(
+ "Optimization status of definition is unknown" . $extra
+ );
}
if ($def->optimized !== $optimized) {
$msg = $optimized ? "optimized" : "unoptimized";
- $extra = $this->chatty ? " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" : "";
- throw new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra);
+ $extra = $this->chatty ?
+ " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
+ : "";
+ throw new HTMLPurifier_Exception(
+ "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
+ );
}
}
// check if definition was in memory
@@ -1836,9 +2303,22 @@ public function getDefinition($type, $raw = false, $optimized = false) {
if (!$optimized) {
if (!is_null($this->get($type . '.DefinitionID'))) {
if ($this->chatty) {
- $this->triggerError("Due to a documentation error in previous version of HTML Purifier, your definitions are not being cached. If this is OK, you can remove the %$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, modify your code to use maybeGetRawDefinition, and test if the returned value is null before making any edits (if it is null, that means that a cached version is available, and no raw operations are necessary). See Customize for more details", E_USER_WARNING);
+ $this->triggerError(
+ 'Due to a documentation error in previous version of HTML Purifier, your ' .
+ 'definitions are not being cached. If this is OK, you can remove the ' .
+ '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
+ 'modify your code to use maybeGetRawDefinition, and test if the returned ' .
+ 'value is null before making any edits (if it is null, that means that a ' .
+ 'cached version is available, and no raw operations are necessary). See ' .
+ '' .
+ 'Customize for more details',
+ E_USER_WARNING
+ );
} else {
- $this->triggerError("Useless DefinitionID declaration", E_USER_WARNING);
+ $this->triggerError(
+ "Useless DefinitionID declaration",
+ E_USER_WARNING
+ );
}
}
}
@@ -1850,7 +2330,16 @@ public function getDefinition($type, $raw = false, $optimized = false) {
throw new HTMLPurifier_Exception("The impossible happened!");
}
- private function initDefinition($type) {
+ /**
+ * Initialise definition
+ *
+ * @param string $type What type of definition to create
+ *
+ * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
+ * @throws HTMLPurifier_Exception
+ */
+ private function initDefinition($type)
+ {
// quick checks failed, let's create the object
if ($type == 'HTML') {
$def = new HTMLPurifier_HTMLDefinition();
@@ -1859,35 +2348,45 @@ private function initDefinition($type) {
} elseif ($type == 'URI') {
$def = new HTMLPurifier_URIDefinition();
} else {
- throw new HTMLPurifier_Exception("Definition of $type type not supported");
+ throw new HTMLPurifier_Exception(
+ "Definition of $type type not supported"
+ );
}
$this->definitions[$type] = $def;
return $def;
}
- public function maybeGetRawDefinition($name) {
+ public function maybeGetRawDefinition($name)
+ {
return $this->getDefinition($name, true, true);
}
- public function maybeGetRawHTMLDefinition() {
+ public function maybeGetRawHTMLDefinition()
+ {
return $this->getDefinition('HTML', true, true);
}
- public function maybeGetRawCSSDefinition() {
+ public function maybeGetRawCSSDefinition()
+ {
return $this->getDefinition('CSS', true, true);
}
- public function maybeGetRawURIDefinition() {
+ public function maybeGetRawURIDefinition()
+ {
return $this->getDefinition('URI', true, true);
}
/**
* Loads configuration values from an array with the following structure:
* Namespace.Directive => Value
- * @param $config_array Configuration associative array
+ *
+ * @param array $config_array Configuration associative array
*/
- public function loadArray($config_array) {
- if ($this->isFinalized('Cannot load directives after finalization')) return;
+ public function loadArray($config_array)
+ {
+ if ($this->isFinalized('Cannot load directives after finalization')) {
+ return;
+ }
foreach ($config_array as $key => $value) {
$key = str_replace('_', '.', $key);
if (strpos($key, '.') !== false) {
@@ -1895,8 +2394,8 @@ public function loadArray($config_array) {
} else {
$namespace = $key;
$namespace_values = $value;
- foreach ($namespace_values as $directive => $value) {
- $this->set($namespace .'.'. $directive, $value);
+ foreach ($namespace_values as $directive => $value2) {
+ $this->set($namespace .'.'. $directive, $value2);
}
}
}
@@ -1906,40 +2405,55 @@ public function loadArray($config_array) {
* Returns a list of array(namespace, directive) for all directives
* that are allowed in a web-form context as per an allowed
* namespaces/directives list.
- * @param $allowed List of allowed namespaces/directives
+ *
+ * @param array $allowed List of allowed namespaces/directives
+ * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+ *
+ * @return array
*/
- public static function getAllowedDirectivesForForm($allowed, $schema = null) {
+ public static function getAllowedDirectivesForForm($allowed, $schema = null)
+ {
if (!$schema) {
$schema = HTMLPurifier_ConfigSchema::instance();
}
if ($allowed !== true) {
- if (is_string($allowed)) $allowed = array($allowed);
- $allowed_ns = array();
- $allowed_directives = array();
- $blacklisted_directives = array();
- foreach ($allowed as $ns_or_directive) {
- if (strpos($ns_or_directive, '.') !== false) {
- // directive
- if ($ns_or_directive[0] == '-') {
- $blacklisted_directives[substr($ns_or_directive, 1)] = true;
- } else {
- $allowed_directives[$ns_or_directive] = true;
- }
- } else {
- // namespace
- $allowed_ns[$ns_or_directive] = true;
- }
- }
+ if (is_string($allowed)) {
+ $allowed = array($allowed);
+ }
+ $allowed_ns = array();
+ $allowed_directives = array();
+ $blacklisted_directives = array();
+ foreach ($allowed as $ns_or_directive) {
+ if (strpos($ns_or_directive, '.') !== false) {
+ // directive
+ if ($ns_or_directive[0] == '-') {
+ $blacklisted_directives[substr($ns_or_directive, 1)] = true;
+ } else {
+ $allowed_directives[$ns_or_directive] = true;
+ }
+ } else {
+ // namespace
+ $allowed_ns[$ns_or_directive] = true;
+ }
+ }
}
$ret = array();
foreach ($schema->info as $key => $def) {
list($ns, $directive) = explode('.', $key, 2);
if ($allowed !== true) {
- if (isset($blacklisted_directives["$ns.$directive"])) continue;
- if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
+ if (isset($blacklisted_directives["$ns.$directive"])) {
+ continue;
+ }
+ if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
+ continue;
+ }
+ }
+ if (isset($def->isAlias)) {
+ continue;
+ }
+ if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
+ continue;
}
- if (isset($def->isAlias)) continue;
- if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
$ret[] = array($ns, $directive);
}
return $ret;
@@ -1948,13 +2462,17 @@ public static function getAllowedDirectivesForForm($allowed, $schema = null) {
/**
* Loads configuration values from $_GET/$_POST that were posted
* via ConfigForm
- * @param $array $_GET or $_POST array to import
- * @param $index Index/name that the config variables are in
- * @param $allowed List of allowed namespaces/directives
- * @param $mq_fix Boolean whether or not to enable magic quotes fix
- * @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy
+ *
+ * @param array $array $_GET or $_POST array to import
+ * @param string|bool $index Index/name that the config variables are in
+ * @param array|bool $allowed List of allowed namespaces/directives
+ * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
+ * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+ *
+ * @return mixed
*/
- public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
+ public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
+ {
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
$config = HTMLPurifier_Config::create($ret, $schema);
return $config;
@@ -1962,9 +2480,14 @@ public static function loadArrayFromForm($array, $index = false, $allowed = true
/**
* Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
- * @note Same parameters as loadArrayFromForm
+ *
+ * @param array $array $_GET or $_POST array to import
+ * @param string|bool $index Index/name that the config variables are in
+ * @param array|bool $allowed List of allowed namespaces/directives
+ * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
*/
- public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) {
+ public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
+ {
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
$this->loadArray($ret);
}
@@ -1972,9 +2495,20 @@ public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_
/**
* Prepares an array from a form into something usable for the more
* strict parts of HTMLPurifier_Config
+ *
+ * @param array $array $_GET or $_POST array to import
+ * @param string|bool $index Index/name that the config variables are in
+ * @param array|bool $allowed List of allowed namespaces/directives
+ * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
+ * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+ *
+ * @return array
*/
- public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
- if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
+ public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
+ {
+ if ($index !== false) {
+ $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
+ }
$mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
@@ -1986,7 +2520,9 @@ public static function prepareArrayFromForm($array, $index = false, $allowed = t
$ret[$ns][$directive] = null;
continue;
}
- if (!isset($array[$skey])) continue;
+ if (!isset($array[$skey])) {
+ continue;
+ }
$value = $mq ? stripslashes($array[$skey]) : $array[$skey];
$ret[$ns][$directive] = $value;
}
@@ -1995,19 +2531,27 @@ public static function prepareArrayFromForm($array, $index = false, $allowed = t
/**
* Loads configuration values from an ini file
- * @param $filename Name of ini file
+ *
+ * @param string $filename Name of ini file
*/
- public function loadIni($filename) {
- if ($this->isFinalized('Cannot load directives after finalization')) return;
+ public function loadIni($filename)
+ {
+ if ($this->isFinalized('Cannot load directives after finalization')) {
+ return;
+ }
$array = parse_ini_file($filename, true);
$this->loadArray($array);
}
/**
* Checks whether or not the configuration object is finalized.
- * @param $error String error message, or false for no error
+ *
+ * @param string|bool $error String error message, or false for no error
+ *
+ * @return bool
*/
- public function isFinalized($error = false) {
+ public function isFinalized($error = false)
+ {
if ($this->finalized && $error) {
$this->triggerError($error, E_USER_ERROR);
}
@@ -2018,7 +2562,8 @@ public function isFinalized($error = false) {
* Finalizes configuration only if auto finalize is on and not
* already finalized
*/
- public function autoFinalize() {
+ public function autoFinalize()
+ {
if ($this->autoFinalize) {
$this->finalize();
} else {
@@ -2029,7 +2574,8 @@ public function autoFinalize() {
/**
* Finalizes a configuration object, prohibiting further change
*/
- public function finalize() {
+ public function finalize()
+ {
$this->finalized = true;
$this->parser = null;
}
@@ -2037,8 +2583,12 @@ public function finalize() {
/**
* Produces a nicely formatted error message by supplying the
* stack frame information OUTSIDE of HTMLPurifier_Config.
+ *
+ * @param string $msg An error message
+ * @param int $no An error number
*/
- protected function triggerError($msg, $no) {
+ protected function triggerError($msg, $no)
+ {
// determine previous stack frame
$extra = '';
if ($this->chatty) {
@@ -2060,8 +2610,11 @@ protected function triggerError($msg, $no) {
/**
* Returns a serialized form of the configuration object that can
* be reconstituted.
+ *
+ * @return string
*/
- public function serialize() {
+ public function serialize()
+ {
$this->getDefinition('HTML');
$this->getDefinition('CSS');
$this->getDefinition('URI');
@@ -2077,21 +2630,24 @@ public function serialize() {
/**
* Configuration definition, defines directives and their defaults.
*/
-class HTMLPurifier_ConfigSchema {
-
+class HTMLPurifier_ConfigSchema
+{
/**
* Defaults of the directives and namespaces.
+ * @type array
* @note This shares the exact same structure as HTMLPurifier_Config::$conf
*/
public $defaults = array();
/**
* The default property list. Do not edit this property list.
+ * @type array
*/
public $defaultPlist;
/**
- * Definition of the directives. The structure of this is:
+ * Definition of the directives.
+ * The structure of this is:
*
* array(
* 'Namespace' => array(
@@ -2118,22 +2674,27 @@ class HTMLPurifier_ConfigSchema {
* This class is friendly with HTMLPurifier_Config. If you need introspection
* about the schema, you're better of using the ConfigSchema_Interchange,
* which uses more memory but has much richer information.
+ * @type array
*/
public $info = array();
/**
* Application-wide singleton
+ * @type HTMLPurifier_ConfigSchema
*/
- static protected $singleton;
+ protected static $singleton;
- public function __construct() {
+ public function __construct()
+ {
$this->defaultPlist = new HTMLPurifier_PropertyList();
}
/**
* Unserializes the default ConfigSchema.
+ * @return HTMLPurifier_ConfigSchema
*/
- public static function makeFromSerial() {
+ public static function makeFromSerial()
+ {
$contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
$r = unserialize($contents);
if (!$r) {
@@ -2145,8 +2706,11 @@ public static function makeFromSerial() {
/**
* Retrieves an instance of the application-wide configuration definition.
+ * @param HTMLPurifier_ConfigSchema $prototype
+ * @return HTMLPurifier_ConfigSchema
*/
- public static function instance($prototype = null) {
+ public static function instance($prototype = null)
+ {
if ($prototype !== null) {
HTMLPurifier_ConfigSchema::$singleton = $prototype;
} elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
@@ -2160,17 +2724,19 @@ public static function instance($prototype = null) {
* @warning Will fail of directive's namespace is defined.
* @warning This method's signature is slightly different from the legacy
* define() static method! Beware!
- * @param $namespace Namespace the directive is in
- * @param $name Key of directive
- * @param $default Default value of directive
- * @param $type Allowed type of the directive. See
+ * @param string $key Name of directive
+ * @param mixed $default Default value of directive
+ * @param string $type Allowed type of the directive. See
* HTMLPurifier_DirectiveDef::$type for allowed values
- * @param $allow_null Whether or not to allow null values
+ * @param bool $allow_null Whether or not to allow null values
*/
- public function add($key, $default, $type, $allow_null) {
+ public function add($key, $default, $type, $allow_null)
+ {
$obj = new stdclass();
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
- if ($allow_null) $obj->allow_null = true;
+ if ($allow_null) {
+ $obj->allow_null = true;
+ }
$this->info[$key] = $obj;
$this->defaults[$key] = $default;
$this->defaultPlist->set($key, $default);
@@ -2181,11 +2747,11 @@ public function add($key, $default, $type, $allow_null) {
*
* Directive value aliases are convenient for developers because it lets
* them set a directive to several values and get the same result.
- * @param $namespace Directive's namespace
- * @param $name Name of Directive
- * @param $aliases Hash of aliased values to the real alias
+ * @param string $key Name of Directive
+ * @param array $aliases Hash of aliased values to the real alias
*/
- public function addValueAliases($key, $aliases) {
+ public function addValueAliases($key, $aliases)
+ {
if (!isset($this->info[$key]->aliases)) {
$this->info[$key]->aliases = array();
}
@@ -2198,22 +2764,21 @@ public function addValueAliases($key, $aliases) {
* Defines a set of allowed values for a directive.
* @warning This is slightly different from the corresponding static
* method definition.
- * @param $namespace Namespace of directive
- * @param $name Name of directive
- * @param $allowed Lookup array of allowed values
+ * @param string $key Name of directive
+ * @param array $allowed Lookup array of allowed values
*/
- public function addAllowedValues($key, $allowed) {
+ public function addAllowedValues($key, $allowed)
+ {
$this->info[$key]->allowed = $allowed;
}
/**
* Defines a directive alias for backwards compatibility
- * @param $namespace
- * @param $name Directive that will be aliased
- * @param $new_namespace
- * @param $new_name Directive that the alias will be to
+ * @param string $key Directive that will be aliased
+ * @param string $new_key Directive that the alias will be to
*/
- public function addAlias($key, $new_key) {
+ public function addAlias($key, $new_key)
+ {
$obj = new stdclass;
$obj->key = $new_key;
$obj->isAlias = true;
@@ -2223,7 +2788,8 @@ public function addAlias($key, $new_key) {
/**
* Replaces any stdclass that only has the type property with type integer.
*/
- public function postProcess() {
+ public function postProcess()
+ {
foreach ($this->info as $key => $v) {
if (count((array) $v) == 1) {
$this->info[$key] = $v->type;
@@ -2232,7 +2798,6 @@ public function postProcess() {
}
}
}
-
}
@@ -2246,35 +2811,42 @@ class HTMLPurifier_ContentSets
{
/**
- * List of content set strings (pipe seperators) indexed by name.
+ * List of content set strings (pipe separators) indexed by name.
+ * @type array
*/
public $info = array();
/**
* List of content set lookups (element => true) indexed by name.
+ * @type array
* @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
*/
public $lookup = array();
/**
- * Synchronized list of defined content sets (keys of info)
+ * Synchronized list of defined content sets (keys of info).
+ * @type array
*/
protected $keys = array();
/**
- * Synchronized list of defined content values (values of info)
+ * Synchronized list of defined content values (values of info).
+ * @type array
*/
protected $values = array();
/**
* Merges in module's content sets, expands identifiers in the content
* sets and populates the keys, values and lookup member variables.
- * @param $modules List of HTMLPurifier_HTMLModule
+ * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule
*/
- public function __construct($modules) {
- if (!is_array($modules)) $modules = array($modules);
+ public function __construct($modules)
+ {
+ if (!is_array($modules)) {
+ $modules = array($modules);
+ }
// populate content_sets based on module hints
// sorry, no way of overloading
- foreach ($modules as $module_i => $module) {
+ foreach ($modules as $module) {
foreach ($module->content_sets as $key => $value) {
$temp = $this->convertToLookup($value);
if (isset($this->lookup[$key])) {
@@ -2309,11 +2881,14 @@ public function __construct($modules) {
/**
* Accepts a definition; generates and assigns a ChildDef for it
- * @param $def HTMLPurifier_ElementDef reference
- * @param $module Module that defined the ElementDef
+ * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference
+ * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
*/
- public function generateChildDef(&$def, $module) {
- if (!empty($def->child)) return; // already done!
+ public function generateChildDef(&$def, $module)
+ {
+ if (!empty($def->child)) { // already done!
+ return;
+ }
$content_model = $def->content_model;
if (is_string($content_model)) {
// Assume that $this->keys is alphanumeric
@@ -2328,7 +2903,8 @@ public function generateChildDef(&$def, $module) {
$def->child = $this->getChildDef($def, $module);
}
- public function generateChildDefCallback($matches) {
+ public function generateChildDefCallback($matches)
+ {
return $this->info[$matches[0]];
}
@@ -2337,10 +2913,12 @@ public function generateChildDefCallback($matches) {
* member variables in HTMLPurifier_ElementDef
* @note This will also defer to modules for custom HTMLPurifier_ChildDef
* subclasses that need content set expansion
- * @param $def HTMLPurifier_ElementDef to have ChildDef extracted
+ * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted
+ * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef
* @return HTMLPurifier_ChildDef corresponding to ElementDef
*/
- public function getChildDef($def, $module) {
+ public function getChildDef($def, $module)
+ {
$value = $def->content_model;
if (is_object($value)) {
trigger_error(
@@ -2365,7 +2943,9 @@ public function getChildDef($def, $module) {
if ($module->defines_child_def) { // save a func call
$return = $module->getChildDef($def);
}
- if ($return !== false) return $return;
+ if ($return !== false) {
+ return $return;
+ }
// error-out
trigger_error(
'Could not determine which ChildDef class to instantiate',
@@ -2377,18 +2957,18 @@ public function getChildDef($def, $module) {
/**
* Converts a string list of elements separated by pipes into
* a lookup array.
- * @param $string List of elements
- * @return Lookup array of elements
+ * @param string $string List of elements
+ * @return array Lookup array of elements
*/
- protected function convertToLookup($string) {
+ protected function convertToLookup($string)
+ {
$array = explode('|', str_replace(' ', '', $string));
$ret = array();
- foreach ($array as $i => $k) {
+ foreach ($array as $k) {
$ret[$k] = true;
}
return $ret;
}
-
}
@@ -2407,18 +2987,22 @@ class HTMLPurifier_Context
/**
* Private array that stores the references.
+ * @type array
*/
private $_storage = array();
/**
* Registers a variable into the context.
- * @param $name String name
- * @param $ref Reference to variable to be registered
+ * @param string $name String name
+ * @param mixed $ref Reference to variable to be registered
*/
- public function register($name, &$ref) {
- if (isset($this->_storage[$name])) {
- trigger_error("Name $name produces collision, cannot re-register",
- E_USER_ERROR);
+ public function register($name, &$ref)
+ {
+ if (array_key_exists($name, $this->_storage)) {
+ trigger_error(
+ "Name $name produces collision, cannot re-register",
+ E_USER_ERROR
+ );
return;
}
$this->_storage[$name] =& $ref;
@@ -2426,14 +3010,18 @@ public function register($name, &$ref) {
/**
* Retrieves a variable reference from the context.
- * @param $name String name
- * @param $ignore_error Boolean whether or not to ignore error
+ * @param string $name String name
+ * @param bool $ignore_error Boolean whether or not to ignore error
+ * @return mixed
*/
- public function &get($name, $ignore_error = false) {
- if (!isset($this->_storage[$name])) {
+ public function &get($name, $ignore_error = false)
+ {
+ if (!array_key_exists($name, $this->_storage)) {
if (!$ignore_error) {
- trigger_error("Attempted to retrieve non-existent variable $name",
- E_USER_ERROR);
+ trigger_error(
+ "Attempted to retrieve non-existent variable $name",
+ E_USER_ERROR
+ );
}
$var = null; // so we can return by reference
return $var;
@@ -2442,13 +3030,16 @@ public function &get($name, $ignore_error = false) {
}
/**
- * Destorys a variable in the context.
- * @param $name String name
+ * Destroys a variable in the context.
+ * @param string $name String name
*/
- public function destroy($name) {
- if (!isset($this->_storage[$name])) {
- trigger_error("Attempted to destroy non-existent variable $name",
- E_USER_ERROR);
+ public function destroy($name)
+ {
+ if (!array_key_exists($name, $this->_storage)) {
+ trigger_error(
+ "Attempted to destroy non-existent variable $name",
+ E_USER_ERROR
+ );
return;
}
unset($this->_storage[$name]);
@@ -2456,22 +3047,24 @@ public function destroy($name) {
/**
* Checks whether or not the variable exists.
- * @param $name String name
+ * @param string $name String name
+ * @return bool
*/
- public function exists($name) {
- return isset($this->_storage[$name]);
+ public function exists($name)
+ {
+ return array_key_exists($name, $this->_storage);
}
/**
* Loads a series of variables from an associative array
- * @param $context_array Assoc array of variables to load
+ * @param array $context_array Assoc array of variables to load
*/
- public function loadArray($context_array) {
+ public function loadArray($context_array)
+ {
foreach ($context_array as $key => $discard) {
$this->register($key, $context_array[$key]);
}
}
-
}
@@ -2488,22 +3081,27 @@ public function loadArray($context_array) {
*/
abstract class HTMLPurifier_DefinitionCache
{
-
+ /**
+ * @type string
+ */
public $type;
/**
- * @param $name Type of definition objects this instance of the
+ * @param string $type Type of definition objects this instance of the
* cache will handle.
*/
- public function __construct($type) {
+ public function __construct($type)
+ {
$this->type = $type;
}
/**
* Generates a unique identifier for a particular configuration
- * @param Instance of HTMLPurifier_Config
+ * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config
+ * @return string
*/
- public function generateKey($config) {
+ public function generateKey($config)
+ {
return $config->version . ',' . // possibly replace with function calls
$config->getBatchSerial($this->type) . ',' .
$config->get($this->type . '.DefinitionRev');
@@ -2512,30 +3110,37 @@ public function generateKey($config) {
/**
* Tests whether or not a key is old with respect to the configuration's
* version and revision number.
- * @param $key Key to test
- * @param $config Instance of HTMLPurifier_Config to test against
+ * @param string $key Key to test
+ * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against
+ * @return bool
*/
- public function isOld($key, $config) {
- if (substr_count($key, ',') < 2) return true;
+ public function isOld($key, $config)
+ {
+ if (substr_count($key, ',') < 2) {
+ return true;
+ }
list($version, $hash, $revision) = explode(',', $key, 3);
$compare = version_compare($version, $config->version);
// version mismatch, is always old
- if ($compare != 0) return true;
+ if ($compare != 0) {
+ return true;
+ }
// versions match, ids match, check revision number
- if (
- $hash == $config->getBatchSerial($this->type) &&
- $revision < $config->get($this->type . '.DefinitionRev')
- ) return true;
+ if ($hash == $config->getBatchSerial($this->type) &&
+ $revision < $config->get($this->type . '.DefinitionRev')) {
+ return true;
+ }
return false;
}
/**
* Checks if a definition's type jives with the cache's type
* @note Throws an error on failure
- * @param $def Definition object to check
- * @return Boolean true if good, false if not
+ * @param HTMLPurifier_Definition $def Definition object to check
+ * @return bool true if good, false if not
*/
- public function checkDefType($def) {
+ public function checkDefType($def)
+ {
if ($def->type !== $this->type) {
trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
return false;
@@ -2545,31 +3150,40 @@ public function checkDefType($def) {
/**
* Adds a definition object to the cache
+ * @param HTMLPurifier_Definition $def
+ * @param HTMLPurifier_Config $config
*/
abstract public function add($def, $config);
/**
* Unconditionally saves a definition object to the cache
+ * @param HTMLPurifier_Definition $def
+ * @param HTMLPurifier_Config $config
*/
abstract public function set($def, $config);
/**
* Replace an object in the cache
+ * @param HTMLPurifier_Definition $def
+ * @param HTMLPurifier_Config $config
*/
abstract public function replace($def, $config);
/**
* Retrieves a definition object from the cache
+ * @param HTMLPurifier_Config $config
*/
abstract public function get($config);
/**
* Removes a definition object to the cache
+ * @param HTMLPurifier_Config $config
*/
abstract public function remove($config);
/**
* Clears all objects from cache
+ * @param HTMLPurifier_Config $config
*/
abstract public function flush($config);
@@ -2578,9 +3192,9 @@ abstract public function flush($config);
* @note Be carefuly implementing this method as flush. Flush must
* not interfere with other Definition types, and cleanup()
* should not be repeatedly called by userland code.
+ * @param HTMLPurifier_Config $config
*/
abstract public function cleanup($config);
-
}
@@ -2592,22 +3206,36 @@ abstract public function cleanup($config);
*/
class HTMLPurifier_DefinitionCacheFactory
{
-
+ /**
+ * @type array
+ */
protected $caches = array('Serializer' => array());
+
+ /**
+ * @type array
+ */
protected $implementations = array();
+
+ /**
+ * @type HTMLPurifier_DefinitionCache_Decorator[]
+ */
protected $decorators = array();
/**
* Initialize default decorators
*/
- public function setup() {
+ public function setup()
+ {
$this->addDecorator('Cleanup');
}
/**
* Retrieves an instance of global definition cache factory.
+ * @param HTMLPurifier_DefinitionCacheFactory $prototype
+ * @return HTMLPurifier_DefinitionCacheFactory
*/
- public static function instance($prototype = null) {
+ public static function instance($prototype = null)
+ {
static $instance;
if ($prototype !== null) {
$instance = $prototype;
@@ -2620,19 +3248,22 @@ public static function instance($prototype = null) {
/**
* Registers a new definition cache object
- * @param $short Short name of cache object, for reference
- * @param $long Full class name of cache object, for construction
+ * @param string $short Short name of cache object, for reference
+ * @param string $long Full class name of cache object, for construction
*/
- public function register($short, $long) {
+ public function register($short, $long)
+ {
$this->implementations[$short] = $long;
}
/**
* Factory method that creates a cache object based on configuration
- * @param $name Name of definitions handled by cache
- * @param $config Instance of HTMLPurifier_Config
+ * @param string $type Name of definitions handled by cache
+ * @param HTMLPurifier_Config $config Config instance
+ * @return mixed
*/
- public function create($type, $config) {
+ public function create($type, $config)
+ {
$method = $config->get('Cache.DefinitionImpl');
if ($method === null) {
return new HTMLPurifier_DefinitionCache_Null($type);
@@ -2640,10 +3271,8 @@ public function create($type, $config) {
if (!empty($this->caches[$method][$type])) {
return $this->caches[$method][$type];
}
- if (
- isset($this->implementations[$method]) &&
- class_exists($class = $this->implementations[$method], false)
- ) {
+ if (isset($this->implementations[$method]) &&
+ class_exists($class = $this->implementations[$method], false)) {
$cache = new $class($type);
} else {
if ($method != 'Serializer') {
@@ -2663,16 +3292,16 @@ class_exists($class = $this->implementations[$method], false)
/**
* Registers a decorator to add to all new cache objects
- * @param
+ * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator
*/
- public function addDecorator($decorator) {
+ public function addDecorator($decorator)
+ {
if (is_string($decorator)) {
$class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
$decorator = new $class;
}
$this->decorators[$decorator->name] = $decorator;
}
-
}
@@ -2689,42 +3318,55 @@ class HTMLPurifier_Doctype
{
/**
* Full name of doctype
+ * @type string
*/
public $name;
/**
* List of standard modules (string identifiers or literal objects)
* that this doctype uses
+ * @type array
*/
public $modules = array();
/**
* List of modules to use for tidying up code
+ * @type array
*/
public $tidyModules = array();
/**
* Is the language derived from XML (i.e. XHTML)?
+ * @type bool
*/
public $xml = true;
/**
* List of aliases for this doctype
+ * @type array
*/
public $aliases = array();
/**
* Public DTD identifier
+ * @type string
*/
public $dtdPublic;
/**
* System DTD identifier
+ * @type string
*/
public $dtdSystem;
- public function __construct($name = null, $xml = true, $modules = array(),
- $tidyModules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null
+ public function __construct(
+ $name = null,
+ $xml = true,
+ $modules = array(),
+ $tidyModules = array(),
+ $aliases = array(),
+ $dtd_public = null,
+ $dtd_system = null
) {
$this->name = $name;
$this->xml = $xml;
@@ -2744,12 +3386,14 @@ class HTMLPurifier_DoctypeRegistry
{
/**
- * Hash of doctype names to doctype objects
+ * Hash of doctype names to doctype objects.
+ * @type array
*/
protected $doctypes;
/**
- * Lookup table of aliases to real doctype names
+ * Lookup table of aliases to real doctype names.
+ * @type array
*/
protected $aliases;
@@ -2757,32 +3401,57 @@ class HTMLPurifier_DoctypeRegistry
* Registers a doctype to the registry
* @note Accepts a fully-formed doctype object, or the
* parameters for constructing a doctype object
- * @param $doctype Name of doctype or literal doctype object
- * @param $modules Modules doctype will load
- * @param $modules_for_modes Modules doctype will load for certain modes
- * @param $aliases Alias names for doctype
- * @return Editable registered doctype
- */
- public function register($doctype, $xml = true, $modules = array(),
- $tidy_modules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null
+ * @param string $doctype Name of doctype or literal doctype object
+ * @param bool $xml
+ * @param array $modules Modules doctype will load
+ * @param array $tidy_modules Modules doctype will load for certain modes
+ * @param array $aliases Alias names for doctype
+ * @param string $dtd_public
+ * @param string $dtd_system
+ * @return HTMLPurifier_Doctype Editable registered doctype
+ */
+ public function register(
+ $doctype,
+ $xml = true,
+ $modules = array(),
+ $tidy_modules = array(),
+ $aliases = array(),
+ $dtd_public = null,
+ $dtd_system = null
) {
- if (!is_array($modules)) $modules = array($modules);
- if (!is_array($tidy_modules)) $tidy_modules = array($tidy_modules);
- if (!is_array($aliases)) $aliases = array($aliases);
+ if (!is_array($modules)) {
+ $modules = array($modules);
+ }
+ if (!is_array($tidy_modules)) {
+ $tidy_modules = array($tidy_modules);
+ }
+ if (!is_array($aliases)) {
+ $aliases = array($aliases);
+ }
if (!is_object($doctype)) {
$doctype = new HTMLPurifier_Doctype(
- $doctype, $xml, $modules, $tidy_modules, $aliases, $dtd_public, $dtd_system
+ $doctype,
+ $xml,
+ $modules,
+ $tidy_modules,
+ $aliases,
+ $dtd_public,
+ $dtd_system
);
}
$this->doctypes[$doctype->name] = $doctype;
$name = $doctype->name;
// hookup aliases
foreach ($doctype->aliases as $alias) {
- if (isset($this->doctypes[$alias])) continue;
+ if (isset($this->doctypes[$alias])) {
+ continue;
+ }
$this->aliases[$alias] = $name;
}
// remove old aliases
- if (isset($this->aliases[$name])) unset($this->aliases[$name]);
+ if (isset($this->aliases[$name])) {
+ unset($this->aliases[$name]);
+ }
return $doctype;
}
@@ -2790,11 +3459,14 @@ public function register($doctype, $xml = true, $modules = array(),
* Retrieves reference to a doctype of a certain name
* @note This function resolves aliases
* @note When possible, use the more fully-featured make()
- * @param $doctype Name of doctype
- * @return Editable doctype object
+ * @param string $doctype Name of doctype
+ * @return HTMLPurifier_Doctype Editable doctype object
*/
- public function get($doctype) {
- if (isset($this->aliases[$doctype])) $doctype = $this->aliases[$doctype];
+ public function get($doctype)
+ {
+ if (isset($this->aliases[$doctype])) {
+ $doctype = $this->aliases[$doctype];
+ }
if (!isset($this->doctypes[$doctype])) {
trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
$anon = new HTMLPurifier_Doctype($doctype);
@@ -2810,20 +3482,30 @@ public function get($doctype) {
* can hold on to (this is necessary in order to tell
* Generator whether or not the current document is XML
* based or not).
+ * @param HTMLPurifier_Config $config
+ * @return HTMLPurifier_Doctype
*/
- public function make($config) {
+ public function make($config)
+ {
return clone $this->get($this->getDoctypeFromConfig($config));
}
/**
* Retrieves the doctype from the configuration object
+ * @param HTMLPurifier_Config $config
+ * @return string
*/
- public function getDoctypeFromConfig($config) {
+ public function getDoctypeFromConfig($config)
+ {
// recommended test
$doctype = $config->get('HTML.Doctype');
- if (!empty($doctype)) return $doctype;
+ if (!empty($doctype)) {
+ return $doctype;
+ }
$doctype = $config->get('HTML.CustomDoctype');
- if (!empty($doctype)) return $doctype;
+ if (!empty($doctype)) {
+ return $doctype;
+ }
// backwards-compatibility
if ($config->get('HTML.XHTML')) {
$doctype = 'XHTML 1.0';
@@ -2837,7 +3519,6 @@ public function getDoctypeFromConfig($config) {
}
return $doctype;
}
-
}
@@ -2854,15 +3535,16 @@ public function getDoctypeFromConfig($config) {
*/
class HTMLPurifier_ElementDef
{
-
/**
* Does the definition work by itself, or is it created solely
* for the purpose of merging into another definition?
+ * @type bool
*/
public $standalone = true;
/**
- * Associative array of attribute name to HTMLPurifier_AttrDef
+ * Associative array of attribute name to HTMLPurifier_AttrDef.
+ * @type array
* @note Before being processed by HTMLPurifier_AttrCollections
* when modules are finalized during
* HTMLPurifier_HTMLDefinition->setup(), this array may also
@@ -2887,26 +3569,30 @@ class HTMLPurifier_ElementDef
// nuking.
/**
- * List of tags HTMLPurifier_AttrTransform to be done before validation
+ * List of tags HTMLPurifier_AttrTransform to be done before validation.
+ * @type array
*/
public $attr_transform_pre = array();
/**
- * List of tags HTMLPurifier_AttrTransform to be done after validation
+ * List of tags HTMLPurifier_AttrTransform to be done after validation.
+ * @type array
*/
public $attr_transform_post = array();
/**
* HTMLPurifier_ChildDef of this tag.
+ * @type HTMLPurifier_ChildDef
*/
public $child;
/**
- * Abstract string representation of internal ChildDef rules. See
- * HTMLPurifier_ContentSets for how this is parsed and then transformed
+ * Abstract string representation of internal ChildDef rules.
+ * @see HTMLPurifier_ContentSets for how this is parsed and then transformed
* into an HTMLPurifier_ChildDef.
* @warning This is a temporary variable that is not available after
* being processed by HTMLDefinition
+ * @type string
*/
public $content_model;
@@ -2916,27 +3602,29 @@ class HTMLPurifier_ElementDef
* @warning This must be lowercase
* @warning This is a temporary variable that is not available after
* being processed by HTMLDefinition
+ * @type string
*/
public $content_model_type;
-
-
/**
* Does the element have a content model (#PCDATA | Inline)*? This
* is important for chameleon ins and del processing in
* HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
* have to worry about this one.
+ * @type bool
*/
public $descendants_are_inline = false;
/**
- * List of the names of required attributes this element has. Dynamically
- * populated by HTMLPurifier_HTMLDefinition::getElement
+ * List of the names of required attributes this element has.
+ * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement()
+ * @type array
*/
public $required_attr = array();
/**
* Lookup table of tags excluded from all descendants of this tag.
+ * @type array
* @note SGML permits exclusions for all descendants, but this is
* not possible with DTDs or XML Schemas. W3C has elected to
* use complicated compositions of content_models to simulate
@@ -2950,6 +3638,7 @@ class HTMLPurifier_ElementDef
/**
* This tag is explicitly auto-closed by the following tags.
+ * @type array
*/
public $autoclose = array();
@@ -2957,19 +3646,22 @@ class HTMLPurifier_ElementDef
* If a foreign element is found in this element, test if it is
* allowed by this sub-element; if it is, instead of closing the
* current element, place it inside this element.
+ * @type string
*/
public $wrap;
/**
* Whether or not this is a formatting element affected by the
* "Active Formatting Elements" algorithm.
+ * @type bool
*/
public $formatting;
/**
* Low-level factory constructor for creating new standalone element defs
*/
- public static function create($content_model, $content_model_type, $attr) {
+ public static function create($content_model, $content_model_type, $attr)
+ {
$def = new HTMLPurifier_ElementDef();
$def->content_model = $content_model;
$def->content_model_type = $content_model_type;
@@ -2981,11 +3673,12 @@ public static function create($content_model, $content_model_type, $attr) {
* Merges the values of another element definition into this one.
* Values from the new element def take precedence if a value is
* not mergeable.
+ * @param HTMLPurifier_ElementDef $def
*/
- public function mergeIn($def) {
-
+ public function mergeIn($def)
+ {
// later keys takes precedence
- foreach($def->attr as $k => $v) {
+ foreach ($def->attr as $k => $v) {
if ($k === 0) {
// merge in the includes
// sorry, no way to override an include
@@ -2995,7 +3688,9 @@ public function mergeIn($def) {
continue;
}
if ($v === false) {
- if (isset($this->attr[$k])) unset($this->attr[$k]);
+ if (isset($this->attr[$k])) {
+ unset($this->attr[$k]);
+ }
continue;
}
$this->attr[$k] = $v;
@@ -3004,19 +3699,24 @@ public function mergeIn($def) {
$this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
$this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);
- if(!empty($def->content_model)) {
+ if (!empty($def->content_model)) {
$this->content_model =
str_replace("#SUPER", $this->content_model, $def->content_model);
$this->child = false;
}
- if(!empty($def->content_model_type)) {
+ if (!empty($def->content_model_type)) {
$this->content_model_type = $def->content_model_type;
$this->child = false;
}
- if(!is_null($def->child)) $this->child = $def->child;
- if(!is_null($def->formatting)) $this->formatting = $def->formatting;
- if($def->descendants_are_inline) $this->descendants_are_inline = $def->descendants_are_inline;
-
+ if (!is_null($def->child)) {
+ $this->child = $def->child;
+ }
+ if (!is_null($def->formatting)) {
+ $this->formatting = $def->formatting;
+ }
+ if ($def->descendants_are_inline) {
+ $this->descendants_are_inline = $def->descendants_are_inline;
+ }
}
/**
@@ -3024,16 +3724,18 @@ public function mergeIn($def) {
* @param $a1 Array by reference that is merged into
* @param $a2 Array that merges into $a1
*/
- private function _mergeAssocArray(&$a1, $a2) {
+ private function _mergeAssocArray(&$a1, $a2)
+ {
foreach ($a2 as $k => $v) {
if ($v === false) {
- if (isset($a1[$k])) unset($a1[$k]);
+ if (isset($a1[$k])) {
+ unset($a1[$k]);
+ }
continue;
}
$a1[$k] = $v;
}
}
-
}
@@ -3050,19 +3752,27 @@ class HTMLPurifier_Encoder
/**
* Constructor throws fatal error if you attempt to instantiate class
*/
- private function __construct() {
+ private function __construct()
+ {
trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
}
/**
* Error-handler that mutes errors, alternative to shut-up operator.
*/
- public static function muteErrorHandler() {}
+ public static function muteErrorHandler()
+ {
+ }
/**
* iconv wrapper which mutes errors, but doesn't work around bugs.
+ * @param string $in Input encoding
+ * @param string $out Output encoding
+ * @param string $text The text to convert
+ * @return string
*/
- public static function unsafeIconv($in, $out, $text) {
+ public static function unsafeIconv($in, $out, $text)
+ {
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
$r = iconv($in, $out, $text);
restore_error_handler();
@@ -3071,8 +3781,14 @@ public static function unsafeIconv($in, $out, $text) {
/**
* iconv wrapper which mutes errors and works around bugs.
- */
- public static function iconv($in, $out, $text, $max_chunk_size = 8000) {
+ * @param string $in Input encoding
+ * @param string $out Output encoding
+ * @param string $text The text to convert
+ * @param int $max_chunk_size
+ * @return string
+ */
+ public static function iconv($in, $out, $text, $max_chunk_size = 8000)
+ {
$code = self::testIconvTruncateBug();
if ($code == self::ICONV_OK) {
return self::unsafeIconv($in, $out, $text);
@@ -3127,6 +3843,10 @@ public static function iconv($in, $out, $text, $max_chunk_size = 8000) {
* It will parse according to UTF-8 and return a valid UTF8 string, with
* non-SGML codepoints excluded.
*
+ * @param string $str The string to clean
+ * @param bool $force_php
+ * @return string
+ *
* @note Just for reference, the non-SGML code points are 0 to 31 and
* 127 to 159, inclusive. However, we allow code points 9, 10
* and 13, which are the tab, line feed and carriage return
@@ -3146,14 +3866,17 @@ public static function iconv($in, $out, $text, $max_chunk_size = 8000) {
* would need that, and I'm probably not going to implement them.
* Once again, PHP 6 should solve all our problems.
*/
- public static function cleanUTF8($str, $force_php = false) {
-
+ public static function cleanUTF8($str, $force_php = false)
+ {
// UTF-8 validity is checked since PHP 4.3.5
// This is an optimization: if the string is already valid UTF-8, no
// need to do PHP stuff. 99% of the time, this will be the case.
// The regexp matches the XML char production, as well as well as excluding
// non-SGML codepoints U+007F to U+009F
- if (preg_match('/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', $str)) {
+ if (preg_match(
+ '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du',
+ $str
+ )) {
return $str;
}
@@ -3172,7 +3895,7 @@ public static function cleanUTF8($str, $force_php = false) {
$char = '';
$len = strlen($str);
- for($i = 0; $i < $len; $i++) {
+ for ($i = 0; $i < $len; $i++) {
$in = ord($str{$i});
$char .= $str[$i]; // append byte to char
if (0 == $mState) {
@@ -3325,8 +4048,9 @@ public static function cleanUTF8($str, $force_php = false) {
// | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes
// +----------+----------+----------+----------+
- public static function unichr($code) {
- if($code > 1114111 or $code < 0 or
+ public static function unichr($code)
+ {
+ if ($code > 1114111 or $code < 0 or
($code >= 55296 and $code <= 57343) ) {
// bits are set outside the "valid" range as defined
// by UNICODE 4.1.0
@@ -3344,7 +4068,7 @@ public static function unichr($code) {
$y = (($code & 2047) >> 6) | 192;
} else {
$y = (($code & 4032) >> 6) | 128;
- if($code < 65536) {
+ if ($code < 65536) {
$z = (($code >> 12) & 15) | 224;
} else {
$z = (($code >> 12) & 63) | 128;
@@ -3354,15 +4078,25 @@ public static function unichr($code) {
}
// set up the actual character
$ret = '';
- if($w) $ret .= chr($w);
- if($z) $ret .= chr($z);
- if($y) $ret .= chr($y);
+ if ($w) {
+ $ret .= chr($w);
+ }
+ if ($z) {
+ $ret .= chr($z);
+ }
+ if ($y) {
+ $ret .= chr($y);
+ }
$ret .= chr($x);
return $ret;
}
- public static function iconvAvailable() {
+ /**
+ * @return bool
+ */
+ public static function iconvAvailable()
+ {
static $iconv = null;
if ($iconv === null) {
$iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
@@ -3371,13 +4105,22 @@ public static function iconvAvailable() {
}
/**
- * Converts a string to UTF-8 based on configuration.
+ * Convert a string to UTF-8 based on configuration.
+ * @param string $str The string to convert
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
*/
- public static function convertToUTF8($str, $config, $context) {
+ public static function convertToUTF8($str, $config, $context)
+ {
$encoding = $config->get('Core.Encoding');
- if ($encoding === 'utf-8') return $str;
+ if ($encoding === 'utf-8') {
+ return $str;
+ }
static $iconv = null;
- if ($iconv === null) $iconv = self::iconvAvailable();
+ if ($iconv === null) {
+ $iconv = self::iconvAvailable();
+ }
if ($iconv && !$config->get('Test.ForceNoIconv')) {
// unaffected by bugs, since UTF-8 support all characters
$str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);
@@ -3399,29 +4142,44 @@ public static function convertToUTF8($str, $config, $context) {
if ($bug == self::ICONV_OK) {
trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);
} else {
- trigger_error('You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 and http://sourceware.org/bugzilla/show_bug.cgi?id=13541', E_USER_ERROR);
+ trigger_error(
+ 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' .
+ 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541',
+ E_USER_ERROR
+ );
}
}
/**
* Converts a string from UTF-8 based on configuration.
+ * @param string $str The string to convert
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
* @note Currently, this is a lossy conversion, with unexpressable
* characters being omitted.
*/
- public static function convertFromUTF8($str, $config, $context) {
+ public static function convertFromUTF8($str, $config, $context)
+ {
$encoding = $config->get('Core.Encoding');
if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
$str = self::convertToASCIIDumbLossless($str);
}
- if ($encoding === 'utf-8') return $str;
+ if ($encoding === 'utf-8') {
+ return $str;
+ }
static $iconv = null;
- if ($iconv === null) $iconv = self::iconvAvailable();
+ if ($iconv === null) {
+ $iconv = self::iconvAvailable();
+ }
if ($iconv && !$config->get('Test.ForceNoIconv')) {
// Undo our previous fix in convertToUTF8, otherwise iconv will barf
$ascii_fix = self::testEncodingSupportsASCII($encoding);
if (!$escape && !empty($ascii_fix)) {
$clear_fix = array();
- foreach ($ascii_fix as $utf8 => $native) $clear_fix[$utf8] = '';
+ foreach ($ascii_fix as $utf8 => $native) {
+ $clear_fix[$utf8] = '';
+ }
$str = strtr($str, $clear_fix);
}
$str = strtr($str, array_flip($ascii_fix));
@@ -3441,8 +4199,8 @@ public static function convertFromUTF8($str, $config, $context) {
/**
* Lossless (character-wise) conversion of HTML to ASCII
- * @param $str UTF-8 string to be converted to ASCII
- * @returns ASCII encoded string with non-ASCII character entity-ized
+ * @param string $str UTF-8 string to be converted to ASCII
+ * @return string ASCII encoded string with non-ASCII character entity-ized
* @warning Adapted from MediaWiki, claiming fair use: this is a common
* algorithm. If you disagree with this license fudgery,
* implement it yourself.
@@ -3455,27 +4213,28 @@ public static function convertFromUTF8($str, $config, $context) {
* @note Sort of with cleanUTF8() but it assumes that $str is
* well-formed UTF-8
*/
- public static function convertToASCIIDumbLossless($str) {
+ public static function convertToASCIIDumbLossless($str)
+ {
$bytesleft = 0;
$result = '';
$working = 0;
$len = strlen($str);
- for( $i = 0; $i < $len; $i++ ) {
- $bytevalue = ord( $str[$i] );
- if( $bytevalue <= 0x7F ) { //0xxx xxxx
- $result .= chr( $bytevalue );
+ for ($i = 0; $i < $len; $i++) {
+ $bytevalue = ord($str[$i]);
+ if ($bytevalue <= 0x7F) { //0xxx xxxx
+ $result .= chr($bytevalue);
$bytesleft = 0;
- } elseif( $bytevalue <= 0xBF ) { //10xx xxxx
+ } elseif ($bytevalue <= 0xBF) { //10xx xxxx
$working = $working << 6;
$working += ($bytevalue & 0x3F);
$bytesleft--;
- if( $bytesleft <= 0 ) {
+ if ($bytesleft <= 0) {
$result .= "" . $working . ";";
}
- } elseif( $bytevalue <= 0xDF ) { //110x xxxx
+ } elseif ($bytevalue <= 0xDF) { //110x xxxx
$working = $bytevalue & 0x1F;
$bytesleft = 1;
- } elseif( $bytevalue <= 0xEF ) { //1110 xxxx
+ } elseif ($bytevalue <= 0xEF) { //1110 xxxx
$working = $bytevalue & 0x0F;
$bytesleft = 2;
} else { //1111 0xxx
@@ -3509,9 +4268,10 @@ public static function convertToASCIIDumbLossless($str) {
* characters, as long as PHP ignores the error code. If PHP starts
* paying attention to the error code, iconv becomes unusable.
*
- * @returns Error code indicating severity of bug.
+ * @return int Error code indicating severity of bug.
*/
- public static function testIconvTruncateBug() {
+ public static function testIconvTruncateBug()
+ {
static $code = null;
if ($code === null) {
// better not use iconv, otherwise infinite loop!
@@ -3521,7 +4281,11 @@ public static function testIconvTruncateBug() {
} elseif (($c = strlen($r)) < 9000) {
$code = self::ICONV_TRUNCATES;
} elseif ($c > 9000) {
- trigger_error('Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: include your iconv version as per phpversion()', E_USER_ERROR);
+ trigger_error(
+ 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' .
+ 'include your iconv version as per phpversion()',
+ E_USER_ERROR
+ );
} else {
$code = self::ICONV_OK;
}
@@ -3540,7 +4304,8 @@ public static function testIconvTruncateBug() {
* @return Array of UTF-8 characters to their corresponding ASCII,
* which can be used to "undo" any overzealous iconv action.
*/
- public static function testEncodingSupportsASCII($encoding, $bypass = false) {
+ public static function testEncodingSupportsASCII($encoding, $bypass = false)
+ {
// All calls to iconv here are unsafe, proof by case analysis:
// If ICONV_OK, no difference.
// If ICONV_TRUNCATE, all calls involve one character inputs,
@@ -3548,7 +4313,9 @@ public static function testEncodingSupportsASCII($encoding, $bypass = false) {
// If ICONV_UNUSABLE, this call is irrelevant
static $encodings = array();
if (!$bypass) {
- if (isset($encodings[$encoding])) return $encodings[$encoding];
+ if (isset($encodings[$encoding])) {
+ return $encodings[$encoding];
+ }
$lenc = strtolower($encoding);
switch ($lenc) {
case 'shift_jis':
@@ -3556,15 +4323,18 @@ public static function testEncodingSupportsASCII($encoding, $bypass = false) {
case 'johab':
return array("\xE2\x82\xA9" => '\\');
}
- if (strpos($lenc, 'iso-8859-') === 0) return array();
+ if (strpos($lenc, 'iso-8859-') === 0) {
+ return array();
+ }
}
$ret = array();
- if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) return false;
+ if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) {
+ return false;
+ }
for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
$c = chr($i); // UTF-8 char
$r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion
- if (
- $r === '' ||
+ if ($r === '' ||
// This line is needed for iconv implementations that do not
// omit characters that do not exist in the target character set
($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)
@@ -3578,8 +4348,6 @@ public static function testEncodingSupportsASCII($encoding, $bypass = false) {
$encodings[$encoding] = $ret;
return $ret;
}
-
-
}
@@ -3589,20 +4357,23 @@ public static function testEncodingSupportsASCII($encoding, $bypass = false) {
/**
* Object that provides entity lookup table from entity name to character
*/
-class HTMLPurifier_EntityLookup {
-
+class HTMLPurifier_EntityLookup
+{
/**
* Assoc array of entity name to character represented.
+ * @type array
*/
public $table;
/**
* Sets up the entity lookup table from the serialized file contents.
+ * @param bool $file
* @note The serialized contents are versioned, but were generated
* using the maintenance script generate_entity_file.php
* @warning This is not in constructor to help enforce the Singleton
*/
- public function setup($file = false) {
+ public function setup($file = false)
+ {
if (!$file) {
$file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
}
@@ -3611,9 +4382,11 @@ public function setup($file = false) {
/**
* Retrieves sole instance of the object.
- * @param Optional prototype of custom lookup table to overload with.
+ * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with.
+ * @return HTMLPurifier_EntityLookup
*/
- public static function instance($prototype = false) {
+ public static function instance($prototype = false)
+ {
// no references, since PHP doesn't copy unless modified
static $instance = null;
if ($prototype) {
@@ -3624,7 +4397,6 @@ public static function instance($prototype = false) {
}
return $instance;
}
-
}
@@ -3643,19 +4415,21 @@ class HTMLPurifier_EntityParser
/**
* Reference to entity lookup table.
+ * @type HTMLPurifier_EntityLookup
*/
protected $_entity_lookup;
/**
* Callback regex string for parsing entities.
+ * @type string
*/
protected $_substituteEntitiesRegex =
-'/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
-// 1. hex 2. dec 3. string (XML style)
-
+ '/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
+ // 1. hex 2. dec 3. string (XML style)
/**
* Decimal to parsed string conversion table for special entities.
+ * @type array
*/
protected $_special_dec2str =
array(
@@ -3668,6 +4442,7 @@ class HTMLPurifier_EntityParser
/**
* Stripped entity names to decimal conversion table for special entities.
+ * @type array
*/
protected $_special_ent2dec =
array(
@@ -3682,41 +4457,45 @@ class HTMLPurifier_EntityParser
* running this whenever you have parsed character is t3h 5uck, we run
* it before everything else.
*
- * @param $string String to have non-special entities parsed.
- * @returns Parsed string.
+ * @param string $string String to have non-special entities parsed.
+ * @return string Parsed string.
*/
- public function substituteNonSpecialEntities($string) {
+ public function substituteNonSpecialEntities($string)
+ {
// it will try to detect missing semicolons, but don't rely on it
return preg_replace_callback(
$this->_substituteEntitiesRegex,
array($this, 'nonSpecialEntityCallback'),
$string
- );
+ );
}
/**
* Callback function for substituteNonSpecialEntities() that does the work.
*
- * @param $matches PCRE matches array, with 0 the entire match, and
+ * @param array $matches PCRE matches array, with 0 the entire match, and
* either index 1, 2 or 3 set with a hex value, dec value,
* or string (respectively).
- * @returns Replacement string.
+ * @return string Replacement string.
*/
- protected function nonSpecialEntityCallback($matches) {
+ protected function nonSpecialEntityCallback($matches)
+ {
// replaces all but big five
$entity = $matches[0];
$is_num = (@$matches[0][1] === '#');
if ($is_num) {
$is_hex = (@$entity[2] === 'x');
$code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
-
// abort for special characters
- if (isset($this->_special_dec2str[$code])) return $entity;
-
+ if (isset($this->_special_dec2str[$code])) {
+ return $entity;
+ }
return HTMLPurifier_Encoder::unichr($code);
} else {
- if (isset($this->_special_ent2dec[$matches[3]])) return $entity;
+ if (isset($this->_special_ent2dec[$matches[3]])) {
+ return $entity;
+ }
if (!$this->_entity_lookup) {
$this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
}
@@ -3734,14 +4513,16 @@ protected function nonSpecialEntityCallback($matches) {
* @notice We try to avoid calling this function because otherwise, it
* would have to be called a lot (for every parsed section).
*
- * @param $string String to have non-special entities parsed.
- * @returns Parsed string.
+ * @param string $string String to have non-special entities parsed.
+ * @return string Parsed string.
*/
- public function substituteSpecialEntities($string) {
+ public function substituteSpecialEntities($string)
+ {
return preg_replace_callback(
$this->_substituteEntitiesRegex,
array($this, 'specialEntityCallback'),
- $string);
+ $string
+ );
}
/**
@@ -3749,12 +4530,13 @@ public function substituteSpecialEntities($string) {
*
* This callback has same syntax as nonSpecialEntityCallback().
*
- * @param $matches PCRE-style matches array, with 0 the entire match, and
+ * @param array $matches PCRE-style matches array, with 0 the entire match, and
* either index 1, 2 or 3 set with a hex value, dec value,
* or string (respectively).
- * @returns Replacement string.
+ * @return string Replacement string.
*/
- protected function specialEntityCallback($matches) {
+ protected function specialEntityCallback($matches)
+ {
$entity = $matches[0];
$is_num = (@$matches[0][1] === '#');
if ($is_num) {
@@ -3769,7 +4551,6 @@ protected function specialEntityCallback($matches) {
$entity;
}
}
-
}
@@ -3792,16 +4573,46 @@ class HTMLPurifier_ErrorCollector
const MESSAGE = 2;
const CHILDREN = 3;
+ /**
+ * @type array
+ */
protected $errors;
+
+ /**
+ * @type array
+ */
protected $_current;
+
+ /**
+ * @type array
+ */
protected $_stacks = array(array());
+
+ /**
+ * @type HTMLPurifier_Language
+ */
protected $locale;
+
+ /**
+ * @type HTMLPurifier_Generator
+ */
protected $generator;
+
+ /**
+ * @type HTMLPurifier_Context
+ */
protected $context;
+ /**
+ * @type array
+ */
protected $lines = array();
- public function __construct($context) {
+ /**
+ * @param HTMLPurifier_Context $context
+ */
+ public function __construct($context)
+ {
$this->locale =& $context->get('Locale');
$this->context = $context;
$this->_current =& $this->_stacks[0];
@@ -3810,13 +4621,11 @@ public function __construct($context) {
/**
* Sends an error message to the collector for later use
- * @param $severity int Error severity, PHP error style (don't use E_USER_)
- * @param $msg string Error message text
- * @param $subst1 string First substitution for $msg
- * @param $subst2 string ...
+ * @param int $severity Error severity, PHP error style (don't use E_USER_)
+ * @param string $msg Error message text
*/
- public function send($severity, $msg) {
-
+ public function send($severity, $msg)
+ {
$args = array();
if (func_num_args() > 2) {
$args = func_get_args();
@@ -3826,7 +4635,7 @@ public function send($severity, $msg) {
$token = $this->context->get('CurrentToken', true);
$line = $token ? $token->line : $this->context->get('CurrentLine', true);
- $col = $token ? $token->col : $this->context->get('CurrentCol', true);
+ $col = $token ? $token->col : $this->context->get('CurrentCol', true);
$attr = $this->context->get('CurrentAttr', true);
// perform special substitutions, also add custom parameters
@@ -3836,7 +4645,9 @@ public function send($severity, $msg) {
}
if (!is_null($attr)) {
$subst['$CurrentAttr.Name'] = $attr;
- if (isset($token->attr[$attr])) $subst['$CurrentAttr.Value'] = $token->attr[$attr];
+ if (isset($token->attr[$attr])) {
+ $subst['$CurrentAttr.Value'] = $token->attr[$attr];
+ }
}
if (empty($args)) {
@@ -3845,7 +4656,9 @@ public function send($severity, $msg) {
$msg = $this->locale->formatMessage($msg, $args);
}
- if (!empty($subst)) $msg = strtr($msg, $subst);
+ if (!empty($subst)) {
+ $msg = strtr($msg, $subst);
+ }
// (numerically indexed)
$error = array(
@@ -3856,16 +4669,15 @@ public function send($severity, $msg) {
);
$this->_current[] = $error;
-
// NEW CODE BELOW ...
-
- $struct = null;
// Top-level errors are either:
// TOKEN type, if $value is set appropriately, or
// "syntax" type, if $value is null
$new_struct = new HTMLPurifier_ErrorStruct();
$new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
- if ($token) $new_struct->value = clone $token;
+ if ($token) {
+ $new_struct->value = clone $token;
+ }
if (is_int($line) && is_int($col)) {
if (isset($this->lines[$line][$col])) {
$struct = $this->lines[$line][$col];
@@ -3904,30 +4716,34 @@ public function send($severity, $msg) {
/**
* Retrieves raw error data for custom formatter to use
- * @param List of arrays in format of array(line of error,
- * error severity, error message,
- * recursive sub-errors array)
*/
- public function getRaw() {
+ public function getRaw()
+ {
return $this->errors;
}
/**
* Default HTML formatting implementation for error messages
- * @param $config Configuration array, vital for HTML output nature
- * @param $errors Errors array to display; used for recursion.
+ * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature
+ * @param array $errors Errors array to display; used for recursion.
+ * @return string
*/
- public function getHTMLFormatted($config, $errors = null) {
+ public function getHTMLFormatted($config, $errors = null)
+ {
$ret = array();
$this->generator = new HTMLPurifier_Generator($config, $this->context);
- if ($errors === null) $errors = $this->errors;
+ if ($errors === null) {
+ $errors = $this->errors;
+ }
// 'At line' message needs to be removed
// generation code for new structure goes here. It needs to be recursive.
foreach ($this->lines as $line => $col_array) {
- if ($line == -1) continue;
+ if ($line == -1) {
+ continue;
+ }
foreach ($col_array as $col => $struct) {
$this->_renderStruct($ret, $struct, $line, $col);
}
@@ -3944,7 +4760,8 @@ public function getHTMLFormatted($config, $errors = null) {
}
- private function _renderStruct(&$ret, $struct, $line = null, $col = null) {
+ private function _renderStruct(&$ret, $struct, $line = null, $col = null)
+ {
$stack = array($struct);
$context_stack = array(array());
while ($current = array_pop($stack)) {
@@ -3970,7 +4787,7 @@ private function _renderStruct(&$ret, $struct, $line = null, $col = null) {
//$string .= '';
$ret[] = $string;
}
- foreach ($current->children as $type => $array) {
+ foreach ($current->children as $array) {
$context[] = $current;
$stack = array_merge($stack, array_reverse($array, true));
for ($i = count($array); $i > 0; $i--) {
@@ -3979,7 +4796,6 @@ private function _renderStruct(&$ret, $struct, $line = null, $col = null) {
}
}
}
-
}
@@ -4005,6 +4821,7 @@ class HTMLPurifier_ErrorStruct
/**
* Type of this struct.
+ * @type string
*/
public $type;
@@ -4014,11 +4831,13 @@ class HTMLPurifier_ErrorStruct
* - TOKEN: Instance of HTMLPurifier_Token
* - ATTR: array('attr-name', 'value')
* - CSSPROP: array('prop-name', 'value')
+ * @type mixed
*/
public $value;
/**
* Errors registered for this structure.
+ * @type array
*/
public $errors = array();
@@ -4026,10 +4845,17 @@ class HTMLPurifier_ErrorStruct
* Child ErrorStructs that are from this structure. For example, a TOKEN
* ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional
* array in structure: [TYPE]['identifier']
+ * @type array
*/
public $children = array();
- public function getChild($type, $id) {
+ /**
+ * @param string $type
+ * @param string $id
+ * @return mixed
+ */
+ public function getChild($type, $id)
+ {
if (!isset($this->children[$type][$id])) {
$this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
$this->children[$type][$id]->type = $type;
@@ -4037,10 +4863,14 @@ public function getChild($type, $id) {
return $this->children[$type][$id];
}
- public function addError($severity, $message) {
+ /**
+ * @param int $severity
+ * @param string $message
+ */
+ public function addError($severity, $message)
+ {
$this->errors[] = array($severity, $message);
}
-
}
@@ -4083,24 +4913,34 @@ class HTMLPurifier_Filter
{
/**
- * Name of the filter for identification purposes
+ * Name of the filter for identification purposes.
+ * @type string
*/
public $name;
/**
* Pre-processor function, handles HTML before HTML Purifier
+ * @param string $html
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
*/
- public function preFilter($html, $config, $context) {
+ public function preFilter($html, $config, $context)
+ {
return $html;
}
/**
* Post-processor function, handles HTML after HTML Purifier
+ * @param string $html
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
*/
- public function postFilter($html, $config, $context) {
+ public function postFilter($html, $config, $context)
+ {
return $html;
}
-
}
@@ -4118,52 +4958,61 @@ class HTMLPurifier_Generator
{
/**
- * Whether or not generator should produce XML output
+ * Whether or not generator should produce XML output.
+ * @type bool
*/
private $_xhtml = true;
/**
- * :HACK: Whether or not generator should comment the insides of )#si',
- array($this, 'scriptCallback'), $html);
+ $html = preg_replace_callback(
+ '#()#si',
+ array($this, 'scriptCallback'),
+ $html
+ );
}
$html = $this->normalize($html, $config, $context);
@@ -15054,15 +18727,15 @@ public function tokenizeHTML($html, $config, $context) {
if ($maintain_line_numbers) {
$current_line = 1;
- $current_col = 0;
+ $current_col = 0;
$length = strlen($html);
} else {
$current_line = false;
- $current_col = false;
+ $current_col = false;
$length = false;
}
$context->register('CurrentLine', $current_line);
- $context->register('CurrentCol', $current_col);
+ $context->register('CurrentCol', $current_col);
$nl = "\n";
// how often to manually recalculate. This will ALWAYS be right,
// but it's pretty wasteful. Set to 0 to turn off
@@ -15076,16 +18749,14 @@ public function tokenizeHTML($html, $config, $context) {
// for testing synchronization
$loops = 0;
- while(++$loops) {
-
+ while (++$loops) {
// $cursor is either at the start of a token, or inside of
// a tag (i.e. there was a < immediately before it), as indicated
// by $inside_tag
if ($maintain_line_numbers) {
-
// $rcursor, however, is always at the start of a token.
- $rcursor = $cursor - (int) $inside_tag;
+ $rcursor = $cursor - (int)$inside_tag;
// Column number is cheap, so we calculate it every round.
// We're interested at the *end* of the newline string, so
@@ -15095,14 +18766,11 @@ public function tokenizeHTML($html, $config, $context) {
$current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
// recalculate lines
- if (
- $synchronize_interval && // synchronization is on
- $cursor > 0 && // cursor is further than zero
- $loops % $synchronize_interval === 0 // time to synchronize!
- ) {
+ if ($synchronize_interval && // synchronization is on
+ $cursor > 0 && // cursor is further than zero
+ $loops % $synchronize_interval === 0) { // time to synchronize!
$current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
}
-
}
$position_next_lt = strpos($html, '<', $cursor);
@@ -15118,35 +18786,42 @@ public function tokenizeHTML($html, $config, $context) {
if (!$inside_tag && $position_next_lt !== false) {
// We are not inside tag and there still is another tag to parse
$token = new
- HTMLPurifier_Token_Text(
- $this->parseData(
- substr(
- $html, $cursor, $position_next_lt - $cursor
- )
+ HTMLPurifier_Token_Text(
+ $this->parseData(
+ substr(
+ $html,
+ $cursor,
+ $position_next_lt - $cursor
)
- );
+ )
+ );
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
$current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
}
$array[] = $token;
- $cursor = $position_next_lt + 1;
+ $cursor = $position_next_lt + 1;
$inside_tag = true;
continue;
} elseif (!$inside_tag) {
// We are not inside tag but there are no more tags
// If we're already at the end, break
- if ($cursor === strlen($html)) break;
+ if ($cursor === strlen($html)) {
+ break;
+ }
// Create Text of rest of string
$token = new
- HTMLPurifier_Token_Text(
- $this->parseData(
- substr(
- $html, $cursor
- )
+ HTMLPurifier_Token_Text(
+ $this->parseData(
+ substr(
+ $html,
+ $cursor
)
- );
- if ($maintain_line_numbers) $token->rawPosition($current_line, $current_col);
+ )
+ );
+ if ($maintain_line_numbers) {
+ $token->rawPosition($current_line, $current_col);
+ }
$array[] = $token;
break;
} elseif ($inside_tag && $position_next_gt !== false) {
@@ -15170,16 +18845,16 @@ public function tokenizeHTML($html, $config, $context) {
}
// Check if it's a comment
- if (
- substr($segment, 0, 3) === '!--'
- ) {
+ if (substr($segment, 0, 3) === '!--') {
// re-determine segment length, looking for -->
$position_comment_end = strpos($html, '-->', $cursor);
if ($position_comment_end === false) {
// uh oh, we have a comment that extends to
// infinity. Can't be helped: set comment
// end position to end of string
- if ($e) $e->send(E_WARNING, 'Lexer: Unclosed comment');
+ if ($e) {
+ $e->send(E_WARNING, 'Lexer: Unclosed comment');
+ }
$position_comment_end = strlen($html);
$end = true;
} else {
@@ -15188,11 +18863,13 @@ public function tokenizeHTML($html, $config, $context) {
$strlen_segment = $position_comment_end - $cursor;
$segment = substr($html, $cursor, $strlen_segment);
$token = new
- HTMLPurifier_Token_Comment(
- substr(
- $segment, 3, $strlen_segment - 3
- )
- );
+ HTMLPurifier_Token_Comment(
+ substr(
+ $segment,
+ 3,
+ $strlen_segment - 3
+ )
+ );
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
$current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
@@ -15204,7 +18881,7 @@ public function tokenizeHTML($html, $config, $context) {
}
// Check if it's an end tag
- $is_end_tag = (strpos($segment,'/') === 0);
+ $is_end_tag = (strpos($segment, '/') === 0);
if ($is_end_tag) {
$type = substr($segment, 1);
$token = new HTMLPurifier_Token_End($type);
@@ -15223,7 +18900,9 @@ public function tokenizeHTML($html, $config, $context) {
// text and go our merry way
if (!ctype_alpha($segment[0])) {
// XML: $segment[0] !== '_' && $segment[0] !== ':'
- if ($e) $e->send(E_NOTICE, 'Lexer: Unescaped lt');
+ if ($e) {
+ $e->send(E_NOTICE, 'Lexer: Unescaped lt');
+ }
$token = new HTMLPurifier_Token_Text('<');
if ($maintain_line_numbers) {
$token->rawPosition($current_line, $current_col);
@@ -15238,7 +18917,7 @@ public function tokenizeHTML($html, $config, $context) {
// trailing slash. Remember, we could have a tag like , so
// any later token processing scripts must convert improperly
// classified EmptyTags from StartTags.
- $is_self_closing = (strrpos($segment,'/') === $strlen_segment-1);
+ $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
if ($is_self_closing) {
$strlen_segment--;
$segment = substr($segment, 0, $strlen_segment);
@@ -15268,14 +18947,16 @@ public function tokenizeHTML($html, $config, $context) {
$attribute_string =
trim(
substr(
- $segment, $position_first_space
+ $segment,
+ $position_first_space
)
);
if ($attribute_string) {
$attr = $this->parseAttributeString(
- $attribute_string
- , $config, $context
- );
+ $attribute_string,
+ $config,
+ $context
+ );
} else {
$attr = array();
}
@@ -15295,15 +18976,19 @@ public function tokenizeHTML($html, $config, $context) {
continue;
} else {
// inside tag, but there's no ending > sign
- if ($e) $e->send(E_WARNING, 'Lexer: Missing gt');
+ if ($e) {
+ $e->send(E_WARNING, 'Lexer: Missing gt');
+ }
$token = new
- HTMLPurifier_Token_Text(
- '<' .
- $this->parseData(
- substr($html, $cursor)
- )
- );
- if ($maintain_line_numbers) $token->rawPosition($current_line, $current_col);
+ HTMLPurifier_Token_Text(
+ '<' .
+ $this->parseData(
+ substr($html, $cursor)
+ )
+ );
+ if ($maintain_line_numbers) {
+ $token->rawPosition($current_line, $current_col);
+ }
// no cursor scroll? Hmm...
$array[] = $token;
break;
@@ -15318,8 +19003,14 @@ public function tokenizeHTML($html, $config, $context) {
/**
* PHP 5.0.x compatible substr_count that implements offset and length
- */
- protected function substrCount($haystack, $needle, $offset, $length) {
+ * @param string $haystack
+ * @param string $needle
+ * @param int $offset
+ * @param int $length
+ * @return int
+ */
+ protected function substrCount($haystack, $needle, $offset, $length)
+ {
static $oldVersion;
if ($oldVersion === null) {
$oldVersion = version_compare(PHP_VERSION, '5.1', '<');
@@ -15335,13 +19026,18 @@ protected function substrCount($haystack, $needle, $offset, $length) {
/**
* Takes the inside of an HTML tag and makes an assoc array of attributes.
*
- * @param $string Inside of tag excluding name.
- * @returns Assoc array of attributes.
+ * @param string $string Inside of tag excluding name.
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return array Assoc array of attributes.
*/
- public function parseAttributeString($string, $config, $context) {
- $string = (string) $string; // quick typecast
+ public function parseAttributeString($string, $config, $context)
+ {
+ $string = (string)$string; // quick typecast
- if ($string == '') return array(); // no attributes
+ if ($string == '') {
+ return array();
+ } // no attributes
$e = false;
if ($config->get('Core.CollectErrors')) {
@@ -15360,46 +19056,55 @@ public function parseAttributeString($string, $config, $context) {
list($key, $quoted_value) = explode('=', $string);
$quoted_value = trim($quoted_value);
if (!$key) {
- if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
+ if ($e) {
+ $e->send(E_ERROR, 'Lexer: Missing attribute key');
+ }
return array();
}
- if (!$quoted_value) return array($key => '');
+ if (!$quoted_value) {
+ return array($key => '');
+ }
$first_char = @$quoted_value[0];
- $last_char = @$quoted_value[strlen($quoted_value)-1];
+ $last_char = @$quoted_value[strlen($quoted_value) - 1];
$same_quote = ($first_char == $last_char);
$open_quote = ($first_char == '"' || $first_char == "'");
- if ( $same_quote && $open_quote) {
+ if ($same_quote && $open_quote) {
// well behaved
$value = substr($quoted_value, 1, strlen($quoted_value) - 2);
} else {
// not well behaved
if ($open_quote) {
- if ($e) $e->send(E_ERROR, 'Lexer: Missing end quote');
+ if ($e) {
+ $e->send(E_ERROR, 'Lexer: Missing end quote');
+ }
$value = substr($quoted_value, 1);
} else {
$value = $quoted_value;
}
}
- if ($value === false) $value = '';
+ if ($value === false) {
+ $value = '';
+ }
return array($key => $this->parseData($value));
}
// setup loop environment
- $array = array(); // return assoc array of attributes
+ $array = array(); // return assoc array of attributes
$cursor = 0; // current position in string (moves forward)
- $size = strlen($string); // size of the string (stays the same)
+ $size = strlen($string); // size of the string (stays the same)
// if we have unquoted attributes, the parser expects a terminating
// space, so let's guarantee that there's always a terminating space.
$string .= ' ';
- while(true) {
-
- if ($cursor >= $size) {
- break;
+ $old_cursor = -1;
+ while ($cursor < $size) {
+ if ($old_cursor >= $cursor) {
+ throw new Exception("Infinite loop detected");
}
+ $old_cursor = $cursor;
$cursor += ($value = strspn($string, $this->_whitespace, $cursor));
// grab the key
@@ -15414,8 +19119,10 @@ public function parseAttributeString($string, $config, $context) {
$key = substr($string, $key_begin, $key_end - $key_begin);
if (!$key) {
- if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
- $cursor += strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
+ if ($e) {
+ $e->send(E_ERROR, 'Lexer: Missing attribute key');
+ }
+ $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
continue; // empty key
}
@@ -15466,24 +19173,177 @@ public function parseAttributeString($string, $config, $context) {
}
$value = substr($string, $value_begin, $value_end - $value_begin);
- if ($value === false) $value = '';
+ if ($value === false) {
+ $value = '';
+ }
$array[$key] = $this->parseData($value);
$cursor++;
-
} else {
// boolattr
if ($key !== '') {
$array[$key] = $key;
} else {
// purely theoretical
- if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
+ if ($e) {
+ $e->send(E_ERROR, 'Lexer: Missing attribute key');
+ }
}
-
}
}
return $array;
}
+}
+
+
+
+
+
+/**
+ * Concrete comment node class.
+ */
+class HTMLPurifier_Node_Comment extends HTMLPurifier_Node
+{
+ /**
+ * Character data within comment.
+ * @type string
+ */
+ public $data;
+
+ /**
+ * @type bool
+ */
+ public $is_whitespace = true;
+
+ /**
+ * Transparent constructor.
+ *
+ * @param string $data String comment data.
+ * @param int $line
+ * @param int $col
+ */
+ public function __construct($data, $line = null, $col = null)
+ {
+ $this->data = $data;
+ $this->line = $line;
+ $this->col = $col;
+ }
+
+ public function toTokenPair() {
+ return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null);
+ }
+}
+
+
+
+/**
+ * Concrete element node class.
+ */
+class HTMLPurifier_Node_Element extends HTMLPurifier_Node
+{
+ /**
+ * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
+ *
+ * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
+ * be lower-casing them, but these tokens cater to HTML tags, which are
+ * insensitive.
+ * @type string
+ */
+ public $name;
+
+ /**
+ * Associative array of the node's attributes.
+ * @type array
+ */
+ public $attr = array();
+
+ /**
+ * List of child elements.
+ * @type array
+ */
+ public $children = array();
+
+ /**
+ * Does this use the form or the form, i.e.
+ * is it a pair of start/end tokens or an empty token.
+ * @bool
+ */
+ public $empty = false;
+
+ public $endCol = null, $endLine = null, $endArmor = array();
+
+ public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
+ $this->name = $name;
+ $this->attr = $attr;
+ $this->line = $line;
+ $this->col = $col;
+ $this->armor = $armor;
+ }
+
+ public function toTokenPair() {
+ // XXX inefficiency here, normalization is not necessary
+ if ($this->empty) {
+ return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null);
+ } else {
+ $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor);
+ $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor);
+ //$end->start = $start;
+ return array($start, $end);
+ }
+ }
+}
+
+
+
+
+/**
+ * Concrete text token class.
+ *
+ * Text tokens comprise of regular parsed character data (PCDATA) and raw
+ * character data (from the CDATA sections). Internally, their
+ * data is parsed with all entities expanded. Surprisingly, the text token
+ * does have a "tag name" called #PCDATA, which is how the DTD represents it
+ * in permissible child nodes.
+ */
+class HTMLPurifier_Node_Text extends HTMLPurifier_Node
+{
+
+ /**
+ * PCDATA tag name compatible with DTD, see
+ * HTMLPurifier_ChildDef_Custom for details.
+ * @type string
+ */
+ public $name = '#PCDATA';
+
+ /**
+ * @type string
+ */
+ public $data;
+ /**< Parsed character data of text. */
+
+ /**
+ * @type bool
+ */
+ public $is_whitespace;
+
+ /**< Bool indicating if node is whitespace. */
+
+ /**
+ * Constructor, accepts data and determines if it is whitespace.
+ * @param string $data String parsed character data.
+ * @param int $line
+ * @param int $col
+ */
+ public function __construct($data, $is_whitespace, $line = null, $col = null)
+ {
+ $this->data = $data;
+ $this->is_whitespace = $is_whitespace;
+ $this->line = $line;
+ $this->col = $col;
+ }
+ public function toTokenPair() {
+ return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null);
+ }
}
@@ -15498,16 +19358,23 @@ abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
/**
* List of strategies to run tokens through.
+ * @type HTMLPurifier_Strategy[]
*/
protected $strategies = array();
- public function execute($tokens, $config, $context) {
+ /**
+ * @param HTMLPurifier_Token[] $tokens
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return HTMLPurifier_Token[]
+ */
+ public function execute($tokens, $config, $context)
+ {
foreach ($this->strategies as $strategy) {
$tokens = $strategy->execute($tokens, $config, $context);
}
return $tokens;
}
-
}
@@ -15519,14 +19386,13 @@ public function execute($tokens, $config, $context) {
*/
class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
{
-
- public function __construct() {
+ public function __construct()
+ {
$this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
$this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
$this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
$this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
}
-
}
@@ -15543,12 +19409,12 @@ public function __construct() {
* document type definitions, such as the chameleon nature of ins/del
* tags and global child exclusions.
*
- * The first major objective of this strategy is to iterate through all the
- * nodes (not tokens) of the list of tokens and determine whether or not
- * their children conform to the element's definition. If they do not, the
- * child definition may optionally supply an amended list of elements that
- * is valid or require that the entire node be deleted (and the previous
- * node rescanned).
+ * The first major objective of this strategy is to iterate through all
+ * the nodes and determine whether or not their children conform to the
+ * element's definition. If they do not, the child definition may
+ * optionally supply an amended list of elements that is valid or
+ * require that the entire node be deleted (and the previous node
+ * rescanned).
*
* The second objective is to ensure that explicitly excluded elements of
* an element do not appear in its children. Code that accomplishes this
@@ -15558,43 +19424,34 @@ public function __construct() {
* @note Whether or not unrecognized children are silently dropped or
* translated into text depends on the child definitions.
*
- * @todo Enable nodes to be bubbled out of the structure.
- *
- * @warning This algorithm (though it may be hard to see) proceeds from
- * a top-down fashion. Thus, parents are processed before
- * children. This is easy to implement and has a nice effiency
- * benefit, in that if a node is removed, we never waste any
- * time processing it, but it also means that if a child
- * changes in a non-encapsulated way (e.g. it is removed), we
- * need to go back and reprocess the parent to see if those
- * changes resulted in problems for the parent. See
- * [BACKTRACK] for an example of this. In the current
- * implementation, this backtracking can only be triggered when
- * a node is removed and if that node was the sole node, the
- * parent would need to be removed. As such, it is easy to see
- * that backtracking only incurs constant overhead. If more
- * sophisticated backtracking is implemented, care must be
- * taken to avoid nontermination or exponential blowup.
+ * @todo Enable nodes to be bubbled out of the structure. This is
+ * easier with our new algorithm.
*/
class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
{
- public function execute($tokens, $config, $context) {
+ /**
+ * @param HTMLPurifier_Token[] $tokens
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return array|HTMLPurifier_Token[]
+ */
+ public function execute($tokens, $config, $context)
+ {
+
//####################################################################//
// Pre-processing
+ // O(n) pass to convert to a tree, so that we can efficiently
+ // refer to substrings
+ $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context);
+
// get a copy of the HTML definition
$definition = $config->getHTMLDefinition();
$excludes_enabled = !$config->get('Core.DisableExcludes');
- // insert implicit "parent" node, will be removed at end.
- // DEFINITION CALL
- $parent_name = $definition->info_parent;
- array_unshift($tokens, new HTMLPurifier_Token_Start($parent_name));
- $tokens[] = new HTMLPurifier_Token_End($parent_name);
-
// setup the context variable 'IsInline', for chameleon processing
// is 'false' when we are not inline, 'true' when it must always
// be inline, and an integer when it is inline for a certain
@@ -15608,272 +19465,116 @@ public function execute($tokens, $config, $context) {
//####################################################################//
// Loop initialization
- // stack that contains the indexes of all parents,
- // $stack[count($stack)-1] being the current parent
- $stack = array();
-
// stack that contains all elements that are excluded
// it is organized by parent elements, similar to $stack,
// but it is only populated when an element with exclusions is
// processed, i.e. there won't be empty exclusions.
- $exclude_stack = array();
+ $exclude_stack = array($definition->info_parent_def->excludes);
// variable that contains the start token while we are processing
// nodes. This enables error reporting to do its job
- $start_token = false;
- $context->register('CurrentToken', $start_token);
+ $node = $top_node;
+ // dummy token
+ list($token, $d) = $node->toTokenPair();
+ $context->register('CurrentNode', $node);
+ $context->register('CurrentToken', $token);
//####################################################################//
// Loop
- // iterate through all start nodes. Determining the start node
- // is complicated so it has been omitted from the loop construct
- for ($i = 0, $size = count($tokens) ; $i < $size; ) {
-
- //################################################################//
- // Gather information on children
-
- // child token accumulator
- $child_tokens = array();
-
- // scroll to the end of this node, report number, and collect
- // all children
- for ($j = $i, $depth = 0; ; $j++) {
- if ($tokens[$j] instanceof HTMLPurifier_Token_Start) {
- $depth++;
- // skip token assignment on first iteration, this is the
- // token we currently are on
- if ($depth == 1) continue;
- } elseif ($tokens[$j] instanceof HTMLPurifier_Token_End) {
- $depth--;
- // skip token assignment on last iteration, this is the
- // end token of the token we're currently on
- if ($depth == 0) break;
- }
- $child_tokens[] = $tokens[$j];
- }
-
- // $i is index of start token
- // $j is index of end token
-
- $start_token = $tokens[$i]; // to make token available via CurrentToken
-
- //################################################################//
- // Gather information on parent
-
- // calculate parent information
- if ($count = count($stack)) {
- $parent_index = $stack[$count-1];
- $parent_name = $tokens[$parent_index]->name;
- if ($parent_index == 0) {
- $parent_def = $definition->info_parent_def;
- } else {
- $parent_def = $definition->info[$parent_name];
- }
- } else {
- // processing as if the parent were the "root" node
- // unknown info, it won't be used anyway, in the future,
- // we may want to enforce one element only (this is
- // necessary for HTML Purifier to clean entire documents
- $parent_index = $parent_name = $parent_def = null;
- }
-
- // calculate context
- if ($is_inline === false) {
- // check if conditions make it inline
- if (!empty($parent_def) && $parent_def->descendants_are_inline) {
- $is_inline = $count - 1;
- }
- } else {
- // check if we're out of inline
- if ($count === $is_inline) {
- $is_inline = false;
- }
- }
-
- //################################################################//
- // Determine whether element is explicitly excluded SGML-style
+ // We need to implement a post-order traversal iteratively, to
+ // avoid running into stack space limits. This is pretty tricky
+ // to reason about, so we just manually stack-ify the recursive
+ // variant:
+ //
+ // function f($node) {
+ // foreach ($node->children as $child) {
+ // f($child);
+ // }
+ // validate($node);
+ // }
+ //
+ // Thus, we will represent a stack frame as array($node,
+ // $is_inline, stack of children)
+ // e.g. array_reverse($node->children) - already processed
+ // children.
+
+ $parent_def = $definition->info_parent_def;
+ $stack = array(
+ array($top_node,
+ $parent_def->descendants_are_inline,
+ $parent_def->excludes, // exclusions
+ 0)
+ );
- // determine whether or not element is excluded by checking all
- // parent exclusions. The array should not be very large, two
- // elements at most.
- $excluded = false;
- if (!empty($exclude_stack) && $excludes_enabled) {
- foreach ($exclude_stack as $lookup) {
- if (isset($lookup[$tokens[$i]->name])) {
- $excluded = true;
- // no need to continue processing
- break;
- }
+ while (!empty($stack)) {
+ list($node, $is_inline, $excludes, $ix) = array_pop($stack);
+ // recursive call
+ $go = false;
+ $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name];
+ while (isset($node->children[$ix])) {
+ $child = $node->children[$ix++];
+ if ($child instanceof HTMLPurifier_Node_Element) {
+ $go = true;
+ $stack[] = array($node, $is_inline, $excludes, $ix);
+ $stack[] = array($child,
+ // ToDo: I don't think it matters if it's def or
+ // child_def, but double check this...
+ $is_inline || $def->descendants_are_inline,
+ empty($def->excludes) ? $excludes
+ : array_merge($excludes, $def->excludes),
+ 0);
+ break;
}
- }
-
- //################################################################//
- // Perform child validation
-
- if ($excluded) {
- // there is an exclusion, remove the entire node
- $result = false;
- $excludes = array(); // not used, but good to initialize anyway
+ };
+ if ($go) continue;
+ list($token, $d) = $node->toTokenPair();
+ // base case
+ if ($excludes_enabled && isset($excludes[$node->name])) {
+ $node->dead = true;
+ if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
} else {
- // DEFINITION CALL
- if ($i === 0) {
- // special processing for the first node
- $def = $definition->info_parent_def;
- } else {
- $def = $definition->info[$tokens[$i]->name];
-
+ // XXX I suppose it would be slightly more efficient to
+ // avoid the allocation here and have children
+ // strategies handle it
+ $children = array();
+ foreach ($node->children as $child) {
+ if (!$child->dead) $children[] = $child;
}
-
- if (!empty($def->child)) {
- // have DTD child def validate children
- $result = $def->child->validateChildren(
- $child_tokens, $config, $context);
+ $result = $def->child->validateChildren($children, $config, $context);
+ if ($result === true) {
+ // nop
+ $node->children = $children;
+ } elseif ($result === false) {
+ $node->dead = true;
+ if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
} else {
- // weird, no child definition, get rid of everything
- $result = false;
- }
-
- // determine whether or not this element has any exclusions
- $excludes = $def->excludes;
- }
-
- // $result is now a bool or array
-
- //################################################################//
- // Process result by interpreting $result
-
- if ($result === true || $child_tokens === $result) {
- // leave the node as is
-
- // register start token as a parental node start
- $stack[] = $i;
-
- // register exclusions if there are any
- if (!empty($excludes)) $exclude_stack[] = $excludes;
-
- // move cursor to next possible start node
- $i++;
-
- } elseif($result === false) {
- // remove entire node
-
- if ($e) {
- if ($excluded) {
- $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
- } else {
- $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
- }
- }
-
- // calculate length of inner tokens and current tokens
- $length = $j - $i + 1;
-
- // perform removal
- array_splice($tokens, $i, $length);
-
- // update size
- $size -= $length;
-
- // there is no start token to register,
- // current node is now the next possible start node
- // unless it turns out that we need to do a double-check
-
- // this is a rought heuristic that covers 100% of HTML's
- // cases and 99% of all other cases. A child definition
- // that would be tricked by this would be something like:
- // ( | a b c) where it's all or nothing. Fortunately,
- // our current implementation claims that that case would
- // not allow empty, even if it did
- if (!$parent_def->child->allow_empty) {
- // we need to do a double-check [BACKTRACK]
- $i = $parent_index;
- array_pop($stack);
- }
-
- // PROJECTED OPTIMIZATION: Process all children elements before
- // reprocessing parent node.
-
- } else {
- // replace node with $result
-
- // calculate length of inner tokens
- $length = $j - $i - 1;
-
- if ($e) {
- if (empty($result) && $length) {
- $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
- } else {
- $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
- }
- }
-
- // perform replacement
- array_splice($tokens, $i + 1, $length, $result);
-
- // update size
- $size -= $length;
- $size += count($result);
-
- // register start token as a parental node start
- $stack[] = $i;
-
- // register exclusions if there are any
- if (!empty($excludes)) $exclude_stack[] = $excludes;
-
- // move cursor to next possible start node
- $i++;
-
- }
-
- //################################################################//
- // Scroll to next start node
-
- // We assume, at this point, that $i is the index of the token
- // that is the first possible new start point for a node.
-
- // Test if the token indeed is a start tag, if not, move forward
- // and test again.
- $size = count($tokens);
- while ($i < $size and !$tokens[$i] instanceof HTMLPurifier_Token_Start) {
- if ($tokens[$i] instanceof HTMLPurifier_Token_End) {
- // pop a token index off the stack if we ended a node
- array_pop($stack);
- // pop an exclusion lookup off exclusion stack if
- // we ended node and that node had exclusions
- if ($i == 0 || $i == $size - 1) {
- // use specialized var if it's the super-parent
- $s_excludes = $definition->info_parent_def->excludes;
- } else {
- $s_excludes = $definition->info[$tokens[$i]->name]->excludes;
- }
- if ($s_excludes) {
- array_pop($exclude_stack);
+ $node->children = $result;
+ if ($e) {
+ // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators
+ if (empty($result) && !empty($children)) {
+ $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
+ } else if ($result != $children) {
+ $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
+ }
}
}
- $i++;
}
-
}
//####################################################################//
// Post-processing
- // remove implicit parent tokens at the beginning and end
- array_shift($tokens);
- array_pop($tokens);
-
// remove context variables
$context->destroy('IsInline');
+ $context->destroy('CurrentNode');
$context->destroy('CurrentToken');
//####################################################################//
// Return
- return $tokens;
-
+ return HTMLPurifier_Arborize::flatten($node, $config, $context);
}
-
}
@@ -15896,66 +19597,83 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
/**
* Array stream of tokens being processed.
+ * @type HTMLPurifier_Token[]
*/
protected $tokens;
/**
- * Current index in $tokens.
+ * Current token.
+ * @type HTMLPurifier_Token
+ */
+ protected $token;
+
+ /**
+ * Zipper managing the true state.
+ * @type HTMLPurifier_Zipper
*/
- protected $t;
+ protected $zipper;
/**
* Current nesting of elements.
+ * @type array
*/
protected $stack;
/**
* Injectors active in this stream processing.
+ * @type HTMLPurifier_Injector[]
*/
protected $injectors;
/**
* Current instance of HTMLPurifier_Config.
+ * @type HTMLPurifier_Config
*/
protected $config;
/**
* Current instance of HTMLPurifier_Context.
+ * @type HTMLPurifier_Context
*/
protected $context;
- public function execute($tokens, $config, $context) {
-
+ /**
+ * @param HTMLPurifier_Token[] $tokens
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return HTMLPurifier_Token[]
+ * @throws HTMLPurifier_Exception
+ */
+ public function execute($tokens, $config, $context)
+ {
$definition = $config->getHTMLDefinition();
// local variables
$generator = new HTMLPurifier_Generator($config, $context);
$escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
// used for autoclose early abortion
- $global_parent_allowed_elements = array();
- if (isset($definition->info[$definition->info_parent])) {
- // may be unset under testing circumstances
- $global_parent_allowed_elements = $definition->info[$definition->info_parent]->child->getAllowedElements($config);
- }
+ $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config);
$e = $context->get('ErrorCollector', true);
- $t = false; // token index
$i = false; // injector index
- $token = false; // the current token
- $reprocess = false; // whether or not to reprocess the same token
+ list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens);
+ if ($token === NULL) {
+ return array();
+ }
+ $reprocess = false; // whether or not to reprocess the same token
$stack = array();
// member variables
- $this->stack =& $stack;
- $this->t =& $t;
- $this->tokens =& $tokens;
- $this->config = $config;
+ $this->stack =& $stack;
+ $this->tokens =& $tokens;
+ $this->token =& $token;
+ $this->zipper =& $zipper;
+ $this->config = $config;
$this->context = $context;
// context variables
$context->register('CurrentNesting', $stack);
- $context->register('InputIndex', $t);
- $context->register('InputTokens', $tokens);
- $context->register('CurrentToken', $token);
+ $context->register('InputZipper', $zipper);
+ $context->register('CurrentToken', $token);
// -- begin INJECTOR --
@@ -15967,9 +19685,13 @@ public function execute($tokens, $config, $context) {
unset($injectors['Custom']); // special case
foreach ($injectors as $injector => $b) {
// XXX: Fix with a legitimate lookup table of enabled filters
- if (strpos($injector, '.') !== false) continue;
+ if (strpos($injector, '.') !== false) {
+ continue;
+ }
$injector = "HTMLPurifier_Injector_$injector";
- if (!$b) continue;
+ if (!$b) {
+ continue;
+ }
$this->injectors[] = new $injector;
}
foreach ($def_injectors as $injector) {
@@ -15977,7 +19699,9 @@ public function execute($tokens, $config, $context) {
$this->injectors[] = $injector;
}
foreach ($custom_injectors as $injector) {
- if (!$injector) continue;
+ if (!$injector) {
+ continue;
+ }
if (is_string($injector)) {
$injector = "HTMLPurifier_Injector_$injector";
$injector = new $injector;
@@ -15989,7 +19713,9 @@ public function execute($tokens, $config, $context) {
// variables for performance reasons
foreach ($this->injectors as $ix => $injector) {
$error = $injector->prepare($config, $context);
- if (!$error) continue;
+ if (!$error) {
+ continue;
+ }
array_splice($this->injectors, $ix, 1); // rm the injector
trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
}
@@ -16005,39 +19731,40 @@ public function execute($tokens, $config, $context) {
// punt ($reprocess = true; continue;) and it does that for us.
// isset is in loop because $tokens size changes during loop exec
- for (
- $t = 0;
- $t == 0 || isset($tokens[$t - 1]);
- // only increment if we don't need to reprocess
- $reprocess ? $reprocess = false : $t++
- ) {
+ for (;;
+ // only increment if we don't need to reprocess
+ $reprocess ? $reprocess = false : $token = $zipper->next($token)) {
// check for a rewind
- if (is_int($i) && $i >= 0) {
+ if (is_int($i)) {
// possibility: disable rewinding if the current token has a
// rewind set on it already. This would offer protection from
// infinite loop, but might hinder some advanced rewinding.
- $rewind_to = $this->injectors[$i]->getRewind();
- if (is_int($rewind_to) && $rewind_to < $t) {
- if ($rewind_to < 0) $rewind_to = 0;
- while ($t > $rewind_to) {
- $t--;
- $prev = $tokens[$t];
+ $rewind_offset = $this->injectors[$i]->getRewindOffset();
+ if (is_int($rewind_offset)) {
+ for ($j = 0; $j < $rewind_offset; $j++) {
+ if (empty($zipper->front)) break;
+ $token = $zipper->prev($token);
// indicate that other injectors should not process this token,
// but we need to reprocess it
- unset($prev->skip[$i]);
- $prev->rewind = $i;
- if ($prev instanceof HTMLPurifier_Token_Start) array_pop($this->stack);
- elseif ($prev instanceof HTMLPurifier_Token_End) $this->stack[] = $prev->start;
+ unset($token->skip[$i]);
+ $token->rewind = $i;
+ if ($token instanceof HTMLPurifier_Token_Start) {
+ array_pop($this->stack);
+ } elseif ($token instanceof HTMLPurifier_Token_End) {
+ $this->stack[] = $token->start;
+ }
}
}
$i = false;
}
// handle case of document end
- if (!isset($tokens[$t])) {
+ if ($token === NULL) {
// kill processing if stack is empty
- if (empty($this->stack)) break;
+ if (empty($this->stack)) {
+ break;
+ }
// peek
$top_nesting = array_pop($this->stack);
@@ -16049,26 +19776,30 @@ public function execute($tokens, $config, $context) {
}
// append, don't splice, since this is the end
- $tokens[] = new HTMLPurifier_Token_End($top_nesting->name);
+ $token = new HTMLPurifier_Token_End($top_nesting->name);
// punt!
$reprocess = true;
continue;
}
- $token = $tokens[$t];
-
- //echo ' '; printTokens($tokens, $t); printTokens($this->stack);
+ //echo ' '; printZipper($zipper, $token);//printTokens($this->stack);
//flush();
// quick-check: if it's not a tag, no need to process
if (empty($token->is_tag)) {
if ($token instanceof HTMLPurifier_Token_Text) {
foreach ($this->injectors as $i => $injector) {
- if (isset($token->skip[$i])) continue;
- if ($token->rewind !== null && $token->rewind !== $i) continue;
- $injector->handleText($token);
- $this->processToken($token, $i);
+ if (isset($token->skip[$i])) {
+ continue;
+ }
+ if ($token->rewind !== null && $token->rewind !== $i) {
+ continue;
+ }
+ // XXX fuckup
+ $r = $token;
+ $injector->handleText($r);
+ $token = $this->processToken($r, $i);
$reprocess = true;
break;
}
@@ -16087,12 +19818,22 @@ public function execute($tokens, $config, $context) {
$ok = false;
if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
// claims to be a start tag but is empty
- $token = new HTMLPurifier_Token_Empty($token->name, $token->attr, $token->line, $token->col, $token->armor);
+ $token = new HTMLPurifier_Token_Empty(
+ $token->name,
+ $token->attr,
+ $token->line,
+ $token->col,
+ $token->armor
+ );
$ok = true;
} elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
// claims to be empty but really is a start tag
- $this->swap(new HTMLPurifier_Token_End($token->name));
- $this->insertBefore(new HTMLPurifier_Token_Start($token->name, $token->attr, $token->line, $token->col, $token->armor));
+ // NB: this assignment is required
+ $old_token = $token;
+ $token = new HTMLPurifier_Token_End($token->name);
+ $token = $this->insertBefore(
+ new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
+ );
// punt (since we had to modify the input stream in a non-trivial way)
$reprocess = true;
continue;
@@ -16121,31 +19862,32 @@ public function execute($tokens, $config, $context) {
$parent = array_pop($this->stack);
$this->stack[] = $parent;
+ $parent_def = null;
+ $parent_elements = null;
+ $autoclose = false;
if (isset($definition->info[$parent->name])) {
- $elements = $definition->info[$parent->name]->child->getAllowedElements($config);
- $autoclose = !isset($elements[$token->name]);
- } else {
- $autoclose = false;
+ $parent_def = $definition->info[$parent->name];
+ $parent_elements = $parent_def->child->getAllowedElements($config);
+ $autoclose = !isset($parent_elements[$token->name]);
}
if ($autoclose && $definition->info[$token->name]->wrap) {
- // Check if an element can be wrapped by another
- // element to make it valid in a context (for
+ // Check if an element can be wrapped by another
+ // element to make it valid in a context (for
// example,
needs a
in between)
$wrapname = $definition->info[$token->name]->wrap;
$wrapdef = $definition->info[$wrapname];
$elements = $wrapdef->child->getAllowedElements($config);
- $parent_elements = $definition->info[$parent->name]->child->getAllowedElements($config);
if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
$newtoken = new HTMLPurifier_Token_Start($wrapname);
- $this->insertBefore($newtoken);
+ $token = $this->insertBefore($newtoken);
$reprocess = true;
continue;
}
}
$carryover = false;
- if ($autoclose && $definition->info[$parent->name]->formatting) {
+ if ($autoclose && $parent_def->formatting) {
$carryover = true;
}
@@ -16175,15 +19917,6 @@ public function execute($tokens, $config, $context) {
// errors need to be updated
$new_token = new HTMLPurifier_Token_End($parent->name);
$new_token->start = $parent;
- if ($carryover) {
- $element = clone $parent;
- // [TagClosedAuto]
- $element->armor['MakeWellFormed_TagClosedError'] = true;
- $element->carryover = true;
- $this->processToken(array($new_token, $token, $element));
- } else {
- $this->insertBefore($new_token);
- }
// [TagClosedSuppress]
if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {
if (!$carryover) {
@@ -16192,8 +19925,17 @@ public function execute($tokens, $config, $context) {
$e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);
}
}
+ if ($carryover) {
+ $element = clone $parent;
+ // [TagClosedAuto]
+ $element->armor['MakeWellFormed_TagClosedError'] = true;
+ $element->carryover = true;
+ $token = $this->processToken(array($new_token, $token, $element));
+ } else {
+ $token = $this->insertBefore($new_token);
+ }
} else {
- $this->remove();
+ $token = $this->remove();
}
$reprocess = true;
continue;
@@ -16205,20 +19947,26 @@ public function execute($tokens, $config, $context) {
if ($ok) {
foreach ($this->injectors as $i => $injector) {
- if (isset($token->skip[$i])) continue;
- if ($token->rewind !== null && $token->rewind !== $i) continue;
- $injector->handleElement($token);
- $this->processToken($token, $i);
+ if (isset($token->skip[$i])) {
+ continue;
+ }
+ if ($token->rewind !== null && $token->rewind !== $i) {
+ continue;
+ }
+ $r = $token;
+ $injector->handleElement($r);
+ $token = $this->processToken($r, $i);
$reprocess = true;
break;
}
if (!$reprocess) {
// ah, nothing interesting happened; do normal processing
- $this->swap($token);
if ($token instanceof HTMLPurifier_Token_Start) {
$this->stack[] = $token;
} elseif ($token instanceof HTMLPurifier_Token_End) {
- throw new HTMLPurifier_Exception('Improper handling of end tag in start code; possible error in MakeWellFormed');
+ throw new HTMLPurifier_Exception(
+ 'Improper handling of end tag in start code; possible error in MakeWellFormed'
+ );
}
}
continue;
@@ -16232,13 +19980,15 @@ public function execute($tokens, $config, $context) {
// make sure that we have something open
if (empty($this->stack)) {
if ($escape_invalid_tags) {
- if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
- $this->swap(new HTMLPurifier_Token_Text(
- $generator->generateFromToken($token)
- ));
+ if ($e) {
+ $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
+ }
+ $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
} else {
- $this->remove();
- if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
+ if ($e) {
+ $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
+ }
+ $token = $this->remove();
}
$reprocess = true;
continue;
@@ -16252,10 +20002,15 @@ public function execute($tokens, $config, $context) {
if ($current_parent->name == $token->name) {
$token->start = $current_parent;
foreach ($this->injectors as $i => $injector) {
- if (isset($token->skip[$i])) continue;
- if ($token->rewind !== null && $token->rewind !== $i) continue;
- $injector->handleEnd($token);
- $this->processToken($token, $i);
+ if (isset($token->skip[$i])) {
+ continue;
+ }
+ if ($token->rewind !== null && $token->rewind !== $i) {
+ continue;
+ }
+ $r = $token;
+ $injector->handleEnd($r);
+ $token = $this->processToken($r, $i);
$this->stack[] = $current_parent;
$reprocess = true;
break;
@@ -16283,13 +20038,15 @@ public function execute($tokens, $config, $context) {
// we didn't find the tag, so remove
if ($skipped_tags === false) {
if ($escape_invalid_tags) {
- $this->swap(new HTMLPurifier_Token_Text(
- $generator->generateFromToken($token)
- ));
- if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
+ if ($e) {
+ $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
+ }
+ $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token));
} else {
- $this->remove();
- if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
+ if ($e) {
+ $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
+ }
+ $token = $this->remove();
}
$reprocess = true;
continue;
@@ -16322,18 +20079,17 @@ public function execute($tokens, $config, $context) {
$replace[] = $element;
}
}
- $this->processToken($replace);
+ $token = $this->processToken($replace);
$reprocess = true;
continue;
}
- $context->destroy('CurrentNesting');
- $context->destroy('InputTokens');
- $context->destroy('InputIndex');
$context->destroy('CurrentToken');
+ $context->destroy('CurrentNesting');
+ $context->destroy('InputZipper');
- unset($this->injectors, $this->stack, $this->tokens, $this->t);
- return $tokens;
+ unset($this->injectors, $this->stack, $this->tokens);
+ return $zipper->toArray($token);
}
/**
@@ -16352,25 +20108,38 @@ public function execute($tokens, $config, $context) {
* If $token is an integer, that number of tokens (with the first token
* being the current one) will be deleted.
*
- * @param $token Token substitution value
- * @param $injector Injector that performed the substitution; default is if
+ * @param HTMLPurifier_Token|array|int|bool $token Token substitution value
+ * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if
* this is not an injector related operation.
+ * @throws HTMLPurifier_Exception
*/
- protected function processToken($token, $injector = -1) {
-
+ protected function processToken($token, $injector = -1)
+ {
// normalize forms of token
- if (is_object($token)) $token = array(1, $token);
- if (is_int($token)) $token = array($token);
- if ($token === false) $token = array(1);
- if (!is_array($token)) throw new HTMLPurifier_Exception('Invalid token type from injector');
- if (!is_int($token[0])) array_unshift($token, 1);
- if ($token[0] === 0) throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
+ if (is_object($token)) {
+ $token = array(1, $token);
+ }
+ if (is_int($token)) {
+ $token = array($token);
+ }
+ if ($token === false) {
+ $token = array(1);
+ }
+ if (!is_array($token)) {
+ throw new HTMLPurifier_Exception('Invalid token type from injector');
+ }
+ if (!is_int($token[0])) {
+ array_unshift($token, 1);
+ }
+ if ($token[0] === 0) {
+ throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
+ }
// $token is now an array with the following form:
// array(number nodes to delete, new node 1, new node 2, ...)
$delete = array_shift($token);
- $old = array_splice($this->tokens, $this->t, $delete, $token);
+ list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
if ($injector > -1) {
// determine appropriate skips
@@ -16381,32 +20150,32 @@ protected function processToken($token, $injector = -1) {
}
}
+ return $r;
+
}
/**
* Inserts a token before the current token. Cursor now points to
* this token. You must reprocess after this.
+ * @param HTMLPurifier_Token $token
*/
- private function insertBefore($token) {
- array_splice($this->tokens, $this->t, 0, array($token));
+ private function insertBefore($token)
+ {
+ // NB not $this->zipper->insertBefore(), due to positioning
+ // differences
+ $splice = $this->zipper->splice($this->token, 0, array($token));
+
+ return $splice[1];
}
/**
* Removes current token. Cursor now points to new token occupying previously
* occupied space. You must reprocess after this.
*/
- private function remove() {
- array_splice($this->tokens, $this->t, 1);
- }
-
- /**
- * Swap current token with new token. Cursor points to new token (no
- * change). You must reprocess after this.
- */
- private function swap($token) {
- $this->tokens[$this->t] = $token;
+ private function remove()
+ {
+ return $this->zipper->delete();
}
-
}
@@ -16424,13 +20193,20 @@ private function swap($token) {
class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
{
- public function execute($tokens, $config, $context) {
+ /**
+ * @param HTMLPurifier_Token[] $tokens
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return array|HTMLPurifier_Token[]
+ */
+ public function execute($tokens, $config, $context)
+ {
$definition = $config->getHTMLDefinition();
$generator = new HTMLPurifier_Generator($config, $context);
$result = array();
$escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
- $remove_invalid_img = $config->get('Core.RemoveInvalidImg');
+ $remove_invalid_img = $config->get('Core.RemoveInvalidImg');
// currently only used to determine if comments should be kept
$trusted = $config->get('HTML.Trusted');
@@ -16439,7 +20215,7 @@ public function execute($tokens, $config, $context) {
$check_comments = $comment_lookup !== array() || $comment_regexp !== null;
$remove_script_contents = $config->get('Core.RemoveScriptContents');
- $hidden_elements = $config->get('Core.HiddenElements');
+ $hidden_elements = $config->get('Core.HiddenElements');
// remove script contents compatibility
if ($remove_script_contents === true) {
@@ -16464,34 +20240,31 @@ public function execute($tokens, $config, $context) {
$e =& $context->get('ErrorCollector');
}
- foreach($tokens as $token) {
+ foreach ($tokens as $token) {
if ($remove_until) {
if (empty($token->is_tag) || $token->name !== $remove_until) {
continue;
}
}
- if (!empty( $token->is_tag )) {
+ if (!empty($token->is_tag)) {
// DEFINITION CALL
// before any processing, try to transform the element
- if (
- isset($definition->info_tag_transform[$token->name])
- ) {
+ if (isset($definition->info_tag_transform[$token->name])) {
$original_name = $token->name;
// there is a transformation for this tag
// DEFINITION CALL
$token = $definition->
- info_tag_transform[$token->name]->
- transform($token, $config, $context);
- if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
+ info_tag_transform[$token->name]->transform($token, $config, $context);
+ if ($e) {
+ $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
+ }
}
if (isset($definition->info[$token->name])) {
-
// mostly everything's good, but
// we need to make sure required attributes are in order
- if (
- ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
+ if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
$definition->info[$token->name]->required_attr &&
($token->name != 'img' || $remove_invalid_img) // ensure config option still works
) {
@@ -16504,7 +20277,13 @@ public function execute($tokens, $config, $context) {
}
}
if (!$ok) {
- if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', $name);
+ if ($e) {
+ $e->send(
+ E_ERROR,
+ 'Strategy_RemoveForeignElements: Missing required attribute',
+ $name
+ );
+ }
continue;
}
$token->armor['ValidateAttributes'] = true;
@@ -16518,7 +20297,9 @@ public function execute($tokens, $config, $context) {
} elseif ($escape_invalid_tags) {
// invalid tag, generate HTML representation and insert in
- if ($e) $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
+ if ($e) {
+ $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
+ }
$token = new HTMLPurifier_Token_Text(
$generator->generateFromToken($token)
);
@@ -16533,9 +20314,13 @@ public function execute($tokens, $config, $context) {
} else {
$remove_until = false;
}
- if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
+ if ($e) {
+ $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
+ }
} else {
- if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
+ if ($e) {
+ $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
+ }
}
continue;
}
@@ -16559,11 +20344,15 @@ public function execute($tokens, $config, $context) {
$found_double_hyphen = true;
$token->data = str_replace('--', '-', $token->data);
}
- if ($trusted || !empty($comment_lookup[trim($token->data)]) || ($comment_regexp !== NULL && preg_match($comment_regexp, trim($token->data)))) {
+ if ($trusted || !empty($comment_lookup[trim($token->data)]) ||
+ ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {
// OK good
if ($e) {
if ($trailing_hyphen) {
- $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed');
+ $e->send(
+ E_NOTICE,
+ 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
+ );
}
if ($found_double_hyphen) {
$e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
@@ -16577,7 +20366,9 @@ public function execute($tokens, $config, $context) {
}
} else {
// strip comments
- if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
+ if ($e) {
+ $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
+ }
continue;
}
} elseif ($token instanceof HTMLPurifier_Token_Text) {
@@ -16590,12 +20381,9 @@ public function execute($tokens, $config, $context) {
// we removed tokens until the end, throw error
$e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
}
-
$context->destroy('CurrentToken');
-
return $result;
}
-
}
@@ -16609,8 +20397,14 @@ public function execute($tokens, $config, $context) {
class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
{
- public function execute($tokens, $config, $context) {
-
+ /**
+ * @param HTMLPurifier_Token[] $tokens
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return HTMLPurifier_Token[]
+ */
+ public function execute($tokens, $config, $context)
+ {
// setup validator
$validator = new HTMLPurifier_AttrValidator();
@@ -16621,21 +20415,21 @@ public function execute($tokens, $config, $context) {
// only process tokens that have attributes,
// namely start and empty tags
- if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) continue;
+ if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {
+ continue;
+ }
// skip tokens that are armored
- if (!empty($token->armor['ValidateAttributes'])) continue;
+ if (!empty($token->armor['ValidateAttributes'])) {
+ continue;
+ }
// note that we have no facilities here for removing tokens
$validator->validateToken($token, $config, $context);
-
- $tokens[$key] = $token; // for PHP 4
}
$context->destroy('CurrentToken');
-
return $tokens;
}
-
}
@@ -16659,9 +20453,14 @@ public function execute($tokens, $config, $context) {
*/
class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
{
-
+ /**
+ * @type string
+ */
public $transform_to = 'span';
+ /**
+ * @type array
+ */
protected $_size_lookup = array(
'0' => 'xx-small',
'1' => 'xx-small',
@@ -16679,8 +20478,14 @@ class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
'+4' => '300%'
);
- public function transform($tag, $config, $context) {
-
+ /**
+ * @param HTMLPurifier_Token_Tag $tag
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return HTMLPurifier_Token_End|string
+ */
+ public function transform($tag, $config, $context)
+ {
if ($tag instanceof HTMLPurifier_Token_End) {
$new_tag = clone $tag;
$new_tag->name = $this->transform_to;
@@ -16707,17 +20512,23 @@ public function transform($tag, $config, $context) {
// normalize large numbers
if ($attr['size'] !== '') {
if ($attr['size']{0} == '+' || $attr['size']{0} == '-') {
- $size = (int) $attr['size'];
- if ($size < -2) $attr['size'] = '-2';
- if ($size > 4) $attr['size'] = '+4';
+ $size = (int)$attr['size'];
+ if ($size < -2) {
+ $attr['size'] = '-2';
+ }
+ if ($size > 4) {
+ $attr['size'] = '+4';
+ }
} else {
- $size = (int) $attr['size'];
- if ($size > 7) $attr['size'] = '7';
+ $size = (int)$attr['size'];
+ if ($size > 7) {
+ $attr['size'] = '7';
+ }
}
}
if (isset($this->_size_lookup[$attr['size']])) {
$prepend_style .= 'font-size:' .
- $this->_size_lookup[$attr['size']] . ';';
+ $this->_size_lookup[$attr['size']] . ';';
}
unset($attr['size']);
}
@@ -16733,7 +20544,6 @@ public function transform($tag, $config, $context) {
$new_tag->attr = $attr;
return $new_tag;
-
}
}
@@ -16748,19 +20558,29 @@ public function transform($tag, $config, $context) {
*/
class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
{
-
+ /**
+ * @type string
+ */
protected $style;
/**
- * @param $transform_to Tag name to transform to.
- * @param $style CSS style to add to the tag
+ * @param string $transform_to Tag name to transform to.
+ * @param string $style CSS style to add to the tag
*/
- public function __construct($transform_to, $style = null) {
+ public function __construct($transform_to, $style = null)
+ {
$this->transform_to = $transform_to;
$this->style = $style;
}
- public function transform($tag, $config, $context) {
+ /**
+ * @param HTMLPurifier_Token_Tag $tag
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
+ */
+ public function transform($tag, $config, $context)
+ {
$new_tag = clone $tag;
$new_tag->name = $this->transform_to;
if (!is_null($this->style) &&
@@ -16770,7 +20590,6 @@ public function transform($tag, $config, $context) {
}
return $new_tag;
}
-
}
@@ -16782,17 +20601,33 @@ public function transform($tag, $config, $context) {
*/
class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
{
- public $data; /**< Character data within comment. */
+ /**
+ * Character data within comment.
+ * @type string
+ */
+ public $data;
+
+ /**
+ * @type bool
+ */
public $is_whitespace = true;
+
/**
* Transparent constructor.
*
- * @param $data String comment data.
+ * @param string $data String comment data.
+ * @param int $line
+ * @param int $col
*/
- public function __construct($data, $line = null, $col = null) {
+ public function __construct($data, $line = null, $col = null)
+ {
$this->data = $data;
$this->line = $line;
- $this->col = $col;
+ $this->col = $col;
+ }
+
+ public function toNode() {
+ return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
}
}
@@ -16803,13 +20638,14 @@ public function __construct($data, $line = null, $col = null) {
/**
* Abstract class of a tag token (start, end or empty), and its behavior.
*/
-class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
+abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
{
/**
* Static bool marker that indicates the class is a tag.
*
* This allows us to check objects with !empty($obj->is_tag)
* without having to use a function call is_a().
+ * @type bool
*/
public $is_tag = true;
@@ -16819,21 +20655,27 @@ class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
* @note Strictly speaking, XML tags are case sensitive, so we shouldn't
* be lower-casing them, but these tokens cater to HTML tags, which are
* insensitive.
+ * @type string
*/
public $name;
/**
* Associative array of the tag's attributes.
+ * @type array
*/
public $attr = array();
/**
* Non-overloaded constructor, which lower-cases passed tag name.
*
- * @param $name String name.
- * @param $attr Associative array of attributes.
- */
- public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
+ * @param string $name String name.
+ * @param array $attr Associative array of attributes.
+ * @param int $line
+ * @param int $col
+ * @param array $armor
+ */
+ public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array())
+ {
$this->name = ctype_lower($name) ? $name : strtolower($name);
foreach ($attr as $key => $value) {
// normalization only necessary when key is not lowercase
@@ -16849,9 +20691,13 @@ public function __construct($name, $attr = array(), $line = null, $col = null, $
}
$this->attr = $attr;
$this->line = $line;
- $this->col = $col;
+ $this->col = $col;
$this->armor = $armor;
}
+
+ public function toNode() {
+ return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
+ }
}
@@ -16863,7 +20709,11 @@ public function __construct($name, $attr = array(), $line = null, $col = null, $
*/
class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
{
-
+ public function toNode() {
+ $n = parent::toNode();
+ $n->empty = true;
+ return $n;
+ }
}
@@ -16880,10 +20730,15 @@ class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
{
/**
- * Token that started this node. Added by MakeWellFormed. Please
- * do not edit this!
+ * Token that started this node.
+ * Added by MakeWellFormed. Please do not edit this!
+ * @type HTMLPurifier_Token
*/
public $start;
+
+ public function toNode() {
+ throw new Exception("HTMLPurifier_Token_End->toNode not supported!");
+ }
}
@@ -16895,7 +20750,6 @@ class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
*/
class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
{
-
}
@@ -16914,22 +20768,42 @@ class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
class HTMLPurifier_Token_Text extends HTMLPurifier_Token
{
- public $name = '#PCDATA'; /**< PCDATA tag name compatible with DTD. */
- public $data; /**< Parsed character data of text. */
- public $is_whitespace; /**< Bool indicating if node is whitespace. */
+ /**
+ * @type string
+ */
+ public $name = '#PCDATA';
+ /**< PCDATA tag name compatible with DTD. */
+
+ /**
+ * @type string
+ */
+ public $data;
+ /**< Parsed character data of text. */
+
+ /**
+ * @type bool
+ */
+ public $is_whitespace;
+
+ /**< Bool indicating if node is whitespace. */
/**
* Constructor, accepts data and determines if it is whitespace.
- *
- * @param $data String parsed character data.
+ * @param string $data String parsed character data.
+ * @param int $line
+ * @param int $col
*/
- public function __construct($data, $line = null, $col = null) {
+ public function __construct($data, $line = null, $col = null)
+ {
$this->data = $data;
$this->is_whitespace = ctype_space($data);
$this->line = $line;
- $this->col = $col;
+ $this->col = $col;
}
+ public function toNode() {
+ return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
+ }
}
@@ -16938,19 +20812,50 @@ public function __construct($data, $line = null, $col = null) {
class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
{
+ /**
+ * @type string
+ */
public $name = 'DisableExternal';
+
+ /**
+ * @type array
+ */
protected $ourHostParts = false;
- public function prepare($config) {
+
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return void
+ */
+ public function prepare($config)
+ {
$our_host = $config->getDefinition('URI')->host;
- if ($our_host !== null) $this->ourHostParts = array_reverse(explode('.', $our_host));
+ if ($our_host !== null) {
+ $this->ourHostParts = array_reverse(explode('.', $our_host));
+ }
}
- public function filter(&$uri, $config, $context) {
- if (is_null($uri->host)) return true;
- if ($this->ourHostParts === false) return false;
+
+ /**
+ * @param HTMLPurifier_URI $uri Reference
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ if (is_null($uri->host)) {
+ return true;
+ }
+ if ($this->ourHostParts === false) {
+ return false;
+ }
$host_parts = array_reverse(explode('.', $uri->host));
foreach ($this->ourHostParts as $i => $x) {
- if (!isset($host_parts[$i])) return false;
- if ($host_parts[$i] != $this->ourHostParts[$i]) return false;
+ if (!isset($host_parts[$i])) {
+ return false;
+ }
+ if ($host_parts[$i] != $this->ourHostParts[$i]) {
+ return false;
+ }
}
return true;
}
@@ -16962,9 +20867,22 @@ public function filter(&$uri, $config, $context) {
class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
{
+ /**
+ * @type string
+ */
public $name = 'DisableExternalResources';
- public function filter(&$uri, $config, $context) {
- if (!$context->get('EmbeddedURI', true)) return true;
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ if (!$context->get('EmbeddedURI', true)) {
+ return true;
+ }
return parent::filter($uri, $config, $context);
}
}
@@ -16975,8 +20893,19 @@ public function filter(&$uri, $config, $context) {
class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
{
+ /**
+ * @type string
+ */
public $name = 'DisableResources';
- public function filter(&$uri, $config, $context) {
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
return !$context->get('EmbeddedURI', true);
}
}
@@ -16991,14 +20920,35 @@ public function filter(&$uri, $config, $context) {
// points are involved), but I'm not 100% sure
class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
{
+ /**
+ * @type string
+ */
public $name = 'HostBlacklist';
+
+ /**
+ * @type array
+ */
protected $blacklist = array();
- public function prepare($config) {
+
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return bool
+ */
+ public function prepare($config)
+ {
$this->blacklist = $config->get('URI.HostBlacklist');
return true;
}
- public function filter(&$uri, $config, $context) {
- foreach($this->blacklist as $blacklisted_host_fragment) {
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ foreach ($this->blacklist as $blacklisted_host_fragment) {
if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
return false;
}
@@ -17015,14 +20965,35 @@ public function filter(&$uri, $config, $context) {
class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
{
+ /**
+ * @type string
+ */
public $name = 'MakeAbsolute';
+
+ /**
+ * @type
+ */
protected $base;
+
+ /**
+ * @type array
+ */
protected $basePathStack = array();
- public function prepare($config) {
+
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return bool
+ */
+ public function prepare($config)
+ {
$def = $config->getDefinition('URI');
$this->base = $def->base;
if (is_null($this->base)) {
- trigger_error('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration', E_USER_WARNING);
+ trigger_error(
+ 'URI.MakeAbsolute is being ignored due to lack of ' .
+ 'value for URI.Base configuration',
+ E_USER_WARNING
+ );
return false;
}
$this->base->fragment = null; // fragment is invalid for base URI
@@ -17032,19 +21003,29 @@ public function prepare($config) {
$this->basePathStack = $stack;
return true;
}
- public function filter(&$uri, $config, $context) {
- if (is_null($this->base)) return true; // abort early
- if (
- $uri->path === '' && is_null($uri->scheme) &&
- is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)
- ) {
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ if (is_null($this->base)) {
+ return true;
+ } // abort early
+ if ($uri->path === '' && is_null($uri->scheme) &&
+ is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
// reference to current document
$uri = clone $this->base;
return true;
}
if (!is_null($uri->scheme)) {
// absolute URI already: don't change
- if (!is_null($uri->host)) return true;
+ if (!is_null($uri->host)) {
+ return true;
+ }
$scheme_obj = $uri->getSchemeObj($config, $context);
if (!$scheme_obj) {
// scheme not recognized
@@ -17077,22 +21058,33 @@ public function filter(&$uri, $config, $context) {
}
// re-combine
$uri->scheme = $this->base->scheme;
- if (is_null($uri->userinfo)) $uri->userinfo = $this->base->userinfo;
- if (is_null($uri->host)) $uri->host = $this->base->host;
- if (is_null($uri->port)) $uri->port = $this->base->port;
+ if (is_null($uri->userinfo)) {
+ $uri->userinfo = $this->base->userinfo;
+ }
+ if (is_null($uri->host)) {
+ $uri->host = $this->base->host;
+ }
+ if (is_null($uri->port)) {
+ $uri->port = $this->base->port;
+ }
return true;
}
/**
* Resolve dots and double-dots in a path stack
+ * @param array $stack
+ * @return array
*/
- private function _collapseStack($stack) {
+ private function _collapseStack($stack)
+ {
$result = array();
$is_folder = false;
for ($i = 0; isset($stack[$i]); $i++) {
$is_folder = false;
// absorb an internally duplicated slash
- if ($stack[$i] == '' && $i && isset($stack[$i+1])) continue;
+ if ($stack[$i] == '' && $i && isset($stack[$i + 1])) {
+ continue;
+ }
if ($stack[$i] == '..') {
if (!empty($result)) {
$segment = array_pop($result);
@@ -17117,7 +21109,9 @@ private function _collapseStack($stack) {
}
$result[] = $stack[$i];
}
- if ($is_folder) $result[] = '';
+ if ($is_folder) {
+ $result[] = '';
+ }
return $result;
}
}
@@ -17128,26 +21122,79 @@ private function _collapseStack($stack) {
class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
{
+ /**
+ * @type string
+ */
public $name = 'Munge';
+
+ /**
+ * @type bool
+ */
public $post = true;
- private $target, $parser, $doEmbed, $secretKey;
+ /**
+ * @type string
+ */
+ private $target;
+
+ /**
+ * @type HTMLPurifier_URIParser
+ */
+ private $parser;
+
+ /**
+ * @type bool
+ */
+ private $doEmbed;
+
+ /**
+ * @type string
+ */
+ private $secretKey;
+
+ /**
+ * @type array
+ */
protected $replace = array();
- public function prepare($config) {
- $this->target = $config->get('URI.' . $this->name);
- $this->parser = new HTMLPurifier_URIParser();
- $this->doEmbed = $config->get('URI.MungeResources');
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return bool
+ */
+ public function prepare($config)
+ {
+ $this->target = $config->get('URI.' . $this->name);
+ $this->parser = new HTMLPurifier_URIParser();
+ $this->doEmbed = $config->get('URI.MungeResources');
$this->secretKey = $config->get('URI.MungeSecretKey');
+ if ($this->secretKey && !function_exists('hash_hmac')) {
+ throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
+ }
return true;
}
- public function filter(&$uri, $config, $context) {
- if ($context->get('EmbeddedURI', true) && !$this->doEmbed) return true;
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
+ return true;
+ }
$scheme_obj = $uri->getSchemeObj($config, $context);
- if (!$scheme_obj) return true; // ignore unknown schemes, maybe another postfilter did it
- if (!$scheme_obj->browsable) return true; // ignore non-browseable schemes, since we can't munge those in a reasonable way
- if ($uri->isBenign($config, $context)) return true; // don't redirect if a benign URL
+ if (!$scheme_obj) {
+ return true;
+ } // ignore unknown schemes, maybe another postfilter did it
+ if (!$scheme_obj->browsable) {
+ return true;
+ } // ignore non-browseable schemes, since we can't munge those in a reasonable way
+ if ($uri->isBenign($config, $context)) {
+ return true;
+ } // don't redirect if a benign URL
$this->makeReplace($uri, $config, $context);
$this->replace = array_map('rawurlencode', $this->replace);
@@ -17156,12 +21203,20 @@ public function filter(&$uri, $config, $context) {
$new_uri = $this->parser->parse($new_uri);
// don't redirect if the target host is the same as the
// starting host
- if ($uri->host === $new_uri->host) return true;
+ if ($uri->host === $new_uri->host) {
+ return true;
+ }
$uri = $new_uri; // overwrite
return true;
}
- protected function makeReplace($uri, $config, $context) {
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ */
+ protected function makeReplace($uri, $config, $context)
+ {
$string = $uri->toString();
// always available
$this->replace['%s'] = $string;
@@ -17171,9 +21226,10 @@ protected function makeReplace($uri, $config, $context) {
$this->replace['%m'] = $context->get('CurrentAttr', true);
$this->replace['%p'] = $context->get('CurrentCSSProperty', true);
// not always available
- if ($this->secretKey) $this->replace['%t'] = sha1($this->secretKey . ':' . $string);
+ if ($this->secretKey) {
+ $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
+ }
}
-
}
@@ -17188,25 +21244,58 @@ protected function makeReplace($uri, $config, $context) {
*/
class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter
{
+ /**
+ * @type string
+ */
public $name = 'SafeIframe';
+
+ /**
+ * @type bool
+ */
public $always_load = true;
- protected $regexp = NULL;
- // XXX: The not so good bit about how this is all setup now is we
+
+ /**
+ * @type string
+ */
+ protected $regexp = null;
+
+ // XXX: The not so good bit about how this is all set up now is we
// can't check HTML.SafeIframe in the 'prepare' step: we have to
// defer till the actual filtering.
- public function prepare($config) {
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return bool
+ */
+ public function prepare($config)
+ {
$this->regexp = $config->get('URI.SafeIframeRegexp');
return true;
}
- public function filter(&$uri, $config, $context) {
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
// check if filter not applicable
- if (!$config->get('HTML.SafeIframe')) return true;
+ if (!$config->get('HTML.SafeIframe')) {
+ return true;
+ }
// check if the filter should actually trigger
- if (!$context->get('EmbeddedURI', true)) return true;
+ if (!$context->get('EmbeddedURI', true)) {
+ return true;
+ }
$token = $context->get('CurrentToken', true);
- if (!($token && $token->name == 'iframe')) return true;
+ if (!($token && $token->name == 'iframe')) {
+ return true;
+ }
// check if we actually have some whitelists enabled
- if ($this->regexp === null) return false;
+ if ($this->regexp === null) {
+ return false;
+ }
// actually check the whitelists
return preg_match($this->regexp, $uri->toString());
}
@@ -17219,21 +21308,38 @@ public function filter(&$uri, $config, $context) {
/**
* Implements data: URI for base64 encoded images supported by GD.
*/
-class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme {
-
+class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme
+{
+ /**
+ * @type bool
+ */
public $browsable = true;
+
+ /**
+ * @type array
+ */
public $allowed_types = array(
// you better write validation code for other types if you
// decide to allow them
'image/jpeg' => true,
'image/gif' => true,
'image/png' => true,
- );
+ );
// this is actually irrelevant since we only write out the path
// component
+ /**
+ * @type bool
+ */
public $may_omit_host = true;
- public function doValidate(&$uri, $config, $context) {
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function doValidate(&$uri, $config, $context)
+ {
$result = explode(',', $uri->path, 2);
$is_base64 = false;
$charset = null;
@@ -17242,7 +21348,7 @@ public function doValidate(&$uri, $config, $context) {
list($metadata, $data) = $result;
// do some legwork on the metadata
$metas = explode(';', $metadata);
- while(!empty($metas)) {
+ while (!empty($metas)) {
$cur = array_shift($metas);
if ($cur == 'base64') {
$is_base64 = true;
@@ -17251,10 +21357,14 @@ public function doValidate(&$uri, $config, $context) {
if (substr($cur, 0, 8) == 'charset=') {
// doesn't match if there are arbitrary spaces, but
// whatever dude
- if ($charset !== null) continue; // garbage
+ if ($charset !== null) {
+ continue;
+ } // garbage
$charset = substr($cur, 8); // not used
} else {
- if ($content_type !== null) continue; // garbage
+ if ($content_type !== null) {
+ continue;
+ } // garbage
$content_type = $cur;
}
}
@@ -17286,7 +21396,9 @@ public function doValidate(&$uri, $config, $context) {
$info = getimagesize($file);
restore_error_handler();
unlink($file);
- if ($info == false) return false;
+ if ($info == false) {
+ return false;
+ }
$image_code = $info[2];
} else {
trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
@@ -17295,7 +21407,9 @@ public function doValidate(&$uri, $config, $context) {
if ($real_content_type != $content_type) {
// we're nice guys; if the content type is something else we
// support, change it over
- if (empty($this->allowed_types[$real_content_type])) return false;
+ if (empty($this->allowed_types[$real_content_type])) {
+ return false;
+ }
$content_type = $real_content_type;
}
// ok, it's kosher, rewrite what we need
@@ -17308,40 +21422,56 @@ public function doValidate(&$uri, $config, $context) {
return true;
}
- public function muteErrorHandler($errno, $errstr) {}
-
+ /**
+ * @param int $errno
+ * @param string $errstr
+ */
+ public function muteErrorHandler($errno, $errstr)
+ {
+ }
}
-
/**
* Validates file as defined by RFC 1630 and RFC 1738.
*/
-class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme {
-
- // Generally file:// URLs are not accessible from most
- // machines, so placing them as an img src is incorrect.
+class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme
+{
+ /**
+ * Generally file:// URLs are not accessible from most
+ * machines, so placing them as an img src is incorrect.
+ * @type bool
+ */
public $browsable = false;
- // Basically the *only* URI scheme for which this is true, since
- // accessing files on the local machine is very common. In fact,
- // browsers on some operating systems don't understand the
- // authority, though I hear it is used on Windows to refer to
- // network shares.
+ /**
+ * Basically the *only* URI scheme for which this is true, since
+ * accessing files on the local machine is very common. In fact,
+ * browsers on some operating systems don't understand the
+ * authority, though I hear it is used on Windows to refer to
+ * network shares.
+ * @type bool
+ */
public $may_omit_host = true;
- public function doValidate(&$uri, $config, $context) {
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function doValidate(&$uri, $config, $context)
+ {
// Authentication method is not supported
$uri->userinfo = null;
// file:// makes no provisions for accessing the resource
- $uri->port = null;
+ $uri->port = null;
// While it seems to work on Firefox, the querystring has
// no possible effect and is thus stripped.
- $uri->query = null;
+ $uri->query = null;
return true;
}
-
}
@@ -17351,14 +21481,32 @@ public function doValidate(&$uri, $config, $context) {
/**
* Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.
*/
-class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme {
-
+class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme
+{
+ /**
+ * @type int
+ */
public $default_port = 21;
+
+ /**
+ * @type bool
+ */
public $browsable = true; // usually
+
+ /**
+ * @type bool
+ */
public $hierarchical = true;
- public function doValidate(&$uri, $config, $context) {
- $uri->query = null;
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function doValidate(&$uri, $config, $context)
+ {
+ $uri->query = null;
// typecode check
$semicolon_pos = strrpos($uri->path, ';'); // reverse
@@ -17381,10 +21529,8 @@ public function doValidate(&$uri, $config, $context) {
$uri->path = str_replace(';', '%3B', $uri->path);
$uri->path .= $type_ret;
}
-
return true;
}
-
}
@@ -17394,17 +21540,34 @@ public function doValidate(&$uri, $config, $context) {
/**
* Validates http (HyperText Transfer Protocol) as defined by RFC 2616
*/
-class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme {
-
+class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme
+{
+ /**
+ * @type int
+ */
public $default_port = 80;
+
+ /**
+ * @type bool
+ */
public $browsable = true;
+
+ /**
+ * @type bool
+ */
public $hierarchical = true;
- public function doValidate(&$uri, $config, $context) {
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function doValidate(&$uri, $config, $context)
+ {
$uri->userinfo = null;
return true;
}
-
}
@@ -17414,11 +21577,16 @@ public function doValidate(&$uri, $config, $context) {
/**
* Validates https (Secure HTTP) according to http scheme.
*/
-class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http {
-
+class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http
+{
+ /**
+ * @type int
+ */
public $default_port = 443;
+ /**
+ * @type bool
+ */
public $secure = true;
-
}
@@ -17434,19 +21602,32 @@ class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http {
* @todo Filter allowed query parameters
*/
-class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme {
-
+class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme
+{
+ /**
+ * @type bool
+ */
public $browsable = false;
+
+ /**
+ * @type bool
+ */
public $may_omit_host = true;
- public function doValidate(&$uri, $config, $context) {
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function doValidate(&$uri, $config, $context)
+ {
$uri->userinfo = null;
$uri->host = null;
$uri->port = null;
// we need to validate path against RFC 2368's addr-spec
return true;
}
-
}
@@ -17456,20 +21637,33 @@ public function doValidate(&$uri, $config, $context) {
/**
* Validates news (Usenet) as defined by generic RFC 1738
*/
-class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme {
-
+class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme
+{
+ /**
+ * @type bool
+ */
public $browsable = false;
+
+ /**
+ * @type bool
+ */
public $may_omit_host = true;
- public function doValidate(&$uri, $config, $context) {
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function doValidate(&$uri, $config, $context)
+ {
$uri->userinfo = null;
- $uri->host = null;
- $uri->port = null;
- $uri->query = null;
+ $uri->host = null;
+ $uri->port = null;
+ $uri->query = null;
// typecode check needed on path
return true;
}
-
}
@@ -17479,17 +21673,30 @@ public function doValidate(&$uri, $config, $context) {
/**
* Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
*/
-class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme {
-
+class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme
+{
+ /**
+ * @type int
+ */
public $default_port = 119;
+
+ /**
+ * @type bool
+ */
public $browsable = false;
- public function doValidate(&$uri, $config, $context) {
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function doValidate(&$uri, $config, $context)
+ {
$uri->userinfo = null;
- $uri->query = null;
+ $uri->query = null;
return true;
}
-
}
@@ -17503,28 +21710,41 @@ public function doValidate(&$uri, $config, $context) {
*/
class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
{
-
- protected function parseImplementation($var, $type, $allow_null) {
- if ($allow_null && $var === null) return null;
+ /**
+ * @param mixed $var
+ * @param int $type
+ * @param bool $allow_null
+ * @return array|bool|float|int|mixed|null|string
+ * @throws HTMLPurifier_VarParserException
+ */
+ protected function parseImplementation($var, $type, $allow_null)
+ {
+ if ($allow_null && $var === null) {
+ return null;
+ }
switch ($type) {
// Note: if code "breaks" from the switch, it triggers a generic
// exception to be thrown. Specific errors can be specifically
// done here.
- case self::MIXED :
- case self::ISTRING :
- case self::STRING :
- case self::TEXT :
- case self::ITEXT :
+ case self::MIXED:
+ case self::ISTRING:
+ case self::STRING:
+ case self::TEXT:
+ case self::ITEXT:
return $var;
- case self::INT :
- if (is_string($var) && ctype_digit($var)) $var = (int) $var;
+ case self::INT:
+ if (is_string($var) && ctype_digit($var)) {
+ $var = (int)$var;
+ }
return $var;
- case self::FLOAT :
- if ((is_string($var) && is_numeric($var)) || is_int($var)) $var = (float) $var;
+ case self::FLOAT:
+ if ((is_string($var) && is_numeric($var)) || is_int($var)) {
+ $var = (float)$var;
+ }
return $var;
- case self::BOOL :
+ case self::BOOL:
if (is_int($var) && ($var === 0 || $var === 1)) {
- $var = (bool) $var;
+ $var = (bool)$var;
} elseif (is_string($var)) {
if ($var == 'on' || $var == 'true' || $var == '1') {
$var = true;
@@ -17535,45 +21755,56 @@ protected function parseImplementation($var, $type, $allow_null) {
}
}
return $var;
- case self::ALIST :
- case self::HASH :
- case self::LOOKUP :
+ case self::ALIST:
+ case self::HASH:
+ case self::LOOKUP:
if (is_string($var)) {
// special case: technically, this is an array with
// a single empty string item, but having an empty
// array is more intuitive
- if ($var == '') return array();
+ if ($var == '') {
+ return array();
+ }
if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
// simplistic string to array method that only works
// for simple lists of tag names or alphanumeric characters
- $var = explode(',',$var);
+ $var = explode(',', $var);
} else {
$var = preg_split('/(,|[\n\r]+)/', $var);
}
// remove spaces
- foreach ($var as $i => $j) $var[$i] = trim($j);
+ foreach ($var as $i => $j) {
+ $var[$i] = trim($j);
+ }
if ($type === self::HASH) {
// key:value,key2:value2
$nvar = array();
foreach ($var as $keypair) {
$c = explode(':', $keypair, 2);
- if (!isset($c[1])) continue;
+ if (!isset($c[1])) {
+ continue;
+ }
$nvar[trim($c[0])] = trim($c[1]);
}
$var = $nvar;
}
}
- if (!is_array($var)) break;
+ if (!is_array($var)) {
+ break;
+ }
$keys = array_keys($var);
if ($keys === array_keys($keys)) {
- if ($type == self::ALIST) return $var;
- elseif ($type == self::LOOKUP) {
+ if ($type == self::ALIST) {
+ return $var;
+ } elseif ($type == self::LOOKUP) {
$new = array();
foreach ($var as $key) {
$new[$key] = true;
}
return $new;
- } else break;
+ } else {
+ break;
+ }
}
if ($type === self::ALIST) {
trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
@@ -17582,7 +21813,11 @@ protected function parseImplementation($var, $type, $allow_null) {
if ($type === self::LOOKUP) {
foreach ($var as $key => $value) {
if ($value !== true) {
- trigger_error("Lookup array has non-true value at key '$key'; maybe your input array was not indexed numerically", E_USER_WARNING);
+ trigger_error(
+ "Lookup array has non-true value at key '$key'; " .
+ "maybe your input array was not indexed numerically",
+ E_USER_WARNING
+ );
}
$var[$key] = true;
}
@@ -17593,7 +21828,6 @@ protected function parseImplementation($var, $type, $allow_null) {
}
$this->errorGeneric($var, $type);
}
-
}
@@ -17608,11 +21842,24 @@ protected function parseImplementation($var, $type, $allow_null) {
class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
{
- protected function parseImplementation($var, $type, $allow_null) {
+ /**
+ * @param mixed $var
+ * @param int $type
+ * @param bool $allow_null
+ * @return null|string
+ */
+ protected function parseImplementation($var, $type, $allow_null)
+ {
return $this->evalExpression($var);
}
- protected function evalExpression($expr) {
+ /**
+ * @param string $expr
+ * @return mixed
+ * @throws HTMLPurifier_VarParserException
+ */
+ protected function evalExpression($expr)
+ {
$var = null;
$result = eval("\$var = $expr;");
if ($result === false) {
@@ -17620,7 +21867,6 @@ protected function evalExpression($expr) {
}
return $var;
}
-
}
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange.php
index 91a5aa7..0e08ae8 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Interchange.php
@@ -10,18 +10,23 @@ class HTMLPurifier_ConfigSchema_Interchange
/**
* Name of the application this schema is describing.
+ * @type string
*/
public $name;
/**
* Array of Directive ID => array(directive info)
+ * @type HTMLPurifier_ConfigSchema_Interchange_Directive[]
*/
public $directives = array();
/**
* Adds a directive array to $directives
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
+ * @throws HTMLPurifier_ConfigSchema_Exception
*/
- public function addDirective($directive) {
+ public function addDirective($directive)
+ {
if (isset($this->directives[$i = $directive->id->toString()])) {
throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'");
}
@@ -32,11 +37,11 @@ public function addDirective($directive) {
* Convenience function to perform standard validation. Throws exception
* on failed validation.
*/
- public function validate() {
+ public function validate()
+ {
$validator = new HTMLPurifier_ConfigSchema_Validator();
return $validator->validate($this);
}
-
}
// vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
index 785b72c..655e6dd 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
@@ -5,21 +5,39 @@ class HTMLPurifier_ConfigSchema_InterchangeBuilder
/**
* Used for processing DEFAULT, nothing else.
+ * @type HTMLPurifier_VarParser
*/
protected $varParser;
- public function __construct($varParser = null) {
+ /**
+ * @param HTMLPurifier_VarParser $varParser
+ */
+ public function __construct($varParser = null)
+ {
$this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native();
}
- public static function buildFromDirectory($dir = null) {
- $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
+ /**
+ * @param string $dir
+ * @return HTMLPurifier_ConfigSchema_Interchange
+ */
+ public static function buildFromDirectory($dir = null)
+ {
+ $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
$interchange = new HTMLPurifier_ConfigSchema_Interchange();
return $builder->buildDir($interchange, $dir);
}
- public function buildDir($interchange, $dir = null) {
- if (!$dir) $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema';
+ /**
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ * @param string $dir
+ * @return HTMLPurifier_ConfigSchema_Interchange
+ */
+ public function buildDir($interchange, $dir = null)
+ {
+ if (!$dir) {
+ $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema';
+ }
if (file_exists($dir . '/info.ini')) {
$info = parse_ini_file($dir . '/info.ini');
$interchange->name = $info['name'];
@@ -39,24 +57,30 @@ public function buildDir($interchange, $dir = null) {
foreach ($files as $file) {
$this->buildFile($interchange, $dir . '/' . $file);
}
-
return $interchange;
}
- public function buildFile($interchange, $file) {
+ /**
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ * @param string $file
+ */
+ public function buildFile($interchange, $file)
+ {
$parser = new HTMLPurifier_StringHashParser();
$this->build(
$interchange,
- new HTMLPurifier_StringHash( $parser->parseFile($file) )
+ new HTMLPurifier_StringHash($parser->parseFile($file))
);
}
/**
* Builds an interchange object based on a hash.
- * @param $interchange HTMLPurifier_ConfigSchema_Interchange object to build
- * @param $hash HTMLPurifier_ConfigSchema_StringHash source data
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build
+ * @param HTMLPurifier_StringHash $hash source data
+ * @throws HTMLPurifier_ConfigSchema_Exception
*/
- public function build($interchange, $hash) {
+ public function build($interchange, $hash)
+ {
if (!$hash instanceof HTMLPurifier_StringHash) {
$hash = new HTMLPurifier_StringHash($hash);
}
@@ -75,7 +99,13 @@ public function build($interchange, $hash) {
$this->_findUnused($hash);
}
- public function buildDirective($interchange, $hash) {
+ /**
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ * @param HTMLPurifier_StringHash $hash
+ * @throws HTMLPurifier_ConfigSchema_Exception
+ */
+ public function buildDirective($interchange, $hash)
+ {
$directive = new HTMLPurifier_ConfigSchema_Interchange_Directive();
// These are required elements:
@@ -84,7 +114,9 @@ public function buildDirective($interchange, $hash) {
if (isset($hash['TYPE'])) {
$type = explode('/', $hash->offsetGet('TYPE'));
- if (isset($type[1])) $directive->typeAllowsNull = true;
+ if (isset($type[1])) {
+ $directive->typeAllowsNull = true;
+ }
$directive->type = $type[0];
} else {
throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined");
@@ -92,7 +124,11 @@ public function buildDirective($interchange, $hash) {
if (isset($hash['DEFAULT'])) {
try {
- $directive->default = $this->varParser->parse($hash->offsetGet('DEFAULT'), $directive->type, $directive->typeAllowsNull);
+ $directive->default = $this->varParser->parse(
+ $hash->offsetGet('DEFAULT'),
+ $directive->type,
+ $directive->typeAllowsNull
+ );
} catch (HTMLPurifier_VarParserException $e) {
throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'");
}
@@ -139,34 +175,45 @@ public function buildDirective($interchange, $hash) {
/**
* Evaluates an array PHP code string without array() wrapper
+ * @param string $contents
*/
- protected function evalArray($contents) {
- return eval('return array('. $contents .');');
+ protected function evalArray($contents)
+ {
+ return eval('return array(' . $contents . ');');
}
/**
* Converts an array list into a lookup array.
+ * @param array $array
+ * @return array
*/
- protected function lookup($array) {
+ protected function lookup($array)
+ {
$ret = array();
- foreach ($array as $val) $ret[$val] = true;
+ foreach ($array as $val) {
+ $ret[$val] = true;
+ }
return $ret;
}
/**
* Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id
* object based on a string Id.
+ * @param string $id
+ * @return HTMLPurifier_ConfigSchema_Interchange_Id
*/
- protected function id($id) {
+ protected function id($id)
+ {
return HTMLPurifier_ConfigSchema_Interchange_Id::make($id);
}
/**
* Triggers errors for any unused keys passed in the hash; such keys
* may indicate typos, missing values, etc.
- * @param $hash Instance of ConfigSchema_StringHash to check.
+ * @param HTMLPurifier_StringHash $hash Hash to check.
*/
- protected function _findUnused($hash) {
+ protected function _findUnused($hash)
+ {
$accessed = $hash->getAccessed();
foreach ($hash as $k => $v) {
if (!isset($accessed[$k])) {
@@ -174,7 +221,6 @@ protected function _findUnused($hash) {
}
}
}
-
}
// vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php
index f374f6a..fb31277 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/Validator.php
@@ -12,36 +12,48 @@ class HTMLPurifier_ConfigSchema_Validator
{
/**
- * Easy to access global objects.
+ * @type HTMLPurifier_ConfigSchema_Interchange
*/
- protected $interchange, $aliases;
+ protected $interchange;
+
+ /**
+ * @type array
+ */
+ protected $aliases;
/**
* Context-stack to provide easy to read error messages.
+ * @type array
*/
protected $context = array();
/**
- * HTMLPurifier_VarParser to test default's type.
+ * to test default's type.
+ * @type HTMLPurifier_VarParser
*/
protected $parser;
- public function __construct() {
+ public function __construct()
+ {
$this->parser = new HTMLPurifier_VarParser();
}
/**
- * Validates a fully-formed interchange object. Throws an
- * HTMLPurifier_ConfigSchema_Exception if there's a problem.
+ * Validates a fully-formed interchange object.
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ * @return bool
*/
- public function validate($interchange) {
+ public function validate($interchange)
+ {
$this->interchange = $interchange;
$this->aliases = array();
// PHP is a bit lax with integer <=> string conversions in
// arrays, so we don't use the identical !== comparison
foreach ($interchange->directives as $i => $directive) {
$id = $directive->id->toString();
- if ($i != $id) $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'");
+ if ($i != $id) {
+ $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'");
+ }
$this->validateDirective($directive);
}
return true;
@@ -49,8 +61,10 @@ public function validate($interchange) {
/**
* Validates a HTMLPurifier_ConfigSchema_Interchange_Id object.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Id $id
*/
- public function validateId($id) {
+ public function validateId($id)
+ {
$id_string = $id->toString();
$this->context[] = "id '$id_string'";
if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) {
@@ -67,8 +81,10 @@ public function validateId($id) {
/**
* Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
*/
- public function validateDirective($d) {
+ public function validateDirective($d)
+ {
$id = $d->id->toString();
$this->context[] = "directive '$id'";
$this->validateId($d->id);
@@ -108,9 +124,13 @@ public function validateDirective($d) {
/**
* Extra validation if $allowed member variable of
* HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
*/
- public function validateDirectiveAllowed($d) {
- if (is_null($d->allowed)) return;
+ public function validateDirectiveAllowed($d)
+ {
+ if (is_null($d->allowed)) {
+ return;
+ }
$this->with($d, 'allowed')
->assertNotEmpty()
->assertIsLookup(); // handled by InterchangeBuilder
@@ -119,7 +139,9 @@ public function validateDirectiveAllowed($d) {
}
$this->context[] = 'allowed';
foreach ($d->allowed as $val => $x) {
- if (!is_string($val)) $this->error("value $val", 'must be a string');
+ if (!is_string($val)) {
+ $this->error("value $val", 'must be a string');
+ }
}
array_pop($this->context);
}
@@ -127,15 +149,23 @@ public function validateDirectiveAllowed($d) {
/**
* Extra validation if $valueAliases member variable of
* HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
*/
- public function validateDirectiveValueAliases($d) {
- if (is_null($d->valueAliases)) return;
+ public function validateDirectiveValueAliases($d)
+ {
+ if (is_null($d->valueAliases)) {
+ return;
+ }
$this->with($d, 'valueAliases')
->assertIsArray(); // handled by InterchangeBuilder
$this->context[] = 'valueAliases';
foreach ($d->valueAliases as $alias => $real) {
- if (!is_string($alias)) $this->error("alias $alias", 'must be a string');
- if (!is_string($real)) $this->error("alias target $real from alias '$alias'", 'must be a string');
+ if (!is_string($alias)) {
+ $this->error("alias $alias", 'must be a string');
+ }
+ if (!is_string($real)) {
+ $this->error("alias target $real from alias '$alias'", 'must be a string');
+ }
if ($alias === $real) {
$this->error("alias '$alias'", "must not be an alias to itself");
}
@@ -155,8 +185,10 @@ public function validateDirectiveValueAliases($d) {
/**
* Extra validation if $aliases member variable of
* HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
*/
- public function validateDirectiveAliases($d) {
+ public function validateDirectiveAliases($d)
+ {
$this->with($d, 'aliases')
->assertIsArray(); // handled by InterchangeBuilder
$this->context[] = 'aliases';
@@ -180,27 +212,37 @@ public function validateDirectiveAliases($d) {
/**
* Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom
* for validating simple member variables of objects.
+ * @param $obj
+ * @param $member
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
*/
- protected function with($obj, $member) {
+ protected function with($obj, $member)
+ {
return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member);
}
/**
* Emits an error, providing helpful context.
+ * @throws HTMLPurifier_ConfigSchema_Exception
*/
- protected function error($target, $msg) {
- if ($target !== false) $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext();
- else $prefix = ucfirst($this->getFormattedContext());
+ protected function error($target, $msg)
+ {
+ if ($target !== false) {
+ $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext();
+ } else {
+ $prefix = ucfirst($this->getFormattedContext());
+ }
throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg));
}
/**
* Returns a formatted context string.
+ * @return string
*/
- protected function getFormattedContext() {
+ protected function getFormattedContext()
+ {
return implode(' in ', array_reverse($this->context));
}
-
}
// vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php
index b95aea1..c9aa364 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/ValidatorAtom.php
@@ -8,59 +8,123 @@
*/
class HTMLPurifier_ConfigSchema_ValidatorAtom
{
+ /**
+ * @type string
+ */
+ protected $context;
- protected $context, $obj, $member, $contents;
+ /**
+ * @type object
+ */
+ protected $obj;
- public function __construct($context, $obj, $member) {
- $this->context = $context;
- $this->obj = $obj;
- $this->member = $member;
- $this->contents =& $obj->$member;
+ /**
+ * @type string
+ */
+ protected $member;
+
+ /**
+ * @type mixed
+ */
+ protected $contents;
+
+ public function __construct($context, $obj, $member)
+ {
+ $this->context = $context;
+ $this->obj = $obj;
+ $this->member = $member;
+ $this->contents =& $obj->$member;
}
- public function assertIsString() {
- if (!is_string($this->contents)) $this->error('must be a string');
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertIsString()
+ {
+ if (!is_string($this->contents)) {
+ $this->error('must be a string');
+ }
return $this;
}
- public function assertIsBool() {
- if (!is_bool($this->contents)) $this->error('must be a boolean');
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertIsBool()
+ {
+ if (!is_bool($this->contents)) {
+ $this->error('must be a boolean');
+ }
return $this;
}
- public function assertIsArray() {
- if (!is_array($this->contents)) $this->error('must be an array');
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertIsArray()
+ {
+ if (!is_array($this->contents)) {
+ $this->error('must be an array');
+ }
return $this;
}
- public function assertNotNull() {
- if ($this->contents === null) $this->error('must not be null');
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertNotNull()
+ {
+ if ($this->contents === null) {
+ $this->error('must not be null');
+ }
return $this;
}
- public function assertAlnum() {
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertAlnum()
+ {
$this->assertIsString();
- if (!ctype_alnum($this->contents)) $this->error('must be alphanumeric');
+ if (!ctype_alnum($this->contents)) {
+ $this->error('must be alphanumeric');
+ }
return $this;
}
- public function assertNotEmpty() {
- if (empty($this->contents)) $this->error('must not be empty');
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertNotEmpty()
+ {
+ if (empty($this->contents)) {
+ $this->error('must not be empty');
+ }
return $this;
}
- public function assertIsLookup() {
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertIsLookup()
+ {
$this->assertIsArray();
foreach ($this->contents as $v) {
- if ($v !== true) $this->error('must be a lookup array');
+ if ($v !== true) {
+ $this->error('must be a lookup array');
+ }
}
return $this;
}
- protected function error($msg) {
+ /**
+ * @param string $msg
+ * @throws HTMLPurifier_ConfigSchema_Exception
+ */
+ protected function error($msg)
+ {
throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg);
}
-
}
// vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser
index fa0bacb..22ea321 100644
Binary files a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser and b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema.ser differ
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt
new file mode 100644
index 0000000..2c910cc
--- /dev/null
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt
@@ -0,0 +1,16 @@
+Core.AllowHostnameUnderscore
+TYPE: bool
+VERSION: 4.6.0
+DEFAULT: false
+--DESCRIPTION--
+
+ By RFC 1123, underscores are not permitted in host names.
+ (This is in contrast to the specification for DNS, RFC
+ 2181, which allows underscores.)
+ However, most browsers do the right thing when faced with
+ an underscore in the host name, and so some poorly written
+ websites are written with the expectation this should work.
+ Setting this parameter to true relaxes our allowed character
+ check so that underscores are permitted.
+
+--# vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
index 4d5b505..a3881be 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
@@ -2,9 +2,11 @@ Core.EscapeInvalidChildren
TYPE: bool
DEFAULT: false
--DESCRIPTION--
-When true, a child is found that is not allowed in the context of the
+
Warning: this configuration option is no longer does anything as of 4.6.0.
+
+
When true, a child is found that is not allowed in the context of the
parent element will be transformed into text as if it were ASCII. When
false, that element and all internal tags will be dropped, though text will
be preserved. There is no option for dropping the element but preserving
-child nodes.
+child nodes.
--# vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
index 0d00f62..1e17c1d 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
@@ -11,7 +11,7 @@ DEFAULT: NULL
to check if a URI has passed through HTML Purifier with this line:
-
If the output is TRUE, the redirector script should accept the URI.
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php
index 8d7b573..c7f197e 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Language/messages/en.php
@@ -4,60 +4,52 @@
$messages = array(
-'HTMLPurifier' => 'HTML Purifier',
-
+ 'HTMLPurifier' => 'HTML Purifier',
// for unit testing purposes
-'LanguageFactoryTest: Pizza' => 'Pizza',
-'LanguageTest: List' => '$1',
-'LanguageTest: Hash' => '$1.Keys; $1.Values',
-
-'Item separator' => ', ',
-'Item separator last' => ' and ', // non-Harvard style
-
-'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.',
-'ErrorCollector: At line' => ' at line $line',
-'ErrorCollector: Incidental errors' => 'Incidental errors',
-
-'Lexer: Unclosed comment' => 'Unclosed comment',
-'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <',
-'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped',
-'Lexer: Missing attribute key' => 'Attribute declaration has no key',
-'Lexer: Missing end quote' => 'Attribute declaration has no end quote',
-'Lexer: Extracted body' => 'Removed document metadata tags',
-
-'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized',
-'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1',
-'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text',
-'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed',
-'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed',
-'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed',
-'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end',
-'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed',
-'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens',
-
-'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed',
-'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text',
-'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact',
-'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact',
-'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed',
-'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text',
-'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized',
-'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document',
-
-'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed',
-'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element',
-'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model',
-'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed',
-
-'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys',
-'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed',
-
+ 'LanguageFactoryTest: Pizza' => 'Pizza',
+ 'LanguageTest: List' => '$1',
+ 'LanguageTest: Hash' => '$1.Keys; $1.Values',
+ 'Item separator' => ', ',
+ 'Item separator last' => ' and ', // non-Harvard style
+
+ 'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.',
+ 'ErrorCollector: At line' => ' at line $line',
+ 'ErrorCollector: Incidental errors' => 'Incidental errors',
+ 'Lexer: Unclosed comment' => 'Unclosed comment',
+ 'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <',
+ 'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped',
+ 'Lexer: Missing attribute key' => 'Attribute declaration has no key',
+ 'Lexer: Missing end quote' => 'Attribute declaration has no end quote',
+ 'Lexer: Extracted body' => 'Removed document metadata tags',
+ 'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized',
+ 'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1',
+ 'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text',
+ 'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed',
+ 'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed',
+ 'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed',
+ 'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end',
+ 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed',
+ 'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens',
+ 'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed',
+ 'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text',
+ 'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact',
+ 'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact',
+ 'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed',
+ 'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text',
+ 'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized',
+ 'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document',
+ 'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed',
+ 'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element',
+ 'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model',
+ 'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed',
+ 'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys',
+ 'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed',
);
$errorNames = array(
- E_ERROR => 'Error',
+ E_ERROR => 'Error',
E_WARNING => 'Warning',
- E_NOTICE => 'Notice'
+ E_NOTICE => 'Notice'
);
// vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php
index e7eb82e..549e4ce 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer.php
@@ -7,25 +7,30 @@ class HTMLPurifier_Printer
{
/**
- * Instance of HTMLPurifier_Generator for HTML generation convenience funcs
+ * For HTML generation convenience funcs.
+ * @type HTMLPurifier_Generator
*/
protected $generator;
/**
- * Instance of HTMLPurifier_Config, for easy access
+ * For easy access.
+ * @type HTMLPurifier_Config
*/
protected $config;
/**
* Initialize $generator.
*/
- public function __construct() {
+ public function __construct()
+ {
}
/**
* Give generator necessary configuration if possible
+ * @param HTMLPurifier_Config $config
*/
- public function prepareGenerator($config) {
+ public function prepareGenerator($config)
+ {
$all = $config->getAll();
$context = new HTMLPurifier_Context();
$this->generator = new HTMLPurifier_Generator($config, $context);
@@ -39,45 +44,62 @@ public function prepareGenerator($config) {
/**
* Returns a start tag
- * @param $tag Tag name
- * @param $attr Attribute array
+ * @param string $tag Tag name
+ * @param array $attr Attribute array
+ * @return string
*/
- protected function start($tag, $attr = array()) {
+ protected function start($tag, $attr = array())
+ {
return $this->generator->generateFromToken(
- new HTMLPurifier_Token_Start($tag, $attr ? $attr : array())
- );
+ new HTMLPurifier_Token_Start($tag, $attr ? $attr : array())
+ );
}
/**
- * Returns an end teg
- * @param $tag Tag name
+ * Returns an end tag
+ * @param string $tag Tag name
+ * @return string
*/
- protected function end($tag) {
+ protected function end($tag)
+ {
return $this->generator->generateFromToken(
- new HTMLPurifier_Token_End($tag)
- );
+ new HTMLPurifier_Token_End($tag)
+ );
}
/**
* Prints a complete element with content inside
- * @param $tag Tag name
- * @param $contents Element contents
- * @param $attr Tag attributes
- * @param $escape Bool whether or not to escape contents
+ * @param string $tag Tag name
+ * @param string $contents Element contents
+ * @param array $attr Tag attributes
+ * @param bool $escape whether or not to escape contents
+ * @return string
*/
- protected function element($tag, $contents, $attr = array(), $escape = true) {
+ protected function element($tag, $contents, $attr = array(), $escape = true)
+ {
return $this->start($tag, $attr) .
- ($escape ? $this->escape($contents) : $contents) .
- $this->end($tag);
+ ($escape ? $this->escape($contents) : $contents) .
+ $this->end($tag);
}
- protected function elementEmpty($tag, $attr = array()) {
+ /**
+ * @param string $tag
+ * @param array $attr
+ * @return string
+ */
+ protected function elementEmpty($tag, $attr = array())
+ {
return $this->generator->generateFromToken(
new HTMLPurifier_Token_Empty($tag, $attr)
);
}
- protected function text($text) {
+ /**
+ * @param string $text
+ * @return string
+ */
+ protected function text($text)
+ {
return $this->generator->generateFromToken(
new HTMLPurifier_Token_Text($text)
);
@@ -85,24 +107,29 @@ protected function text($text) {
/**
* Prints a simple key/value row in a table.
- * @param $name Key
- * @param $value Value
+ * @param string $name Key
+ * @param mixed $value Value
+ * @return string
*/
- protected function row($name, $value) {
- if (is_bool($value)) $value = $value ? 'On' : 'Off';
+ protected function row($name, $value)
+ {
+ if (is_bool($value)) {
+ $value = $value ? 'On' : 'Off';
+ }
return
$this->start('tr') . "\n" .
- $this->element('th', $name) . "\n" .
- $this->element('td', $value) . "\n" .
- $this->end('tr')
- ;
+ $this->element('th', $name) . "\n" .
+ $this->element('td', $value) . "\n" .
+ $this->end('tr');
}
/**
* Escapes a string for HTML output.
- * @param $string String to escape
+ * @param string $string String to escape
+ * @return string
*/
- protected function escape($string) {
+ protected function escape($string)
+ {
$string = HTMLPurifier_Encoder::cleanUTF8($string);
$string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
return $string;
@@ -110,32 +137,46 @@ protected function escape($string) {
/**
* Takes a list of strings and turns them into a single list
- * @param $array List of strings
- * @param $polite Bool whether or not to add an end before the last
+ * @param string[] $array List of strings
+ * @param bool $polite Bool whether or not to add an end before the last
+ * @return string
*/
- protected function listify($array, $polite = false) {
- if (empty($array)) return 'None';
+ protected function listify($array, $polite = false)
+ {
+ if (empty($array)) {
+ return 'None';
+ }
$ret = '';
$i = count($array);
foreach ($array as $value) {
$i--;
$ret .= $value;
- if ($i > 0 && !($polite && $i == 1)) $ret .= ', ';
- if ($polite && $i == 1) $ret .= 'and ';
+ if ($i > 0 && !($polite && $i == 1)) {
+ $ret .= ', ';
+ }
+ if ($polite && $i == 1) {
+ $ret .= 'and ';
+ }
}
return $ret;
}
/**
* Retrieves the class of an object without prefixes, as well as metadata
- * @param $obj Object to determine class of
- * @param $prefix Further prefix to remove
+ * @param object $obj Object to determine class of
+ * @param string $sec_prefix Further prefix to remove
+ * @return string
*/
- protected function getClass($obj, $sec_prefix = '') {
+ protected function getClass($obj, $sec_prefix = '')
+ {
static $five = null;
- if ($five === null) $five = version_compare(PHP_VERSION, '5', '>=');
+ if ($five === null) {
+ $five = version_compare(PHP_VERSION, '5', '>=');
+ }
$prefix = 'HTMLPurifier_' . $sec_prefix;
- if (!$five) $prefix = strtolower($prefix);
+ if (!$five) {
+ $prefix = strtolower($prefix);
+ }
$class = str_replace($prefix, '', get_class($obj));
$lclass = strtolower($class);
$class .= '(';
@@ -164,13 +205,14 @@ protected function getClass($obj, $sec_prefix = '') {
break;
case 'css_importantdecorator':
$class .= $this->getClass($obj->def, $sec_prefix);
- if ($obj->allow) $class .= ', !important';
+ if ($obj->allow) {
+ $class .= ', !important';
+ }
break;
}
$class .= ')';
return $class;
}
-
}
// vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php
index 81f9865..29505fe 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/CSSDefinition.php
@@ -2,10 +2,17 @@
class HTMLPurifier_Printer_CSSDefinition extends HTMLPurifier_Printer
{
-
+ /**
+ * @type HTMLPurifier_CSSDefinition
+ */
protected $def;
- public function render($config) {
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return string
+ */
+ public function render($config)
+ {
$this->def = $config->getCSSDefinition();
$ret = '';
@@ -32,7 +39,6 @@ public function render($config) {
return $ret;
}
-
}
// vim: et sw=4 sts=4
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php
index 02aa656..36100ce 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/ConfigForm.php
@@ -7,17 +7,20 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
{
/**
- * Printers for specific fields
+ * Printers for specific fields.
+ * @type HTMLPurifier_Printer[]
*/
protected $fields = array();
/**
- * Documentation URL, can have fragment tagged on end
+ * Documentation URL, can have fragment tagged on end.
+ * @type string
*/
protected $docURL;
/**
- * Name of form element to stuff config in
+ * Name of form element to stuff config in.
+ * @type string
*/
protected $name;
@@ -25,24 +28,27 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
* Whether or not to compress directive names, clipping them off
* after a certain amount of letters. False to disable or integer letters
* before clipping.
+ * @type bool
*/
protected $compress = false;
/**
- * @param $name Form element name for directives to be stuffed into
- * @param $doc_url String documentation URL, will have fragment tagged on
- * @param $compress Integer max length before compressing a directive name, set to false to turn off
+ * @param string $name Form element name for directives to be stuffed into
+ * @param string $doc_url String documentation URL, will have fragment tagged on
+ * @param bool $compress Integer max length before compressing a directive name, set to false to turn off
*/
public function __construct(
- $name, $doc_url = null, $compress = false
+ $name,
+ $doc_url = null,
+ $compress = false
) {
parent::__construct();
$this->docURL = $doc_url;
- $this->name = $name;
+ $this->name = $name;
$this->compress = $compress;
// initialize sub-printers
- $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default();
- $this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool();
+ $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default();
+ $this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool();
}
/**
@@ -50,32 +56,42 @@ public function __construct(
* @param $cols Integer columns of textarea, null to use default
* @param $rows Integer rows of textarea, null to use default
*/
- public function setTextareaDimensions($cols = null, $rows = null) {
- if ($cols) $this->fields['default']->cols = $cols;
- if ($rows) $this->fields['default']->rows = $rows;
+ public function setTextareaDimensions($cols = null, $rows = null)
+ {
+ if ($cols) {
+ $this->fields['default']->cols = $cols;
+ }
+ if ($rows) {
+ $this->fields['default']->rows = $rows;
+ }
}
/**
* Retrieves styling, in case it is not accessible by webserver
*/
- public static function getCSS() {
+ public static function getCSS()
+ {
return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css');
}
/**
* Retrieves JavaScript, in case it is not accessible by webserver
*/
- public static function getJavaScript() {
+ public static function getJavaScript()
+ {
return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js');
}
/**
* Returns HTML output for a configuration form
- * @param $config Configuration object of current form state, or an array
+ * @param HTMLPurifier_Config|array $config Configuration object of current form state, or an array
* where [0] has an HTML namespace and [1] is being rendered.
- * @param $allowed Optional namespace(s) and directives to restrict form to.
+ * @param array|bool $allowed Optional namespace(s) and directives to restrict form to.
+ * @param bool $render_controls
+ * @return string
*/
- public function render($config, $allowed = true, $render_controls = true) {
+ public function render($config, $allowed = true, $render_controls = true)
+ {
if (is_array($config) && isset($config[0])) {
$gen_config = $config[0];
$config = $config[1];
@@ -91,29 +107,29 @@ public function render($config, $allowed = true, $render_controls = true) {
$all = array();
foreach ($allowed as $key) {
list($ns, $directive) = $key;
- $all[$ns][$directive] = $config->get($ns .'.'. $directive);
+ $all[$ns][$directive] = $config->get($ns . '.' . $directive);
}
$ret = '';
$ret .= $this->start('table', array('class' => 'hp-config'));
$ret .= $this->start('thead');
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive'));
- $ret .= $this->element('th', 'Value', array('class' => 'hp-value'));
+ $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive'));
+ $ret .= $this->element('th', 'Value', array('class' => 'hp-value'));
$ret .= $this->end('tr');
$ret .= $this->end('thead');
foreach ($all as $ns => $directives) {
$ret .= $this->renderNamespace($ns, $directives);
}
if ($render_controls) {
- $ret .= $this->start('tbody');
- $ret .= $this->start('tr');
- $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls'));
- $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit'));
- $ret .= '[Reset]';
- $ret .= $this->end('td');
- $ret .= $this->end('tr');
- $ret .= $this->end('tbody');
+ $ret .= $this->start('tbody');
+ $ret .= $this->start('tr');
+ $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls'));
+ $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit'));
+ $ret .= '[Reset]';
+ $ret .= $this->end('td');
+ $ret .= $this->end('tr');
+ $ret .= $this->end('tbody');
}
$ret .= $this->end('table');
return $ret;
@@ -122,13 +138,15 @@ public function render($config, $allowed = true, $render_controls = true) {
/**
* Renders a single namespace
* @param $ns String namespace name
- * @param $directive Associative array of directives to values
+ * @param array $directives array of directives to values
+ * @return string
*/
- protected function renderNamespace($ns, $directives) {
+ protected function renderNamespace($ns, $directives)
+ {
$ret = '';
$ret .= $this->start('tbody', array('class' => 'namespace'));
$ret .= $this->start('tr');
- $ret .= $this->element('th', $ns, array('colspan' => 2));
+ $ret .= $this->element('th', $ns, array('colspan' => 2));
$ret .= $this->end('tr');
$ret .= $this->end('tbody');
$ret .= $this->start('tbody');
@@ -139,40 +157,44 @@ protected function renderNamespace($ns, $directives) {
$url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL);
$ret .= $this->start('a', array('href' => $url));
}
- $attr = array('for' => "{$this->name}:$ns.$directive");
-
- // crop directive name if it's too long
- if (!$this->compress || (strlen($directive) < $this->compress)) {
- $directive_disp = $directive;
- } else {
- $directive_disp = substr($directive, 0, $this->compress - 2) . '...';
- $attr['title'] = $directive;
- }
+ $attr = array('for' => "{$this->name}:$ns.$directive");
+
+ // crop directive name if it's too long
+ if (!$this->compress || (strlen($directive) < $this->compress)) {
+ $directive_disp = $directive;
+ } else {
+ $directive_disp = substr($directive, 0, $this->compress - 2) . '...';
+ $attr['title'] = $directive;
+ }
- $ret .= $this->element(
- 'label',
- $directive_disp,
- // component printers must create an element with this id
- $attr
- );
- if ($this->docURL) $ret .= $this->end('a');
+ $ret .= $this->element(
+ 'label',
+ $directive_disp,
+ // component printers must create an element with this id
+ $attr
+ );
+ if ($this->docURL) {
+ $ret .= $this->end('a');
+ }
$ret .= $this->end('th');
$ret .= $this->start('td');
- $def = $this->config->def->info["$ns.$directive"];
- if (is_int($def)) {
- $allow_null = $def < 0;
- $type = abs($def);
- } else {
- $type = $def->type;
- $allow_null = isset($def->allow_null);
- }
- if (!isset($this->fields[$type])) $type = 0; // default
- $type_obj = $this->fields[$type];
- if ($allow_null) {
- $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj);
- }
- $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config));
+ $def = $this->config->def->info["$ns.$directive"];
+ if (is_int($def)) {
+ $allow_null = $def < 0;
+ $type = abs($def);
+ } else {
+ $type = $def->type;
+ $allow_null = isset($def->allow_null);
+ }
+ if (!isset($this->fields[$type])) {
+ $type = 0;
+ } // default
+ $type_obj = $this->fields[$type];
+ if ($allow_null) {
+ $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj);
+ }
+ $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config));
$ret .= $this->end('td');
$ret .= $this->end('tr');
}
@@ -185,19 +207,33 @@ protected function renderNamespace($ns, $directives) {
/**
* Printer decorator for directives that accept null
*/
-class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer {
+class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer
+{
/**
* Printer being decorated
+ * @type HTMLPurifier_Printer
*/
protected $obj;
+
/**
- * @param $obj Printer to decorate
+ * @param HTMLPurifier_Printer $obj Printer to decorate
*/
- public function __construct($obj) {
+ public function __construct($obj)
+ {
parent::__construct();
$this->obj = $obj;
}
- public function render($ns, $directive, $value, $name, $config) {
+
+ /**
+ * @param string $ns
+ * @param string $directive
+ * @param string $value
+ * @param string $name
+ * @param HTMLPurifier_Config|array $config
+ * @return string
+ */
+ public function render($ns, $directive, $value, $name, $config)
+ {
if (is_array($config) && isset($config[0])) {
$gen_config = $config[0];
$config = $config[1];
@@ -215,15 +251,19 @@ public function render($ns, $directive, $value, $name, $config) {
'type' => 'checkbox',
'value' => '1',
'class' => 'null-toggle',
- 'name' => "$name"."[Null_$ns.$directive]",
+ 'name' => "$name" . "[Null_$ns.$directive]",
'id' => "$name:Null_$ns.$directive",
'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!!
);
if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) {
// modify inline javascript slightly
- $attr['onclick'] = "toggleWriteability('$name:Yes_$ns.$directive',checked);toggleWriteability('$name:No_$ns.$directive',checked)";
+ $attr['onclick'] =
+ "toggleWriteability('$name:Yes_$ns.$directive',checked);" .
+ "toggleWriteability('$name:No_$ns.$directive',checked)";
+ }
+ if ($value === null) {
+ $attr['checked'] = 'checked';
}
- if ($value === null) $attr['checked'] = 'checked';
$ret .= $this->elementEmpty('input', $attr);
$ret .= $this->text(' or ');
$ret .= $this->elementEmpty('br');
@@ -235,10 +275,28 @@ public function render($ns, $directive, $value, $name, $config) {
/**
* Swiss-army knife configuration form field printer
*/
-class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
+class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer
+{
+ /**
+ * @type int
+ */
public $cols = 18;
+
+ /**
+ * @type int
+ */
public $rows = 5;
- public function render($ns, $directive, $value, $name, $config) {
+
+ /**
+ * @param string $ns
+ * @param string $directive
+ * @param string $value
+ * @param string $name
+ * @param HTMLPurifier_Config|array $config
+ * @return string
+ */
+ public function render($ns, $directive, $value, $name, $config)
+ {
if (is_array($config) && isset($config[0])) {
$gen_config = $config[0];
$config = $config[1];
@@ -262,6 +320,7 @@ public function render($ns, $directive, $value, $name, $config) {
foreach ($array as $val => $b) {
$value[] = $val;
}
+ //TODO does this need a break?
case HTMLPurifier_VarParser::ALIST:
$value = implode(PHP_EOL, $value);
break;
@@ -281,25 +340,27 @@ public function render($ns, $directive, $value, $name, $config) {
$value = serialize($value);
}
$attr = array(
- 'name' => "$name"."[$ns.$directive]",
+ 'name' => "$name" . "[$ns.$directive]",
'id' => "$name:$ns.$directive"
);
- if ($value === null) $attr['disabled'] = 'disabled';
+ if ($value === null) {
+ $attr['disabled'] = 'disabled';
+ }
if (isset($def->allowed)) {
$ret .= $this->start('select', $attr);
foreach ($def->allowed as $val => $b) {
$attr = array();
- if ($value == $val) $attr['selected'] = 'selected';
+ if ($value == $val) {
+ $attr['selected'] = 'selected';
+ }
$ret .= $this->element('option', $val, $attr);
}
$ret .= $this->end('select');
- } elseif (
- $type === HTMLPurifier_VarParser::TEXT ||
- $type === HTMLPurifier_VarParser::ITEXT ||
- $type === HTMLPurifier_VarParser::ALIST ||
- $type === HTMLPurifier_VarParser::HASH ||
- $type === HTMLPurifier_VarParser::LOOKUP
- ) {
+ } elseif ($type === HTMLPurifier_VarParser::TEXT ||
+ $type === HTMLPurifier_VarParser::ITEXT ||
+ $type === HTMLPurifier_VarParser::ALIST ||
+ $type === HTMLPurifier_VarParser::HASH ||
+ $type === HTMLPurifier_VarParser::LOOKUP) {
$attr['cols'] = $this->cols;
$attr['rows'] = $this->rows;
$ret .= $this->start('textarea', $attr);
@@ -317,8 +378,18 @@ public function render($ns, $directive, $value, $name, $config) {
/**
* Bool form field printer
*/
-class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer {
- public function render($ns, $directive, $value, $name, $config) {
+class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer
+{
+ /**
+ * @param string $ns
+ * @param string $directive
+ * @param string $value
+ * @param string $name
+ * @param HTMLPurifier_Config|array $config
+ * @return string
+ */
+ public function render($ns, $directive, $value, $name, $config)
+ {
if (is_array($config) && isset($config[0])) {
$gen_config = $config[0];
$config = $config[1];
@@ -336,12 +407,16 @@ public function render($ns, $directive, $value, $name, $config) {
$attr = array(
'type' => 'radio',
- 'name' => "$name"."[$ns.$directive]",
+ 'name' => "$name" . "[$ns.$directive]",
'id' => "$name:Yes_$ns.$directive",
'value' => '1'
);
- if ($value === true) $attr['checked'] = 'checked';
- if ($value === null) $attr['disabled'] = 'disabled';
+ if ($value === true) {
+ $attr['checked'] = 'checked';
+ }
+ if ($value === null) {
+ $attr['disabled'] = 'disabled';
+ }
$ret .= $this->elementEmpty('input', $attr);
$ret .= $this->start('label', array('for' => "$name:No_$ns.$directive"));
@@ -351,12 +426,16 @@ public function render($ns, $directive, $value, $name, $config) {
$attr = array(
'type' => 'radio',
- 'name' => "$name"."[$ns.$directive]",
+ 'name' => "$name" . "[$ns.$directive]",
'id' => "$name:No_$ns.$directive",
'value' => '0'
);
- if ($value === false) $attr['checked'] = 'checked';
- if ($value === null) $attr['disabled'] = 'disabled';
+ if ($value === false) {
+ $attr['checked'] = 'checked';
+ }
+ if ($value === null) {
+ $attr['disabled'] = 'disabled';
+ }
$ret .= $this->elementEmpty('input', $attr);
$ret .= $this->end('div');
diff --git a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php
index 8a8f126..5f2f2f8 100644
--- a/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php
+++ b/framework/vendors/htmlpurifier/standalone/HTMLPurifier/Printer/HTMLDefinition.php
@@ -4,11 +4,16 @@ class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer
{
/**
- * Instance of HTMLPurifier_HTMLDefinition, for easy access
+ * @type HTMLPurifier_HTMLDefinition, for easy access
*/
protected $def;
- public function render($config) {
+ /**
+ * @param HTMLPurifier_Config $config
+ * @return string
+ */
+ public function render($config)
+ {
$ret = '';
$this->config =& $config;
@@ -28,8 +33,10 @@ public function render($config) {
/**
* Renders the Doctype table
+ * @return string
*/
- protected function renderDoctype() {
+ protected function renderDoctype()
+ {
$doctype = $this->def->doctype;
$ret = '';
$ret .= $this->start('table');
@@ -45,8 +52,10 @@ protected function renderDoctype() {
/**
* Renders environment table, which is miscellaneous info
+ * @return string
*/
- protected function renderEnvironment() {
+ protected function renderEnvironment()
+ {
$def = $this->def;
$ret = '';
@@ -59,28 +68,28 @@ protected function renderEnvironment() {
$ret .= $this->row('Block wrap name', $def->info_block_wrapper);
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Global attributes');
- $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr),0,0);
+ $ret .= $this->element('th', 'Global attributes');
+ $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr), null, 0);
$ret .= $this->end('tr');
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Tag transforms');
- $list = array();
- foreach ($def->info_tag_transform as $old => $new) {
- $new = $this->getClass($new, 'TagTransform_');
- $list[] = "<$old> with $new";
- }
- $ret .= $this->element('td', $this->listify($list));
+ $ret .= $this->element('th', 'Tag transforms');
+ $list = array();
+ foreach ($def->info_tag_transform as $old => $new) {
+ $new = $this->getClass($new, 'TagTransform_');
+ $list[] = "<$old> with $new";
+ }
+ $ret .= $this->element('td', $this->listify($list));
$ret .= $this->end('tr');
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Pre-AttrTransform');
- $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre));
+ $ret .= $this->element('th', 'Pre-AttrTransform');
+ $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre));
$ret .= $this->end('tr');
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Post-AttrTransform');
- $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post));
+ $ret .= $this->element('th', 'Post-AttrTransform');
+ $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post));
$ret .= $this->end('tr');
$ret .= $this->end('table');
@@ -89,8 +98,10 @@ protected function renderEnvironment() {
/**
* Renders the Content Sets table
+ * @return string
*/
- protected function renderContentSets() {
+ protected function renderContentSets()
+ {
$ret = '';
$ret .= $this->start('table');
$ret .= $this->element('caption', 'Content Sets');
@@ -106,8 +117,10 @@ protected function renderContentSets() {
/**
* Renders the Elements ($info) table
+ * @return string
*/
- protected function renderInfo() {
+ protected function renderInfo()
+ {
$ret = '';
$ret .= $this->start('table');
$ret .= $this->element('caption', 'Elements ($info)');
@@ -118,39 +131,39 @@ protected function renderInfo() {
$ret .= $this->end('tr');
foreach ($this->def->info as $name => $def) {
$ret .= $this->start('tr');
- $ret .= $this->element('th', "<$name>", array('class'=>'heavy', 'colspan' => 2));
+ $ret .= $this->element('th', "<$name>", array('class' => 'heavy', 'colspan' => 2));
$ret .= $this->end('tr');
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Inline content');
- $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No');
+ $ret .= $this->element('th', 'Inline content');
+ $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No');
$ret .= $this->end('tr');
if (!empty($def->excludes)) {
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Excludes');
- $ret .= $this->element('td', $this->listifyTagLookup($def->excludes));
+ $ret .= $this->element('th', 'Excludes');
+ $ret .= $this->element('td', $this->listifyTagLookup($def->excludes));
$ret .= $this->end('tr');
}
if (!empty($def->attr_transform_pre)) {
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Pre-AttrTransform');
- $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre));
+ $ret .= $this->element('th', 'Pre-AttrTransform');
+ $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre));
$ret .= $this->end('tr');
}
if (!empty($def->attr_transform_post)) {
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Post-AttrTransform');
- $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post));
+ $ret .= $this->element('th', 'Post-AttrTransform');
+ $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post));
$ret .= $this->end('tr');
}
if (!empty($def->auto_close)) {
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Auto closed by');
- $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close));
+ $ret .= $this->element('th', 'Auto closed by');
+ $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close));
$ret .= $this->end('tr');
}
$ret .= $this->start('tr');
- $ret .= $this->element('th', 'Allowed attributes');
- $ret .= $this->element('td',$this->listifyAttr($def->attr), array(), 0);
+ $ret .= $this->element('th', 'Allowed attributes');
+ $ret .= $this->element('td', $this->listifyAttr($def->attr), array(), 0);
$ret .= $this->end('tr');
if (!empty($def->required_attr)) {
@@ -165,64 +178,94 @@ protected function renderInfo() {
/**
* Renders a row describing the allowed children of an element
- * @param $def HTMLPurifier_ChildDef of pertinent element
+ * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element
+ * @return string
*/
- protected function renderChildren($def) {
+ protected function renderChildren($def)
+ {
$context = new HTMLPurifier_Context();
$ret = '';
$ret .= $this->start('tr');
- $elements = array();
- $attr = array();
- if (isset($def->elements)) {
- if ($def->type == 'strictblockquote') {
- $def->validateChildren(array(), $this->config, $context);
- }
- $elements = $def->elements;
+ $elements = array();
+ $attr = array();
+ if (isset($def->elements)) {
+ if ($def->type == 'strictblockquote') {
+ $def->validateChildren(array(), $this->config, $context);
}
- if ($def->type == 'chameleon') {
- $attr['rowspan'] = 2;
- } elseif ($def->type == 'empty') {
- $elements = array();
- } elseif ($def->type == 'table') {
- $elements = array_flip(array('col', 'caption', 'colgroup', 'thead',
- 'tfoot', 'tbody', 'tr'));
- }
- $ret .= $this->element('th', 'Allowed children', $attr);
-
- if ($def->type == 'chameleon') {
-
- $ret .= $this->element('td',
- 'Block: ' .
- $this->escape($this->listifyTagLookup($def->block->elements)),0,0);
- $ret .= $this->end('tr');
- $ret .= $this->start('tr');
- $ret .= $this->element('td',
- 'Inline: ' .
- $this->escape($this->listifyTagLookup($def->inline->elements)),0,0);
-
- } elseif ($def->type == 'custom') {
+ $elements = $def->elements;
+ }
+ if ($def->type == 'chameleon') {
+ $attr['rowspan'] = 2;
+ } elseif ($def->type == 'empty') {
+ $elements = array();
+ } elseif ($def->type == 'table') {
+ $elements = array_flip(
+ array(
+ 'col',
+ 'caption',
+ 'colgroup',
+ 'thead',
+ 'tfoot',
+ 'tbody',
+ 'tr'
+ )
+ );
+ }
+ $ret .= $this->element('th', 'Allowed children', $attr);
- $ret .= $this->element('td', ''.ucfirst($def->type).': ' .
- $def->dtd_regex);
+ if ($def->type == 'chameleon') {
- } else {
- $ret .= $this->element('td',
- ''.ucfirst($def->type).': ' .
- $this->escape($this->listifyTagLookup($elements)),0,0);
- }
+ $ret .= $this->element(
+ 'td',
+ 'Block: ' .
+ $this->escape($this->listifyTagLookup($def->block->elements)),
+ null,
+ 0
+ );
+ $ret .= $this->end('tr');
+ $ret .= $this->start('tr');
+ $ret .= $this->element(
+ 'td',
+ 'Inline: ' .
+ $this->escape($this->listifyTagLookup($def->inline->elements)),
+ null,
+ 0
+ );
+
+ } elseif ($def->type == 'custom') {
+
+ $ret .= $this->element(
+ 'td',
+ '' . ucfirst($def->type) . ': ' .
+ $def->dtd_regex
+ );
+
+ } else {
+ $ret .= $this->element(
+ 'td',
+ '' . ucfirst($def->type) . ': ' .
+ $this->escape($this->listifyTagLookup($elements)),
+ null,
+ 0
+ );
+ }
$ret .= $this->end('tr');
return $ret;
}
/**
* Listifies a tag lookup table.
- * @param $array Tag lookup array in form of array('tagname' => true)
+ * @param array $array Tag lookup array in form of array('tagname' => true)
+ * @return string
*/
- protected function listifyTagLookup($array) {
+ protected function listifyTagLookup($array)
+ {
ksort($array);
$list = array();
foreach ($array as $name => $discard) {
- if ($name !== '#PCDATA' && !isset($this->def->info[$name])) continue;
+ if ($name !== '#PCDATA' && !isset($this->def->info[$name])) {
+ continue;
+ }
$list[] = $name;
}
return $this->listify($list);
@@ -230,13 +273,15 @@ protected function listifyTagLookup($array) {
/**
* Listifies a list of objects by retrieving class names and internal state
- * @param $array List of objects
+ * @param array $array List of objects
+ * @return string
* @todo Also add information about internal state
*/
- protected function listifyObjectList($array) {
+ protected function listifyObjectList($array)
+ {
ksort($array);
$list = array();
- foreach ($array as $discard => $obj) {
+ foreach ($array as $obj) {
$list[] = $this->getClass($obj, 'AttrTransform_');
}
return $this->listify($list);
@@ -244,13 +289,17 @@ protected function listifyObjectList($array) {
/**
* Listifies a hash of attributes to AttrDef classes
- * @param $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef)
+ * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef)
+ * @return string
*/
- protected function listifyAttr($array) {
+ protected function listifyAttr($array)
+ {
ksort($array);
$list = array();
foreach ($array as $name => $obj) {
- if ($obj === false) continue;
+ if ($obj === false) {
+ continue;
+ }
$list[] = "$name = " . $this->getClass($obj, 'AttrDef_') . '';
}
return $this->listify($list);
@@ -258,15 +307,18 @@ protected function listifyAttr($array) {
/**
* Creates a heavy header row
+ * @param string $text
+ * @param int $num
+ * @return string
*/
- protected function heavyHeader($text, $num = 1) {
+ protected function heavyHeader($text, $num = 1)
+ {
$ret = '';
$ret .= $this->start('tr');
$ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy'));
$ret .= $this->end('tr');
return $ret;
}
-
}
// vim: et sw=4 sts=4
diff --git a/framework/vendors/jquery/LICENSE.txt b/framework/vendors/jquery/LICENSE.txt
index 1b63f2a..6a62c1d 100644
--- a/framework/vendors/jquery/LICENSE.txt
+++ b/framework/vendors/jquery/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright 2012 jQuery Foundation and other contributors
+Copyright 2014 jQuery Foundation and other contributors
http://jquery.com/
Permission is hereby granted, free of charge, to any person obtaining
diff --git a/framework/vendors/jqueryui/MIT-LICENSE.txt b/framework/vendors/jqueryui/MIT-LICENSE.txt
index 7aaf1e8..c36fa56 100644
--- a/framework/vendors/jqueryui/MIT-LICENSE.txt
+++ b/framework/vendors/jqueryui/MIT-LICENSE.txt
@@ -1,10 +1,14 @@
-Copyright 2012 jQuery Foundation and other contributors,
-http://jqueryui.com/
+Copyright 2007, 2014 jQuery Foundation and other contributors,
+https://jquery.org/
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/
+individuals. For exact contribution history, see the revision history
+available at https://github.com/jquery/jquery-ui
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -23,4 +27,18 @@ 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.
\ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code contained within the demos directory.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+All files located in the node_modules and external directories are
+externally maintained libraries used by this software which have their
+own licenses; we recommend you read them, as their terms may differ from
+the terms above.
\ No newline at end of file
diff --git a/framework/views/da/error.php b/framework/views/da/error.php
new file mode 100644
index 0000000..86cc704
--- /dev/null
+++ b/framework/views/da/error.php
@@ -0,0 +1,37 @@
+
+
+
+
+Fejl
+
+
+
+
+
+
Fejl
+
+
+Ovenstående fejl opstod da webserveren behandlede din forespørgsel.
+
+
+Hvis du tror at dette er en server-fejl, kontakt venligst .
+
+
+Tak!
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/views/da/error400.php b/framework/views/da/error400.php
new file mode 100644
index 0000000..fa5b051
--- /dev/null
+++ b/framework/views/da/error400.php
@@ -0,0 +1,33 @@
+
+
+
+
+Ugyldig forespørgsel
+
+
+
+
Ugyldig forespørgsel
+
+
+Serveren forstod ikke syntaksen i forespørgslen. Gentag venligst ikke
+forespørgslen uændret.
+
+
+Hvis du tror at dette er en server-fejl, kontakt venligst .
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/views/da/error403.php b/framework/views/da/error403.php
new file mode 100644
index 0000000..31cde6a
--- /dev/null
+++ b/framework/views/da/error403.php
@@ -0,0 +1,32 @@
+
+
+
+
+Uautoriseret
+
+
+
+
Uautoriseret
+
+
+Du har ikke tilstrækkelige rettigheder til at tilgå denne side.
+
+
+Hvis du tror at dette er en server-fejl, kontakt venligst .
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/views/da/error404.php b/framework/views/da/error404.php
new file mode 100644
index 0000000..6b79b98
--- /dev/null
+++ b/framework/views/da/error404.php
@@ -0,0 +1,33 @@
+
+
+
+
+Siden findes ikke
+
+
+
+
Siden findes ikke
+
+
+Serveren kunne ikke finde den forespurgte side.
+Hvis du skrev URL'en manuelt tjek venligst om den er korrekt og prøv igen.
+
+
+Hvis du tror at dette er en server-fejl, kontakt venligst .
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/views/da/error500.php b/framework/views/da/error500.php
new file mode 100644
index 0000000..6b100b9
--- /dev/null
+++ b/framework/views/da/error500.php
@@ -0,0 +1,35 @@
+
+
+
+
+Intern serverfejl
+
+
+
+
+
+
Intern serverfejl
+
+
+Det opstod en fejl, og serveren kan ikke fuldføre forespørgslen.
+Kontakt venligst for at rapportere problemet.
+
+
+Tak!
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/views/da/error503.php b/framework/views/da/error503.php
new file mode 100644
index 0000000..a3e8e4e
--- /dev/null
+++ b/framework/views/da/error503.php
@@ -0,0 +1,31 @@
+
+
+
+
+Tjenesten er utilgængelig
+
+
+
+
Tjenesten er utilgængelig
+
+Serveren er utilgængelig i øjeblikket grundet vedligeholdelse. Prøv venligst igen senere.
+
+
+Tak!
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/views/da/exception.php b/framework/views/da/exception.php
new file mode 100644
index 0000000..c6e56b3
--- /dev/null
+++ b/framework/views/da/exception.php
@@ -0,0 +1,242 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/views/da/profile-callstack-firebug.php b/framework/views/da/profile-callstack-firebug.php
new file mode 100644
index 0000000..890eadf
--- /dev/null
+++ b/framework/views/da/profile-callstack-firebug.php
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/framework/views/da/profile-callstack.php b/framework/views/da/profile-callstack.php
new file mode 100644
index 0000000..a0ee300
--- /dev/null
+++ b/framework/views/da/profile-callstack.php
@@ -0,0 +1,30 @@
+
+
+
\ No newline at end of file
diff --git a/framework/views/error403.php b/framework/views/error403.php
index c6ecf61..e589009 100644
--- a/framework/views/error403.php
+++ b/framework/views/error403.php
@@ -4,7 +4,7 @@
-Unauthorized
+Forbidden
-
Unauthorized
+
Forbidden
You do not have the proper credential to access this page.
diff --git a/framework/views/ja/error403.php b/framework/views/ja/error403.php
index f6220dc..aae953c 100644
--- a/framework/views/ja/error403.php
+++ b/framework/views/ja/error403.php
@@ -4,7 +4,7 @@
-403 未認証
+403 禁止
-
403 未認証
+
403 禁止
あなたはこのページをアクセスする正当な許可を得ていません。
diff --git a/framework/web/CAssetManager.php b/framework/web/CAssetManager.php
index 38708d4..d7e7963 100644
--- a/framework/web/CAssetManager.php
+++ b/framework/web/CAssetManager.php
@@ -211,7 +211,7 @@ public function publish($path,$hashByName=false,$level=-1,$forceCopy=null)
throw new CException(Yii::t('yii','The "forceCopy" and "linkAssets" cannot be both true.'));
if(isset($this->_published[$path]))
return $this->_published[$path];
- elseif(($src=realpath($path))!==false)
+ elseif(is_string($path) && ($src=realpath($path))!==false)
{
$dir=$this->generatePath($src,$hashByName);
$dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir;
@@ -271,7 +271,7 @@ public function publish($path,$hashByName=false,$level=-1,$forceCopy=null)
*/
public function getPublishedPath($path,$hashByName=false)
{
- if(($path=realpath($path))!==false)
+ if(is_string($path) && ($path=realpath($path))!==false)
{
$base=$this->getBasePath().DIRECTORY_SEPARATOR.$this->generatePath($path,$hashByName);
return is_file($path) ? $base.DIRECTORY_SEPARATOR.basename($path) : $base ;
@@ -295,7 +295,7 @@ public function getPublishedUrl($path,$hashByName=false)
{
if(isset($this->_published[$path]))
return $this->_published[$path];
- if(($path=realpath($path))!==false)
+ if(is_string($path) && ($path=realpath($path))!==false)
{
$base=$this->getBaseUrl().'/'.$this->generatePath($path,$hashByName);
return is_file($path) ? $base.'/'.basename($path) : $base;
@@ -325,9 +325,9 @@ protected function hash($path)
protected function generatePath($file,$hashByName=false)
{
if (is_file($file))
- $pathForHashing=$hashByName ? basename($file) : dirname($file).filemtime($file);
+ $pathForHashing=$hashByName ? dirname($file) : dirname($file).filemtime($file);
else
- $pathForHashing=$hashByName ? basename($file) : $file.filemtime($file);
+ $pathForHashing=$hashByName ? $file : $file.filemtime($file);
return $this->hash($pathForHashing);
}
diff --git a/framework/web/CBaseController.php b/framework/web/CBaseController.php
index 9f5d649..e738f17 100644
--- a/framework/web/CBaseController.php
+++ b/framework/web/CBaseController.php
@@ -163,8 +163,16 @@ public function widget($className,$properties=array(),$captureOutput=false)
{
ob_start();
ob_implicit_flush(false);
- $widget=$this->createWidget($className,$properties);
- $widget->run();
+ try
+ {
+ $widget=$this->createWidget($className,$properties);
+ $widget->run();
+ }
+ catch(Exception $e)
+ {
+ ob_end_clean();
+ throw $e;
+ }
return ob_get_clean();
}
else
diff --git a/framework/web/CClientScript.php b/framework/web/CClientScript.php
index f7d3c20..586fb47 100644
--- a/framework/web/CClientScript.php
+++ b/framework/web/CClientScript.php
@@ -318,13 +318,14 @@ protected function renderScriptBatch(array $scripts)
$scriptContent = $scriptValue['content'];
unset($scriptValue['content']);
$scriptHtmlOptions = $scriptValue;
+ ksort($scriptHtmlOptions);
}
else
{
$scriptContent = $scriptValue;
$scriptHtmlOptions = array();
}
- $key=serialize(ksort($scriptHtmlOptions));
+ $key=serialize($scriptHtmlOptions);
$scriptBatches[$key]['htmlOptions']=$scriptHtmlOptions;
$scriptBatches[$key]['scripts'][]=$scriptContent;
}
@@ -553,7 +554,7 @@ public function getPackageBaseUrl($name)
* 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).
+ * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
* @since 1.1.7
* @see renderCoreScript
*/
@@ -565,7 +566,7 @@ public function registerPackage($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).
+ * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
* @see renderCoreScript
*/
public function registerCoreScript($name)
@@ -593,6 +594,11 @@ public function registerCoreScript($name)
$params=func_get_args();
$this->recordCachingAction('clientScript','registerCoreScript',$params);
}
+ elseif(YII_DEBUG)
+ throw new CException('There is no CClientScript package: '.$name);
+ else
+ Yii::log('There is no CClientScript package: '.$name,CLogger::LEVEL_WARNING,'system.web.CClientScript');
+
return $this;
}
@@ -600,7 +606,7 @@ public function registerCoreScript($name)
* 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).
+ * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerCssFile($url,$media='')
{
@@ -616,7 +622,7 @@ public function registerCssFile($url,$media='')
* @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).
+ * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerCss($id,$css,$media='')
{
@@ -637,7 +643,7 @@ public function registerCss($id,$css,$media='')
*
CClientScript::POS_END : the script is inserted at the end of the body section.
*
* @param array $htmlOptions additional HTML attributes
- * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
+ * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerScriptFile($url,$position=null,array $htmlOptions=array())
{
@@ -671,7 +677,7 @@ public function registerScriptFile($url,$position=null,array $htmlOptions=array(
*
* @param array $htmlOptions additional HTML attributes
* Note: HTML attributes are not allowed for script positions "CClientScript::POS_LOAD" and "CClientScript::POS_READY".
- * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
+ * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerScript($id,$script,$position=null,array $htmlOptions=array())
{
@@ -711,7 +717,7 @@ public function registerScript($id,$script,$position=null,array $htmlOptions=arr
* @param string $httpEquiv http-equiv attribute of the meta tag. If null, the attribute will not be generated
* @param array $options other options in name-value pairs (e.g. 'scheme', 'lang')
* @param string $id Optional id of the meta tag to avoid duplicates
- * @return CClientScript the CClientScript object itself (to support method chaining, available since version 1.1.5).
+ * @return static the CClientScript object itself (to support method chaining, available since version 1.1.5).
*/
public function registerMetaTag($content,$name=null,$httpEquiv=null,$options=array(),$id=null)
{
@@ -734,7 +740,7 @@ public function registerMetaTag($content,$name=null,$httpEquiv=null,$options=arr
* @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).
+ * @return static 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())
{
@@ -829,7 +835,7 @@ protected function recordCachingAction($context,$method,$params)
* @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).
+ * @return static the CClientScript object itself (to support method chaining, available since version 1.1.10).
*
* @since 1.1.9
*/
diff --git a/framework/web/CHttpRequest.php b/framework/web/CHttpRequest.php
index f83efad..2075d8b 100644
--- a/framework/web/CHttpRequest.php
+++ b/framework/web/CHttpRequest.php
@@ -32,10 +32,11 @@
* @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 string $requestType Request type, such as GET, POST, HEAD, PUT, PATCH, 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 $isPatchRequest Whether this is a PATCH request.
* @property boolean $isAjaxRequest Whether this is an AJAX (XMLHttpRequest) request.
* @property boolean $isFlashRequest Whether this is an Adobe Flash or Adobe Flex request.
* @property string $serverName Server name.
@@ -99,6 +100,7 @@ class CHttpRequest extends CApplicationComponent
private $_preferredLanguages;
private $_csrfToken;
private $_restParams;
+ private $_httpVersion;
/**
* Initializes the application component.
@@ -250,7 +252,32 @@ public function getPut($name,$defaultValue=null)
}
/**
- * Returns request parameters. Typically PUT or DELETE.
+ * Returns the named PATCH parameter value.
+ * If the PATCH parameter does not exist or if the current request is not a PATCH request,
+ * the second parameter to this method will be returned.
+ * If the PATCH request was tunneled through POST via _method parameter, the POST parameter
+ * will be returned instead.
+ * @param string $name the PATCH parameter name
+ * @param mixed $defaultValue the default parameter value if the PATCH parameter does not exist.
+ * @return mixed the PATCH parameter value
+ * @since 1.1.16
+ */
+ public function getPatch($name,$defaultValue=null)
+ {
+ if($this->getIsPatchViaPostRequest())
+ return $this->getPost($name, $defaultValue);
+
+ if($this->getIsPatchRequest())
+ {
+ $restParams=$this->getRestParams();
+ return isset($restParams[$name]) ? $restParams[$name] : $defaultValue;
+ }
+ else
+ return $defaultValue;
+ }
+
+ /**
+ * Returns request parameters. Typically PUT, PATCH or DELETE.
* @return array the request parameters
* @since 1.1.7
* @since 1.1.13 method became public
@@ -446,7 +473,15 @@ public function getPathInfo()
else
throw new CException(Yii::t('yii','CHttpRequest is unable to determine the path info of the request.'));
- $this->_pathInfo=trim($pathInfo,'/');
+ if($pathInfo==='/')
+ $pathInfo='';
+ elseif($pathInfo[0]==='/')
+ $pathInfo=substr($pathInfo,1);
+
+ if(($posEnd=strlen($pathInfo)-1)>0 && $pathInfo[$posEnd]==='/')
+ $pathInfo=substr($pathInfo,0,$posEnd);
+
+ $this->_pathInfo=$pathInfo;
}
return $this->_pathInfo;
}
@@ -537,21 +572,23 @@ public function getQueryString()
*/
public function getIsSecureConnection()
{
- return isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS']=='on' || $_SERVER['HTTPS']==1)
- || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO']=='https';
+ return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'],'on')===0 || $_SERVER['HTTPS']==1)
+ || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'],'https')===0;
}
/**
- * Returns the request type, such as GET, POST, HEAD, PUT, DELETE.
+ * Returns the request type, such as GET, POST, HEAD, PUT, PATCH, DELETE.
* Request type can be manually set in POST requests with a parameter named _method. Useful
- * for RESTful request from older browsers which do not support PUT or DELETE
+ * for RESTful request from older browsers which do not support PUT, PATCH or DELETE
* natively (available since version 1.1.11).
- * @return string request type, such as GET, POST, HEAD, PUT, DELETE.
+ * @return string request type, such as GET, POST, HEAD, PUT, PATCH, DELETE.
*/
public function getRequestType()
{
if(isset($_POST['_method']))
return strtoupper($_POST['_method']);
+ elseif(isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']))
+ return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
return strtoupper(isset($_SERVER['REQUEST_METHOD'])?$_SERVER['REQUEST_METHOD']:'GET');
}
@@ -605,6 +642,26 @@ protected function getIsPutViaPostRequest()
return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'PUT');
}
+ /**
+ * Returns whether this is a PATCH request.
+ * @return boolean whether this is a PATCH request.
+ * @since 1.1.16
+ */
+ public function getIsPatchRequest()
+ {
+ return (isset($_SERVER['REQUEST_METHOD']) && !strcasecmp($_SERVER['REQUEST_METHOD'],'PATCH')) || $this->getIsPatchViaPostRequest();
+ }
+
+ /**
+ * Returns whether this is a PATCH request which was tunneled through POST.
+ * @return boolean whether this is a PATCH request tunneled through POST.
+ * @since 1.1.16
+ */
+ protected function getIsPatchViaPostRequest()
+ {
+ return isset($_POST['_method']) && !strcasecmp($_POST['_method'],'PATCH');
+ }
+
/**
* Returns whether this is an AJAX (XMLHttpRequest) request.
* @return boolean whether this is an AJAX (XMLHttpRequest) request.
@@ -975,14 +1032,31 @@ public function getPreferredLanguages()
}
/**
- * Returns the user preferred language.
- * The returned language ID will be canonicalized using {@link CLocale::getCanonicalID}.
- * @return string the user preferred language or false if the user does not have any.
+ * Returns the user-preferred language that should be used by this application.
+ * The language resolution is based on the user preferred languages and the languages
+ * supported by the application. The method will try to find the best match.
+ * @param array $languages a list of the languages supported by the application.
+ * If empty, this method will return the first language returned by [[getPreferredLanguages()]].
+ * @return string the language that the application should use. false is returned if both [[getPreferredLanguages()]]
+ * and `$languages` are empty.
*/
- public function getPreferredLanguage()
+ public function getPreferredLanguage($languages=array())
{
$preferredLanguages=$this->getPreferredLanguages();
- return !empty($preferredLanguages) ? CLocale::getCanonicalID($preferredLanguages[0]) : false;
+ if(empty($languages)) {
+ return !empty($preferredLanguages) ? CLocale::getCanonicalID($preferredLanguages[0]) : false;
+ }
+ foreach ($preferredLanguages as $preferredLanguage) {
+ $preferredLanguage=CLocale::getCanonicalID($preferredLanguage);
+ foreach ($languages as $language) {
+ $language=CLocale::getCanonicalID($language);
+ // en_us==en_us, en==en_us, en_us==en
+ if($language===$acceptedLanguage || strpos($acceptedLanguage,$language.'_')===0 || strpos($language,$acceptedLanguage.'_')===0) {
+ return $language;
+ }
+ }
+ }
+ return reset($languages);
}
/**
@@ -1004,6 +1078,7 @@ public function sendFile($fileName,$content,$mimeType=null,$terminate=true)
$contentStart=0;
$contentEnd=$fileSize-1;
+ $httpVersion=$this->getHttpVersion();
if(isset($_SERVER['HTTP_RANGE']))
{
header('Accept-Ranges: bytes');
@@ -1045,11 +1120,11 @@ public function sendFile($fileName,$content,$mimeType=null,$terminate=true)
throw new CHttpException(416,'Requested Range Not Satisfiable');
}
- header('HTTP/1.1 206 Partial Content');
+ header("HTTP/$httpVersion 206 Partial Content");
header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
}
else
- header('HTTP/1.1 200 OK');
+ header("HTTP/$httpVersion 200 OK");
$length=$contentEnd-$contentStart+1; // Calculate new content length
@@ -1219,6 +1294,7 @@ public function validateCsrfToken($event)
{
if ($this->getIsPostRequest() ||
$this->getIsPutRequest() ||
+ $this->getIsPatchRequest() ||
$this->getIsDeleteRequest())
{
$cookies=$this->getCookies();
@@ -1232,6 +1308,9 @@ public function validateCsrfToken($event)
case 'PUT':
$userToken=$this->getPut($this->csrfTokenName);
break;
+ case 'PATCH':
+ $userToken=$this->getPatch($this->csrfTokenName);
+ break;
case 'DELETE':
$userToken=$this->getDelete($this->csrfTokenName);
}
@@ -1247,6 +1326,25 @@ public function validateCsrfToken($event)
throw new CHttpException(400,Yii::t('yii','The CSRF token could not be verified.'));
}
}
+
+
+ /**
+ * Returns the version of the HTTP protocol used by client.
+ *
+ * @return string the version of the HTTP protocol.
+ * @since 1.1.16
+ */
+ public function getHttpVersion()
+ {
+ if($this->_httpVersion===null)
+ {
+ if(isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL']==='HTTP/1.0')
+ $this->_httpVersion='1.0';
+ else
+ $this->_httpVersion='1.1';
+ }
+ return $this->_httpVersion;
+ }
}
diff --git a/framework/web/CHttpSession.php b/framework/web/CHttpSession.php
index 0554b69..e01266e 100644
--- a/framework/web/CHttpSession.php
+++ b/framework/web/CHttpSession.php
@@ -178,7 +178,8 @@ public function setSessionID($value)
*/
public function regenerateID($deleteOldSession=false)
{
- session_regenerate_id($deleteOldSession);
+ if($this->getIsStarted())
+ session_regenerate_id($deleteOldSession);
}
/**
diff --git a/framework/web/CSort.php b/framework/web/CSort.php
index 9fb5fdb..5353a71 100644
--- a/framework/web/CSort.php
+++ b/framework/web/CSort.php
@@ -245,9 +245,9 @@ public function getOrderBy($criteria=null)
if(is_array($definition))
{
if($descending)
- $orders[]=isset($definition['desc']) ? $definition['desc'] : $attribute.' DESC';
+ $orders[]=isset($definition['desc']) ? (is_array($definition['desc']) ? implode(', ',$definition['desc']) : $definition['desc']) : $attribute.' DESC';
else
- $orders[]=isset($definition['asc']) ? $definition['asc'] : $attribute;
+ $orders[]=isset($definition['asc']) ? (is_array($definition['asc']) ? implode(', ',$definition['asc']) : $definition['asc']) : $attribute;
}
elseif($definition!==false)
{
@@ -469,4 +469,4 @@ protected function createLink($attribute,$label,$url,$htmlOptions)
{
return CHtml::link($label,$url,$htmlOptions);
}
-}
\ No newline at end of file
+}
diff --git a/framework/web/CUploadedFile.php b/framework/web/CUploadedFile.php
index 1548b36..8e9ccf0 100644
--- a/framework/web/CUploadedFile.php
+++ b/framework/web/CUploadedFile.php
@@ -267,9 +267,6 @@ public function getHasError()
*/
public function getExtensionName()
{
- if(($pos=strrpos($this->_name,'.'))!==false)
- return (string)substr($this->_name,$pos+1);
- else
- return '';
+ return CFileHelper::getExtension($this->_name);
}
}
diff --git a/framework/web/CUrlManager.php b/framework/web/CUrlManager.php
index cca699f..688a3ab 100644
--- a/framework/web/CUrlManager.php
+++ b/framework/web/CUrlManager.php
@@ -658,6 +658,7 @@ public function __construct($route,$pattern)
$this->route=trim($route,'/');
$tr2['/']=$tr['/']='\\/';
+ $tr['.']='\\.';
if(strpos($route,'<')!==false && preg_match_all('/<(\w+)>/',$route,$matches2))
{
diff --git a/framework/web/CWebApplication.php b/framework/web/CWebApplication.php
index fa651d4..8d63a12 100644
--- a/framework/web/CWebApplication.php
+++ b/framework/web/CWebApplication.php
@@ -346,7 +346,7 @@ public function createController($route,$owner=null)
$classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php';
if($owner->controllerNamespace!==null)
- $className=$owner->controllerNamespace.'\\'.$className;
+ $className=$owner->controllerNamespace.'\\'.str_replace('/','\\',$controllerID).$className;
if(is_file($classFile))
{
diff --git a/framework/web/actions/CAction.php b/framework/web/actions/CAction.php
index ab04e5e..b2dad0e 100644
--- a/framework/web/actions/CAction.php
+++ b/framework/web/actions/CAction.php
@@ -72,8 +72,9 @@ public function runWithParams($params)
$method=new ReflectionMethod($this, 'run');
if($method->getNumberOfParameters()>0)
return $this->runWithParamsInternal($this, $method, $params);
- else
- return $this->run();
+
+ $this->run();
+ return true;
}
/**
diff --git a/framework/web/actions/CInlineAction.php b/framework/web/actions/CInlineAction.php
index da6581f..d93225f 100644
--- a/framework/web/actions/CInlineAction.php
+++ b/framework/web/actions/CInlineAction.php
@@ -45,8 +45,8 @@ public function runWithParams($params)
$method=new ReflectionMethod($controller, $methodName);
if($method->getNumberOfParameters()>0)
return $this->runWithParamsInternal($controller, $method, $params);
- else
- return $controller->$methodName();
- }
+ $controller->$methodName();
+ return true;
+ }
}
diff --git a/framework/web/actions/CViewAction.php b/framework/web/actions/CViewAction.php
index 22886f8..daa3c86 100644
--- a/framework/web/actions/CViewAction.php
+++ b/framework/web/actions/CViewAction.php
@@ -93,7 +93,7 @@ public function getRequestedView()
* 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
+ * @throws CHttpException if the user-specified view is invalid
*/
protected function resolveView($viewPath)
{
diff --git a/framework/web/auth/CAuthManager.php b/framework/web/auth/CAuthManager.php
index 7916f47..7c70375 100644
--- a/framework/web/auth/CAuthManager.php
+++ b/framework/web/auth/CAuthManager.php
@@ -18,7 +18,7 @@
* 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
+ * {@link CAuthItem authorization items}. Items on higher 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.
diff --git a/framework/web/auth/CDbAuthManager.php b/framework/web/auth/CDbAuthManager.php
index 41d24fa..0a254c4 100644
--- a/framework/web/auth/CDbAuthManager.php
+++ b/framework/web/auth/CDbAuthManager.php
@@ -421,7 +421,7 @@ public function getAuthItems($type=null,$userId=null)
* 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
+ * Authorization items form a hierarchy. Higher level items inherit 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).
diff --git a/framework/web/auth/CPhpAuthManager.php b/framework/web/auth/CPhpAuthManager.php
index bb8953f..3584f1d 100644
--- a/framework/web/auth/CPhpAuthManager.php
+++ b/framework/web/auth/CPhpAuthManager.php
@@ -100,7 +100,7 @@ public function checkAccess($itemName,$userId,$params=array())
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)));
+ throw new CException(Yii::t('yii','Either "{parent}" or "{child}" does not exist.',array('{child}'=>$childName,'{parent}'=>$itemName)));
$child=$this->_items[$childName];
$item=$this->_items[$itemName];
$this->checkItemChildType($item->getType(),$child->getType());
@@ -272,7 +272,7 @@ public function getAuthItems($type=null,$userId=null)
* 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
+ * Authorization items form a hierarchy. Higher level items inherit 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).
diff --git a/framework/web/auth/schema-cubrid.sql b/framework/web/auth/schema-cubrid.sql
new file mode 100644
index 0000000..2585aca
--- /dev/null
+++ b/framework/web/auth/schema-cubrid.sql
@@ -0,0 +1,42 @@
+/**
+ * Database schema required by CDbAuthManager.
+ *
+ * @author Esen Sagynov
+ * @link http://www.yiiframework.com/
+ * @copyright 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @since 1.1.14
+ */
+
+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` varchar(65535),
+ `bizrule` varchar(65535),
+ `data` string,
+ 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 restrict,
+ foreign key (`child`) references `AuthItem` (`name`) on delete cascade on update restrict
+);
+
+create table `AuthAssignment`
+(
+ `itemname` varchar(64) not null,
+ `userid` varchar(64) not null,
+ `bizrule` varchar(65535),
+ `data` string,
+ primary key (`itemname`,`userid`),
+ foreign key (`itemname`) references `AuthItem` (`name`) on delete cascade on update restrict
+);
diff --git a/framework/web/filters/CHttpCacheFilter.php b/framework/web/filters/CHttpCacheFilter.php
index f69d8be..2f1f5cf 100644
--- a/framework/web/filters/CHttpCacheFilter.php
+++ b/framework/web/filters/CHttpCacheFilter.php
@@ -180,7 +180,8 @@ protected function checkLastModified($lastModified)
*/
protected function send304Header()
{
- header('HTTP/1.1 304 Not Modified');
+ $httpVersion=Yii::app()->request->getHttpVersion();
+ header("HTTP/$httpVersion 304 Not Modified");
}
/**
diff --git a/framework/web/form/CForm.php b/framework/web/form/CForm.php
index 3fc2645..29372a9 100644
--- a/framework/web/form/CForm.php
+++ b/framework/web/form/CForm.php
@@ -133,6 +133,14 @@ class CForm extends CFormElement implements ArrayAccess
* @since 1.1.14
*/
public $showErrors;
+ /**
+ * @var string|null HTML code to prepend to the list of errors in the error summary. See {@link CActiveForm::errorSummary()}.
+ */
+ public $errorSummaryHeader;
+ /**
+ * @var string|null HTML code to append to the list of errors in the error summary. See {@link CActiveForm::errorSummary()}.
+ */
+ public $errorSummaryFooter;
/**
* @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.
@@ -476,7 +484,7 @@ public function renderBody()
$output.="
\n".$this->description."
\n";
if($this->showErrorSummary && ($model=$this->getModel(false))!==null)
- $output.=$this->getActiveFormWidget()->errorSummary($model)."\n";
+ $output.=$this->getActiveFormWidget()->errorSummary($model,$this->errorSummaryHeader,$this->errorSummaryFooter)."\n";
$output.=$this->renderElements()."\n".$this->renderButtons()."\n";
diff --git a/framework/web/form/CFormElement.php b/framework/web/form/CFormElement.php
index a8a2428..841a2c1 100644
--- a/framework/web/form/CFormElement.php
+++ b/framework/web/form/CFormElement.php
@@ -86,6 +86,27 @@ public function __get($name)
array('{class}'=>get_class($this), '{property}'=>$name)));
}
+ /**
+ * Checks a property value or an attribute value on existence or not null
+ * 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:
+ *
+ * isset($element->propertyName);
+ *
+ * @param string $name the property or attribute name
+ * @return boolean
+ */
+ public function __isset($name)
+ {
+ $getter='get'.$name;
+ if(method_exists($this,$getter))
+ return $this->$getter()!==null;
+ elseif(isset($this->attributes[$name]))
+ return isset($this->attributes[$name]);
+ else
+ return false;
+ }
+
/**
* Sets value of a property or attribute.
* Do not call this method. This is a PHP magic method that we override
diff --git a/framework/web/form/CFormInputElement.php b/framework/web/form/CFormInputElement.php
index 0549dcb..3eb7390 100644
--- a/framework/web/form/CFormInputElement.php
+++ b/framework/web/form/CFormInputElement.php
@@ -29,6 +29,13 @@
*
number: an HTML5 number input generated using {@link CHtml::activeNumberField}
*
range: an HTML5 range input generated using {@link CHtml::activeRangeField}
*
date: an HTML5 date input generated using {@link CHtml::activeDateField}
+ *
time: an HTML5 time input generated using {@link CHtml::activeTimeField}
+ *
datetime: an HTML5 datetime input generated using {@link CHtml::activeDateTimeField}
+ *
datetimelocal: an HTML5 datetime-local input generated using {@link CHtml::activeDateTimeLocalField}
+ *
week: an HTML5 week input generated using {@link CHtml::activeWeekField}
+ *
color: an HTML5 color input generated using {@link CHtml::activeColorField}
+ *
tel: an HTML5 tel input generated using {@link CHtml::activeTelField}
+ *
search: an HTML5 search input generated using {@link CHtml::activeSearchField}
*
* 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
@@ -68,7 +75,14 @@ class CFormInputElement extends CFormElement
'email'=>'activeEmailField',
'number'=>'activeNumberField',
'range'=>'activeRangeField',
- 'date'=>'activeDateField'
+ 'date'=>'activeDateField',
+ 'time'=>'activeTimeField',
+ 'datetime'=>'activeDateTimeField',
+ 'datetimelocal'=>'activeDateTimeLocalField',
+ 'week'=>'activeWeekField',
+ 'color'=>'activeColorField',
+ 'tel'=>'activeTelField',
+ 'search'=>'activeSearchField',
);
/**
@@ -197,9 +211,7 @@ public function renderLabel()
);
if(!empty($this->attributes['id']))
- {
- $options['for'] = $this->attributes['id'];
- }
+ $options['for']=$this->attributes['id'];
return CHtml::activeLabel($this->getParent()->getModel(), $this->name, $options);
}
diff --git a/framework/web/helpers/CHtml.php b/framework/web/helpers/CHtml.php
index 3758c5c..c8e5585 100644
--- a/framework/web/helpers/CHtml.php
+++ b/framework/web/helpers/CHtml.php
@@ -12,6 +12,11 @@
/**
* CHtml is a static class that provides a collection of helper methods for creating HTML views.
*
+ * Nearly all of the methods in this class allow setting additional html attributes for the html
+ * tags they generate. You can specify for example. 'class', 'style' or 'id' for an html element.
+ * For example when using array('class' => 'my-class', 'target' => '_blank') as htmlOptions
+ * it will result in the html attributes rendered like this: class="my-class" target="_blank".
+ *
* @author Qiang Xue
* @package system.web.helpers
* @since 1.0
@@ -335,6 +340,14 @@ public static function form($action='',$method='post',$htmlOptions=array())
public static function beginForm($action='',$method='post',$htmlOptions=array())
{
$htmlOptions['action']=$url=self::normalizeUrl($action);
+ if(strcasecmp($method,'get')!==0 && strcasecmp($method,'post')!==0)
+ {
+ $customMethod=$method;
+ $method='post';
+ }
+ else
+ $customMethod=false;
+
$htmlOptions['method']=$method;
$form=self::tag('form',$htmlOptions,false,false);
$hiddens=array();
@@ -351,8 +364,10 @@ public static function beginForm($action='',$method='post',$htmlOptions=array())
$request=Yii::app()->request;
if($request->enableCsrfValidation && !strcasecmp($method,'post'))
$hiddens[]=self::hiddenField($request->csrfTokenName,$request->getCsrfToken(),array('id'=>false));
+ if($customMethod!==false)
+ $hiddens[]=self::hiddenField('_method',$customMethod);
if($hiddens!==array())
- $form.="\n".self::tag('div',array('style'=>'display:none'),implode("\n",$hiddens));
+ $form.="\n".implode("\n",$hiddens);
return $form;
}
@@ -580,6 +595,23 @@ public static function label($label,$for,$htmlOptions=array())
return self::tag('label',$htmlOptions,$label);
}
+ /**
+ * Generates a color picker 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
+ * @since 1.1.16
+ */
+ public static function colorField($name,$value='',$htmlOptions=array())
+ {
+ self::clientChange('change',$htmlOptions);
+ return self::inputField('color',$name,$value,$htmlOptions);
+ }
+
/**
* Generates a text field input.
* @param string $name the input name
@@ -596,6 +628,22 @@ public static function textField($name,$value='',$htmlOptions=array())
return self::inputField('text',$name,$value,$htmlOptions);
}
+ /**
+ * Generates a search 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
+ * @since 1.1.16
+ */
+ public static function searchField($name,$value='',$htmlOptions=array())
+ {
+ self::clientChange('change',$htmlOptions);
+ return self::inputField('search',$name,$value,$htmlOptions);
+ }
/**
* Generates a number field input.
* @param string $name the input name
@@ -664,6 +712,57 @@ public static function timeField($name,$value='',$htmlOptions=array())
return self::inputField('time',$name,$value,$htmlOptions);
}
+ /**
+ * Generates a datetime 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
+ * @since 1.1.16
+ */
+ public static function dateTimeField($name,$value='',$htmlOptions=array())
+ {
+ self::clientChange('change',$htmlOptions);
+ return self::inputField('datetime',$name,$value,$htmlOptions);
+ }
+
+ /**
+ * Generates a local datetime 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
+ * @since 1.1.16
+ */
+ public static function dateTimeLocalField($name,$value='',$htmlOptions=array())
+ {
+ self::clientChange('change',$htmlOptions);
+ return self::inputField('datetime-local',$name,$value,$htmlOptions);
+ }
+
+ /**
+ * Generates a week 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
+ * @since 1.1.16
+ */
+ public static function weekField($name,$value='',$htmlOptions=array())
+ {
+ self::clientChange('change',$htmlOptions);
+ return self::inputField('week',$name,$value,$htmlOptions);
+ }
+
/**
* Generates an email field input.
* @param string $name the input name
@@ -1024,7 +1123,7 @@ public static function listBox($name,$select,$data,$htmlOptions=array())
public static function checkBoxList($name,$select,$data,$htmlOptions=array())
{
$template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}';
- $separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:" \n";
+ $separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:self::tag('br');
$container=isset($htmlOptions['container'])?$htmlOptions['container']:'span';
unset($htmlOptions['template'],$htmlOptions['separator'],$htmlOptions['container']);
@@ -1140,7 +1239,7 @@ public static function checkBoxList($name,$select,$data,$htmlOptions=array())
public static function radioButtonList($name,$select,$data,$htmlOptions=array())
{
$template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}';
- $separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:" \n";
+ $separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:self::tag('br');
$container=isset($htmlOptions['container'])?$htmlOptions['container']:'span';
unset($htmlOptions['template'],$htmlOptions['separator'],$htmlOptions['container']);
@@ -1151,7 +1250,7 @@ public static function radioButtonList($name,$select,$data,$htmlOptions=array())
{
if(!is_array($htmlOptions['empty']))
$htmlOptions['empty']=array(''=>$htmlOptions['empty']);
- $data=array_merge($htmlOptions['empty'],$data);
+ $data=CMap::mergeArray($htmlOptions['empty'],$data);
unset($htmlOptions['empty']);
}
@@ -1574,6 +1673,86 @@ public static function activeTimeField($model,$attribute,$htmlOptions=array())
return self::activeInputField('time',$model,$attribute,$htmlOptions);
}
+ /**
+ * Generates a datetime 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
+ * @since 1.1.16
+ */
+ public static function activeDateTimeField($model,$attribute,$htmlOptions=array())
+ {
+ self::resolveNameID($model,$attribute,$htmlOptions);
+ self::clientChange('change',$htmlOptions);
+ return self::activeInputField('datetime',$model,$attribute,$htmlOptions);
+ }
+
+ /**
+ * Generates a datetime-local 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
+ * @since 1.1.16
+ */
+ public static function activeDateTimeLocalField($model,$attribute,$htmlOptions=array())
+ {
+ self::resolveNameID($model,$attribute,$htmlOptions);
+ self::clientChange('change',$htmlOptions);
+ return self::activeInputField('datetime-local',$model,$attribute,$htmlOptions);
+ }
+
+ /**
+ * Generates a week 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
+ * @since 1.1.16
+ */
+ public static function activeWeekField($model,$attribute,$htmlOptions=array())
+ {
+ self::resolveNameID($model,$attribute,$htmlOptions);
+ self::clientChange('change',$htmlOptions);
+ return self::activeInputField('week',$model,$attribute,$htmlOptions);
+ }
+
+ /**
+ * Generates a color picker 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
+ * @since 1.1.16
+ */
+ public static function activeColorField($model,$attribute,$htmlOptions=array())
+ {
+ self::resolveNameID($model,$attribute,$htmlOptions);
+ self::clientChange('change',$htmlOptions);
+ return self::activeInputField('color',$model,$attribute,$htmlOptions);
+ }
+
/**
* Generates a telephone field input for a model attribute.
* If the attribute has input error, the input field's CSS class will
@@ -1891,6 +2070,12 @@ public static function activeListBox($model,$attribute,$data,$htmlOptions=array(
* or is false, the 'check all' checkbox will be displayed at the beginning of
* the checkbox list.
*
encode: boolean, specifies whether to encode HTML-encode tag attributes and values. Defaults to true.
+ *
labelOptions: array, specifies the additional HTML attributes to be rendered
+ * for every label tag in the list.
+ *
container: string, specifies the checkboxes enclosing tag. Defaults to 'span'.
+ * If the value is an empty string, no enclosing tag will be generated
+ *
baseID: string, specifies the base ID prefix to be used for checkboxes in the list.
+ * This option is available since version 1.1.13.
*
* 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 ''.
@@ -1936,9 +2121,21 @@ public static function activeCheckBoxList($model,$attribute,$data,$htmlOptions=a
*
*
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.
+ * radio button input tag while "{label}" will be replaced by the corresponding radio button label,
+ * {beginLabel} will be replaced by <label> with labelOptions, {labelTitle} will be replaced
+ * by the corresponding radio button label title and {endLabel} will be replaced by </label>
*
separator: string, specifies the string that separates the generated radio buttons. Defaults to new line ( ).
*
encode: boolean, specifies whether to encode HTML-encode tag attributes and values. Defaults to true.
+ *
labelOptions: array, specifies the additional HTML attributes to be rendered
+ * for every label tag in the list.
+ *
container: string, specifies the radio buttons enclosing tag. Defaults to 'span'.
+ * If the value is an empty string, no enclosing tag will be generated
+ *
baseID: string, specifies the base ID prefix to be used for radio buttons in the list.
+ * This option is available since version 1.1.13.
+ *
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 radio button at the beginning. Note, the text label will NOT be HTML-encoded.
+ * This option is available since version 1.1.14.
*
* 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
@@ -2200,7 +2397,7 @@ public static function setModelNameConverter($converter)
else
throw new CException(Yii::t('yii','The $converter argument must be a valid callback or null.'));
}
-
+
/**
* Generates input field name for a model attribute.
* Unlike {@link resolveName}, this method does NOT modify the attribute name.
@@ -2229,7 +2426,9 @@ public static function activeName($model,$attribute)
protected static function activeInputField($type,$model,$attribute,$htmlOptions)
{
$htmlOptions['type']=$type;
- if($type==='text' || $type==='password')
+ if($type==='text'||$type==='password'||$type==='color'||$type==='date'||$type==='datetime'||
+ $type==='datetime-local'||$type==='email'||$type==='month'||$type==='number'||$type==='range'||
+ $type==='search'||$type==='tel'||$type==='time'||$type==='url'||$type==='week')
{
if(!isset($htmlOptions['maxlength']))
{
@@ -2473,7 +2672,7 @@ public static function resolveNameID($model,&$attribute,&$htmlOptions)
public static function resolveName($model,&$attribute)
{
$modelName=self::modelName($model);
-
+
if(($pos=strpos($attribute,'['))!==false)
{
if($pos!==0) // e.g. name[a][b]
@@ -2555,9 +2754,9 @@ protected static function addErrorCss(&$htmlOptions)
public static function renderAttributes($htmlOptions)
{
static $specialAttributes=array(
- 'async'=>1,
'autofocus'=>1,
'autoplay'=>1,
+ 'async'=>1,
'checked'=>1,
'controls'=>1,
'declare'=>1,
@@ -2567,6 +2766,7 @@ public static function renderAttributes($htmlOptions)
'formnovalidate'=>1,
'hidden'=>1,
'ismap'=>1,
+ 'itemscope'=>1,
'loop'=>1,
'multiple'=>1,
'muted'=>1,
@@ -2599,7 +2799,10 @@ public static function renderAttributes($htmlOptions)
{
if(isset($specialAttributes[$name]))
{
- if($value)
+ if($value===false && $name==='async') {
+ $html .= ' ' . $name.'="false"';
+ }
+ elseif($value)
{
$html .= ' ' . $name;
if(self::$renderSpecialAttributesValue)
diff --git a/framework/web/helpers/CJSON.php b/framework/web/helpers/CJSON.php
index aa17b02..1d8f3d6 100644
--- a/framework/web/helpers/CJSON.php
+++ b/framework/web/helpers/CJSON.php
@@ -251,7 +251,15 @@ public static function encode($var)
return '[' . join(',', array_map(array('CJSON', 'encode'), $var)) . ']';
case 'object':
- if ($var instanceof Traversable)
+ // Check for the JsonSerializable interface available in PHP5.4
+ // Note that instanceof returns false in case it doesnt know the interface.
+ if (interface_exists('JsonSerializable', false) && $var instanceof JsonSerializable)
+ {
+ // We use the function defined in the interface instead of json_encode.
+ // This way even for PHP < 5.4 one could define the interface and use it.
+ return self::encode($var->jsonSerialize());
+ }
+ elseif ($var instanceof Traversable)
{
$vars = array();
foreach ($var as $k=>$v)
diff --git a/framework/web/js/source/jquery.autocomplete.js b/framework/web/js/source/jquery.autocomplete.js
index 324b104..a42a0f9 100644
--- a/framework/web/js/source/jquery.autocomplete.js
+++ b/framework/web/js/source/jquery.autocomplete.js
@@ -4,6 +4,7 @@
* Modified for Yii Framework:
* - Renamed "autocomplete" to "legacyautocomplete".
* - Fixed IE8 problems (mario.ffranco).
+ * - Fixed compatibility for jQuery 1.9+ (.browser is deprecated)
*
* Copyright (c) 2009 Jörn Zaefferer
*
@@ -84,7 +85,7 @@ $.Autocompleter = function(input, options) {
var blockSubmit;
// prevent form submit in opera when selecting with return key
- $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
+ (navigator.userAgent.match(/OPERA|OPR\//i) !== null) && $(input.form).bind("submit.autocomplete", function() {
if (blockSubmit) {
blockSubmit = false;
return false;
@@ -92,7 +93,7 @@ $.Autocompleter = function(input, options) {
});
// 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) {
+ $input.bind(((navigator.userAgent.match(/OPERA|OPR\//i) !== null) ? "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;
@@ -738,7 +739,7 @@ $.Autocompleter.Select = function (options, input, select, config) {
overflow: 'auto'
});
- if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
+ if(navigator.userAgent.match(/MSIE/i) !== null && typeof document.body.style.maxHeight === "undefined") {
var listHeight = 0;
listItems.each(function() {
listHeight += this.offsetHeight;
diff --git a/framework/web/js/source/jquery.bgiframe.js b/framework/web/js/source/jquery.bgiframe.js
index 5cd38bb..f8be0b5 100644
--- a/framework/web/js/source/jquery.bgiframe.js
+++ b/framework/web/js/source/jquery.bgiframe.js
@@ -1,39 +1,69 @@
-/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
+/*! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh)
* Licensed under the MIT License (LICENSE.txt).
*
- * Version 2.1.2
+ * Version 3.0.1
+ *
+ * Requires jQuery >= 1.2.6
*/
-(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 = '';
- 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
+(function (factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS style for Browserify
+ module.exports = factory;
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+ $.fn.bgiframe = function(s) {
+ s = $.extend({
+ top : 'auto', // auto == borderTopWidth
+ left : 'auto', // auto == borderLeftWidth
+ width : 'auto', // auto == offsetWidth
+ height : 'auto', // auto == offsetHeight
+ opacity : true,
+ src : 'javascript:false;',
+ conditional : /MSIE 6\.0/.test(navigator.userAgent) // expresion or function. return false to prevent iframe insertion
+ }, s);
+
+ // wrap conditional in a function if it isn't already
+ if (!$.isFunction(s.conditional)) {
+ var condition = s.conditional;
+ s.conditional = function() { return condition; };
+ }
+
+ var $iframe = $('');
+
+ return this.each(function() {
+ var $this = $(this);
+ if ( s.conditional(this) === false ) { return; }
+ var existing = $this.children('iframe.bgiframe');
+ var $el = existing.length === 0 ? $iframe.clone() : existing;
+ $el.css({
+ 'top': s.top == 'auto' ?
+ ((parseInt($this.css('borderTopWidth'),10)||0)*-1)+'px' : prop(s.top),
+ 'left': s.left == 'auto' ?
+ ((parseInt($this.css('borderLeftWidth'),10)||0)*-1)+'px' : prop(s.left),
+ 'width': s.width == 'auto' ? (this.offsetWidth + 'px') : prop(s.width),
+ 'height': s.height == 'auto' ? (this.offsetHeight + 'px') : prop(s.height),
+ 'opacity': s.opacity === true ? 0 : undefined
+ });
+
+ if ( existing.length === 0 ) {
+ $this.prepend($el);
+ }
+ });
+ };
+
+ // old alias
+ $.fn.bgIframe = $.fn.bgiframe;
+
+ function prop(n) {
+ return n && n.constructor === Number ? n + 'px' : n;
+ }
+
+}));
diff --git a/framework/web/js/source/jquery.js b/framework/web/js/source/jquery.js
index 8c24ffc..d4b67f7 100644
--- a/framework/web/js/source/jquery.js
+++ b/framework/web/js/source/jquery.js
@@ -1,71 +1,81 @@
/*!
- * jQuery JavaScript Library v1.8.3
+ * jQuery JavaScript Library v1.11.1
* http://jquery.com/
*
* Includes Sizzle.js
* http://sizzlejs.com/
*
- * Copyright 2012 jQuery Foundation and other contributors
+ * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*
- * Date: Tue Nov 13 2012 08:20:33 GMT-0500 (Eastern Standard Time)
+ * Date: 2014-05-01T17:42Z
*/
-(function( window, undefined ) {
-var
- // A central reference to the root jQuery(document)
- rootjQuery,
- // The deferred used on DOM ready
- readyList,
+(function( global, factory ) {
+
+ if ( typeof module === "object" && typeof module.exports === "object" ) {
+ // For CommonJS and CommonJS-like environments where a proper window is present,
+ // execute the factory and get jQuery
+ // For environments that do not inherently posses a window with a document
+ // (such as Node.js), expose a jQuery-making factory as module.exports
+ // This accentuates the need for the creation of a real window
+ // e.g. var jQuery = require("jquery")(window);
+ // See ticket #14549 for more info
+ module.exports = global.document ?
+ factory( global, true ) :
+ function( w ) {
+ if ( !w.document ) {
+ throw new Error( "jQuery requires a window with a document" );
+ }
+ return factory( w );
+ };
+ } else {
+ factory( global );
+ }
- // Use the correct document accordingly with window argument (sandbox)
- document = window.document,
- location = window.location,
- navigator = window.navigator,
+// Pass this if window is not defined yet
+}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
+// Can't do this because several apps including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+// Support: Firefox 18+
+//
- // Map over the $ in case of overwrite
- _$ = window.$,
+var deletedIds = [];
- // Save a reference to some core methods
- core_push = Array.prototype.push,
- core_slice = Array.prototype.slice,
- core_indexOf = Array.prototype.indexOf,
- core_toString = Object.prototype.toString,
- core_hasOwn = Object.prototype.hasOwnProperty,
- core_trim = String.prototype.trim,
+var slice = deletedIds.slice;
- // Define a local copy of jQuery
- jQuery = function( selector, context ) {
- // The jQuery object is actually just the init constructor 'enhanced'
- return new jQuery.fn.init( selector, context, rootjQuery );
- },
+var concat = deletedIds.concat;
- // Used for matching numbers
- core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
+var push = deletedIds.push;
- // Used for detecting and trimming whitespace
- core_rnotwhite = /\S/,
- core_rspace = /\s+/,
+var indexOf = deletedIds.indexOf;
- // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
- rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+var class2type = {};
- // A simple way to check for HTML strings
- // Prioritize #id over to avoid XSS via location.hash (#9521)
- rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+var toString = class2type.toString;
- // Match a standalone tag
- rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+var hasOwn = class2type.hasOwnProperty;
- // JSON RegExp
- rvalidchars = /^[\],:{}\s]*$/,
- rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
- rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
- rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
+var support = {};
+
+
+
+var
+ version = "1.11.1",
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ // Need init if jQuery is called (just allow error to be thrown if not included)
+ return new jQuery.fn.init( selector, context );
+ },
+
+ // Support: Android<4.1, IE<9
+ // Make sure we trim BOM and NBSP
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
// Matches dashed string for camelizing
rmsPrefix = /^-ms-/,
@@ -73,163 +83,48 @@ var
// Used by jQuery.camelCase as callback to replace()
fcamelCase = function( all, letter ) {
- return ( letter + "" ).toUpperCase();
- },
-
- // The ready event handler and self cleanup method
- DOMContentLoaded = function() {
- if ( document.addEventListener ) {
- document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
- jQuery.ready();
- } else if ( document.readyState === "complete" ) {
- // we're here because readyState === "complete" in oldIE
- // which is good enough for us to call the dom ready!
- document.detachEvent( "onreadystatechange", DOMContentLoaded );
- jQuery.ready();
- }
- },
-
- // [[Class]] -> type pairs
- class2type = {};
+ return letter.toUpperCase();
+ };
jQuery.fn = jQuery.prototype = {
- constructor: jQuery,
- init: function( selector, context, rootjQuery ) {
- var match, elem, ret, doc;
-
- // Handle $(""), $(null), $(undefined), $(false)
- if ( !selector ) {
- return this;
- }
-
- // Handle $(DOMElement)
- if ( selector.nodeType ) {
- this.context = this[0] = selector;
- this.length = 1;
- return this;
- }
-
- // Handle HTML strings
- if ( typeof selector === "string" ) {
- 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 = rquickExpr.exec( selector );
- }
-
- // Match html or make sure no context is specified for #id
- if ( match && (match[1] || !context) ) {
-
- // HANDLE: $(html) -> $(array)
- if ( match[1] ) {
- context = context instanceof jQuery ? context[0] : context;
- doc = ( context && context.nodeType ? context.ownerDocument || context : document );
-
- // scripts is true for back-compat
- selector = jQuery.parseHTML( match[1], doc, true );
- if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
- this.attr.call( selector, context, true );
- }
-
- 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;
- }
+ // The current version of jQuery being used
+ jquery: version,
- return jQuery.makeArray( selector, this );
- },
+ constructor: jQuery,
// Start with an empty selector
selector: "",
- // The current version of jQuery being used
- jquery: "1.8.3",
-
// 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 core_slice.call( this );
+ return slice.call( this );
},
// 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 num != null ?
- // Return a 'clean' array
- this.toArray() :
+ // Return just the one element from the set
+ ( num < 0 ? this[ num + this.length ] : this[ num ] ) :
- // Return just the object
- ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ // Return all the elements in a clean array
+ slice.call( this );
},
// Take an array of elements and push it onto the stack
// (returning the new matched element set)
- pushStack: function( elems, name, selector ) {
+ pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), 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;
},
@@ -241,18 +136,14 @@ jQuery.fn = jQuery.prototype = {
return jQuery.each( this, callback, args );
},
- ready: function( fn ) {
- // Add the callback
- jQuery.ready.promise().done( fn );
-
- return this;
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
},
- eq: function( i ) {
- i = +i;
- return i === -1 ?
- this.slice( i ) :
- this.slice( i, i + 1 );
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ) );
},
first: function() {
@@ -263,15 +154,10 @@ jQuery.fn = jQuery.prototype = {
return this.eq( -1 );
},
- slice: function() {
- return this.pushStack( core_slice.apply( this, arguments ),
- "slice", core_slice.call(arguments).join(",") );
- },
-
- map: function( callback ) {
- return this.pushStack( jQuery.map(this, function( elem, i ) {
- return callback.call( elem, i, elem );
- }));
+ eq: function( i ) {
+ var len = this.length,
+ j = +i + ( i < 0 ? len : 0 );
+ return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
},
end: function() {
@@ -280,16 +166,13 @@ jQuery.fn = jQuery.prototype = {
// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
- push: core_push,
- sort: [].sort,
- splice: [].splice
+ push: push,
+ sort: deletedIds.sort,
+ splice: deletedIds.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,
+ var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
@@ -298,9 +181,10 @@ jQuery.extend = jQuery.fn.extend = function() {
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
- target = arguments[1] || {};
+
// skip the boolean and the target
- i = 2;
+ target = arguments[ i ] || {};
+ i++;
}
// Handle case when target is a string or something (possible in deep copy)
@@ -309,9 +193,9 @@ jQuery.extend = jQuery.fn.extend = function() {
}
// extend jQuery itself if only one argument is passed
- if ( length === i ) {
+ if ( i === length ) {
target = this;
- --i;
+ i--;
}
for ( ; i < length; i++ ) {
@@ -353,63 +237,17 @@ jQuery.extend = jQuery.fn.extend = function() {
};
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,
+ // Unique for each copy of jQuery on the page
+ expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
- // A counter to track how many items to wait for before
- // the ready event fires. See #6781
- readyWait: 1,
+ // Assume jQuery is ready without the ready module
+ isReady: true,
- // Hold (or release) the ready event
- holdReady: function( hold ) {
- if ( hold ) {
- jQuery.readyWait++;
- } else {
- jQuery.ready( true );
- }
+ error: function( msg ) {
+ throw new Error( msg );
},
- // Handle when the DOM is ready
- ready: function( wait ) {
-
- // Abort if there are pending holds or we're already ready
- if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
- return;
- }
-
- // 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.resolveWith( document, [ jQuery ] );
-
- // Trigger any bound ready events
- if ( jQuery.fn.trigger ) {
- jQuery( document ).trigger("ready").off("ready");
- }
- },
+ noop: function() {},
// See test/unit/core.js for details concerning isFunction.
// Since version 1.3, DOM methods and functions like alert
@@ -423,20 +261,28 @@ jQuery.extend({
},
isWindow: function( obj ) {
+ /* jshint eqeqeq: false */
return obj != null && obj == obj.window;
},
isNumeric: function( obj ) {
- return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ // parseFloat NaNs numeric-cast false positives (null|true|false|"")
+ // ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+ // subtraction forces infinities to NaN
+ return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0;
},
- type: function( obj ) {
- return obj == null ?
- String( obj ) :
- class2type[ core_toString.call(obj) ] || "object";
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
},
isPlainObject: function( obj ) {
+ var key;
+
// 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
@@ -447,8 +293,8 @@ jQuery.extend({
try {
// Not own constructor property must be Object
if ( obj.constructor &&
- !core_hasOwn.call(obj, "constructor") &&
- !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
return false;
}
} catch ( e ) {
@@ -456,107 +302,35 @@ jQuery.extend({
return false;
}
+ // Support: IE<9
+ // Handle iteration over inherited properties before own properties.
+ if ( support.ownLast ) {
+ for ( key in obj ) {
+ return hasOwn.call( obj, key );
+ }
+ }
+
// 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 || core_hasOwn.call( obj, key );
- },
-
- isEmptyObject: function( obj ) {
- var name;
- for ( name in obj ) {
- return false;
- }
- return true;
- },
-
- error: function( msg ) {
- throw new Error( msg );
- },
-
- // data: string of html
- // context (optional): If specified, the fragment will be created in this context, defaults to document
- // scripts (optional): If true, will include scripts passed in the html string
- parseHTML: function( data, context, scripts ) {
- var parsed;
- if ( !data || typeof data !== "string" ) {
- return null;
- }
- if ( typeof context === "boolean" ) {
- scripts = context;
- context = 0;
- }
- context = context || document;
-
- // Single tag
- if ( (parsed = rsingleTag.exec( data )) ) {
- return [ context.createElement( parsed[1] ) ];
- }
-
- parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
- return jQuery.merge( [],
- (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
- },
-
- parseJSON: function( data ) {
- if ( !data || typeof data !== "string") {
- 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 );
+ return key === undefined || hasOwn.call( obj, key );
},
- // Cross-browser xml parsing
- parseXML: function( data ) {
- var xml, tmp;
- if ( !data || typeof data !== "string" ) {
- return null;
- }
- 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 );
+ type: function( obj ) {
+ if ( obj == null ) {
+ return obj + "";
}
- return xml;
+ return typeof obj === "object" || typeof obj === "function" ?
+ class2type[ toString.call(obj) ] || "object" :
+ typeof obj;
},
- 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 && core_rnotwhite.test( data ) ) {
+ if ( data && jQuery.trim( data ) ) {
// We use execScript on Internet Explorer
// We use an anonymous function so that context is window
// rather than jQuery in Firefox
@@ -578,21 +352,25 @@ jQuery.extend({
// args is for internal usage only
each: function( obj, callback, args ) {
- var name,
+ var value,
i = 0,
length = obj.length,
- isObj = length === undefined || jQuery.isFunction( obj );
+ isArray = isArraylike( obj );
if ( args ) {
- if ( isObj ) {
- for ( name in obj ) {
- if ( callback.apply( obj[ name ], args ) === false ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
break;
}
}
} else {
- for ( ; i < length; ) {
- if ( callback.apply( obj[ i++ ], args ) === false ) {
+ for ( i in obj ) {
+ value = callback.apply( obj[ i ], args );
+
+ if ( value === false ) {
break;
}
}
@@ -600,15 +378,19 @@ jQuery.extend({
// A special, fast, case for the most common use of each
} else {
- if ( isObj ) {
- for ( name in obj ) {
- if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
break;
}
}
} else {
- for ( ; i < length; ) {
- if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
+ for ( i in obj ) {
+ value = callback.call( obj[ i ], i, obj[ i ] );
+
+ if ( value === false ) {
break;
}
}
@@ -618,35 +400,25 @@ jQuery.extend({
return obj;
},
- // Use native String.trim function wherever possible
- trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
- function( text ) {
- return text == null ?
- "" :
- core_trim.call( text );
- } :
-
- // Otherwise use our own trimming functionality
- function( text ) {
- return text == null ?
- "" :
- ( text + "" ).replace( rtrim, "" );
- },
+ // Support: Android<4.1, IE<9
+ trim: function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
// results is for internal usage only
makeArray: function( arr, results ) {
- var type,
- ret = results || [];
+ var ret = results || [];
if ( arr != null ) {
- // The window, strings (and functions) also have 'length'
- // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
- type = jQuery.type( arr );
-
- if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
- core_push.call( ret, arr );
+ if ( isArraylike( Object(arr) ) ) {
+ jQuery.merge( ret,
+ typeof arr === "string" ?
+ [ arr ] : arr
+ );
} else {
- jQuery.merge( ret, arr );
+ push.call( ret, arr );
}
}
@@ -657,8 +429,8 @@ jQuery.extend({
var len;
if ( arr ) {
- if ( core_indexOf ) {
- return core_indexOf.call( arr, elem, i );
+ if ( indexOf ) {
+ return indexOf.call( arr, elem, i );
}
len = arr.length;
@@ -676,16 +448,17 @@ jQuery.extend({
},
merge: function( first, second ) {
- var l = second.length,
- i = first.length,
- j = 0;
+ var len = +second.length,
+ j = 0,
+ i = first.length;
- if ( typeof l === "number" ) {
- for ( ; j < l; j++ ) {
- first[ i++ ] = second[ j ];
- }
+ while ( j < len ) {
+ first[ i++ ] = second[ j++ ];
+ }
- } else {
+ // Support: IE<9
+ // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
+ if ( len !== len ) {
while ( second[j] !== undefined ) {
first[ i++ ] = second[ j++ ];
}
@@ -696,57 +469,56 @@ jQuery.extend({
return first;
},
- grep: function( elems, callback, inv ) {
- var retVal,
- ret = [],
+ grep: function( elems, callback, invert ) {
+ var callbackInverse,
+ matches = [],
i = 0,
- length = elems.length;
- inv = !!inv;
+ length = elems.length,
+ callbackExpect = !invert;
// Go through the array, only saving the items
// that pass the validator function
for ( ; i < length; i++ ) {
- retVal = !!callback( elems[ i ], i );
- if ( inv !== retVal ) {
- ret.push( elems[ i ] );
+ callbackInverse = !callback( elems[ i ], i );
+ if ( callbackInverse !== callbackExpect ) {
+ matches.push( elems[ i ] );
}
}
- return ret;
+ return matches;
},
// arg is for internal usage only
map: function( elems, callback, arg ) {
- var value, key,
- ret = [],
+ var value,
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 ) ) ;
+ isArray = isArraylike( elems ),
+ ret = [];
- // Go through the array, translating each of the items to their
+ // Go through the array, translating each of the items to their new values
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
- ret[ ret.length ] = value;
+ ret.push( value );
}
}
// Go through every key on the object,
} else {
- for ( key in elems ) {
- value = callback( elems[ key ], key, arg );
+ for ( i in elems ) {
+ value = callback( elems[ i ], i, arg );
if ( value != null ) {
- ret[ ret.length ] = value;
+ ret.push( value );
}
}
}
// Flatten any nested arrays
- return ret.concat.apply( [], ret );
+ return concat.apply( [], ret );
},
// A global GUID counter for objects
@@ -755,7 +527,7 @@ jQuery.extend({
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function( fn, context ) {
- var tmp, args, proxy;
+ var args, proxy, tmp;
if ( typeof context === "string" ) {
tmp = fn[ context ];
@@ -770,9 +542,9 @@ jQuery.extend({
}
// Simulated bind
- args = core_slice.call( arguments, 2 );
+ args = slice.call( arguments, 2 );
proxy = function() {
- return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+ return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
@@ -781,187 +553,2560 @@ jQuery.extend({
return proxy;
},
- // Multifunctional method to get and set values of a collection
- // The value/s can optionally be executed if it's a function
- access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
- var exec,
- bulk = key == null,
- i = 0,
- length = elems.length;
-
- // Sets many values
- if ( key && typeof key === "object" ) {
- for ( i in key ) {
- jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
- }
- chainable = 1;
-
- // Sets one value
- } else if ( value !== undefined ) {
- // Optionally, function values get executed if exec is true
- exec = pass === undefined && jQuery.isFunction( value );
-
- if ( bulk ) {
- // Bulk operations only iterate when executing function values
- if ( exec ) {
- exec = fn;
- fn = function( elem, key, value ) {
- return exec.call( jQuery( elem ), value );
- };
+ now: function() {
+ return +( new Date() );
+ },
- // Otherwise they run against the entire set
- } else {
- fn.call( elems, value );
- fn = null;
- }
- }
+ // jQuery.support is not used in Core but other projects attach their
+ // properties to it so it needs to exist.
+ support: support
+});
- if ( fn ) {
- for (; i < length; i++ ) {
- fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
- }
- }
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+ var length = obj.length,
+ type = jQuery.type( obj );
+
+ if ( type === "function" || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ if ( obj.nodeType === 1 && length ) {
+ return true;
+ }
+
+ return type === "array" || length === 0 ||
+ typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v1.10.19
+ * http://sizzlejs.com/
+ *
+ * Copyright 2013 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-04-18
+ */
+(function( window ) {
+
+var i,
+ support,
+ Expr,
+ getText,
+ isXML,
+ tokenize,
+ compile,
+ select,
+ outermostContext,
+ sortInput,
+ hasDuplicate,
+
+ // Local document vars
+ setDocument,
+ document,
+ docElem,
+ documentIsHTML,
+ rbuggyQSA,
+ rbuggyMatches,
+ matches,
+ contains,
- chainable = 1;
+ // Instance-specific data
+ expando = "sizzle" + -(new Date()),
+ preferredDoc = window.document,
+ dirruns = 0,
+ done = 0,
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
}
+ return 0;
+ },
- return chainable ?
- elems :
+ // General-purpose constants
+ strundefined = typeof undefined,
+ MAX_NEGATIVE = 1 << 31,
- // Gets
- bulk ?
- fn.call( elems ) :
- length ? fn( elems[0], key ) : emptyGet;
+ // Instance methods
+ hasOwn = ({}).hasOwnProperty,
+ arr = [],
+ pop = arr.pop,
+ push_native = arr.push,
+ push = arr.push,
+ slice = arr.slice,
+ // Use a stripped-down indexOf if we can't use a native one
+ indexOf = arr.indexOf || function( elem ) {
+ var i = 0,
+ len = this.length;
+ for ( ; i < len; i++ ) {
+ if ( this[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
},
- now: function() {
- return ( new Date() ).getTime();
- }
-});
+ booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
-jQuery.ready.promise = function( obj ) {
- if ( !readyList ) {
+ // Regular expressions
- readyList = jQuery.Deferred();
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
- // Catch cases where $(document).ready() is called after the browser event has already occurred.
- // we once tried to use readyState "interactive" here, but it caused issues like the one
- // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
- if ( document.readyState === "complete" ) {
- // Handle it asynchronously to allow scripts the opportunity to delay ready
- setTimeout( jQuery.ready, 1 );
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
- // Standards-based browsers support DOMContentLoaded
- } else if ( document.addEventListener ) {
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
+ // Operator (capture 2)
+ "*([*^$|!~]?=)" + whitespace +
+ // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+ "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
+ "*\\]",
+
+ pseudos = ":(" + characterEncoding + ")(?:\\((" +
+ // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+ // 1. quoted (capture 3; capture 4 or capture 5)
+ "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+ // 2. simple (capture 6)
+ "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+ // 3. anything else (capture 2)
+ ".*" +
+ ")\\)|)",
- // A fallback to window.onload, that will always work
- window.addEventListener( "load", jQuery.ready, false );
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
- // If IE event model is used
- } else {
- // Ensure firing before onload, maybe late but safe also for iframes
- document.attachEvent( "onreadystatechange", DOMContentLoaded );
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
- // A fallback to window.onload, that will always work
- window.attachEvent( "onload", jQuery.ready );
+ rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
- // If IE and not a frame
- // continually check to see if the document is ready
- var top = false;
+ rpseudo = new RegExp( pseudos ),
+ ridentifier = new RegExp( "^" + identifier + "$" ),
- try {
- top = window.frameElement == null && document.documentElement;
- } catch(e) {}
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+ // For use in libraries implementing .is()
+ // We use this for POS matching in `select`
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+ whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+ },
- if ( top && top.doScroll ) {
- (function doScrollCheck() {
- if ( !jQuery.isReady ) {
+ rinputs = /^(?:input|select|textarea|button)$/i,
+ rheader = /^h\d$/i,
- try {
- // Use the trick by Diego Perini
- // http://javascript.nwbox.com/IEContentLoaded/
- top.doScroll("left");
- } catch(e) {
- return setTimeout( doScrollCheck, 50 );
+ rnative = /^[^{]+\{\s*\[native \w/,
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+ rsibling = /[+~]/,
+ rescape = /'|\\/g,
+
+ // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+ runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+ funescape = function( _, escaped, escapedWhitespace ) {
+ var high = "0x" + escaped - 0x10000;
+ // NaN means non-codepoint
+ // Support: Firefox<24
+ // Workaround erroneous numeric interpretation of +"0x"
+ return high !== high || escapedWhitespace ?
+ escaped :
+ high < 0 ?
+ // BMP codepoint
+ String.fromCharCode( high + 0x10000 ) :
+ // Supplemental Plane codepoint (surrogate pair)
+ String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+ };
+
+// Optimize for push.apply( _, NodeList )
+try {
+ push.apply(
+ (arr = slice.call( preferredDoc.childNodes )),
+ preferredDoc.childNodes
+ );
+ // Support: Android<4.0
+ // Detect silently failing push.apply
+ arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+ push = { apply: arr.length ?
+
+ // Leverage slice if possible
+ function( target, els ) {
+ push_native.apply( target, slice.call(els) );
+ } :
+
+ // Support: IE<9
+ // Otherwise append directly
+ function( target, els ) {
+ var j = target.length,
+ i = 0;
+ // Can't trust NodeList.length
+ while ( (target[j++] = els[i++]) ) {}
+ target.length = j - 1;
+ }
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ var match, elem, m, nodeType,
+ // QSA vars
+ i, groups, old, nid, newContext, newSelector;
+
+ if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+ setDocument( context );
+ }
+
+ context = context || document;
+ results = results || [];
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( documentIsHTML && !seed ) {
+
+ // Shortcuts
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document (jQuery #6963)
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
}
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
- // and execute any waiting functions
- jQuery.ready();
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, context.getElementsByTagName( selector ) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
+ push.apply( results, context.getElementsByClassName( m ) );
+ return results;
+ }
+ }
+
+ // QSA path
+ if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ nid = old = expando;
+ newContext = context;
+ newSelector = nodeType === 9 && selector;
+
+ // 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
+ if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + toSelector( groups[i] );
+ }
+ newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results,
+ newContext.querySelectorAll( newSelector )
+ );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
}
- })();
+ }
}
}
}
- return readyList.promise( obj );
-};
-// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
- class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
-// All jQuery objects should point back to these
-rootjQuery = jQuery(document);
-// String to Object options format cache
-var optionsCache = {};
+/**
+ * Create key-value caches of limited size
+ * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ * deleting the oldest entry
+ */
+function createCache() {
+ var keys = [];
-// Convert String-formatted options into Object-formatted ones and store in cache
-function createOptions( options ) {
- var object = optionsCache[ options ] = {};
- jQuery.each( options.split( core_rspace ), function( _, flag ) {
- object[ flag ] = true;
- });
- return object;
+ function cache( key, value ) {
+ // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+ if ( keys.push( key + " " ) > Expr.cacheLength ) {
+ // Only keep the most recent entries
+ delete cache[ keys.shift() ];
+ }
+ return (cache[ key + " " ] = value);
+ }
+ return cache;
}
-/*
- * Create a callback list using the following parameters:
- *
- * options: an optional list of space-separated options that will change how
- * the callback list behaves or a more traditional option object
- *
- * By default a callback list will act like an event callback list and can be
- * "fired" multiple times.
- *
- * Possible options:
- *
- * 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
- *
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
*/
-jQuery.Callbacks = function( options ) {
+function markFunction( fn ) {
+ fn[ expando ] = true;
+ return fn;
+}
- // Convert options from String-formatted to Object-formatted if needed
- // (we check in cache first)
- options = typeof options === "string" ?
- ( optionsCache[ options ] || createOptions( options ) ) :
- jQuery.extend( {}, options );
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+function assert( fn ) {
+ var div = document.createElement("div");
- var // Last fire value (for non-forgettable lists)
- memory,
- // Flag to know if list was already fired
- fired,
- // 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,
- // Actual callback list
- list = [],
- // Stack of fire calls for repeatable lists
- stack = !options.once && [],
+ try {
+ return !!fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // Remove from its parent by default
+ if ( div.parentNode ) {
+ div.parentNode.removeChild( div );
+ }
+ // release memory in IE
+ div = null;
+ }
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+ var arr = attrs.split("|"),
+ i = attrs.length;
+
+ while ( i-- ) {
+ Expr.attrHandle[ arr[i] ] = handler;
+ }
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+ var cur = b && a,
+ diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+ ( ~b.sourceIndex || MAX_NEGATIVE ) -
+ ( ~a.sourceIndex || MAX_NEGATIVE );
+
+ // Use IE sourceIndex if available on both nodes
+ if ( diff ) {
+ return diff;
+ }
+
+ // Check if b follows a
+ if ( cur ) {
+ while ( (cur = cur.nextSibling) ) {
+ if ( cur === b ) {
+ return -1;
+ }
+ }
+ }
+
+ return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+ return context && typeof context.getElementsByTagName !== strundefined && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = 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).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+ var hasCompare,
+ doc = node ? node.ownerDocument || node : preferredDoc,
+ parent = doc.defaultView;
+
+ // If no document and documentElement is available, return
+ if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+ return document;
+ }
+
+ // Set our document
+ document = doc;
+ docElem = doc.documentElement;
+
+ // Support tests
+ documentIsHTML = !isXML( doc );
+
+ // Support: IE>8
+ // If iframe document is assigned to "document" variable and if iframe has been reloaded,
+ // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
+ // IE6-8 do not support the defaultView property so parent will be undefined
+ if ( parent && parent !== parent.top ) {
+ // IE11 does not have attachEvent, so all must suffer
+ if ( parent.addEventListener ) {
+ parent.addEventListener( "unload", function() {
+ setDocument();
+ }, false );
+ } else if ( parent.attachEvent ) {
+ parent.attachEvent( "onunload", function() {
+ setDocument();
+ });
+ }
+ }
+
+ /* Attributes
+ ---------------------------------------------------------------------- */
+
+ // Support: IE<8
+ // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
+ support.attributes = assert(function( div ) {
+ div.className = "i";
+ return !div.getAttribute("className");
+ });
+
+ /* getElement(s)By*
+ ---------------------------------------------------------------------- */
+
+ // Check if getElementsByTagName("*") returns only elements
+ support.getElementsByTagName = assert(function( div ) {
+ div.appendChild( doc.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ });
+
+ // Check if getElementsByClassName can be trusted
+ support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {
+ div.innerHTML = "";
+
+ // Support: Safari<4
+ // Catch class over-caching
+ div.firstChild.className = "i";
+ // Support: Opera<10
+ // Catch gEBCN failure to find non-leading classes
+ return div.getElementsByClassName("i").length === 2;
+ });
+
+ // Support: IE<10
+ // Check if getElementById returns elements by name
+ // The broken getElementById methods don't pick up programatically-set names,
+ // so use a roundabout getElementsByName test
+ support.getById = assert(function( div ) {
+ docElem.appendChild( div ).id = expando;
+ return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+ });
+
+ // ID find and filter
+ if ( support.getById ) {
+ Expr.find["ID"] = function( id, context ) {
+ if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [ m ] : [];
+ }
+ };
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ return elem.getAttribute("id") === attrId;
+ };
+ };
+ } else {
+ // Support: IE6/7
+ // getElementById is not reliable as a find shortcut
+ delete Expr.find["ID"];
+
+ Expr.filter["ID"] = function( id ) {
+ var attrId = id.replace( runescape, funescape );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === attrId;
+ };
+ };
+ }
+
+ // Tag
+ Expr.find["TAG"] = support.getElementsByTagName ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var elem,
+ tmp = [],
+ i = 0,
+ results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ };
+
+ // Class
+ Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+ if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
+ return context.getElementsByClassName( className );
+ }
+ };
+
+ /* QSA/matchesSelector
+ ---------------------------------------------------------------------- */
+
+ // QSA and matchesSelector support
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ rbuggyMatches = [];
+
+ // qSa(:focus) reports false when true (Chrome 21)
+ // We allow this because of a bug in IE8/9 that throws an error
+ // whenever `document.activeElement` is accessed on an iframe
+ // So, we allow :focus to pass through QSA all the time to avoid the IE error
+ // See http://bugs.jquery.com/ticket/13378
+ rbuggyQSA = [];
+
+ if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explicitly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ div.innerHTML = "";
+
+ // Support: IE8, Opera 11-12.16
+ // Nothing should be selected when empty strings follow ^= or $= or *=
+ // The test attribute must be unknown in Opera but "safe" for WinRT
+ // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+ if ( div.querySelectorAll("[msallowclip^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+ }
+
+ // Support: IE8
+ // Boolean attributes and "value" are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
+
+ assert(function( div ) {
+ // Support: Windows 8 Native Apps
+ // The type and name attributes are restricted during .innerHTML assignment
+ var input = doc.createElement("input");
+ input.setAttribute( "type", "hidden" );
+ div.appendChild( input ).setAttribute( "name", "D" );
+
+ // Support: IE8
+ // Enforce case-sensitivity of name attribute
+ if ( div.querySelectorAll("[name=d]").length ) {
+ rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here and will not see later tests
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push( ":enabled", ":disabled" );
+ }
+
+ // Opera 10-11 does not throw on post-comma invalid pseudos
+ div.querySelectorAll("*,:x");
+ rbuggyQSA.push(",.*:");
+ });
+ }
+
+ if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
+ docElem.webkitMatchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector) )) ) {
+
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ support.disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( div, "[s!='']:x" );
+ rbuggyMatches.push( "!=", pseudos );
+ });
+ }
+
+ rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+ rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+ /* Contains
+ ---------------------------------------------------------------------- */
+ hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+ // Element contains another
+ // Purposefully does not implement inclusive descendent
+ // As in, an element does not contain itself
+ contains = hasCompare || rnative.test( docElem.contains ) ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && (
+ adown.contains ?
+ adown.contains( bup ) :
+ a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+ ));
+ } :
+ function( a, b ) {
+ if ( b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+
+ /* Sorting
+ ---------------------------------------------------------------------- */
+
+ // Document order sorting
+ sortOrder = hasCompare ?
+ function( a, b ) {
+
+ // Flag for duplicate removal
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ // Sort on method existence if only one input has compareDocumentPosition
+ var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+ if ( compare ) {
+ return compare;
+ }
+
+ // Calculate position if both inputs belong to the same document
+ compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+ a.compareDocumentPosition( b ) :
+
+ // Otherwise we know they are disconnected
+ 1;
+
+ // Disconnected nodes
+ if ( compare & 1 ||
+ (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+ // Choose the first element that is related to our preferred document
+ if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+ return -1;
+ }
+ if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+ return 1;
+ }
+
+ // Maintain original order
+ return sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
+ }
+
+ return compare & 4 ? -1 : 1;
+ } :
+ function( a, b ) {
+ // Exit early if the nodes are identical
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ var cur,
+ i = 0,
+ aup = a.parentNode,
+ bup = b.parentNode,
+ ap = [ a ],
+ bp = [ b ];
+
+ // Parentless nodes are either documents or disconnected
+ if ( !aup || !bup ) {
+ return a === doc ? -1 :
+ b === doc ? 1 :
+ aup ? -1 :
+ bup ? 1 :
+ sortInput ?
+ ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
+ 0;
+
+ // If the nodes are siblings, we can do a quick check
+ } else if ( aup === bup ) {
+ return siblingCheck( a, b );
+ }
+
+ // Otherwise we need full lists of their ancestors for comparison
+ cur = a;
+ while ( (cur = cur.parentNode) ) {
+ ap.unshift( cur );
+ }
+ cur = b;
+ while ( (cur = cur.parentNode) ) {
+ bp.unshift( cur );
+ }
+
+ // Walk down the tree looking for a discrepancy
+ while ( ap[i] === bp[i] ) {
+ i++;
+ }
+
+ return i ?
+ // Do a sibling check if the nodes have a common ancestor
+ siblingCheck( ap[i], bp[i] ) :
+
+ // Otherwise nodes in our document sort first
+ ap[i] === preferredDoc ? -1 :
+ bp[i] === preferredDoc ? 1 :
+ 0;
+ };
+
+ return doc;
+};
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ if ( support.matchesSelector && documentIsHTML &&
+ ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+ ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
+
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || support.disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+ // Set document vars if needed
+ if ( ( context.ownerDocument || context ) !== document ) {
+ setDocument( context );
+ }
+ return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+ // Set document vars if needed
+ if ( ( elem.ownerDocument || elem ) !== document ) {
+ setDocument( elem );
+ }
+
+ var fn = Expr.attrHandle[ name.toLowerCase() ],
+ // Don't get fooled by Object.prototype properties (jQuery #13807)
+ val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+ fn( elem, name, !documentIsHTML ) :
+ undefined;
+
+ return val !== undefined ?
+ val :
+ support.attributes || !documentIsHTML ?
+ elem.getAttribute( name ) :
+ (val = elem.getAttributeNode(name)) && val.specified ?
+ val.value :
+ null;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ j = 0,
+ i = 0;
+
+ // Unless we *know* we can detect duplicates, assume their presence
+ hasDuplicate = !support.detectDuplicates;
+ sortInput = !support.sortStable && results.slice( 0 );
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ while ( (elem = results[i++]) ) {
+ if ( elem === results[ i ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ // Clear input after sorting to release objects
+ // See https://github.com/jquery/sizzle/pull/225
+ sortInput = null;
+
+ return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( !nodeType ) {
+ // If no nodeType, this is expected to be an array
+ while ( (node = elem[i++]) ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (jQuery #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+
+ return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ attrHandle: {},
+
+ find: {},
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( runescape, funescape );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 what (child|of-type)
+ 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 4 xn-component of xn+y argument ([+-]?\d*n|)
+ 5 sign of xn-component
+ 6 x of xn-component
+ 7 sign of y-component
+ 8 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1].slice( 0, 3 ) === "nth" ) {
+ // nth-* requires argument
+ if ( !match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+ match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[3] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var excess,
+ unquoted = !match[6] && match[2];
+
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ // Accept quoted arguments as-is
+ if ( match[3] ) {
+ match[2] = match[4] || match[5] || "";
+
+ // Strip excess characters from unquoted arguments
+ } else if ( unquoted && rpseudo.test( unquoted ) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ match[0] = match[0].slice( 0, excess );
+ match[2] = unquoted.slice( 0, excess );
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+
+ "TAG": function( nodeNameSelector ) {
+ var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+ return nodeNameSelector === "*" ?
+ function() { return true; } :
+ function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ className + " " ];
+
+ return pattern ||
+ (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+ classCache( className, function( elem ) {
+ return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
+ });
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.slice( -check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, what, argument, first, last ) {
+ var simple = type.slice( 0, 3 ) !== "nth",
+ forward = type.slice( -4 ) !== "last",
+ ofType = what === "of-type";
+
+ return first === 1 && last === 0 ?
+
+ // Shortcut for :nth-*(n)
+ function( elem ) {
+ return !!elem.parentNode;
+ } :
+
+ function( elem, context, xml ) {
+ var cache, outerCache, node, diff, nodeIndex, start,
+ dir = simple !== forward ? "nextSibling" : "previousSibling",
+ parent = elem.parentNode,
+ name = ofType && elem.nodeName.toLowerCase(),
+ useCache = !xml && !ofType;
+
+ if ( parent ) {
+
+ // :(first|last|only)-(child|of-type)
+ if ( simple ) {
+ while ( dir ) {
+ node = elem;
+ while ( (node = node[ dir ]) ) {
+ if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+ return false;
+ }
+ }
+ // Reverse direction for :only-* (if we haven't yet done so)
+ start = dir = type === "only" && !start && "nextSibling";
+ }
+ return true;
+ }
+
+ start = [ forward ? parent.firstChild : parent.lastChild ];
+
+ // non-xml :nth-child(...) stores cache data on `parent`
+ if ( forward && useCache ) {
+ // Seek `elem` from a previously-cached index
+ outerCache = parent[ expando ] || (parent[ expando ] = {});
+ cache = outerCache[ type ] || [];
+ nodeIndex = cache[0] === dirruns && cache[1];
+ diff = cache[0] === dirruns && cache[2];
+ node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+ // Fallback to seeking `elem` from the start
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ // When found, cache indexes on `parent` and break
+ if ( node.nodeType === 1 && ++diff && node === elem ) {
+ outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+ break;
+ }
+ }
+
+ // Use previously-cached element index if available
+ } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+ diff = cache[1];
+
+ // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+ } else {
+ // Use the same loop as above to seek `elem` from the start
+ while ( (node = ++nodeIndex && node && node[ dir ] ||
+ (diff = nodeIndex = 0) || start.pop()) ) {
+
+ if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
+ // Cache the index of each encountered element
+ if ( useCache ) {
+ (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
+ }
+
+ if ( node === elem ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset, then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ // Potentially complex pseudos
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ // "Whether an element is represented by a :lang() selector
+ // is based solely on the element's language value
+ // being equal to the identifier C,
+ // or beginning with the identifier C immediately followed by "-".
+ // The matching of C against the element's language value is performed case-insensitively.
+ // The identifier C does not have to be a valid language name."
+ // http://www.w3.org/TR/selectors/#lang-pseudo
+ "lang": markFunction( function( lang ) {
+ // lang value must be a valid identifier
+ if ( !ridentifier.test(lang || "") ) {
+ Sizzle.error( "unsupported lang: " + lang );
+ }
+ lang = lang.replace( runescape, funescape ).toLowerCase();
+ return function( elem ) {
+ var elemLang;
+ do {
+ if ( (elemLang = documentIsHTML ?
+ elem.lang :
+ elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+ elemLang = elemLang.toLowerCase();
+ return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+ }
+ } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+ return false;
+ };
+ }),
+
+ // Miscellaneous
+ "target": function( elem ) {
+ var hash = window.location && window.location.hash;
+ return hash && hash.slice( 1 ) === elem.id;
+ },
+
+ "root": function( elem ) {
+ return elem === docElem;
+ },
+
+ "focus": function( elem ) {
+ return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
+
+ // Boolean properties
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "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;
+ },
+
+ // Contents
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+ // but not by others (comment: 8; processing instruction: 7; etc.)
+ // nodeType < 6 works because attributes (2) do not appear as children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ if ( elem.nodeType < 6 ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ // Element/input types
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "text": function( elem ) {
+ var attr;
+ return elem.nodeName.toLowerCase() === "input" &&
+ elem.type === "text" &&
+
+ // Support: IE<8
+ // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+ },
+
+ // Position-in-collection
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 0;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ var i = 1;
+ for ( ; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ var i = argument < 0 ? argument + length : argument;
+ for ( ; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+ Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+ Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[0].length ) || soFar;
+ }
+ groups.push( (tokens = []) );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ // Cast descendant combinators to space
+ type: match[0].replace( rtrim, " " )
+ });
+ soFar = soFar.slice( matched.length );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match ))) ) {
+ matched = match.shift();
+ tokens.push({
+ value: matched,
+ type: type,
+ matches: match
+ });
+ soFar = soFar.slice( matched.length );
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+ var i = 0,
+ len = tokens.length,
+ selector = "";
+ for ( ; i < len; i++ ) {
+ selector += tokens[i].value;
+ }
+ return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ var oldCache, outerCache,
+ newCache = [ dirruns, doneName ];
+
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ if ( matcher( elem, context, xml ) ) {
+ return true;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( elem.nodeType === 1 || checkNonElements ) {
+ outerCache = elem[ expando ] || (elem[ expando ] = {});
+ if ( (oldCache = outerCache[ dir ]) &&
+ oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+ // Assign to newCache so results back-propagate to previous elements
+ return (newCache[ 2 ] = oldCache[ 2 ]);
+ } else {
+ // Reuse newcache so results back-propagate to previous elements
+ outerCache[ dir ] = newCache;
+
+ // A match means we're done; a fail means we have to keep checking
+ if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
+ }
+ return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( (elem = temp[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( (matcherIn[i] = elem) );
+ }
+ }
+ postFinder( null, (matcherOut = []), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) &&
+ (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
+
+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+ } else {
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && toSelector(
+ // If the preceding token was a descendant combinator, insert an implicit any-element `*`
+ tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+ ).replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && toSelector( tokens )
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, outermost ) {
+ var elem, j, matcher,
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ setMatched = [],
+ contextBackup = outermostContext,
+ // We must always have either seed elements or outermost context
+ elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+ // Use integer dirruns iff this is the outermost matcher
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+ len = elems.length;
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+ // Support: IE<9, Safari
+ // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
+ for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ j = 0;
+ while ( (matcher = elementMatchers[j++]) ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ j = 0;
+ while ( (matcher = setMatchers[j++]) ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ selector + " " ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !match ) {
+ match = tokenize( selector );
+ }
+ i = match.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( match[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+ // Save selector and tokenization
+ cached.selector = selector;
+ }
+ return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ * selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ * selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+ var i, tokens, token, type, find,
+ compiled = typeof selector === "function" && selector,
+ match = !seed && tokenize( (selector = compiled.selector || selector) );
+
+ results = results || [];
+
+ // Try to minimize operations if there is no seed and only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ support.getById && context.nodeType === 9 && documentIsHTML &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+ if ( !context ) {
+ return results;
+
+ // Precompiled matchers will still verify ancestry, so step up a level
+ } else if ( compiled ) {
+ context = context.parentNode;
+ }
+
+ selector = selector.slice( tokens.shift().value.length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+ while ( i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( runescape, funescape ),
+ rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && toSelector( tokens );
+ if ( !selector ) {
+ push.apply( results, seed );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function if one is not provided
+ // Provide `match` to avoid retokenization if we modified the selector above
+ ( compiled || compile( selector, match ) )(
+ seed,
+ context,
+ !documentIsHTML,
+ results,
+ rsibling.test( selector ) && testContext( context.parentNode ) || context
+ );
+ return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Support: Chrome<14
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert(function( div1 ) {
+ // Should return 1, but returns 4 (following)
+ return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+});
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert(function( div ) {
+ div.innerHTML = "";
+ return div.firstChild.getAttribute("href") === "#" ;
+}) ) {
+ addHandle( "type|href|height|width", function( elem, name, isXML ) {
+ if ( !isXML ) {
+ return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+ }
+ });
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert(function( div ) {
+ div.innerHTML = "";
+ div.firstChild.setAttribute( "value", "" );
+ return div.firstChild.getAttribute( "value" ) === "";
+}) ) {
+ addHandle( "value", function( elem, name, isXML ) {
+ if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+ return elem.defaultValue;
+ }
+ });
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert(function( div ) {
+ return div.getAttribute("disabled") == null;
+}) ) {
+ addHandle( booleans, function( elem, name, isXML ) {
+ var val;
+ if ( !isXML ) {
+ return elem[ name ] === true ? name.toLowerCase() :
+ (val = elem.getAttributeNode( name )) && val.specified ?
+ val.value :
+ null;
+ }
+ });
+}
+
+return Sizzle;
+
+})( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
+
+
+
+var risSimple = /^.[^:#\[\.,]*$/;
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep( elements, function( elem, i ) {
+ /* jshint -W018 */
+ return !!qualifier.call( elem, i, elem ) !== not;
+ });
+
+ }
+
+ if ( qualifier.nodeType ) {
+ return jQuery.grep( elements, function( elem ) {
+ return ( elem === qualifier ) !== not;
+ });
+
+ }
+
+ if ( typeof qualifier === "string" ) {
+ if ( risSimple.test( qualifier ) ) {
+ return jQuery.filter( qualifier, elements, not );
+ }
+
+ qualifier = jQuery.filter( qualifier, elements );
+ }
+
+ return jQuery.grep( elements, function( elem ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
+ });
+}
+
+jQuery.filter = function( expr, elems, not ) {
+ var elem = elems[ 0 ];
+
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 && elem.nodeType === 1 ?
+ jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+ jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+ return elem.nodeType === 1;
+ }));
+};
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i,
+ ret = [],
+ self = this,
+ len = self.length;
+
+ if ( typeof selector !== "string" ) {
+ return this.pushStack( jQuery( selector ).filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ }) );
+ }
+
+ for ( i = 0; i < len; i++ ) {
+ jQuery.find( selector, self[ i ], ret );
+ }
+
+ // Needed because $( selector, context ) becomes $( context ).find( selector )
+ ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+ ret.selector = this.selector ? this.selector + " " + selector : selector;
+ return ret;
+ },
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], false) );
+ },
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector || [], true) );
+ },
+ is: function( selector ) {
+ return !!winnow(
+ this,
+
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ typeof selector === "string" && rneedsContext.test( selector ) ?
+ jQuery( selector ) :
+ selector || [],
+ false
+ ).length;
+ }
+});
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over to avoid XSS via location.hash (#9521)
+ // Strict HTML recognition (#11290: must start with <)
+ rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+ init = jQuery.fn.init = function( selector, context ) {
+ var match, elem;
+
+ // HANDLE: $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ 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 = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+
+ // scripts is true for back-compat
+ // Intentionally let the error be thrown if parseHTML is not present
+ jQuery.merge( this, jQuery.parseHTML(
+ match[1],
+ context && context.nodeType ? context.ownerDocument || context : document,
+ true
+ ) );
+
+ // HANDLE: $(html, props)
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ for ( match in context ) {
+ // Properties of context are called as methods if possible
+ if ( jQuery.isFunction( this[ match ] ) ) {
+ this[ match ]( context[ match ] );
+
+ // ...and otherwise set as attributes
+ } else {
+ this.attr( match, context[ match ] );
+ }
+ }
+ }
+
+ return this;
+
+ // 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: $(DOMElement)
+ } else if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return typeof rootjQuery.ready !== "undefined" ?
+ rootjQuery.ready( selector ) :
+ // Execute immediately if ready is not present
+ selector( jQuery );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ };
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.extend({
+ 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;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+jQuery.fn.extend({
+ has: function( target ) {
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
+
+ return this.filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ matched = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+ // Always skip document fragments
+ if ( cur.nodeType < 11 && (pos ?
+ pos.index(cur) > -1 :
+
+ // Don't pass non-elements to Sizzle
+ cur.nodeType === 1 &&
+ jQuery.find.matchesSelector(cur, selectors)) ) {
+
+ matched.push( cur );
+ break;
+ }
+ }
+ }
+
+ return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+ },
+
+ // 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.first().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 ) {
+ return this.pushStack(
+ jQuery.unique(
+ jQuery.merge( this.get(), jQuery( selector, context ) )
+ )
+ );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+function sibling( cur, dir ) {
+ do {
+ cur = cur[ dir ];
+ } while ( cur && cur.nodeType !== 1 );
+
+ return cur;
+}
+
+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 sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "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.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( name.slice( -5 ) !== "Until" ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ if ( this.length > 1 ) {
+ // Remove duplicates
+ if ( !guaranteedUnique[ name ] ) {
+ ret = jQuery.unique( ret );
+ }
+
+ // Reverse order for parents* and prev-derivatives
+ if ( rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+ }
+
+ return this.pushStack( ret );
+ };
+});
+var rnotwhite = (/\S+/g);
+
+
+
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * 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( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Flag to know if list is currently firing
+ firing,
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
// Fire callbacks
fire = function( data ) {
memory = options.memory && data;
@@ -1027,7 +3172,7 @@ jQuery.Callbacks = function( options ) {
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
- while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
if ( firing ) {
@@ -1043,13 +3188,15 @@ jQuery.Callbacks = function( options ) {
}
return this;
},
- // Control if a given callback is in the list
+ // Check if a given callback is in the list.
+ // If no argument is given, return whether or not list has callbacks attached.
has: function( fn ) {
- return jQuery.inArray( fn, list ) > -1;
+ return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
},
// Remove all callbacks from the list
empty: function() {
list = [];
+ firingLength = 0;
return this;
},
// Have the list do nothing anymore
@@ -1075,9 +3222,9 @@ jQuery.Callbacks = function( options ) {
},
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
- args = args || [];
- args = [ context, args.slice ? args.slice() : args ];
if ( list && ( !fired || stack ) ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
if ( firing ) {
stack.push( args );
} else {
@@ -1099,6 +3246,8 @@ jQuery.Callbacks = function( options ) {
return self;
};
+
+
jQuery.extend({
Deferred: function( func ) {
@@ -1121,23 +3270,19 @@ jQuery.extend({
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
- var action = tuple[ 0 ],
- fn = fns[ i ];
+ var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
- deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
- function() {
- var returned = fn.apply( this, arguments );
- if ( returned && jQuery.isFunction( returned.promise ) ) {
- returned.promise()
- .done( newDefer.resolve )
- .fail( newDefer.reject )
- .progress( newDefer.notify );
- } else {
- newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
- }
- } :
- newDefer[ action ]
- );
+ deferred[ tuple[1] ](function() {
+ var returned = fn && fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+ }
+ });
});
fns = null;
}).promise();
@@ -1171,8 +3316,11 @@ jQuery.extend({
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
- // deferred[ resolve | reject | notify ] = list.fire
- deferred[ tuple[0] ] = list.fire;
+ // deferred[ resolve | reject | notify ]
+ deferred[ tuple[0] ] = function() {
+ deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+ return this;
+ };
deferred[ tuple[0] + "With" ] = list.fireWith;
});
@@ -1191,7 +3339,7 @@ jQuery.extend({
// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
- resolveValues = core_slice.call( arguments ),
+ resolveValues = slice.call( arguments ),
length = resolveValues.length,
// the count of uncompleted subordinates
@@ -1204,10 +3352,11 @@ jQuery.extend({
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
- values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
- if( values === progressValues ) {
+ values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if ( values === progressValues ) {
deferred.notifyWith( contexts, values );
- } else if ( !( --remaining ) ) {
+
+ } else if ( !(--remaining) ) {
deferred.resolveWith( contexts, values );
}
};
@@ -1240,1398 +3389,882 @@ jQuery.extend({
return deferred.promise();
}
});
-jQuery.support = (function() {
-
- var support,
- all,
- a,
- select,
- opt,
- input,
- fragment,
- eventName,
- i,
- isSupported,
- clickFn,
- div = document.createElement("div");
-
- // Setup
- div.setAttribute( "className", "t" );
- div.innerHTML = "
a";
-
- // Support tests won't run in some limited or non-browser environments
- all = div.getElementsByTagName("*");
- a = div.getElementsByTagName("a")[ 0 ];
- if ( !all || !a || !all.length ) {
- return {};
- }
-
- // First batch of tests
- select = document.createElement("select");
- opt = select.appendChild( document.createElement("option") );
- input = div.getElementsByTagName("input")[ 0 ];
-
- a.style.cssText = "top:1px;float:left;opacity:.5";
- 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.5/.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>",
-
- // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
- boxModel: ( document.compatMode === "CSS1Compat" ),
-
- // Will be defined later
- submitBubbles: true,
- changeBubbles: true,
- focusinBubbles: false,
- deleteExpando: true,
- noCloneEvent: true,
- inlineBlockNeedsLayout: false,
- shrinkWrapBlocks: false,
- reliableMarginRight: true,
- boxSizingReliable: true,
- pixelPosition: false
- };
-
- // 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", clickFn = function() {
- // Cloning a node shouldn't copy over any
- // bound event handlers (IE does this)
- support.noCloneEvent = false;
- });
- div.cloneNode( true ).fireEvent("onclick");
- div.detachEvent( "onclick", clickFn );
- }
-
- // 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" );
-
- // #11217 - WebKit loses check when the name is after the checked attribute
- input.setAttribute( "name", "t" );
-
- 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 );
-
- // 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: true,
- change: true,
- focusin: true
- }) {
- eventName = "on" + i;
- isSupported = ( eventName in div );
- if ( !isSupported ) {
- div.setAttribute( eventName, "return;" );
- isSupported = ( typeof div[ eventName ] === "function" );
- }
- support[ i + "Bubbles" ] = isSupported;
- }
- }
-
- // Run tests that need a body at doc ready
- jQuery(function() {
- var container, div, tds, marginDiv,
- divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
- body = document.getElementsByTagName("body")[0];
-
- if ( !body ) {
- // Return for frameset docs that don't have a body
- return;
- }
-
- container = document.createElement("div");
- container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
- 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 = "
t
";
- tds = div.getElementsByTagName("td");
- tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
- 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 );
-
- // Check box-sizing and margin behavior
- div.innerHTML = "";
- div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
- support.boxSizing = ( div.offsetWidth === 4 );
- support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
-
- // NOTE: To any future maintainer, we've window.getComputedStyle
- // because jsdom on node.js will break without it.
- if ( window.getComputedStyle ) {
- support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
- support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
-
- // 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
- marginDiv = document.createElement("div");
- marginDiv.style.cssText = div.style.cssText = divReset;
- marginDiv.style.marginRight = marginDiv.style.width = "0";
- div.style.width = "1px";
- div.appendChild( marginDiv );
- support.reliableMarginRight =
- !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
- }
-
- 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.innerHTML = "";
- div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
- support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
-
- // Check if elements with layout shrink-wrap their children
- // (IE 6 does this)
- div.style.display = "block";
- div.style.overflow = "visible";
- div.innerHTML = "";
- div.firstChild.style.width = "5px";
- support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
-
- container.style.zoom = 1;
- }
-
- // Null elements to avoid leaks in IE
- body.removeChild( container );
- container = div = tds = marginDiv = null;
- });
-
- // Null elements to avoid leaks in IE
- fragment.removeChild( div );
- all = a = select = opt = input = fragment = div = null;
-
- return support;
-})();
-var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
- rmultiDash = /([A-Z])/g;
-
-jQuery.extend({
- cache: {},
-
- deletedIds: [],
-
- // Remove at next major release (1.9/2.0)
- 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 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;
-
- // 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] || (!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.deletedIds.pop() || jQuery.guid++;
- } 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 );
- }
- }
-
- 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;
- }
-
- // 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,
-
- isNode = elem.nodeType,
-
- // See jQuery.data for more information
- cache = isNode ? jQuery.cache : elem,
- id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
-
- // 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;
- }
- }
-
- // Destroy the cache
- if ( isNode ) {
- jQuery.cleanData( [ elem ], true );
-
- // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
- } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
- delete cache[ id ];
-
- // When all else fails, null
- } else {
- cache[ id ] = 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 ) {
- var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
-
- // nodes accept data unless otherwise specified; rejection can be conditional
- return !noData || noData !== true && elem.getAttribute("classid") === noData;
- }
-});
-
-jQuery.fn.extend({
- data: function( key, value ) {
- var parts, part, attr, name, l,
- elem = this[0],
- i = 0,
- data = null;
+// The deferred used on DOM ready
+var readyList;
- // Gets all values
- if ( key === undefined ) {
- if ( this.length ) {
- data = jQuery.data( elem );
+jQuery.fn.ready = function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
- if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
- attr = elem.attributes;
- for ( l = attr.length; i < l; i++ ) {
- name = attr[i].name;
+ return this;
+};
- if ( !name.indexOf( "data-" ) ) {
- name = jQuery.camelCase( name.substring(5) );
+jQuery.extend({
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
- dataAttr( elem, name, data[ name ] );
- }
- }
- jQuery._data( elem, "parsedAttrs", true );
- }
- }
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
- return data;
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
}
+ },
- // Sets multiple values
- if ( typeof key === "object" ) {
- return this.each(function() {
- jQuery.data( this, key );
- });
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
}
- parts = key.split( ".", 2 );
- parts[1] = parts[1] ? "." + parts[1] : "";
- part = parts[1] + "!";
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready );
+ }
- return jQuery.access( this, function( value ) {
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
- if ( value === undefined ) {
- data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
- // Try to fetch any internally stored data first
- if ( data === undefined && elem ) {
- data = jQuery.data( elem, key );
- data = dataAttr( elem, key, data );
- }
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
- return data === undefined && parts[1] ?
- this.data( parts[0] ) :
- data;
- }
+ // Trigger any bound ready events
+ if ( jQuery.fn.triggerHandler ) {
+ jQuery( document ).triggerHandler( "ready" );
+ jQuery( document ).off( "ready" );
+ }
+ }
+});
- parts[1] = value;
- this.each(function() {
- var self = jQuery( this );
+/**
+ * Clean-up method for dom ready events
+ */
+function detach() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", completed, false );
+ window.removeEventListener( "load", completed, false );
- self.triggerHandler( "setData" + part, parts );
- jQuery.data( this, key, value );
- self.triggerHandler( "changeData" + part, parts );
- });
- }, null, value, arguments.length > 1, null, false );
- },
+ } else {
+ document.detachEvent( "onreadystatechange", completed );
+ window.detachEvent( "onload", completed );
+ }
+}
- removeData: function( key ) {
- return this.each(function() {
- jQuery.removeData( this, key );
- });
+/**
+ * The ready event handler and self cleanup method
+ */
+function completed() {
+ // readyState === "complete" is good enough for us to call the dom ready in oldIE
+ if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
+ detach();
+ jQuery.ready();
}
-});
+}
-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 ) {
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
- var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+ readyList = jQuery.Deferred();
- data = elem.getAttribute( name );
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready );
- if ( typeof data === "string" ) {
- try {
- data = data === "true" ? true :
- data === "false" ? false :
- data === "null" ? null :
- // Only convert to a number if it doesn't change the string
- +data + "" === data ? +data :
- rbrace.test( data ) ? jQuery.parseJSON( data ) :
- data;
- } catch( e ) {}
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", completed, false );
- // Make sure we set the data so it isn't changed later
- jQuery.data( elem, key, data );
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", completed, false );
+ // If IE event model is used
} else {
- data = undefined;
- }
- }
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", completed );
- return data;
-}
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", completed );
-// checks a cache object for emptiness
-function isEmptyDataObject( obj ) {
- var name;
- for ( name in obj ) {
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
- // 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;
- }
- }
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
- return true;
-}
-jQuery.extend({
- queue: function( elem, type, data ) {
- var queue;
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
- if ( elem ) {
- type = ( type || "fx" ) + "queue";
- queue = jQuery._data( elem, type );
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
- // Speed up dequeue by getting out quickly if this is just a lookup
- if ( data ) {
- if ( !queue || jQuery.isArray(data) ) {
- queue = jQuery._data( elem, type, jQuery.makeArray(data) );
- } else {
- queue.push( data );
- }
+ // detach all dom ready events
+ detach();
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
}
- return queue || [];
}
- },
+ }
+ return readyList.promise( obj );
+};
- dequeue: function( elem, type ) {
- type = type || "fx";
- var queue = jQuery.queue( elem, type ),
- startLength = queue.length,
- fn = queue.shift(),
- hooks = jQuery._queueHooks( elem, type ),
- next = function() {
- jQuery.dequeue( elem, type );
- };
+var strundefined = typeof undefined;
- // If the fx queue is dequeued, always remove the progress sentinel
- if ( fn === "inprogress" ) {
- fn = queue.shift();
- startLength--;
- }
- if ( fn ) {
- // Add a progress sentinel to prevent the fx queue from being
- // automatically dequeued
- if ( type === "fx" ) {
- queue.unshift( "inprogress" );
- }
+// Support: IE<9
+// Iteration over object's inherited properties before its own
+var i;
+for ( i in jQuery( support ) ) {
+ break;
+}
+support.ownLast = i !== "0";
- // clear up the last queue stop function
- delete hooks.stop;
- fn.call( elem, next, hooks );
- }
+// Note: most support tests are defined in their respective modules.
+// false until the test is run
+support.inlineBlockNeedsLayout = false;
- if ( !startLength && hooks ) {
- hooks.empty.fire();
- }
- },
+// Execute ASAP in case we need to set body.style.zoom
+jQuery(function() {
+ // Minified: var a,b,c,d
+ var val, div, body, container;
- // not intended for public consumption - generates a queueHooks object, or returns the current one
- _queueHooks: function( elem, type ) {
- var key = type + "queueHooks";
- return jQuery._data( elem, key ) || jQuery._data( elem, key, {
- empty: jQuery.Callbacks("once memory").add(function() {
- jQuery.removeData( elem, type + "queue", true );
- jQuery.removeData( elem, key, true );
- })
- });
+ body = document.getElementsByTagName( "body" )[ 0 ];
+ if ( !body || !body.style ) {
+ // Return for frameset docs that don't have a body
+ return;
}
-});
-jQuery.fn.extend({
- queue: function( type, data ) {
- var setter = 2;
+ // Setup
+ div = document.createElement( "div" );
+ container = document.createElement( "div" );
+ container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
+ body.appendChild( container ).appendChild( div );
- if ( typeof type !== "string" ) {
- data = type;
- type = "fx";
- setter--;
- }
+ if ( typeof div.style.zoom !== strundefined ) {
+ // Support: IE<8
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1";
- if ( arguments.length < setter ) {
- return jQuery.queue( this[0], type );
+ support.inlineBlockNeedsLayout = val = div.offsetWidth === 3;
+ if ( val ) {
+ // Prevent IE 6 from affecting layout for positioned elements #11048
+ // Prevent IE from shrinking the body in IE 7 mode #12869
+ // Support: IE<8
+ body.style.zoom = 1;
}
+ }
- return data === undefined ?
- this :
- this.each(function() {
- var queue = jQuery.queue( this, type, data );
-
- // ensure a hooks for this queue
- jQuery._queueHooks( this, type );
-
- 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, obj ) {
- var tmp,
- count = 1,
- defer = jQuery.Deferred(),
- elements = this,
- i = this.length,
- resolve = function() {
- if ( !( --count ) ) {
- defer.resolveWith( elements, [ elements ] );
- }
- };
+ body.removeChild( container );
+});
- if ( typeof type !== "string" ) {
- obj = type;
- type = undefined;
- }
- type = type || "fx";
- while( i-- ) {
- tmp = jQuery._data( elements[ i ], type + "queueHooks" );
- if ( tmp && tmp.empty ) {
- count++;
- tmp.empty.add( resolve );
- }
+
+
+(function() {
+ var div = document.createElement( "div" );
+
+ // Execute the test only if not already executed in another module.
+ if (support.deleteExpando == null) {
+ // Support: IE<9
+ support.deleteExpando = true;
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
}
- resolve();
- return defer.promise( obj );
}
-});
-var nodeHook, boolHook, fixSpecified,
- rclass = /[\t\r\n]/g,
- 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;
-jQuery.fn.extend({
- attr: function( name, value ) {
- return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
- },
+ // Null elements to avoid leaks in IE.
+ div = null;
+})();
- removeAttr: function( name ) {
- return this.each(function() {
- jQuery.removeAttr( this, name );
- });
- },
- prop: function( name, value ) {
- return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
- },
+/**
+ * Determines whether an object can have data
+ */
+jQuery.acceptData = function( elem ) {
+ var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ],
+ nodeType = +elem.nodeType || 1;
- 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)
+ // Do not set data on non-element DOM nodes because it will not be cleared (#8335).
+ return nodeType !== 1 && nodeType !== 9 ?
+ false :
+
+ // Nodes accept data unless otherwise specified; rejection can be conditional
+ !noData || noData !== true && elem.getAttribute("classid") === noData;
+};
+
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+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 {
- this[ name ] = undefined;
- delete this[ name ];
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
} catch( e ) {}
- });
- },
- addClass: function( value ) {
- var classNames, i, l, elem,
- setClass, c, cl;
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
- if ( jQuery.isFunction( value ) ) {
- return this.each(function( j ) {
- jQuery( this ).addClass( value.call(this, j, this.className) );
- });
+ } else {
+ data = undefined;
}
+ }
- if ( value && typeof value === "string" ) {
- classNames = value.split( core_rspace );
+ return data;
+}
- for ( i = 0, l = this.length; i < l; i++ ) {
- elem = this[ i ];
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
- if ( elem.nodeType === 1 ) {
- if ( !elem.className && classNames.length === 1 ) {
- elem.className = value;
+ // 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;
+ }
+ }
- } else {
- setClass = " " + elem.className + " ";
+ return true;
+}
- for ( c = 0, cl = classNames.length; c < cl; c++ ) {
- if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
- setClass += classNames[ c ] + " ";
- }
- }
- elem.className = jQuery.trim( setClass );
- }
- }
- }
- }
+function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
- return this;
- },
+ var ret, thisCache,
+ internalKey = jQuery.expando,
- removeClass: function( value ) {
- var removes, className, elem, c, cl, i, l;
+ // 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,
- 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 ) {
- removes = ( value || "" ).split( core_rspace );
+ // 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,
- for ( i = 0, l = this.length; i < l; i++ ) {
- elem = this[ i ];
- if ( elem.nodeType === 1 && elem.className ) {
+ // 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;
- className = (" " + elem.className + " ").replace( rclass, " " );
+ // 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] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
+ return;
+ }
- // loop over each item in the removal list
- for ( c = 0, cl = removes.length; c < cl; c++ ) {
- // Remove until there is nothing to remove,
- while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
- className = className.replace( " " + removes[ c ] + " " , " " );
- }
- }
- elem.className = value ? jQuery.trim( className ) : "";
- }
- }
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
}
+ }
- return this;
- },
+ if ( !cache[ id ] ) {
+ // Avoid exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
+ }
- toggleClass: function( value, stateVal ) {
- var type = typeof value,
- isBool = typeof stateVal === "boolean";
+ // 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 );
+ }
+ }
- if ( jQuery.isFunction( value ) ) {
- return this.each(function( i ) {
- jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
- });
+ 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 = {};
}
- return this.each(function() {
- if ( type === "string" ) {
- // toggle individual class names
- var className,
- i = 0,
- self = jQuery( this ),
- state = stateVal,
- classNames = value.split( core_rspace );
+ thisCache = thisCache.data;
+ }
- while ( (className = classNames[ i++ ]) ) {
- // check each className given, space separated list
- state = isBool ? state : !self.hasClass( className );
- self[ state ? "addClass" : "removeClass" ]( className );
- }
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
- } else if ( type === "undefined" || type === "boolean" ) {
- if ( this.className ) {
- // store className if set
- jQuery._data( this, "__className__", this.className );
- }
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( typeof name === "string" ) {
- // toggle whole className
- this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
- }
- });
- },
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
- 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 ) >= 0 ) {
- return true;
- }
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
}
+ } else {
+ ret = thisCache;
+ }
- return false;
- },
+ return ret;
+}
- val: function( value ) {
- var hooks, ret, isFunction,
- elem = this[0];
+function internalRemoveData( elem, name, pvt ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
- if ( !arguments.length ) {
- if ( elem ) {
- hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+ var thisCache, i,
+ isNode = elem.nodeType,
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
- return ret;
- }
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
- ret = elem.value;
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
- return typeof ret === "string" ?
- // handle most common string cases
- ret.replace(rreturn, "") :
- // handle cases where value is null/undef or number
- ret == null ? "" : ret;
- }
+ if ( name ) {
- return;
- }
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
- isFunction = jQuery.isFunction( value );
+ if ( thisCache ) {
- return this.each(function( i ) {
- var val,
- self = jQuery(this);
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
- if ( this.nodeType !== 1 ) {
- return;
- }
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
- if ( isFunction ) {
- val = value.call( this, i, self.val() );
+ // 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(" ");
+ }
+ }
} else {
- val = value;
+ // If "name" is an array of keys...
+ // When data is initially created, via ("key", "val") signature,
+ // keys will be converted to camelCase.
+ // Since there is no way to tell _how_ a key was added, remove
+ // both plain key and camelCase key. #12786
+ // This will only penalize the array argument path.
+ name = name.concat( jQuery.map( name, jQuery.camelCase ) );
}
- // 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 + "";
- });
+ i = name.length;
+ while ( i-- ) {
+ delete thisCache[ name[i] ];
}
- hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
-
- // If set returns undefined, fall back to normal setting
- if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
- this.value = val;
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( pvt ? !isEmptyDataObject(thisCache) : !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;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ /* jshint eqeqeq: false */
+ } else if ( support.deleteExpando || cache != cache.window ) {
+ /* jshint eqeqeq: true */
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+}
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, option,
- options = elem.options,
- index = elem.selectedIndex,
- one = elem.type === "select-one" || index < 0,
- values = one ? null : [],
- max = one ? index + 1 : options.length,
- i = index < 0 ?
- max :
- one ? index : 0;
+ cache: {},
- // Loop through all the selected options
- for ( ; i < max; i++ ) {
- option = options[ i ];
+ // The following elements (space-suffixed to avoid Object.prototype collisions)
+ // throw uncatchable exceptions if you attempt to set expando properties
+ noData: {
+ "applet ": true,
+ "embed ": true,
+ // ...but Flash objects (which have this classid) *can* handle expandos
+ "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
+ },
- // oldIE doesn't update selected after form reset (#2551)
- if ( ( option.selected || i === index ) &&
- // Don't return options that are disabled or in a disabled optgroup
- ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
- ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data ) {
+ return internalData( elem, name, data );
+ },
+
+ removeData: function( elem, name ) {
+ return internalRemoveData( elem, name );
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return internalData( elem, name, data, true );
+ },
+
+ _removeData: function( elem, name ) {
+ return internalRemoveData( elem, name, true );
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var i, name, data,
+ elem = this[0],
+ attrs = elem && elem.attributes;
- // Get the specific value for the option
- value = jQuery( option ).val();
+ // Special expections of .data basically thwart jQuery.access,
+ // so implement the relevant behavior ourselves
- // We don't need an array for one selects
- if ( one ) {
- return value;
- }
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
- // Multi-Selects return an array
- values.push( value );
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ i = attrs.length;
+ while ( i-- ) {
+
+ // Support: IE11+
+ // The attrs elements can be null (#14894)
+ if ( attrs[ i ] ) {
+ name = attrs[ i ].name;
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.slice(5) );
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
}
+ jQuery._data( elem, "parsedAttrs", true );
}
+ }
- return values;
- },
+ return data;
+ }
- set: function( elem, value ) {
- var values = jQuery.makeArray( value );
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
- jQuery(elem).find("option").each(function() {
- this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
- });
+ return arguments.length > 1 ?
- if ( !values.length ) {
- elem.selectedIndex = -1;
- }
- return values;
- }
- }
+ // Sets one value
+ this.each(function() {
+ jQuery.data( this, key, value );
+ }) :
+
+ // Gets one value
+ // Try to fetch any internally stored data first
+ elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
},
- // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
- attrFn: {},
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
- 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;
- }
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
- if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
- return jQuery( elem )[ name ]( value );
- }
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
- // Fallback to prop when attributes are not supported
- if ( typeof elem.getAttribute === "undefined" ) {
- return jQuery.prop( elem, name, value );
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
}
+ },
- 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 );
- }
+ dequeue: function( elem, type ) {
+ type = type || "fx";
- if ( value !== undefined ) {
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
- if ( value === null ) {
- jQuery.removeAttr( elem, name );
- return;
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
- } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
- return ret;
+ if ( fn ) {
- } else {
- elem.setAttribute( name, value + "" );
- return value;
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
}
- } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
- return ret;
-
- } else {
-
- ret = elem.getAttribute( name );
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
- // Non-existent attributes return null, we normalize to undefined
- return ret === null ?
- undefined :
- ret;
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
}
},
- removeAttr: function( elem, value ) {
- var propName, attrNames, name, isBool,
- i = 0;
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery._removeData( elem, type + "queue" );
+ jQuery._removeData( elem, key );
+ })
+ });
+ }
+});
- if ( value && elem.nodeType === 1 ) {
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
- attrNames = value.split( core_rspace );
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
- for ( ; i < attrNames.length; i++ ) {
- name = attrNames[ i ];
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
- if ( name ) {
- propName = jQuery.propFix[ name ] || name;
- isBool = rboolean.test( name );
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
- // See #9699 for explanation of this approach (setting first, then removal)
- // Do not do this for boolean attributes (see #10870)
- if ( !isBool ) {
- jQuery.attr( elem, name, "" );
- }
- elem.removeAttribute( getSetAttribute ? name : propName );
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
- // Set corresponding property to false for boolean attributes
- if ( isBool && propName in elem ) {
- elem[ propName ] = false;
- }
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
}
- }
- }
+ });
},
-
- 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;
- }
- }
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
},
-
- 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"
+ 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, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
- prop: function( elem, name, value ) {
- var ret, hooks, notxml,
- nType = elem.nodeType;
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
- // don't get/set properties on text, comment and attribute nodes
- if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
- return;
+ while ( i-- ) {
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
}
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
- notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
- if ( notxml ) {
- // Fix name and attach hooks
- name = jQuery.propFix[ name ] || name;
- hooks = jQuery.propHooks[ name ];
- }
+var isHidden = function( elem, el ) {
+ // isHidden might be called from jQuery#filter function;
+ // in that case, element will be second argument
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+ };
- 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;
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+ var i = 0,
+ length = elems.length,
+ bulk = key == null;
- } else {
- return elem[ name ];
- }
+ // Sets many values
+ if ( jQuery.type( key ) === "object" ) {
+ chainable = true;
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
}
- },
- 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");
+ // Sets one value
+ } else if ( value !== undefined ) {
+ chainable = true;
- return attributeNode && attributeNode.specified ?
- parseInt( attributeNode.value, 10 ) :
- rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
- 0 :
- undefined;
- }
+ if ( !jQuery.isFunction( value ) ) {
+ raw = true;
}
- }
-});
-// 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;
+ if ( bulk ) {
+ // Bulk operations run against the entire set
+ if ( raw ) {
+ fn.call( elems, value );
+ fn = null;
+
+ // ...except when executing function values
+ } else {
+ bulk = fn;
+ fn = function( elem, key, value ) {
+ return bulk.call( jQuery( elem ), value );
+ };
}
+ }
- elem.setAttribute( name, name.toLowerCase() );
+ if ( fn ) {
+ for ( ; i < length; i++ ) {
+ fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+ }
}
- return name;
}
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
};
+var rcheckableType = (/^(?:checkbox|radio)$/i);
-// IE6/7 do not support getting/setting some attributes with get/setAttribute
-if ( !getSetAttribute ) {
- fixSpecified = {
- name: true,
- id: true,
- coords: 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.value !== "" : ret.specified ) ?
- ret.value :
- 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.value = value + "" );
- }
- };
+(function() {
+ // Minified: var a,b,c
+ var input = document.createElement( "input" ),
+ div = document.createElement( "div" ),
+ fragment = document.createDocumentFragment();
- // 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;
- }
- }
- });
- });
+ // Setup
+ div.innerHTML = "
a";
- // 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 );
- }
- };
-}
+ // IE strips leading whitespace when .innerHTML is used
+ support.leadingWhitespace = div.firstChild.nodeType === 3;
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ support.tbody = !div.getElementsByTagName( "tbody" ).length;
-// 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;
- }
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ support.htmlSerialize = !!div.getElementsByTagName( "link" ).length;
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ support.html5Clone =
+ document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>";
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ input.type = "checkbox";
+ input.checked = true;
+ fragment.appendChild( input );
+ support.appendChecked = input.checked;
+
+ // Make sure textarea (and checkbox) defaultValue is properly cloned
+ // Support: IE6-IE11+
+ div.innerHTML = "";
+ support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ fragment.appendChild( div );
+ div.innerHTML = "";
+
+ // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
+ // old WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Support: IE<9
+ // Opera does not clone events (and typeof div.attachEvent === undefined).
+ // IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
+ support.noCloneEvent = true;
+ if ( div.attachEvent ) {
+ div.attachEvent( "onclick", function() {
+ support.noCloneEvent = false;
});
- });
-}
-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 + "" );
+ div.cloneNode( true ).click();
+ }
+
+ // Execute the test only if not already executed in another module.
+ if (support.deleteExpando == null) {
+ // Support: IE<9
+ support.deleteExpando = true;
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
}
- };
-}
+ }
+})();
-// 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;
+(function() {
+ var i, eventName,
+ div = document.createElement( "div" );
- // Make sure that it also works with optgroups, see #5701
- if ( parent.parentNode ) {
- parent.parentNode.selectedIndex;
- }
- }
- return null;
+ // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event)
+ for ( i in { submit: true, change: true, focusin: true }) {
+ eventName = "on" + i;
+
+ if ( !(support[ i + "Bubbles" ] = eventName in window) ) {
+ // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
+ div.setAttribute( eventName, "t" );
+ support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false;
}
- });
+ }
+
+ // Null elements to avoid leaks in IE.
+ div = null;
+})();
+
+
+var rformElems = /^(?:input|select|textarea)$/i,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+ return true;
}
-// IE6/7 call enctype encoding
-if ( !jQuery.support.enctype ) {
- jQuery.propFix.enctype = "encoding";
+function returnFalse() {
+ return false;
}
-// 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;
- }
- };
- });
+function safeActiveElement() {
+ try {
+ return document.activeElement;
+ } catch ( err ) { }
}
-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 = /(?:^|\s)hover(\.\S+|)\b/,
- rkeyEvent = /^key/,
- rmouseEvent = /^(?:mouse|contextmenu)|click/,
- rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
- 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.
@@ -2639,14 +4272,16 @@ var rformElems = /^(?:textarea|input|select)$/i,
*/
jQuery.event = {
- add: function( elem, types, handler, data, selector ) {
+ global: {},
- var elemData, eventHandle, events,
- t, tns, type, namespaces, handleObj,
- handleObjIn, handlers, special;
+ add: function( elem, types, handler, data, selector ) {
+ var tmp, events, t, handleObjIn,
+ special, eventHandle, handleObj,
+ handlers, type, namespaces, origType,
+ elemData = jQuery._data( elem );
- // 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 )) ) {
+ // Don't attach events to noData or text/comment nodes (but allow plain objects)
+ if ( !elemData ) {
return;
}
@@ -2663,16 +4298,14 @@ jQuery.event = {
}
// Init the element's event structure and main handler, if this is the first
- events = elemData.events;
- if ( !events ) {
- elemData.events = events = {};
+ if ( !(events = elemData.events) ) {
+ events = elemData.events = {};
}
- eventHandle = elemData.handle;
- if ( !eventHandle ) {
- elemData.handle = eventHandle = function( e ) {
+ if ( !(eventHandle = elemData.handle) ) {
+ eventHandle = elemData.handle = 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) ?
+ return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
undefined;
};
@@ -2681,13 +4314,17 @@ jQuery.event = {
}
// 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();
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+ // There *must* be a type, no attaching namespace-only handlers
+ if ( !type ) {
+ continue;
+ }
// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[ type ] || {};
@@ -2701,7 +4338,7 @@ jQuery.event = {
// handleObj is passed to all event handlers
handleObj = jQuery.extend({
type: type,
- origType: tns[1],
+ origType: origType,
data: data,
handler: handler,
guid: handler.guid,
@@ -2711,8 +4348,7 @@ jQuery.event = {
}, handleObjIn );
// Init the event handler queue if we're the first
- handlers = events[ type ];
- if ( !handlers ) {
+ if ( !(handlers = events[ type ]) ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0;
@@ -2751,13 +4387,12 @@ jQuery.event = {
elem = null;
},
- global: {},
-
// Detach an event or set of events from an element
remove: function( elem, types, handler, selector, mappedTypes ) {
-
- var t, tns, type, origType, namespaces, origCount,
- j, events, special, eventType, handleObj,
+ var j, handleObj, tmp,
+ origCount, t, events,
+ special, handlers, type,
+ namespaces, origType,
elemData = jQuery.hasData( elem ) && jQuery._data( elem );
if ( !elemData || !(events = elemData.events) ) {
@@ -2765,11 +4400,12 @@ jQuery.event = {
}
// 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];
+ types = ( types || "" ).match( rnotwhite ) || [ "" ];
+ t = types.length;
+ while ( t-- ) {
+ tmp = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tmp[1];
+ namespaces = ( tmp[2] || "" ).split( "." ).sort();
// Unbind all events (on this namespace, if provided) for the element
if ( !type ) {
@@ -2780,23 +4416,23 @@ jQuery.event = {
}
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;
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+ handlers = events[ type ] || [];
+ tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
// Remove matching events
- for ( j = 0; j < eventType.length; j++ ) {
- handleObj = eventType[ j ];
+ origCount = j = handlers.length;
+ while ( j-- ) {
+ handleObj = handlers[ 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 );
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !tmp || tmp.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ handlers.splice( j, 1 );
if ( handleObj.selector ) {
- eventType.delegateCount--;
+ handlers.delegateCount--;
}
if ( special.remove ) {
special.remove.call( elem, handleObj );
@@ -2806,7 +4442,7 @@ jQuery.event = {
// 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 ( origCount && !handlers.length ) {
if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
jQuery.removeEvent( elem, type, elemData.handle );
}
@@ -2821,80 +4457,48 @@ jQuery.event = {
// removeData also checks for emptiness and clears the expando if empty
// so use it instead of delete
- jQuery.removeData( elem, "events", true );
+ jQuery._removeData( elem, "events" );
}
},
- // 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 ) {
+ var handle, ontype, cur,
+ bubbleType, special, tmp, i,
+ eventPath = [ elem || document ],
+ type = hasOwn.call( event, "type" ) ? event.type : event,
+ namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
+
+ cur = tmp = elem = elem || document;
+
// Don't do events on text and comment nodes
- if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
- // Event object or event type
- var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
- type = event.type || event,
- namespaces = [];
-
// 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 ) {
+ if ( type.indexOf(".") >= 0 ) {
// Namespaced trigger; create a regexp to match event type in handle()
namespaces = type.split(".");
type = namespaces.shift();
namespaces.sort();
}
+ ontype = type.indexOf(":") < 0 && "on" + type;
- 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 ) {
+ // Caller can pass in a jQuery.Event object, Object, or just an event type string
+ event = event[ jQuery.expando ] ?
+ event :
+ new jQuery.Event( type, typeof event === "object" && event );
- // 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;
- }
+ // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+ event.isTrigger = onlyHandlers ? 2 : 3;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = event.namespace ?
+ new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+ null;
// Clean up the event in case it is being reused
event.result = undefined;
@@ -2903,47 +4507,56 @@ jQuery.event = {
}
// Clone any incoming data and prepend the event, creating the handler arg list
- data = data != null ? jQuery.makeArray( data ) : [];
- data.unshift( event );
+ data = data == null ?
+ [ event ] :
+ jQuery.makeArray( data, [ event ] );
// Allow special events to draw outside the lines
special = jQuery.event.special[ type ] || {};
- if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ if ( !onlyHandlers && 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;
- for ( old = elem; cur; cur = cur.parentNode ) {
- eventPath.push([ cur, bubbleType ]);
- old = cur;
+ if ( !rfocusMorph.test( bubbleType + type ) ) {
+ cur = cur.parentNode;
+ }
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push( cur );
+ tmp = cur;
}
// Only add window if we got to document (e.g., not plain obj or detached DOM)
- if ( old === (elem.ownerDocument || document) ) {
- eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ if ( tmp === (elem.ownerDocument || document) ) {
+ eventPath.push( tmp.defaultView || tmp.parentWindow || window );
}
}
// Fire handlers on the event path
- for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+ i = 0;
+ while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
- cur = eventPath[i][0];
- event.type = eventPath[i][1];
+ event.type = i > 1 ?
+ bubbleType :
+ special.bindType || type;
+ // jQuery handler
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
+
+ // Native handler
handle = ontype && cur[ ontype ];
- if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
- event.preventDefault();
+ if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
+ event.result = handle.apply( cur, data );
+ if ( event.result === false ) {
+ event.preventDefault();
+ }
}
}
event.type = type;
@@ -2951,29 +4564,33 @@ jQuery.event = {
// 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 ) ) {
+ if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+ 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 ) ) {
+ if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
// Don't re-trigger an onFOO event when we call its FOO() method
- old = elem[ ontype ];
+ tmp = elem[ ontype ];
- if ( old ) {
+ if ( tmp ) {
elem[ ontype ] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
jQuery.event.triggered = type;
- elem[ type ]();
+ try {
+ elem[ type ]();
+ } catch ( e ) {
+ // IE<9 dies on focus/blur to hidden element (#1486,#12518)
+ // only reproducible on winXP IE8 native, not IE9 in IE8 mode
+ }
jQuery.event.triggered = undefined;
- if ( old ) {
- elem[ ontype ] = old;
+ if ( tmp ) {
+ elem[ ontype ] = tmp;
}
}
}
@@ -2985,15 +4602,13 @@ jQuery.event = {
dispatch: function( event ) {
// Make a writable jQuery.Event from the native event object
- event = jQuery.event.fix( event || window.event );
+ event = jQuery.event.fix( event );
- var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
- handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
- delegateCount = handlers.delegateCount,
- args = core_slice.call( arguments ),
- run_all = !event.exclusive && !event.namespace,
- special = jQuery.event.special[ event.type ] || {},
- handlerQueue = [];
+ var i, ret, handleObj, matched, j,
+ handlerQueue = [],
+ args = slice.call( arguments ),
+ handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
+ special = jQuery.event.special[ event.type ] || {};
// Use the fix-ed jQuery.Event rather than the (read-only) native event
args[0] = event;
@@ -3004,81 +4619,142 @@ jQuery.event = {
return;
}
- // Determine handlers that should run if there are delegated events
+ // Determine handlers
+ handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+ // Run delegates first; they may want to stop propagation beneath us
+ i = 0;
+ while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+ event.currentTarget = matched.elem;
+
+ j = 0;
+ while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+ // Triggered event must either 1) have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.handleObj = handleObj;
+ event.data = handleObj.data;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ if ( (event.result = ret) === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ handlers: function( event, handlers ) {
+ var sel, handleObj, matches, i,
+ handlerQueue = [],
+ delegateCount = handlers.delegateCount,
+ cur = event.target;
+
+ // Find delegate handlers
+ // Black-hole SVG