diff --git a/application/admin/controller/Estimate.php b/application/admin/controller/Estimate.php index aa622af..83189a5 100644 --- a/application/admin/controller/Estimate.php +++ b/application/admin/controller/Estimate.php @@ -62,6 +62,7 @@ class Estimate extends Base{ public function estimate_add(){ return $this->fetch(); } + // 规则详情页面 public function estimate_content_observe(){ $data = input(); $result = Db::table('admin_estimate')->where(['id'=>$data['id']])->find(); @@ -72,6 +73,7 @@ class Estimate extends Base{ 'list'=>[], ]; $temporary_array = json_decode($result['content'],true); + return $this->msg($temporary_array); $temporary_array2 = []; foreach ($temporary_array as $key => $value) { $num = array_push($temporary_array2,[ @@ -82,39 +84,43 @@ class Estimate extends Base{ $num2 = array_push($temporary_array2[$num-1]['list'],[ 'title'=>$k1, 'score'=>'', - 'gender'=>'', 'choose_num'=>'', + 'choose_all'=>count($v1), 'list'=>[], ]); foreach ($v1 as $k2 => $v2) { $temporary_array2[$num-1]['list'][$num2-1]['score'] = $v2['score']; - $temporary_array2[$num-1]['list'][$num2-1]['gender'] = $v2['gender']; + + $temporary_array2[$num-1]['list'][$num2-1]['choose_num'] = $v2['choose_num']; - // $num3 = array_push($temporary_array2[$num-1]['list'][$num2-1]['list'],[ - // 'title'=>$k2, - // 'list'=>[], - // ]); + $num3 = array_push($temporary_array2[$num-1]['list'][$num2-1]['list'],[ + 'title'=>$k2, + 'gender'=>'', + 'unit_data'=>$v2['unit_data'], + 'proportion'=>$v2['proportion'], + 'list'=>count($v2['content'])==0?[['成绩','无'],['数据','无']]:[['成绩'],['数据']], + ]); + if($v2['gender'] == '1'){ + $temporary_array2[$num-1]['list'][$num2-1]['list'][$num3-1]['gender'] = '男生'; + }else if($v2['gender'] == '2'){ + $temporary_array2[$num-1]['list'][$num2-1]['list'][$num3-1]['gender'] = '女生'; + }else{ + $temporary_array2[$num-1]['list'][$num2-1]['list'][$num3-1]['gender'] = '男生&女生'; + } + foreach ($v2['content'] as $k3 => $v3) { + array_push($temporary_array2[$num-1]['list'][$num2-1]['list'][$num3-1]['list'][0],$v3[2]); + array_push($temporary_array2[$num-1]['list'][$num2-1]['list'][$num3-1]['list'][1],$v3[1].$v3[0]); + } + } } } $data['list'] = $temporary_array2; - // foreach ($variable as $key => $value) { - // # code... - // } - // foreach ($temporary_array as $key => $value) { - // array_push($temporary_array2,[ - // 'province'=>$result['province'], - // 'city'=>$result['city']=='无'?'':'-'.$result['city'], - // 'title'=>$key.'评定标准', - // 'list'=>[], - // ]); - // } - - dump($data); - // dump($temporary_array2); - dump($temporary_array); - die; - // return $this->fetch(); + $this->assign([ + 'result' => $data, + 'num' => 1, + ]); + return $this->fetch(); } ################################################################业务接口################################################################ @@ -136,11 +142,12 @@ class Estimate extends Base{ } if($file){ // 移动到框架应用根目录/public/uploads/ 目录下 - $file_name_new = time().'_ceshi.xlsx'; + $file_name_new = date('YmdHis').'_ceshi.xlsx'; $info = $file->validate(['size' => 2048000, 'ext' => 'xlsx'])->move(ROOT_PATH . 'public' . DS . 'estimate',$file_name_new); if($info){ $original_data = $this->excelToArray(ROOT_PATH . 'public' . DS . 'estimate' . DS . $info->getSaveName()); $handle_data = $this->transformation_data($original_data,$address_data,$name['type']); + // die; if($name['type'] == 'create'){ $result = Db::table('admin_estimate')->insert($handle_data); }else{ @@ -223,54 +230,92 @@ class Estimate extends Base{ ]; } $temporary_array = []; + + + $gender = []; foreach ($data as $key => $value) { - if(!array_key_exists($value[0],$temporary_array)){ - // 如果第一分类不存在 - $temporary_array[$value[0]][$value[1]][$value[3]] = [ - 'score'=>$value[2], - 'unit_data'=>$value[4], - 'gender'=>$value[5], - 'choose_num'=>$value[9], - 'proportion'=>$value[10], - 'content'=>[ - [$value[6],$value[7],$value[8]] - ], - ]; - }else{ - // 第一分类存在 - if(!array_key_exists($value[1],$temporary_array[$value[0]])){ - // 如果第二分类不存在 - // array_push($temporary_array[$value[0]]); - $temporary_array[$value[0]][$value[1]][$value[3]] = [ - 'score'=>$value[2], + $gender = explode(',',$value[5]); + foreach ($gender as $gk => $gv) { + if(!array_key_exists($gv,$temporary_array)){ + $temporary_array[$gv][$value[0]][$value[1]]['score'] = $value[2]; + $temporary_array[$gv][$value[0]][$value[1]]['choose_num'] = $value[9]; + $temporary_array[$gv][$value[0]][$value[1]][$value[3]] = [ 'unit_data'=>$value[4], - 'gender'=>$value[5], - 'choose_num'=>$value[9], 'proportion'=>$value[10], - 'content'=>[ - [$value[6],$value[7],$value[8]] - ], + 'value'=>$value[11], + 'type'=>$value[12], ]; - }else{ - // 第二分类存在 - if(!array_key_exists($value[3],$temporary_array[$value[0]][$value[1]])){ - // 如果项目名不存在 - $temporary_array[$value[0]][$value[1]][$value[3]] = [ - 'score'=>$value[2], - 'unit_data'=>$value[4], - 'gender'=>$value[5], - 'choose_num'=>$value[9], - 'proportion'=>$value[10], - 'content'=>[ - [$value[6],$value[7],$value[8]] - ], - ]; + if($value[6] == null){ + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['score'] = $value[8]; + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'] = []; }else{ - array_push($temporary_array[$value[0]][$value[1]][$value[3]]['content'],[$value[6],$value[7],$value[8]]); + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'] = [[$value[6],$value[7],$value[8]]]; + } + }else{ + // 如果性别已经存在 + if(!array_key_exists($value[0],$temporary_array[$gv])){ + // 如果第一分类不存在 + $temporary_array[$gv][$value[0]][$value[1]]['score'] = $value[2]; + $temporary_array[$gv][$value[0]][$value[1]]['choose_num'] = $value[9]; + $temporary_array[$gv][$value[0]][$value[1]][$value[3]] = [ + 'unit_data'=>$value[4], + 'proportion'=>$value[10], + 'value'=>$value[11], + 'type'=>$value[12], + ]; + if($value[6] == null){ + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['score'] = $value[8]; + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'] = []; + }else{ + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'] = [[$value[6],$value[7],$value[8]]]; + } + }else{ + // 第一分类存在 + if(!array_key_exists($value[1],$temporary_array[$gv][$value[0]])){ + // 如果第二分类不存在 + // array_push($temporary_array[$value[0]]); + $temporary_array[$gv][$value[0]][$value[1]]['score'] = $value[2]; + $temporary_array[$gv][$value[0]][$value[1]]['choose_num'] = $value[9]; + $temporary_array[$gv][$value[0]][$value[1]][$value[3]] = [ + 'unit_data'=>$value[4], + 'proportion'=>$value[10], + 'value'=>$value[11], + 'type'=>$value[12], + ]; + if($value[6] == null){ + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['score'] = $value[8]; + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'] = []; + }else{ + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'] = [[$value[6],$value[7],$value[8]]]; + } + }else{ + // 第二分类存在 + + if(!array_key_exists($value[3],$temporary_array[$gv][$value[0]][$value[1]])){ + // 如果项目名不存在 + $temporary_array[$gv][$value[0]][$value[1]]['score'] = $value[2]; + $temporary_array[$gv][$value[0]][$value[1]]['choose_num'] = $value[9]; + $temporary_array[$gv][$value[0]][$value[1]][$value[3]] = [ + 'unit_data'=>$value[4], + 'proportion'=>$value[10], + 'value'=>$value[11], + 'type'=>$value[12], + ]; + if($value[6] == null){ + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['score'] = $value[8]; + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'] = []; + }else{ + $temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'] = [[$value[6],$value[7],$value[8]]]; + } + }else{ + array_push($temporary_array[$gv][$value[0]][$value[1]][$value[3]]['content'],[$value[6],$value[7],$value[8]]); + } + } } } } } + // return $temporary_array; $temporary_array = json_encode($temporary_array); $return_result['content'] = $temporary_array; diff --git a/application/admin/view/estimate/device_edit.html b/application/admin/view/estimate/device_edit.html deleted file mode 100644 index d80b175..0000000 --- a/application/admin/view/estimate/device_edit.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - 设备管理_edit - - - - - - - - - - - - -
-
-
-
- - -
- - -
- -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
-
- - - -
-
-
-
- - - - - - \ No newline at end of file diff --git a/application/admin/view/estimate/estimate_content_observe.html b/application/admin/view/estimate/estimate_content_observe.html index 4f849f6..898bea9 100644 --- a/application/admin/view/estimate/estimate_content_observe.html +++ b/application/admin/view/estimate/estimate_content_observe.html @@ -11,349 +11,77 @@ -
- - 首页 - 演示 - - 导航元素 - - - -
+
- -
- - - + {volist name="result.list" id="vo" key="k" } +
+ {$result.province}{$result.city == ''?'':'-'.$result.city}{$vo.title}
-
- - - - - - - - - - - - - - {volist name="result" id="vo"} - - - - - - - - - - {/volist} - -
ID区域创建时间修改时间操作
{$vo.id}{$vo.province}/{$vo.city == '无'?'':$vo.city}{$vo.create_time}{$vo.update_time} - - -
-
-
-
+ {volist name="vo.list" id="vo1" key="k1" } +
+ {$vo1.title}({$vo1.choose_all}选{$vo1.choose_num})(分值{$vo1.score})
-
- +
+ + + {volist name="vo2.list" id="vo3" key="k3" } + + {volist name="vo3" id="vo4" key="k4"} + + {/volist} + + {/volist} + + +
{$vo4}
+
+ {/volist} + + {/volist} + + {/volist}
- \ No newline at end of file diff --git a/application/admin/view/estimate/estimate_edit.html b/application/admin/view/estimate/estimate_edit.html new file mode 100644 index 0000000..0c4a61e --- /dev/null +++ b/application/admin/view/estimate/estimate_edit.html @@ -0,0 +1,383 @@ + + + + + + 设备管理_add + + + + + + + + + + + +
+
+
+ + +
+
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ + +
+ +
+
+ + + + +
+ + + +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/application/admin/view/estimate/index.html b/application/admin/view/estimate/index.html index fc4c0c8..bcf2341 100644 --- a/application/admin/view/estimate/index.html +++ b/application/admin/view/estimate/index.html @@ -60,7 +60,7 @@
- +
@@ -80,7 +80,7 @@ - + diff --git a/application/app/controller/Card.php b/application/app/controller/Card.php index 7fabb89..30c8d83 100644 --- a/application/app/controller/Card.php +++ b/application/app/controller/Card.php @@ -144,6 +144,9 @@ class Card extends Base{ if(!array_key_exists('aud_id', $data) || !array_key_exists('height', $data) || !array_key_exists('weight', $data) || !array_key_exists('adc', $data) || !array_key_exists('token', $data)){ return $this->msg(10001); } + if($data['adc'] == 0){ + $data['adc'] = 550; + } // if($this->token_time_validate($data['token']) === false){ // return $this->msg(20001); // } diff --git a/public/estimate/1720120672_ceshi.xlsx b/public/estimate/1720120672_ceshi.xlsx deleted file mode 100644 index 88a3840..0000000 Binary files a/public/estimate/1720120672_ceshi.xlsx and /dev/null differ diff --git a/public/estimate/20240706181307_ceshi.xlsx b/public/estimate/20240706181307_ceshi.xlsx new file mode 100644 index 0000000..9e121ac Binary files /dev/null and b/public/estimate/20240706181307_ceshi.xlsx differ diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..f9bf948 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7ad5982 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/myclabs/php-enum/stubs/Stringable.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..f58706c --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,11 @@ + $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..1ee7a39 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($vendorDir . '/ezyang/htmlpurifier/library'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..1fe6f08 --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,22 @@ + array($vendorDir . '/topthink/think-installer/src'), + 'think\\' => array($baseDir . '/thinkphp/library/think'), + 'app\\' => array($baseDir . '/application'), + 'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'), + 'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'), + 'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'), + 'Matrix\\' => array($vendorDir . '/markbaker/matrix/classes/src'), + 'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..6d2dff5 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,73 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit2bc4f313dba415539e266f7ac2c87dcd::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit2bc4f313dba415539e266f7ac2c87dcd::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire2bc4f313dba415539e266f7ac2c87dcd($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire2bc4f313dba415539e266f7ac2c87dcd($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..6afeefc --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,131 @@ + __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php', + ); + + public static $prefixLengthsPsr4 = array ( + 't' => + array ( + 'think\\composer\\' => 15, + 'think\\' => 6, + ), + 'a' => + array ( + 'app\\' => 4, + ), + 'Z' => + array ( + 'ZipStream\\' => 10, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Mbstring\\' => 26, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'PhpOffice\\PhpSpreadsheet\\' => 25, + 'PHPMailer\\PHPMailer\\' => 20, + ), + 'M' => + array ( + 'MyCLabs\\Enum\\' => 13, + 'Matrix\\' => 7, + ), + 'C' => + array ( + 'Complex\\' => 8, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'think\\composer\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-installer/src', + ), + 'think\\' => + array ( + 0 => __DIR__ . '/../..' . '/thinkphp/library/think', + ), + 'app\\' => + array ( + 0 => __DIR__ . '/../..' . '/application', + ), + 'ZipStream\\' => + array ( + 0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'PhpOffice\\PhpSpreadsheet\\' => + array ( + 0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet', + ), + 'PHPMailer\\PHPMailer\\' => + array ( + 0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src', + ), + 'MyCLabs\\Enum\\' => + array ( + 0 => __DIR__ . '/..' . '/myclabs/php-enum/src', + ), + 'Matrix\\' => + array ( + 0 => __DIR__ . '/..' . '/markbaker/matrix/classes/src', + ), + 'Complex\\' => + array ( + 0 => __DIR__ . '/..' . '/markbaker/complex/classes/src', + ), + ); + + public static $prefixesPsr0 = array ( + 'H' => + array ( + 'HTMLPurifier' => + array ( + 0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library', + ), + ), + ); + + public static $classMap = array ( + 'Stringable' => __DIR__ . '/..' . '/myclabs/php-enum/stubs/Stringable.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit2bc4f313dba415539e266f7ac2c87dcd::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit2bc4f313dba415539e266f7ac2c87dcd::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit2bc4f313dba415539e266f7ac2c87dcd::$prefixesPsr0; + $loader->classMap = ComposerStaticInit2bc4f313dba415539e266f7ac2c87dcd::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..fc2ec5e --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,862 @@ +[ + { + "name": "ezyang/htmlpurifier", + "version": "v4.17.0", + "version_normalized": "4.17.0.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "time": "2023-11-17T15:01:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ] + }, + { + "name": "maennchen/zipstream-php", + "version": "2.1.0", + "version_normalized": "2.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58", + "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58", + "shasum": "" + }, + "require": { + "myclabs/php-enum": "^1.5", + "php": ">= 7.1", + "psr/http-message": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "ext-zip": "*", + "guzzlehttp/guzzle": ">= 6.3", + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": ">= 7.5" + }, + "time": "2020-05-30T13:11:16+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ] + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "time": "2022-12-06T16:21:08+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ] + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "version_normalized": "3.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "time": "2022-12-02T22:17:43+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ] + }, + { + "name": "myclabs/php-enum", + "version": "1.8.4", + "version_normalized": "1.8.4.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/php-enum.git", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/php-enum/zipball/a867478eae49c9f59ece437ae7f9506bfaa27483", + "reference": "a867478eae49c9f59ece437ae7f9506bfaa27483", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + }, + "time": "2022-08-04T09:53:51+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "description": "PHP Enum implementation", + "homepage": "http://github.com/myclabs/php-enum", + "keywords": [ + "enum" + ], + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum", + "type": "tidelift" + } + ] + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.9.1", + "version_normalized": "6.9.1.0", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/039de174cd9c17a8389754d3b877a2ed22743e18", + "reference": "039de174cd9c17a8389754d3b877a2ed22743e18", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "time": "2023-11-25T22:23:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ] + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.25.2", + "version_normalized": "1.25.2.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "a317a09e7def49852400a4b3eca4a4b0790ceeb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/a317a09e7def49852400a4b3eca4a4b0790ceeb5", + "reference": "a317a09e7def49852400a4b3eca4a4b0790ceeb5", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.3 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "dompdf/dompdf": "^1.0 || ^2.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "10.2.4", + "mpdf/mpdf": "8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "time": "2022-09-25T17:21:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ] + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "version_normalized": "1.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2023-09-23T14:17:50+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ] + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "time": "2024-04-15T12:06:14+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ] + }, + { + "name": "psr/http-message", + "version": "1.1", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "time": "2023-04-04T09:50:52+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ] + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-10-23T01:57:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ] + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "version_normalized": "1.29.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2024-01-29T20:11:03+00:00", + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ] + }, + { + "name": "topthink/framework", + "version": "v5.0.25", + "version_normalized": "5.0.25.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "643c58ed1bd22a2823ce5e95b3b68a5075f9087c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/643c58ed1bd22a2823ce5e95b3b68a5075f9087c", + "reference": "643c58ed1bd22a2823ce5e95b3b68a5075f9087c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phpdocumentor/reflection-docblock": "^2.0", + "phploc/phploc": "2.*", + "phpunit/phpunit": "4.8.*", + "sebastian/phpcpd": "2.*" + }, + "time": "2022-10-25T14:59:38+00:00", + "type": "think-framework", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "library/think" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the new thinkphp framework", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ] + }, + { + "name": "topthink/think-installer", + "version": "v1.0.14", + "version_normalized": "1.0.14.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-installer.git", + "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-installer/zipball/eae1740ac264a55c06134b6685dfb9f837d004d1", + "reference": "eae1740ac264a55c06134b6685dfb9f837d004d1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0||^2.0" + }, + "require-dev": { + "composer/composer": "^1.0||^2.0" + }, + "time": "2021-03-25T08:34:02+00:00", + "type": "composer-plugin", + "extra": { + "class": "think\\composer\\Plugin" + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\composer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ] + } +] diff --git a/vendor/ezyang/htmlpurifier/CREDITS b/vendor/ezyang/htmlpurifier/CREDITS new file mode 100644 index 0000000..7921b45 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/CREDITS @@ -0,0 +1,9 @@ + +CREDITS + +Almost everything written by Edward Z. Yang (Ambush Commander). Lots of thanks +to the DevNetwork Community for their help (see docs/ref-devnetwork.html for +more details), Feyd especially (namely IPv6 and optimization). Thanks to RSnake +for letting me package his fantastic XSS cheatsheet for a smoketest. + + vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/LICENSE b/vendor/ezyang/htmlpurifier/LICENSE new file mode 100644 index 0000000..8c88a20 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/README.md b/vendor/ezyang/htmlpurifier/README.md new file mode 100644 index 0000000..e6b7199 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/README.md @@ -0,0 +1,29 @@ +HTML Purifier [![Build Status](https://github.com/ezyang/htmlpurifier/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ezyang/htmlpurifier/actions/workflows/ci.yml) +============= + +HTML Purifier is an HTML filtering solution that uses a unique combination +of robust whitelists and aggressive parsing to ensure that not only are +XSS attacks thwarted, but the resulting HTML is standards compliant. + +HTML Purifier is oriented towards richly formatted documents from +untrusted sources that require CSS and a full tag-set. This library can +be configured to accept a more restrictive set of tags, but it won't be +as efficient as more bare-bones parsers. It will, however, do the job +right, which may be more important. + +Places to go: + +* See INSTALL for a quick installation guide +* See docs/ for developer-oriented documentation, code examples and + an in-depth installation guide. +* See WYSIWYG for information on editors like TinyMCE and FCKeditor + +HTML Purifier can be found on the web at: [http://htmlpurifier.org/](http://htmlpurifier.org/) + +## Installation + +Package available on [Composer](https://packagist.org/packages/ezyang/htmlpurifier). + +If you're using Composer to manage dependencies, you can use + + $ composer require ezyang/htmlpurifier diff --git a/vendor/ezyang/htmlpurifier/VERSION b/vendor/ezyang/htmlpurifier/VERSION new file mode 100644 index 0000000..8643e72 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/VERSION @@ -0,0 +1 @@ +4.17.0 \ No newline at end of file diff --git a/vendor/ezyang/htmlpurifier/composer.json b/vendor/ezyang/htmlpurifier/composer.json new file mode 100644 index 0000000..ed46bd5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/composer.json @@ -0,0 +1,45 @@ +{ + "name": "ezyang/htmlpurifier", + "description": "Standards compliant HTML filter written in PHP", + "type": "library", + "keywords": ["html"], + "homepage": "http://htmlpurifier.org/", + "license": "LGPL-2.1-or-later", + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "autoload": { + "psr-0": { "HTMLPurifier": "library/" }, + "files": ["library/HTMLPurifier.composer.php"], + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-tidy": "Used for pretty-printing HTML" + }, + "config": { + "sort-packages": true + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/ezyang/simpletest.git", + "no-api": true + } + ] +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php new file mode 100644 index 0000000..1960c39 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php @@ -0,0 +1,11 @@ +purify($html, $config); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php new file mode 100644 index 0000000..77ebf2d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php @@ -0,0 +1,235 @@ + $attributes) { + $allowed_elements[$element] = true; + foreach ($attributes as $attribute => $x) { + $allowed_attributes["$element.$attribute"] = true; + } + } + $config->set('HTML.AllowedElements', $allowed_elements); + $config->set('HTML.AllowedAttributes', $allowed_attributes); + if ($allowed_protocols !== null) { + $config->set('URI.AllowedSchemes', $allowed_protocols); + } + $purifier = new HTMLPurifier($config); + return $purifier->purify($string); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php new file mode 100644 index 0000000..39b1b65 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php @@ -0,0 +1,11 @@ +config = HTMLPurifier_Config::create($config); + $this->strategy = new HTMLPurifier_Strategy_Core(); + } + + /** + * Adds a filter to process the output. First come first serve + * + * @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 + ); + $this->filters[] = $filter; + } + + /** + * Filters an HTML snippet/document to be XSS-free and standards-compliant. + * + * @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 string Purified HTML + */ + public function purify($html, $config = null) + { + // :TODO: make the config merge in, instead of replace + $config = $config ? HTMLPurifier_Config::create($config) : $this->config; + + // implementation is partially environment dependant, partially + // configuration dependant + $lexer = HTMLPurifier_Lexer::create($config); + + $context = new HTMLPurifier_Context(); + + // setup HTML generator + $this->generator = new HTMLPurifier_Generator($config, $context); + $context->register('Generator', $this->generator); + + // set up global context variables + if ($config->get('Core.CollectErrors')) { + // may get moved out if other facilities use it + $language_factory = HTMLPurifier_LanguageFactory::instance(); + $language = $language_factory->create($config, $context); + $context->register('Locale', $language); + + $error_collector = new HTMLPurifier_ErrorCollector($context); + $context->register('ErrorCollector', $error_collector); + } + + // setup id_accumulator context, necessary due to the fact that + // AttrValidator can be called from many places + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + + $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); + + // setup filters + $filter_flags = $config->getBatch('Filter'); + $custom_filters = $filter_flags['Custom']; + unset($filter_flags['Custom']); + $filters = array(); + foreach ($filter_flags as $filter => $flag) { + if (!$flag) { + continue; + } + if (strpos($filter, '.') !== false) { + continue; + } + $class = "HTMLPurifier_Filter_$filter"; + $filters[] = new $class; + } + foreach ($custom_filters as $filter) { + // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat + $filters[] = $filter; + } + $filters = array_merge($filters, $this->filters); + // maybe prepare(), but later + + for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) { + $html = $filters[$i]->preFilter($html, $config, $context); + } + + // purified HTML + $html = + $this->generator->generateFromTokens( + // list of tokens + $this->strategy->execute( + // list of un-purified tokens + $lexer->tokenizeHTML( + // un-purified HTML + $html, + $config, + $context + ), + $config, + $context + ) + ); + + for ($i = $filter_size - 1; $i >= 0; $i--) { + $html = $filters[$i]->postFilter($html, $config, $context); + } + + $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); + $this->context =& $context; + return $html; + } + + /** + * Filters an array of HTML snippets + * + * @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 string[] Array of purified HTML + */ + public function purifyArray($array_of_html, $config = null) + { + $context_array = array(); + $array = array(); + foreach($array_of_html as $key=>$value){ + if (is_array($value)) { + $array[$key] = $this->purifyArray($value, $config); + } else { + $array[$key] = $this->purify($value, $config); + } + $context_array[$key] = $this->context; + } + $this->context = $context_array; + return $array; + } + + /** + * 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 + */ + public static function instance($prototype = null) + { + if (!self::$instance || $prototype) { + if ($prototype instanceof HTMLPurifier) { + self::$instance = $prototype; + } elseif ($prototype) { + self::$instance = new HTMLPurifier($prototype); + } else { + self::$instance = new HTMLPurifier(); + } + } + return self::$instance; + } + + /** + * 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) + { + return HTMLPurifier::instance($prototype); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php new file mode 100644 index 0000000..94543f5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php @@ -0,0 +1,229 @@ +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; + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php new file mode 100644 index 0000000..c7b17cf --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php @@ -0,0 +1,148 @@ +doConstruct($attr_types, $modules); + } + + public function doConstruct($attr_types, $modules) + { + // load extensions from the modules + foreach ($modules as $module) { + foreach ($module->attr_collections as $coll_i => $coll) { + if (!isset($this->info[$coll_i])) { + $this->info[$coll_i] = array(); + } + foreach ($coll as $attr_i => $attr) { + 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 + ); + continue; + } + $this->info[$coll_i][$attr_i] = $attr; + } + } + } + // perform internal expansions and inclusions + foreach ($this->info as $name => $attr) { + // merge attribute collections that include others + $this->performInclusions($this->info[$name]); + // replace string identifiers with actual attribute objects + $this->expandIdentifiers($this->info[$name], $attr_types); + } + } + + /** + * Takes a reference to an attribute associative array and performs + * all inclusions specified by the zero index. + * @param array &$attr Reference to attribute array + */ + 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; + } + $seen[$merge[$i]] = true; + // foreach attribute of the inclusion, copy it over + if (!isset($this->info[$merge[$i]])) { + continue; + } + foreach ($this->info[$merge[$i]] as $key => $value) { + if (isset($attr[$key])) { + continue; + } // also catches more inclusions + $attr[$key] = $value; + } + if (isset($this->info[$merge[$i]][0])) { + // recursion + $merge = array_merge($merge, $this->info[$merge[$i]][0]); + } + } + unset($attr[0]); + } + + /** + * Expands all string identifiers in an attribute array by replacing + * them with the appropriate values inside HTMLPurifier_AttrTypes + * @param array &$attr Reference to attribute array + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance + */ + 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 (isset($processed[$def_i])) { + continue; + } + + // determine whether or not attribute is required + if ($required = (strpos($def_i, '*') !== false)) { + // rename the definition + unset($attr[$def_i]); + $def_i = trim($def_i, '*'); + $attr[$def_i] = $def; + } + + $processed[$def_i] = true; + + // if we've already got a literal object, move on + if (is_object($def)) { + // preserve previous required + $attr[$def_i]->required = ($required || $attr[$def_i]->required); + continue; + } + + if ($def === false) { + unset($attr[$def_i]); + continue; + } + + if ($t = $attr_types->get($def)) { + $attr[$def_i] = $t; + $attr[$def_i]->required = $required; + } else { + unset($attr[$def_i]); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php new file mode 100644 index 0000000..739646f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php @@ -0,0 +1,144 @@ + by removing + * leading and trailing whitespace, ignoring line feeds, and replacing + * carriage returns and tabs with spaces. While most useful for HTML + * attributes specified as CDATA, it can also be applied to most CSS + * values. + * + * @note This method is not entirely standards compliant, as trim() removes + * more types of whitespace than specified in the spec. In practice, + * this is rarely a problem, as those extra characters usually have + * already been removed by HTMLPurifier_Encoder. + * + * @warning This processing is inconsistent with XML's whitespace handling + * as specified by section 3.3.3 and referenced XHTML 1.0 section + * 4.7. However, note that we are NOT necessarily + * parsing XML, thus, this behavior may still be correct. We + * assume that newlines have been normalized. + */ + public function parseCDATA($string) + { + $string = trim($string); + $string = str_replace(array("\n", "\t", "\r"), ' ', $string); + return $string; + } + + /** + * Factory method for creating this class from a string. + * @param string $string String construction info + * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $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 + // to clone or instantiate new copies. (Instantiation is safer.) + return $this; + } + + /** + * 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) + { + $p = '\s*(\d+(\.\d+)?([%]?))\s*'; + + if (preg_match('/(rgba|hsla)\(/', $string)) { + return preg_replace('/(rgba|hsla)\('.$p.','.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8,\11)', $string); + } + + return preg_replace('/(rgb|hsl)\('.$p.','.$p.','.$p.'\)/', '\1(\2,\5,\8)', $string); + } + + /** + * Parses a possibly escaped CSS string and returns the "pure" + * version of it. + */ + protected function expandCSSEscape($string) + { + // flexibly parse it + $ret = ''; + for ($i = 0, $c = strlen($string); $i < $c; $i++) { + if ($string[$i] === '\\') { + $i++; + if ($i >= $c) { + $ret .= '\\'; + break; + } + if (ctype_xdigit($string[$i])) { + $code = $string[$i]; + for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { + 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; + } + $ret .= $char; + if ($i < $c && trim($string[$i]) !== '') { + $i--; + } + continue; + } + if ($string[$i] === "\n") { + continue; + } + } + $ret .= $string[$i]; + } + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php new file mode 100644 index 0000000..ad2cb90 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php @@ -0,0 +1,136 @@ +parseCDATA($css); + + $definition = $config->getCSSDefinition(); + $allow_duplicates = $config->get("CSS.AllowDuplicates"); + + + // According to the CSS2.1 spec, the places where a + // non-delimiting semicolon can appear are in strings + // escape sequences. So here is some dumb hack to + // handle quotes. + $len = strlen($css); + $accum = ""; + $declarations = array(); + $quoted = false; + for ($i = 0; $i < $len; $i++) { + $c = strcspn($css, ";'\"", $i); + $accum .= substr($css, $i, $c); + $i += $c; + if ($i == $len) break; + $d = $css[$i]; + if ($quoted) { + $accum .= $d; + if ($d == $quoted) { + $quoted = false; + } + } else { + if ($d == ";") { + $declarations[] = $accum; + $accum = ""; + } else { + $accum .= $d; + $quoted = $d; + } + } + } + if ($accum != "") $declarations[] = $accum; + + $propvalues = array(); + $new_declarations = ''; + + /** + * Name of the current CSS property being validated. + */ + $property = false; + $context->register('CurrentCSSProperty', $property); + + foreach ($declarations as $declaration) { + if (!$declaration) { + continue; + } + if (!strpos($declaration, ':')) { + continue; + } + list($property, $value) = explode(':', $declaration, 2); + $property = trim($property); + $value = trim($value); + $ok = false; + do { + if (isset($definition->info[$property])) { + $ok = true; + break; + } + if (ctype_lower($property)) { + break; + } + $property = strtolower($property); + if (isset($definition->info[$property])) { + $ok = true; + break; + } + } while (0); + if (!$ok) { + continue; + } + // inefficient call, since the validator will do this again + if (strtolower(trim($value)) !== 'inherit') { + // inherit works for everything (but only on the base property) + $result = $definition->info[$property]->validate( + $value, + $config, + $context + ); + } else { + $result = 'inherit'; + } + if ($result === false) { + continue; + } + if ($allow_duplicates) { + $new_declarations .= "$property:$result;"; + } else { + $propvalues[$property] = $result; + } + } + + $context->destroy('CurrentCSSProperty'); + + // procedure does not write the new CSS simultaneously, so it's + // slightly inefficient, but it's the only way of getting rid of + // duplicates. Perhaps config to optimize it, but not now. + + foreach ($propvalues as $prop => $value) { + $new_declarations .= "$prop:$value;"; + } + + return $new_declarations ? $new_declarations : false; + + } + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php new file mode 100644 index 0000000..af2b83d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php @@ -0,0 +1,34 @@ + 1.0) { + $result = '1'; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php new file mode 100644 index 0000000..28c4988 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php @@ -0,0 +1,113 @@ +getCSSDefinition(); + $this->info['background-color'] = $def->info['background-color']; + $this->info['background-image'] = $def->info['background-image']; + $this->info['background-repeat'] = $def->info['background-repeat']; + $this->info['background-attachment'] = $def->info['background-attachment']; + $this->info['background-position'] = $def->info['background-position']; + $this->info['background-size'] = $def->info['background-size']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // munge rgb() decl if necessary + $string = $this->mungeRgb($string); + + // assumes URI doesn't have spaces in it + $bits = explode(' ', $string); // bits to process + + $caught = array(); + $caught['color'] = false; + $caught['image'] = false; + $caught['repeat'] = false; + $caught['attachment'] = false; + $caught['position'] = false; + $caught['size'] = false; + + $i = 0; // number of catches + + foreach ($bits as $bit) { + if ($bit === '') { + continue; + } + foreach ($caught as $key => $status) { + if ($key != 'position') { + if ($status !== false) { + continue; + } + $r = $this->info['background-' . $key]->validate($bit, $config, $context); + } else { + $r = $bit; + } + if ($r === false) { + continue; + } + if ($key == 'position') { + if ($caught[$key] === false) { + $caught[$key] = ''; + } + $caught[$key] .= $r . ' '; + } else { + $caught[$key] = $r; + } + $i++; + break; + } + } + + if (!$i) { + return false; + } + if ($caught['position'] !== false) { + $caught['position'] = $this->info['background-position']-> + validate($caught['position'], $config, $context); + } + + $ret = array(); + foreach ($caught as $value) { + if ($value === false) { + continue; + } + $ret[] = $value; + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php new file mode 100644 index 0000000..4580ef5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php @@ -0,0 +1,157 @@ + | | left | center | right + ] + [ + | | top | center | bottom + ]? + ] | + [ // this signifies that the vertical and horizontal adjectives + // can be arbitrarily ordered, however, there can only be two, + // one of each, or none at all + [ + left | center | right + ] || + [ + top | center | bottom + ] + ] + top, left = 0% + center, (none) = 50% + bottom, right = 100% +*/ + +/* QuirksMode says: + keyword + length/percentage must be ordered correctly, as per W3C + + Internet Explorer and Opera, however, support arbitrary ordering. We + should fix it up. + + Minor issue though, not strictly necessary. +*/ + +// control freaks may appreciate the ability to convert these to +// percentages or something, but it's not necessary + +/** + * Validates the value of background-position. + */ +class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef +{ + + /** + * @type HTMLPurifier_AttrDef_CSS_Length + */ + protected $length; + + /** + * @type HTMLPurifier_AttrDef_CSS_Percentage + */ + protected $percentage; + + public function __construct() + { + $this->length = new HTMLPurifier_AttrDef_CSS_Length(); + $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage(); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + $bits = explode(' ', $string); + + $keywords = array(); + $keywords['h'] = false; // left, right + $keywords['v'] = false; // top, bottom + $keywords['ch'] = false; // center (first word) + $keywords['cv'] = false; // center (second word) + $measures = array(); + + $i = 0; + + $lookup = array( + 'top' => 'v', + 'bottom' => 'v', + 'left' => 'h', + 'right' => 'h', + 'center' => 'c' + ); + + foreach ($bits as $bit) { + if ($bit === '') { + continue; + } + + // test for keyword + $lbit = ctype_lower($bit) ? $bit : strtolower($bit); + if (isset($lookup[$lbit])) { + $status = $lookup[$lbit]; + if ($status == 'c') { + if ($i == 0) { + $status = 'ch'; + } else { + $status = 'cv'; + } + } + $keywords[$status] = $lbit; + $i++; + } + + // test for length + $r = $this->length->validate($bit, $config, $context); + if ($r !== false) { + $measures[] = $r; + $i++; + } + + // test for percentage + $r = $this->percentage->validate($bit, $config, $context); + if ($r !== false) { + $measures[] = $r; + $i++; + } + } + + if (!$i) { + return false; + } // no valid values were caught + + $ret = array(); + + // first keyword + if ($keywords['h']) { + $ret[] = $keywords['h']; + } elseif ($keywords['ch']) { + $ret[] = $keywords['ch']; + $keywords['cv'] = false; // prevent re-use: center = center center + } elseif (count($measures)) { + $ret[] = array_shift($measures); + } + + if ($keywords['v']) { + $ret[] = $keywords['v']; + } elseif ($keywords['cv']) { + $ret[] = $keywords['cv']; + } elseif (count($measures)) { + $ret[] = array_shift($measures); + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php new file mode 100644 index 0000000..16243ba --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php @@ -0,0 +1,56 @@ +getCSSDefinition(); + $this->info['border-width'] = $def->info['border-width']; + $this->info['border-style'] = $def->info['border-style']; + $this->info['border-top-color'] = $def->info['border-top-color']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + $string = $this->mungeRgb($string); + $bits = explode(' ', $string); + $done = array(); // segments we've finished + $ret = ''; // return value + foreach ($bits as $bit) { + foreach ($this->info as $propname => $validator) { + if (isset($done[$propname])) { + continue; + } + $r = $validator->validate($bit, $config, $context); + if ($r !== false) { + $ret .= $r . ' '; + $done[$propname] = true; + break; + } + } + } + return rtrim($ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php new file mode 100644 index 0000000..d7287a0 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php @@ -0,0 +1,161 @@ +alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + } + + /** + * @param string $color + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($color, $config, $context) + { + static $colors = null; + if ($colors === null) { + $colors = $config->get('Core.ColorKeywords'); + } + + $color = trim($color); + if ($color === '') { + return false; + } + + $lower = strtolower($color); + if (isset($colors[$lower])) { + return $colors[$lower]; + } + + if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) { + $length = strlen($color); + if (strpos($color, ')') !== $length - 1) { + return false; + } + + // get used function : rgb, rgba, hsl or hsla + $function = $matches[1]; + + $parameters_size = 3; + $alpha_channel = false; + if (substr($function, -1) === 'a') { + $parameters_size = 4; + $alpha_channel = true; + } + + /* + * Allowed types for values : + * parameter_position => [type => max_value] + */ + $allowed_types = array( + 1 => array('percentage' => 100, 'integer' => 255), + 2 => array('percentage' => 100, 'integer' => 255), + 3 => array('percentage' => 100, 'integer' => 255), + ); + $allow_different_types = false; + + if (strpos($function, 'hsl') !== false) { + $allowed_types = array( + 1 => array('integer' => 360), + 2 => array('percentage' => 100), + 3 => array('percentage' => 100), + ); + $allow_different_types = true; + } + + $values = trim(str_replace($function, '', $color), ' ()'); + + $parts = explode(',', $values); + if (count($parts) !== $parameters_size) { + return false; + } + + $type = false; + $new_parts = array(); + $i = 0; + + foreach ($parts as $part) { + $i++; + $part = trim($part); + + if ($part === '') { + return false; + } + + // different check for alpha channel + if ($alpha_channel === true && $i === count($parts)) { + $result = $this->alpha->validate($part, $config, $context); + + if ($result === false) { + return false; + } + + $new_parts[] = (string)$result; + continue; + } + + if (substr($part, -1) === '%') { + $current_type = 'percentage'; + } else { + $current_type = 'integer'; + } + + if (!array_key_exists($current_type, $allowed_types[$i])) { + return false; + } + + if (!$type) { + $type = $current_type; + } + + if ($allow_different_types === false && $type != $current_type) { + return false; + } + + $max_value = $allowed_types[$i][$current_type]; + + if ($current_type == 'integer') { + // Return value between range 0 -> $max_value + $new_parts[] = (int)max(min($part, $max_value), 0); + } elseif ($current_type == 'percentage') { + $new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%'; + } + } + + $new_values = implode(',', $new_parts); + + $color = $function . '(' . $new_values . ')'; + } else { + // hexadecimal handling + if ($color[0] === '#') { + $hex = substr($color, 1); + } else { + $hex = $color; + $color = '#' . $color; + } + $length = strlen($hex); + if ($length !== 3 && $length !== 6) { + return false; + } + if (!ctype_xdigit($hex)) { + return false; + } + } + return $color; + } + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php new file mode 100644 index 0000000..9c17505 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php @@ -0,0 +1,48 @@ +defs = $defs; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + foreach ($this->defs as $i => $def) { + $result = $this->defs[$i]->validate($string, $config, $context); + if ($result !== false) { + return $result; + } + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php new file mode 100644 index 0000000..9d77cc9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php @@ -0,0 +1,44 @@ +def = $def; + $this->element = $element; + } + + /** + * Checks if CurrentToken is set and equal to $this->element + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $token = $context->get('CurrentToken', true); + if ($token && $token->name == $this->element) { + return false; + } + return $this->def->validate($string, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php new file mode 100644 index 0000000..bde4c33 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php @@ -0,0 +1,77 @@ +intValidator = new HTMLPurifier_AttrDef_Integer(); + } + + /** + * @param string $value + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($value, $config, $context) + { + $value = $this->parseCDATA($value); + if ($value === 'none') { + return $value; + } + // if we looped this we could support multiple filters + $function_length = strcspn($value, '('); + $function = trim(substr($value, 0, $function_length)); + if ($function !== 'alpha' && + $function !== 'Alpha' && + $function !== 'progid:DXImageTransform.Microsoft.Alpha' + ) { + return false; + } + $cursor = $function_length + 1; + $parameters_length = strcspn($value, ')', $cursor); + $parameters = substr($value, $cursor, $parameters_length); + $params = explode(',', $parameters); + $ret_params = array(); + $lookup = array(); + foreach ($params as $param) { + list($key, $value) = explode('=', $param); + $key = trim($key); + $value = trim($value); + if (isset($lookup[$key])) { + continue; + } + if ($key !== 'opacity') { + continue; + } + $value = $this->intValidator->validate($value, $config, $context); + if ($value === false) { + continue; + } + $int = (int)$value; + if ($int > 100) { + $value = '100'; + } + if ($int < 0) { + $value = '0'; + } + $ret_params[] = "$key=$value"; + $lookup[$key] = true; + } + $ret_parameters = implode(',', $ret_params); + $ret_function = "$function($ret_parameters)"; + return $ret_function; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php new file mode 100644 index 0000000..579b97e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php @@ -0,0 +1,176 @@ +getCSSDefinition(); + $this->info['font-style'] = $def->info['font-style']; + $this->info['font-variant'] = $def->info['font-variant']; + $this->info['font-weight'] = $def->info['font-weight']; + $this->info['font-size'] = $def->info['font-size']; + $this->info['line-height'] = $def->info['line-height']; + $this->info['font-family'] = $def->info['font-family']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $system_fonts = array( + 'caption' => true, + 'icon' => true, + 'menu' => true, + 'message-box' => true, + 'small-caption' => true, + 'status-bar' => true + ); + + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // check if it's one of the keywords + $lowercase_string = strtolower($string); + if (isset($system_fonts[$lowercase_string])) { + return $lowercase_string; + } + + $bits = explode(' ', $string); // bits to process + $stage = 0; // this indicates what we're looking for + $caught = array(); // which stage 0 properties have we caught? + $stage_1 = array('font-style', 'font-variant', 'font-weight'); + $final = ''; // output + + for ($i = 0, $size = count($bits); $i < $size; $i++) { + if ($bits[$i] === '') { + continue; + } + switch ($stage) { + case 0: // attempting to catch font-style, font-variant or font-weight + foreach ($stage_1 as $validator_name) { + if (isset($caught[$validator_name])) { + continue; + } + $r = $this->info[$validator_name]->validate( + $bits[$i], + $config, + $context + ); + if ($r !== false) { + $final .= $r . ' '; + $caught[$validator_name] = true; + break; + } + } + // all three caught, continue on + if (count($caught) >= 3) { + $stage = 1; + } + if ($r !== false) { + break; + } + case 1: // attempting to catch font-size and perhaps line-height + $found_slash = false; + if (strpos($bits[$i], '/') !== false) { + list($font_size, $line_height) = + explode('/', $bits[$i]); + if ($line_height === '') { + // ooh, there's a space after the slash! + $line_height = false; + $found_slash = true; + } + } else { + $font_size = $bits[$i]; + $line_height = false; + } + $r = $this->info['font-size']->validate( + $font_size, + $config, + $context + ); + if ($r !== false) { + $final .= $r; + // attempt to catch line-height + if ($line_height === false) { + // we need to scroll forward + for ($j = $i + 1; $j < $size; $j++) { + if ($bits[$j] === '') { + continue; + } + if ($bits[$j] === '/') { + if ($found_slash) { + return false; + } else { + $found_slash = true; + continue; + } + } + $line_height = $bits[$j]; + break; + } + } else { + // slash already found + $found_slash = true; + $j = $i; + } + if ($found_slash) { + $i = $j; + $r = $this->info['line-height']->validate( + $line_height, + $config, + $context + ); + if ($r !== false) { + $final .= '/' . $r; + } + } + $final .= ' '; + $stage = 2; + break; + } + return false; + case 2: // attempting to catch font-family + $font_family = + implode(' ', array_slice($bits, $i, $size - $i)); + $r = $this->info['font-family']->validate( + $font_family, + $config, + $context + ); + if ($r !== false) { + $final .= $r . ' '; + // processing completed successfully + return rtrim($final); + } + return false; + } + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php new file mode 100644 index 0000000..f1ff116 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php @@ -0,0 +1,217 @@ +mask = array_reduce($c, function ($carry, $value) { + return $carry . $value; + }, '_- '); + + /* + PHP's internal strcspn implementation is + O(length of string * length of mask), making it inefficient + for large masks. However, it's still faster than + preg_match 8) + for (p = s1;;) { + spanp = s2; + do { + if (*spanp == c || p == s1_end) { + return p - s1; + } + } while (spanp++ < (s2_end - 1)); + c = *++p; + } + */ + // possible optimization: invert the mask. + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $generic_names = array( + 'serif' => true, + 'sans-serif' => true, + 'monospace' => true, + 'fantasy' => true, + 'cursive' => true + ); + $allowed_fonts = $config->get('CSS.AllowedFonts'); + + // assume that no font names contain commas in them + $fonts = explode(',', $string); + $final = ''; + foreach ($fonts as $font) { + $font = trim($font); + if ($font === '') { + continue; + } + // match a generic name + if (isset($generic_names[$font])) { + if ($allowed_fonts === null || isset($allowed_fonts[$font])) { + $final .= $font . ', '; + } + continue; + } + // match a quoted name + if ($font[0] === '"' || $font[0] === "'") { + $length = strlen($font); + if ($length <= 2) { + continue; + } + $quote = $font[0]; + if ($font[$length - 1] !== $quote) { + continue; + } + $font = substr($font, 1, $length - 2); + } + + $font = $this->expandCSSEscape($font); + + // $font is a pure representation of the font name + + if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) { + continue; + } + + if (ctype_alnum($font) && $font !== '') { + // very simple font, allow it in unharmed + $final .= $font . ', '; + continue; + } + + // bugger out on whitespace. form feed (0C) really + // shouldn't show up regardless + $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font); + + // Here, there are various classes of characters which need + // to be treated differently: + // - Alphanumeric characters are essentially safe. We + // handled these above. + // - Spaces require quoting, though most parsers will do + // the right thing if there aren't any characters that + // can be misinterpreted + // - Dashes rarely occur, but they fairly unproblematic + // for parsing/rendering purposes. + // The above characters cover the majority of Western font + // names. + // - Arbitrary Unicode characters not in ASCII. Because + // most parsers give little thought to Unicode, treatment + // of these codepoints is basically uniform, even for + // punctuation-like codepoints. These characters can + // show up in non-Western pages and are supported by most + // major browsers, for example: "MS 明朝" is a + // legitimate font-name + // . See + // the CSS3 spec for more examples: + // + // You can see live samples of these on the Internet: + // + // However, most of these fonts have ASCII equivalents: + // for example, 'MS Mincho', and it's considered + // professional to use ASCII font names instead of + // Unicode font names. Thanks Takeshi Terada for + // providing this information. + // The following characters, to my knowledge, have not been + // used to name font names. + // - Single quote. While theoretically you might find a + // font name that has a single quote in its name (serving + // as an apostrophe, e.g. Dave's Scribble), I haven't + // been able to find any actual examples of this. + // Internet Explorer's cssText translation (which I + // believe is invoked by innerHTML) normalizes any + // quoting to single quotes, and fails to escape single + // quotes. (Note that this is not IE's behavior for all + // CSS properties, just some sort of special casing for + // font-family). So a single quote *cannot* be used + // safely in the font-family context if there will be an + // innerHTML/cssText translation. Note that Firefox 3.x + // does this too. + // - Double quote. In IE, these get normalized to + // single-quotes, no matter what the encoding. (Fun + // fact, in IE8, the 'content' CSS property gained + // support, where they special cased to preserve encoded + // double quotes, but still translate unadorned double + // quotes into single quotes.) So, because their + // fixpoint behavior is identical to single quotes, they + // cannot be allowed either. Firefox 3.x displays + // single-quote style behavior. + // - Backslashes are reduced by one (so \\ -> \) every + // iteration, so they cannot be used safely. This shows + // up in IE7, IE8 and FF3 + // - Semicolons, commas and backticks are handled properly. + // - The rest of the ASCII punctuation is handled properly. + // We haven't checked what browsers do to unadorned + // versions, but this is not important as long as the + // browser doesn't /remove/ surrounding quotes (as IE does + // for HTML). + // + // With these results in hand, we conclude that there are + // various levels of safety: + // - Paranoid: alphanumeric, spaces and dashes(?) + // - International: Paranoid + non-ASCII Unicode + // - Edgy: Everything except quotes, backslashes + // - NoJS: Standards compliance, e.g. sod IE. Note that + // with some judicious character escaping (since certain + // types of escaping doesn't work) this is theoretically + // OK as long as innerHTML/cssText is not called. + // We believe that international is a reasonable default + // (that we will implement now), and once we do more + // extensive research, we may feel comfortable with dropping + // it down to edgy. + + // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of + // str(c)spn assumes that the string was already well formed + // Unicode (which of course it is). + if (strspn($font, $this->mask) !== strlen($font)) { + continue; + } + + // Historical: + // In the absence of innerHTML/cssText, these ugly + // transforms don't pose a security risk (as \\ and \" + // might--these escapes are not supported by most browsers). + // We could try to be clever and use single-quote wrapping + // when there is a double quote present, but I have choosen + // not to implement that. (NOTE: you can reduce the amount + // of escapes by one depending on what quoting style you use) + // $font = str_replace('\\', '\\5C ', $font); + // $font = str_replace('"', '\\22 ', $font); + // $font = str_replace("'", '\\27 ', $font); + + // font possibly with spaces, requires quoting + $final .= "'$font', "; + } + $final = rtrim($final, ', '); + if ($final === '') { + return false; + } + return $final; + } + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php new file mode 100644 index 0000000..973002c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php @@ -0,0 +1,32 @@ +def = $def; + $this->allow = $allow; + } + + /** + * Intercepts and removes !important if necessary + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // test for ! and important tokens + $string = trim($string); + $is_important = false; + // :TODO: optimization: test directly for !important and ! important + if (strlen($string) >= 9 && substr($string, -9) === 'important') { + $temp = rtrim(substr($string, 0, -9)); + // use a temp, because we might want to restore important + if (strlen($temp) >= 1 && substr($temp, -1) === '!') { + $string = rtrim(substr($temp, 0, -1)); + $is_important = true; + } + } + $string = $this->def->validate($string, $config, $context); + if ($this->allow && $is_important) { + $string .= ' !important'; + } + return $string; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php new file mode 100644 index 0000000..f12453a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php @@ -0,0 +1,77 @@ +min = $min !== null ? HTMLPurifier_Length::make($min) : null; + $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + + // Optimizations + if ($string === '') { + return false; + } + if ($string === '0') { + return '0'; + } + if (strlen($string) === 1) { + return false; + } + + $length = HTMLPurifier_Length::make($string); + if (!$length->isValid()) { + return false; + } + + if ($this->min) { + $c = $length->compareTo($this->min); + if ($c === false) { + return false; + } + if ($c < 0) { + return false; + } + } + if ($this->max) { + $c = $length->compareTo($this->max); + if ($c === false) { + return false; + } + if ($c > 0) { + return false; + } + } + return $length->toString(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php new file mode 100644 index 0000000..e74d426 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php @@ -0,0 +1,112 @@ +getCSSDefinition(); + $this->info['list-style-type'] = $def->info['list-style-type']; + $this->info['list-style-position'] = $def->info['list-style-position']; + $this->info['list-style-image'] = $def->info['list-style-image']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // assumes URI doesn't have spaces in it + $bits = explode(' ', strtolower($string)); // bits to process + + $caught = array(); + $caught['type'] = false; + $caught['position'] = false; + $caught['image'] = false; + + $i = 0; // number of catches + $none = false; + + foreach ($bits as $bit) { + if ($i >= 3) { + return; + } // optimization bit + if ($bit === '') { + continue; + } + foreach ($caught as $key => $status) { + if ($status !== false) { + continue; + } + $r = $this->info['list-style-' . $key]->validate($bit, $config, $context); + if ($r === false) { + continue; + } + if ($r === 'none') { + if ($none) { + continue; + } else { + $none = true; + } + if ($key == 'image') { + continue; + } + } + $caught[$key] = $r; + $i++; + break; + } + } + + if (!$i) { + return false; + } + + $ret = array(); + + // construct type + if ($caught['type']) { + $ret[] = $caught['type']; + } + + // construct image + if ($caught['image']) { + $ret[] = $caught['image']; + } + + // construct position + if ($caught['position']) { + $ret[] = $caught['position']; + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php new file mode 100644 index 0000000..e707f87 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php @@ -0,0 +1,71 @@ +single = $single; + $this->max = $max; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->mungeRgb($this->parseCDATA($string)); + if ($string === '') { + return false; + } + $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n + $length = count($parts); + $final = ''; + for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) { + if (ctype_space($parts[$i])) { + continue; + } + $result = $this->single->validate($parts[$i], $config, $context); + if ($result !== false) { + $final .= $result . ' '; + $num++; + } + } + if ($final === '') { + return false; + } + return rtrim($final); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php new file mode 100644 index 0000000..ef49d20 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php @@ -0,0 +1,90 @@ +non_negative = $non_negative; + } + + /** + * @param string $number + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string|bool + * @warning Some contexts do not pass $config, $context. These + * variables should not be used without checking HTMLPurifier_Length + */ + public function validate($number, $config, $context) + { + $number = $this->parseCDATA($number); + + if ($number === '') { + return false; + } + if ($number === '0') { + return '0'; + } + + $sign = ''; + switch ($number[0]) { + case '-': + if ($this->non_negative) { + return false; + } + $sign = '-'; + case '+': + $number = substr($number, 1); + } + + if (ctype_digit($number)) { + $number = ltrim($number, '0'); + return $number ? $sign . $number : '0'; + } + + // Period is the only non-numeric character allowed + if (strpos($number, '.') === false) { + return false; + } + + list($left, $right) = explode('.', $number, 2); + + if ($left === '' && $right === '') { + return false; + } + if ($left !== '' && !ctype_digit($left)) { + return false; + } + + // Remove leading zeros until positive number or a zero stays left + if (ltrim($left, '0') != '') { + $left = ltrim($left, '0'); + } else { + $left = '0'; + } + + $right = rtrim($right, '0'); + + if ($right === '') { + return $left ? $sign . $left : '0'; + } elseif (!ctype_digit($right)) { + return false; + } + return $sign . $left . '.' . $right; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php new file mode 100644 index 0000000..f0f25c5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php @@ -0,0 +1,54 @@ +number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + + if ($string === '') { + return false; + } + $length = strlen($string); + if ($length === 1) { + return false; + } + if ($string[$length - 1] !== '%') { + return false; + } + + $number = substr($string, 0, $length - 1); + $number = $this->number_def->validate($number, $config, $context); + + if ($number === false) { + return false; + } + return "$number%"; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php new file mode 100644 index 0000000..5fd4b7f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php @@ -0,0 +1,46 @@ + true, + 'overline' => true, + 'underline' => true, + ); + + $string = strtolower($this->parseCDATA($string)); + + if ($string === 'none') { + return $string; + } + + $parts = explode(' ', $string); + $final = ''; + foreach ($parts as $part) { + if (isset($allowed_values[$part])) { + $final .= $part . ' '; + } + } + $final = rtrim($final); + if ($final === '') { + return false; + } + return $final; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php new file mode 100644 index 0000000..6617aca --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php @@ -0,0 +1,77 @@ +parseCDATA($uri_string); + if (strpos($uri_string, 'url(') !== 0) { + return false; + } + $uri_string = substr($uri_string, 4); + if (strlen($uri_string) == 0) { + return false; + } + $new_length = strlen($uri_string) - 1; + if ($uri_string[$new_length] != ')') { + return false; + } + $uri = trim(substr($uri_string, 0, $new_length)); + + if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) { + $quote = $uri[0]; + $new_length = strlen($uri) - 1; + if ($uri[$new_length] !== $quote) { + return false; + } + $uri = substr($uri, 1, $new_length - 1); + } + + $uri = $this->expandCSSEscape($uri); + + $result = parent::validate($uri, $config, $context); + + if ($result === false) { + return false; + } + + // extra sanity check; should have been done by URI + $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result); + + // suspicious characters are ()'; we're going to percent encode + // them for safety. + $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result); + + // there's an extra bug where ampersands lose their escaping on + // an innerHTML cycle, so a very unlucky query parameter could + // then change the meaning of the URL. Unfortunately, there's + // not much we can do about that... + return "url(\"$result\")"; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php new file mode 100644 index 0000000..6698a00 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php @@ -0,0 +1,44 @@ +clone = $clone; + } + + /** + * @param string $v + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($v, $config, $context) + { + return $this->clone->validate($v, $config, $context); + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef + */ + public function make($string) + { + return clone $this->clone; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php new file mode 100644 index 0000000..8abda7f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php @@ -0,0 +1,73 @@ +valid_values = array_flip($valid_values); + $this->case_sensitive = $case_sensitive; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if (!$this->case_sensitive) { + // we may want to do full case-insensitive libraries + $string = ctype_lower($string) ? $string : strtolower($string); + } + $result = isset($this->valid_values[$string]); + + return $result ? $string : false; + } + + /** + * @param string $string In form of comma-delimited list of case-insensitive + * valid values. Example: "foo,bar,baz". Prepend "s:" to make + * case sensitive + * @return HTMLPurifier_AttrDef_Enum + */ + public function make($string) + { + if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') { + $string = substr($string, 2); + $sensitive = true; + } else { + $sensitive = false; + } + $values = explode(',', $string); + return new HTMLPurifier_AttrDef_Enum($values, $sensitive); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php new file mode 100644 index 0000000..be3bbc8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php @@ -0,0 +1,48 @@ +name = $name; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + return $this->name; + } + + /** + * @param string $string Name of attribute + * @return HTMLPurifier_AttrDef_HTML_Bool + */ + public function make($string) + { + return new HTMLPurifier_AttrDef_HTML_Bool($string); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php new file mode 100644 index 0000000..d501348 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php @@ -0,0 +1,48 @@ +getDefinition('HTML')->doctype->name; + if ($name == "XHTML 1.1" || $name == "XHTML 2.0") { + return parent::split($string, $config, $context); + } else { + return preg_split('/\s+/', $string); + } + } + + /** + * @param array $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function filter($tokens, $config, $context) + { + $allowed = $config->get('Attr.AllowedClasses'); + $forbidden = $config->get('Attr.ForbiddenClasses'); + $ret = array(); + foreach ($tokens as $token) { + if (($allowed === null || isset($allowed[$token])) && + !isset($forbidden[$token]) && + // We need this O(n) check because of PHP's array + // implementation that casts -0 to 0. + !in_array($token, $ret, true) + ) { + $ret[] = $token; + } + } + return $ret; + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php new file mode 100644 index 0000000..946ebb7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php @@ -0,0 +1,51 @@ +get('Core.ColorKeywords'); + } + + $string = trim($string); + + if (empty($string)) { + return false; + } + $lower = strtolower($string); + if (isset($colors[$lower])) { + return $colors[$lower]; + } + if ($string[0] === '#') { + $hex = substr($string, 1); + } else { + $hex = $string; + } + + $length = strlen($hex); + if ($length !== 3 && $length !== 6) { + return false; + } + if (!ctype_xdigit($hex)) { + return false; + } + if ($length === 3) { + $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; + } + return "#$hex"; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ContentEditable.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ContentEditable.php new file mode 100644 index 0000000..5b03d3e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ContentEditable.php @@ -0,0 +1,16 @@ +get('HTML.Trusted')) { + $allowed = array('', 'true', 'false'); + } + + $enum = new HTMLPurifier_AttrDef_Enum($allowed); + + return $enum->validate($string, $config, $context); + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php new file mode 100644 index 0000000..d79ba12 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php @@ -0,0 +1,38 @@ +valid_values === false) { + $this->valid_values = $config->get('Attr.AllowedFrameTargets'); + } + return parent::validate($string, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php new file mode 100644 index 0000000..4ba4561 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php @@ -0,0 +1,113 @@ +selector = $selector; + } + + /** + * @param string $id + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($id, $config, $context) + { + if (!$this->selector && !$config->get('Attr.EnableID')) { + return false; + } + + $id = trim($id); // trim it first + + if ($id === '') { + return false; + } + + $prefix = $config->get('Attr.IDPrefix'); + if ($prefix !== '') { + $prefix .= $config->get('Attr.IDPrefixLocal'); + // prevent re-appending the prefix + if (strpos($id, $prefix) !== 0) { + $id = $prefix . $id; + } + } elseif ($config->get('Attr.IDPrefixLocal') !== '') { + trigger_error( + '%Attr.IDPrefixLocal cannot be used unless ' . + '%Attr.IDPrefix is set', + E_USER_WARNING + ); + } + + if (!$this->selector) { + $id_accumulator =& $context->get('IDAccumulator'); + if (isset($id_accumulator->ids[$id])) { + return false; + } + } + + // we purposely avoid using regex, hopefully this is faster + + if ($config->get('Attr.ID.HTML5') === true) { + if (preg_match('/[\t\n\x0b\x0c ]/', $id)) { + return false; + } + } else { + if (ctype_alpha($id)) { + // OK + } else { + if (!ctype_alpha(@$id[0])) { + return false; + } + // primitive style of regexps, I suppose + $trim = trim( + $id, + 'A..Za..z0..9:-._' + ); + if ($trim !== '') { + return false; + } + } + } + + $regexp = $config->get('Attr.IDBlacklistRegexp'); + if ($regexp && preg_match($regexp, $id)) { + return false; + } + + if (!$this->selector) { + $id_accumulator->add($id); + } + + // if no change was made to the ID, return the result + // else, return the new id if stripping whitespace made it + // valid, or return false. + return $id; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php new file mode 100644 index 0000000..1c4006f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php @@ -0,0 +1,56 @@ + 100) { + return '100%'; + } + return ((string)$points) . '%'; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php new file mode 100644 index 0000000..63fa04c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php @@ -0,0 +1,72 @@ + 'AllowedRel', + 'rev' => 'AllowedRev' + ); + if (!isset($configLookup[$name])) { + trigger_error( + 'Unrecognized attribute name for link ' . + 'relationship.', + E_USER_ERROR + ); + return; + } + $this->name = $configLookup[$name]; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $allowed = $config->get('Attr.' . $this->name); + if (empty($allowed)) { + return false; + } + + $string = $this->parseCDATA($string); + $parts = explode(' ', $string); + + // lookup to prevent duplicates + $ret_lookup = array(); + foreach ($parts as $part) { + $part = strtolower(trim($part)); + if (!isset($allowed[$part])) { + continue; + } + $ret_lookup[$part] = true; + } + + if (empty($ret_lookup)) { + return false; + } + $string = implode(' ', array_keys($ret_lookup)); + return $string; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php new file mode 100644 index 0000000..bbb20f2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php @@ -0,0 +1,60 @@ +split($string, $config, $context); + $tokens = $this->filter($tokens, $config, $context); + if (empty($tokens)) { + return false; + } + return implode(' ', $tokens); + } + + /** + * Splits a space separated list of tokens into its constituent parts. + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function split($string, $config, $context) + { + // OPTIMIZABLE! + // do the preg_match, capture all subpatterns for reformulation + + // we don't support U+00A1 and up codepoints or + // escaping because I don't know how to do that with regexps + // and plus it would complicate optimization efforts (you never + // see that anyway). + $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start + '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' . + '(?:(?=\s)|\z)/'; // look ahead for space or string end + preg_match_all($pattern, $string, $matches); + return $matches[1]; + } + + /** + * Template method for removing certain tokens based on arbitrary criteria. + * @note If we wanted to be really functional, we'd do an array_filter + * with a callback. But... we're not. + * @param array $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function filter($tokens, $config, $context) + { + return $tokens; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php new file mode 100644 index 0000000..a1d019e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php @@ -0,0 +1,76 @@ +max = $max; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if ($string === '0') { + return $string; + } + if ($string === '') { + return false; + } + $length = strlen($string); + if (substr($string, $length - 2) == 'px') { + $string = substr($string, 0, $length - 2); + } + if (!is_numeric($string)) { + return false; + } + $int = (int)$string; + + if ($int < 0) { + return '0'; + } + + // upper-bound value, extremely high values can + // crash operating systems, see + // WARNING, above link WILL crash you if you're using Windows + + if ($this->max !== null && $int > $this->max) { + return (string)$this->max; + } + return (string)$int; + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef + */ + public function make($string) + { + if ($string === '') { + $max = null; + } else { + $max = (int)$string; + } + $class = get_class($this); + return new $class($max); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php new file mode 100644 index 0000000..400e707 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php @@ -0,0 +1,91 @@ +negative = $negative; + $this->zero = $zero; + $this->positive = $positive; + } + + /** + * @param string $integer + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($integer, $config, $context) + { + $integer = $this->parseCDATA($integer); + if ($integer === '') { + return false; + } + + // we could possibly simply typecast it to integer, but there are + // certain fringe cases that must not return an integer. + + // clip leading sign + if ($this->negative && $integer[0] === '-') { + $digits = substr($integer, 1); + if ($digits === '0') { + $integer = '0'; + } // rm minus sign for zero + } elseif ($this->positive && $integer[0] === '+') { + $digits = $integer = substr($integer, 1); // rm unnecessary plus + } else { + $digits = $integer; + } + + // test if it's numeric + if (!ctype_digit($digits)) { + return false; + } + + // perform scope tests + if (!$this->zero && $integer == 0) { + return false; + } + if (!$this->positive && $integer > 0) { + return false; + } + if (!$this->negative && $integer < 0) { + return false; + } + + return $integer; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php new file mode 100644 index 0000000..2a55cea --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php @@ -0,0 +1,86 @@ + 8 || !ctype_alnum($subtags[1])) { + return $new_string; + } + if (!ctype_lower($subtags[1])) { + $subtags[1] = strtolower($subtags[1]); + } + + $new_string .= '-' . $subtags[1]; + if ($num_subtags == 2) { + return $new_string; + } + + // process all other subtags, index 2 and up + for ($i = 2; $i < $num_subtags; $i++) { + $length = strlen($subtags[$i]); + if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) { + return $new_string; + } + if (!ctype_lower($subtags[$i])) { + $subtags[$i] = strtolower($subtags[$i]); + } + $new_string .= '-' . $subtags[$i]; + } + return $new_string; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php new file mode 100644 index 0000000..c7eb319 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php @@ -0,0 +1,53 @@ +tag = $tag; + $this->withTag = $with_tag; + $this->withoutTag = $without_tag; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $token = $context->get('CurrentToken', true); + if (!$token || $token->name !== $this->tag) { + return $this->withoutTag->validate($string, $config, $context); + } else { + return $this->withTag->validate($string, $config, $context); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php new file mode 100644 index 0000000..4553a4e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php @@ -0,0 +1,21 @@ +parseCDATA($string); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php new file mode 100644 index 0000000..c1cd897 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php @@ -0,0 +1,111 @@ +parser = new HTMLPurifier_URIParser(); + $this->embedsResource = (bool)$embeds_resource; + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef_URI + */ + public function make($string) + { + $embeds = ($string === 'embedded'); + return new HTMLPurifier_AttrDef_URI($embeds); + } + + /** + * @param string $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($uri, $config, $context) + { + if ($config->get('URI.Disable')) { + return false; + } + + $uri = $this->parseCDATA($uri); + + // parse the URI + $uri = $this->parser->parse($uri); + if ($uri === false) { + return false; + } + + // add embedded flag to context for validators + $context->register('EmbeddedURI', $this->embedsResource); + + $ok = false; + do { + + // generic validation + $result = $uri->validate($config, $context); + if (!$result) { + break; + } + + // chained filtering + $uri_def = $config->getDefinition('URI'); + $result = $uri_def->filter($uri, $config, $context); + if (!$result) { + break; + } + + // scheme-specific validation + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + break; + } + if ($this->embedsResource && !$scheme_obj->browsable) { + break; + } + $result = $scheme_obj->validate($uri, $config, $context); + if (!$result) { + break; + } + + // Post chained filtering + $result = $uri_def->postFilter($uri, $config, $context); + if (!$result) { + break; + } + + // survived gauntlet + $ok = true; + + } while (false); + + $context->destroy('EmbeddedURI'); + if (!$ok) { + return false; + } + // back to string + return $uri->toString(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php new file mode 100644 index 0000000..daf32b7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php @@ -0,0 +1,20 @@ +" + // that needs more percent encoding to be done + if ($string == '') { + return false; + } + $string = trim($string); + $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string); + return $result ? $string : false; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php new file mode 100644 index 0000000..ddc5dfb --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php @@ -0,0 +1,142 @@ +ipv4 = new HTMLPurifier_AttrDef_URI_IPv4(); + $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6(); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $length = strlen($string); + // empty hostname is OK; it's usually semantically equivalent: + // the default host as defined by a URI scheme is used: + // + // If the URI scheme defines a default for host, then that + // default applies when the host subcomponent is undefined + // or when the registered name is empty (zero length). + if ($string === '') { + return ''; + } + if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') { + //IPv6 + $ip = substr($string, 1, $length - 2); + $valid = $this->ipv6->validate($ip, $config, $context); + if ($valid === false) { + return false; + } + return '[' . $valid . ']'; + } + + // need to do checks on unusual encodings too + $ipv4 = $this->ipv4->validate($string, $config, $context); + if ($ipv4 !== false) { + return $ipv4; + } + + // A regular domain name. + + // This doesn't match I18N domain names, but we don't have proper IRI support, + // so force users to insert Punycode. + + // There is not a good sense in which underscores should be + // allowed, since it's technically not! (And if you go as + // far to allow everything as specified by the DNS spec... + // well, that's literally everything, modulo some space limits + // for the components and the overall name (which, by the way, + // we are NOT checking!). So we (arbitrarily) decide this: + // let's allow underscores wherever we would have allowed + // hyphens, if they are enabled. This is a pretty good match + // for browser behavior, for example, a large number of browsers + // cannot handle foo_.example.com, but foo_bar.example.com is + // fairly well supported. + $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : ''; + + // Based off of RFC 1738, but amended so that + // as per RFC 3696, the top label need only not be all numeric. + // The productions describing this are: + $a = '[a-z]'; // alpha + $an = '[a-z0-9]'; // alphanum + $and = "[a-z0-9-$underscore]"; // alphanum | "-" + // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + $domainlabel = "$an(?:$and*$an)?"; + // AMENDED as per RFC 3696 + // toplabel = alphanum | alphanum *( alphanum | "-" ) alphanum + // side condition: not all numeric + $toplabel = "$an(?:$and*$an)?"; + // hostname = *( domainlabel "." ) toplabel [ "." ] + if (preg_match("/^(?:$domainlabel\.)*($toplabel)\.?$/i", $string, $matches)) { + if (!ctype_digit($matches[1])) { + return $string; + } + } + + // PHP 5.3 and later support this functionality natively + if (function_exists('idn_to_ascii')) { + if (defined('IDNA_NONTRANSITIONAL_TO_ASCII') && defined('INTL_IDNA_VARIANT_UTS46')) { + $string = idn_to_ascii($string, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46); + } else { + $string = idn_to_ascii($string); + } + + // If we have Net_IDNA2 support, we can support IRIs by + // punycoding them. (This is the most portable thing to do, + // since otherwise we have to assume browsers support + } elseif ($config->get('Core.EnableIDNA') && class_exists('Net_IDNA2')) { + $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true)); + // we need to encode each period separately + $parts = explode('.', $string); + try { + $new_parts = array(); + foreach ($parts as $part) { + $encodable = false; + for ($i = 0, $c = strlen($part); $i < $c; $i++) { + if (ord($part[$i]) > 0x7a) { + $encodable = true; + break; + } + } + if (!$encodable) { + $new_parts[] = $part; + } else { + $new_parts[] = $idna->encode($part); + } + } + $string = implode('.', $new_parts); + } catch (Exception $e) { + // XXX error reporting + } + } + // Try again + if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { + return $string; + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php new file mode 100644 index 0000000..30ac16c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php @@ -0,0 +1,45 @@ +ip4) { + $this->_loadRegex(); + } + + if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) { + return $aIP; + } + return false; + } + + /** + * Lazy load function to prevent regex from being stuffed in + * cache. + */ + protected function _loadRegex() + { + $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255 + $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})"; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php new file mode 100644 index 0000000..f243793 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php @@ -0,0 +1,89 @@ +ip4) { + $this->_loadRegex(); + } + + $original = $aIP; + + $hex = '[0-9a-fA-F]'; + $blk = '(?:' . $hex . '{1,4})'; + $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128 + + // prefix check + if (strpos($aIP, '/') !== false) { + if (preg_match('#' . $pre . '$#s', $aIP, $find)) { + $aIP = substr($aIP, 0, 0 - strlen($find[0])); + unset($find); + } else { + return false; + } + } + + // IPv4-compatiblity check + if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) { + $aIP = substr($aIP, 0, 0 - strlen($find[0])); + $ip = explode('.', $find[0]); + $ip = array_map('dechex', $ip); + $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3]; + unset($find, $ip); + } + + // compression check + $aIP = explode('::', $aIP); + $c = count($aIP); + if ($c > 2) { + return false; + } elseif ($c == 2) { + list($first, $second) = $aIP; + $first = explode(':', $first); + $second = explode(':', $second); + + if (count($first) + count($second) > 8) { + return false; + } + + while (count($first) < 8) { + array_push($first, '0'); + } + + array_splice($first, 8 - count($second), 8, $second); + $aIP = $first; + unset($first, $second); + } else { + $aIP = explode(':', $aIP[0]); + } + $c = count($aIP); + + if ($c != 8) { + return false; + } + + // All the pieces should be 16-bit hex strings. Are they? + foreach ($aIP as $piece) { + if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) { + return false; + } + } + return $original; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php new file mode 100644 index 0000000..b428331 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php @@ -0,0 +1,60 @@ +confiscateAttr($attr, 'background'); + // some validation should happen here + + $this->prependCSS($attr, "background-image:url($background);"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php new file mode 100644 index 0000000..d66c04a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php @@ -0,0 +1,27 @@ +get('Attr.DefaultTextDir'); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php new file mode 100644 index 0000000..0f51fd2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php @@ -0,0 +1,28 @@ +confiscateAttr($attr, 'bgcolor'); + // some validation should happen here + + $this->prependCSS($attr, "background-color:$bgcolor;"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php new file mode 100644 index 0000000..f25cd01 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php @@ -0,0 +1,47 @@ +attr = $attr; + $this->css = $css; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + unset($attr[$this->attr]); + $this->prependCSS($attr, $this->css); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php new file mode 100644 index 0000000..057dc01 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php @@ -0,0 +1,26 @@ +confiscateAttr($attr, 'border'); + // some validation should happen here + $this->prependCSS($attr, "border:{$border_width}px solid;"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php new file mode 100644 index 0000000..7ccd0e3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php @@ -0,0 +1,68 @@ +attr = $attr; + $this->enumToCSS = $enum_to_css; + $this->caseSensitive = (bool)$case_sensitive; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + + $value = trim($attr[$this->attr]); + unset($attr[$this->attr]); + + if (!$this->caseSensitive) { + $value = strtolower($value); + } + + if (!isset($this->enumToCSS[$value])) { + return $attr; + } + $this->prependCSS($attr, $this->enumToCSS[$value]); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php new file mode 100644 index 0000000..235ebb3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php @@ -0,0 +1,47 @@ +get('Core.RemoveInvalidImg')) { + return $attr; + } + $attr['src'] = $config->get('Attr.DefaultInvalidImage'); + $src = false; + } + + if (!isset($attr['alt'])) { + if ($src) { + $alt = $config->get('Attr.DefaultImageAlt'); + if ($alt === null) { + $attr['alt'] = basename($attr['src']); + } else { + $attr['alt'] = $alt; + } + } else { + $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt'); + } + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php new file mode 100644 index 0000000..350b335 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php @@ -0,0 +1,61 @@ + array('left', 'right'), + 'vspace' => array('top', 'bottom') + ); + + /** + * @param string $attr + */ + public function __construct($attr) + { + $this->attr = $attr; + if (!isset($this->css[$attr])) { + trigger_error(htmlspecialchars($attr) . ' is not valid space attribute'); + } + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + + $width = $this->confiscateAttr($attr, $this->attr); + // some validation could happen here + + if (!isset($this->css[$this->attr])) { + return $attr; + } + + $style = ''; + foreach ($this->css[$this->attr] as $suffix) { + $property = "margin-$suffix"; + $style .= "$property:{$width}px;"; + } + $this->prependCSS($attr, $style); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php new file mode 100644 index 0000000..3ab47ed --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php @@ -0,0 +1,56 @@ +pixels = new HTMLPurifier_AttrDef_HTML_Pixels(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['type'])) { + $t = 'text'; + } else { + $t = strtolower($attr['type']); + } + if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') { + unset($attr['checked']); + } + if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') { + unset($attr['maxlength']); + } + if (isset($attr['size']) && $t !== 'text' && $t !== 'password') { + $result = $this->pixels->validate($attr['size'], $config, $context); + if ($result === false) { + unset($attr['size']); + } else { + $attr['size'] = $result; + } + } + if (isset($attr['src']) && $t !== 'image') { + unset($attr['src']); + } + if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) { + $attr['value'] = ''; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php new file mode 100644 index 0000000..5b0aff0 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php @@ -0,0 +1,31 @@ +name = $name; + $this->cssName = $css_name ? $css_name : $name; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->name])) { + return $attr; + } + $length = $this->confiscateAttr($attr, $this->name); + if (ctype_digit($length)) { + $length .= 'px'; + } + $this->prependCSS($attr, $this->cssName . ":$length;"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php new file mode 100644 index 0000000..63cce68 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php @@ -0,0 +1,33 @@ +get('HTML.Attr.Name.UseCDATA')) { + return $attr; + } + if (!isset($attr['name'])) { + return $attr; + } + $id = $this->confiscateAttr($attr, 'name'); + if (isset($attr['id'])) { + return $attr; + } + $attr['id'] = $id; + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php new file mode 100644 index 0000000..5a1fdbb --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php @@ -0,0 +1,46 @@ +idDef = new HTMLPurifier_AttrDef_HTML_ID(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['name'])) { + return $attr; + } + $name = $attr['name']; + if (isset($attr['id']) && $attr['id'] === $name) { + return $attr; + } + $result = $this->idDef->validate($name, $config, $context); + if ($result === false) { + unset($attr['name']); + } else { + $attr['name'] = $result; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php new file mode 100644 index 0000000..1057ebe --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php @@ -0,0 +1,52 @@ +parser = new HTMLPurifier_URIParser(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['href'])) { + return $attr; + } + + // XXX Kind of inefficient + $url = $this->parser->parse($attr['href']); + $scheme = $url->getSchemeObj($config, $context); + + if ($scheme->browsable && !$url->isLocal($config, $context)) { + if (isset($attr['rel'])) { + $rels = explode(' ', $attr['rel']); + if (!in_array('nofollow', $rels)) { + $rels[] = 'nofollow'; + } + $attr['rel'] = implode(' ', $rels); + } else { + $attr['rel'] = 'nofollow'; + } + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php new file mode 100644 index 0000000..231c81a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php @@ -0,0 +1,25 @@ +uri = new HTMLPurifier_AttrDef_URI(true); // embedded + $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent')); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + // If we add support for other objects, we'll need to alter the + // transforms. + switch ($attr['name']) { + // application/x-shockwave-flash + // Keep this synchronized with Injector/SafeObject.php + case 'allowScriptAccess': + $attr['value'] = 'never'; + break; + case 'allowNetworking': + $attr['value'] = 'internal'; + break; + case 'allowFullScreen': + if ($config->get('HTML.FlashAllowFullScreen')) { + $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false'; + } else { + $attr['value'] = 'false'; + } + break; + case 'wmode': + $attr['value'] = $this->wmode->validate($attr['value'], $config, $context); + break; + case 'movie': + case 'src': + $attr['name'] = "movie"; + $attr['value'] = $this->uri->validate($attr['value'], $config, $context); + break; + case 'flashvars': + // we're going to allow arbitrary inputs to the SWF, on + // the reasoning that it could only hack the SWF, not us. + break; + // add other cases to support other param name/value pairs + default: + $attr['name'] = $attr['value'] = null; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php new file mode 100644 index 0000000..b7057bb --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php @@ -0,0 +1,23 @@ + + */ +class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['type'])) { + $attr['type'] = 'text/javascript'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php new file mode 100644 index 0000000..cc30ab8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php @@ -0,0 +1,49 @@ +parser = new HTMLPurifier_URIParser(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['href'])) { + return $attr; + } + + // XXX Kind of inefficient + $url = $this->parser->parse($attr['href']); + + // Ignore invalid schemes (e.g. `javascript:`) + if (!($scheme = $url->getSchemeObj($config, $context))) { + return $attr; + } + + if ($scheme->browsable && !$url->isBenign($config, $context)) { + $attr['target'] = '_blank'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php new file mode 100644 index 0000000..1db3c6c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetNoopener.php @@ -0,0 +1,37 @@ + + */ +class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + // Calculated from Firefox + if (!isset($attr['cols'])) { + $attr['cols'] = '22'; + } + if (!isset($attr['rows'])) { + $attr['rows'] = '3'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php new file mode 100644 index 0000000..e4429e8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php @@ -0,0 +1,97 @@ +info['Enum'] = new HTMLPurifier_AttrDef_Enum(); + $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); + + $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID(); + $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length(); + $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength(); + $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens(); + $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels(); + $this->info['Text'] = new HTMLPurifier_AttrDef_Text(); + $this->info['URI'] = new HTMLPurifier_AttrDef_URI(); + $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); + $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color(); + $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right'); + $this->info['LAlign'] = self::makeEnum('top,bottom,left,right'); + $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget(); + $this->info['ContentEditable'] = new HTMLPurifier_AttrDef_HTML_ContentEditable(); + + // unimplemented aliases + $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Character'] = new HTMLPurifier_AttrDef_Text(); + + // "proprietary" types + $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class(); + + // number is really a positive integer (one or more digits) + // FIXME: ^^ not always, see start and value of list items + $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); + } + + private static function makeEnum($in) + { + return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in))); + } + + /** + * Retrieves a type + * @param string $type String type name + * @return HTMLPurifier_AttrDef Object AttrDef for 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 (!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 string $type String type name + * @param HTMLPurifier_AttrDef $impl Object AttrDef for type + */ + public function set($type, $impl) + { + $this->info[$type] = $impl; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php new file mode 100644 index 0000000..f97dc93 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php @@ -0,0 +1,178 @@ +getHTMLDefinition(); + $e =& $context->get('ErrorCollector', true); + + // initialize IDAccumulator if necessary + $ok =& $context->get('IDAccumulator', true); + if (!$ok) { + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + } + + // initialize CurrentToken if necessary + $current_token =& $context->get('CurrentToken', true); + if (!$current_token) { + $context->register('CurrentToken', $token); + } + + if (!$token instanceof HTMLPurifier_Token_Start && + !$token instanceof HTMLPurifier_Token_Empty + ) { + return; + } + + // create alias to global definition array, see also $defs + // DEFINITION CALL + $d_defs = $definition->info_global_attr; + + // don't update token until the very end, to ensure an atomic update + $attr = $token->attr; + + // do global transformations (pre) + // nothing currently utilizes this + 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); + } + } + } + + // do local transformations only applicable to this element (pre) + // ex.

to

+ 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); + } + } + } + + // create alias to this element's attribute definition array, see + // also $d_defs (global attribute definition array) + // DEFINITION CALL + $defs = $definition->info[$token->name]->attr; + + $attr_key = false; + $context->register('CurrentAttr', $attr_key); + + // iterate through all the attribute keypairs + // Watch out for name collisions: $key has previously been used + foreach ($attr as $attr_key => $value) { + + // call the definition + 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. + // This is usually when there's a global definition + // that must be overridden. + // Theoretically speaking, we could have a + // AttrDef_DenyAll, but this is faster! + $result = false; + } else { + // validate according to the element's definition + $result = $defs[$attr_key]->validate( + $value, + $config, + $context + ); + } + } 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 + ); + } else { + // system never heard of the attribute? DELETE! + $result = false; + } + + // put the results into effect + 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'); + } + + // remove the attribute + unset($attr[$attr_key]); + } elseif (is_string($result)) { + // generally, if a substitution is happening, there + // was some sort of implicit correction going on. We'll + // delegate it to the attribute classes to say exactly what. + + // simple substitution + $attr[$attr_key] = $result; + } else { + // nothing happens + } + + // we'd also want slightly more complicated substitution + // involving an array as the return value, + // although we're not sure how colliding attributes would + // resolve (certain ones would be completely overriden, + // others would prepend themselves). + } + + $context->destroy('CurrentAttr'); + + // post transforms + + // global (error reporting untested) + 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); + } + } + } + + // local (error reporting untested) + 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); + } + } + } + + $token->attr = $attr; + + // destroy CurrentToken if we made it ourselves + if (!$current_token) { + $context->destroy('CurrentToken'); + } + + } + + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php new file mode 100644 index 0000000..bd8f998 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php @@ -0,0 +1,91 @@ + +if (!defined('PHP_EOL')) { + switch (strtoupper(substr(PHP_OS, 0, 3))) { + case 'WIN': + define('PHP_EOL', "\r\n"); + break; + case 'DAR': + define('PHP_EOL', "\r"); + break; + default: + define('PHP_EOL', "\n"); + } +} + +/** + * Bootstrap class that contains meta-functionality for HTML Purifier such as + * the autoload function. + * + * @note + * This class may be used without any other files from HTML Purifier. + */ +class HTMLPurifier_Bootstrap +{ + + /** + * Autoload function for HTML Purifier + * @param string $class Class to load + * @return bool + */ + public static function autoload($class) + { + $file = HTMLPurifier_Bootstrap::getPath($class); + 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 + // may be broken. Since we have efficient alternatives, pay + // the cost here and avoid the bug. + require_once HTMLPURIFIER_PREFIX . '/' . $file; + return true; + } + + /** + * 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; + } + // Custom implementations + if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { + $code = str_replace('_', '-', substr($class, 22)); + $file = 'HTMLPurifier/Language/classes/' . $code . '.php'; + } else { + $file = str_replace('_', '/', $class) . '.php'; + } + if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) { + return false; + } + return $file; + } + + /** + * "Pre-registers" our autoloader on the SPL stack. + */ + public static function registerAutoload() + { + $autoload = array('HTMLPurifier_Bootstrap', 'autoload'); + if (spl_autoload_functions() === false) { + spl_autoload_register($autoload); + } else { + // prepend flag exists, no need for shenanigans + spl_autoload_register($autoload, true, true); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php new file mode 100644 index 0000000..1bc419c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php @@ -0,0 +1,566 @@ +info['text-align'] = new HTMLPurifier_AttrDef_Enum( + ['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( + [ + '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( + ['none', 'left', 'right', 'both'], + false + ); + $this->info['float'] = new HTMLPurifier_AttrDef_Enum( + ['none', 'left', 'right'], + false + ); + $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( + ['normal', 'italic', 'oblique'], + false + ); + $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( + ['normal', 'small-caps'], + false + ); + + $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_Enum(['none']), + new HTMLPurifier_AttrDef_CSS_URI() + ] + ); + + $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( + ['inside', 'outside'], + false + ); + $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( + [ + '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( + ['capitalize', 'uppercase', 'lowercase', 'none'], + false + ); + $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + $this->info['background-image'] = $uri_or_none; + $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( + ['repeat', 'repeat-x', 'repeat-y', 'no-repeat'] + ); + $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( + ['scroll', 'fixed'] + ); + $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); + + $this->info['background-size'] = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_Enum( + [ + 'auto', + 'cover', + 'contain', + 'initial', + 'inherit', + ] + ), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_CSS_Length() + ] + ); + + $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( + [ + new HTMLPurifier_AttrDef_Enum(['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( + [ + new HTMLPurifier_AttrDef_Enum(['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( + [ + new HTMLPurifier_AttrDef_Enum(['normal']), + new HTMLPurifier_AttrDef_CSS_Length() + ] + ); + + $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_Enum(['normal']), + new HTMLPurifier_AttrDef_CSS_Length() + ] + ); + + $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_Enum( + [ + '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( + [ + new HTMLPurifier_AttrDef_Enum(['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( + [ + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(['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( + [ + 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( + [ + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ] + ); + + $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(['auto', 'initial', 'inherit']) + ] + ); + $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(['initial', 'inherit']) + ] + ); + $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(['none', 'initial', 'inherit']) + ] + ); + $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( + [ + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(['auto']) + ] + ), + // For everyone else: + $trusted_wh + ); + $this->info['min-width'] = + $this->info['min-height'] = + $max === null ? + $trusted_min_wh : + new HTMLPurifier_AttrDef_Switch( + 'img', + // For img tags: + new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(['initial', 'inherit']) + ] + ), + // For everyone else: + $trusted_min_wh + ); + $this->info['max-width'] = + $this->info['max-height'] = + $max === null ? + $trusted_max_wh : + new HTMLPurifier_AttrDef_Switch( + 'img', + // For img tags: + new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(['none', 'initial', 'inherit']) + ] + ), + // For everyone else: + $trusted_max_wh + ); + + // text-decoration and related shorthands + $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); + + $this->info['text-decoration-line'] = new HTMLPurifier_AttrDef_Enum( + ['none', 'underline', 'overline', 'line-through', 'initial', 'inherit'] + ); + + $this->info['text-decoration-style'] = new HTMLPurifier_AttrDef_Enum( + ['solid', 'double', 'dotted', 'dashed', 'wavy', 'initial', 'inherit'] + ); + + $this->info['text-decoration-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + $this->info['text-decoration-thickness'] = new HTMLPurifier_AttrDef_CSS_Composite([ + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(['auto', 'from-font', 'initial', 'inherit']) + ]); + + $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); + + // this could use specialized code + $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( + [ + '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 + $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); + + // same here + $this->info['border'] = + $this->info['border-bottom'] = + $this->info['border-top'] = + $this->info['border-left'] = + $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); + + $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum( + ['collapse', 'separate'] + ); + + $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum( + ['top', 'bottom'] + ); + + $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum( + ['auto', 'fixed'] + ); + + $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_Enum( + [ + '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( + ['nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line'] + ); + + if ($config->get('CSS.Proprietary')) { + $this->doSetupProprietary($config); + } + + if ($config->get('CSS.AllowTricky')) { + $this->doSetupTricky($config); + } + + if ($config->get('CSS.Trusted')) { + $this->doSetupTrusted($config); + } + + $allow_important = $config->get('CSS.AllowImportant'); + // wrap all attr-defs with decorator that handles !important + foreach ($this->info as $k => $v) { + $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); + } + + $this->setupConfigStuff($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(); + + // vendor specific prefixes of opacity + $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( + [ + 'auto', + 'always', + 'avoid', + 'left', + 'right' + ] + ); + $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(['auto', 'avoid']); + + $border_radius = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative + new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative + ]); + + $this->info['border-top-left-radius'] = + $this->info['border-top-right-radius'] = + $this->info['border-bottom-right-radius'] = + $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2); + // TODO: support SLASH syntax + $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4); + + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTricky($config) + { + $this->info['display'] = new HTMLPurifier_AttrDef_Enum( + [ + '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( + ['visible', 'hidden', 'collapse'] + ); + $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(['visible', 'hidden', 'auto', 'scroll']); + $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTrusted($config) + { + $this->info['position'] = new HTMLPurifier_AttrDef_Enum( + ['static', 'relative', 'absolute', 'fixed'] + ); + $this->info['top'] = + $this->info['left'] = + $this->info['right'] = + $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(['auto']), + ] + ); + $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite( + [ + new HTMLPurifier_AttrDef_Integer(), + new HTMLPurifier_AttrDef_Enum(['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) + { + // setup allowed elements + $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]); + } + unset($allowed_properties[$name]); + } + // emit errors + foreach ($allowed_properties as $name => $d) { + // :TODO: Is this htmlspecialchars() call really necessary? + $name = htmlspecialchars($name); + trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); + } + } + + $forbidden_properties = $config->get('CSS.ForbiddenProperties'); + if ($forbidden_properties !== null) { + foreach ($this->info as $name => $d) { + if (isset($forbidden_properties[$name])) { + unset($this->info[$name]); + } + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php new file mode 100644 index 0000000..8eb17b8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php @@ -0,0 +1,52 @@ +elements; + } + + /** + * Validates nodes according to definition and returns modification. + * + * @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); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php new file mode 100644 index 0000000..7439be2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php @@ -0,0 +1,67 @@ +inline = new HTMLPurifier_ChildDef_Optional($inline); + $this->block = new HTMLPurifier_ChildDef_Optional($block); + $this->elements = $this->block->elements; + } + + /** + * @param HTMLPurifier_Node[] $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function validateChildren($children, $config, $context) + { + if ($context->get('IsInline') === false) { + return $this->block->validateChildren( + $children, + $config, + $context + ); + } else { + return $this->inline->validateChildren( + $children, + $config, + $context + ); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php new file mode 100644 index 0000000..f515888 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php @@ -0,0 +1,102 @@ +dtd_regex = $dtd_regex; + $this->_compileRegex(); + } + + /** + * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex) + */ + protected function _compileRegex() + { + $raw = str_replace(' ', '', $this->dtd_regex); + if ($raw[0] != '(') { + $raw = "($raw)"; + } + $el = '[#a-zA-Z0-9_.-]+'; + $reg = $raw; + + // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M + // DOING! Seriously: if there's problems, please report them. + + // collect all elements into the $elements array + preg_match_all("/$el/", $reg, $matches); + foreach ($matches[0] as $match) { + $this->elements[$match] = true; + } + + // setup all elements as parentheticals with leading commas + $reg = preg_replace("/$el/", '(,\\0)', $reg); + + // remove commas when they were not solicited + $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg); + + // remove all non-paranthetical commas: they are handled by first regex + $reg = preg_replace("/,\(/", '(', $reg); + + $this->_pcre_regex = $reg; + } + + /** + * @param HTMLPurifier_Node[] $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function validateChildren($children, $config, $context) + { + $list_of_children = ''; + $nesting = 0; // depth into the nest + foreach ($children as $node) { + if (!empty($node->is_whitespace)) { + continue; + } + $list_of_children .= $node->name . ','; + } + // add leading comma to deal with stray comma declarations + $list_of_children = ',' . rtrim($list_of_children, ','); + $okay = + preg_match( + '/^,?' . $this->_pcre_regex . '$/', + $list_of_children + ); + return (bool)$okay; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php new file mode 100644 index 0000000..a8a6cbd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php @@ -0,0 +1,38 @@ + true, 'ul' => true, 'ol' => true); + + public $whitespace; + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + // Flag for subclasses + $this->whitespace = false; + + // if there are no tokens, delete parent node + if (empty($children)) { + return false; + } + + // if li is not allowed, delete parent node + if (!isset($config->getHTMLDefinition()->info['li'])) { + trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING); + return false; + } + + // the new set of children + $result = array(); + + // a little sanity check to make sure it's not ALL whitespace + $all_whitespace = true; + + $current_li = null; + + foreach ($children as $node) { + if (!empty($node->is_whitespace)) { + $result[] = $node; + continue; + } + $all_whitespace = false; // phew, we're not talking about whitespace + + if ($node->name === 'li') { + // good + $current_li = $node; + $result[] = $node; + } else { + // we want to tuck this into the previous li + // Invariant: we expect the node to be ol/ul + // ToDo: Make this more robust in the case of not ol/ul + // by distinguishing between existing li and li created + // to handle non-list elements; non-list elements should + // not be appended to an existing li; only li created + // for non-list. This distinction is not currently made. + if ($current_li === null) { + $current_li = new HTMLPurifier_Node_Element('li'); + $result[] = $current_li; + } + $current_li->children[] = $node; + $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo + } + } + if (empty($result)) { + return false; + } + if ($all_whitespace) { + return false; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php new file mode 100644 index 0000000..b946806 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php @@ -0,0 +1,45 @@ +whitespace) { + return $children; + } else { + return array(); + } + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php new file mode 100644 index 0000000..0d1c8f5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php @@ -0,0 +1,118 @@ + $x) { + $elements[$i] = true; + if (empty($i)) { + unset($elements[$i]); + } // remove blank + } + } + $this->elements = $elements; + } + + /** + * @type bool + */ + public $allow_empty = false; + + /** + * @type string + */ + public $type = 'required'; + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + // Flag for subclasses + $this->whitespace = false; + + // if there are no tokens, delete parent node + if (empty($children)) { + return false; + } + + // the new set of children + $result = array(); + + // whether or not parsed character data is allowed + // this controls whether or not we silently drop a tag + // or generate escaped HTML from it + $pcdata_allowed = isset($this->elements['#PCDATA']); + + // a little sanity check to make sure it's not ALL whitespace + $all_whitespace = true; + + $stack = array_reverse($children); + while (!empty($stack)) { + $node = array_pop($stack); + if (!empty($node->is_whitespace)) { + $result[] = $node; + continue; + } + $all_whitespace = false; // phew, we're not talking about whitespace + + if (!isset($this->elements[$node->name])) { + // special case text + // XXX One of these ought to be redundant or something + if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) { + $result[] = $node; + continue; + } + // spill the child contents in + // ToDo: Make configurable + if ($node instanceof HTMLPurifier_Node_Element) { + for ($i = count($node->children) - 1; $i >= 0; $i--) { + $stack[] = $node->children[$i]; + } + continue; + } + continue; + } + $result[] = $node; + } + if (empty($result)) { + return false; + } + if ($all_whitespace) { + $this->whitespace = true; + return false; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php new file mode 100644 index 0000000..3270a46 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php @@ -0,0 +1,110 @@ +init($config); + return $this->fake_elements; + } + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + $this->init($config); + + // trick the parent class into thinking it allows more + $this->elements = $this->fake_elements; + $result = parent::validateChildren($children, $config, $context); + $this->elements = $this->real_elements; + + if ($result === false) { + return array(); + } + if ($result === true) { + $result = $children; + } + + $def = $config->getHTMLDefinition(); + $block_wrap_name = $def->info_block_wrapper; + $block_wrap = false; + $ret = array(); + + foreach ($result as $node) { + if ($block_wrap === false) { + if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) || + ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) { + $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper); + $ret[] = $block_wrap; + } + } else { + if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) { + $block_wrap = false; + + } + } + if ($block_wrap) { + $block_wrap->children[] = $node; + } else { + $ret[] = $node; + } + } + return $ret; + } + + /** + * @param HTMLPurifier_Config $config + */ + private function init($config) + { + if (!$this->init) { + $def = $config->getHTMLDefinition(); + // allow all inline elements + $this->real_elements = $this->elements; + $this->fake_elements = $def->info_content_sets['Flow']; + $this->fake_elements['#PCDATA'] = true; + $this->init = true; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php new file mode 100644 index 0000000..67c7e95 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php @@ -0,0 +1,224 @@ + true, + 'tbody' => true, + 'thead' => true, + 'tfoot' => true, + 'caption' => true, + 'colgroup' => true, + 'col' => true + ); + + public function __construct() + { + } + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + if (empty($children)) { + return false; + } + + // only one of these elements is allowed in a table + $caption = false; + $thead = false; + $tfoot = false; + + // whitespace + $initial_ws = array(); + $after_caption_ws = array(); + $after_thead_ws = array(); + $after_tfoot_ws = array(); + + // as many of these as you want + $cols = array(); + $content = array(); + + $tbody_mode = false; // if true, then we need to wrap any stray + //

s with a . + + $ws_accum =& $initial_ws; + + foreach ($children as $node) { + if ($node instanceof HTMLPurifier_Node_Comment) { + $ws_accum[] = $node; + continue; + } + switch ($node->name) { + case 'tbody': + $tbody_mode = true; + // fall through + case 'tr': + $content[] = $node; + $ws_accum =& $content; + break; + case 'caption': + // there can only be one caption! + if ($caption !== false) break; + $caption = $node; + $ws_accum =& $after_caption_ws; + break; + case 'thead': + $tbody_mode = true; + // XXX This breaks rendering properties with + // Firefox, which never floats a to + // the top. Ever. (Our scheme will float the + // first to the top.) So maybe + // s that are not first should be + // turned into ? Very tricky, indeed. + if ($thead === false) { + $thead = $node; + $ws_accum =& $after_thead_ws; + } else { + // Oops, there's a second one! What + // should we do? Current behavior is to + // transmutate the first and last entries into + // tbody tags, and then put into content. + // Maybe a better idea is to *attach + // it* to the existing thead or tfoot? + // We don't do this, because Firefox + // doesn't float an extra tfoot to the + // bottom like it does for the first one. + $node->name = 'tbody'; + $content[] = $node; + $ws_accum =& $content; + } + break; + case 'tfoot': + // see above for some aveats + $tbody_mode = true; + if ($tfoot === false) { + $tfoot = $node; + $ws_accum =& $after_tfoot_ws; + } else { + $node->name = 'tbody'; + $content[] = $node; + $ws_accum =& $content; + } + break; + case 'colgroup': + case 'col': + $cols[] = $node; + $ws_accum =& $cols; + break; + case '#PCDATA': + // How is whitespace handled? We treat is as sticky to + // the *end* of the previous element. So all of the + // nonsense we have worked on is to keep things + // together. + if (!empty($node->is_whitespace)) { + $ws_accum[] = $node; + } + break; + } + } + + if (empty($content) && $thead === false && $tfoot === false) { + return false; + } + + $ret = $initial_ws; + if ($caption !== false) { + $ret[] = $caption; + $ret = array_merge($ret, $after_caption_ws); + } + if ($cols !== false) { + $ret = array_merge($ret, $cols); + } + if ($thead !== false) { + $ret[] = $thead; + $ret = array_merge($ret, $after_thead_ws); + } + if ($tfoot !== false) { + $ret[] = $tfoot; + $ret = array_merge($ret, $after_tfoot_ws); + } + + if ($tbody_mode) { + // we have to shuffle tr into tbody + $current_tr_tbody = null; + + foreach($content as $node) { + switch ($node->name) { + case 'tbody': + $current_tr_tbody = null; + $ret[] = $node; + break; + case 'tr': + if ($current_tr_tbody === null) { + $current_tr_tbody = new HTMLPurifier_Node_Element('tbody'); + $ret[] = $current_tr_tbody; + } + $current_tr_tbody->children[] = $node; + break; + case '#PCDATA': + //assert($node->is_whitespace); + if ($current_tr_tbody === null) { + $ret[] = $node; + } else { + $current_tr_tbody->children[] = $node; + } + break; + } + } + } else { + $ret = array_merge($ret, $content); + } + + return $ret; + + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php new file mode 100644 index 0000000..f7511ca --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php @@ -0,0 +1,920 @@ +defaultPlist; + $this->plist = new HTMLPurifier_PropertyList($parent); + $this->def = $definition; // keep a copy around for checking + $this->parser = new HTMLPurifier_VarParser_Flexible(); + } + + /** + * Convenience constructor that creates a config object based on a mixed var + * @param mixed $config Variable that defines the state of the config + * 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 Schema object + * @return HTMLPurifier_Config Configured object + */ + public static function create($config, $schema = null) + { + if ($config instanceof HTMLPurifier_Config) { + // pass-through + return $config; + } + if (!$schema) { + $ret = HTMLPurifier_Config::createDefault(); + } else { + $ret = new HTMLPurifier_Config($schema); + } + 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. + * @return HTMLPurifier_Config object with $config as its parent. + */ + public static function inherit(HTMLPurifier_Config $config) + { + return new HTMLPurifier_Config($config->def, $config->plist); + } + + /** + * Convenience constructor that creates a default configuration object. + * @return HTMLPurifier_Config default object. + */ + public static function createDefault() + { + $definition = HTMLPurifier_ConfigSchema::instance(); + $config = new HTMLPurifier_Config($definition); + return $config; + } + + /** + * Retrieves a value from the configuration. + * + * @param string $key String key + * @param mixed $a + * + * @return mixed + */ + public function get($key, $a = null) + { + if ($a !== null) { + $this->triggerError( + "Using deprecated API: use \$config->get('$key.$a') instead", + E_USER_WARNING + ); + $key = "$key.$a"; + } + 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 + ); + 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 + ); + 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 + ); + return; + } + } + return $this->plist->get($key); + } + + /** + * 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(); + } + $full = $this->getAll(); + if (!isset($full[$namespace])) { + $this->triggerError( + 'Cannot retrieve undefined namespace ' . + htmlspecialchars($namespace), + E_USER_WARNING + ); + return; + } + return $full[$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! + */ + public function getBatchSerial($namespace) + { + if (empty($this->serials[$namespace])) { + $batch = $this->getBatch($namespace); + unset($batch['DefinitionRev']); + $this->serials[$namespace] = sha1(serialize($batch)); + } + return $this->serials[$namespace]; + } + + /** + * Returns a SHA-1 signature for the entire configuration object + * that uniquely identifies that particular configuration + * + * @return string + */ + public function getSerial() + { + if (empty($this->serial)) { + $this->serial = sha1(serialize($this->getAll())); + } + return $this->serial; + } + + /** + * 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(); + } + $ret = array(); + foreach ($this->plist->squash() as $name => $value) { + list($ns, $key) = explode('.', $name, 2); + $ret[$ns][$key] = $value; + } + return $ret; + } + + /** + * Sets a value to configuration. + * + * @param string $key key + * @param mixed $value value + * @param mixed $a + */ + public function set($key, $value, $a = null) + { + if (strpos($key, '.') === false) { + $namespace = $key; + $directive = $value; + $value = $a; + $key = "$key.$directive"; + $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); + } else { + list($namespace) = explode('.', $key); + } + 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 + ); + 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 + ); + return; + } + $this->aliasMode = true; + $this->set($def->key, $value); + $this->aliasMode = false; + $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); + return; + } + + // Raw type might be negative when using the fully optimized form + // of stdClass, which indicates allow_null == true + $rtype = is_int($def) ? $def : $def->type; + if ($rtype < 0) { + $type = -$rtype; + $allow_null = true; + } else { + $type = $rtype; + $allow_null = isset($def->allow_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 + ); + return; + } + if (is_string($value) && is_object($def)) { + // resolve value alias if defined + if (isset($def->aliases[$value])) { + $value = $def->aliases[$value]; + } + // 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 + ); + return; + } + } + $this->plist->set($key, $value); + + // reset definitions if the directives they depend on changed + // this is a very costly process, so it's discouraged + // with finalization + if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { + $this->definitions[$namespace] = null; + } + + $this->serials[$namespace] = false; + } + + /** + * Convenience function for error reporting + * + * @param array $lookup + * + * @return string + */ + private function _listify($lookup) + { + $list = array(); + foreach ($lookup as $name => $b) { + $list[] = $name; + } + return implode(', ', $list); + } + + /** + * Retrieves object reference to the HTML definition. + * + * @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 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|null + */ + public function getHTMLDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('HTML', $raw, $optimized); + } + + /** + * Retrieves object reference to the CSS definition + * + * @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 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|null + */ + public function getCSSDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('CSS', $raw, $optimized); + } + + /** + * Retrieves object reference to the URI definition + * + * @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 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|null + */ + public function getURIDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('URI', $raw, $optimized); + } + + /** + * Retrieves a definition + * + * @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 + * way in order to ensure that caching is done properly. + * 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|null + */ + 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(); + } + // temporarily suspend locks, so we can handle recursive definition calls + $lock = $this->lock; + $this->lock = null; + $factory = HTMLPurifier_DefinitionCacheFactory::instance(); + $cache = $factory->create($type, $this); + $this->lock = $lock; + if (!$raw) { + // full definition + // --------------- + // check if definition is in memory + if (!empty($this->definitions[$type])) { + $def = $this->definitions[$type]; + // check if the definition is setup + if ($def->setup) { + return $def; + } else { + $def->setup($this); + if ($def->optimized) { + $cache->add($def, $this); + } + return $def; + } + } + // check if definition is in cache + $def = $cache->get($this); + if ($def) { + // definition in cache, save to memory and return it + $this->definitions[$type] = $def; + return $def; + } + // initialize it + $def = $this->initDefinition($type); + // set it up + $this->lock = $type; + $def->setup($this); + $this->lock = null; + // save in cache + $cache->add($def, $this); + // return it + return $def; + } else { + // raw definition + // -------------- + // check preconditions + $def = null; + 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" + ); + } + } + 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 + ); + } + if ($def->optimized === null) { + $extra = $this->chatty ? " (try flushing your cache)" : ""; + 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 + ); + } + } + // check if definition was in memory + if ($def) { + if ($def->setup) { + // invariant: $optimized === true (checked above) + return null; + } else { + return $def; + } + } + // if optimized, check if definition was in cache + // (because we do the memory check first, this formulation + // is prone to cache slamming, but I think + // guaranteeing that either /all/ of the raw + // setup code or /none/ of it is run is more important.) + if ($optimized) { + // This code path only gets run once; once we put + // something in $definitions (which is guaranteed by the + // trailing code), we always short-circuit above. + $def = $cache->get($this); + if ($def) { + // save the full definition for later, but don't + // return it yet + $this->definitions[$type] = $def; + return null; + } + } + // check invariants for creation + 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 + ); + } else { + $this->triggerError( + "Useless DefinitionID declaration", + E_USER_WARNING + ); + } + } + } + // initialize it + $def = $this->initDefinition($type); + $def->optimized = $optimized; + return $def; + } + throw new HTMLPurifier_Exception("The impossible happened!"); + } + + /** + * 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(); + } elseif ($type == 'CSS') { + $def = new HTMLPurifier_CSSDefinition(); + } elseif ($type == 'URI') { + $def = new HTMLPurifier_URIDefinition(); + } else { + throw new HTMLPurifier_Exception( + "Definition of $type type not supported" + ); + } + $this->definitions[$type] = $def; + return $def; + } + + public function maybeGetRawDefinition($name) + { + return $this->getDefinition($name, true, true); + } + + /** + * @return HTMLPurifier_HTMLDefinition|null + */ + public function maybeGetRawHTMLDefinition() + { + return $this->getDefinition('HTML', true, true); + } + + /** + * @return HTMLPurifier_CSSDefinition|null + */ + public function maybeGetRawCSSDefinition() + { + return $this->getDefinition('CSS', true, true); + } + + /** + * @return HTMLPurifier_URIDefinition|null + */ + public function maybeGetRawURIDefinition() + { + return $this->getDefinition('URI', true, true); + } + + /** + * Loads configuration values from an array with the following structure: + * Namespace.Directive => Value + * + * @param array $config_array Configuration associative array + */ + 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) { + $this->set($key, $value); + } else { + $namespace = $key; + $namespace_values = $value; + foreach ($namespace_values as $directive => $value2) { + $this->set($namespace .'.'. $directive, $value2); + } + } + } + } + + /** + * 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 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) + { + 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; + } + } + } + $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($def->isAlias)) { + continue; + } + if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') { + continue; + } + $ret[] = array($ns, $directive); + } + return $ret; + } + + /** + * Loads configuration values from $_GET/$_POST that were posted + * via ConfigForm + * + * @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) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); + $config = HTMLPurifier_Config::create($ret, $schema); + return $config; + } + + /** + * Merges in configuration values from $_GET/$_POST to object. NOT STATIC. + * + * @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) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); + $this->loadArray($ret); + } + + /** + * 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(); + } + $mq = $mq_fix && version_compare(PHP_VERSION, '7.4.0', '<') && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); + $ret = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $skey = "$ns.$directive"; + if (!empty($array["Null_$skey"])) { + $ret[$ns][$directive] = null; + continue; + } + if (!isset($array[$skey])) { + continue; + } + $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; + $ret[$ns][$directive] = $value; + } + return $ret; + } + + /** + * Loads configuration values from an ini file + * + * @param string $filename Name of ini file + */ + 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 string|bool $error String error message, or false for no error + * + * @return bool + */ + public function isFinalized($error = false) + { + if ($this->finalized && $error) { + $this->triggerError($error, E_USER_ERROR); + } + return $this->finalized; + } + + /** + * Finalizes configuration only if auto finalize is on and not + * already finalized + */ + public function autoFinalize() + { + if ($this->autoFinalize) { + $this->finalize(); + } else { + $this->plist->squash(true); + } + } + + /** + * Finalizes a configuration object, prohibiting further change + */ + public function finalize() + { + $this->finalized = true; + $this->parser = null; + } + + /** + * 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) + { + // determine previous stack frame + $extra = ''; + if ($this->chatty) { + $trace = debug_backtrace(); + // zip(tail(trace), trace) -- but PHP is not Haskell har har + for ($i = 0, $c = count($trace); $i < $c - 1; $i++) { + // XXX this is not correct on some versions of HTML Purifier + if (isset($trace[$i + 1]['class']) && $trace[$i + 1]['class'] === 'HTMLPurifier_Config') { + continue; + } + $frame = $trace[$i]; + $extra = " invoked on line {$frame['line']} in file {$frame['file']}"; + break; + } + } + trigger_error($msg . $extra, $no); + } + + /** + * Returns a serialized form of the configuration object that can + * be reconstituted. + * + * @return string + */ + public function serialize() + { + $this->getDefinition('HTML'); + $this->getDefinition('CSS'); + $this->getDefinition('URI'); + return serialize($this); + } + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php new file mode 100644 index 0000000..c3fe8cd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php @@ -0,0 +1,176 @@ + array( + * 'Directive' => new stdClass(), + * ) + * ) + * + * The stdClass may have the following properties: + * + * - If isAlias isn't set: + * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions + * - allow_null: If set, this directive allows null values + * - aliases: If set, an associative array of value aliases to real values + * - allowed: If set, a lookup array of allowed (string) values + * - If isAlias is set: + * - namespace: Namespace this directive aliases to + * - name: Directive name this directive aliases to + * + * In certain degenerate cases, stdClass will actually be an integer. In + * that case, the value is equivalent to an stdClass with the type + * property set to the integer. If the integer is negative, type is + * equal to the absolute value of integer, and allow_null is true. + * + * 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 + */ + protected static $singleton; + + public function __construct() + { + $this->defaultPlist = new HTMLPurifier_PropertyList(); + } + + /** + * Unserializes the default ConfigSchema. + * @return HTMLPurifier_ConfigSchema + */ + public static function makeFromSerial() + { + $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'); + $r = unserialize($contents); + if (!$r) { + $hash = sha1($contents); + trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR); + } + return $r; + } + + /** + * Retrieves an instance of the application-wide configuration definition. + * @param HTMLPurifier_ConfigSchema $prototype + * @return HTMLPurifier_ConfigSchema + */ + public static function instance($prototype = null) + { + if ($prototype !== null) { + HTMLPurifier_ConfigSchema::$singleton = $prototype; + } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { + HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial(); + } + return HTMLPurifier_ConfigSchema::$singleton; + } + + /** + * Defines a directive for configuration + * @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 string $key Name of directive + * @param mixed $default Default value of directive + * @param string $type Allowed type of the directive. See + * HTMLPurifier_VarParser::$types for allowed values + * @param bool $allow_null Whether or not to allow null values + */ + 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; + } + $this->info[$key] = $obj; + $this->defaults[$key] = $default; + $this->defaultPlist->set($key, $default); + } + + /** + * Defines a directive value alias. + * + * Directive value aliases are convenient for developers because it lets + * them set a directive to several values and get the same result. + * @param string $key Name of Directive + * @param array $aliases Hash of aliased values to the real alias + */ + public function addValueAliases($key, $aliases) + { + if (!isset($this->info[$key]->aliases)) { + $this->info[$key]->aliases = array(); + } + foreach ($aliases as $alias => $real) { + $this->info[$key]->aliases[$alias] = $real; + } + } + + /** + * Defines a set of allowed values for a directive. + * @warning This is slightly different from the corresponding static + * method definition. + * @param string $key Name of directive + * @param array $allowed Lookup array of allowed values + */ + public function addAllowedValues($key, $allowed) + { + $this->info[$key]->allowed = $allowed; + } + + /** + * Defines a directive alias for backwards compatibility + * @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) + { + $obj = new stdClass; + $obj->key = $new_key; + $obj->isAlias = true; + $this->info[$key] = $obj; + } + + /** + * Replaces any stdClass that only has the type property with type integer. + */ + public function postProcess() + { + foreach ($this->info as $key => $v) { + if (count((array) $v) == 1) { + $this->info[$key] = $v->type; + } elseif (count((array) $v) == 2 && isset($v->allow_null)) { + $this->info[$key] = -$v->type; + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php new file mode 100644 index 0000000..d5906cd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php @@ -0,0 +1,48 @@ +directives as $d) { + $schema->add( + $d->id->key, + $d->default, + $d->type, + $d->typeAllowsNull + ); + if ($d->allowed !== null) { + $schema->addAllowedValues( + $d->id->key, + $d->allowed + ); + } + foreach ($d->aliases as $alias) { + $schema->addAlias( + $alias->key, + $d->id->key + ); + } + if ($d->valueAliases !== null) { + $schema->addValueAliases( + $d->id->key, + $d->valueAliases + ); + } + } + $schema->postProcess(); + return $schema; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php new file mode 100644 index 0000000..5fa56f7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php @@ -0,0 +1,144 @@ +startElement('div'); + + $purifier = HTMLPurifier::getInstance(); + $html = $purifier->purify($html); + $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + $this->writeRaw($html); + + $this->endElement(); // div + } + + /** + * @param mixed $var + * @return string + */ + protected function export($var) + { + if ($var === array()) { + return 'array()'; + } + return var_export($var, true); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + */ + public function build($interchange) + { + // global access, only use as last resort + $this->interchange = $interchange; + + $this->setIndent(true); + $this->startDocument('1.0', 'UTF-8'); + $this->startElement('configdoc'); + $this->writeElement('title', $interchange->name); + + foreach ($interchange->directives as $directive) { + $this->buildDirective($directive); + } + + if ($this->namespace) { + $this->endElement(); + } // namespace + + $this->endElement(); // configdoc + $this->flush(); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive + */ + public function buildDirective($directive) + { + // Kludge, although I suppose having a notion of a "root namespace" + // certainly makes things look nicer when documentation is built. + // Depends on things being sorted. + if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) { + if ($this->namespace) { + $this->endElement(); + } // namespace + $this->namespace = $directive->id->getRootNamespace(); + $this->startElement('namespace'); + $this->writeAttribute('id', $this->namespace); + $this->writeElement('name', $this->namespace); + } + + $this->startElement('directive'); + $this->writeAttribute('id', $directive->id->toString()); + + $this->writeElement('name', $directive->id->getDirective()); + + $this->startElement('aliases'); + foreach ($directive->aliases as $alias) { + $this->writeElement('alias', $alias->toString()); + } + $this->endElement(); // aliases + + $this->startElement('constraints'); + if ($directive->version) { + $this->writeElement('version', $directive->version); + } + $this->startElement('type'); + if ($directive->typeAllowsNull) { + $this->writeAttribute('allow-null', 'yes'); + } + $this->text($directive->type); + $this->endElement(); // type + if ($directive->allowed) { + $this->startElement('allowed'); + foreach ($directive->allowed as $value => $x) { + $this->writeElement('value', $value); + } + $this->endElement(); // allowed + } + $this->writeElement('default', $this->export($directive->default)); + $this->writeAttribute('xml:space', 'preserve'); + if ($directive->external) { + $this->startElement('external'); + foreach ($directive->external as $project) { + $this->writeElement('project', $project); + } + $this->endElement(); + } + $this->endElement(); // constraints + + if ($directive->deprecatedVersion) { + $this->startElement('deprecated'); + $this->writeElement('version', $directive->deprecatedVersion); + $this->writeElement('use', $directive->deprecatedUse->toString()); + $this->endElement(); // deprecated + } + + $this->startElement('description'); + $this->writeHTMLDiv($directive->description); + $this->endElement(); // description + + $this->endElement(); // directive + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php new file mode 100644 index 0000000..2671516 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php @@ -0,0 +1,11 @@ + 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) + { + if (isset($this->directives[$i = $directive->id->toString()])) { + throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); + } + $this->directives[$i] = $directive; + } + + /** + * Convenience function to perform standard validation. Throws exception + * on failed validation. + */ + public function validate() + { + $validator = new HTMLPurifier_ConfigSchema_Validator(); + return $validator->validate($this); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php new file mode 100644 index 0000000..127a39a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php @@ -0,0 +1,89 @@ + true). + * Null if all values are allowed. + * @type array + */ + public $allowed; + + /** + * List of aliases for the directive. + * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))). + * @type HTMLPurifier_ConfigSchema_Interchange_Id[] + */ + public $aliases = array(); + + /** + * Hash of value aliases, e.g. array('alt' => 'real'). Null if value + * aliasing is disabled (necessary for non-scalar types). + * @type array + */ + public $valueAliases; + + /** + * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'. + * Null if the directive has always existed. + * @type string + */ + public $version; + + /** + * ID of directive that supercedes this old directive. + * Null if not deprecated. + * @type HTMLPurifier_ConfigSchema_Interchange_Id + */ + public $deprecatedUse; + + /** + * Version of HTML Purifier this directive was deprecated. Null if not + * deprecated. + * @type string + */ + public $deprecatedVersion; + + /** + * List of external projects this directive depends on, e.g. array('CSSTidy'). + * @type array + */ + public $external = array(); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php new file mode 100644 index 0000000..126f09d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php @@ -0,0 +1,58 @@ +key = $key; + } + + /** + * @return string + * @warning This is NOT magic, to ensure that people don't abuse SPL and + * cause problems for PHP 5.0 support. + */ + public function toString() + { + return $this->key; + } + + /** + * @return string + */ + public function getRootNamespace() + { + return substr($this->key, 0, strpos($this->key, ".")); + } + + /** + * @return string + */ + public function getDirective() + { + return substr($this->key, strpos($this->key, ".") + 1); + } + + /** + * @param string $id + * @return HTMLPurifier_ConfigSchema_Interchange_Id + */ + public static function make($id) + { + return new HTMLPurifier_ConfigSchema_Interchange_Id($id); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php new file mode 100644 index 0000000..655e6dd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php @@ -0,0 +1,226 @@ +varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); + } + + /** + * @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); + } + + /** + * @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']; + } + + $files = array(); + $dh = opendir($dir); + while (false !== ($file = readdir($dh))) { + if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') { + continue; + } + $files[] = $file; + } + closedir($dh); + + sort($files); + foreach ($files as $file) { + $this->buildFile($interchange, $dir . '/' . $file); + } + return $interchange; + } + + /** + * @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)) + ); + } + + /** + * Builds an interchange object based on a hash. + * @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) + { + if (!$hash instanceof HTMLPurifier_StringHash) { + $hash = new HTMLPurifier_StringHash($hash); + } + if (!isset($hash['ID'])) { + throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID'); + } + if (strpos($hash['ID'], '.') === false) { + if (count($hash) == 2 && isset($hash['DESCRIPTION'])) { + $hash->offsetGet('DESCRIPTION'); // prevent complaining + } else { + throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace'); + } + } else { + $this->buildDirective($interchange, $hash); + } + $this->_findUnused($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: + $directive->id = $this->id($hash->offsetGet('ID')); + $id = $directive->id->toString(); // convenience + + if (isset($hash['TYPE'])) { + $type = explode('/', $hash->offsetGet('TYPE')); + if (isset($type[1])) { + $directive->typeAllowsNull = true; + } + $directive->type = $type[0]; + } else { + throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); + } + + if (isset($hash['DEFAULT'])) { + try { + $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'"); + } + } + + if (isset($hash['DESCRIPTION'])) { + $directive->description = $hash->offsetGet('DESCRIPTION'); + } + + if (isset($hash['ALLOWED'])) { + $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED'))); + } + + if (isset($hash['VALUE-ALIASES'])) { + $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES')); + } + + if (isset($hash['ALIASES'])) { + $raw_aliases = trim($hash->offsetGet('ALIASES')); + $aliases = preg_split('/\s*,\s*/', $raw_aliases); + foreach ($aliases as $alias) { + $directive->aliases[] = $this->id($alias); + } + } + + if (isset($hash['VERSION'])) { + $directive->version = $hash->offsetGet('VERSION'); + } + + if (isset($hash['DEPRECATED-USE'])) { + $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE')); + } + + if (isset($hash['DEPRECATED-VERSION'])) { + $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION'); + } + + if (isset($hash['EXTERNAL'])) { + $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL'))); + } + + $interchange->addDirective($directive); + } + + /** + * Evaluates an array PHP code string without array() wrapper + * @param string $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) + { + $ret = array(); + 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) + { + 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 HTMLPurifier_StringHash $hash Hash to check. + */ + protected function _findUnused($hash) + { + $accessed = $hash->getAccessed(); + foreach ($hash as $k => $v) { + if (!isset($accessed[$k])) { + trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php new file mode 100644 index 0000000..fb31277 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php @@ -0,0 +1,248 @@ +parser = new HTMLPurifier_VarParser(); + } + + /** + * Validates a fully-formed interchange object. + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @return bool + */ + 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'"); + } + $this->validateDirective($directive); + } + return true; + } + + /** + * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. + * @param HTMLPurifier_ConfigSchema_Interchange_Id $id + */ + public function validateId($id) + { + $id_string = $id->toString(); + $this->context[] = "id '$id_string'"; + if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { + // handled by InterchangeBuilder + $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id'); + } + // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.) + // we probably should check that it has at least one namespace + $this->with($id, 'key') + ->assertNotEmpty() + ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder + array_pop($this->context); + } + + /** + * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirective($d) + { + $id = $d->id->toString(); + $this->context[] = "directive '$id'"; + $this->validateId($d->id); + + $this->with($d, 'description') + ->assertNotEmpty(); + + // BEGIN - handled by InterchangeBuilder + $this->with($d, 'type') + ->assertNotEmpty(); + $this->with($d, 'typeAllowsNull') + ->assertIsBool(); + try { + // This also tests validity of $d->type + $this->parser->parse($d->default, $d->type, $d->typeAllowsNull); + } catch (HTMLPurifier_VarParserException $e) { + $this->error('default', 'had error: ' . $e->getMessage()); + } + // END - handled by InterchangeBuilder + + if (!is_null($d->allowed) || !empty($d->valueAliases)) { + // allowed and valueAliases require that we be dealing with + // strings, so check for that early. + $d_int = HTMLPurifier_VarParser::$types[$d->type]; + if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) { + $this->error('type', 'must be a string type when used with allowed or value aliases'); + } + } + + $this->validateDirectiveAllowed($d); + $this->validateDirectiveValueAliases($d); + $this->validateDirectiveAliases($d); + + array_pop($this->context); + } + + /** + * 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; + } + $this->with($d, 'allowed') + ->assertNotEmpty() + ->assertIsLookup(); // handled by InterchangeBuilder + if (is_string($d->default) && !isset($d->allowed[$d->default])) { + $this->error('default', 'must be an allowed value'); + } + $this->context[] = 'allowed'; + foreach ($d->allowed as $val => $x) { + if (!is_string($val)) { + $this->error("value $val", 'must be a string'); + } + } + array_pop($this->context); + } + + /** + * 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; + } + $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 ($alias === $real) { + $this->error("alias '$alias'", "must not be an alias to itself"); + } + } + if (!is_null($d->allowed)) { + foreach ($d->valueAliases as $alias => $real) { + if (isset($d->allowed[$alias])) { + $this->error("alias '$alias'", 'must not be an allowed value'); + } elseif (!isset($d->allowed[$real])) { + $this->error("alias '$alias'", 'must be an alias to an allowed value'); + } + } + } + array_pop($this->context); + } + + /** + * Extra validation if $aliases member variable of + * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirectiveAliases($d) + { + $this->with($d, 'aliases') + ->assertIsArray(); // handled by InterchangeBuilder + $this->context[] = 'aliases'; + foreach ($d->aliases as $alias) { + $this->validateId($alias); + $s = $alias->toString(); + if (isset($this->interchange->directives[$s])) { + $this->error("alias '$s'", 'collides with another directive'); + } + if (isset($this->aliases[$s])) { + $other_directive = $this->aliases[$s]; + $this->error("alias '$s'", "collides with alias for directive '$other_directive'"); + } + $this->aliases[$s] = $d->id->toString(); + } + array_pop($this->context); + } + + // protected helper functions + + /** + * 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) + { + 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()); + } + throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); + } + + /** + * Returns a formatted context string. + * @return string + */ + protected function getFormattedContext() + { + return implode(' in ', array_reverse($this->context)); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php new file mode 100644 index 0000000..c9aa364 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php @@ -0,0 +1,130 @@ +context = $context; + $this->obj = $obj; + $this->member = $member; + $this->contents =& $obj->$member; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsString() + { + if (!is_string($this->contents)) { + $this->error('must be a string'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsBool() + { + if (!is_bool($this->contents)) { + $this->error('must be a boolean'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsArray() + { + if (!is_array($this->contents)) { + $this->error('must be an array'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotNull() + { + if ($this->contents === null) { + $this->error('must not be null'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertAlnum() + { + $this->assertIsString(); + if (!ctype_alnum($this->contents)) { + $this->error('must be alphanumeric'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotEmpty() + { + if (empty($this->contents)) { + $this->error('must not be empty'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsLookup() + { + $this->assertIsArray(); + foreach ($this->contents as $v) { + if ($v !== true) { + $this->error('must be a lookup array'); + } + } + return $this; + } + + /** + * @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/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser new file mode 100644 index 0000000..a5426c7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser @@ -0,0 +1 @@ +O:25:"HTMLPurifier_ConfigSchema":3:{s:8:"defaults";a:127:{s:19:"Attr.AllowedClasses";N;s:24:"Attr.AllowedFrameTargets";a:0:{}s:15:"Attr.AllowedRel";a:0:{}s:15:"Attr.AllowedRev";a:0:{}s:18:"Attr.ClassUseCDATA";N;s:20:"Attr.DefaultImageAlt";N;s:24:"Attr.DefaultInvalidImage";s:0:"";s:27:"Attr.DefaultInvalidImageAlt";s:13:"Invalid image";s:19:"Attr.DefaultTextDir";s:3:"ltr";s:13:"Attr.EnableID";b:0;s:21:"Attr.ForbiddenClasses";a:0:{}s:13:"Attr.ID.HTML5";N;s:16:"Attr.IDBlacklist";a:0:{}s:22:"Attr.IDBlacklistRegexp";N;s:13:"Attr.IDPrefix";s:0:"";s:18:"Attr.IDPrefixLocal";s:0:"";s:24:"AutoFormat.AutoParagraph";b:0;s:17:"AutoFormat.Custom";a:0:{}s:25:"AutoFormat.DisplayLinkURI";b:0;s:18:"AutoFormat.Linkify";b:0;s:33:"AutoFormat.PurifierLinkify.DocURL";s:3:"#%s";s:26:"AutoFormat.PurifierLinkify";b:0;s:32:"AutoFormat.RemoveEmpty.Predicate";a:4:{s:8:"colgroup";a:0:{}s:2:"th";a:0:{}s:2:"td";a:0:{}s:6:"iframe";a:1:{i:0;s:3:"src";}}s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";a:2:{s:2:"td";b:1;s:2:"th";b:1;}s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";b:0;s:22:"AutoFormat.RemoveEmpty";b:0;s:39:"AutoFormat.RemoveSpansWithoutAttributes";b:0;s:19:"CSS.AllowDuplicates";b:0;s:18:"CSS.AllowImportant";b:0;s:15:"CSS.AllowTricky";b:0;s:16:"CSS.AllowedFonts";N;s:21:"CSS.AllowedProperties";N;s:17:"CSS.DefinitionRev";i:1;s:23:"CSS.ForbiddenProperties";a:0:{}s:16:"CSS.MaxImgLength";s:6:"1200px";s:15:"CSS.Proprietary";b:0;s:11:"CSS.Trusted";b:0;s:20:"Cache.DefinitionImpl";s:10:"Serializer";s:20:"Cache.SerializerPath";N;s:27:"Cache.SerializerPermissions";i:493;s:22:"Core.AggressivelyFixLt";b:1;s:29:"Core.AggressivelyRemoveScript";b:1;s:28:"Core.AllowHostnameUnderscore";b:0;s:23:"Core.AllowParseManyTags";b:0;s:18:"Core.CollectErrors";b:0;s:18:"Core.ColorKeywords";a:148:{s:9:"aliceblue";s:7:"#F0F8FF";s:12:"antiquewhite";s:7:"#FAEBD7";s:4:"aqua";s:7:"#00FFFF";s:10:"aquamarine";s:7:"#7FFFD4";s:5:"azure";s:7:"#F0FFFF";s:5:"beige";s:7:"#F5F5DC";s:6:"bisque";s:7:"#FFE4C4";s:5:"black";s:7:"#000000";s:14:"blanchedalmond";s:7:"#FFEBCD";s:4:"blue";s:7:"#0000FF";s:10:"blueviolet";s:7:"#8A2BE2";s:5:"brown";s:7:"#A52A2A";s:9:"burlywood";s:7:"#DEB887";s:9:"cadetblue";s:7:"#5F9EA0";s:10:"chartreuse";s:7:"#7FFF00";s:9:"chocolate";s:7:"#D2691E";s:5:"coral";s:7:"#FF7F50";s:14:"cornflowerblue";s:7:"#6495ED";s:8:"cornsilk";s:7:"#FFF8DC";s:7:"crimson";s:7:"#DC143C";s:4:"cyan";s:7:"#00FFFF";s:8:"darkblue";s:7:"#00008B";s:8:"darkcyan";s:7:"#008B8B";s:13:"darkgoldenrod";s:7:"#B8860B";s:8:"darkgray";s:7:"#A9A9A9";s:8:"darkgrey";s:7:"#A9A9A9";s:9:"darkgreen";s:7:"#006400";s:9:"darkkhaki";s:7:"#BDB76B";s:11:"darkmagenta";s:7:"#8B008B";s:14:"darkolivegreen";s:7:"#556B2F";s:10:"darkorange";s:7:"#FF8C00";s:10:"darkorchid";s:7:"#9932CC";s:7:"darkred";s:7:"#8B0000";s:10:"darksalmon";s:7:"#E9967A";s:12:"darkseagreen";s:7:"#8FBC8F";s:13:"darkslateblue";s:7:"#483D8B";s:13:"darkslategray";s:7:"#2F4F4F";s:13:"darkslategrey";s:7:"#2F4F4F";s:13:"darkturquoise";s:7:"#00CED1";s:10:"darkviolet";s:7:"#9400D3";s:8:"deeppink";s:7:"#FF1493";s:11:"deepskyblue";s:7:"#00BFFF";s:7:"dimgray";s:7:"#696969";s:7:"dimgrey";s:7:"#696969";s:10:"dodgerblue";s:7:"#1E90FF";s:9:"firebrick";s:7:"#B22222";s:11:"floralwhite";s:7:"#FFFAF0";s:11:"forestgreen";s:7:"#228B22";s:7:"fuchsia";s:7:"#FF00FF";s:9:"gainsboro";s:7:"#DCDCDC";s:10:"ghostwhite";s:7:"#F8F8FF";s:4:"gold";s:7:"#FFD700";s:9:"goldenrod";s:7:"#DAA520";s:4:"gray";s:7:"#808080";s:4:"grey";s:7:"#808080";s:5:"green";s:7:"#008000";s:11:"greenyellow";s:7:"#ADFF2F";s:8:"honeydew";s:7:"#F0FFF0";s:7:"hotpink";s:7:"#FF69B4";s:9:"indianred";s:7:"#CD5C5C";s:6:"indigo";s:7:"#4B0082";s:5:"ivory";s:7:"#FFFFF0";s:5:"khaki";s:7:"#F0E68C";s:8:"lavender";s:7:"#E6E6FA";s:13:"lavenderblush";s:7:"#FFF0F5";s:9:"lawngreen";s:7:"#7CFC00";s:12:"lemonchiffon";s:7:"#FFFACD";s:9:"lightblue";s:7:"#ADD8E6";s:10:"lightcoral";s:7:"#F08080";s:9:"lightcyan";s:7:"#E0FFFF";s:20:"lightgoldenrodyellow";s:7:"#FAFAD2";s:9:"lightgray";s:7:"#D3D3D3";s:9:"lightgrey";s:7:"#D3D3D3";s:10:"lightgreen";s:7:"#90EE90";s:9:"lightpink";s:7:"#FFB6C1";s:11:"lightsalmon";s:7:"#FFA07A";s:13:"lightseagreen";s:7:"#20B2AA";s:12:"lightskyblue";s:7:"#87CEFA";s:14:"lightslategray";s:7:"#778899";s:14:"lightslategrey";s:7:"#778899";s:14:"lightsteelblue";s:7:"#B0C4DE";s:11:"lightyellow";s:7:"#FFFFE0";s:4:"lime";s:7:"#00FF00";s:9:"limegreen";s:7:"#32CD32";s:5:"linen";s:7:"#FAF0E6";s:7:"magenta";s:7:"#FF00FF";s:6:"maroon";s:7:"#800000";s:16:"mediumaquamarine";s:7:"#66CDAA";s:10:"mediumblue";s:7:"#0000CD";s:12:"mediumorchid";s:7:"#BA55D3";s:12:"mediumpurple";s:7:"#9370DB";s:14:"mediumseagreen";s:7:"#3CB371";s:15:"mediumslateblue";s:7:"#7B68EE";s:17:"mediumspringgreen";s:7:"#00FA9A";s:15:"mediumturquoise";s:7:"#48D1CC";s:15:"mediumvioletred";s:7:"#C71585";s:12:"midnightblue";s:7:"#191970";s:9:"mintcream";s:7:"#F5FFFA";s:9:"mistyrose";s:7:"#FFE4E1";s:8:"moccasin";s:7:"#FFE4B5";s:11:"navajowhite";s:7:"#FFDEAD";s:4:"navy";s:7:"#000080";s:7:"oldlace";s:7:"#FDF5E6";s:5:"olive";s:7:"#808000";s:9:"olivedrab";s:7:"#6B8E23";s:6:"orange";s:7:"#FFA500";s:9:"orangered";s:7:"#FF4500";s:6:"orchid";s:7:"#DA70D6";s:13:"palegoldenrod";s:7:"#EEE8AA";s:9:"palegreen";s:7:"#98FB98";s:13:"paleturquoise";s:7:"#AFEEEE";s:13:"palevioletred";s:7:"#DB7093";s:10:"papayawhip";s:7:"#FFEFD5";s:9:"peachpuff";s:7:"#FFDAB9";s:4:"peru";s:7:"#CD853F";s:4:"pink";s:7:"#FFC0CB";s:4:"plum";s:7:"#DDA0DD";s:10:"powderblue";s:7:"#B0E0E6";s:6:"purple";s:7:"#800080";s:13:"rebeccapurple";s:7:"#663399";s:3:"red";s:7:"#FF0000";s:9:"rosybrown";s:7:"#BC8F8F";s:9:"royalblue";s:7:"#4169E1";s:11:"saddlebrown";s:7:"#8B4513";s:6:"salmon";s:7:"#FA8072";s:10:"sandybrown";s:7:"#F4A460";s:8:"seagreen";s:7:"#2E8B57";s:8:"seashell";s:7:"#FFF5EE";s:6:"sienna";s:7:"#A0522D";s:6:"silver";s:7:"#C0C0C0";s:7:"skyblue";s:7:"#87CEEB";s:9:"slateblue";s:7:"#6A5ACD";s:9:"slategray";s:7:"#708090";s:9:"slategrey";s:7:"#708090";s:4:"snow";s:7:"#FFFAFA";s:11:"springgreen";s:7:"#00FF7F";s:9:"steelblue";s:7:"#4682B4";s:3:"tan";s:7:"#D2B48C";s:4:"teal";s:7:"#008080";s:7:"thistle";s:7:"#D8BFD8";s:6:"tomato";s:7:"#FF6347";s:9:"turquoise";s:7:"#40E0D0";s:6:"violet";s:7:"#EE82EE";s:5:"wheat";s:7:"#F5DEB3";s:5:"white";s:7:"#FFFFFF";s:10:"whitesmoke";s:7:"#F5F5F5";s:6:"yellow";s:7:"#FFFF00";s:11:"yellowgreen";s:7:"#9ACD32";}s:30:"Core.ConvertDocumentToFragment";b:1;s:36:"Core.DirectLexLineNumberSyncInterval";i:0;s:20:"Core.DisableExcludes";b:0;s:15:"Core.EnableIDNA";b:0;s:13:"Core.Encoding";s:5:"utf-8";s:26:"Core.EscapeInvalidChildren";b:0;s:22:"Core.EscapeInvalidTags";b:0;s:29:"Core.EscapeNonASCIICharacters";b:0;s:19:"Core.HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:13:"Core.Language";s:2:"en";s:24:"Core.LegacyEntityDecoder";b:0;s:14:"Core.LexerImpl";N;s:24:"Core.MaintainLineNumbers";N;s:22:"Core.NormalizeNewlines";b:1;s:21:"Core.RemoveInvalidImg";b:1;s:33:"Core.RemoveProcessingInstructions";b:0;s:25:"Core.RemoveScriptContents";N;s:13:"Filter.Custom";a:0:{}s:34:"Filter.ExtractStyleBlocks.Escaping";b:1;s:31:"Filter.ExtractStyleBlocks.Scope";N;s:34:"Filter.ExtractStyleBlocks.TidyImpl";N;s:25:"Filter.ExtractStyleBlocks";b:0;s:14:"Filter.YouTube";b:0;s:12:"HTML.Allowed";N;s:22:"HTML.AllowedAttributes";N;s:20:"HTML.AllowedComments";a:0:{}s:26:"HTML.AllowedCommentsRegexp";N;s:20:"HTML.AllowedElements";N;s:19:"HTML.AllowedModules";N;s:23:"HTML.Attr.Name.UseCDATA";b:0;s:17:"HTML.BlockWrapper";s:1:"p";s:16:"HTML.CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:18:"HTML.CustomDoctype";N;s:17:"HTML.DefinitionID";N;s:18:"HTML.DefinitionRev";i:1;s:12:"HTML.Doctype";N;s:25:"HTML.FlashAllowFullScreen";b:0;s:24:"HTML.ForbiddenAttributes";a:0:{}s:22:"HTML.ForbiddenElements";a:0:{}s:10:"HTML.Forms";b:0;s:17:"HTML.MaxImgLength";i:1200;s:13:"HTML.Nofollow";b:0;s:11:"HTML.Parent";s:3:"div";s:16:"HTML.Proprietary";b:0;s:14:"HTML.SafeEmbed";b:0;s:15:"HTML.SafeIframe";b:0;s:15:"HTML.SafeObject";b:0;s:18:"HTML.SafeScripting";a:0:{}s:11:"HTML.Strict";b:0;s:16:"HTML.TargetBlank";b:0;s:19:"HTML.TargetNoopener";b:1;s:21:"HTML.TargetNoreferrer";b:1;s:12:"HTML.TidyAdd";a:0:{}s:14:"HTML.TidyLevel";s:6:"medium";s:15:"HTML.TidyRemove";a:0:{}s:12:"HTML.Trusted";b:0;s:10:"HTML.XHTML";b:1;s:28:"Output.CommentScriptContents";b:1;s:19:"Output.FixInnerHTML";b:1;s:18:"Output.FlashCompat";b:0;s:14:"Output.Newline";N;s:15:"Output.SortAttr";b:0;s:17:"Output.TidyFormat";b:0;s:17:"Test.ForceNoIconv";b:0;s:18:"URI.AllowedSchemes";a:7:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;s:3:"tel";b:1;}s:8:"URI.Base";N;s:17:"URI.DefaultScheme";s:4:"http";s:16:"URI.DefinitionID";N;s:17:"URI.DefinitionRev";i:1;s:11:"URI.Disable";b:0;s:19:"URI.DisableExternal";b:0;s:28:"URI.DisableExternalResources";b:0;s:20:"URI.DisableResources";b:0;s:8:"URI.Host";N;s:17:"URI.HostBlacklist";a:0:{}s:16:"URI.MakeAbsolute";b:0;s:9:"URI.Munge";N;s:18:"URI.MungeResources";b:0;s:18:"URI.MungeSecretKey";N;s:26:"URI.OverrideAllowedSchemes";b:1;s:20:"URI.SafeIframeRegexp";N;}s:12:"defaultPlist";O:25:"HTMLPurifier_PropertyList":3:{s:7:"*data";a:127:{s:19:"Attr.AllowedClasses";N;s:24:"Attr.AllowedFrameTargets";a:0:{}s:15:"Attr.AllowedRel";a:0:{}s:15:"Attr.AllowedRev";a:0:{}s:18:"Attr.ClassUseCDATA";N;s:20:"Attr.DefaultImageAlt";N;s:24:"Attr.DefaultInvalidImage";s:0:"";s:27:"Attr.DefaultInvalidImageAlt";s:13:"Invalid image";s:19:"Attr.DefaultTextDir";s:3:"ltr";s:13:"Attr.EnableID";b:0;s:21:"Attr.ForbiddenClasses";a:0:{}s:13:"Attr.ID.HTML5";N;s:16:"Attr.IDBlacklist";a:0:{}s:22:"Attr.IDBlacklistRegexp";N;s:13:"Attr.IDPrefix";s:0:"";s:18:"Attr.IDPrefixLocal";s:0:"";s:24:"AutoFormat.AutoParagraph";b:0;s:17:"AutoFormat.Custom";a:0:{}s:25:"AutoFormat.DisplayLinkURI";b:0;s:18:"AutoFormat.Linkify";b:0;s:33:"AutoFormat.PurifierLinkify.DocURL";s:3:"#%s";s:26:"AutoFormat.PurifierLinkify";b:0;s:32:"AutoFormat.RemoveEmpty.Predicate";a:4:{s:8:"colgroup";a:0:{}s:2:"th";a:0:{}s:2:"td";a:0:{}s:6:"iframe";a:1:{i:0;s:3:"src";}}s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";a:2:{s:2:"td";b:1;s:2:"th";b:1;}s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";b:0;s:22:"AutoFormat.RemoveEmpty";b:0;s:39:"AutoFormat.RemoveSpansWithoutAttributes";b:0;s:19:"CSS.AllowDuplicates";b:0;s:18:"CSS.AllowImportant";b:0;s:15:"CSS.AllowTricky";b:0;s:16:"CSS.AllowedFonts";N;s:21:"CSS.AllowedProperties";N;s:17:"CSS.DefinitionRev";i:1;s:23:"CSS.ForbiddenProperties";a:0:{}s:16:"CSS.MaxImgLength";s:6:"1200px";s:15:"CSS.Proprietary";b:0;s:11:"CSS.Trusted";b:0;s:20:"Cache.DefinitionImpl";s:10:"Serializer";s:20:"Cache.SerializerPath";N;s:27:"Cache.SerializerPermissions";i:493;s:22:"Core.AggressivelyFixLt";b:1;s:29:"Core.AggressivelyRemoveScript";b:1;s:28:"Core.AllowHostnameUnderscore";b:0;s:23:"Core.AllowParseManyTags";b:0;s:18:"Core.CollectErrors";b:0;s:18:"Core.ColorKeywords";a:148:{s:9:"aliceblue";s:7:"#F0F8FF";s:12:"antiquewhite";s:7:"#FAEBD7";s:4:"aqua";s:7:"#00FFFF";s:10:"aquamarine";s:7:"#7FFFD4";s:5:"azure";s:7:"#F0FFFF";s:5:"beige";s:7:"#F5F5DC";s:6:"bisque";s:7:"#FFE4C4";s:5:"black";s:7:"#000000";s:14:"blanchedalmond";s:7:"#FFEBCD";s:4:"blue";s:7:"#0000FF";s:10:"blueviolet";s:7:"#8A2BE2";s:5:"brown";s:7:"#A52A2A";s:9:"burlywood";s:7:"#DEB887";s:9:"cadetblue";s:7:"#5F9EA0";s:10:"chartreuse";s:7:"#7FFF00";s:9:"chocolate";s:7:"#D2691E";s:5:"coral";s:7:"#FF7F50";s:14:"cornflowerblue";s:7:"#6495ED";s:8:"cornsilk";s:7:"#FFF8DC";s:7:"crimson";s:7:"#DC143C";s:4:"cyan";s:7:"#00FFFF";s:8:"darkblue";s:7:"#00008B";s:8:"darkcyan";s:7:"#008B8B";s:13:"darkgoldenrod";s:7:"#B8860B";s:8:"darkgray";s:7:"#A9A9A9";s:8:"darkgrey";s:7:"#A9A9A9";s:9:"darkgreen";s:7:"#006400";s:9:"darkkhaki";s:7:"#BDB76B";s:11:"darkmagenta";s:7:"#8B008B";s:14:"darkolivegreen";s:7:"#556B2F";s:10:"darkorange";s:7:"#FF8C00";s:10:"darkorchid";s:7:"#9932CC";s:7:"darkred";s:7:"#8B0000";s:10:"darksalmon";s:7:"#E9967A";s:12:"darkseagreen";s:7:"#8FBC8F";s:13:"darkslateblue";s:7:"#483D8B";s:13:"darkslategray";s:7:"#2F4F4F";s:13:"darkslategrey";s:7:"#2F4F4F";s:13:"darkturquoise";s:7:"#00CED1";s:10:"darkviolet";s:7:"#9400D3";s:8:"deeppink";s:7:"#FF1493";s:11:"deepskyblue";s:7:"#00BFFF";s:7:"dimgray";s:7:"#696969";s:7:"dimgrey";s:7:"#696969";s:10:"dodgerblue";s:7:"#1E90FF";s:9:"firebrick";s:7:"#B22222";s:11:"floralwhite";s:7:"#FFFAF0";s:11:"forestgreen";s:7:"#228B22";s:7:"fuchsia";s:7:"#FF00FF";s:9:"gainsboro";s:7:"#DCDCDC";s:10:"ghostwhite";s:7:"#F8F8FF";s:4:"gold";s:7:"#FFD700";s:9:"goldenrod";s:7:"#DAA520";s:4:"gray";s:7:"#808080";s:4:"grey";s:7:"#808080";s:5:"green";s:7:"#008000";s:11:"greenyellow";s:7:"#ADFF2F";s:8:"honeydew";s:7:"#F0FFF0";s:7:"hotpink";s:7:"#FF69B4";s:9:"indianred";s:7:"#CD5C5C";s:6:"indigo";s:7:"#4B0082";s:5:"ivory";s:7:"#FFFFF0";s:5:"khaki";s:7:"#F0E68C";s:8:"lavender";s:7:"#E6E6FA";s:13:"lavenderblush";s:7:"#FFF0F5";s:9:"lawngreen";s:7:"#7CFC00";s:12:"lemonchiffon";s:7:"#FFFACD";s:9:"lightblue";s:7:"#ADD8E6";s:10:"lightcoral";s:7:"#F08080";s:9:"lightcyan";s:7:"#E0FFFF";s:20:"lightgoldenrodyellow";s:7:"#FAFAD2";s:9:"lightgray";s:7:"#D3D3D3";s:9:"lightgrey";s:7:"#D3D3D3";s:10:"lightgreen";s:7:"#90EE90";s:9:"lightpink";s:7:"#FFB6C1";s:11:"lightsalmon";s:7:"#FFA07A";s:13:"lightseagreen";s:7:"#20B2AA";s:12:"lightskyblue";s:7:"#87CEFA";s:14:"lightslategray";s:7:"#778899";s:14:"lightslategrey";s:7:"#778899";s:14:"lightsteelblue";s:7:"#B0C4DE";s:11:"lightyellow";s:7:"#FFFFE0";s:4:"lime";s:7:"#00FF00";s:9:"limegreen";s:7:"#32CD32";s:5:"linen";s:7:"#FAF0E6";s:7:"magenta";s:7:"#FF00FF";s:6:"maroon";s:7:"#800000";s:16:"mediumaquamarine";s:7:"#66CDAA";s:10:"mediumblue";s:7:"#0000CD";s:12:"mediumorchid";s:7:"#BA55D3";s:12:"mediumpurple";s:7:"#9370DB";s:14:"mediumseagreen";s:7:"#3CB371";s:15:"mediumslateblue";s:7:"#7B68EE";s:17:"mediumspringgreen";s:7:"#00FA9A";s:15:"mediumturquoise";s:7:"#48D1CC";s:15:"mediumvioletred";s:7:"#C71585";s:12:"midnightblue";s:7:"#191970";s:9:"mintcream";s:7:"#F5FFFA";s:9:"mistyrose";s:7:"#FFE4E1";s:8:"moccasin";s:7:"#FFE4B5";s:11:"navajowhite";s:7:"#FFDEAD";s:4:"navy";s:7:"#000080";s:7:"oldlace";s:7:"#FDF5E6";s:5:"olive";s:7:"#808000";s:9:"olivedrab";s:7:"#6B8E23";s:6:"orange";s:7:"#FFA500";s:9:"orangered";s:7:"#FF4500";s:6:"orchid";s:7:"#DA70D6";s:13:"palegoldenrod";s:7:"#EEE8AA";s:9:"palegreen";s:7:"#98FB98";s:13:"paleturquoise";s:7:"#AFEEEE";s:13:"palevioletred";s:7:"#DB7093";s:10:"papayawhip";s:7:"#FFEFD5";s:9:"peachpuff";s:7:"#FFDAB9";s:4:"peru";s:7:"#CD853F";s:4:"pink";s:7:"#FFC0CB";s:4:"plum";s:7:"#DDA0DD";s:10:"powderblue";s:7:"#B0E0E6";s:6:"purple";s:7:"#800080";s:13:"rebeccapurple";s:7:"#663399";s:3:"red";s:7:"#FF0000";s:9:"rosybrown";s:7:"#BC8F8F";s:9:"royalblue";s:7:"#4169E1";s:11:"saddlebrown";s:7:"#8B4513";s:6:"salmon";s:7:"#FA8072";s:10:"sandybrown";s:7:"#F4A460";s:8:"seagreen";s:7:"#2E8B57";s:8:"seashell";s:7:"#FFF5EE";s:6:"sienna";s:7:"#A0522D";s:6:"silver";s:7:"#C0C0C0";s:7:"skyblue";s:7:"#87CEEB";s:9:"slateblue";s:7:"#6A5ACD";s:9:"slategray";s:7:"#708090";s:9:"slategrey";s:7:"#708090";s:4:"snow";s:7:"#FFFAFA";s:11:"springgreen";s:7:"#00FF7F";s:9:"steelblue";s:7:"#4682B4";s:3:"tan";s:7:"#D2B48C";s:4:"teal";s:7:"#008080";s:7:"thistle";s:7:"#D8BFD8";s:6:"tomato";s:7:"#FF6347";s:9:"turquoise";s:7:"#40E0D0";s:6:"violet";s:7:"#EE82EE";s:5:"wheat";s:7:"#F5DEB3";s:5:"white";s:7:"#FFFFFF";s:10:"whitesmoke";s:7:"#F5F5F5";s:6:"yellow";s:7:"#FFFF00";s:11:"yellowgreen";s:7:"#9ACD32";}s:30:"Core.ConvertDocumentToFragment";b:1;s:36:"Core.DirectLexLineNumberSyncInterval";i:0;s:20:"Core.DisableExcludes";b:0;s:15:"Core.EnableIDNA";b:0;s:13:"Core.Encoding";s:5:"utf-8";s:26:"Core.EscapeInvalidChildren";b:0;s:22:"Core.EscapeInvalidTags";b:0;s:29:"Core.EscapeNonASCIICharacters";b:0;s:19:"Core.HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:13:"Core.Language";s:2:"en";s:24:"Core.LegacyEntityDecoder";b:0;s:14:"Core.LexerImpl";N;s:24:"Core.MaintainLineNumbers";N;s:22:"Core.NormalizeNewlines";b:1;s:21:"Core.RemoveInvalidImg";b:1;s:33:"Core.RemoveProcessingInstructions";b:0;s:25:"Core.RemoveScriptContents";N;s:13:"Filter.Custom";a:0:{}s:34:"Filter.ExtractStyleBlocks.Escaping";b:1;s:31:"Filter.ExtractStyleBlocks.Scope";N;s:34:"Filter.ExtractStyleBlocks.TidyImpl";N;s:25:"Filter.ExtractStyleBlocks";b:0;s:14:"Filter.YouTube";b:0;s:12:"HTML.Allowed";N;s:22:"HTML.AllowedAttributes";N;s:20:"HTML.AllowedComments";a:0:{}s:26:"HTML.AllowedCommentsRegexp";N;s:20:"HTML.AllowedElements";N;s:19:"HTML.AllowedModules";N;s:23:"HTML.Attr.Name.UseCDATA";b:0;s:17:"HTML.BlockWrapper";s:1:"p";s:16:"HTML.CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:18:"HTML.CustomDoctype";N;s:17:"HTML.DefinitionID";N;s:18:"HTML.DefinitionRev";i:1;s:12:"HTML.Doctype";N;s:25:"HTML.FlashAllowFullScreen";b:0;s:24:"HTML.ForbiddenAttributes";a:0:{}s:22:"HTML.ForbiddenElements";a:0:{}s:10:"HTML.Forms";b:0;s:17:"HTML.MaxImgLength";i:1200;s:13:"HTML.Nofollow";b:0;s:11:"HTML.Parent";s:3:"div";s:16:"HTML.Proprietary";b:0;s:14:"HTML.SafeEmbed";b:0;s:15:"HTML.SafeIframe";b:0;s:15:"HTML.SafeObject";b:0;s:18:"HTML.SafeScripting";a:0:{}s:11:"HTML.Strict";b:0;s:16:"HTML.TargetBlank";b:0;s:19:"HTML.TargetNoopener";b:1;s:21:"HTML.TargetNoreferrer";b:1;s:12:"HTML.TidyAdd";a:0:{}s:14:"HTML.TidyLevel";s:6:"medium";s:15:"HTML.TidyRemove";a:0:{}s:12:"HTML.Trusted";b:0;s:10:"HTML.XHTML";b:1;s:28:"Output.CommentScriptContents";b:1;s:19:"Output.FixInnerHTML";b:1;s:18:"Output.FlashCompat";b:0;s:14:"Output.Newline";N;s:15:"Output.SortAttr";b:0;s:17:"Output.TidyFormat";b:0;s:17:"Test.ForceNoIconv";b:0;s:18:"URI.AllowedSchemes";a:7:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;s:3:"tel";b:1;}s:8:"URI.Base";N;s:17:"URI.DefaultScheme";s:4:"http";s:16:"URI.DefinitionID";N;s:17:"URI.DefinitionRev";i:1;s:11:"URI.Disable";b:0;s:19:"URI.DisableExternal";b:0;s:28:"URI.DisableExternalResources";b:0;s:20:"URI.DisableResources";b:0;s:8:"URI.Host";N;s:17:"URI.HostBlacklist";a:0:{}s:16:"URI.MakeAbsolute";b:0;s:9:"URI.Munge";N;s:18:"URI.MungeResources";b:0;s:18:"URI.MungeSecretKey";N;s:26:"URI.OverrideAllowedSchemes";b:1;s:20:"URI.SafeIframeRegexp";N;}s:9:"*parent";N;s:8:"*cache";N;}s:4:"info";a:140:{s:19:"Attr.AllowedClasses";i:-8;s:24:"Attr.AllowedFrameTargets";i:8;s:15:"Attr.AllowedRel";i:8;s:15:"Attr.AllowedRev";i:8;s:18:"Attr.ClassUseCDATA";i:-7;s:20:"Attr.DefaultImageAlt";i:-1;s:24:"Attr.DefaultInvalidImage";i:1;s:27:"Attr.DefaultInvalidImageAlt";i:1;s:19:"Attr.DefaultTextDir";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:2:{s:3:"ltr";b:1;s:3:"rtl";b:1;}}s:13:"Attr.EnableID";i:7;s:17:"HTML.EnableAttrID";O:8:"stdClass":2:{s:3:"key";s:13:"Attr.EnableID";s:7:"isAlias";b:1;}s:21:"Attr.ForbiddenClasses";i:8;s:13:"Attr.ID.HTML5";i:-7;s:16:"Attr.IDBlacklist";i:9;s:22:"Attr.IDBlacklistRegexp";i:-1;s:13:"Attr.IDPrefix";i:1;s:18:"Attr.IDPrefixLocal";i:1;s:24:"AutoFormat.AutoParagraph";i:7;s:17:"AutoFormat.Custom";i:9;s:25:"AutoFormat.DisplayLinkURI";i:7;s:18:"AutoFormat.Linkify";i:7;s:33:"AutoFormat.PurifierLinkify.DocURL";i:1;s:37:"AutoFormatParam.PurifierLinkifyDocURL";O:8:"stdClass":2:{s:3:"key";s:33:"AutoFormat.PurifierLinkify.DocURL";s:7:"isAlias";b:1;}s:26:"AutoFormat.PurifierLinkify";i:7;s:32:"AutoFormat.RemoveEmpty.Predicate";i:10;s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";i:8;s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";i:7;s:22:"AutoFormat.RemoveEmpty";i:7;s:39:"AutoFormat.RemoveSpansWithoutAttributes";i:7;s:19:"CSS.AllowDuplicates";i:7;s:18:"CSS.AllowImportant";i:7;s:15:"CSS.AllowTricky";i:7;s:16:"CSS.AllowedFonts";i:-8;s:21:"CSS.AllowedProperties";i:-8;s:17:"CSS.DefinitionRev";i:5;s:23:"CSS.ForbiddenProperties";i:8;s:16:"CSS.MaxImgLength";i:-1;s:15:"CSS.Proprietary";i:7;s:11:"CSS.Trusted";i:7;s:20:"Cache.DefinitionImpl";i:-1;s:20:"Core.DefinitionCache";O:8:"stdClass":2:{s:3:"key";s:20:"Cache.DefinitionImpl";s:7:"isAlias";b:1;}s:20:"Cache.SerializerPath";i:-1;s:27:"Cache.SerializerPermissions";i:-5;s:22:"Core.AggressivelyFixLt";i:7;s:29:"Core.AggressivelyRemoveScript";i:7;s:28:"Core.AllowHostnameUnderscore";i:7;s:23:"Core.AllowParseManyTags";i:7;s:18:"Core.CollectErrors";i:7;s:18:"Core.ColorKeywords";i:10;s:30:"Core.ConvertDocumentToFragment";i:7;s:24:"Core.AcceptFullDocuments";O:8:"stdClass":2:{s:3:"key";s:30:"Core.ConvertDocumentToFragment";s:7:"isAlias";b:1;}s:36:"Core.DirectLexLineNumberSyncInterval";i:5;s:20:"Core.DisableExcludes";i:7;s:15:"Core.EnableIDNA";i:7;s:13:"Core.Encoding";i:2;s:26:"Core.EscapeInvalidChildren";i:7;s:22:"Core.EscapeInvalidTags";i:7;s:29:"Core.EscapeNonASCIICharacters";i:7;s:19:"Core.HiddenElements";i:8;s:13:"Core.Language";i:1;s:24:"Core.LegacyEntityDecoder";i:7;s:14:"Core.LexerImpl";i:-11;s:24:"Core.MaintainLineNumbers";i:-7;s:22:"Core.NormalizeNewlines";i:7;s:21:"Core.RemoveInvalidImg";i:7;s:33:"Core.RemoveProcessingInstructions";i:7;s:25:"Core.RemoveScriptContents";i:-7;s:13:"Filter.Custom";i:9;s:34:"Filter.ExtractStyleBlocks.Escaping";i:7;s:33:"Filter.ExtractStyleBlocksEscaping";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.Escaping";s:7:"isAlias";b:1;}s:38:"FilterParam.ExtractStyleBlocksEscaping";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.Escaping";s:7:"isAlias";b:1;}s:31:"Filter.ExtractStyleBlocks.Scope";i:-1;s:30:"Filter.ExtractStyleBlocksScope";O:8:"stdClass":2:{s:3:"key";s:31:"Filter.ExtractStyleBlocks.Scope";s:7:"isAlias";b:1;}s:35:"FilterParam.ExtractStyleBlocksScope";O:8:"stdClass":2:{s:3:"key";s:31:"Filter.ExtractStyleBlocks.Scope";s:7:"isAlias";b:1;}s:34:"Filter.ExtractStyleBlocks.TidyImpl";i:-11;s:38:"FilterParam.ExtractStyleBlocksTidyImpl";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.TidyImpl";s:7:"isAlias";b:1;}s:25:"Filter.ExtractStyleBlocks";i:7;s:14:"Filter.YouTube";i:7;s:12:"HTML.Allowed";i:-4;s:22:"HTML.AllowedAttributes";i:-8;s:20:"HTML.AllowedComments";i:8;s:26:"HTML.AllowedCommentsRegexp";i:-1;s:20:"HTML.AllowedElements";i:-8;s:19:"HTML.AllowedModules";i:-8;s:23:"HTML.Attr.Name.UseCDATA";i:7;s:17:"HTML.BlockWrapper";i:1;s:16:"HTML.CoreModules";i:8;s:18:"HTML.CustomDoctype";i:-1;s:17:"HTML.DefinitionID";i:-1;s:18:"HTML.DefinitionRev";i:5;s:12:"HTML.Doctype";O:8:"stdClass":3:{s:4:"type";i:1;s:10:"allow_null";b:1;s:7:"allowed";a:5:{s:22:"HTML 4.01 Transitional";b:1;s:16:"HTML 4.01 Strict";b:1;s:22:"XHTML 1.0 Transitional";b:1;s:16:"XHTML 1.0 Strict";b:1;s:9:"XHTML 1.1";b:1;}}s:25:"HTML.FlashAllowFullScreen";i:7;s:24:"HTML.ForbiddenAttributes";i:8;s:22:"HTML.ForbiddenElements";i:8;s:10:"HTML.Forms";i:7;s:17:"HTML.MaxImgLength";i:-5;s:13:"HTML.Nofollow";i:7;s:11:"HTML.Parent";i:1;s:16:"HTML.Proprietary";i:7;s:14:"HTML.SafeEmbed";i:7;s:15:"HTML.SafeIframe";i:7;s:15:"HTML.SafeObject";i:7;s:18:"HTML.SafeScripting";i:8;s:11:"HTML.Strict";i:7;s:16:"HTML.TargetBlank";i:7;s:19:"HTML.TargetNoopener";i:7;s:21:"HTML.TargetNoreferrer";i:7;s:12:"HTML.TidyAdd";i:8;s:14:"HTML.TidyLevel";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:4:{s:4:"none";b:1;s:5:"light";b:1;s:6:"medium";b:1;s:5:"heavy";b:1;}}s:15:"HTML.TidyRemove";i:8;s:12:"HTML.Trusted";i:7;s:10:"HTML.XHTML";i:7;s:10:"Core.XHTML";O:8:"stdClass":2:{s:3:"key";s:10:"HTML.XHTML";s:7:"isAlias";b:1;}s:28:"Output.CommentScriptContents";i:7;s:26:"Core.CommentScriptContents";O:8:"stdClass":2:{s:3:"key";s:28:"Output.CommentScriptContents";s:7:"isAlias";b:1;}s:19:"Output.FixInnerHTML";i:7;s:18:"Output.FlashCompat";i:7;s:14:"Output.Newline";i:-1;s:15:"Output.SortAttr";i:7;s:17:"Output.TidyFormat";i:7;s:15:"Core.TidyFormat";O:8:"stdClass":2:{s:3:"key";s:17:"Output.TidyFormat";s:7:"isAlias";b:1;}s:17:"Test.ForceNoIconv";i:7;s:18:"URI.AllowedSchemes";i:8;s:8:"URI.Base";i:-1;s:17:"URI.DefaultScheme";i:-1;s:16:"URI.DefinitionID";i:-1;s:17:"URI.DefinitionRev";i:5;s:11:"URI.Disable";i:7;s:15:"Attr.DisableURI";O:8:"stdClass":2:{s:3:"key";s:11:"URI.Disable";s:7:"isAlias";b:1;}s:19:"URI.DisableExternal";i:7;s:28:"URI.DisableExternalResources";i:7;s:20:"URI.DisableResources";i:7;s:8:"URI.Host";i:-1;s:17:"URI.HostBlacklist";i:9;s:16:"URI.MakeAbsolute";i:7;s:9:"URI.Munge";i:-1;s:18:"URI.MungeResources";i:7;s:18:"URI.MungeSecretKey";i:-1;s:26:"URI.OverrideAllowedSchemes";i:7;s:20:"URI.SafeIframeRegexp";i:-1;}} \ No newline at end of file diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt new file mode 100644 index 0000000..0517fed --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt @@ -0,0 +1,8 @@ +Attr.AllowedClasses +TYPE: lookup/null +VERSION: 4.0.0 +DEFAULT: null +--DESCRIPTION-- +List of allowed class values in the class attribute. By default, this is null, +which means all classes are allowed. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt new file mode 100644 index 0000000..249edd6 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt @@ -0,0 +1,12 @@ +Attr.AllowedFrameTargets +TYPE: lookup +DEFAULT: array() +--DESCRIPTION-- +Lookup table of all allowed link frame targets. Some commonly used link +targets include _blank, _self, _parent and _top. Values should be +lowercase, as validation will be done in a case-sensitive manner despite +W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute +so this directive will have no effect in that doctype. XHTML 1.1 does not +enable the Target module by default, you will have to manually enable it +(see the module documentation for more details.) +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt new file mode 100644 index 0000000..9a8fa6a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt @@ -0,0 +1,9 @@ +Attr.AllowedRel +TYPE: lookup +VERSION: 1.6.0 +DEFAULT: array() +--DESCRIPTION-- +List of allowed forward document relationships in the rel attribute. Common +values may be nofollow or print. By default, this is empty, meaning that no +document relationships are allowed. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt new file mode 100644 index 0000000..b017883 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt @@ -0,0 +1,9 @@ +Attr.AllowedRev +TYPE: lookup +VERSION: 1.6.0 +DEFAULT: array() +--DESCRIPTION-- +List of allowed reverse document relationships in the rev attribute. This +attribute is a bit of an edge-case; if you don't know what it is for, stay +away. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt new file mode 100644 index 0000000..e774b82 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt @@ -0,0 +1,19 @@ +Attr.ClassUseCDATA +TYPE: bool/null +DEFAULT: null +VERSION: 4.0.0 +--DESCRIPTION-- +If null, class will auto-detect the doctype and, if matching XHTML 1.1 or +XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise, +it will use a relaxed CDATA definition. If true, the relaxed CDATA definition +is forced; if false, the NMTOKENS definition is forced. To get behavior +of HTML Purifier prior to 4.0.0, set this directive to false. + +Some rational behind the auto-detection: +in previous versions of HTML Purifier, it was assumed that the form of +class was NMTOKENS, as specified by the XHTML Modularization (representing +XHTML 1.1 and XHTML 2.0). The DTDs for HTML 4.01 and XHTML 1.0, however +specify class as CDATA. HTML 5 effectively defines it as CDATA, but +with the additional constraint that each name should be unique (this is not +explicitly outlined in previous specifications). +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt new file mode 100644 index 0000000..533165e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt @@ -0,0 +1,11 @@ +Attr.DefaultImageAlt +TYPE: string/null +DEFAULT: null +VERSION: 3.2.0 +--DESCRIPTION-- +This is the content of the alt tag of an image if the user had not +previously specified an alt attribute. This applies to all images without +a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which +only applies to invalid images, and overrides in the case of an invalid image. +Default behavior with null is to use the basename of the src tag for the alt. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt new file mode 100644 index 0000000..9eb7e38 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt @@ -0,0 +1,9 @@ +Attr.DefaultInvalidImage +TYPE: string +DEFAULT: '' +--DESCRIPTION-- +This is the default image an img tag will be pointed to if it does not have +a valid src attribute. In future versions, we may allow the image tag to +be removed completely, but due to design issues, this is not possible right +now. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt new file mode 100644 index 0000000..2f17bf4 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt @@ -0,0 +1,8 @@ +Attr.DefaultInvalidImageAlt +TYPE: string +DEFAULT: 'Invalid image' +--DESCRIPTION-- +This is the content of the alt tag of an invalid image if the user had not +previously specified an alt attribute. It has no effect when the image is +valid but there was no alt attribute present. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt new file mode 100644 index 0000000..52654b5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt @@ -0,0 +1,10 @@ +Attr.DefaultTextDir +TYPE: string +DEFAULT: 'ltr' +--DESCRIPTION-- +Defines the default text direction (ltr or rtl) of the document being +parsed. This generally is the same as the value of the dir attribute in +HTML, or ltr if that is not specified. +--ALLOWED-- +'ltr', 'rtl' +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt new file mode 100644 index 0000000..6440d21 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt @@ -0,0 +1,16 @@ +Attr.EnableID +TYPE: bool +DEFAULT: false +VERSION: 1.2.0 +--DESCRIPTION-- +Allows the ID attribute in HTML. This is disabled by default due to the +fact that without proper configuration user input can easily break the +validation of a webpage by specifying an ID that is already on the +surrounding HTML. If you don't mind throwing caution to the wind, enable +this directive, but I strongly recommend you also consider blacklisting IDs +you use (%Attr.IDBlacklist) or prefixing all user supplied IDs +(%Attr.IDPrefix). When set to true HTML Purifier reverts to the behavior of +pre-1.2.0 versions. +--ALIASES-- +HTML.EnableAttrID +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt new file mode 100644 index 0000000..f31d226 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt @@ -0,0 +1,8 @@ +Attr.ForbiddenClasses +TYPE: lookup +VERSION: 4.0.0 +DEFAULT: array() +--DESCRIPTION-- +List of forbidden class values in the class attribute. By default, this is +empty, which means that no classes are forbidden. See also %Attr.AllowedClasses. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt new file mode 100644 index 0000000..735d4b7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt @@ -0,0 +1,10 @@ +Attr.ID.HTML5 +TYPE: bool/null +DEFAULT: null +VERSION: 4.8.0 +--DESCRIPTION-- +In HTML5, restrictions on the format of the id attribute have been significantly +relaxed, such that any string is valid so long as it contains no spaces and +is at least one character. In lieu of a general HTML5 compatibility flag, +set this configuration directive to true to use the relaxed rules. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt new file mode 100644 index 0000000..5f2b5e3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt @@ -0,0 +1,5 @@ +Attr.IDBlacklist +TYPE: list +DEFAULT: array() +DESCRIPTION: Array of IDs not allowed in the document. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt new file mode 100644 index 0000000..6f58245 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt @@ -0,0 +1,9 @@ +Attr.IDBlacklistRegexp +TYPE: string/null +VERSION: 1.6.0 +DEFAULT: NULL +--DESCRIPTION-- +PCRE regular expression to be matched against all IDs. If the expression is +matches, the ID is rejected. Use this with care: may cause significant +degradation. ID matching is done after all other validation. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt new file mode 100644 index 0000000..cc49d43 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt @@ -0,0 +1,12 @@ +Attr.IDPrefix +TYPE: string +VERSION: 1.2.0 +DEFAULT: '' +--DESCRIPTION-- +String to prefix to IDs. If you have no idea what IDs your pages may use, +you may opt to simply add a prefix to all user-submitted ID attributes so +that they are still usable, but will not conflict with core page IDs. +Example: setting the directive to 'user_' will result in a user submitted +'foo' to become 'user_foo' Be sure to set %HTML.EnableAttrID to true +before using this. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt new file mode 100644 index 0000000..2c5924a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt @@ -0,0 +1,14 @@ +Attr.IDPrefixLocal +TYPE: string +VERSION: 1.2.0 +DEFAULT: '' +--DESCRIPTION-- +Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If you +need to allow multiple sets of user content on web page, you may need to +have a seperate prefix that changes with each iteration. This way, +seperately submitted user content displayed on the same page doesn't +clobber each other. Ideal values are unique identifiers for the content it +represents (i.e. the id of the row in the database). Be sure to add a +seperator (like an underscore) at the end. Warning: this directive will +not work unless %Attr.IDPrefix is set to a non-empty value! +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt new file mode 100644 index 0000000..d5caa1b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt @@ -0,0 +1,31 @@ +AutoFormat.AutoParagraph +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

+ This directive turns on auto-paragraphing, where double newlines are + converted in to paragraphs whenever possible. Auto-paragraphing: +

+
    +
  • Always applies to inline elements or text in the root node,
  • +
  • Applies to inline elements or text with double newlines in nodes + that allow paragraph tags,
  • +
  • Applies to double newlines in paragraph tags
  • +
+

+ p tags must be allowed for this directive to take effect. + We do not use br tags for paragraphing, as that is + semantically incorrect. +

+

+ To prevent auto-paragraphing as a content-producer, refrain from using + double-newlines except to specify a new paragraph or in contexts where + it has special meaning (whitespace usually has no meaning except in + tags like pre, so this should not be difficult.) To prevent + the paragraphing of inline text adjacent to block elements, wrap them + in div tags (the behavior is slightly different outside of + the root node.) +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt new file mode 100644 index 0000000..2a47648 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt @@ -0,0 +1,12 @@ +AutoFormat.Custom +TYPE: list +VERSION: 2.0.1 +DEFAULT: array() +--DESCRIPTION-- + +

+ This directive can be used to add custom auto-format injectors. + Specify an array of injector names (class name minus the prefix) + or concrete implementations. Injector class must exist. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt new file mode 100644 index 0000000..663064a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt @@ -0,0 +1,11 @@ +AutoFormat.DisplayLinkURI +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

+ This directive turns on the in-text display of URIs in <a> tags, and disables + those links. For example, example becomes + example (http://example.com). +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt new file mode 100644 index 0000000..3a48ba9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt @@ -0,0 +1,12 @@ +AutoFormat.Linkify +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

+ This directive turns on linkification, auto-linking http, ftp and + https URLs. a tags with the href attribute + must be allowed. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt new file mode 100644 index 0000000..db58b13 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt @@ -0,0 +1,12 @@ +AutoFormat.PurifierLinkify.DocURL +TYPE: string +VERSION: 2.0.1 +DEFAULT: '#%s' +ALIASES: AutoFormatParam.PurifierLinkifyDocURL +--DESCRIPTION-- +

+ Location of configuration documentation to link to, let %s substitute + into the configuration's namespace and directive names sans the percent + sign. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt new file mode 100644 index 0000000..7996488 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt @@ -0,0 +1,12 @@ +AutoFormat.PurifierLinkify +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

+ Internal auto-formatter that converts configuration directives in + syntax %Namespace.Directive to links. a tags + with the href attribute must be allowed. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt new file mode 100644 index 0000000..6367fe2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.Predicate.txt @@ -0,0 +1,14 @@ +AutoFormat.RemoveEmpty.Predicate +TYPE: hash +VERSION: 4.7.0 +DEFAULT: array('colgroup' => array(), 'th' => array(), 'td' => array(), 'iframe' => array('src')) +--DESCRIPTION-- +

+ Given that an element has no contents, it will be removed by default, unless + this predicate dictates otherwise. The predicate can either be an associative + map from tag name to list of attributes that must be present for the element + to be considered preserved: thus, the default always preserves colgroup, + th and td, and also iframe if it + has a src. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt new file mode 100644 index 0000000..35c393b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt @@ -0,0 +1,11 @@ +AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions +TYPE: lookup +VERSION: 4.0.0 +DEFAULT: array('td' => true, 'th' => true) +--DESCRIPTION-- +

+ When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp + are enabled, this directive defines what HTML elements should not be + removede if they have only a non-breaking space in them. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt new file mode 100644 index 0000000..9228dee --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt @@ -0,0 +1,15 @@ +AutoFormat.RemoveEmpty.RemoveNbsp +TYPE: bool +VERSION: 4.0.0 +DEFAULT: false +--DESCRIPTION-- +

+ When enabled, HTML Purifier will treat any elements that contain only + non-breaking spaces as well as regular whitespace as empty, and remove + them when %AutoFormat.RemoveEmpty is enabled. +

+

+ See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements + that don't have this behavior applied to them. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt new file mode 100644 index 0000000..34657ba --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt @@ -0,0 +1,46 @@ +AutoFormat.RemoveEmpty +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

+ When enabled, HTML Purifier will attempt to remove empty elements that + contribute no semantic information to the document. The following types + of nodes will be removed: +

+
  • + Tags with no attributes and no content, and that are not empty + elements (remove <a></a> but not + <br />), and +
  • +
  • + Tags with no content, except for:
      +
    • The colgroup element, or
    • +
    • + Elements with the id or name attribute, + when those attributes are permitted on those elements. +
    • +
  • +
+

+ Please be very careful when using this functionality; while it may not + seem that empty elements contain useful information, they can alter the + layout of a document given appropriate styling. This directive is most + useful when you are processing machine-generated HTML, please avoid using + it on regular user HTML. +

+

+ Elements that contain only whitespace will be treated as empty. Non-breaking + spaces, however, do not count as whitespace. See + %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior. +

+

+ This algorithm is not perfect; you may still notice some empty tags, + particularly if a node had elements, but those elements were later removed + because they were not permitted in that context, or tags that, after + being auto-closed by another tag, where empty. This is for safety reasons + to prevent clever code from breaking validation. The general rule of thumb: + if a tag looked empty on the way in, it will get removed; if HTML Purifier + made it empty, it will stay. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt new file mode 100644 index 0000000..dde990a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt @@ -0,0 +1,11 @@ +AutoFormat.RemoveSpansWithoutAttributes +TYPE: bool +VERSION: 4.0.1 +DEFAULT: false +--DESCRIPTION-- +

+ This directive causes span tags without any attributes + to be removed. It will also remove spans that had all attributes + removed during processing. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt new file mode 100644 index 0000000..4d054b1 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowDuplicates.txt @@ -0,0 +1,11 @@ +CSS.AllowDuplicates +TYPE: bool +DEFAULT: false +VERSION: 4.8.0 +--DESCRIPTION-- +

+ By default, HTML Purifier removes duplicate CSS properties, + like color:red; color:blue. If this is set to + true, duplicate properties are allowed. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt new file mode 100644 index 0000000..b324608 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt @@ -0,0 +1,8 @@ +CSS.AllowImportant +TYPE: bool +DEFAULT: false +VERSION: 3.1.0 +--DESCRIPTION-- +This parameter determines whether or not !important cascade modifiers should +be allowed in user CSS. If false, !important will stripped. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt new file mode 100644 index 0000000..748be0e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt @@ -0,0 +1,11 @@ +CSS.AllowTricky +TYPE: bool +DEFAULT: false +VERSION: 3.1.0 +--DESCRIPTION-- +This parameter determines whether or not to allow "tricky" CSS properties and +values. Tricky CSS properties/values can drastically modify page layout or +be used for deceptive practices but do not directly constitute a security risk. +For example, display:none; is considered a tricky property that +will only be allowed if this directive is set to true. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt new file mode 100644 index 0000000..3fd4654 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt @@ -0,0 +1,12 @@ +CSS.AllowedFonts +TYPE: lookup/null +VERSION: 4.3.0 +DEFAULT: NULL +--DESCRIPTION-- +

+ Allows you to manually specify a set of allowed fonts. If + NULL, all fonts are allowed. This directive + affects generic names (serif, sans-serif, monospace, cursive, + fantasy) as well as specific font families. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt new file mode 100644 index 0000000..460112e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt @@ -0,0 +1,18 @@ +CSS.AllowedProperties +TYPE: lookup/null +VERSION: 3.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ If HTML Purifier's style attributes set is unsatisfactory for your needs, + you can overload it with your own list of tags to allow. Note that this + method is subtractive: it does its job by taking away from HTML Purifier + usual feature set, so you cannot add an attribute that HTML Purifier never + supported in the first place. +

+

+ Warning: If another directive conflicts with the + elements here, that directive will win and override. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt new file mode 100644 index 0000000..5cb7dda --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt @@ -0,0 +1,11 @@ +CSS.DefinitionRev +TYPE: int +VERSION: 2.0.0 +DEFAULT: 1 +--DESCRIPTION-- + +

+ Revision identifier for your custom definition. See + %HTML.DefinitionRev for details. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt new file mode 100644 index 0000000..f1f5c5f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt @@ -0,0 +1,13 @@ +CSS.ForbiddenProperties +TYPE: lookup +VERSION: 4.2.0 +DEFAULT: array() +--DESCRIPTION-- +

+ This is the logical inverse of %CSS.AllowedProperties, and it will + override that directive or any other directive. If possible, + %CSS.AllowedProperties is recommended over this directive, + because it can sometimes be difficult to tell whether or not you've + forbidden all of the CSS properties you truly would like to disallow. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt new file mode 100644 index 0000000..7a32914 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt @@ -0,0 +1,16 @@ +CSS.MaxImgLength +TYPE: string/null +DEFAULT: '1200px' +VERSION: 3.1.1 +--DESCRIPTION-- +

+ This parameter sets the maximum allowed length on img tags, + effectively the width and height properties. + Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is + in place to prevent imagecrash attacks, disable with null at your own risk. + This directive is similar to %HTML.MaxImgLength, and both should be + concurrently edited, although there are + subtle differences in the input format (the CSS max is a number with + a unit). +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt new file mode 100644 index 0000000..148eedb --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt @@ -0,0 +1,10 @@ +CSS.Proprietary +TYPE: bool +VERSION: 3.0.0 +DEFAULT: false +--DESCRIPTION-- + +

+ Whether or not to allow safe, proprietary CSS values. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt new file mode 100644 index 0000000..e733a61 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt @@ -0,0 +1,9 @@ +CSS.Trusted +TYPE: bool +VERSION: 4.2.1 +DEFAULT: false +--DESCRIPTION-- +Indicates whether or not the user's CSS input is trusted or not. If the +input is trusted, a more expansive set of allowed properties. See +also %HTML.Trusted. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt new file mode 100644 index 0000000..c486724 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt @@ -0,0 +1,14 @@ +Cache.DefinitionImpl +TYPE: string/null +VERSION: 2.0.0 +DEFAULT: 'Serializer' +--DESCRIPTION-- + +This directive defines which method to use when caching definitions, +the complex data-type that makes HTML Purifier tick. Set to null +to disable caching (not recommended, as you will see a definite +performance degradation). + +--ALIASES-- +Core.DefinitionCache +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt new file mode 100644 index 0000000..5403650 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt @@ -0,0 +1,13 @@ +Cache.SerializerPath +TYPE: string/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ Absolute path with no trailing slash to store serialized definitions in. + Default is within the + HTML Purifier library inside DefinitionCache/Serializer. This + path must be writable by the webserver. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt new file mode 100644 index 0000000..2e0cc81 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt @@ -0,0 +1,16 @@ +Cache.SerializerPermissions +TYPE: int/null +VERSION: 4.3.0 +DEFAULT: 0755 +--DESCRIPTION-- + +

+ Directory permissions of the files and directories created inside + the DefinitionCache/Serializer or other custom serializer path. +

+

+ In HTML Purifier 4.8.0, this also supports NULL, + which means that no chmod'ing or directory creation shall + occur. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt new file mode 100644 index 0000000..568cbf3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt @@ -0,0 +1,18 @@ +Core.AggressivelyFixLt +TYPE: bool +VERSION: 2.1.0 +DEFAULT: true +--DESCRIPTION-- +

+ This directive enables aggressive pre-filter fixes HTML Purifier can + perform in order to ensure that open angled-brackets do not get killed + during parsing stage. Enabling this will result in two preg_replace_callback + calls and at least two preg_replace calls for every HTML document parsed; + if your users make very well-formed HTML, you can set this directive false. + This has no effect when DirectLex is used. +

+

+ Notice: This directive's default turned from false to true + in HTML Purifier 3.2.0. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt new file mode 100644 index 0000000..b2b6ab1 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyRemoveScript.txt @@ -0,0 +1,16 @@ +Core.AggressivelyRemoveScript +TYPE: bool +VERSION: 4.9.0 +DEFAULT: true +--DESCRIPTION-- +

+ This directive enables aggressive pre-filter removal of + script tags. This is not necessary for security, + but it can help work around a bug in libxml where embedded + HTML elements inside script sections cause the parser to + choke. To revert to pre-4.9.0 behavior, set this to false. + This directive has no effect if %Core.Trusted is true, + %Core.RemoveScriptContents is false, or %Core.HiddenElements + does not contain script. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt new file mode 100644 index 0000000..2c910cc --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/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/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt new file mode 100644 index 0000000..06278f8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowParseManyTags.txt @@ -0,0 +1,12 @@ +Core.AllowParseManyTags +TYPE: bool +DEFAULT: false +VERSION: 4.10.1 +--DESCRIPTION-- +

+ This directive allows parsing of many nested tags. + If you set true, relaxes any hardcoded limit from the parser. + However, in that case it may cause a Dos attack. + Be careful when enabling it. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt new file mode 100644 index 0000000..d731791 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt @@ -0,0 +1,12 @@ +Core.CollectErrors +TYPE: bool +VERSION: 2.0.0 +DEFAULT: false +--DESCRIPTION-- + +Whether or not to collect errors found while filtering the document. This +is a useful way to give feedback to your users. Warning: +Currently this feature is very patchy and experimental, with lots of +possible error messages not yet implemented. It will not cause any +problems, but it may not help your users either. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt new file mode 100644 index 0000000..a75844c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt @@ -0,0 +1,160 @@ +Core.ColorKeywords +TYPE: hash +VERSION: 2.0.0 +--DEFAULT-- +array ( + 'aliceblue' => '#F0F8FF', + 'antiquewhite' => '#FAEBD7', + 'aqua' => '#00FFFF', + 'aquamarine' => '#7FFFD4', + 'azure' => '#F0FFFF', + 'beige' => '#F5F5DC', + 'bisque' => '#FFE4C4', + 'black' => '#000000', + 'blanchedalmond' => '#FFEBCD', + 'blue' => '#0000FF', + 'blueviolet' => '#8A2BE2', + 'brown' => '#A52A2A', + 'burlywood' => '#DEB887', + 'cadetblue' => '#5F9EA0', + 'chartreuse' => '#7FFF00', + 'chocolate' => '#D2691E', + 'coral' => '#FF7F50', + 'cornflowerblue' => '#6495ED', + 'cornsilk' => '#FFF8DC', + 'crimson' => '#DC143C', + 'cyan' => '#00FFFF', + 'darkblue' => '#00008B', + 'darkcyan' => '#008B8B', + 'darkgoldenrod' => '#B8860B', + 'darkgray' => '#A9A9A9', + 'darkgrey' => '#A9A9A9', + 'darkgreen' => '#006400', + 'darkkhaki' => '#BDB76B', + 'darkmagenta' => '#8B008B', + 'darkolivegreen' => '#556B2F', + 'darkorange' => '#FF8C00', + 'darkorchid' => '#9932CC', + 'darkred' => '#8B0000', + 'darksalmon' => '#E9967A', + 'darkseagreen' => '#8FBC8F', + 'darkslateblue' => '#483D8B', + 'darkslategray' => '#2F4F4F', + 'darkslategrey' => '#2F4F4F', + 'darkturquoise' => '#00CED1', + 'darkviolet' => '#9400D3', + 'deeppink' => '#FF1493', + 'deepskyblue' => '#00BFFF', + 'dimgray' => '#696969', + 'dimgrey' => '#696969', + 'dodgerblue' => '#1E90FF', + 'firebrick' => '#B22222', + 'floralwhite' => '#FFFAF0', + 'forestgreen' => '#228B22', + 'fuchsia' => '#FF00FF', + 'gainsboro' => '#DCDCDC', + 'ghostwhite' => '#F8F8FF', + 'gold' => '#FFD700', + 'goldenrod' => '#DAA520', + 'gray' => '#808080', + 'grey' => '#808080', + 'green' => '#008000', + 'greenyellow' => '#ADFF2F', + 'honeydew' => '#F0FFF0', + 'hotpink' => '#FF69B4', + 'indianred' => '#CD5C5C', + 'indigo' => '#4B0082', + 'ivory' => '#FFFFF0', + 'khaki' => '#F0E68C', + 'lavender' => '#E6E6FA', + 'lavenderblush' => '#FFF0F5', + 'lawngreen' => '#7CFC00', + 'lemonchiffon' => '#FFFACD', + 'lightblue' => '#ADD8E6', + 'lightcoral' => '#F08080', + 'lightcyan' => '#E0FFFF', + 'lightgoldenrodyellow' => '#FAFAD2', + 'lightgray' => '#D3D3D3', + 'lightgrey' => '#D3D3D3', + 'lightgreen' => '#90EE90', + 'lightpink' => '#FFB6C1', + 'lightsalmon' => '#FFA07A', + 'lightseagreen' => '#20B2AA', + 'lightskyblue' => '#87CEFA', + 'lightslategray' => '#778899', + 'lightslategrey' => '#778899', + 'lightsteelblue' => '#B0C4DE', + 'lightyellow' => '#FFFFE0', + 'lime' => '#00FF00', + 'limegreen' => '#32CD32', + 'linen' => '#FAF0E6', + 'magenta' => '#FF00FF', + 'maroon' => '#800000', + 'mediumaquamarine' => '#66CDAA', + 'mediumblue' => '#0000CD', + 'mediumorchid' => '#BA55D3', + 'mediumpurple' => '#9370DB', + 'mediumseagreen' => '#3CB371', + 'mediumslateblue' => '#7B68EE', + 'mediumspringgreen' => '#00FA9A', + 'mediumturquoise' => '#48D1CC', + 'mediumvioletred' => '#C71585', + 'midnightblue' => '#191970', + 'mintcream' => '#F5FFFA', + 'mistyrose' => '#FFE4E1', + 'moccasin' => '#FFE4B5', + 'navajowhite' => '#FFDEAD', + 'navy' => '#000080', + 'oldlace' => '#FDF5E6', + 'olive' => '#808000', + 'olivedrab' => '#6B8E23', + 'orange' => '#FFA500', + 'orangered' => '#FF4500', + 'orchid' => '#DA70D6', + 'palegoldenrod' => '#EEE8AA', + 'palegreen' => '#98FB98', + 'paleturquoise' => '#AFEEEE', + 'palevioletred' => '#DB7093', + 'papayawhip' => '#FFEFD5', + 'peachpuff' => '#FFDAB9', + 'peru' => '#CD853F', + 'pink' => '#FFC0CB', + 'plum' => '#DDA0DD', + 'powderblue' => '#B0E0E6', + 'purple' => '#800080', + 'rebeccapurple' => '#663399', + 'red' => '#FF0000', + 'rosybrown' => '#BC8F8F', + 'royalblue' => '#4169E1', + 'saddlebrown' => '#8B4513', + 'salmon' => '#FA8072', + 'sandybrown' => '#F4A460', + 'seagreen' => '#2E8B57', + 'seashell' => '#FFF5EE', + 'sienna' => '#A0522D', + 'silver' => '#C0C0C0', + 'skyblue' => '#87CEEB', + 'slateblue' => '#6A5ACD', + 'slategray' => '#708090', + 'slategrey' => '#708090', + 'snow' => '#FFFAFA', + 'springgreen' => '#00FF7F', + 'steelblue' => '#4682B4', + 'tan' => '#D2B48C', + 'teal' => '#008080', + 'thistle' => '#D8BFD8', + 'tomato' => '#FF6347', + 'turquoise' => '#40E0D0', + 'violet' => '#EE82EE', + 'wheat' => '#F5DEB3', + 'white' => '#FFFFFF', + 'whitesmoke' => '#F5F5F5', + 'yellow' => '#FFFF00', + 'yellowgreen' => '#9ACD32' +) +--DESCRIPTION-- + +Lookup array of color names to six digit hexadecimal number corresponding +to color, with preceding hash mark. Used when parsing colors. The lookup +is done in a case-insensitive manner. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt new file mode 100644 index 0000000..64b114f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt @@ -0,0 +1,14 @@ +Core.ConvertDocumentToFragment +TYPE: bool +DEFAULT: true +--DESCRIPTION-- + +This parameter determines whether or not the filter should convert +input that is a full document with html and body tags to a fragment +of just the contents of a body tag. This parameter is simply something +HTML Purifier can do during an edge-case: for most inputs, this +processing is not necessary. + +--ALIASES-- +Core.AcceptFullDocuments +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt new file mode 100644 index 0000000..36f16e0 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt @@ -0,0 +1,17 @@ +Core.DirectLexLineNumberSyncInterval +TYPE: int +VERSION: 2.0.0 +DEFAULT: 0 +--DESCRIPTION-- + +

+ Specifies the number of tokens the DirectLex line number tracking + implementations should process before attempting to resyncronize the + current line count by manually counting all previous new-lines. When + at 0, this functionality is disabled. Lower values will decrease + performance, and this is only strictly necessary if the counting + algorithm is buggy (in which case you should report it as a bug). + This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is + not being used. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt new file mode 100644 index 0000000..1cd4c2c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt @@ -0,0 +1,14 @@ +Core.DisableExcludes +TYPE: bool +DEFAULT: false +VERSION: 4.5.0 +--DESCRIPTION-- +

+ This directive disables SGML-style exclusions, e.g. the exclusion of + <object> in any descendant of a + <pre> tag. Disabling excludes will allow some + invalid documents to pass through HTML Purifier, but HTML Purifier + will also be less likely to accidentally remove large documents during + processing. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt new file mode 100644 index 0000000..ce243c3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt @@ -0,0 +1,9 @@ +Core.EnableIDNA +TYPE: bool +DEFAULT: false +VERSION: 4.4.0 +--DESCRIPTION-- +Allows international domain names in URLs. This configuration option +requires the PEAR Net_IDNA2 module to be installed. It operates by +punycoding any internationalized host names for maximum portability. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt new file mode 100644 index 0000000..8bfb47c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt @@ -0,0 +1,15 @@ +Core.Encoding +TYPE: istring +DEFAULT: 'utf-8' +--DESCRIPTION-- +If for some reason you are unable to convert all webpages to UTF-8, you can +use this directive as a stop-gap compatibility change to let HTML Purifier +deal with non UTF-8 input. This technique has notable deficiencies: +absolutely no characters outside of the selected character encoding will be +preserved, not even the ones that have been ampersand escaped (this is due +to a UTF-8 specific feature that automatically resolves all +entities), making it pretty useless for anything except the most I18N-blind +applications, although %Core.EscapeNonASCIICharacters offers fixes this +trouble with another tradeoff. This directive only accepts ISO-8859-1 if +iconv is not enabled. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt new file mode 100644 index 0000000..a3881be --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt @@ -0,0 +1,12 @@ +Core.EscapeInvalidChildren +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +

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.

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt new file mode 100644 index 0000000..a7a5b24 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt @@ -0,0 +1,7 @@ +Core.EscapeInvalidTags +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +When true, invalid tags will be written back to the document as plain text. +Otherwise, they are silently dropped. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt new file mode 100644 index 0000000..abb4999 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt @@ -0,0 +1,13 @@ +Core.EscapeNonASCIICharacters +TYPE: bool +VERSION: 1.4.0 +DEFAULT: false +--DESCRIPTION-- +This directive overcomes a deficiency in %Core.Encoding by blindly +converting all non-ASCII characters into decimal numeric entities before +converting it to its native encoding. This means that even characters that +can be expressed in the non-UTF-8 encoding will be entity-ized, which can +be a real downer for encodings like Big5. It also assumes that the ASCII +repetoire is available, although this is the case for almost all encodings. +Anyway, use UTF-8! +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt new file mode 100644 index 0000000..915391e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt @@ -0,0 +1,19 @@ +Core.HiddenElements +TYPE: lookup +--DEFAULT-- +array ( + 'script' => true, + 'style' => true, +) +--DESCRIPTION-- + +

+ This directive is a lookup array of elements which should have their + contents removed when they are not allowed by the HTML definition. + For example, the contents of a script tag are not + normally shown in a document, so if script tags are to be removed, + their contents should be removed to. This is opposed to a b + tag, which defines some presentational changes but does not hide its + contents. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt new file mode 100644 index 0000000..233fca1 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt @@ -0,0 +1,10 @@ +Core.Language +TYPE: string +VERSION: 2.0.0 +DEFAULT: 'en' +--DESCRIPTION-- + +ISO 639 language code for localizable things in HTML Purifier to use, +which is mainly error reporting. There is currently only an English (en) +translation, so this directive is currently useless. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt new file mode 100644 index 0000000..392b436 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LegacyEntityDecoder.txt @@ -0,0 +1,36 @@ +Core.LegacyEntityDecoder +TYPE: bool +VERSION: 4.9.0 +DEFAULT: false +--DESCRIPTION-- +

+ Prior to HTML Purifier 4.9.0, entities were decoded by performing + a global search replace for all entities whose decoded versions + did not have special meanings under HTML, and replaced them with + their decoded versions. We would match all entities, even if they did + not have a trailing semicolon, but only if there weren't any trailing + alphanumeric characters. +

+
{$vo.id}{$vo.province}/{$vo.city == '无'?'':$vo.city}{$vo.province}{$vo.city == '无'?'':'-'.$vo.city} {$vo.create_time} {$vo.update_time} - +
+ + + + + +
OriginalTextAttribute
&yen;¥¥
&yen¥¥
&yena&yena&yena
&yen=¥=¥=
+

+ In HTML Purifier 4.9.0, we changed the behavior of entity parsing + to match entities that had missing trailing semicolons in less + cases, to more closely match HTML5 parsing behavior: +

+ + + + + + +
OriginalTextAttribute
&yen;¥¥
&yen¥¥
&yena¥a&yena
&yen=¥=&yen=
+

+ This flag reverts back to pre-HTML Purifier 4.9.0 behavior. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt new file mode 100644 index 0000000..8983e2c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt @@ -0,0 +1,34 @@ +Core.LexerImpl +TYPE: mixed/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ This parameter determines what lexer implementation can be used. The + valid values are: +

+
+
null
+
+ Recommended, the lexer implementation will be auto-detected based on + your PHP-version and configuration. +
+
string lexer identifier
+
+ This is a slim way of manually overridding the implementation. + Currently recognized values are: DOMLex (the default PHP5 +implementation) + and DirectLex (the default PHP4 implementation). Only use this if + you know what you are doing: usually, the auto-detection will + manage things for cases you aren't even aware of. +
+
object lexer instance
+
+ Super-advanced: you can specify your own, custom, implementation that + implements the interface defined by HTMLPurifier_Lexer. + I may remove this option simply because I don't expect anyone + to use it. +
+
+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt new file mode 100644 index 0000000..eb841a7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt @@ -0,0 +1,16 @@ +Core.MaintainLineNumbers +TYPE: bool/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ If true, HTML Purifier will add line number information to all tokens. + This is useful when error reporting is turned on, but can result in + significant performance degradation and should not be used when + unnecessary. This directive must be used with the DirectLex lexer, + as the DOMLex lexer does not (yet) support this functionality. + If the value is null, an appropriate value will be selected based + on other configuration. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt new file mode 100644 index 0000000..d77f536 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt @@ -0,0 +1,11 @@ +Core.NormalizeNewlines +TYPE: bool +VERSION: 4.2.0 +DEFAULT: true +--DESCRIPTION-- +

+ Whether or not to normalize newlines to the operating + system default. When false, HTML Purifier + will attempt to preserve mixed newline files. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt new file mode 100644 index 0000000..4070c2a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt @@ -0,0 +1,12 @@ +Core.RemoveInvalidImg +TYPE: bool +DEFAULT: true +VERSION: 1.3.0 +--DESCRIPTION-- + +

+ This directive enables pre-emptive URI checking in img + tags, as the attribute validation strategy is not authorized to + remove elements from the document. Revert to pre-1.3.0 behavior by setting to false. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt new file mode 100644 index 0000000..3397d9f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt @@ -0,0 +1,11 @@ +Core.RemoveProcessingInstructions +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +Instead of escaping processing instructions in the form <? ... +?>, remove it out-right. This may be useful if the HTML +you are validating contains XML processing instruction gunk, however, +it can also be user-unfriendly for people attempting to post PHP +snippets. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt new file mode 100644 index 0000000..a4cd966 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt @@ -0,0 +1,12 @@ +Core.RemoveScriptContents +TYPE: bool/null +DEFAULT: NULL +VERSION: 2.0.0 +DEPRECATED-VERSION: 2.1.0 +DEPRECATED-USE: Core.HiddenElements +--DESCRIPTION-- +

+ This directive enables HTML Purifier to remove not only script tags + but all of their contents. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt new file mode 100644 index 0000000..3db50ef --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt @@ -0,0 +1,11 @@ +Filter.Custom +TYPE: list +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

+ This directive can be used to add custom filters; it is nearly the + equivalent of the now deprecated HTMLPurifier->addFilter() + method. Specify an array of concrete implementations. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt new file mode 100644 index 0000000..16829bc --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt @@ -0,0 +1,14 @@ +Filter.ExtractStyleBlocks.Escaping +TYPE: bool +VERSION: 3.0.0 +DEFAULT: true +ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping +--DESCRIPTION-- + +

+ Whether or not to escape the dangerous characters <, > and & + as \3C, \3E and \26, respectively. This is can be safely set to false + if the contents of StyleBlocks will be placed in an external stylesheet, + where there is no risk of it being interpreted as HTML. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt new file mode 100644 index 0000000..7f95f54 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt @@ -0,0 +1,29 @@ +Filter.ExtractStyleBlocks.Scope +TYPE: string/null +VERSION: 3.0.0 +DEFAULT: NULL +ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope +--DESCRIPTION-- + +

+ If you would like users to be able to define external stylesheets, but + only allow them to specify CSS declarations for a specific node and + prevent them from fiddling with other elements, use this directive. + It accepts any valid CSS selector, and will prepend this to any + CSS declaration extracted from the document. For example, if this + directive is set to #user-content and a user uses the + selector a:hover, the final selector will be + #user-content a:hover. +

+

+ The comma shorthand may be used; consider the above example, with + #user-content, #user-content2, the final selector will + be #user-content a:hover, #user-content2 a:hover. +

+

+ Warning: It is possible for users to bypass this measure + using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML + Purifier, and I am working to get it fixed. Until then, HTML Purifier + performs a basic check to prevent this. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt new file mode 100644 index 0000000..6c231b2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt @@ -0,0 +1,16 @@ +Filter.ExtractStyleBlocks.TidyImpl +TYPE: mixed/null +VERSION: 3.1.0 +DEFAULT: NULL +ALIASES: FilterParam.ExtractStyleBlocksTidyImpl +--DESCRIPTION-- +

+ If left NULL, HTML Purifier will attempt to instantiate a csstidy + class to use for internal cleaning. This will usually be good enough. +

+

+ However, for trusted user input, you can set this to false to + disable cleaning. In addition, you can supply your own concrete implementation + of Tidy's interface to use, although I don't know why you'd want to do that. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt new file mode 100644 index 0000000..078d087 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt @@ -0,0 +1,74 @@ +Filter.ExtractStyleBlocks +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +EXTERNAL: CSSTidy +--DESCRIPTION-- +

+ This directive turns on the style block extraction filter, which removes + style blocks from input HTML, cleans them up with CSSTidy, + and places them in the StyleBlocks context variable, for further + use by you, usually to be placed in an external stylesheet, or a + style block in the head of your document. +

+

+ Sample usage: +

+
';
+?>
+
+
+
+  Filter.ExtractStyleBlocks
+body {color:#F00;} Some text';
+
+    $config = HTMLPurifier_Config::createDefault();
+    $config->set('Filter', 'ExtractStyleBlocks', true);
+    $purifier = new HTMLPurifier($config);
+
+    $html = $purifier->purify($dirty);
+
+    // This implementation writes the stylesheets to the styles/ directory.
+    // You can also echo the styles inside the document, but it's a bit
+    // more difficult to make sure they get interpreted properly by
+    // browsers; try the usual CSS armoring techniques.
+    $styles = $purifier->context->get('StyleBlocks');
+    $dir = 'styles/';
+    if (!is_dir($dir)) mkdir($dir);
+    $hash = sha1($_GET['html']);
+    foreach ($styles as $i => $style) {
+        file_put_contents($name = $dir . $hash . "_$i");
+        echo '';
+    }
+?>
+
+
+  
+ +
+ + +]]>
+

+ Warning: It is possible for a user to mount an + imagecrash attack using this CSS. Counter-measures are difficult; + it is not simply enough to limit the range of CSS lengths (using + relative lengths with many nesting levels allows for large values + to be attained without actually specifying them in the stylesheet), + and the flexible nature of selectors makes it difficult to selectively + disable lengths on image tags (HTML Purifier, however, does disable + CSS width and height in inline styling). There are probably two effective + counter measures: an explicit width and height set to auto in all + images in your document (unlikely) or the disabling of width and + height (somewhat reasonable). Whether or not these measures should be + used is left to the reader. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt new file mode 100644 index 0000000..321eaa2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt @@ -0,0 +1,16 @@ +Filter.YouTube +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +--DESCRIPTION-- +

+ Warning: Deprecated in favor of %HTML.SafeObject and + %Output.FlashCompat (turn both on to allow YouTube videos and other + Flash content). +

+

+ This directive enables YouTube video embedding in HTML Purifier. Check + this document + on embedding videos for more information on what this filter does. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt new file mode 100644 index 0000000..0b2c106 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt @@ -0,0 +1,25 @@ +HTML.Allowed +TYPE: itext/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ This is a preferred convenience directive that combines + %HTML.AllowedElements and %HTML.AllowedAttributes. + Specify elements and attributes that are allowed using: + element1[attr1|attr2],element2.... For example, + if you would like to only allow paragraphs and links, specify + a[href],p. You can specify attributes that apply + to all elements using an asterisk, e.g. *[lang]. + You can also use newlines instead of commas to separate elements. +

+

+ Warning: + All of the constraints on the component directives are still enforced. + The syntax is a subset of TinyMCE's valid_elements + whitelist: directly copy-pasting it here will probably result in + broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes + are set, this directive has no effect. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt new file mode 100644 index 0000000..fcf093f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt @@ -0,0 +1,19 @@ +HTML.AllowedAttributes +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ If HTML Purifier's attribute set is unsatisfactory, overload it! + The syntax is "tag.attr" or "*.attr" for the global attributes + (style, id, class, dir, lang, xml:lang). +

+

+ Warning: If another directive conflicts with the + elements here, that directive will win and override. For + example, %HTML.EnableAttrID will take precedence over *.id in this + directive. You must set that directive to true before you can use + IDs at all. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt new file mode 100644 index 0000000..140e214 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt @@ -0,0 +1,10 @@ +HTML.AllowedComments +TYPE: lookup +VERSION: 4.4.0 +DEFAULT: array() +--DESCRIPTION-- +A whitelist which indicates what explicit comment bodies should be +allowed, modulo leading and trailing whitespace. See also %HTML.AllowedCommentsRegexp +(these directives are union'ed together, so a comment is considered +valid if any directive deems it valid.) +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt new file mode 100644 index 0000000..f22e977 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt @@ -0,0 +1,15 @@ +HTML.AllowedCommentsRegexp +TYPE: string/null +VERSION: 4.4.0 +DEFAULT: NULL +--DESCRIPTION-- +A regexp, which if it matches the body of a comment, indicates that +it should be allowed. Trailing and leading spaces are removed prior +to running this regular expression. +Warning: Make sure you specify +correct anchor metacharacters ^regex$, otherwise you may accept +comments that you did not mean to! In particular, the regex /foo|bar/ +is probably not sufficiently strict, since it also allows foobar. +See also %HTML.AllowedComments (these directives are union'ed together, +so a comment is considered valid if any directive deems it valid.) +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt new file mode 100644 index 0000000..1d3fa79 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt @@ -0,0 +1,23 @@ +HTML.AllowedElements +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- +

+ If HTML Purifier's tag set is unsatisfactory for your needs, you can + overload it with your own list of tags to allow. If you change + this, you probably also want to change %HTML.AllowedAttributes; see + also %HTML.Allowed which lets you set allowed elements and + attributes at the same time. +

+

+ If you attempt to allow an element that HTML Purifier does not know + about, HTML Purifier will raise an error. You will need to manually + tell HTML Purifier about this element by using the + advanced customization features. +

+

+ Warning: If another directive conflicts with the + elements here, that directive will win and override. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt new file mode 100644 index 0000000..5a59a55 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt @@ -0,0 +1,20 @@ +HTML.AllowedModules +TYPE: lookup/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ A doctype comes with a set of usual modules to use. Without having + to mucking about with the doctypes, you can quickly activate or + disable these modules by specifying which modules you wish to allow + with this directive. This is most useful for unit testing specific + modules, although end users may find it useful for their own ends. +

+

+ If you specify a module that does not exist, the manager will silently + fail to use it, so be careful! User-defined modules are not affected + by this directive. Modules defined in %HTML.CoreModules are not + affected by this directive. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt new file mode 100644 index 0000000..151fb7b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt @@ -0,0 +1,11 @@ +HTML.Attr.Name.UseCDATA +TYPE: bool +DEFAULT: false +VERSION: 4.0.0 +--DESCRIPTION-- +The W3C specification DTD defines the name attribute to be CDATA, not ID, due +to limitations of DTD. In certain documents, this relaxed behavior is desired, +whether it is to specify duplicate names, or to specify names that would be +illegal IDs (for example, names that begin with a digit.) Set this configuration +directive to true to use the relaxed parsing rules. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt new file mode 100644 index 0000000..45ae469 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt @@ -0,0 +1,18 @@ +HTML.BlockWrapper +TYPE: string +VERSION: 1.3.0 +DEFAULT: 'p' +--DESCRIPTION-- + +

+ String name of element to wrap inline elements that are inside a block + context. This only occurs in the children of blockquote in strict mode. +

+

+ Example: by default value, + <blockquote>Foo</blockquote> would become + <blockquote><p>Foo</p></blockquote>. + The <p> tags can be replaced with whatever you desire, + as long as it is a block level element. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt new file mode 100644 index 0000000..5246188 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt @@ -0,0 +1,23 @@ +HTML.CoreModules +TYPE: lookup +VERSION: 2.0.0 +--DEFAULT-- +array ( + 'Structure' => true, + 'Text' => true, + 'Hypertext' => true, + 'List' => true, + 'NonXMLCommonAttributes' => true, + 'XMLCommonAttributes' => true, + 'CommonAttributes' => true, +) +--DESCRIPTION-- + +

+ Certain modularized doctypes (XHTML, namely), have certain modules + that must be included for the doctype to be an conforming document + type: put those modules here. By default, XHTML's core modules + are used. You can set this to a blank array to disable core module + protection, but this is not recommended. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt new file mode 100644 index 0000000..6ed70b5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt @@ -0,0 +1,9 @@ +HTML.CustomDoctype +TYPE: string/null +VERSION: 2.0.1 +DEFAULT: NULL +--DESCRIPTION-- + +A custom doctype for power-users who defined their own document +type. This directive only applies when %HTML.Doctype is blank. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt new file mode 100644 index 0000000..103db75 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt @@ -0,0 +1,33 @@ +HTML.DefinitionID +TYPE: string/null +DEFAULT: NULL +VERSION: 2.0.0 +--DESCRIPTION-- + +

+ Unique identifier for a custom-built HTML definition. If you edit + the raw version of the HTMLDefinition, introducing changes that the + configuration object does not reflect, you must specify this variable. + If you change your custom edits, you should change this directive, or + clear your cache. Example: +

+
+$config = HTMLPurifier_Config::createDefault();
+$config->set('HTML', 'DefinitionID', '1');
+$def = $config->getHTMLDefinition();
+$def->addAttribute('a', 'tabindex', 'Number');
+
+

+ In the above example, the configuration is still at the defaults, but + using the advanced API, an extra attribute has been added. The + configuration object normally has no way of knowing that this change + has taken place, so it needs an extra directive: %HTML.DefinitionID. + If someone else attempts to use the default configuration, these two + pieces of code will not clobber each other in the cache, since one has + an extra directive attached to it. +

+

+ You must specify a value to this directive to use the + advanced API features. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt new file mode 100644 index 0000000..229ae02 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt @@ -0,0 +1,16 @@ +HTML.DefinitionRev +TYPE: int +VERSION: 2.0.0 +DEFAULT: 1 +--DESCRIPTION-- + +

+ Revision identifier for your custom definition specified in + %HTML.DefinitionID. This serves the same purpose: uniquely identifying + your custom definition, but this one does so in a chronological + context: revision 3 is more up-to-date then revision 2. Thus, when + this gets incremented, the cache handling is smart enough to clean + up any older revisions of your definition as well as flush the + cache. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt new file mode 100644 index 0000000..9dab497 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt @@ -0,0 +1,11 @@ +HTML.Doctype +TYPE: string/null +DEFAULT: NULL +--DESCRIPTION-- +Doctype to use during filtering. Technically speaking this is not actually +a doctype (as it does not identify a corresponding DTD), but we are using +this name for sake of simplicity. When non-blank, this will override any +older directives like %HTML.XHTML or %HTML.Strict. +--ALLOWED-- +'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1' +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt new file mode 100644 index 0000000..7878dc0 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt @@ -0,0 +1,11 @@ +HTML.FlashAllowFullScreen +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +

+ Whether or not to permit embedded Flash content from + %HTML.SafeObject to expand to the full screen. Corresponds to + the allowFullScreen parameter. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt new file mode 100644 index 0000000..57358f9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt @@ -0,0 +1,21 @@ +HTML.ForbiddenAttributes +TYPE: lookup +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

+ While this directive is similar to %HTML.AllowedAttributes, for + forwards-compatibility with XML, this attribute has a different syntax. Instead of + tag.attr, use tag@attr. To disallow href + attributes in a tags, set this directive to + a@href. You can also disallow an attribute globally with + attr or *@attr (either syntax is fine; the latter + is provided for consistency with %HTML.AllowedAttributes). +

+

+ Warning: This directive complements %HTML.ForbiddenElements, + accordingly, check + out that directive for a discussion of why you + should think twice before using this directive. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt new file mode 100644 index 0000000..93a53e1 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt @@ -0,0 +1,20 @@ +HTML.ForbiddenElements +TYPE: lookup +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

+ This was, perhaps, the most requested feature ever in HTML + Purifier. Please don't abuse it! This is the logical inverse of + %HTML.AllowedElements, and it will override that directive, or any + other directive. +

+

+ If possible, %HTML.Allowed is recommended over this directive, because it + can sometimes be difficult to tell whether or not you've forbidden all of + the behavior you would like to disallow. If you forbid img + with the expectation of preventing images on your site, you'll be in for + a nasty surprise when people start using the background-image + CSS property. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Forms.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Forms.txt new file mode 100644 index 0000000..4a432d8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Forms.txt @@ -0,0 +1,11 @@ +HTML.Forms +TYPE: bool +VERSION: 4.13.0 +DEFAULT: false +--DESCRIPTION-- +

+ Whether or not to permit form elements in the user input, regardless of + %HTML.Trusted value. Please be very careful when using this functionality, as + enabling forms in untrusted documents may allow for phishing attacks. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt new file mode 100644 index 0000000..e424c38 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt @@ -0,0 +1,14 @@ +HTML.MaxImgLength +TYPE: int/null +DEFAULT: 1200 +VERSION: 3.1.1 +--DESCRIPTION-- +

+ This directive controls the maximum number of pixels in the width and + height attributes in img tags. This is + in place to prevent imagecrash attacks, disable with null at your own risk. + This directive is similar to %CSS.MaxImgLength, and both should be + concurrently edited, although there are + subtle differences in the input format (the HTML max is an integer). +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt new file mode 100644 index 0000000..700b309 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt @@ -0,0 +1,7 @@ +HTML.Nofollow +TYPE: bool +VERSION: 4.3.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, nofollow rel attributes are added to all outgoing links. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt new file mode 100644 index 0000000..62e8e16 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt @@ -0,0 +1,12 @@ +HTML.Parent +TYPE: string +VERSION: 1.3.0 +DEFAULT: 'div' +--DESCRIPTION-- + +

+ String name of element that HTML fragment passed to library will be + inserted in. An interesting variation would be using span as the + parent element, meaning that only inline tags would be allowed. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt new file mode 100644 index 0000000..dfb7204 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt @@ -0,0 +1,12 @@ +HTML.Proprietary +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +--DESCRIPTION-- +

+ Whether or not to allow proprietary elements and attributes in your + documents, as per HTMLPurifier_HTMLModule_Proprietary. + Warning: This can cause your documents to stop + validating! +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt new file mode 100644 index 0000000..cdda09a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt @@ -0,0 +1,13 @@ +HTML.SafeEmbed +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

+ Whether or not to permit embed tags in documents, with a number of extra + security features added to prevent script execution. This is similar to + what websites like MySpace do to embed tags. Embed is a proprietary + element and will cause your website to stop validating; you should + see if you can use %Output.FlashCompat with %HTML.SafeObject instead + first.

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt new file mode 100644 index 0000000..5eb6ec2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt @@ -0,0 +1,13 @@ +HTML.SafeIframe +TYPE: bool +VERSION: 4.4.0 +DEFAULT: false +--DESCRIPTION-- +

+ Whether or not to permit iframe tags in untrusted documents. This + directive must be accompanied by a whitelist of permitted iframes, + such as %URI.SafeIframeRegexp, otherwise it will fatally error. + This directive has no effect on strict doctypes, as iframes are not + valid. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt new file mode 100644 index 0000000..ceb342e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt @@ -0,0 +1,13 @@ +HTML.SafeObject +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

+ Whether or not to permit object tags in documents, with a number of extra + security features added to prevent script execution. This is similar to + what websites like MySpace do to object tags. You should also enable + %Output.FlashCompat in order to generate Internet Explorer + compatibility code for your object tags. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt new file mode 100644 index 0000000..5ebc7a1 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt @@ -0,0 +1,10 @@ +HTML.SafeScripting +TYPE: lookup +VERSION: 4.5.0 +DEFAULT: array() +--DESCRIPTION-- +

+ Whether or not to permit script tags to external scripts in documents. + Inline scripting is not allowed, and the script must match an explicit whitelist. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt new file mode 100644 index 0000000..a8b1de5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt @@ -0,0 +1,9 @@ +HTML.Strict +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +DEPRECATED-VERSION: 1.7.0 +DEPRECATED-USE: HTML.Doctype +--DESCRIPTION-- +Determines whether or not to use Transitional (loose) or Strict rulesets. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt new file mode 100644 index 0000000..587a167 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt @@ -0,0 +1,8 @@ +HTML.TargetBlank +TYPE: bool +VERSION: 4.4.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, target=blank attributes are added to all outgoing links. +(This includes links from an HTTPS version of a page to an HTTP version.) +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt new file mode 100644 index 0000000..dd514c0 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt @@ -0,0 +1,10 @@ +--# vim: et sw=4 sts=4 +HTML.TargetNoopener +TYPE: bool +VERSION: 4.8.0 +DEFAULT: TRUE +--DESCRIPTION-- +If enabled, noopener rel attributes are added to links which have +a target attribute associated with them. This prevents malicious +destinations from overwriting the original window. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt new file mode 100644 index 0000000..cb5a0b0 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt @@ -0,0 +1,9 @@ +HTML.TargetNoreferrer +TYPE: bool +VERSION: 4.8.0 +DEFAULT: TRUE +--DESCRIPTION-- +If enabled, noreferrer rel attributes are added to links which have +a target attribute associated with them. This prevents malicious +destinations from overwriting the original window. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt new file mode 100644 index 0000000..b4c271b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt @@ -0,0 +1,8 @@ +HTML.TidyAdd +TYPE: lookup +VERSION: 2.0.0 +DEFAULT: array() +--DESCRIPTION-- + +Fixes to add to the default set of Tidy fixes as per your level. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt new file mode 100644 index 0000000..4186ccd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt @@ -0,0 +1,24 @@ +HTML.TidyLevel +TYPE: string +VERSION: 2.0.0 +DEFAULT: 'medium' +--DESCRIPTION-- + +

General level of cleanliness the Tidy module should enforce. +There are four allowed values:

+
+
none
+
No extra tidying should be done
+
light
+
Only fix elements that would be discarded otherwise due to + lack of support in doctype
+
medium
+
Enforce best practices
+
heavy
+
Transform all deprecated elements and attributes to standards + compliant equivalents
+
+ +--ALLOWED-- +'none', 'light', 'medium', 'heavy' +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt new file mode 100644 index 0000000..996762b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt @@ -0,0 +1,8 @@ +HTML.TidyRemove +TYPE: lookup +VERSION: 2.0.0 +DEFAULT: array() +--DESCRIPTION-- + +Fixes to remove from the default set of Tidy fixes as per your level. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt new file mode 100644 index 0000000..1db9237 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt @@ -0,0 +1,9 @@ +HTML.Trusted +TYPE: bool +VERSION: 2.0.0 +DEFAULT: false +--DESCRIPTION-- +Indicates whether or not the user input is trusted or not. If the input is +trusted, a more expansive set of allowed tags and attributes will be used. +See also %CSS.Trusted. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt new file mode 100644 index 0000000..2a47e38 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt @@ -0,0 +1,11 @@ +HTML.XHTML +TYPE: bool +DEFAULT: true +VERSION: 1.1.0 +DEPRECATED-VERSION: 1.7.0 +DEPRECATED-USE: HTML.Doctype +--DESCRIPTION-- +Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor. +--ALIASES-- +Core.XHTML +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt new file mode 100644 index 0000000..08921fd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt @@ -0,0 +1,10 @@ +Output.CommentScriptContents +TYPE: bool +VERSION: 2.0.0 +DEFAULT: true +--DESCRIPTION-- +Determines whether or not HTML Purifier should attempt to fix up the +contents of script tags for legacy browsers with comments. +--ALIASES-- +Core.CommentScriptContents +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt new file mode 100644 index 0000000..d6f0d9f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt @@ -0,0 +1,15 @@ +Output.FixInnerHTML +TYPE: bool +VERSION: 4.3.0 +DEFAULT: true +--DESCRIPTION-- +

+ If true, HTML Purifier will protect against Internet Explorer's + mishandling of the innerHTML attribute by appending + a space to any attribute that does not contain angled brackets, spaces + or quotes, but contains a backtick. This slightly changes the + semantics of any given attribute, so if this is unacceptable and + you do not use innerHTML on any of your pages, you can + turn this directive off. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt new file mode 100644 index 0000000..93398e8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt @@ -0,0 +1,11 @@ +Output.FlashCompat +TYPE: bool +VERSION: 4.1.0 +DEFAULT: false +--DESCRIPTION-- +

+ If true, HTML Purifier will generate Internet Explorer compatibility + code for all object code. This is highly recommended if you enable + %HTML.SafeObject. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt new file mode 100644 index 0000000..79f8ad8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt @@ -0,0 +1,13 @@ +Output.Newline +TYPE: string/null +VERSION: 2.0.1 +DEFAULT: NULL +--DESCRIPTION-- + +

+ Newline string to format final output with. If left null, HTML Purifier + will auto-detect the default newline type of the system and use that; + you can manually override it here. Remember, \r\n is Windows, \r + is Mac, and \n is Unix. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt new file mode 100644 index 0000000..232b023 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt @@ -0,0 +1,14 @@ +Output.SortAttr +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

+ If true, HTML Purifier will sort attributes by name before writing them back + to the document, converting a tag like: <el b="" a="" c="" /> + to <el a="" b="" c="" />. This is a workaround for + a bug in FCKeditor which causes it to swap attributes order, adding noise + to text diffs. If you're not seeing this bug, chances are, you don't need + this directive. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt new file mode 100644 index 0000000..06bab00 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt @@ -0,0 +1,25 @@ +Output.TidyFormat +TYPE: bool +VERSION: 1.1.1 +DEFAULT: false +--DESCRIPTION-- +

+ Determines whether or not to run Tidy on the final output for pretty + formatting reasons, such as indentation and wrap. +

+

+ This can greatly improve readability for editors who are hand-editing + the HTML, but is by no means necessary as HTML Purifier has already + fixed all major errors the HTML may have had. Tidy is a non-default + extension, and this directive will silently fail if Tidy is not + available. +

+

+ If you are looking to make the overall look of your page's source + better, I recommend running Tidy on the entire page rather than just + user-content (after all, the indentation relative to the containing + blocks will be incorrect). +

+--ALIASES-- +Core.TidyFormat +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt new file mode 100644 index 0000000..071bc02 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt @@ -0,0 +1,7 @@ +Test.ForceNoIconv +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +When set to true, HTMLPurifier_Encoder will act as if iconv does not exist +and use only pure PHP implementations. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt new file mode 100644 index 0000000..eb97307 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt @@ -0,0 +1,18 @@ +URI.AllowedSchemes +TYPE: lookup +--DEFAULT-- +array ( + 'http' => true, + 'https' => true, + 'mailto' => true, + 'ftp' => true, + 'nntp' => true, + 'news' => true, + 'tel' => true, +) +--DESCRIPTION-- +Whitelist that defines the schemes that a URI is allowed to have. This +prevents XSS attacks from using pseudo-schemes like javascript or mocha. +There is also support for the data and file +URI schemes, but they are not enabled by default. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt new file mode 100644 index 0000000..876f068 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt @@ -0,0 +1,17 @@ +URI.Base +TYPE: string/null +VERSION: 2.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ The base URI is the URI of the document this purified HTML will be + inserted into. This information is important if HTML Purifier needs + to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute + is on. You may use a non-absolute URI for this value, but behavior + may vary (%URI.MakeAbsolute deals nicely with both absolute and + relative paths, but forwards-compatibility is not guaranteed). + Warning: If set, the scheme on this URI + overrides the one specified by %URI.DefaultScheme. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt new file mode 100644 index 0000000..834bc08 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt @@ -0,0 +1,15 @@ +URI.DefaultScheme +TYPE: string/null +DEFAULT: 'http' +--DESCRIPTION-- + +

+ Defines through what scheme the output will be served, in order to + select the proper object validator when no scheme information is present. +

+ +

+ Starting with HTML Purifier 4.9.0, the default scheme can be null, in + which case we reject all URIs which do not have explicit schemes. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt new file mode 100644 index 0000000..f05312b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt @@ -0,0 +1,11 @@ +URI.DefinitionID +TYPE: string/null +VERSION: 2.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ Unique identifier for a custom-built URI definition. If you want + to add custom URIFilters, you must specify this value. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt new file mode 100644 index 0000000..80cfea9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt @@ -0,0 +1,11 @@ +URI.DefinitionRev +TYPE: int +VERSION: 2.1.0 +DEFAULT: 1 +--DESCRIPTION-- + +

+ Revision identifier for your custom definition. See + %HTML.DefinitionRev for details. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt new file mode 100644 index 0000000..71ce025 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt @@ -0,0 +1,14 @@ +URI.Disable +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +--DESCRIPTION-- + +

+ Disables all URIs in all forms. Not sure why you'd want to do that + (after all, the Internet's founded on the notion of a hyperlink). +

+ +--ALIASES-- +Attr.DisableURI +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt new file mode 100644 index 0000000..13c122c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt @@ -0,0 +1,11 @@ +URI.DisableExternal +TYPE: bool +VERSION: 1.2.0 +DEFAULT: false +--DESCRIPTION-- +Disables links to external websites. This is a highly effective anti-spam +and anti-pagerank-leech measure, but comes at a hefty price: nolinks or +images outside of your domain will be allowed. Non-linkified URIs will +still be preserved. If you want to be able to link to subdomains or use +absolute URIs, specify %URI.Host for your website. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt new file mode 100644 index 0000000..abcc1ef --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt @@ -0,0 +1,13 @@ +URI.DisableExternalResources +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +--DESCRIPTION-- +Disables the embedding of external resources, preventing users from +embedding things like images from other hosts. This prevents access +tracking (good for email viewers), bandwidth leeching, cross-site request +forging, goatse.cx posting, and other nasties, but also results in a loss +of end-user functionality (they can't directly post a pic they posted from +Flickr anymore). Use it if you don't have a robust user-content moderation +team. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt new file mode 100644 index 0000000..f891de4 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt @@ -0,0 +1,15 @@ +URI.DisableResources +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +

+ Disables embedding resources, essentially meaning no pictures. You can + still link to them though. See %URI.DisableExternalResources for why + this might be a good idea. +

+

+ Note: While this directive has been available since 1.3.0, + it didn't actually start doing anything until 4.2.0. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt new file mode 100644 index 0000000..ee83b12 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt @@ -0,0 +1,19 @@ +URI.Host +TYPE: string/null +VERSION: 1.2.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ Defines the domain name of the server, so we can determine whether or + an absolute URI is from your website or not. Not strictly necessary, + as users should be using relative URIs to reference resources on your + website. It will, however, let you use absolute URIs to link to + subdomains of the domain you post here: i.e. example.com will allow + sub.example.com. However, higher up domains will still be excluded: + if you set %URI.Host to sub.example.com, example.com will be blocked. + Note: This directive overrides %URI.Base because + a given page may be on a sub-domain, but you wish HTML Purifier to be + more relaxed and allow some of the parent domains too. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt new file mode 100644 index 0000000..0b6df76 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt @@ -0,0 +1,9 @@ +URI.HostBlacklist +TYPE: list +VERSION: 1.3.0 +DEFAULT: array() +--DESCRIPTION-- +List of strings that are forbidden in the host of any URI. Use it to kill +domain names of spam, etc. Note that it will catch anything in the domain, +so moo.com will catch moo.com.example.com. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt new file mode 100644 index 0000000..4214900 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt @@ -0,0 +1,13 @@ +URI.MakeAbsolute +TYPE: bool +VERSION: 2.1.0 +DEFAULT: false +--DESCRIPTION-- + +

+ Converts all URIs into absolute forms. This is useful when the HTML + being filtered assumes a specific base path, but will actually be + viewed in a different context (and setting an alternate base URI is + not possible). %URI.Base must be set for this directive to work. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt new file mode 100644 index 0000000..58c81dc --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt @@ -0,0 +1,83 @@ +URI.Munge +TYPE: string/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- + +

+ Munges all browsable (usually http, https and ftp) + absolute URIs into another URI, usually a URI redirection service. + This directive accepts a URI, formatted with a %s where + the url-encoded original URI should be inserted (sample: + http://www.google.com/url?q=%s). +

+

+ Uses for this directive: +

+
    +
  • + Prevent PageRank leaks, while being fairly transparent + to users (you may also want to add some client side JavaScript to + override the text in the statusbar). Notice: + Many security experts believe that this form of protection does not deter spam-bots. +
  • +
  • + Redirect users to a splash page telling them they are leaving your + website. While this is poor usability practice, it is often mandated + in corporate environments. +
  • +
+

+ Prior to HTML Purifier 3.1.1, this directive also enabled the munging + of browsable external resources, which could break things if your redirection + script was a splash page or used meta tags. To revert to + previous behavior, please use %URI.MungeResources. +

+

+ You may want to also use %URI.MungeSecretKey along with this directive + in order to enforce what URIs your redirector script allows. Open + redirector scripts can be a security risk and negatively affect the + reputation of your domain name. +

+

+ Starting with HTML Purifier 3.1.1, there is also these substitutions: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyDescriptionExample <a href="">
%r1 - The URI embeds a resource
(blank) - The URI is merely a link
%nThe name of the tag this URI came froma
%mThe name of the attribute this URI came fromhref
%pThe name of the CSS property this URI came from, or blank if irrelevant
+

+ Admittedly, these letters are somewhat arbitrary; the only stipulation + was that they couldn't be a through f. r is for resource (I would have preferred + e, but you take what you can get), n is for name, m + was picked because it came after n (and I couldn't use a), p is for + property. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt new file mode 100644 index 0000000..6fce0fd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt @@ -0,0 +1,17 @@ +URI.MungeResources +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

+ If true, any URI munging directives like %URI.Munge + will also apply to embedded resources, such as <img src="">. + Be careful enabling this directive if you have a redirector script + that does not use the Location HTTP header; all of your images + and other embedded resources will break. +

+

+ Warning: It is strongly advised you use this in conjunction + %URI.MungeSecretKey to mitigate the security risk of an open redirector. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt new file mode 100644 index 0000000..1e17c1d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt @@ -0,0 +1,30 @@ +URI.MungeSecretKey +TYPE: string/null +VERSION: 3.1.1 +DEFAULT: NULL +--DESCRIPTION-- +

+ This directive enables secure checksum generation along with %URI.Munge. + It should be set to a secure key that is not shared with anyone else. + The checksum can be placed in the URI using %t. Use of this checksum + affords an additional level of protection by allowing a redirector + to check if a URI has passed through HTML Purifier with this line: +

+ +
$checksum === hash_hmac("sha256", $url, $secret_key)
+ +

+ If the output is TRUE, the redirector script should accept the URI. +

+ +

+ Please note that it would still be possible for an attacker to procure + secure hashes en-mass by abusing your website's Preview feature or the + like, but this service affords an additional level of protection + that should be combined with website blacklisting. +

+ +

+ Remember this has no effect if %URI.Munge is not on. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt new file mode 100644 index 0000000..23331a4 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt @@ -0,0 +1,9 @@ +URI.OverrideAllowedSchemes +TYPE: bool +DEFAULT: true +--DESCRIPTION-- +If this is set to true (which it is by default), you can override +%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the +registry. If false, you will also have to update that directive in order +to add more schemes. +--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt new file mode 100644 index 0000000..7908483 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt @@ -0,0 +1,22 @@ +URI.SafeIframeRegexp +TYPE: string/null +VERSION: 4.4.0 +DEFAULT: NULL +--DESCRIPTION-- +

+ A PCRE regular expression that will be matched against an iframe URI. This is + a relatively inflexible scheme, but works well enough for the most common + use-case of iframes: embedded video. This directive only has an effect if + %HTML.SafeIframe is enabled. Here are some example values: +

+
    +
  • %^http://www.youtube.com/embed/% - Allow YouTube videos
  • +
  • %^http://player.vimeo.com/video/% - Allow Vimeo videos
  • +
  • %^http://(www.youtube.com/embed/|player.vimeo.com/video/)% - Allow both
  • +
+

+ Note that this directive does not give you enough granularity to, say, disable + all autoplay videos. Pipe up on the HTML Purifier forums if this + is a capability you want. +

+--# vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini new file mode 100644 index 0000000..5de4505 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini @@ -0,0 +1,3 @@ +name = "HTML Purifier" + +; vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php new file mode 100644 index 0000000..543e3f8 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php @@ -0,0 +1,170 @@ + 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). + * @type array + */ + protected $keys = array(); + /** + * 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 HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule + */ + 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) { + foreach ($module->content_sets as $key => $value) { + $temp = $this->convertToLookup($value); + if (isset($this->lookup[$key])) { + // add it into the existing content set + $this->lookup[$key] = array_merge($this->lookup[$key], $temp); + } else { + $this->lookup[$key] = $temp; + } + } + } + $old_lookup = false; + while ($old_lookup !== $this->lookup) { + $old_lookup = $this->lookup; + foreach ($this->lookup as $i => $set) { + $add = array(); + foreach ($set as $element => $x) { + if (isset($this->lookup[$element])) { + $add += $this->lookup[$element]; + unset($this->lookup[$i][$element]); + } + } + $this->lookup[$i] += $add; + } + } + + foreach ($this->lookup as $key => $lookup) { + $this->info[$key] = implode(' | ', array_keys($lookup)); + } + $this->keys = array_keys($this->info); + $this->values = array_values($this->info); + } + + /** + * Accepts a definition; generates and assigns a ChildDef for it + * @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)) { // already done! + return; + } + $content_model = $def->content_model; + if (is_string($content_model)) { + // Assume that $this->keys is alphanumeric + $def->content_model = preg_replace_callback( + '/\b(' . implode('|', $this->keys) . ')\b/', + array($this, 'generateChildDefCallback'), + $content_model + ); + //$def->content_model = str_replace( + // $this->keys, $this->values, $content_model); + } + $def->child = $this->getChildDef($def, $module); + } + + public function generateChildDefCallback($matches) + { + return $this->info[$matches[0]]; + } + + /** + * Instantiates a ChildDef based on content_model and content_model_type + * member variables in HTMLPurifier_ElementDef + * @note This will also defer to modules for custom HTMLPurifier_ChildDef + * subclasses that need content set expansion + * @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) + { + $value = $def->content_model; + if (is_object($value)) { + trigger_error( + 'Literal object child definitions should be stored in '. + 'ElementDef->child not ElementDef->content_model', + E_USER_NOTICE + ); + return $value; + } + switch ($def->content_model_type) { + case 'required': + return new HTMLPurifier_ChildDef_Required($value); + case 'optional': + return new HTMLPurifier_ChildDef_Optional($value); + case 'empty': + return new HTMLPurifier_ChildDef_Empty(); + case 'custom': + return new HTMLPurifier_ChildDef_Custom($value); + } + // defer to its module + $return = false; + if ($module->defines_child_def) { // save a func call + $return = $module->getChildDef($def); + } + if ($return !== false) { + return $return; + } + // error-out + trigger_error( + 'Could not determine which ChildDef class to instantiate', + E_USER_ERROR + ); + return false; + } + + /** + * Converts a string list of elements separated by pipes into + * a lookup array. + * @param string $string List of elements + * @return array Lookup array of elements + */ + protected function convertToLookup($string) + { + $array = explode('|', str_replace(' ', '', $string)); + $ret = array(); + foreach ($array as $k) { + $ret[$k] = true; + } + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php new file mode 100644 index 0000000..00e509c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php @@ -0,0 +1,95 @@ +_storage)) { + trigger_error( + "Name $name produces collision, cannot re-register", + E_USER_ERROR + ); + return; + } + $this->_storage[$name] =& $ref; + } + + /** + * Retrieves a variable reference from the context. + * @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 (!array_key_exists($name, $this->_storage)) { + if (!$ignore_error) { + trigger_error( + "Attempted to retrieve non-existent variable $name", + E_USER_ERROR + ); + } + $var = null; // so we can return by reference + return $var; + } + return $this->_storage[$name]; + } + + /** + * Destroys a variable in the context. + * @param string $name String name + */ + 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]); + } + + /** + * Checks whether or not the variable exists. + * @param string $name String name + * @return bool + */ + public function exists($name) + { + return array_key_exists($name, $this->_storage); + } + + /** + * Loads a series of variables from an associative array + * @param array $context_array Assoc array of variables to load + */ + public function loadArray($context_array) + { + foreach ($context_array as $key => $discard) { + $this->register($key, $context_array[$key]); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php new file mode 100644 index 0000000..bc6d433 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php @@ -0,0 +1,55 @@ +setup) { + return; + } + $this->setup = true; + $this->doSetup($config); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php new file mode 100644 index 0000000..9aa8ff3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php @@ -0,0 +1,129 @@ +type = $type; + } + + /** + * Generates a unique identifier for a particular configuration + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @return string + */ + public function generateKey($config) + { + return $config->version . ',' . // possibly replace with function calls + $config->getBatchSerial($this->type) . ',' . + $config->get($this->type . '.DefinitionRev'); + } + + /** + * Tests whether or not a key is old with respect to the configuration's + * version and revision number. + * @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; + } + list($version, $hash, $revision) = explode(',', $key, 3); + $compare = version_compare($version, $config->version); + // version mismatch, is always old + 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; + } + return false; + } + + /** + * Checks if a definition's type jives with the cache's type + * @note Throws an error on failure + * @param HTMLPurifier_Definition $def Definition object to check + * @return bool true if good, false if not + */ + 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; + } + return true; + } + + /** + * 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); + + /** + * Clears all expired (older version or revision) objects from cache + * @note Be careful 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); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php new file mode 100644 index 0000000..b57a51b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php @@ -0,0 +1,112 @@ +copy(); + // reference is necessary for mocks in PHP 4 + $decorator->cache =& $cache; + $decorator->type = $cache->type; + return $decorator; + } + + /** + * Cross-compatible clone substitute + * @return HTMLPurifier_DefinitionCache_Decorator + */ + public function copy() + { + return new HTMLPurifier_DefinitionCache_Decorator(); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function add($def, $config) + { + return $this->cache->add($def, $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function set($def, $config) + { + return $this->cache->set($def, $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function replace($def, $config) + { + return $this->cache->replace($def, $config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function get($config) + { + return $this->cache->get($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function remove($config) + { + return $this->cache->remove($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function flush($config) + { + return $this->cache->flush($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function cleanup($config) + { + return $this->cache->cleanup($config); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php new file mode 100644 index 0000000..4991777 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php @@ -0,0 +1,78 @@ +definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function set($def, $config) + { + $status = parent::set($def, $config); + if ($status) { + $this->definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function replace($def, $config) + { + $status = parent::replace($def, $config); + if ($status) { + $this->definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function get($config) + { + $key = $this->generateKey($config); + if (isset($this->definitions[$key])) { + return $this->definitions[$key]; + } + $this->definitions[$key] = parent::get($config); + return $this->definitions[$key]; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in new file mode 100644 index 0000000..b1fec8d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in @@ -0,0 +1,82 @@ +checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (file_exists($file)) { + return false; + } + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return int|bool + */ + public function set($def, $config) + { + if (!$this->checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return int|bool + */ + public function replace($def, $config) + { + if (!$this->checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool|HTMLPurifier_Config + */ + public function get($config) + { + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + return unserialize(file_get_contents($file)); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function remove($config) + { + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + return unlink($file); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function flush($config) + { + if (!$this->_prepareDir($config)) { + return false; + } + $dir = $this->generateDirectoryPath($config); + $dh = opendir($dir); + // Apparently, on some versions of PHP, readdir will return + // an empty string if you pass an invalid argument to readdir. + // So you need this test. See #49. + if (false === $dh) { + return false; + } + while (false !== ($filename = readdir($dh))) { + if (empty($filename)) { + continue; + } + if ($filename[0] === '.') { + continue; + } + unlink($dir . '/' . $filename); + } + closedir($dh); + return true; + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function cleanup($config) + { + if (!$this->_prepareDir($config)) { + return false; + } + $dir = $this->generateDirectoryPath($config); + $dh = opendir($dir); + // See #49 (and above). + if (false === $dh) { + return false; + } + while (false !== ($filename = readdir($dh))) { + if (empty($filename)) { + continue; + } + if ($filename[0] === '.') { + continue; + } + $key = substr($filename, 0, strlen($filename) - 4); + if ($this->isOld($key, $config)) { + unlink($dir . '/' . $filename); + } + } + closedir($dh); + return true; + } + + /** + * Generates the file path to the serial file corresponding to + * the configuration and definition name + * @param HTMLPurifier_Config $config + * @return string + * @todo Make protected + */ + public function generateFilePath($config) + { + $key = $this->generateKey($config); + return $this->generateDirectoryPath($config) . '/' . $key . '.ser'; + } + + /** + * Generates the path to the directory contain this cache's serial files + * @param HTMLPurifier_Config $config + * @return string + * @note No trailing slash + * @todo Make protected + */ + public function generateDirectoryPath($config) + { + $base = $this->generateBaseDirectoryPath($config); + return $base . '/' . $this->type; + } + + /** + * Generates path to base directory that contains all definition type + * serials + * @param HTMLPurifier_Config $config + * @return mixed|string + * @todo Make protected + */ + public function generateBaseDirectoryPath($config) + { + $base = $config->get('Cache.SerializerPath'); + $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base; + return $base; + } + + /** + * Convenience wrapper function for file_put_contents + * @param string $file File name to write to + * @param string $data Data to write into file + * @param HTMLPurifier_Config $config + * @return int|bool Number of bytes written if success, or false if failure. + */ + private function _write($file, $data, $config) + { + $result = file_put_contents($file, $data); + if ($result !== false) { + // set permissions of the new file (no execute) + $chmod = $config->get('Cache.SerializerPermissions'); + if ($chmod !== null) { + chmod($file, $chmod & 0666); + } + } + return $result; + } + + /** + * Prepares the directory that this type stores the serials in + * @param HTMLPurifier_Config $config + * @return bool True if successful + */ + private function _prepareDir($config) + { + $directory = $this->generateDirectoryPath($config); + $chmod = $config->get('Cache.SerializerPermissions'); + if ($chmod === null) { + if (!@mkdir($directory) && !is_dir($directory)) { + trigger_error( + 'Could not create directory ' . $directory . '', + E_USER_WARNING + ); + return false; + } + return true; + } + if (!is_dir($directory)) { + $base = $this->generateBaseDirectoryPath($config); + if (!is_dir($base)) { + trigger_error( + 'Base directory ' . $base . ' does not exist, + please create or change using %Cache.SerializerPath', + E_USER_WARNING + ); + return false; + } elseif (!$this->_testPermissions($base, $chmod)) { + return false; + } + if (!@mkdir($directory, $chmod) && !is_dir($directory)) { + trigger_error( + 'Could not create directory ' . $directory . '', + E_USER_WARNING + ); + return false; + } + if (!$this->_testPermissions($directory, $chmod)) { + return false; + } + } elseif (!$this->_testPermissions($directory, $chmod)) { + return false; + } + return true; + } + + /** + * Tests permissions on a directory and throws out friendly + * error messages and attempts to chmod it itself if possible + * @param string $dir Directory path + * @param int $chmod Permissions + * @return bool True if directory is writable + */ + private function _testPermissions($dir, $chmod) + { + // early abort, if it is writable, everything is hunky-dory + if (is_writable($dir)) { + return true; + } + if (!is_dir($dir)) { + // generally, you'll want to handle this beforehand + // so a more specific error message can be given + trigger_error( + 'Directory ' . $dir . ' does not exist', + E_USER_WARNING + ); + return false; + } + if (function_exists('posix_getuid') && $chmod !== null) { + // POSIX system, we can give more specific advice + if (fileowner($dir) === posix_getuid()) { + // we can chmod it ourselves + $chmod = $chmod | 0700; + if (chmod($dir, $chmod)) { + return true; + } + } elseif (filegroup($dir) === posix_getgid()) { + $chmod = $chmod | 0070; + } else { + // PHP's probably running as nobody, it is + // not obvious how to fix this (777 is probably + // bad if you are multi-user), let the user figure it out + $chmod = null; + } + trigger_error( + 'Directory ' . $dir . ' not writable. ' . + ($chmod === null ? '' : 'Please chmod to ' . decoct($chmod)), + E_USER_WARNING + ); + } else { + // generic error message + trigger_error( + 'Directory ' . $dir . ' not writable, ' . + 'please alter file permissions', + E_USER_WARNING + ); + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README new file mode 100644 index 0000000..2e35c1c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README @@ -0,0 +1,3 @@ +This is a dummy file to prevent Git from ignoring this empty directory. + + vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php new file mode 100644 index 0000000..3a0f461 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php @@ -0,0 +1,106 @@ + array()); + + /** + * @type array + */ + protected $implementations = array(); + + /** + * @type HTMLPurifier_DefinitionCache_Decorator[] + */ + protected $decorators = array(); + + /** + * Initialize default decorators + */ + 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) + { + static $instance; + if ($prototype !== null) { + $instance = $prototype; + } elseif ($instance === null || $prototype === true) { + $instance = new HTMLPurifier_DefinitionCacheFactory(); + $instance->setup(); + } + return $instance; + } + + /** + * Registers a new definition cache object + * @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) + { + $this->implementations[$short] = $long; + } + + /** + * Factory method that creates a cache object based on configuration + * @param string $type Name of definitions handled by cache + * @param HTMLPurifier_Config $config Config instance + * @return mixed + */ + public function create($type, $config) + { + $method = $config->get('Cache.DefinitionImpl'); + if ($method === null) { + return new HTMLPurifier_DefinitionCache_Null($type); + } + if (!empty($this->caches[$method][$type])) { + return $this->caches[$method][$type]; + } + if (isset($this->implementations[$method]) && + class_exists($class = $this->implementations[$method])) { + $cache = new $class($type); + } else { + if ($method != 'Serializer') { + trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING); + } + $cache = new HTMLPurifier_DefinitionCache_Serializer($type); + } + foreach ($this->decorators as $decorator) { + $new_cache = $decorator->decorate($cache); + // prevent infinite recursion in PHP 4 + unset($cache); + $cache = $new_cache; + } + $this->caches[$method][$type] = $cache; + return $this->caches[$method][$type]; + } + + /** + * Registers a decorator to add to all new cache objects + * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator + */ + public function addDecorator($decorator) + { + if (is_string($decorator)) { + $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator"; + $decorator = new $class; + } + $this->decorators[$decorator->name] = $decorator; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php new file mode 100644 index 0000000..4acd06e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php @@ -0,0 +1,73 @@ +renderDoctype. + * If structure changes, please update that function. + */ +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 + ) { + $this->name = $name; + $this->xml = $xml; + $this->modules = $modules; + $this->tidyModules = $tidyModules; + $this->aliases = $aliases; + $this->dtdPublic = $dtd_public; + $this->dtdSystem = $dtd_system; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php new file mode 100644 index 0000000..acc1d64 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php @@ -0,0 +1,142 @@ +doctypes[$doctype->name] = $doctype; + $name = $doctype->name; + // hookup aliases + foreach ($doctype->aliases as $alias) { + if (isset($this->doctypes[$alias])) { + continue; + } + $this->aliases[$alias] = $name; + } + // remove old aliases + if (isset($this->aliases[$name])) { + unset($this->aliases[$name]); + } + return $doctype; + } + + /** + * Retrieves reference to a doctype of a certain name + * @note This function resolves aliases + * @note When possible, use the more fully-featured make() + * @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]; + } + if (!isset($this->doctypes[$doctype])) { + trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR); + $anon = new HTMLPurifier_Doctype($doctype); + return $anon; + } + return $this->doctypes[$doctype]; + } + + /** + * Creates a doctype based on a configuration object, + * will perform initialization on the doctype + * @note Use this function to get a copy of doctype that config + * 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) + { + return clone $this->get($this->getDoctypeFromConfig($config)); + } + + /** + * Retrieves the doctype from the configuration object + * @param HTMLPurifier_Config $config + * @return string + */ + public function getDoctypeFromConfig($config) + { + // recommended test + $doctype = $config->get('HTML.Doctype'); + if (!empty($doctype)) { + return $doctype; + } + $doctype = $config->get('HTML.CustomDoctype'); + if (!empty($doctype)) { + return $doctype; + } + // backwards-compatibility + if ($config->get('HTML.XHTML')) { + $doctype = 'XHTML 1.0'; + } else { + $doctype = 'HTML 4.01'; + } + if ($config->get('HTML.Strict')) { + $doctype .= ' Strict'; + } else { + $doctype .= ' Transitional'; + } + return $doctype; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php new file mode 100644 index 0000000..57cfd2b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php @@ -0,0 +1,216 @@ +setup(), this array may also + * contain an array at index 0 that indicates which attribute + * collections to load into the full array. It may also + * contain string indentifiers in lieu of HTMLPurifier_AttrDef, + * see HTMLPurifier_AttrTypes on how they are expanded during + * HTMLPurifier_HTMLDefinition->setup() processing. + */ + public $attr = array(); + + // XXX: Design note: currently, it's not possible to override + // previously defined AttrTransforms without messing around with + // the final generated config. This is by design; a previous version + // used an associated list of attr_transform, but it was extremely + // easy to accidentally override other attribute transforms by + // forgetting to specify an index (and just using 0.) While we + // could check this by checking the index number and complaining, + // there is a second problem which is that it is not at all easy to + // tell when something is getting overridden. Combine this with a + // codebase where this isn't really being used, and it's perfect for + // nuking. + + /** + * 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. + * @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 + * into an HTMLPurifier_ChildDef. + * @warning This is a temporary variable that is not available after + * being processed by HTMLDefinition + * @type string + */ + public $content_model; + + /** + * Value of $child->type, used to determine which ChildDef to use, + * used in combination with $content_model. + * @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() + * @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 + * exclusion for children, but we go the simpler, SGML-style + * route of flat-out exclusions, which correctly apply to + * all descendants and not just children. Note that the XHTML + * Modularization Abstract Modules are blithely unaware of such + * distinctions. + */ + public $excludes = array(); + + /** + * This tag is explicitly auto-closed by the following tags. + * @type array + */ + public $autoclose = array(); + + /** + * 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) + { + $def = new HTMLPurifier_ElementDef(); + $def->content_model = $content_model; + $def->content_model_type = $content_model_type; + $def->attr = $attr; + return $def; + } + + /** + * 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) + { + // later keys takes precedence + foreach ($def->attr as $k => $v) { + if ($k === 0) { + // merge in the includes + // sorry, no way to override an include + foreach ($v as $v2) { + $this->attr[0][] = $v2; + } + continue; + } + if ($v === false) { + if (isset($this->attr[$k])) { + unset($this->attr[$k]); + } + continue; + } + $this->attr[$k] = $v; + } + $this->_mergeAssocArray($this->excludes, $def->excludes); + $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)) { + $this->content_model = + str_replace("#SUPER", (string)$this->content_model, $def->content_model); + $this->child = false; + } + 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; + } + } + + /** + * Merges one array into another, removes values which equal false + * @param $a1 Array by reference that is merged into + * @param $a2 Array that merges into $a1 + */ + private function _mergeAssocArray(&$a1, $a2) + { + foreach ($a2 as $k => $v) { + if ($v === false) { + if (isset($a1[$k])) { + unset($a1[$k]); + } + continue; + } + $a1[$k] = $v; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php new file mode 100644 index 0000000..d4791cc --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php @@ -0,0 +1,617 @@ += $c) { + $r .= self::unsafeIconv($in, $out, substr($text, $i)); + break; + } + // wibble the boundary + if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) { + $chunk_size = $max_chunk_size; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) { + $chunk_size = $max_chunk_size - 1; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) { + $chunk_size = $max_chunk_size - 2; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) { + $chunk_size = $max_chunk_size - 3; + } else { + return false; // rather confusing UTF-8... + } + $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths + $r .= self::unsafeIconv($in, $out, $chunk); + $i += $chunk_size; + } + return $r; + } else { + return false; + } + } else { + return false; + } + } + + /** + * Cleans a UTF-8 string for well-formedness and SGML validity + * + * It will parse according to UTF-8 and return a valid UTF8 string, with + * non-SGML codepoints excluded. + * + * Specifically, it will permit: + * \x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF} + * Source: https://www.w3.org/TR/REC-xml/#NT-Char + * Arguably this function should be modernized to the HTML5 set + * of allowed characters: + * https://www.w3.org/TR/html5/syntax.html#preprocessing-the-input-stream + * which simultaneously expand and restrict the set of allowed characters. + * + * @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 + * respectively. 128 and above the code points map to multibyte + * UTF-8 representations. + * + * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and + * hsivonen@iki.fi at under the + * LGPL license. Notes on what changed are inside, but in general, + * the original code transformed UTF-8 text into an array of integer + * Unicode codepoints. Understandably, transforming that back to + * a string would be somewhat expensive, so the function was modded to + * directly operate on the string. However, this discourages code + * reuse, and the logic enumerated here would be useful for any + * function that needs to be able to understand UTF-8 characters. + * As of right now, only smart lossless character encoding converters + * would need that, and I'm probably not going to implement them. + */ + 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. + 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; + } + + $mState = 0; // cached expected number of octets after the current octet + // until the beginning of the next UTF8 character sequence + $mUcs4 = 0; // cached Unicode character + $mBytes = 1; // cached expected number of octets in the current sequence + + // original code involved an $out that was an array of Unicode + // codepoints. Instead of having to convert back into UTF-8, we've + // decided to directly append valid UTF-8 characters onto a string + // $out once they're done. $char accumulates raw bytes, while $mUcs4 + // turns into the Unicode code point, so there's some redundancy. + + $out = ''; + $char = ''; + + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $in = ord($str[$i]); + $char .= $str[$i]; // append byte to char + if (0 == $mState) { + // When mState is zero we expect either a US-ASCII character + // or a multi-octet sequence. + if (0 == (0x80 & ($in))) { + // US-ASCII, pass straight through. + if (($in <= 31 || $in == 127) && + !($in == 9 || $in == 13 || $in == 10) // save \r\t\n + ) { + // control characters, remove + } else { + $out .= $char; + } + // reset + $char = ''; + $mBytes = 1; + } elseif (0xC0 == (0xE0 & ($in))) { + // First octet of 2 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x1F) << 6; + $mState = 1; + $mBytes = 2; + } elseif (0xE0 == (0xF0 & ($in))) { + // First octet of 3 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x0F) << 12; + $mState = 2; + $mBytes = 3; + } elseif (0xF0 == (0xF8 & ($in))) { + // First octet of 4 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x07) << 18; + $mState = 3; + $mBytes = 4; + } elseif (0xF8 == (0xFC & ($in))) { + // First octet of 5 octet sequence. + // + // This is illegal because the encoded codepoint must be + // either: + // (a) not the shortest form or + // (b) outside the Unicode range of 0-0x10FFFF. + // Rather than trying to resynchronize, we will carry on + // until the end of the sequence and let the later error + // handling code catch it. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x03) << 24; + $mState = 4; + $mBytes = 5; + } elseif (0xFC == (0xFE & ($in))) { + // First octet of 6 octet sequence, see comments for 5 + // octet sequence. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 1) << 30; + $mState = 5; + $mBytes = 6; + } else { + // Current octet is neither in the US-ASCII range nor a + // legal first octet of a multi-octet sequence. + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char = ''; + } + } else { + // When mState is non-zero, we expect a continuation of the + // multi-octet sequence + if (0x80 == (0xC0 & ($in))) { + // Legal continuation. + $shift = ($mState - 1) * 6; + $tmp = $in; + $tmp = ($tmp & 0x0000003F) << $shift; + $mUcs4 |= $tmp; + + if (0 == --$mState) { + // End of the multi-octet sequence. mUcs4 now contains + // the final Unicode codepoint to be output + + // Check for illegal sequences and codepoints. + + // From Unicode 3.1, non-shortest form is illegal + if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || + ((3 == $mBytes) && ($mUcs4 < 0x0800)) || + ((4 == $mBytes) && ($mUcs4 < 0x10000)) || + (4 < $mBytes) || + // From Unicode 3.2, surrogate characters = illegal + (($mUcs4 & 0xFFFFF800) == 0xD800) || + // Codepoints outside the Unicode range are illegal + ($mUcs4 > 0x10FFFF) + ) { + + } elseif (0xFEFF != $mUcs4 && // omit BOM + // check for valid Char unicode codepoints + ( + 0x9 == $mUcs4 || + 0xA == $mUcs4 || + 0xD == $mUcs4 || + (0x20 <= $mUcs4 && 0x7E >= $mUcs4) || + // 7F-9F is not strictly prohibited by XML, + // but it is non-SGML, and thus we don't allow it + (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) || + (0xE000 <= $mUcs4 && 0xFFFD >= $mUcs4) || + (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4) + ) + ) { + $out .= $char; + } + // initialize UTF8 cache (reset) + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char = ''; + } + } else { + // ((0xC0 & (*in) != 0x80) && (mState != 0)) + // Incomplete multi-octet sequence. + // used to result in complete fail, but we'll reset + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char =''; + } + } + } + return $out; + } + + /** + * Translates a Unicode codepoint into its corresponding UTF-8 character. + * @note Based on Feyd's function at + * , + * which is in public domain. + * @note While we're going to do code point parsing anyway, a good + * optimization would be to refuse to translate code points that + * are non-SGML characters. However, this could lead to duplication. + * @note This is very similar to the unichr function in + * maintenance/generate-entity-file.php (although this is superior, + * due to its sanity checks). + */ + + // +----------+----------+----------+----------+ + // | 33222222 | 22221111 | 111111 | | + // | 10987654 | 32109876 | 54321098 | 76543210 | bit + // +----------+----------+----------+----------+ + // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F + // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF + // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF + // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF + // +----------+----------+----------+----------+ + // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF) + // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes + // +----------+----------+----------+----------+ + + 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 + return ''; + } + + $x = $y = $z = $w = 0; + if ($code < 128) { + // regular ASCII character + $x = $code; + } else { + // set up bits for UTF-8 + $x = ($code & 63) | 128; + if ($code < 2048) { + $y = (($code & 2047) >> 6) | 192; + } else { + $y = (($code & 4032) >> 6) | 128; + if ($code < 65536) { + $z = (($code >> 12) & 15) | 224; + } else { + $z = (($code >> 12) & 63) | 128; + $w = (($code >> 18) & 7) | 240; + } + } + } + // set up the actual character + $ret = ''; + if ($w) { + $ret .= chr($w); + } + if ($z) { + $ret .= chr($z); + } + if ($y) { + $ret .= chr($y); + } + $ret .= chr($x); + + return $ret; + } + + /** + * @return bool + */ + public static function iconvAvailable() + { + static $iconv = null; + if ($iconv === null) { + $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE; + } + return $iconv; + } + + /** + * 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) + { + $encoding = $config->get('Core.Encoding'); + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + 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); + if ($str === false) { + // $encoding is not a valid encoding + trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR); + return ''; + } + // If the string is bjorked by Shift_JIS or a similar encoding + // that doesn't support all of ASCII, convert the naughty + // characters to their true byte-wise ASCII/UTF-8 equivalents. + $str = strtr($str, self::testEncodingSupportsASCII($encoding)); + return $str; + } elseif ($encoding === 'iso-8859-1' && function_exists('mb_convert_encoding')) { + $str = mb_convert_encoding($str, 'UTF-8', 'ISO-8859-1'); + return $str; + } + $bug = HTMLPurifier_Encoder::testIconvTruncateBug(); + 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 + ); + } + } + + /** + * 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) + { + $encoding = $config->get('Core.Encoding'); + if ($escape = $config->get('Core.EscapeNonASCIICharacters')) { + $str = self::convertToASCIIDumbLossless($str); + } + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + 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] = ''; + } + $str = strtr($str, $clear_fix); + } + $str = strtr($str, array_flip($ascii_fix)); + // Normal stuff + $str = self::iconv('utf-8', $encoding . '//IGNORE', $str); + return $str; + } elseif ($encoding === 'iso-8859-1' && function_exists('mb_convert_encoding')) { + $str = mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8'); + return $str; + } + trigger_error('Encoding not supported', E_USER_ERROR); + // You might be tempted to assume that the ASCII representation + // might be OK, however, this is *not* universally true over all + // encodings. So we take the conservative route here, rather + // than forcibly turn on %Core.EscapeNonASCIICharacters + } + + /** + * Lossless (character-wise) conversion of HTML to ASCII + * @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. + * @note Uses decimal numeric entities since they are best supported. + * @note This is a DUMB function: it has no concept of keeping + * character entities that the projected character encoding + * can allow. We could possibly implement a smart version + * but that would require it to also know which Unicode + * codepoints the charset supported (not an easy task). + * @note Sort of with cleanUTF8() but it assumes that $str is + * well-formed UTF-8 + */ + 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); + $bytesleft = 0; + } elseif ($bytevalue <= 0xBF) { //10xx xxxx + $working = $working << 6; + $working += ($bytevalue & 0x3F); + $bytesleft--; + if ($bytesleft <= 0) { + $result .= "&#" . $working . ";"; + } + } elseif ($bytevalue <= 0xDF) { //110x xxxx + $working = $bytevalue & 0x1F; + $bytesleft = 1; + } elseif ($bytevalue <= 0xEF) { //1110 xxxx + $working = $bytevalue & 0x0F; + $bytesleft = 2; + } else { //1111 0xxx + $working = $bytevalue & 0x07; + $bytesleft = 3; + } + } + return $result; + } + + /** No bugs detected in iconv. */ + const ICONV_OK = 0; + + /** Iconv truncates output if converting from UTF-8 to another + * character set with //IGNORE, and a non-encodable character is found */ + const ICONV_TRUNCATES = 1; + + /** Iconv does not support //IGNORE, making it unusable for + * transcoding purposes */ + const ICONV_UNUSABLE = 2; + + /** + * glibc iconv has a known bug where it doesn't handle the magic + * //IGNORE stanza correctly. In particular, rather than ignore + * characters, it will return an EILSEQ after consuming some number + * of characters, and expect you to restart iconv as if it were + * an E2BIG. Old versions of PHP did not respect the errno, and + * returned the fragment, so as a result you would see iconv + * mysteriously truncating output. We can work around this by + * manually chopping our input into segments of about 8000 + * characters, as long as PHP ignores the error code. If PHP starts + * paying attention to the error code, iconv becomes unusable. + * + * @return int Error code indicating severity of bug. + */ + public static function testIconvTruncateBug() + { + static $code = null; + if ($code === null) { + // better not use iconv, otherwise infinite loop! + $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000)); + if ($r === false) { + $code = self::ICONV_UNUSABLE; + } 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 + ); + } else { + $code = self::ICONV_OK; + } + } + return $code; + } + + /** + * This expensive function tests whether or not a given character + * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will + * fail this test, and require special processing. Variable width + * encodings shouldn't ever fail. + * + * @param string $encoding Encoding name to test, as per iconv format + * @param bool $bypass Whether or not to bypass the precompiled arrays. + * @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) + { + // 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, + // so bug is not triggered. + // If ICONV_UNUSABLE, this call is irrelevant + static $encodings = array(); + if (!$bypass) { + if (isset($encodings[$encoding])) { + return $encodings[$encoding]; + } + $lenc = strtolower($encoding); + switch ($lenc) { + case 'shift_jis': + return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~'); + case 'johab': + return array("\xE2\x82\xA9" => '\\'); + } + if (strpos($lenc, 'iso-8859-') === 0) { + return array(); + } + } + $ret = array(); + 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 === '' || + // 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) + ) { + // Reverse engineer: what's the UTF-8 equiv of this byte + // sequence? This assumes that there's no variable width + // encoding that doesn't support ASCII. + $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c; + } + } + $encodings[$encoding] = $ret; + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php new file mode 100644 index 0000000..f12ff13 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php @@ -0,0 +1,48 @@ +table = unserialize(file_get_contents($file)); + } + + /** + * Retrieves sole instance of the object. + * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with. + * @return HTMLPurifier_EntityLookup + */ + public static function instance($prototype = false) + { + // no references, since PHP doesn't copy unless modified + static $instance = null; + if ($prototype) { + $instance = $prototype; + } elseif (!$instance) { + $instance = new HTMLPurifier_EntityLookup(); + $instance->setup(); + } + return $instance; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser new file mode 100644 index 0000000..e8b0812 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser @@ -0,0 +1 @@ +a:253:{s:4:"fnof";s:2:"ƒ";s:5:"Alpha";s:2:"Α";s:4:"Beta";s:2:"Β";s:5:"Gamma";s:2:"Γ";s:5:"Delta";s:2:"Δ";s:7:"Epsilon";s:2:"Ε";s:4:"Zeta";s:2:"Ζ";s:3:"Eta";s:2:"Η";s:5:"Theta";s:2:"Θ";s:4:"Iota";s:2:"Ι";s:5:"Kappa";s:2:"Κ";s:6:"Lambda";s:2:"Λ";s:2:"Mu";s:2:"Μ";s:2:"Nu";s:2:"Ν";s:2:"Xi";s:2:"Ξ";s:7:"Omicron";s:2:"Ο";s:2:"Pi";s:2:"Π";s:3:"Rho";s:2:"Ρ";s:5:"Sigma";s:2:"Σ";s:3:"Tau";s:2:"Τ";s:7:"Upsilon";s:2:"Υ";s:3:"Phi";s:2:"Φ";s:3:"Chi";s:2:"Χ";s:3:"Psi";s:2:"Ψ";s:5:"Omega";s:2:"Ω";s:5:"alpha";s:2:"α";s:4:"beta";s:2:"β";s:5:"gamma";s:2:"γ";s:5:"delta";s:2:"δ";s:7:"epsilon";s:2:"ε";s:4:"zeta";s:2:"ζ";s:3:"eta";s:2:"η";s:5:"theta";s:2:"θ";s:4:"iota";s:2:"ι";s:5:"kappa";s:2:"κ";s:6:"lambda";s:2:"λ";s:2:"mu";s:2:"μ";s:2:"nu";s:2:"ν";s:2:"xi";s:2:"ξ";s:7:"omicron";s:2:"ο";s:2:"pi";s:2:"π";s:3:"rho";s:2:"ρ";s:6:"sigmaf";s:2:"ς";s:5:"sigma";s:2:"σ";s:3:"tau";s:2:"τ";s:7:"upsilon";s:2:"υ";s:3:"phi";s:2:"φ";s:3:"chi";s:2:"χ";s:3:"psi";s:2:"ψ";s:5:"omega";s:2:"ω";s:8:"thetasym";s:2:"ϑ";s:5:"upsih";s:2:"ϒ";s:3:"piv";s:2:"ϖ";s:4:"bull";s:3:"•";s:6:"hellip";s:3:"…";s:5:"prime";s:3:"′";s:5:"Prime";s:3:"″";s:5:"oline";s:3:"‾";s:5:"frasl";s:3:"⁄";s:6:"weierp";s:3:"℘";s:5:"image";s:3:"ℑ";s:4:"real";s:3:"ℜ";s:5:"trade";s:3:"™";s:7:"alefsym";s:3:"ℵ";s:4:"larr";s:3:"←";s:4:"uarr";s:3:"↑";s:4:"rarr";s:3:"→";s:4:"darr";s:3:"↓";s:4:"harr";s:3:"↔";s:5:"crarr";s:3:"↵";s:4:"lArr";s:3:"⇐";s:4:"uArr";s:3:"⇑";s:4:"rArr";s:3:"⇒";s:4:"dArr";s:3:"⇓";s:4:"hArr";s:3:"⇔";s:6:"forall";s:3:"∀";s:4:"part";s:3:"∂";s:5:"exist";s:3:"∃";s:5:"empty";s:3:"∅";s:5:"nabla";s:3:"∇";s:4:"isin";s:3:"∈";s:5:"notin";s:3:"∉";s:2:"ni";s:3:"∋";s:4:"prod";s:3:"∏";s:3:"sum";s:3:"∑";s:5:"minus";s:3:"−";s:6:"lowast";s:3:"∗";s:5:"radic";s:3:"√";s:4:"prop";s:3:"∝";s:5:"infin";s:3:"∞";s:3:"ang";s:3:"∠";s:3:"and";s:3:"∧";s:2:"or";s:3:"∨";s:3:"cap";s:3:"∩";s:3:"cup";s:3:"∪";s:3:"int";s:3:"∫";s:6:"there4";s:3:"∴";s:3:"sim";s:3:"∼";s:4:"cong";s:3:"≅";s:5:"asymp";s:3:"≈";s:2:"ne";s:3:"≠";s:5:"equiv";s:3:"≡";s:2:"le";s:3:"≤";s:2:"ge";s:3:"≥";s:3:"sub";s:3:"⊂";s:3:"sup";s:3:"⊃";s:4:"nsub";s:3:"⊄";s:4:"sube";s:3:"⊆";s:4:"supe";s:3:"⊇";s:5:"oplus";s:3:"⊕";s:6:"otimes";s:3:"⊗";s:4:"perp";s:3:"⊥";s:4:"sdot";s:3:"⋅";s:5:"lceil";s:3:"⌈";s:5:"rceil";s:3:"⌉";s:6:"lfloor";s:3:"⌊";s:6:"rfloor";s:3:"⌋";s:4:"lang";s:3:"〈";s:4:"rang";s:3:"〉";s:3:"loz";s:3:"◊";s:6:"spades";s:3:"♠";s:5:"clubs";s:3:"♣";s:6:"hearts";s:3:"♥";s:5:"diams";s:3:"♦";s:4:"quot";s:1:""";s:3:"amp";s:1:"&";s:2:"lt";s:1:"<";s:2:"gt";s:1:">";s:4:"apos";s:1:"'";s:5:"OElig";s:2:"Œ";s:5:"oelig";s:2:"œ";s:6:"Scaron";s:2:"Š";s:6:"scaron";s:2:"š";s:4:"Yuml";s:2:"Ÿ";s:4:"circ";s:2:"ˆ";s:5:"tilde";s:2:"˜";s:4:"ensp";s:3:" ";s:4:"emsp";s:3:" ";s:6:"thinsp";s:3:" ";s:4:"zwnj";s:3:"‌";s:3:"zwj";s:3:"‍";s:3:"lrm";s:3:"‎";s:3:"rlm";s:3:"‏";s:5:"ndash";s:3:"–";s:5:"mdash";s:3:"—";s:5:"lsquo";s:3:"‘";s:5:"rsquo";s:3:"’";s:5:"sbquo";s:3:"‚";s:5:"ldquo";s:3:"“";s:5:"rdquo";s:3:"”";s:5:"bdquo";s:3:"„";s:6:"dagger";s:3:"†";s:6:"Dagger";s:3:"‡";s:6:"permil";s:3:"‰";s:6:"lsaquo";s:3:"‹";s:6:"rsaquo";s:3:"›";s:4:"euro";s:3:"€";s:4:"nbsp";s:2:" ";s:5:"iexcl";s:2:"¡";s:4:"cent";s:2:"¢";s:5:"pound";s:2:"£";s:6:"curren";s:2:"¤";s:3:"yen";s:2:"¥";s:6:"brvbar";s:2:"¦";s:4:"sect";s:2:"§";s:3:"uml";s:2:"¨";s:4:"copy";s:2:"©";s:4:"ordf";s:2:"ª";s:5:"laquo";s:2:"«";s:3:"not";s:2:"¬";s:3:"shy";s:2:"­";s:3:"reg";s:2:"®";s:4:"macr";s:2:"¯";s:3:"deg";s:2:"°";s:6:"plusmn";s:2:"±";s:4:"sup2";s:2:"²";s:4:"sup3";s:2:"³";s:5:"acute";s:2:"´";s:5:"micro";s:2:"µ";s:4:"para";s:2:"¶";s:6:"middot";s:2:"·";s:5:"cedil";s:2:"¸";s:4:"sup1";s:2:"¹";s:4:"ordm";s:2:"º";s:5:"raquo";s:2:"»";s:6:"frac14";s:2:"¼";s:6:"frac12";s:2:"½";s:6:"frac34";s:2:"¾";s:6:"iquest";s:2:"¿";s:6:"Agrave";s:2:"À";s:6:"Aacute";s:2:"Á";s:5:"Acirc";s:2:"Â";s:6:"Atilde";s:2:"Ã";s:4:"Auml";s:2:"Ä";s:5:"Aring";s:2:"Å";s:5:"AElig";s:2:"Æ";s:6:"Ccedil";s:2:"Ç";s:6:"Egrave";s:2:"È";s:6:"Eacute";s:2:"É";s:5:"Ecirc";s:2:"Ê";s:4:"Euml";s:2:"Ë";s:6:"Igrave";s:2:"Ì";s:6:"Iacute";s:2:"Í";s:5:"Icirc";s:2:"Î";s:4:"Iuml";s:2:"Ï";s:3:"ETH";s:2:"Ð";s:6:"Ntilde";s:2:"Ñ";s:6:"Ograve";s:2:"Ò";s:6:"Oacute";s:2:"Ó";s:5:"Ocirc";s:2:"Ô";s:6:"Otilde";s:2:"Õ";s:4:"Ouml";s:2:"Ö";s:5:"times";s:2:"×";s:6:"Oslash";s:2:"Ø";s:6:"Ugrave";s:2:"Ù";s:6:"Uacute";s:2:"Ú";s:5:"Ucirc";s:2:"Û";s:4:"Uuml";s:2:"Ü";s:6:"Yacute";s:2:"Ý";s:5:"THORN";s:2:"Þ";s:5:"szlig";s:2:"ß";s:6:"agrave";s:2:"à";s:6:"aacute";s:2:"á";s:5:"acirc";s:2:"â";s:6:"atilde";s:2:"ã";s:4:"auml";s:2:"ä";s:5:"aring";s:2:"å";s:5:"aelig";s:2:"æ";s:6:"ccedil";s:2:"ç";s:6:"egrave";s:2:"è";s:6:"eacute";s:2:"é";s:5:"ecirc";s:2:"ê";s:4:"euml";s:2:"ë";s:6:"igrave";s:2:"ì";s:6:"iacute";s:2:"í";s:5:"icirc";s:2:"î";s:4:"iuml";s:2:"ï";s:3:"eth";s:2:"ð";s:6:"ntilde";s:2:"ñ";s:6:"ograve";s:2:"ò";s:6:"oacute";s:2:"ó";s:5:"ocirc";s:2:"ô";s:6:"otilde";s:2:"õ";s:4:"ouml";s:2:"ö";s:6:"divide";s:2:"÷";s:6:"oslash";s:2:"ø";s:6:"ugrave";s:2:"ù";s:6:"uacute";s:2:"ú";s:5:"ucirc";s:2:"û";s:4:"uuml";s:2:"ü";s:6:"yacute";s:2:"ý";s:5:"thorn";s:2:"þ";s:4:"yuml";s:2:"ÿ";} \ No newline at end of file diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php new file mode 100644 index 0000000..3ef2d09 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php @@ -0,0 +1,285 @@ +_semiOptionalPrefixRegex = "/&()()()($semi_optional)/"; + + $this->_textEntitiesRegex = + '/&(?:'. + // hex + '[#]x([a-fA-F0-9]+);?|'. + // dec + '[#]0*(\d+);?|'. + // string (mandatory semicolon) + // NB: order matters: match semicolon preferentially + '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'. + // string (optional semicolon) + "($semi_optional)". + ')/'; + + $this->_attrEntitiesRegex = + '/&(?:'. + // hex + '[#]x([a-fA-F0-9]+);?|'. + // dec + '[#]0*(\d+);?|'. + // string (mandatory semicolon) + // NB: order matters: match semicolon preferentially + '([A-Za-z_:][A-Za-z0-9.\-_:]*);|'. + // string (optional semicolon) + // don't match if trailing is equals or alphanumeric (URL + // like) + "($semi_optional)(?![=;A-Za-z0-9])". + ')/'; + + } + + /** + * Substitute entities with the parsed equivalents. Use this on + * textual data in an HTML document (as opposed to attributes.) + * + * @param string $string String to have entities parsed. + * @return string Parsed string. + */ + public function substituteTextEntities($string) + { + return preg_replace_callback( + $this->_textEntitiesRegex, + array($this, 'entityCallback'), + $string + ); + } + + /** + * Substitute entities with the parsed equivalents. Use this on + * attribute contents in documents. + * + * @param string $string String to have entities parsed. + * @return string Parsed string. + */ + public function substituteAttrEntities($string) + { + return preg_replace_callback( + $this->_attrEntitiesRegex, + array($this, 'entityCallback'), + $string + ); + } + + /** + * Callback function for substituteNonSpecialEntities() that does the work. + * + * @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). + * @return string Replacement string. + */ + + protected function entityCallback($matches) + { + $entity = $matches[0]; + $hex_part = @$matches[1]; + $dec_part = @$matches[2]; + $named_part = empty($matches[3]) ? (empty($matches[4]) ? "" : $matches[4]) : $matches[3]; + if ($hex_part !== NULL && $hex_part !== "") { + return HTMLPurifier_Encoder::unichr(hexdec($hex_part)); + } elseif ($dec_part !== NULL && $dec_part !== "") { + return HTMLPurifier_Encoder::unichr((int) $dec_part); + } else { + if (!$this->_entity_lookup) { + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + } + if (isset($this->_entity_lookup->table[$named_part])) { + return $this->_entity_lookup->table[$named_part]; + } else { + // exact match didn't match anything, so test if + // any of the semicolon optional match the prefix. + // Test that this is an EXACT match is important to + // prevent infinite loop + if (!empty($matches[3])) { + return preg_replace_callback( + $this->_semiOptionalPrefixRegex, + array($this, 'entityCallback'), + $entity + ); + } + return $entity; + } + } + } + + // LEGACY CODE BELOW + + /** + * 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) + + /** + * Decimal to parsed string conversion table for special entities. + * @type array + */ + protected $_special_dec2str = + array( + 34 => '"', + 38 => '&', + 39 => "'", + 60 => '<', + 62 => '>' + ); + + /** + * Stripped entity names to decimal conversion table for special entities. + * @type array + */ + protected $_special_ent2dec = + array( + 'quot' => 34, + 'amp' => 38, + 'lt' => 60, + 'gt' => 62 + ); + + /** + * Substitutes non-special entities with their parsed equivalents. Since + * running this whenever you have parsed character is t3h 5uck, we run + * it before everything else. + * + * @param string $string String to have non-special entities parsed. + * @return string Parsed 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 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). + * @return string Replacement string. + */ + + 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; + } + return HTMLPurifier_Encoder::unichr($code); + } else { + if (isset($this->_special_ent2dec[$matches[3]])) { + return $entity; + } + if (!$this->_entity_lookup) { + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + } + if (isset($this->_entity_lookup->table[$matches[3]])) { + return $this->_entity_lookup->table[$matches[3]]; + } else { + return $entity; + } + } + } + + /** + * Substitutes only special entities with their parsed equivalents. + * + * @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 String to have non-special entities parsed. + * @return string Parsed string. + */ + public function substituteSpecialEntities($string) + { + return preg_replace_callback( + $this->_substituteEntitiesRegex, + array($this, 'specialEntityCallback'), + $string + ); + } + + /** + * Callback function for substituteSpecialEntities() that does the work. + * + * This callback has same syntax as nonSpecialEntityCallback(). + * + * @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). + * @return string Replacement string. + */ + protected function specialEntityCallback($matches) + { + $entity = $matches[0]; + $is_num = (@$matches[0][1] === '#'); + if ($is_num) { + $is_hex = (@$entity[2] === 'x'); + $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; + return isset($this->_special_dec2str[$int]) ? + $this->_special_dec2str[$int] : + $entity; + } else { + return isset($this->_special_ent2dec[$matches[3]]) ? + $this->_special_dec2str[$this->_special_ent2dec[$matches[3]]] : + $entity; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php new file mode 100644 index 0000000..d47e3f2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php @@ -0,0 +1,244 @@ +locale =& $context->get('Locale'); + $this->context = $context; + $this->_current =& $this->_stacks[0]; + $this->errors =& $this->_stacks[0]; + } + + /** + * Sends an error message to the collector for later use + * @param int $severity Error severity, PHP error style (don't use E_USER_) + * @param string $msg Error message text + */ + public function send($severity, $msg) + { + $args = array(); + if (func_num_args() > 2) { + $args = func_get_args(); + array_shift($args); + unset($args[0]); + } + + $token = $this->context->get('CurrentToken', true); + $line = $token ? $token->line : $this->context->get('CurrentLine', true); + $col = $token ? $token->col : $this->context->get('CurrentCol', true); + $attr = $this->context->get('CurrentAttr', true); + + // perform special substitutions, also add custom parameters + $subst = array(); + if (!is_null($token)) { + $args['CurrentToken'] = $token; + } + if (!is_null($attr)) { + $subst['$CurrentAttr.Name'] = $attr; + if (isset($token->attr[$attr])) { + $subst['$CurrentAttr.Value'] = $token->attr[$attr]; + } + } + + if (empty($args)) { + $msg = $this->locale->getMessage($msg); + } else { + $msg = $this->locale->formatMessage($msg, $args); + } + + if (!empty($subst)) { + $msg = strtr($msg, $subst); + } + + // (numerically indexed) + $error = array( + self::LINENO => $line, + self::SEVERITY => $severity, + self::MESSAGE => $msg, + self::CHILDREN => array() + ); + $this->_current[] = $error; + + // NEW CODE BELOW ... + // 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 (is_int($line) && is_int($col)) { + if (isset($this->lines[$line][$col])) { + $struct = $this->lines[$line][$col]; + } else { + $struct = $this->lines[$line][$col] = $new_struct; + } + // These ksorts may present a performance problem + ksort($this->lines[$line], SORT_NUMERIC); + } else { + if (isset($this->lines[-1])) { + $struct = $this->lines[-1]; + } else { + $struct = $this->lines[-1] = $new_struct; + } + } + ksort($this->lines, SORT_NUMERIC); + + // Now, check if we need to operate on a lower structure + if (!empty($attr)) { + $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr); + if (!$struct->value) { + $struct->value = array($attr, 'PUT VALUE HERE'); + } + } + if (!empty($cssprop)) { + $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop); + if (!$struct->value) { + // if we tokenize CSS this might be a little more difficult to do + $struct->value = array($cssprop, 'PUT VALUE HERE'); + } + } + + // Ok, structs are all setup, now time to register the error + $struct->addError($severity, $msg); + } + + /** + * Retrieves raw error data for custom formatter to use + */ + public function getRaw() + { + return $this->errors; + } + + /** + * Default HTML formatting implementation for error messages + * @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) + { + $ret = array(); + + $this->generator = new HTMLPurifier_Generator($config, $this->context); + 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; + } + foreach ($col_array as $col => $struct) { + $this->_renderStruct($ret, $struct, $line, $col); + } + } + if (isset($this->lines[-1])) { + $this->_renderStruct($ret, $this->lines[-1]); + } + + if (empty($errors)) { + return '

' . $this->locale->getMessage('ErrorCollector: No errors') . '

'; + } else { + return '
  • ' . implode('
  • ', $ret) . '
'; + } + + } + + private function _renderStruct(&$ret, $struct, $line = null, $col = null) + { + $stack = array($struct); + $context_stack = array(array()); + while ($current = array_pop($stack)) { + $context = array_pop($context_stack); + foreach ($current->errors as $error) { + list($severity, $msg) = $error; + $string = ''; + $string .= '
'; + // W3C uses an icon to indicate the severity of the error. + $error = $this->locale->getErrorName($severity); + $string .= "$error "; + if (!is_null($line) && !is_null($col)) { + $string .= "Line $line, Column $col: "; + } else { + $string .= 'End of Document: '; + } + $string .= '' . $this->generator->escape($msg) . ' '; + $string .= '
'; + // Here, have a marker for the character on the column appropriate. + // Be sure to clip extremely long lines. + //$string .= '
';
+                //$string .= '';
+                //$string .= '
'; + $ret[] = $string; + } + foreach ($current->children as $array) { + $context[] = $current; + $stack = array_merge($stack, array_reverse($array, true)); + for ($i = count($array); $i > 0; $i--) { + $context_stack[] = $context; + } + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php new file mode 100644 index 0000000..cf869d3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php @@ -0,0 +1,74 @@ +children[$type][$id])) { + $this->children[$type][$id] = new HTMLPurifier_ErrorStruct(); + $this->children[$type][$id]->type = $type; + } + return $this->children[$type][$id]; + } + + /** + * @param int $severity + * @param string $message + */ + public function addError($severity, $message) + { + $this->errors[] = array($severity, $message); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php new file mode 100644 index 0000000..be85b4c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php @@ -0,0 +1,12 @@ +preFilter, + * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter, + * 1->postFilter. + * + * @note Methods are not declared abstract as it is perfectly legitimate + * for an implementation not to want anything to happen on a step + */ + +class HTMLPurifier_Filter +{ + + /** + * 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) + { + 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) + { + return $html; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php new file mode 100644 index 0000000..6f8e779 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php @@ -0,0 +1,345 @@ + blocks from input HTML, cleans them up + * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks') + * so they can be used elsewhere in the document. + * + * @note + * See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for + * sample usage. + * + * @note + * This filter can also be used on stylesheets not included in the + * document--something purists would probably prefer. Just directly + * call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS() + */ +class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter +{ + /** + * @type string + */ + public $name = 'ExtractStyleBlocks'; + + /** + * @type array + */ + private $_styleMatches = array(); + + /** + * @type csstidy + */ + private $_tidy; + + /** + * @type HTMLPurifier_AttrDef_HTML_ID + */ + private $_id_attrdef; + + /** + * @type HTMLPurifier_AttrDef_CSS_Ident + */ + private $_class_attrdef; + + /** + * @type HTMLPurifier_AttrDef_Enum + */ + private $_enum_attrdef; + + public function __construct() + { + $this->_tidy = new csstidy(); + $this->_tidy->set_cfg('lowercase_s', false); + $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true); + $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident(); + $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum( + array( + 'first-child', + 'link', + 'visited', + 'active', + 'hover', + 'focus' + ) + ); + } + + /** + * Save the contents of CSS blocks to style matches + * @param array $matches preg_replace style $matches array + */ + protected function styleCallback($matches) + { + $this->_styleMatches[] = $matches[1]; + } + + /** + * Removes inline + // we must not grab foo in a font-family prop). + if ($config->get('Filter.ExtractStyleBlocks.Escaping')) { + $css = str_replace( + array('<', '>', '&'), + array('\3C ', '\3E ', '\26 '), + $css + ); + } + return $css; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php new file mode 100644 index 0000000..276d836 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php @@ -0,0 +1,65 @@ +]+>.+?' . + '(?:http:)?//www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s'; + $pre_replace = '\1'; + return preg_replace($pre_regex, $pre_replace, $html); + } + + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function postFilter($html, $config, $context) + { + $post_regex = '#((?:v|cp)/[A-Za-z0-9\-_=]+)#'; + return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); + } + + /** + * @param $url + * @return string + */ + protected function armorUrl($url) + { + return str_replace('--', '--', $url); + } + + /** + * @param array $matches + * @return string + */ + protected function postFilterCallback($matches) + { + $url = $this->armorUrl($matches[1]); + return '' . + '' . + '' . + ''; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php new file mode 100644 index 0000000..eb56e2d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php @@ -0,0 +1,286 @@ + tags. + * @type bool + */ + private $_scriptFix = false; + + /** + * Cache of HTMLDefinition during HTML output to determine whether or + * not attributes should be minimized. + * @type HTMLPurifier_HTMLDefinition + */ + private $_def; + + /** + * Cache of %Output.SortAttr. + * @type bool + */ + private $_sortAttr; + + /** + * Cache of %Output.FlashCompat. + * @type bool + */ + private $_flashCompat; + + /** + * Cache of %Output.FixInnerHTML. + * @type bool + */ + private $_innerHTMLFix; + + /** + * Stack for keeping track of object information when outputting IE + * compatibility code. + * @type array + */ + private $_flashStack = array(); + + /** + * Configuration for the generator + * @type HTMLPurifier_Config + */ + protected $config; + + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + public function __construct($config, $context) + { + $this->config = $config; + $this->_scriptFix = $config->get('Output.CommentScriptContents'); + $this->_innerHTMLFix = $config->get('Output.FixInnerHTML'); + $this->_sortAttr = $config->get('Output.SortAttr'); + $this->_flashCompat = $config->get('Output.FlashCompat'); + $this->_def = $config->getHTMLDefinition(); + $this->_xhtml = $this->_def->doctype->xml; + } + + /** + * Generates HTML from an array of tokens. + * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token + * @return string Generated HTML + */ + public function generateFromTokens($tokens) + { + if (!$tokens) { + return ''; + } + + // Basic algorithm + $html = ''; + for ($i = 0, $size = count($tokens); $i < $size; $i++) { + if ($this->_scriptFix && $tokens[$i]->name === 'script' + && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) { + // script special case + // the contents of the script block must be ONE token + // for this to work. + $html .= $this->generateFromToken($tokens[$i++]); + $html .= $this->generateScriptFromToken($tokens[$i++]); + } + $html .= $this->generateFromToken($tokens[$i]); + } + + // Tidy cleanup + if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) { + $tidy = new Tidy; + $tidy->parseString( + $html, + array( + 'indent'=> true, + 'output-xhtml' => $this->_xhtml, + 'show-body-only' => true, + 'indent-spaces' => 2, + 'wrap' => 68, + ), + 'utf8' + ); + $tidy->cleanRepair(); + $html = (string) $tidy; // explicit cast necessary + } + + // Normalize newlines to system defined value + if ($this->config->get('Core.NormalizeNewlines')) { + $nl = $this->config->get('Output.Newline'); + if ($nl === null) { + $nl = PHP_EOL; + } + if ($nl !== "\n") { + $html = str_replace("\n", $nl, $html); + } + } + return $html; + } + + /** + * Generates HTML from a single token. + * @param HTMLPurifier_Token $token HTMLPurifier_Token object. + * @return string Generated HTML + */ + public function generateFromToken($token) + { + if (!$token instanceof HTMLPurifier_Token) { + trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING); + return ''; + + } elseif ($token instanceof HTMLPurifier_Token_Start) { + $attr = $this->generateAttributes($token->attr, $token->name); + if ($this->_flashCompat) { + if ($token->name == "object") { + $flash = new stdClass(); + $flash->attr = $token->attr; + $flash->param = array(); + $this->_flashStack[] = $flash; + } + } + return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_End) { + $_extra = ''; + if ($this->_flashCompat) { + if ($token->name == "object" && !empty($this->_flashStack)) { + // doesn't do anything for now + } + } + return $_extra . 'name . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) { + $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value']; + } + $attr = $this->generateAttributes($token->attr, $token->name); + return '<' . $token->name . ($attr ? ' ' : '') . $attr . + ( $this->_xhtml ? ' /': '' ) //
v.
+ . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_Text) { + return $this->escape($token->data, ENT_NOQUOTES); + + } elseif ($token instanceof HTMLPurifier_Token_Comment) { + return ''; + } else { + return ''; + + } + } + + /** + * Special case processor for the contents of script tags + * @param HTMLPurifier_Token $token HTMLPurifier_Token object. + * @return string + * @warning This runs into problems if there's already a literal + * --> somewhere inside the script contents. + */ + public function generateScriptFromToken($token) + { + if (!$token instanceof HTMLPurifier_Token_Text) { + return $this->generateFromToken($token); + } + // Thanks + $data = preg_replace('#//\s*$#', '', $token->data); + return ''; + } + + /** + * Generates attribute declarations from attribute array. + * @note This does not include the leading or trailing space. + * @param array $assoc_array_of_attributes Attribute array + * @param string $element Name of element attributes are for, used to check + * attribute minimization. + * @return string Generated HTML fragment for insertion. + */ + public function generateAttributes($assoc_array_of_attributes, $element = '') + { + $html = ''; + if ($this->_sortAttr) { + ksort($assoc_array_of_attributes); + } + foreach ($assoc_array_of_attributes as $key => $value) { + if (!$this->_xhtml) { + // Remove namespaced attributes + if (strpos($key, ':') !== false) { + continue; + } + // Check if we should minimize the attribute: val="val" -> val + if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) { + $html .= $key . ' '; + continue; + } + } + // Workaround for Internet Explorer innerHTML bug. + // Essentially, Internet Explorer, when calculating + // innerHTML, omits quotes if there are no instances of + // angled brackets, quotes or spaces. However, when parsing + // HTML (for example, when you assign to innerHTML), it + // treats backticks as quotes. Thus, + // `` + // becomes + // `` + // becomes + // + // Fortunately, all we need to do is trigger an appropriate + // quoting style, which we do by adding an extra space. + // This also is consistent with the W3C spec, which states + // that user agents may ignore leading or trailing + // whitespace (in fact, most don't, at least for attributes + // like alt, but an extra space at the end is barely + // noticeable). Still, we have a configuration knob for + // this, since this transformation is not necesary if you + // don't process user input with innerHTML or you don't plan + // on supporting Internet Explorer. + if ($this->_innerHTMLFix) { + if (strpos($value, '`') !== false) { + // check if correct quoting style would not already be + // triggered + if (strcspn($value, '"\' <>') === strlen($value)) { + // protect! + $value .= ' '; + } + } + } + $html .= $key.'="'.$this->escape($value).'" '; + } + return rtrim($html); + } + + /** + * Escapes raw text data. + * @todo This really ought to be protected, but until we have a facility + * for properly generating HTML here w/o using tokens, it stays + * public. + * @param string $string String data to escape for HTML. + * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is + * permissible for non-attribute output. + * @return string escaped data. + */ + public function escape($string, $quote = null) + { + // Workaround for APC bug on Mac Leopard reported by sidepodcast + // http://htmlpurifier.org/phorum/read.php?3,4823,4846 + if ($quote === null) { + $quote = ENT_COMPAT; + } + return htmlspecialchars($string, $quote, 'UTF-8'); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php new file mode 100644 index 0000000..9b7b334 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php @@ -0,0 +1,493 @@ +getAnonymousModule(); + if (!isset($module->info[$element_name])) { + $element = $module->addBlankElement($element_name); + } else { + $element = $module->info[$element_name]; + } + $element->attr[$attr_name] = $def; + } + + /** + * Adds a custom element to your HTML definition + * @see HTMLPurifier_HTMLModule::addElement() for detailed + * parameter and return value descriptions. + */ + public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) + { + $module = $this->getAnonymousModule(); + // assume that if the user is calling this, the element + // is safe. This may not be a good idea + $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes); + return $element; + } + + /** + * Adds a blank element to your HTML definition, for overriding + * existing behavior + * @param string $element_name + * @return HTMLPurifier_ElementDef + * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed + * parameter and return value descriptions. + */ + public function addBlankElement($element_name) + { + $module = $this->getAnonymousModule(); + $element = $module->addBlankElement($element_name); + return $element; + } + + /** + * Retrieves a reference to the anonymous module, so you can + * bust out advanced features without having to make your own + * module. + * @return HTMLPurifier_HTMLModule + */ + public function getAnonymousModule() + { + if (!$this->_anonModule) { + $this->_anonModule = new HTMLPurifier_HTMLModule(); + $this->_anonModule->name = 'Anonymous'; + } + return $this->_anonModule; + } + + private $_anonModule = null; + + // PUBLIC BUT INTERNAL VARIABLES -------------------------------------- + + /** + * @type string + */ + public $type = 'HTML'; + + /** + * @type HTMLPurifier_HTMLModuleManager + */ + public $manager; + + /** + * Performs low-cost, preliminary initialization. + */ + public function __construct() + { + $this->manager = new HTMLPurifier_HTMLModuleManager(); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetup($config) + { + $this->processModules($config); + $this->setupConfigStuff($config); + unset($this->manager); + + // cleanup some of the element definitions + foreach ($this->info as $k => $v) { + unset($this->info[$k]->content_model); + unset($this->info[$k]->content_model_type); + } + } + + /** + * Extract out the information from the manager + * @param HTMLPurifier_Config $config + */ + protected function processModules($config) + { + if ($this->_anonModule) { + // for user specific changes + // this is late-loaded so we don't have to deal with PHP4 + // reference wonky-ness + $this->manager->addModule($this->_anonModule); + unset($this->_anonModule); + } + + $this->manager->setup($config); + $this->doctype = $this->manager->doctype; + + foreach ($this->manager->modules as $module) { + foreach ($module->info_tag_transform as $k => $v) { + if ($v === false) { + unset($this->info_tag_transform[$k]); + } else { + $this->info_tag_transform[$k] = $v; + } + } + foreach ($module->info_attr_transform_pre as $k => $v) { + if ($v === false) { + unset($this->info_attr_transform_pre[$k]); + } else { + $this->info_attr_transform_pre[$k] = $v; + } + } + foreach ($module->info_attr_transform_post as $k => $v) { + if ($v === false) { + unset($this->info_attr_transform_post[$k]); + } else { + $this->info_attr_transform_post[$k] = $v; + } + } + foreach ($module->info_injector as $k => $v) { + if ($v === false) { + unset($this->info_injector[$k]); + } else { + $this->info_injector[$k] = $v; + } + } + } + $this->info = $this->manager->getElements(); + $this->info_content_sets = $this->manager->contentSets->lookup; + } + + /** + * Sets up stuff based on config. We need a better way of doing this. + * @param HTMLPurifier_Config $config + */ + protected function setupConfigStuff($config) + { + $block_wrapper = $config->get('HTML.BlockWrapper'); + if (isset($this->info_content_sets['Block'][$block_wrapper])) { + $this->info_block_wrapper = $block_wrapper; + } else { + trigger_error( + 'Cannot use non-block element as block wrapper', + E_USER_ERROR + ); + } + + $parent = $config->get('HTML.Parent'); + $def = $this->manager->getElement($parent, true); + if ($def) { + $this->info_parent = $parent; + $this->info_parent_def = $def; + } else { + trigger_error( + 'Cannot use unrecognized element as parent', + E_USER_ERROR + ); + $this->info_parent_def = $this->manager->getElement($this->info_parent, true); + } + + // support template text + $support = "(for information on implementing this, see the support forums) "; + + // setup allowed elements ----------------------------------------- + + $allowed_elements = $config->get('HTML.AllowedElements'); + $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early + + if (!is_array($allowed_elements) && !is_array($allowed_attributes)) { + $allowed = $config->get('HTML.Allowed'); + if (is_string($allowed)) { + list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed); + } + } + + if (is_array($allowed_elements)) { + foreach ($this->info as $name => $d) { + if (!isset($allowed_elements[$name])) { + unset($this->info[$name]); + } + unset($allowed_elements[$name]); + } + // emit errors + foreach ($allowed_elements as $element => $d) { + $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful! + trigger_error("Element '$element' is not supported $support", E_USER_WARNING); + } + } + + // setup allowed attributes --------------------------------------- + + $allowed_attributes_mutable = $allowed_attributes; // by copy! + if (is_array($allowed_attributes)) { + // This actually doesn't do anything, since we went away from + // global attributes. It's possible that userland code uses + // it, but HTMLModuleManager doesn't! + foreach ($this->info_global_attr as $attr => $x) { + $keys = array($attr, "*@$attr", "*.$attr"); + $delete = true; + foreach ($keys as $key) { + if ($delete && isset($allowed_attributes[$key])) { + $delete = false; + } + if (isset($allowed_attributes_mutable[$key])) { + unset($allowed_attributes_mutable[$key]); + } + } + if ($delete) { + unset($this->info_global_attr[$attr]); + } + } + + foreach ($this->info as $tag => $info) { + foreach ($info->attr as $attr => $x) { + $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr"); + $delete = true; + foreach ($keys as $key) { + if ($delete && isset($allowed_attributes[$key])) { + $delete = false; + } + if (isset($allowed_attributes_mutable[$key])) { + unset($allowed_attributes_mutable[$key]); + } + } + if ($delete) { + if ($this->info[$tag]->attr[$attr]->required) { + trigger_error( + "Required attribute '$attr' in element '$tag' " . + "was not allowed, which means '$tag' will not be allowed either", + E_USER_WARNING + ); + } + unset($this->info[$tag]->attr[$attr]); + } + } + } + // emit errors + foreach ($allowed_attributes_mutable as $elattr => $d) { + $bits = preg_split('/[.@]/', $elattr, 2); + $c = count($bits); + switch ($c) { + case 2: + if ($bits[0] !== '*') { + $element = htmlspecialchars($bits[0]); + $attribute = htmlspecialchars($bits[1]); + if (!isset($this->info[$element])) { + trigger_error( + "Cannot allow attribute '$attribute' if element " . + "'$element' is not allowed/supported $support" + ); + } else { + trigger_error( + "Attribute '$attribute' in element '$element' not supported $support", + E_USER_WARNING + ); + } + break; + } + // otherwise fall through + case 1: + $attribute = htmlspecialchars($bits[0]); + trigger_error( + "Global attribute '$attribute' is not ". + "supported in any elements $support", + E_USER_WARNING + ); + break; + } + } + } + + // setup forbidden elements --------------------------------------- + + $forbidden_elements = $config->get('HTML.ForbiddenElements'); + $forbidden_attributes = $config->get('HTML.ForbiddenAttributes'); + + foreach ($this->info as $tag => $info) { + if (isset($forbidden_elements[$tag])) { + unset($this->info[$tag]); + continue; + } + foreach ($info->attr as $attr => $x) { + if (isset($forbidden_attributes["$tag@$attr"]) || + isset($forbidden_attributes["*@$attr"]) || + isset($forbidden_attributes[$attr]) + ) { + unset($this->info[$tag]->attr[$attr]); + continue; + } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually + // $tag.$attr are not user supplied, so no worries! + trigger_error( + "Error with $tag.$attr: tag.attr syntax not supported for " . + "HTML.ForbiddenAttributes; use tag@attr instead", + E_USER_WARNING + ); + } + } + } + foreach ($forbidden_attributes as $key => $v) { + if (strlen($key) < 2) { + continue; + } + if ($key[0] != '*') { + continue; + } + if ($key[1] == '.') { + trigger_error( + "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", + E_USER_WARNING + ); + } + } + + // setup injectors ----------------------------------------------------- + foreach ($this->info_injector as $i => $injector) { + if ($injector->checkNeeded($config) !== false) { + // remove injector that does not have it's required + // elements/attributes present, and is thus not needed. + unset($this->info_injector[$i]); + } + } + } + + /** + * Parses a TinyMCE-flavored Allowed Elements and Attributes list into + * separate lists for processing. Format is element[attr1|attr2],element2... + * @warning Although it's largely drawn from TinyMCE's implementation, + * it is different, and you'll probably have to modify your lists + * @param array $list String list to parse + * @return array + * @todo Give this its own class, probably static interface + */ + public function parseTinyMCEAllowedList($list) + { + $list = str_replace(array(' ', "\t"), '', $list); + + $elements = array(); + $attributes = array(); + + $chunks = preg_split('/(,|[\n\r]+)/', $list); + foreach ($chunks as $chunk) { + if (empty($chunk)) { + continue; + } + // remove TinyMCE element control characters + if (!strpos($chunk, '[')) { + $element = $chunk; + $attr = false; + } else { + list($element, $attr) = explode('[', $chunk); + } + if ($element !== '*') { + $elements[$element] = true; + } + if (!$attr) { + continue; + } + $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ] + $attr = explode('|', $attr); + foreach ($attr as $key) { + $attributes["$element.$key"] = true; + } + } + return array($elements, $attributes); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php new file mode 100644 index 0000000..9dbb987 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php @@ -0,0 +1,285 @@ +info, since the object's data is only info, + * with extra behavior associated with it. + * @type array + */ + public $attr_collections = array(); + + /** + * Associative array of deprecated tag name to HTMLPurifier_TagTransform. + * @type array + */ + public $info_tag_transform = array(); + + /** + * List of HTMLPurifier_AttrTransform to be performed before validation. + * @type array + */ + public $info_attr_transform_pre = array(); + + /** + * List of HTMLPurifier_AttrTransform to be performed after validation. + * @type array + */ + public $info_attr_transform_post = array(); + + /** + * List of HTMLPurifier_Injector to be performed during well-formedness fixing. + * An injector will only be invoked if all of it's pre-requisites are met; + * if an injector fails setup, there will be no error; it will simply be + * silently disabled. + * @type array + */ + public $info_injector = array(); + + /** + * Boolean flag that indicates whether or not getChildDef is implemented. + * For optimization reasons: may save a call to a function. Be sure + * to set it if you do implement getChildDef(), otherwise it will have + * no effect! + * @type bool + */ + public $defines_child_def = false; + + /** + * Boolean flag whether or not this module is safe. If it is not safe, all + * of its members are unsafe. Modules are safe by default (this might be + * slightly dangerous, but it doesn't make much sense to force HTML Purifier, + * which is based off of safe HTML, to explicitly say, "This is safe," even + * though there are modules which are "unsafe") + * + * @type bool + * @note Previously, safety could be applied at an element level granularity. + * We've removed this ability, so in order to add "unsafe" elements + * or attributes, a dedicated module with this property set to false + * must be used. + */ + public $safe = true; + + /** + * Retrieves a proper HTMLPurifier_ChildDef subclass based on + * content_model and content_model_type member variables of + * the HTMLPurifier_ElementDef class. There is a similar function + * in HTMLPurifier_HTMLDefinition. + * @param HTMLPurifier_ElementDef $def + * @return HTMLPurifier_ChildDef subclass + */ + public function getChildDef($def) + { + return false; + } + + // -- Convenience ----------------------------------------------------- + + /** + * Convenience function that sets up a new element + * @param string $element Name of element to add + * @param string|bool $type What content set should element be registered to? + * Set as false to skip this step. + * @param string|HTMLPurifier_ChildDef $contents Allowed children in form of: + * "$content_model_type: $content_model" + * @param array|string $attr_includes What attribute collections to register to + * element? + * @param array $attr What unique attributes does the element define? + * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters. + * @return HTMLPurifier_ElementDef Created element definition object, so you + * can set advanced parameters + */ + public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()) + { + $this->elements[] = $element; + // parse content_model + list($content_model_type, $content_model) = $this->parseContents($contents); + // merge in attribute inclusions + $this->mergeInAttrIncludes($attr, $attr_includes); + // add element to content sets + if ($type) { + $this->addElementToContentSet($element, $type); + } + // create element + $this->info[$element] = HTMLPurifier_ElementDef::create( + $content_model, + $content_model_type, + $attr + ); + // literal object $contents means direct child manipulation + if (!is_string($contents)) { + $this->info[$element]->child = $contents; + } + return $this->info[$element]; + } + + /** + * Convenience function that creates a totally blank, non-standalone + * element. + * @param string $element Name of element to create + * @return HTMLPurifier_ElementDef Created element + */ + public function addBlankElement($element) + { + if (!isset($this->info[$element])) { + $this->elements[] = $element; + $this->info[$element] = new HTMLPurifier_ElementDef(); + $this->info[$element]->standalone = false; + } else { + trigger_error("Definition for $element already exists in module, cannot redefine"); + } + return $this->info[$element]; + } + + /** + * Convenience function that registers an element to a content set + * @param string $element Element to register + * @param string $type Name content set (warning: case sensitive, usually upper-case + * first letter) + */ + public function addElementToContentSet($element, $type) + { + if (!isset($this->content_sets[$type])) { + $this->content_sets[$type] = ''; + } else { + $this->content_sets[$type] .= ' | '; + } + $this->content_sets[$type] .= $element; + } + + /** + * Convenience function that transforms single-string contents + * into separate content model and content model type + * @param string $contents Allowed children in form of: + * "$content_model_type: $content_model" + * @return array + * @note If contents is an object, an array of two nulls will be + * returned, and the callee needs to take the original $contents + * and use it directly. + */ + public function parseContents($contents) + { + if (!is_string($contents)) { + return array(null, null); + } // defer + switch ($contents) { + // check for shorthand content model forms + case 'Empty': + return array('empty', ''); + case 'Inline': + return array('optional', 'Inline | #PCDATA'); + case 'Flow': + return array('optional', 'Flow | #PCDATA'); + } + list($content_model_type, $content_model) = explode(':', $contents); + $content_model_type = strtolower(trim($content_model_type)); + $content_model = trim($content_model); + return array($content_model_type, $content_model); + } + + /** + * Convenience function that merges a list of attribute includes into + * an attribute array. + * @param array $attr Reference to attr array to modify + * @param array $attr_includes Array of includes / string include to merge in + */ + public function mergeInAttrIncludes(&$attr, $attr_includes) + { + if (!is_array($attr_includes)) { + if (empty($attr_includes)) { + $attr_includes = array(); + } else { + $attr_includes = array($attr_includes); + } + } + $attr[0] = $attr_includes; + } + + /** + * Convenience function that generates a lookup table with boolean + * true as value. + * @param string $list List of values to turn into a lookup + * @note You can also pass an arbitrary number of arguments in + * place of the regular argument + * @return array array equivalent of list + */ + public function makeLookup($list) + { + $args = func_get_args(); + if (is_string($list)) { + $list = $args; + } + $ret = array(); + foreach ($list as $value) { + if (is_null($value)) { + continue; + } + $ret[$value] = true; + } + return $ret; + } + + /** + * Lazy load construction of the module after determining whether + * or not it's needed, and also when a finalized configuration object + * is available. + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php new file mode 100644 index 0000000..1e67c79 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php @@ -0,0 +1,44 @@ + array('dir' => false) + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $bdo = $this->addElement( + 'bdo', + 'Inline', + 'Inline', + array('Core', 'Lang'), + array( + 'dir' => 'Enum#ltr,rtl', // required + // The Abstract Module specification has the attribute + // inclusions wrong for bdo: bdo allows Lang + ) + ); + $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir(); + + $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl'; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php new file mode 100644 index 0000000..7220c14 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php @@ -0,0 +1,32 @@ + array( + 0 => array('Style'), + // 'xml:space' => false, + 'class' => 'Class', + 'id' => 'ID', + 'title' => 'CDATA', + 'contenteditable' => 'ContentEditable', + ), + 'Lang' => array(), + 'I18N' => array( + 0 => array('Lang'), // proprietary, for xml:lang/lang + ), + 'Common' => array( + 0 => array('Core', 'I18N') + ) + ); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php new file mode 100644 index 0000000..a9042a3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php @@ -0,0 +1,55 @@ + 'URI', + // 'datetime' => 'Datetime', // not implemented + ); + $this->addElement('del', 'Inline', $contents, 'Common', $attr); + $this->addElement('ins', 'Inline', $contents, 'Common', $attr); + } + + // HTML 4.01 specifies that ins/del must not contain block + // elements when used in an inline context, chameleon is + // a complicated workaround to acheive this effect + + // Inline context ! Block context (exclamation mark is + // separator, see getChildDef for parsing) + + /** + * @type bool + */ + public $defines_child_def = true; + + /** + * @param HTMLPurifier_ElementDef $def + * @return HTMLPurifier_ChildDef_Chameleon + */ + public function getChildDef($def) + { + if ($def->content_model_type != 'chameleon') { + return false; + } + $value = explode('!', $def->content_model); + return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php new file mode 100644 index 0000000..eb0edcf --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php @@ -0,0 +1,194 @@ + 'Form', + 'Inline' => 'Formctrl', + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + if ($config->get('HTML.Forms')) { + $this->safe = true; + } + + $form = $this->addElement( + 'form', + 'Form', + 'Required: Heading | List | Block | fieldset', + 'Common', + array( + 'accept' => 'ContentTypes', + 'accept-charset' => 'Charsets', + 'action*' => 'URI', + 'method' => 'Enum#get,post', + // really ContentType, but these two are the only ones used today + 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data', + ) + ); + $form->excludes = array('form' => true); + + $input = $this->addElement( + 'input', + 'Formctrl', + 'Empty', + 'Common', + array( + 'accept' => 'ContentTypes', + 'accesskey' => 'Character', + 'alt' => 'Text', + 'checked' => 'Bool#checked', + 'disabled' => 'Bool#disabled', + 'maxlength' => 'Number', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'size' => 'Number', + 'src' => 'URI#embedded', + 'tabindex' => 'Number', + 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image', + 'value' => 'CDATA', + ) + ); + $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input(); + + $this->addElement( + 'select', + 'Formctrl', + 'Required: optgroup | option', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'multiple' => 'Bool#multiple', + 'name' => 'CDATA', + 'size' => 'Number', + 'tabindex' => 'Number', + ) + ); + + $this->addElement( + 'option', + false, + 'Optional: #PCDATA', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'label' => 'Text', + 'selected' => 'Bool#selected', + 'value' => 'CDATA', + ) + ); + // It's illegal for there to be more than one selected, but not + // be multiple. Also, no selected means undefined behavior. This might + // be difficult to implement; perhaps an injector, or a context variable. + + $textarea = $this->addElement( + 'textarea', + 'Formctrl', + 'Optional: #PCDATA', + 'Common', + array( + 'accesskey' => 'Character', + 'cols*' => 'Number', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'rows*' => 'Number', + 'tabindex' => 'Number', + ) + ); + $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea(); + + $button = $this->addElement( + 'button', + 'Formctrl', + 'Optional: #PCDATA | Heading | List | Block | Inline', + 'Common', + array( + 'accesskey' => 'Character', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'tabindex' => 'Number', + 'type' => 'Enum#button,submit,reset', + 'value' => 'CDATA', + ) + ); + + // For exclusions, ideally we'd specify content sets, not literal elements + $button->excludes = $this->makeLookup( + 'form', + 'fieldset', // Form + 'input', + 'select', + 'textarea', + 'label', + 'button', // Formctrl + 'a', // as per HTML 4.01 spec, this is omitted by modularization + 'isindex', + 'iframe' // legacy items + ); + + // Extra exclusion: img usemap="" is not permitted within this element. + // We'll omit this for now, since we don't have any good way of + // indicating it yet. + + // This is HIGHLY user-unfriendly; we need a custom child-def for this + $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common'); + + $label = $this->addElement( + 'label', + 'Formctrl', + 'Optional: #PCDATA | Inline', + 'Common', + array( + 'accesskey' => 'Character', + // 'for' => 'IDREF', // IDREF not implemented, cannot allow + ) + ); + $label->excludes = array('label' => true); + + $this->addElement( + 'legend', + false, + 'Optional: #PCDATA | Inline', + 'Common', + array( + 'accesskey' => 'Character', + ) + ); + + $this->addElement( + 'optgroup', + false, + 'Required: option', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'label*' => 'Text', + ) + ); + // Don't forget an injector for . This one's a little complex + // because it maps to multiple elements. + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php new file mode 100644 index 0000000..72d7a31 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php @@ -0,0 +1,40 @@ +addElement( + 'a', + 'Inline', + 'Inline', + 'Common', + array( + // 'accesskey' => 'Character', + // 'charset' => 'Charset', + 'href' => 'URI', + // 'hreflang' => 'LanguageCode', + 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'), + 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'), + // 'tabindex' => 'Number', + // 'type' => 'ContentType', + ) + ); + $a->formatting = true; + $a->excludes = array('a' => true); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php new file mode 100644 index 0000000..f7e7c91 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php @@ -0,0 +1,51 @@ +get('HTML.SafeIframe')) { + $this->safe = true; + } + $this->addElement( + 'iframe', + 'Inline', + 'Flow', + 'Common', + array( + 'src' => 'URI#embedded', + 'width' => 'Length', + 'height' => 'Length', + 'name' => 'ID', + 'scrolling' => 'Enum#yes,no,auto', + 'frameborder' => 'Enum#0,1', + 'longdesc' => 'URI', + 'marginheight' => 'Pixels', + 'marginwidth' => 'Pixels', + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php new file mode 100644 index 0000000..0f5fdb3 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php @@ -0,0 +1,49 @@ +get('HTML.MaxImgLength'); + $img = $this->addElement( + 'img', + 'Inline', + 'Empty', + 'Common', + array( + 'alt*' => 'Text', + // According to the spec, it's Length, but percents can + // be abused, so we allow only Pixels. + 'height' => 'Pixels#' . $max, + 'width' => 'Pixels#' . $max, + 'longdesc' => 'URI', + 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded + ) + ); + if ($max === null || $config->get('HTML.Trusted')) { + $img->attr['height'] = + $img->attr['width'] = 'Length'; + } + + // kind of strange, but splitting things up would be inefficient + $img->attr_transform_pre[] = + $img->attr_transform_post[] = + new HTMLPurifier_AttrTransform_ImgRequired(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php new file mode 100644 index 0000000..86b5299 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php @@ -0,0 +1,186 @@ +addElement( + 'basefont', + 'Inline', + 'Empty', + null, + array( + 'color' => 'Color', + 'face' => 'Text', // extremely broad, we should + 'size' => 'Text', // tighten it + 'id' => 'ID' + ) + ); + $this->addElement('center', 'Block', 'Flow', 'Common'); + $this->addElement( + 'dir', + 'Block', + 'Required: li', + 'Common', + array( + 'compact' => 'Bool#compact' + ) + ); + $this->addElement( + 'font', + 'Inline', + 'Inline', + array('Core', 'I18N'), + array( + 'color' => 'Color', + 'face' => 'Text', // extremely broad, we should + 'size' => 'Text', // tighten it + ) + ); + $this->addElement( + 'menu', + 'Block', + 'Required: li', + 'Common', + array( + 'compact' => 'Bool#compact' + ) + ); + + $s = $this->addElement('s', 'Inline', 'Inline', 'Common'); + $s->formatting = true; + + $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common'); + $strike->formatting = true; + + $u = $this->addElement('u', 'Inline', 'Inline', 'Common'); + $u->formatting = true; + + // setup modifications to old elements + + $align = 'Enum#left,right,center,justify'; + + $address = $this->addBlankElement('address'); + $address->content_model = 'Inline | #PCDATA | p'; + $address->content_model_type = 'optional'; + $address->child = false; + + $blockquote = $this->addBlankElement('blockquote'); + $blockquote->content_model = 'Flow | #PCDATA'; + $blockquote->content_model_type = 'optional'; + $blockquote->child = false; + + $br = $this->addBlankElement('br'); + $br->attr['clear'] = 'Enum#left,all,right,none'; + + $caption = $this->addBlankElement('caption'); + $caption->attr['align'] = 'Enum#top,bottom,left,right'; + + $div = $this->addBlankElement('div'); + $div->attr['align'] = $align; + + $dl = $this->addBlankElement('dl'); + $dl->attr['compact'] = 'Bool#compact'; + + for ($i = 1; $i <= 6; $i++) { + $h = $this->addBlankElement("h$i"); + $h->attr['align'] = $align; + } + + $hr = $this->addBlankElement('hr'); + $hr->attr['align'] = $align; + $hr->attr['noshade'] = 'Bool#noshade'; + $hr->attr['size'] = 'Pixels'; + $hr->attr['width'] = 'Length'; + + $img = $this->addBlankElement('img'); + $img->attr['align'] = 'IAlign'; + $img->attr['border'] = 'Pixels'; + $img->attr['hspace'] = 'Pixels'; + $img->attr['vspace'] = 'Pixels'; + + // figure out this integer business + + $li = $this->addBlankElement('li'); + $li->attr['value'] = new HTMLPurifier_AttrDef_Integer(); + $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle'; + + $ol = $this->addBlankElement('ol'); + $ol->attr['compact'] = 'Bool#compact'; + $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer(); + $ol->attr['type'] = 'Enum#s:1,i,I,a,A'; + + $p = $this->addBlankElement('p'); + $p->attr['align'] = $align; + + $pre = $this->addBlankElement('pre'); + $pre->attr['width'] = 'Number'; + + // script omitted + + $table = $this->addBlankElement('table'); + $table->attr['align'] = 'Enum#left,center,right'; + $table->attr['bgcolor'] = 'Color'; + + $tr = $this->addBlankElement('tr'); + $tr->attr['bgcolor'] = 'Color'; + + $th = $this->addBlankElement('th'); + $th->attr['bgcolor'] = 'Color'; + $th->attr['height'] = 'Length'; + $th->attr['nowrap'] = 'Bool#nowrap'; + $th->attr['width'] = 'Length'; + + $td = $this->addBlankElement('td'); + $td->attr['bgcolor'] = 'Color'; + $td->attr['height'] = 'Length'; + $td->attr['nowrap'] = 'Bool#nowrap'; + $td->attr['width'] = 'Length'; + + $ul = $this->addBlankElement('ul'); + $ul->attr['compact'] = 'Bool#compact'; + $ul->attr['type'] = 'Enum#square,disc,circle'; + + // "safe" modifications to "unsafe" elements + // WARNING: If you want to add support for an unsafe, legacy + // attribute, make a new TrustedLegacy module with the trusted + // bit set appropriately + + $form = $this->addBlankElement('form'); + $form->content_model = 'Flow | #PCDATA'; + $form->content_model_type = 'optional'; + $form->attr['target'] = 'FrameTarget'; + + $input = $this->addBlankElement('input'); + $input->attr['align'] = 'IAlign'; + + $legend = $this->addBlankElement('legend'); + $legend->attr['align'] = 'LAlign'; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php new file mode 100644 index 0000000..7a20ff7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php @@ -0,0 +1,51 @@ + 'List'); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common'); + $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common'); + // XXX The wrap attribute is handled by MakeWellFormed. This is all + // quite unsatisfactory, because we generated this + // *specifically* for lists, and now a big chunk of the handling + // is done properly by the List ChildDef. So actually, we just + // want enough information to make autoclosing work properly, + // and then hand off the tricky stuff to the ChildDef. + $ol->wrap = 'li'; + $ul->wrap = 'li'; + $this->addElement('dl', 'List', 'Required: dt | dd', 'Common'); + + $this->addElement('li', false, 'Flow', 'Common'); + + $this->addElement('dd', false, 'Flow', 'Common'); + $this->addElement('dt', false, 'Inline', 'Common'); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php new file mode 100644 index 0000000..60c0545 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php @@ -0,0 +1,26 @@ +addBlankElement($name); + $element->attr['name'] = 'CDATA'; + if (!$config->get('HTML.Attr.Name.UseCDATA')) { + $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync(); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php new file mode 100644 index 0000000..dc9410a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php @@ -0,0 +1,25 @@ +addBlankElement('a'); + $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php new file mode 100644 index 0000000..da72225 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php @@ -0,0 +1,20 @@ + array( + 'lang' => 'LanguageCode', + ) + ); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php new file mode 100644 index 0000000..2f9efc5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php @@ -0,0 +1,62 @@ + to cater to legacy browsers: this + * module does not allow this sort of behavior + */ +class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule +{ + /** + * @type string + */ + public $name = 'Object'; + + /** + * @type bool + */ + public $safe = false; + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->addElement( + 'object', + 'Inline', + 'Optional: #PCDATA | Flow | param', + 'Common', + array( + 'archive' => 'URI', + 'classid' => 'URI', + 'codebase' => 'URI', + 'codetype' => 'Text', + 'data' => 'URI', + 'declare' => 'Bool#declare', + 'height' => 'Length', + 'name' => 'CDATA', + 'standby' => 'Text', + 'tabindex' => 'Number', + 'type' => 'ContentType', + 'width' => 'Length' + ) + ); + + $this->addElement( + 'param', + false, + 'Empty', + null, + array( + 'id' => 'ID', + 'name*' => 'Text', + 'type' => 'Text', + 'value' => 'Text', + 'valuetype' => 'Enum#data,ref,object' + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php new file mode 100644 index 0000000..6458ce9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php @@ -0,0 +1,42 @@ +addElement('hr', 'Block', 'Empty', 'Common'); + $this->addElement('sub', 'Inline', 'Inline', 'Common'); + $this->addElement('sup', 'Inline', 'Inline', 'Common'); + $b = $this->addElement('b', 'Inline', 'Inline', 'Common'); + $b->formatting = true; + $big = $this->addElement('big', 'Inline', 'Inline', 'Common'); + $big->formatting = true; + $i = $this->addElement('i', 'Inline', 'Inline', 'Common'); + $i->formatting = true; + $small = $this->addElement('small', 'Inline', 'Inline', 'Common'); + $small->formatting = true; + $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common'); + $tt->formatting = true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php new file mode 100644 index 0000000..5ee3c8e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php @@ -0,0 +1,40 @@ +addElement( + 'marquee', + 'Inline', + 'Flow', + 'Common', + array( + 'direction' => 'Enum#left,right,up,down', + 'behavior' => 'Enum#alternate', + 'width' => 'Length', + 'height' => 'Length', + 'scrolldelay' => 'Number', + 'scrollamount' => 'Number', + 'loop' => 'Number', + 'bgcolor' => 'Color', + 'hspace' => 'Pixels', + 'vspace' => 'Pixels', + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php new file mode 100644 index 0000000..a0d4892 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php @@ -0,0 +1,36 @@ +addElement( + 'ruby', + 'Inline', + 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))', + 'Common' + ); + $this->addElement('rbc', false, 'Required: rb', 'Common'); + $this->addElement('rtc', false, 'Required: rt', 'Common'); + $rb = $this->addElement('rb', false, 'Inline', 'Common'); + $rb->excludes = array('ruby' => true); + $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number')); + $rt->excludes = array('ruby' => true); + $this->addElement('rp', false, 'Optional: #PCDATA', 'Common'); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php new file mode 100644 index 0000000..04e6689 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php @@ -0,0 +1,40 @@ +get('HTML.MaxImgLength'); + $embed = $this->addElement( + 'embed', + 'Inline', + 'Empty', + 'Common', + array( + 'src*' => 'URI#embedded', + 'type' => 'Enum#application/x-shockwave-flash', + 'width' => 'Pixels#' . $max, + 'height' => 'Pixels#' . $max, + 'allowscriptaccess' => 'Enum#never', + 'allownetworking' => 'Enum#internal', + 'flashvars' => 'Text', + 'wmode' => 'Enum#window,transparent,opaque', + 'name' => 'ID', + ) + ); + $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php new file mode 100644 index 0000000..1297f80 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php @@ -0,0 +1,62 @@ +get('HTML.MaxImgLength'); + $object = $this->addElement( + 'object', + 'Inline', + 'Optional: param | Flow | #PCDATA', + 'Common', + array( + // While technically not required by the spec, we're forcing + // it to this value. + 'type' => 'Enum#application/x-shockwave-flash', + 'width' => 'Pixels#' . $max, + 'height' => 'Pixels#' . $max, + 'data' => 'URI#embedded', + 'codebase' => new HTMLPurifier_AttrDef_Enum( + array( + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0' + ) + ), + ) + ); + $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject(); + + $param = $this->addElement( + 'param', + false, + 'Empty', + false, + array( + 'id' => 'ID', + 'name*' => 'Text', + 'value' => 'Text' + ) + ); + $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam(); + $this->info_injector[] = 'SafeObject'; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php new file mode 100644 index 0000000..aea7584 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php @@ -0,0 +1,40 @@ +get('HTML.SafeScripting'); + $script = $this->addElement( + 'script', + 'Inline', + 'Optional:', // Not `Empty` to not allow to autoclose the #i', '', $html); + } + + return $html; + } + + /** + * Takes a string of HTML (fragment or document) and returns the content + * @todo Consider making protected + */ + public function extractBody($html) + { + $matches = array(); + $result = preg_match('|(.*?)]*>(.*)|is', $html, $matches); + if ($result) { + // Make sure it's not in a comment + $comment_start = strrpos($matches[1], ''); + if ($comment_start === false || + ($comment_end !== false && $comment_end > $comment_start)) { + return $matches[2]; + } + } + return $html; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php new file mode 100644 index 0000000..5e8104b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/DOMLex.php @@ -0,0 +1,337 @@ +factory = new HTMLPurifier_TokenFactory(); + } + + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] + */ + public function tokenizeHTML($html, $config, $context) + { + $html = $this->normalize($html, $config, $context); + + // attempt to armor stray angled brackets that cannot possibly + // form tags and thus are probably being used as emoticons + if ($config->get('Core.AggressivelyFixLt')) { + $char = '[^a-z!\/]'; + $comment = "/|\z)/is"; + $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html); + do { + $old = $html; + $html = preg_replace("/<($char)/i", '<\\1', $html); + } while ($html !== $old); + $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments + } + + // preprocess html, essential for UTF-8 + $html = $this->wrapHTML($html, $config, $context); + + $doc = new DOMDocument(); + $doc->encoding = 'UTF-8'; // theoretically, the above has this covered + + $options = 0; + if ($config->get('Core.AllowParseManyTags') && defined('LIBXML_PARSEHUGE')) { + $options |= LIBXML_PARSEHUGE; + } + + set_error_handler(array($this, 'muteErrorHandler')); + // loadHTML() fails on PHP 5.3 when second parameter is given + if ($options) { + $doc->loadHTML($html, $options); + } else { + $doc->loadHTML($html); + } + restore_error_handler(); + + $body = $doc->getElementsByTagName('html')->item(0)-> // + getElementsByTagName('body')->item(0); // + + $div = $body->getElementsByTagName('div')->item(0); //
+ $tokens = array(); + $this->tokenizeDOM($div, $tokens, $config); + // If the div has a sibling, that means we tripped across + // a premature
tag. So remove the div we parsed, + // and then tokenize the rest of body. We can't tokenize + // the sibling directly as we'll lose the tags in that case. + if ($div->nextSibling) { + $body->removeChild($div); + $this->tokenizeDOM($body, $tokens, $config); + } + return $tokens; + } + + /** + * Iterative function that tokenizes a node, putting it into an accumulator. + * To iterate is human, to recurse divine - L. Peter Deutsch + * @param DOMNode $node DOMNode to be tokenized. + * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. + */ + protected function tokenizeDOM($node, &$tokens, $config) + { + $level = 0; + $nodes = array($level => new HTMLPurifier_Queue(array($node))); + $closingNodes = array(); + do { + while (!$nodes[$level]->isEmpty()) { + $node = $nodes[$level]->shift(); // FIFO + $collect = $level > 0 ? true : false; + $needEndingTag = $this->createStartNode($node, $tokens, $collect, $config); + if ($needEndingTag) { + $closingNodes[$level][] = $node; + } + if ($node->childNodes && $node->childNodes->length) { + $level++; + $nodes[$level] = new HTMLPurifier_Queue(); + foreach ($node->childNodes as $childNode) { + $nodes[$level]->push($childNode); + } + } + } + $level--; + if ($level && isset($closingNodes[$level])) { + while ($node = array_pop($closingNodes[$level])) { + $this->createEndNode($node, $tokens); + } + } + } while ($level > 0); + } + + /** + * Portably retrieve the tag name of a node; deals with older versions + * of libxml like 2.7.6 + * @param DOMNode $node + */ + protected function getTagName($node) + { + if (isset($node->tagName)) { + return $node->tagName; + } else if (isset($node->nodeName)) { + return $node->nodeName; + } else if (isset($node->localName)) { + return $node->localName; + } + return null; + } + + /** + * Portably retrieve the data of a node; deals with older versions + * of libxml like 2.7.6 + * @param DOMNode $node + */ + protected function getData($node) + { + if (isset($node->data)) { + return $node->data; + } else if (isset($node->nodeValue)) { + return $node->nodeValue; + } else if (isset($node->textContent)) { + return $node->textContent; + } + return null; + } + + + /** + * @param DOMNode $node DOMNode to be tokenized. + * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. + * @param bool $collect Says whether or start and close are collected, set to + * false at first recursion because it's the implicit DIV + * tag you're dealing with. + * @return bool if the token needs an endtoken + * @todo data and tagName properties don't seem to exist in DOMNode? + */ + protected function createStartNode($node, &$tokens, $collect, $config) + { + // intercept non element nodes. WE MUST catch all of them, + // but we're not getting the character reference nodes because + // those should have been preprocessed + if ($node->nodeType === XML_TEXT_NODE) { + $data = $this->getData($node); // Handle variable data property + if ($data !== null) { + $tokens[] = $this->factory->createText($data); + } + return false; + } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) { + // undo libxml's special treatment of )#si', + array($this, 'scriptCallback'), + $html + ); + } + + $html = $this->normalize($html, $config, $context); + + $cursor = 0; // our location in the text + $inside_tag = false; // whether or not we're parsing the inside of a tag + $array = array(); // result array + + // This is also treated to mean maintain *column* numbers too + $maintain_line_numbers = $config->get('Core.MaintainLineNumbers'); + + if ($maintain_line_numbers === null) { + // automatically determine line numbering by checking + // if error collection is on + $maintain_line_numbers = $config->get('Core.CollectErrors'); + } + + if ($maintain_line_numbers) { + $current_line = 1; + $current_col = 0; + $length = strlen($html); + } else { + $current_line = false; + $current_col = false; + $length = false; + } + $context->register('CurrentLine', $current_line); + $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 + $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval'); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // for testing synchronization + $loops = 0; + + 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; + + // Column number is cheap, so we calculate it every round. + // We're interested at the *end* of the newline string, so + // we need to add strlen($nl) == 1 to $nl_pos before subtracting it + // from our "rcursor" position. + $nl_pos = strrpos($html, $nl, $rcursor - $length); + $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! + $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); + } + } + + $position_next_lt = strpos($html, '<', $cursor); + $position_next_gt = strpos($html, '>', $cursor); + + // triggers on "asdf" but not "asdf " + // special case to set up context + if ($position_next_lt === $cursor) { + $inside_tag = true; + $cursor++; + } + + 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->parseText( + substr( + $html, + $cursor, + $position_next_lt - $cursor + ), $config + ) + ); + 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; + $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; + } + // Create Text of rest of string + $token = new + HTMLPurifier_Token_Text( + $this->parseText( + substr( + $html, + $cursor + ), $config + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + $array[] = $token; + break; + } elseif ($inside_tag && $position_next_gt !== false) { + // We are in tag and it is well formed + // Grab the internals of the tag + $strlen_segment = $position_next_gt - $cursor; + + if ($strlen_segment < 1) { + // there's nothing to process! + $token = new HTMLPurifier_Token_Text('<'); + $cursor++; + continue; + } + + $segment = substr($html, $cursor, $strlen_segment); + + if ($segment === false) { + // somehow, we attempted to access beyond the end of + // the string, defense-in-depth, reported by Nate Abele + break; + } + + // Check if it's a comment + 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'); + } + $position_comment_end = strlen($html); + $end = true; + } else { + $end = false; + } + $strlen_segment = $position_comment_end - $cursor; + $segment = substr($html, $cursor, $strlen_segment); + $token = new + 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); + } + $array[] = $token; + $cursor = $end ? $position_comment_end : $position_comment_end + 3; + $inside_tag = false; + continue; + } + + // Check if it's an end tag + $is_end_tag = (strpos($segment, '/') === 0); + if ($is_end_tag) { + $type = substr($segment, 1); + $token = new HTMLPurifier_Token_End($type); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Check leading character is alnum, if not, we may + // have accidently grabbed an emoticon. Translate into + // 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'); + } + $token = new HTMLPurifier_Token_Text('<'); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + continue; + } + + // Check if it is explicitly self closing, if so, remove + // 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); + if ($is_self_closing) { + $strlen_segment--; + $segment = substr($segment, 0, $strlen_segment); + } + + // Check if there are any attributes + $position_first_space = strcspn($segment, $this->_whitespace); + + if ($position_first_space >= $strlen_segment) { + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($segment); + } else { + $token = new HTMLPurifier_Token_Start($segment); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Grab out all the data + $type = substr($segment, 0, $position_first_space); + $attribute_string = + trim( + substr( + $segment, + $position_first_space + ) + ); + if ($attribute_string) { + $attr = $this->parseAttributeString( + $attribute_string, + $config, + $context + ); + } else { + $attr = array(); + } + + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($type, $attr); + } else { + $token = new HTMLPurifier_Token_Start($type, $attr); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $cursor = $position_next_gt + 1; + $inside_tag = false; + continue; + } else { + // inside tag, but there's no ending > sign + if ($e) { + $e->send(E_WARNING, 'Lexer: Missing gt'); + } + $token = new + HTMLPurifier_Token_Text( + '<' . + $this->parseText( + substr($html, $cursor), $config + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + // no cursor scroll? Hmm... + $array[] = $token; + break; + } + break; + } + + $context->destroy('CurrentLine'); + $context->destroy('CurrentCol'); + return $array; + } + + /** + * PHP 5.0.x compatible substr_count that implements offset and 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', '<'); + } + if ($oldVersion) { + $haystack = substr($haystack, $offset, $length); + return substr_count($haystack, $needle); + } else { + return substr_count($haystack, $needle, $offset, $length); + } + } + + /** + * Takes the inside of an HTML tag and makes an 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 + + if ($string == '') { + return array(); + } // no attributes + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // let's see if we can abort as quickly as possible + // one equal sign, no spaces => one attribute + $num_equal = substr_count($string, '='); + $has_space = strpos($string, ' '); + if ($num_equal === 0 && !$has_space) { + // bool attribute + return array($string => $string); + } elseif ($num_equal === 1 && !$has_space) { + // only one attribute + list($key, $quoted_value) = explode('=', $string); + $quoted_value = trim($quoted_value); + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + return array(); + } + if (!$quoted_value) { + return array($key => ''); + } + $first_char = @$quoted_value[0]; + $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) { + // 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'); + } + $value = substr($quoted_value, 1); + } else { + $value = $quoted_value; + } + } + if ($value === false) { + $value = ''; + } + return array($key => $this->parseAttr($value, $config)); + } + + // setup loop environment + $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) + + // if we have unquoted attributes, the parser expects a terminating + // space, so let's guarantee that there's always a terminating space. + $string .= ' '; + + $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 + + $key_begin = $cursor; //we're currently at the start of the key + + // scroll past all characters that are the key (not whitespace or =) + $cursor += strcspn($string, $this->_whitespace . '=', $cursor); + + $key_end = $cursor; // now at the end of the key + + $key = substr($string, $key_begin, $key_end - $key_begin); + + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop + continue; // empty key + } + + // scroll past all whitespace + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor >= $size) { + $array[$key] = $key; + break; + } + + // if the next character is an equal sign, we've got a regular + // pair, otherwise, it's a bool attribute + $first_char = @$string[$cursor]; + + if ($first_char == '=') { + // key="value" + + $cursor++; + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor === false) { + $array[$key] = ''; + break; + } + + // we might be in front of a quote right now + + $char = @$string[$cursor]; + + if ($char == '"' || $char == "'") { + // it's quoted, end bound is $char + $cursor++; + $value_begin = $cursor; + $cursor = strpos($string, $char, $cursor); + $value_end = $cursor; + } else { + // it's not quoted, end bound is whitespace + $value_begin = $cursor; + $cursor += strcspn($string, $this->_whitespace, $cursor); + $value_end = $cursor; + } + + // we reached a premature end + if ($cursor === false) { + $cursor = $size; + $value_end = $cursor; + } + + $value = substr($string, $value_begin, $value_end - $value_begin); + if ($value === false) { + $value = ''; + } + $array[$key] = $this->parseAttr($value, $config); + $cursor++; + } else { + // boolattr + if ($key !== '') { + $array[$key] = $key; + } else { + // purely theoretical + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + } + } + } + return $array; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php new file mode 100644 index 0000000..1564f28 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php @@ -0,0 +1,4788 @@ +normalize($html, $config, $context); + $new_html = $this->wrapHTML($new_html, $config, $context, false /* no div */); + try { + $parser = new HTML5($new_html); + $doc = $parser->save(); + } catch (DOMException $e) { + // Uh oh, it failed. Punt to DirectLex. + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $context->register('PH5PError', $e); // save the error, so we can detect it + return $lexer->tokenizeHTML($html, $config, $context); // use original HTML + } + $tokens = array(); + $this->tokenizeDOM( + $doc->getElementsByTagName('html')->item(0)-> // + getElementsByTagName('body')->item(0) // + , + $tokens, $config + ); + return $tokens; + } +} + +/* + +Copyright 2007 Jeroen van der Meer + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +class HTML5 +{ + private $data; + private $char; + private $EOF; + private $state; + private $tree; + private $token; + private $content_model; + private $escape = false; + private $entities = array( + 'AElig;', + 'AElig', + 'AMP;', + 'AMP', + 'Aacute;', + 'Aacute', + 'Acirc;', + 'Acirc', + 'Agrave;', + 'Agrave', + 'Alpha;', + 'Aring;', + 'Aring', + 'Atilde;', + 'Atilde', + 'Auml;', + 'Auml', + 'Beta;', + 'COPY;', + 'COPY', + 'Ccedil;', + 'Ccedil', + 'Chi;', + 'Dagger;', + 'Delta;', + 'ETH;', + 'ETH', + 'Eacute;', + 'Eacute', + 'Ecirc;', + 'Ecirc', + 'Egrave;', + 'Egrave', + 'Epsilon;', + 'Eta;', + 'Euml;', + 'Euml', + 'GT;', + 'GT', + 'Gamma;', + 'Iacute;', + 'Iacute', + 'Icirc;', + 'Icirc', + 'Igrave;', + 'Igrave', + 'Iota;', + 'Iuml;', + 'Iuml', + 'Kappa;', + 'LT;', + 'LT', + 'Lambda;', + 'Mu;', + 'Ntilde;', + 'Ntilde', + 'Nu;', + 'OElig;', + 'Oacute;', + 'Oacute', + 'Ocirc;', + 'Ocirc', + 'Ograve;', + 'Ograve', + 'Omega;', + 'Omicron;', + 'Oslash;', + 'Oslash', + 'Otilde;', + 'Otilde', + 'Ouml;', + 'Ouml', + 'Phi;', + 'Pi;', + 'Prime;', + 'Psi;', + 'QUOT;', + 'QUOT', + 'REG;', + 'REG', + 'Rho;', + 'Scaron;', + 'Sigma;', + 'THORN;', + 'THORN', + 'TRADE;', + 'Tau;', + 'Theta;', + 'Uacute;', + 'Uacute', + 'Ucirc;', + 'Ucirc', + 'Ugrave;', + 'Ugrave', + 'Upsilon;', + 'Uuml;', + 'Uuml', + 'Xi;', + 'Yacute;', + 'Yacute', + 'Yuml;', + 'Zeta;', + 'aacute;', + 'aacute', + 'acirc;', + 'acirc', + 'acute;', + 'acute', + 'aelig;', + 'aelig', + 'agrave;', + 'agrave', + 'alefsym;', + 'alpha;', + 'amp;', + 'amp', + 'and;', + 'ang;', + 'apos;', + 'aring;', + 'aring', + 'asymp;', + 'atilde;', + 'atilde', + 'auml;', + 'auml', + 'bdquo;', + 'beta;', + 'brvbar;', + 'brvbar', + 'bull;', + 'cap;', + 'ccedil;', + 'ccedil', + 'cedil;', + 'cedil', + 'cent;', + 'cent', + 'chi;', + 'circ;', + 'clubs;', + 'cong;', + 'copy;', + 'copy', + 'crarr;', + 'cup;', + 'curren;', + 'curren', + 'dArr;', + 'dagger;', + 'darr;', + 'deg;', + 'deg', + 'delta;', + 'diams;', + 'divide;', + 'divide', + 'eacute;', + 'eacute', + 'ecirc;', + 'ecirc', + 'egrave;', + 'egrave', + 'empty;', + 'emsp;', + 'ensp;', + 'epsilon;', + 'equiv;', + 'eta;', + 'eth;', + 'eth', + 'euml;', + 'euml', + 'euro;', + 'exist;', + 'fnof;', + 'forall;', + 'frac12;', + 'frac12', + 'frac14;', + 'frac14', + 'frac34;', + 'frac34', + 'frasl;', + 'gamma;', + 'ge;', + 'gt;', + 'gt', + 'hArr;', + 'harr;', + 'hearts;', + 'hellip;', + 'iacute;', + 'iacute', + 'icirc;', + 'icirc', + 'iexcl;', + 'iexcl', + 'igrave;', + 'igrave', + 'image;', + 'infin;', + 'int;', + 'iota;', + 'iquest;', + 'iquest', + 'isin;', + 'iuml;', + 'iuml', + 'kappa;', + 'lArr;', + 'lambda;', + 'lang;', + 'laquo;', + 'laquo', + 'larr;', + 'lceil;', + 'ldquo;', + 'le;', + 'lfloor;', + 'lowast;', + 'loz;', + 'lrm;', + 'lsaquo;', + 'lsquo;', + 'lt;', + 'lt', + 'macr;', + 'macr', + 'mdash;', + 'micro;', + 'micro', + 'middot;', + 'middot', + 'minus;', + 'mu;', + 'nabla;', + 'nbsp;', + 'nbsp', + 'ndash;', + 'ne;', + 'ni;', + 'not;', + 'not', + 'notin;', + 'nsub;', + 'ntilde;', + 'ntilde', + 'nu;', + 'oacute;', + 'oacute', + 'ocirc;', + 'ocirc', + 'oelig;', + 'ograve;', + 'ograve', + 'oline;', + 'omega;', + 'omicron;', + 'oplus;', + 'or;', + 'ordf;', + 'ordf', + 'ordm;', + 'ordm', + 'oslash;', + 'oslash', + 'otilde;', + 'otilde', + 'otimes;', + 'ouml;', + 'ouml', + 'para;', + 'para', + 'part;', + 'permil;', + 'perp;', + 'phi;', + 'pi;', + 'piv;', + 'plusmn;', + 'plusmn', + 'pound;', + 'pound', + 'prime;', + 'prod;', + 'prop;', + 'psi;', + 'quot;', + 'quot', + 'rArr;', + 'radic;', + 'rang;', + 'raquo;', + 'raquo', + 'rarr;', + 'rceil;', + 'rdquo;', + 'real;', + 'reg;', + 'reg', + 'rfloor;', + 'rho;', + 'rlm;', + 'rsaquo;', + 'rsquo;', + 'sbquo;', + 'scaron;', + 'sdot;', + 'sect;', + 'sect', + 'shy;', + 'shy', + 'sigma;', + 'sigmaf;', + 'sim;', + 'spades;', + 'sub;', + 'sube;', + 'sum;', + 'sup1;', + 'sup1', + 'sup2;', + 'sup2', + 'sup3;', + 'sup3', + 'sup;', + 'supe;', + 'szlig;', + 'szlig', + 'tau;', + 'there4;', + 'theta;', + 'thetasym;', + 'thinsp;', + 'thorn;', + 'thorn', + 'tilde;', + 'times;', + 'times', + 'trade;', + 'uArr;', + 'uacute;', + 'uacute', + 'uarr;', + 'ucirc;', + 'ucirc', + 'ugrave;', + 'ugrave', + 'uml;', + 'uml', + 'upsih;', + 'upsilon;', + 'uuml;', + 'uuml', + 'weierp;', + 'xi;', + 'yacute;', + 'yacute', + 'yen;', + 'yen', + 'yuml;', + 'yuml', + 'zeta;', + 'zwj;', + 'zwnj;' + ); + + const PCDATA = 0; + const RCDATA = 1; + const CDATA = 2; + const PLAINTEXT = 3; + + const DOCTYPE = 0; + const STARTTAG = 1; + const ENDTAG = 2; + const COMMENT = 3; + const CHARACTR = 4; + const EOF = 5; + + public function __construct($data) + { + $this->data = $data; + $this->char = -1; + $this->EOF = strlen($data); + $this->tree = new HTML5TreeConstructer; + $this->content_model = self::PCDATA; + + $this->state = 'data'; + + while ($this->state !== null) { + $this->{$this->state . 'State'}(); + } + } + + public function save() + { + return $this->tree->save(); + } + + private function char() + { + return ($this->char < $this->EOF) + ? $this->data[$this->char] + : false; + } + + private function character($s, $l = 0) + { + if ($s + $l < $this->EOF) { + if ($l === 0) { + return $this->data[$s]; + } else { + return substr($this->data, $s, $l); + } + } + } + + private function characters($char_class, $start) + { + return preg_replace('#^([' . $char_class . ']+).*#s', '\\1', substr($this->data, $start)); + } + + private function dataState() + { + // Consume the next input character + $this->char++; + $char = $this->char(); + + if ($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { + /* U+0026 AMPERSAND (&) + When the content model flag is set to one of the PCDATA or RCDATA + states: switch to the entity data state. Otherwise: treat it as per + the "anything else" entry below. */ + $this->state = 'entityData'; + + } elseif ($char === '-') { + /* If the content model flag is set to either the RCDATA state or + the CDATA state, and the escape flag is false, and there are at + least three characters before this one in the input stream, and the + last four characters in the input stream, including this one, are + U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, + and U+002D HYPHEN-MINUS (""), + set the escape flag to false. */ + if (($this->content_model === self::RCDATA || + $this->content_model === self::CDATA) && $this->escape === true && + $this->character($this->char, 3) === '-->' + ) { + $this->escape = false; + } + + /* In any case, emit the input character as a character token. + Stay in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + } elseif ($this->char === $this->EOF) { + /* EOF + Emit an end-of-file token. */ + $this->EOF(); + + } elseif ($this->content_model === self::PLAINTEXT) { + /* When the content model flag is set to the PLAINTEXT state + THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of + the text and emit it as a character token. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => substr($this->data, $this->char) + ) + ); + + $this->EOF(); + + } else { + /* Anything else + THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that + otherwise would also be treated as a character token and emit it + as a single character token. Stay in the data state. */ + $len = strcspn($this->data, '<&', $this->char); + $char = substr($this->data, $this->char, $len); + $this->char += $len - 1; + + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + $this->state = 'data'; + } + } + + private function entityDataState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, emit a U+0026 AMPERSAND character token. + // Otherwise, emit the character token that was returned. + $char = (!$entity) ? '&' : $entity; + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + // Finally, switch to the data state. + $this->state = 'data'; + } + + private function tagOpenState() + { + switch ($this->content_model) { + case self::RCDATA: + case self::CDATA: + /* If the next input character is a U+002F SOLIDUS (/) character, + consume it and switch to the close tag open state. If the next + input character is not a U+002F SOLIDUS (/) character, emit a + U+003C LESS-THAN SIGN character token and switch to the data + state to process the next input character. */ + if ($this->character($this->char + 1) === '/') { + $this->char++; + $this->state = 'closeTagOpen'; + + } else { + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->state = 'data'; + } + break; + + case self::PCDATA: + // If the content model flag is set to the PCDATA state + // Consume the next input character: + $this->char++; + $char = $this->char(); + + if ($char === '!') { + /* U+0021 EXCLAMATION MARK (!) + Switch to the markup declaration open state. */ + $this->state = 'markupDeclarationOpen'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Switch to the close tag open state. */ + $this->state = 'closeTagOpen'; + + } elseif (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new start tag token, set its tag name to the lowercase + version of the input character (add 0x0020 to the character's code + point), then switch to the tag name state. (Don't emit the token + yet; further details will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::STARTTAG, + 'attr' => array() + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Emit a U+003C LESS-THAN SIGN character token and a + U+003E GREATER-THAN SIGN character token. Switch to the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<>' + ) + ); + + $this->state = 'data'; + + } elseif ($char === '?') { + /* U+003F QUESTION MARK (?) + Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + + } else { + /* Anything else + Parse error. Emit a U+003C LESS-THAN SIGN character token and + reconsume the current input character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->char--; + $this->state = 'data'; + } + break; + } + } + + private function closeTagOpenState() + { + $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); + $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; + + if (($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && + (!$the_same || ($the_same && (!preg_match( + '/[\t\n\x0b\x0c >\/]/', + $this->character($this->char + 1 + strlen($next_node)) + ) || $this->EOF === $this->char))) + ) { + /* If the content model flag is set to the RCDATA or CDATA states then + examine the next few characters. If they do not match the tag name of + the last start tag token emitted (case insensitively), or if they do but + they are not immediately followed by one of the following characters: + * U+0009 CHARACTER TABULATION + * U+000A LINE FEED (LF) + * U+000B LINE TABULATION + * U+000C FORM FEED (FF) + * U+0020 SPACE + * U+003E GREATER-THAN SIGN (>) + * U+002F SOLIDUS (/) + * EOF + ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character + token, a U+002F SOLIDUS character token, and switch to the data state + to process the next input character. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => 'state = 'data'; + + } else { + /* Otherwise, if the content model flag is set to the PCDATA state, + or if the next few characters do match that tag name, consume the + next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new end tag token, set its tag name to the lowercase version + of the input character (add 0x0020 to the character's code point), then + switch to the tag name state. (Don't emit the token yet; further details + will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::ENDTAG + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Switch to the data state. */ + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F + SOLIDUS character token. Reconsume the EOF character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => 'char--; + $this->state = 'data'; + + } else { + /* Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + } + } + } + + private function tagNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } else { + /* Anything else + Append the current input character to the current tag token's tag name. + Stay in the tag name state. */ + $this->token['name'] .= strtolower($char); + $this->state = 'tagName'; + } + } + + private function beforeAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Stay in the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function attributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's name. + Stay in the attribute name state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['name'] .= strtolower($char); + + $this->state = 'attributeName'; + } + } + + private function afterAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the after attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the + before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function beforeAttributeValueState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the attribute value (double-quoted) state. */ + $this->state = 'attributeValueDoubleQuoted'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the attribute value (unquoted) state and reconsume + this input character. */ + $this->char--; + $this->state = 'attributeValueUnquoted'; + + } elseif ($char === '\'') { + /* U+0027 APOSTROPHE (') + Switch to the attribute value (single-quoted) state. */ + $this->state = 'attributeValueSingleQuoted'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Switch to the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function attributeValueDoubleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('double'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (double-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueDoubleQuoted'; + } + } + + private function attributeValueSingleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '\'') { + /* U+0022 QUOTATION MARK (') + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('single'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (single-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueSingleQuoted'; + } + } + + private function attributeValueUnquotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState(); + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function entityInAttributeValueState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, append a U+0026 AMPERSAND character to the + // current attribute's value. Otherwise, emit the character token that + // was returned. + $char = (!$entity) + ? '&' + : $entity; + + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + } + + private function bogusCommentState() + { + /* Consume every character up to the first U+003E GREATER-THAN SIGN + character (>) or the end of the file (EOF), whichever comes first. Emit + a comment token whose data is the concatenation of all the characters + starting from and including the character that caused the state machine + to switch into the bogus comment state, up to and including the last + consumed character before the U+003E character, if any, or up to the + end of the file otherwise. (If the comment was started by the end of + the file (EOF), the token is empty.) */ + $data = $this->characters('^>', $this->char); + $this->emitToken( + array( + 'data' => $data, + 'type' => self::COMMENT + ) + ); + + $this->char += strlen($data); + + /* Switch to the data state. */ + $this->state = 'data'; + + /* If the end of the file was reached, reconsume the EOF character. */ + if ($this->char === $this->EOF) { + $this->char = $this->EOF - 1; + } + } + + private function markupDeclarationOpenState() + { + /* If the next two characters are both U+002D HYPHEN-MINUS (-) + characters, consume those two characters, create a comment token whose + data is the empty string, and switch to the comment state. */ + if ($this->character($this->char + 1, 2) === '--') { + $this->char += 2; + $this->state = 'comment'; + $this->token = array( + 'data' => null, + 'type' => self::COMMENT + ); + + /* Otherwise if the next seven chacacters are a case-insensitive match + for the word "DOCTYPE", then consume those characters and switch to the + DOCTYPE state. */ + } elseif (strtolower($this->character($this->char + 1, 7)) === 'doctype') { + $this->char += 7; + $this->state = 'doctype'; + + /* Otherwise, is is a parse error. Switch to the bogus comment state. + The next character that is consumed, if any, is the first character + that will be in the comment. */ + } else { + $this->char++; + $this->state = 'bogusComment'; + } + } + + private function commentState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment dash state */ + $this->state = 'commentDash'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append the input character to the comment token's data. Stay in + the comment state. */ + $this->token['data'] .= $char; + } + } + + private function commentDashState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment end state */ + $this->state = 'commentEnd'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append a U+002D HYPHEN-MINUS (-) character and the input + character to the comment token's data. Switch to the comment state. */ + $this->token['data'] .= '-' . $char; + $this->state = 'comment'; + } + } + + private function commentEndState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '-') { + $this->token['data'] .= '-'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['data'] .= '--' . $char; + $this->state = 'comment'; + } + } + + private function doctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'beforeDoctypeName'; + + } else { + $this->char--; + $this->state = 'beforeDoctypeName'; + } + } + + private function beforeDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the before DOCTYPE name state. + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token = array( + 'name' => strtoupper($char), + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + + } elseif ($char === '>') { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->char--; + $this->state = 'data'; + + } else { + $this->token = array( + 'name' => $char, + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + } + } + + private function doctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'AfterDoctypeName'; + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token['name'] .= strtoupper($char); + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['name'] .= $char; + } + + $this->token['error'] = ($this->token['name'] === 'HTML') + ? false + : true; + } + + private function afterDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the DOCTYPE name state. + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['error'] = true; + $this->state = 'bogusDoctype'; + } + } + + private function bogusDoctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + // Stay in the bogus DOCTYPE state. + } + } + + private function entity() + { + $start = $this->char; + + // This section defines how to consume an entity. This definition is + // used when parsing entities in text and in attributes. + + // The behaviour depends on the identity of the next character (the + // one immediately after the U+0026 AMPERSAND character): + + switch ($this->character($this->char + 1)) { + // U+0023 NUMBER SIGN (#) + case '#': + + // The behaviour further depends on the character after the + // U+0023 NUMBER SIGN: + switch ($this->character($this->char + 1)) { + // U+0078 LATIN SMALL LETTER X + // U+0058 LATIN CAPITAL LETTER X + case 'x': + case 'X': + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 + // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER + // A, through to U+0046 LATIN CAPITAL LETTER F (in other + // words, 0-9, A-F, a-f). + $char = 1; + $char_class = '0-9A-Fa-f'; + break; + + // Anything else + default: + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE (i.e. just 0-9). + $char = 0; + $char_class = '0-9'; + break; + } + + // Consume as many characters as match the range of characters + // given above. + $this->char++; + $e_name = $this->characters($char_class, $this->char + $char + 1); + $entity = $this->character($start, $this->char); + $cond = strlen($e_name) > 0; + + // The rest of the parsing happens below. + break; + + // Anything else + default: + // Consume the maximum number of characters possible, with the + // consumed characters case-sensitively matching one of the + // identifiers in the first column of the entities table. + + $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); + $len = strlen($e_name); + + for ($c = 1; $c <= $len; $c++) { + $id = substr($e_name, 0, $c); + $this->char++; + + if (in_array($id, $this->entities)) { + if ($e_name[$c - 1] !== ';') { + if ($c < $len && $e_name[$c] == ';') { + $this->char++; // consume extra semicolon + } + } + $entity = $id; + break; + } + } + + $cond = isset($entity); + // The rest of the parsing happens below. + break; + } + + if (!$cond) { + // If no match can be made, then this is a parse error. No + // characters are consumed, and nothing is returned. + $this->char = $start; + return false; + } + + // Return a character token for the character corresponding to the + // entity name (as given by the second column of the entities table). + return html_entity_decode('&' . rtrim($entity, ';') . ';', ENT_QUOTES, 'UTF-8'); + } + + private function emitToken($token) + { + $emit = $this->tree->emitToken($token); + + if (is_int($emit)) { + $this->content_model = $emit; + + } elseif ($token['type'] === self::ENDTAG) { + $this->content_model = self::PCDATA; + } + } + + private function EOF() + { + $this->state = null; + $this->tree->emitToken( + array( + 'type' => self::EOF + ) + ); + } +} + +class HTML5TreeConstructer +{ + public $stack = array(); + + private $phase; + private $mode; + private $dom; + private $foster_parent = null; + private $a_formatting = array(); + + private $head_pointer = null; + private $form_pointer = null; + + private $scoping = array('button', 'caption', 'html', 'marquee', 'object', 'table', 'td', 'th'); + private $formatting = array( + 'a', + 'b', + 'big', + 'em', + 'font', + 'i', + 'nobr', + 's', + 'small', + 'strike', + 'strong', + 'tt', + 'u' + ); + private $special = array( + 'address', + 'area', + 'base', + 'basefont', + 'bgsound', + 'blockquote', + 'body', + 'br', + 'center', + 'col', + 'colgroup', + 'dd', + 'dir', + 'div', + 'dl', + 'dt', + 'embed', + 'fieldset', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'hr', + 'iframe', + 'image', + 'img', + 'input', + 'isindex', + 'li', + 'link', + 'listing', + 'menu', + 'meta', + 'noembed', + 'noframes', + 'noscript', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'plaintext', + 'pre', + 'script', + 'select', + 'spacer', + 'style', + 'tbody', + 'textarea', + 'tfoot', + 'thead', + 'title', + 'tr', + 'ul', + 'wbr' + ); + + // The different phases. + const INIT_PHASE = 0; + const ROOT_PHASE = 1; + const MAIN_PHASE = 2; + const END_PHASE = 3; + + // The different insertion modes for the main phase. + const BEFOR_HEAD = 0; + const IN_HEAD = 1; + const AFTER_HEAD = 2; + const IN_BODY = 3; + const IN_TABLE = 4; + const IN_CAPTION = 5; + const IN_CGROUP = 6; + const IN_TBODY = 7; + const IN_ROW = 8; + const IN_CELL = 9; + const IN_SELECT = 10; + const AFTER_BODY = 11; + const IN_FRAME = 12; + const AFTR_FRAME = 13; + + // The different types of elements. + const SPECIAL = 0; + const SCOPING = 1; + const FORMATTING = 2; + const PHRASING = 3; + + const MARKER = 0; + + public function __construct() + { + $this->phase = self::INIT_PHASE; + $this->mode = self::BEFOR_HEAD; + $this->dom = new DOMDocument; + + $this->dom->encoding = 'UTF-8'; + $this->dom->preserveWhiteSpace = true; + $this->dom->substituteEntities = true; + $this->dom->strictErrorChecking = false; + } + + // Process tag tokens + public function emitToken($token) + { + switch ($this->phase) { + case self::INIT_PHASE: + return $this->initPhase($token); + break; + case self::ROOT_PHASE: + return $this->rootElementPhase($token); + break; + case self::MAIN_PHASE: + return $this->mainPhase($token); + break; + case self::END_PHASE : + return $this->trailingEndPhase($token); + break; + } + } + + private function initPhase($token) + { + /* Initially, the tree construction stage must handle each token + emitted from the tokenisation stage as follows: */ + + /* A DOCTYPE token that is marked as being in error + A comment token + A start tag token + An end tag token + A character token that is not one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE + An end-of-file token */ + if ((isset($token['error']) && $token['error']) || + $token['type'] === HTML5::COMMENT || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF || + ($token['type'] === HTML5::CHARACTR && isset($token['data']) && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) + ) { + /* This specification does not define how to handle this case. In + particular, user agents may ignore the entirety of this specification + altogether for such documents, and instead invoke special parse modes + with a greater emphasis on backwards compatibility. */ + + $this->phase = self::ROOT_PHASE; + return $this->rootElementPhase($token); + + /* A DOCTYPE token marked as being correct */ + } elseif (isset($token['error']) && !$token['error']) { + /* Append a DocumentType node to the Document node, with the name + attribute set to the name given in the DOCTYPE token (which will be + "HTML"), and the other attributes specific to DocumentType objects + set to null, empty lists, or the empty string as appropriate. */ + $doctype = new DOMDocumentType(null, null, 'HTML'); + + /* Then, switch to the root element phase of the tree construction + stage. */ + $this->phase = self::ROOT_PHASE; + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif (isset($token['data']) && preg_match( + '/^[\t\n\x0b\x0c ]+$/', + $token['data'] + ) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + } + } + + private function rootElementPhase($token) + { + /* After the initial phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED + (FF), or U+0020 SPACE + A start tag token + An end tag token + An end-of-file token */ + } elseif (($token['type'] === HTML5::CHARACTR && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF + ) { + /* Create an HTMLElement node with the tag name html, in the HTML + namespace. Append it to the Document object. Switch to the main + phase and reprocess the current token. */ + $html = $this->dom->createElement('html'); + $this->dom->appendChild($html); + $this->stack[] = $html; + + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + } + } + + private function mainPhase($token) + { + /* Tokens in the main phase must be handled as follows: */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A start tag token with the tag name "html" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { + /* If this start tag token was not the first start tag token, then + it is a parse error. */ + + /* For each attribute on the token, check to see if the attribute + is already present on the top element of the stack of open elements. + If it is not, add the attribute and its corresponding value to that + element. */ + foreach ($token['attr'] as $attr) { + if (!$this->stack[0]->hasAttribute($attr['name'])) { + $this->stack[0]->setAttribute($attr['name'], $attr['value']); + } + } + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Anything else. */ + } else { + /* Depends on the insertion mode: */ + switch ($this->mode) { + case self::BEFOR_HEAD: + return $this->beforeHead($token); + break; + case self::IN_HEAD: + return $this->inHead($token); + break; + case self::AFTER_HEAD: + return $this->afterHead($token); + break; + case self::IN_BODY: + return $this->inBody($token); + break; + case self::IN_TABLE: + return $this->inTable($token); + break; + case self::IN_CAPTION: + return $this->inCaption($token); + break; + case self::IN_CGROUP: + return $this->inColumnGroup($token); + break; + case self::IN_TBODY: + return $this->inTableBody($token); + break; + case self::IN_ROW: + return $this->inRow($token); + break; + case self::IN_CELL: + return $this->inCell($token); + break; + case self::IN_SELECT: + return $this->inSelect($token); + break; + case self::AFTER_BODY: + return $this->afterBody($token); + break; + case self::IN_FRAME: + return $this->inFrameset($token); + break; + case self::AFTR_FRAME: + return $this->afterFrameset($token); + break; + case self::END_PHASE: + return $this->trailingEndPhase($token); + break; + } + } + } + + private function beforeHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "head" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { + /* Create an element for the token, append the new element to the + current node and push it onto the stack of open elements. */ + $element = $this->insertElement($token); + + /* Set the head element pointer to this new element node. */ + $this->head_pointer = $element; + + /* Change the insertion mode to "in head". */ + $this->mode = self::IN_HEAD; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title". Or an end tag with the tag name "html". + Or a character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or any other start tag token */ + } elseif ($token['type'] === HTML5::STARTTAG || + ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || + ($token['type'] === HTML5::CHARACTR && !preg_match( + '/^[\t\n\x0b\x0c ]$/', + $token['data'] + )) + ) { + /* Act as if a start tag token with the tag name "head" and no + attributes had been seen, then reprocess the current token. */ + $this->beforeHead( + array( + 'name' => 'head', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inHead($token); + + /* Any other end tag */ + } elseif ($token['type'] === HTML5::ENDTAG) { + /* Parse error. Ignore the token. */ + } + } + + private function inHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. + + THIS DIFFERS FROM THE SPEC: If the current node is either a title, style + or script element, append the character to the current node regardless + of its content. */ + if (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( + $token['type'] === HTML5::CHARACTR && in_array( + end($this->stack)->nodeName, + array('title', 'style', 'script') + )) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('title', 'style', 'script')) + ) { + array_pop($this->stack); + return HTML5::PCDATA; + + /* A start tag with the tag name "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $element = $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the RCDATA state. */ + return HTML5::RCDATA; + + /* A start tag with the tag name "style" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "script" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { + /* Create an element for the token. */ + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "base", "link", or "meta" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta') + ) + ) { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + array_pop($this->stack); + + } else { + $this->insertElement($token); + } + + /* An end tag with the tag name "head" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { + /* If the current node is a head element, pop the current node off + the stack of open elements. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + array_pop($this->stack); + + /* Otherwise, this is a parse error. */ + } else { + // k + } + + /* Change the insertion mode to "after head". */ + $this->mode = self::AFTER_HEAD; + + /* A start tag with the tag name "head" or an end tag except "html". */ + } elseif (($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || + ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html') + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* If the current node is a head element, act as if an end tag + token with the tag name "head" had been seen. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + $this->inHead( + array( + 'name' => 'head', + 'type' => HTML5::ENDTAG + ) + ); + + /* Otherwise, change the insertion mode to "after head". */ + } else { + $this->mode = self::AFTER_HEAD; + } + + /* Then, reprocess the current token. */ + return $this->afterHead($token); + } + } + + private function afterHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "body" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { + /* Insert a body element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in body". */ + $this->mode = self::IN_BODY; + + /* A start tag token with the tag name "frameset" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { + /* Insert a frameset element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in frameset". */ + $this->mode = self::IN_FRAME; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta', 'script', 'style', 'title') + ) + ) { + /* Parse error. Switch the insertion mode back to "in head" and + reprocess the token. */ + $this->mode = self::IN_HEAD; + return $this->inHead($token); + + /* Anything else */ + } else { + /* Act as if a start tag token with the tag name "body" and no + attributes had been seen, and then reprocess the current token. */ + $this->afterHead( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inBody($token); + } + } + + private function inBody($token) + { + /* Handle the token as follows: */ + + switch ($token['type']) { + /* A character token */ + case HTML5::CHARACTR: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + break; + + /* A comment token */ + case HTML5::COMMENT: + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + break; + + case HTML5::STARTTAG: + switch ($token['name']) { + /* A start tag token whose tag name is one of: "script", + "style" */ + case 'script': + case 'style': + /* Process the token as if the insertion mode had been "in + head". */ + return $this->inHead($token); + break; + + /* A start tag token whose tag name is one of: "base", "link", + "meta", "title" */ + case 'base': + case 'link': + case 'meta': + case 'title': + /* Parse error. Process the token as if the insertion mode + had been "in head". */ + return $this->inHead($token); + break; + + /* A start tag token with the tag name "body" */ + case 'body': + /* Parse error. If the second element on the stack of open + elements is not a body element, or, if the stack of open + elements has only one node on it, then ignore the token. + (innerHTML case) */ + if (count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { + // Ignore + + /* Otherwise, for each attribute on the token, check to see + if the attribute is already present on the body element (the + second element) on the stack of open elements. If it is not, + add the attribute and its corresponding value to that + element. */ + } else { + foreach ($token['attr'] as $attr) { + if (!$this->stack[1]->hasAttribute($attr['name'])) { + $this->stack[1]->setAttribute($attr['name'], $attr['value']); + } + } + } + break; + + /* A start tag whose tag name is one of: "address", + "blockquote", "center", "dir", "div", "dl", "fieldset", + "listing", "menu", "ol", "p", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'p': + case 'ul': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "form" */ + case 'form': + /* If the form element pointer is not null, ignore the + token with a parse error. */ + if ($this->form_pointer !== null) { + // Ignore. + + /* Otherwise: */ + } else { + /* If the stack of open elements has a p element in + scope, then act as if an end tag with the tag name p + had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token, and set the + form element pointer to point to the element created. */ + $element = $this->insertElement($token); + $this->form_pointer = $element; + } + break; + + /* A start tag whose tag name is "li", "dd" or "dt" */ + case 'li': + case 'dd': + case 'dt': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + $stack_length = count($this->stack) - 1; + + for ($n = $stack_length; 0 <= $n; $n--) { + /* 1. Initialise node to be the current node (the + bottommost node of the stack). */ + $stop = false; + $node = $this->stack[$n]; + $cat = $this->getElementCategory($node->tagName); + + /* 2. If node is an li, dd or dt element, then pop all + the nodes from the current node up to node, including + node, then stop this algorithm. */ + if ($token['name'] === $node->tagName || ($token['name'] !== 'li' + && ($node->tagName === 'dd' || $node->tagName === 'dt')) + ) { + for ($x = $stack_length; $x >= $n; $x--) { + array_pop($this->stack); + } + + break; + } + + /* 3. If node is not in the formatting category, and is + not in the phrasing category, and is not an address or + div element, then stop this algorithm. */ + if ($cat !== self::FORMATTING && $cat !== self::PHRASING && + $node->tagName !== 'address' && $node->tagName !== 'div' + ) { + break; + } + } + + /* Finally, insert an HTML element with the same tag + name as the token's. */ + $this->insertElement($token); + break; + + /* A start tag token whose tag name is "plaintext" */ + case 'plaintext': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + return HTML5::PLAINTEXT; + break; + + /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + this is a parse error; pop elements from the stack until an + element with one of those tag names has been popped from the + stack. */ + while ($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { + array_pop($this->stack); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "a" */ + case 'a': + /* If the list of active formatting elements contains + an element whose tag name is "a" between the end of the + list and the last marker on the list (or the start of + the list if there is no marker on the list), then this + is a parse error; act as if an end tag with the tag name + "a" had been seen, then remove that element from the list + of active formatting elements and the stack of open + elements if the end tag didn't already remove it (it + might not have if the element is not in table scope). */ + $leng = count($this->a_formatting); + + for ($n = $leng - 1; $n >= 0; $n--) { + if ($this->a_formatting[$n] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$n]->nodeName === 'a') { + $this->emitToken( + array( + 'name' => 'a', + 'type' => HTML5::ENDTAG + ) + ); + break; + } + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag whose tag name is one of: "b", "big", "em", "font", + "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag token whose tag name is "button" */ + case 'button': + /* If the stack of open elements has a button element in scope, + then this is a parse error; act as if an end tag with the tag + name "button" had been seen, then reprocess the token. (We don't + do that. Unnecessary.) */ + if ($this->elementInScope('button')) { + $this->inBody( + array( + 'name' => 'button', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is one of: "marquee", "object" */ + case 'marquee': + case 'object': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is "xmp" */ + case 'xmp': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Switch the content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "table" */ + case 'table': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + break; + + /* A start tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'img': + case 'param': + case 'spacer': + case 'wbr': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "hr" */ + case 'hr': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "image" */ + case 'image': + /* Parse error. Change the token's tag name to "img" and + reprocess it. (Don't ask.) */ + $token['name'] = 'img'; + return $this->inBody($token); + break; + + /* A start tag whose tag name is "input" */ + case 'input': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an input element for the token. */ + $element = $this->insertElement($token, false); + + /* If the form element pointer is not null, then associate the + input element with the form element pointed to by the form + element pointer. */ + $this->form_pointer !== null + ? $this->form_pointer->appendChild($element) + : end($this->stack)->appendChild($element); + + /* Pop that input element off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "isindex" */ + case 'isindex': + /* Parse error. */ + // w/e + + /* If the form element pointer is not null, + then ignore the token. */ + if ($this->form_pointer === null) { + /* Act as if a start tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a stream of character tokens had been seen. */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if a start tag token with the tag name "input" + had been seen, with all the attributes from the "isindex" + token, except with the "name" attribute set to the value + "isindex" (ignoring any explicit "name" attribute). */ + $attr = $token['attr']; + $attr[] = array('name' => 'name', 'value' => 'isindex'); + + $this->inBody( + array( + 'name' => 'input', + 'type' => HTML5::STARTTAG, + 'attr' => $attr + ) + ); + + /* Act as if a stream of character tokens had been seen + (see below for what they should say). */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if an end tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'form', + 'type' => HTML5::ENDTAG + ) + ); + } + break; + + /* A start tag whose tag name is "textarea" */ + case 'textarea': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the + RCDATA state. */ + return HTML5::RCDATA; + break; + + /* A start tag whose tag name is one of: "iframe", "noembed", + "noframes" */ + case 'iframe': + case 'noembed': + case 'noframes': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "select" */ + case 'select': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in select". */ + $this->mode = self::IN_SELECT; + break; + + /* A start or end tag whose tag name is one of: "caption", "col", + "colgroup", "frame", "frameset", "head", "option", "optgroup", + "tbody", "td", "tfoot", "th", "thead", "tr". */ + case 'caption': + case 'col': + case 'colgroup': + case 'frame': + case 'frameset': + case 'head': + case 'option': + case 'optgroup': + case 'tbody': + case 'td': + case 'tfoot': + case 'th': + case 'thead': + case 'tr': + // Parse error. Ignore the token. + break; + + /* A start or end tag whose tag name is one of: "event-source", + "section", "nav", "article", "aside", "header", "footer", + "datagrid", "command" */ + case 'event-source': + case 'section': + case 'nav': + case 'article': + case 'aside': + case 'header': + case 'footer': + case 'datagrid': + case 'command': + // Work in progress! + break; + + /* A start tag token not covered by the previous entries */ + default: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + $this->insertElement($token, true, true); + break; + } + break; + + case HTML5::ENDTAG: + switch ($token['name']) { + /* An end tag with the tag name "body" */ + case 'body': + /* If the second element in the stack of open elements is + not a body element, this is a parse error. Ignore the token. + (innerHTML case) */ + if (count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { + // Ignore. + + /* If the current node is not the body element, then this + is a parse error. */ + } elseif (end($this->stack)->nodeName !== 'body') { + // Parse error. + } + + /* Change the insertion mode to "after body". */ + $this->mode = self::AFTER_BODY; + break; + + /* An end tag with the tag name "html" */ + case 'html': + /* Act as if an end tag with tag name "body" had been seen, + then, if that token wasn't ignored, reprocess the current + token. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->afterBody($token); + break; + + /* An end tag whose tag name is one of: "address", "blockquote", + "center", "dir", "div", "dl", "fieldset", "listing", "menu", + "ol", "pre", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'pre': + case 'ul': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with + the same tag name as that of the token, then this + is a parse error. */ + // w/e + + /* If the stack of open elements has an element in + scope with the same tag name as that of the token, + then pop elements from this stack until an element + with that tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is "form" */ + case 'form': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + } + + if (end($this->stack)->nodeName !== $token['name']) { + /* Now, if the current node is not an element with the + same tag name as that of the token, then this is a parse + error. */ + // w/e + + } else { + /* Otherwise, if the current node is an element with + the same tag name as that of the token pop that element + from the stack. */ + array_pop($this->stack); + } + + /* In any case, set the form element pointer to null. */ + $this->form_pointer = null; + break; + + /* An end tag whose tag name is "p" */ + case 'p': + /* If the stack of open elements has a p element in scope, + then generate implied end tags, except for p elements. */ + if ($this->elementInScope('p')) { + $this->generateImpliedEndTags(array('p')); + + /* If the current node is not a p element, then this is + a parse error. */ + // k + + /* If the stack of open elements has a p element in + scope, then pop elements from this stack until the stack + no longer has a p element in scope. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->elementInScope('p')) { + array_pop($this->stack); + + } else { + break; + } + } + } + break; + + /* An end tag whose tag name is "dd", "dt", or "li" */ + case 'dd': + case 'dt': + case 'li': + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + generate implied end tags, except for elements with the + same tag name as the token. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(array($token['name'])); + + /* If the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + pop elements from this stack until an element with that + tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + generate implied end tags. */ + if ($this->elementInScope($elements)) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as that of the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has in scope an element + whose tag name is one of "h1", "h2", "h3", "h4", "h5", or + "h6", then pop elements from the stack until an element + with one of those tag names has been popped from the stack. */ + while ($this->elementInScope($elements)) { + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "a", "b", "big", "em", + "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'a': + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* 1. Let the formatting element be the last element in + the list of active formatting elements that: + * is between the end of the list and the last scope + marker in the list, if any, or the start of the list + otherwise, and + * has the same tag name as the token. + */ + while (true) { + for ($a = count($this->a_formatting) - 1; $a >= 0; $a--) { + if ($this->a_formatting[$a] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$a]->tagName === $token['name']) { + $formatting_element = $this->a_formatting[$a]; + $in_stack = in_array($formatting_element, $this->stack, true); + $fe_af_pos = $a; + break; + } + } + + /* If there is no such node, or, if that node is + also in the stack of open elements but the element + is not in scope, then this is a parse error. Abort + these steps. The token is ignored. */ + if (!isset($formatting_element) || ($in_stack && + !$this->elementInScope($token['name'])) + ) { + break; + + /* Otherwise, if there is such a node, but that node + is not in the stack of open elements, then this is a + parse error; remove the element from the list, and + abort these steps. */ + } elseif (isset($formatting_element) && !$in_stack) { + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 2. Let the furthest block be the topmost node in the + stack of open elements that is lower in the stack + than the formatting element, and is not an element in + the phrasing or formatting categories. There might + not be one. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $length = count($this->stack); + + for ($s = $fe_s_pos + 1; $s < $length; $s++) { + $category = $this->getElementCategory($this->stack[$s]->nodeName); + + if ($category !== self::PHRASING && $category !== self::FORMATTING) { + $furthest_block = $this->stack[$s]; + } + } + + /* 3. If there is no furthest block, then the UA must + skip the subsequent steps and instead just pop all + the nodes from the bottom of the stack of open + elements, from the current node up to the formatting + element, and remove the formatting element from the + list of active formatting elements. */ + if (!isset($furthest_block)) { + for ($n = $length - 1; $n >= $fe_s_pos; $n--) { + array_pop($this->stack); + } + + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 4. Let the common ancestor be the element + immediately above the formatting element in the stack + of open elements. */ + $common_ancestor = $this->stack[$fe_s_pos - 1]; + + /* 5. If the furthest block has a parent node, then + remove the furthest block from its parent node. */ + if ($furthest_block->parentNode !== null) { + $furthest_block->parentNode->removeChild($furthest_block); + } + + /* 6. Let a bookmark note the position of the + formatting element in the list of active formatting + elements relative to the elements on either side + of it in the list. */ + $bookmark = $fe_af_pos; + + /* 7. Let node and last node be the furthest block. + Follow these steps: */ + $node = $furthest_block; + $last_node = $furthest_block; + + while (true) { + for ($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { + /* 7.1 Let node be the element immediately + prior to node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 7.2 If node is not in the list of active + formatting elements, then remove node from + the stack of open elements and then go back + to step 1. */ + if (!in_array($node, $this->a_formatting, true)) { + unset($this->stack[$n]); + $this->stack = array_merge($this->stack); + + } else { + break; + } + } + + /* 7.3 Otherwise, if node is the formatting + element, then go to the next step in the overall + algorithm. */ + if ($node === $formatting_element) { + break; + + /* 7.4 Otherwise, if last node is the furthest + block, then move the aforementioned bookmark to + be immediately after the node in the list of + active formatting elements. */ + } elseif ($last_node === $furthest_block) { + $bookmark = array_search($node, $this->a_formatting, true) + 1; + } + + /* 7.5 If node has any children, perform a + shallow clone of node, replace the entry for + node in the list of active formatting elements + with an entry for the clone, replace the entry + for node in the stack of open elements with an + entry for the clone, and let node be the clone. */ + if ($node->hasChildNodes()) { + $clone = $node->cloneNode(); + $s_pos = array_search($node, $this->stack, true); + $a_pos = array_search($node, $this->a_formatting, true); + + $this->stack[$s_pos] = $clone; + $this->a_formatting[$a_pos] = $clone; + $node = $clone; + } + + /* 7.6 Insert last node into node, first removing + it from its previous parent node if any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $node->appendChild($last_node); + + /* 7.7 Let last node be node. */ + $last_node = $node; + } + + /* 8. Insert whatever last node ended up being in + the previous step into the common ancestor node, + first removing it from its previous parent node if + any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $common_ancestor->appendChild($last_node); + + /* 9. Perform a shallow clone of the formatting + element. */ + $clone = $formatting_element->cloneNode(); + + /* 10. Take all of the child nodes of the furthest + block and append them to the clone created in the + last step. */ + while ($furthest_block->hasChildNodes()) { + $child = $furthest_block->firstChild; + $furthest_block->removeChild($child); + $clone->appendChild($child); + } + + /* 11. Append that clone to the furthest block. */ + $furthest_block->appendChild($clone); + + /* 12. Remove the formatting element from the list + of active formatting elements, and insert the clone + into the list of active formatting elements at the + position of the aforementioned bookmark. */ + $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + + $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); + $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); + $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); + + /* 13. Remove the formatting element from the stack + of open elements, and insert the clone into the stack + of open elements immediately after (i.e. in a more + deeply nested position than) the position of the + furthest block in that stack. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $fb_s_pos = array_search($furthest_block, $this->stack, true); + unset($this->stack[$fe_s_pos]); + + $s_part1 = array_slice($this->stack, 0, $fb_s_pos); + $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); + $this->stack = array_merge($s_part1, array($clone), $s_part2); + + /* 14. Jump back to step 1 in this series of steps. */ + unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); + } + break; + + /* An end tag token whose tag name is one of: "button", + "marquee", "object" */ + case 'button': + case 'marquee': + case 'object': + /* If the stack of open elements has an element in scope whose + tag name matches the tag name of the token, then generate implied + tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // k + + /* Now, if the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then pop + elements from the stack until that element has been popped from + the stack, and clear the list of active formatting elements up + to the last marker. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + + $marker = end(array_keys($this->a_formatting, self::MARKER, true)); + + for ($n = count($this->a_formatting) - 1; $n > $marker; $n--) { + array_pop($this->a_formatting); + } + } + break; + + /* Or an end tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "hr", "iframe", "image", "img", + "input", "isindex", "noembed", "noframes", "param", "select", + "spacer", "table", "textarea", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'hr': + case 'iframe': + case 'image': + case 'img': + case 'input': + case 'isindex': + case 'noembed': + case 'noframes': + case 'param': + case 'select': + case 'spacer': + case 'table': + case 'textarea': + case 'wbr': + // Parse error. Ignore the token. + break; + + /* An end tag token not covered by the previous entries */ + default: + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + /* Initialise node to be the current node (the bottommost + node of the stack). */ + $node = end($this->stack); + + /* If node has the same tag name as the end tag token, + then: */ + if ($token['name'] === $node->nodeName) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* If the tag name of the end tag token does not + match the tag name of the current node, this is a + parse error. */ + // k + + /* Pop all the nodes from the current node up to + node, including node, then stop this algorithm. */ + for ($x = count($this->stack) - $n; $x >= $n; $x--) { + array_pop($this->stack); + } + + } else { + $category = $this->getElementCategory($node); + + if ($category !== self::SPECIAL && $category !== self::SCOPING) { + /* Otherwise, if node is in neither the formatting + category nor the phrasing category, then this is a + parse error. Stop this algorithm. The end tag token + is ignored. */ + return false; + } + } + } + break; + } + break; + } + } + + private function inTable($token) + { + $clear = array('html', 'table'); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "caption" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'caption' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + + /* Insert an HTML element for the token, then switch the + insertion mode to "in caption". */ + $this->insertElement($token); + $this->mode = self::IN_CAPTION; + + /* A start tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'colgroup' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the + insertion mode to "in column group". */ + $this->insertElement($token); + $this->mode = self::IN_CGROUP; + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'col' + ) { + $this->inTable( + array( + 'name' => 'colgroup', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + $this->inColumnGroup($token); + + /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('tbody', 'tfoot', 'thead') + ) + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in table body". */ + $this->insertElement($token); + $this->mode = self::IN_TBODY; + + /* A start tag whose tag name is one of: "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && + in_array($token['name'], array('td', 'th', 'tr')) + ) { + /* Act as if a start tag token with the tag name "tbody" had been + seen, then reprocess the current token. */ + $this->inTable( + array( + 'name' => 'tbody', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inTableBody($token); + + /* A start tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'table' + ) { + /* Parse error. Act as if an end tag token with the tag name "table" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inTable( + array( + 'name' => 'table', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + + /* An end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + return false; + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a table element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a table element has been + popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'table') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'caption', + 'col', + 'colgroup', + 'html', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Parse error. Process the token as if the insertion mode was "in + body", with the following exception: */ + + /* If the current node is a table, tbody, tfoot, thead, or tr + element, then, whenever a node would be inserted into the current + node, it must instead be inserted into the foster parent element. */ + if (in_array( + end($this->stack)->nodeName, + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* The foster parent element is the parent element of the last + table element in the stack of open elements, if there is a + table element and it has such a parent element. If there is no + table element in the stack of open elements (innerHTML case), + then the foster parent element is the first element in the + stack of open elements (the html element). Otherwise, if there + is a table element in the stack of open elements, but the last + table element in the stack of open elements has no parent, or + its parent node is not an element, then the foster parent + element is the element before the last table element in the + stack of open elements. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table') { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $table->parentNode !== null) { + $this->foster_parent = $table->parentNode; + + } elseif (!isset($table)) { + $this->foster_parent = $this->stack[0]; + + } elseif (isset($table) && ($table->parentNode === null || + $table->parentNode->nodeType !== XML_ELEMENT_NODE) + ) { + $this->foster_parent = $this->stack[$n - 1]; + } + } + + $this->inBody($token); + } + } + + private function inCaption($token) + { + /* An end tag whose tag name is "caption" */ + if ($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a caption element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a caption element has + been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === 'caption') { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag + name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + )) || ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table') + ) { + /* Parse error. Act as if an end tag with the tag name "caption" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inCaption( + array( + 'name' => 'caption', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + + /* An end tag whose tag name is one of: "body", "col", "colgroup", + "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'col', + 'colgroup', + 'html', + 'tbody', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inColumnGroup($token) + { + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { + /* Insert a col element for the token. Immediately pop the current + node off the stack of open elements. */ + $this->insertElement($token); + array_pop($this->stack); + + /* An end tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'colgroup' + ) { + /* If the current node is the root html element, then this is a + parse error, ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + /* Otherwise, pop the current node (which will be a colgroup + element) from the stack of open elements. Switch the insertion + mode to "in table". */ + } else { + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* An end tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Act as if an end tag with the tag name "colgroup" had been seen, + and then, if that token wasn't ignored, reprocess the current token. */ + $this->inColumnGroup( + array( + 'name' => 'colgroup', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + } + } + + private function inTableBody($token) + { + $clear = array('tbody', 'tfoot', 'thead', 'html'); + + /* A start tag whose tag name is "tr" */ + if ($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Insert a tr element for the token, then switch the insertion + mode to "in row". */ + $this->insertElement($token); + $this->mode = self::IN_ROW; + + /* A start tag whose tag name is one of: "th", "td" */ + } elseif ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Parse error. Act as if a start tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inTableBody( + array( + 'name' => 'tr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inRow($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node from the stack of open elements. Switch + the insertion mode to "in table". */ + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead') + )) || + ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table') + ) { + /* If the stack of open elements does not have a tbody, thead, or + tfoot element in table scope, this is a parse error. Ignore the + token. (innerHTML case) */ + if (!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Act as if an end tag with the same tag name as the current + node ("tbody", "tfoot", or "thead") had been seen, then + reprocess the current token. */ + $this->inTableBody( + array( + 'name' => end($this->stack)->nodeName, + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inRow($token) + { + $clear = array('tr', 'html'); + + /* A start tag whose tag name is one of: "th", "td" */ + if ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in cell". */ + $this->insertElement($token); + $this->mode = self::IN_CELL; + + /* Insert a marker at the end of the list of active formatting + elements. */ + $this->a_formatting[] = self::MARKER; + + /* An end tag whose tag name is "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node (which will be a tr element) from the + stack of open elements. Switch the insertion mode to "in table + body". */ + array_pop($this->stack); + $this->mode = self::IN_TBODY; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* Act as if an end tag with the tag name "tr" had been seen, then, + if that token wasn't ignored, reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Otherwise, act as if an end tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inCell($token) + { + /* An end tag whose tag name is one of: "td", "th" */ + if ($token['type'] === HTML5::ENDTAG && + ($token['name'] === 'td' || $token['name'] === 'th') + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token, then this is a + parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Generate implied end tags, except for elements with the same + tag name as the token. */ + $this->generateImpliedEndTags(array($token['name'])); + + /* Now, if the current node is not an element with the same tag + name as the token, then this is a parse error. */ + // k + + /* Pop elements from this stack until an element with the same + tag name as the token has been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === $token['name']) { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in row". (The current node + will be a tr element at this point.) */ + $this->mode = self::IN_ROW; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html') + ) + ) { + /* Parse error. Ignore the token. */ + + /* An end tag whose tag name is one of: "table", "tbody", "tfoot", + "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token (which can only + happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), + then this is a parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inSelect($token) + { + /* Handle the token as follows: */ + + /* A character token */ + if ($token['type'] === HTML5::CHARACTR) { + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* A start tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'optgroup' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, act as if an end tag + with the tag name "optgroup" had been seen. */ + if (end($this->stack)->nodeName === 'optgroup') { + $this->inSelect( + array( + 'name' => 'optgroup', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* An end tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'optgroup' + ) { + /* First, if the current node is an option element, and the node + immediately before it in the stack of open elements is an optgroup + element, then act as if an end tag with the tag name "option" had + been seen. */ + $elements_in_stack = count($this->stack); + + if ($this->stack[$elements_in_stack - 1]->nodeName === 'option' && + $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup' + ) { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if ($this->stack[$elements_in_stack - 1] === 'optgroup') { + array_pop($this->stack); + } + + /* An end tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if (end($this->stack)->nodeName === 'option') { + array_pop($this->stack); + } + + /* An end tag whose tag name is "select" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'select' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // w/e + + /* Otherwise: */ + } else { + /* Pop elements from the stack of open elements until a select + element has been popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'select') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* A start tag whose tag name is "select" */ + } elseif ($token['name'] === 'select' && + $token['type'] === HTML5::STARTTAG + ) { + /* Parse error. Act as if the token had been an end tag with the + tag name "select" instead. */ + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + /* An end tag whose tag name is one of: "caption", "table", "tbody", + "tfoot", "thead", "tr", "td", "th" */ + } elseif (in_array( + $token['name'], + array( + 'caption', + 'table', + 'tbody', + 'tfoot', + 'thead', + 'tr', + 'td', + 'th' + ) + ) && $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. */ + // w/e + + /* If the stack of open elements has an element in table scope with + the same tag name as that of the token, then act as if an end tag + with the tag name "select" had been seen, and reprocess the token. + Otherwise, ignore the token. */ + if ($this->elementInScope($token['name'], true)) { + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + $this->mainPhase($token); + } + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterBody($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed if the insertion mode + was "in body". */ + $this->inBody($token); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the first element in the stack of open + elements (the html element), with the data attribute set to the + data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->stack[0]->appendChild($comment); + + /* An end tag with the tag name "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { + /* If the parser was originally created in order to handle the + setting of an element's innerHTML attribute, this is a parse error; + ignore the token. (The element will be an html element in this + case.) (innerHTML case) */ + + /* Otherwise, switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* Anything else */ + } else { + /* Parse error. Set the insertion mode to "in body" and reprocess + the token. */ + $this->mode = self::IN_BODY; + return $this->inBody($token); + } + } + + private function inFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::STARTTAG + ) { + $this->insertElement($token); + + /* An end tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::ENDTAG + ) { + /* If the current node is the root html element, then this is a + parse error; ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + } else { + /* Otherwise, pop the current node from the stack of open + elements. */ + array_pop($this->stack); + + /* If the parser was not originally created in order to handle + the setting of an element's innerHTML attribute (innerHTML case), + and the current node is no longer a frameset element, then change + the insertion mode to "after frameset". */ + $this->mode = self::AFTR_FRAME; + } + + /* A start tag with the tag name "frame" */ + } elseif ($token['name'] === 'frame' && + $token['type'] === HTML5::STARTTAG + ) { + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* An end tag with the tag name "html" */ + } elseif ($token['name'] === 'html' && + $token['type'] === HTML5::ENDTAG + ) { + /* Switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function trailingEndPhase($token) + { + /* After the main phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed in the main phase. */ + $this->mainPhase($token); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or a start tag token. Or an end tag token. */ + } elseif (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. Switch back to the main phase and reprocess the + token. */ + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* OMG DONE!! */ + } + } + + private function insertElement($token, $append = true, $check = false) + { + // Proprietary workaround for libxml2's limitations with tag names + if ($check) { + // Slightly modified HTML5 tag-name modification, + // removing anything that's not an ASCII letter, digit, or hyphen + $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); + // Remove leading hyphens and numbers + $token['name'] = ltrim($token['name'], '-0..9'); + // In theory, this should ever be needed, but just in case + if ($token['name'] === '') { + $token['name'] = 'span'; + } // arbitrary generic choice + } + + $el = $this->dom->createElement($token['name']); + + foreach ($token['attr'] as $attr) { + if (!$el->hasAttribute($attr['name'])) { + $el->setAttribute($attr['name'], (string)$attr['value']); + } + } + + $this->appendToRealParent($el); + $this->stack[] = $el; + + return $el; + } + + private function insertText($data) + { + $text = $this->dom->createTextNode($data); + $this->appendToRealParent($text); + } + + private function insertComment($data) + { + $comment = $this->dom->createComment($data); + $this->appendToRealParent($comment); + } + + private function appendToRealParent($node) + { + if ($this->foster_parent === null) { + end($this->stack)->appendChild($node); + + } elseif ($this->foster_parent !== null) { + /* If the foster parent element is the parent element of the + last table element in the stack of open elements, then the new + node must be inserted immediately before the last table element + in the stack of open elements in the foster parent element; + otherwise, the new node must be appended to the foster parent + element. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table' && + $this->stack[$n]->parentNode !== null + ) { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $this->foster_parent->isSameNode($table->parentNode)) { + $this->foster_parent->insertBefore($node, $table); + } else { + $this->foster_parent->appendChild($node); + } + + $this->foster_parent = null; + } + } + + private function elementInScope($el, $table = false) + { + if (is_array($el)) { + foreach ($el as $element) { + if ($this->elementInScope($element, $table)) { + return true; + } + } + + return false; + } + + $leng = count($this->stack); + + for ($n = 0; $n < $leng; $n++) { + /* 1. Initialise node to be the current node (the bottommost node of + the stack). */ + $node = $this->stack[$leng - 1 - $n]; + + if ($node->tagName === $el) { + /* 2. If node is the target node, terminate in a match state. */ + return true; + + } elseif ($node->tagName === 'table') { + /* 3. Otherwise, if node is a table element, terminate in a failure + state. */ + return false; + + } elseif ($table === true && in_array( + $node->tagName, + array( + 'caption', + 'td', + 'th', + 'button', + 'marquee', + 'object' + ) + ) + ) { + /* 4. Otherwise, if the algorithm is the "has an element in scope" + variant (rather than the "has an element in table scope" variant), + and node is one of the following, terminate in a failure state. */ + return false; + + } elseif ($node === $node->ownerDocument->documentElement) { + /* 5. Otherwise, if node is an html element (root element), terminate + in a failure state. (This can only happen if the node is the topmost + node of the stack of open elements, and prevents the next step from + being invoked if there are no more elements in the stack.) */ + return false; + } + + /* Otherwise, set node to the previous entry in the stack of open + elements and return to step 2. (This will never fail, since the loop + will always terminate in the previous step if the top of the stack + is reached.) */ + } + } + + private function reconstructActiveFormattingElements() + { + /* 1. If there are no entries in the list of active formatting elements, + then there is nothing to reconstruct; stop this algorithm. */ + $formatting_elements = count($this->a_formatting); + + if ($formatting_elements === 0) { + return false; + } + + /* 3. Let entry be the last (most recently added) element in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. If the last (most recently added) entry in the list of active + formatting elements is a marker, or if it is an element that is in the + stack of open elements, then there is nothing to reconstruct; stop this + algorithm. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + return false; + } + + for ($a = $formatting_elements - 1; $a >= 0; true) { + /* 4. If there are no entries before entry in the list of active + formatting elements, then jump to step 8. */ + if ($a === 0) { + $step_seven = false; + break; + } + + /* 5. Let entry be the entry one earlier than entry in the list of + active formatting elements. */ + $a--; + $entry = $this->a_formatting[$a]; + + /* 6. If entry is neither a marker nor an element that is also in + thetack of open elements, go to step 4. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + break; + } + } + + while (true) { + /* 7. Let entry be the element one later than entry in the list of + active formatting elements. */ + if (isset($step_seven) && $step_seven === true) { + $a++; + $entry = $this->a_formatting[$a]; + } + + /* 8. Perform a shallow clone of the element entry to obtain clone. */ + $clone = $entry->cloneNode(); + + /* 9. Append clone to the current node and push it onto the stack + of open elements so that it is the new current node. */ + end($this->stack)->appendChild($clone); + $this->stack[] = $clone; + + /* 10. Replace the entry for entry in the list with an entry for + clone. */ + $this->a_formatting[$a] = $clone; + + /* 11. If the entry for clone in the list of active formatting + elements is not the last entry in the list, return to step 7. */ + if (end($this->a_formatting) !== $clone) { + $step_seven = true; + } else { + break; + } + } + } + + private function clearTheActiveFormattingElementsUpToTheLastMarker() + { + /* When the steps below require the UA to clear the list of active + formatting elements up to the last marker, the UA must perform the + following steps: */ + + while (true) { + /* 1. Let entry be the last (most recently added) entry in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. Remove entry from the list of active formatting elements. */ + array_pop($this->a_formatting); + + /* 3. If entry was a marker, then stop the algorithm at this point. + The list has been cleared up to the last marker. */ + if ($entry === self::MARKER) { + break; + } + } + } + + private function generateImpliedEndTags($exclude = array()) + { + /* When the steps below require the UA to generate implied end tags, + then, if the current node is a dd element, a dt element, an li element, + a p element, a td element, a th element, or a tr element, the UA must + act as if an end tag with the respective tag name had been seen and + then generate implied end tags again. */ + $node = end($this->stack); + $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); + + while (in_array(end($this->stack)->nodeName, $elements)) { + array_pop($this->stack); + } + } + + private function getElementCategory($node) + { + $name = $node->tagName; + if (in_array($name, $this->special)) { + return self::SPECIAL; + } elseif (in_array($name, $this->scoping)) { + return self::SCOPING; + } elseif (in_array($name, $this->formatting)) { + return self::FORMATTING; + } else { + return self::PHRASING; + } + } + + private function clearStackToTableContext($elements) + { + /* When the steps above require the UA to clear the stack back to a + table context, it means that the UA must, while the current node is not + a table element or an html element, pop elements from the stack of open + elements. If this causes any elements to be popped from the stack, then + this is a parse error. */ + while (true) { + $node = end($this->stack)->nodeName; + + if (in_array($node, $elements)) { + break; + } else { + array_pop($this->stack); + } + } + } + + private function resetInsertionMode() + { + /* 1. Let last be false. */ + $last = false; + $leng = count($this->stack); + + for ($n = $leng - 1; $n >= 0; $n--) { + /* 2. Let node be the last node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 3. If node is the first node in the stack of open elements, then + set last to true. If the element whose innerHTML attribute is being + set is neither a td element nor a th element, then set node to the + element whose innerHTML attribute is being set. (innerHTML case) */ + if ($this->stack[0]->isSameNode($node)) { + $last = true; + } + + /* 4. If node is a select element, then switch the insertion mode to + "in select" and abort these steps. (innerHTML case) */ + if ($node->nodeName === 'select') { + $this->mode = self::IN_SELECT; + break; + + /* 5. If node is a td or th element, then switch the insertion mode + to "in cell" and abort these steps. */ + } elseif ($node->nodeName === 'td' || $node->nodeName === 'th') { + $this->mode = self::IN_CELL; + break; + + /* 6. If node is a tr element, then switch the insertion mode to + "in row" and abort these steps. */ + } elseif ($node->nodeName === 'tr') { + $this->mode = self::IN_ROW; + break; + + /* 7. If node is a tbody, thead, or tfoot element, then switch the + insertion mode to "in table body" and abort these steps. */ + } elseif (in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { + $this->mode = self::IN_TBODY; + break; + + /* 8. If node is a caption element, then switch the insertion mode + to "in caption" and abort these steps. */ + } elseif ($node->nodeName === 'caption') { + $this->mode = self::IN_CAPTION; + break; + + /* 9. If node is a colgroup element, then switch the insertion mode + to "in column group" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'colgroup') { + $this->mode = self::IN_CGROUP; + break; + + /* 10. If node is a table element, then switch the insertion mode + to "in table" and abort these steps. */ + } elseif ($node->nodeName === 'table') { + $this->mode = self::IN_TABLE; + break; + + /* 11. If node is a head element, then switch the insertion mode + to "in body" ("in body"! not "in head"!) and abort these steps. + (innerHTML case) */ + } elseif ($node->nodeName === 'head') { + $this->mode = self::IN_BODY; + break; + + /* 12. If node is a body element, then switch the insertion mode to + "in body" and abort these steps. */ + } elseif ($node->nodeName === 'body') { + $this->mode = self::IN_BODY; + break; + + /* 13. If node is a frameset element, then switch the insertion + mode to "in frameset" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'frameset') { + $this->mode = self::IN_FRAME; + break; + + /* 14. If node is an html element, then: if the head element + pointer is null, switch the insertion mode to "before head", + otherwise, switch the insertion mode to "after head". In either + case, abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'html') { + $this->mode = ($this->head_pointer === null) + ? self::BEFOR_HEAD + : self::AFTER_HEAD; + + break; + + /* 15. If last is true, then set the insertion mode to "in body" + and abort these steps. (innerHTML case) */ + } elseif ($last) { + $this->mode = self::IN_BODY; + break; + } + } + } + + private function closeCell() + { + /* If the stack of open elements has a td or th element in table scope, + then act as if an end tag token with that tag name had been seen. */ + foreach (array('td', 'th') as $cell) { + if ($this->elementInScope($cell, true)) { + $this->inCell( + array( + 'name' => $cell, + 'type' => HTML5::ENDTAG + ) + ); + + break; + } + } + } + + public function save() + { + return $this->dom; + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php new file mode 100644 index 0000000..3995fec --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php @@ -0,0 +1,49 @@ +data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null); + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php new file mode 100644 index 0000000..6cbf56d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php @@ -0,0 +1,59 @@ + 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); + } + } +} + diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php new file mode 100644 index 0000000..aec9166 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php @@ -0,0 +1,54 @@ +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); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php new file mode 100644 index 0000000..18c8bbb --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php @@ -0,0 +1,111 @@ +preserve[$i] = true; + } + for ($i = 65; $i <= 90; $i++) { // upper-case + $this->preserve[$i] = true; + } + for ($i = 97; $i <= 122; $i++) { // lower-case + $this->preserve[$i] = true; + } + $this->preserve[45] = true; // Dash - + $this->preserve[46] = true; // Period . + $this->preserve[95] = true; // Underscore _ + $this->preserve[126]= true; // Tilde ~ + + // extra letters not to escape + if ($preserve !== false) { + for ($i = 0, $c = strlen($preserve); $i < $c; $i++) { + $this->preserve[ord($preserve[$i])] = true; + } + } + } + + /** + * Our replacement for urlencode, it encodes all non-reserved characters, + * as well as any extra characters that were instructed to be preserved. + * @note + * Assumes that the string has already been normalized, making any + * and all percent escape sequences valid. Percents will not be + * re-escaped, regardless of their status in $preserve + * @param string $string String to be encoded + * @return string Encoded string. + */ + public function encode($string) + { + $ret = ''; + for ($i = 0, $c = strlen($string); $i < $c; $i++) { + if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) { + $ret .= '%' . sprintf('%02X', $int); + } else { + $ret .= $string[$i]; + } + } + return $ret; + } + + /** + * Fix up percent-encoding by decoding unreserved characters and normalizing. + * @warning This function is affected by $preserve, even though the + * usual desired behavior is for this not to preserve those + * characters. Be careful when reusing instances of PercentEncoder! + * @param string $string String to normalize + * @return string + */ + public function normalize($string) + { + if ($string == '') { + return ''; + } + $parts = explode('%', $string); + $ret = array_shift($parts); + foreach ($parts as $part) { + $length = strlen($part); + if ($length < 2) { + $ret .= '%25' . $part; + continue; + } + $encoding = substr($part, 0, 2); + $text = substr($part, 2); + if (!ctype_xdigit($encoding)) { + $ret .= '%25' . $part; + continue; + } + $int = hexdec($encoding); + if (isset($this->preserve[$int])) { + $ret .= chr($int) . $text; + continue; + } + $encoding = strtoupper($encoding); + $ret .= '%' . $encoding . $text; + } + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php new file mode 100644 index 0000000..549e4ce --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php @@ -0,0 +1,218 @@ +getAll(); + $context = new HTMLPurifier_Context(); + $this->generator = new HTMLPurifier_Generator($config, $context); + } + + /** + * Main function that renders object or aspect of that object + * @note Parameters vary depending on printer + */ + // function render() {} + + /** + * Returns a start tag + * @param string $tag Tag name + * @param array $attr Attribute array + * @return string + */ + protected function start($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) + ); + } + + /** + * Returns an end tag + * @param string $tag Tag name + * @return string + */ + protected function end($tag) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_End($tag) + ); + } + + /** + * Prints a complete element with content inside + * @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) + { + return $this->start($tag, $attr) . + ($escape ? $this->escape($contents) : $contents) . + $this->end($tag); + } + + /** + * @param string $tag + * @param array $attr + * @return string + */ + protected function elementEmpty($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Empty($tag, $attr) + ); + } + + /** + * @param string $text + * @return string + */ + protected function text($text) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Text($text) + ); + } + + /** + * Prints a simple key/value row in a table. + * @param string $name Key + * @param mixed $value Value + * @return string + */ + 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'); + } + + /** + * Escapes a string for HTML output. + * @param string $string String to escape + * @return string + */ + protected function escape($string) + { + $string = HTMLPurifier_Encoder::cleanUTF8($string); + $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); + return $string; + } + + /** + * Takes a list of strings and turns them into a single list + * @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'; + } + $ret = ''; + $i = count($array); + foreach ($array as $value) { + $i--; + $ret .= $value; + 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 object $obj Object to determine class of + * @param string $sec_prefix Further prefix to remove + * @return string + */ + protected function getClass($obj, $sec_prefix = '') + { + static $five = null; + if ($five === null) { + $five = version_compare(PHP_VERSION, '5', '>='); + } + $prefix = 'HTMLPurifier_' . $sec_prefix; + if (!$five) { + $prefix = strtolower($prefix); + } + $class = str_replace($prefix, '', get_class($obj)); + $lclass = strtolower($class); + $class .= '('; + switch ($lclass) { + case 'enum': + $values = array(); + foreach ($obj->valid_values as $value => $bool) { + $values[] = $value; + } + $class .= implode(', ', $values); + break; + case 'css_composite': + $values = array(); + foreach ($obj->defs as $def) { + $values[] = $this->getClass($def, $sec_prefix); + } + $class .= implode(', ', $values); + break; + case 'css_multiple': + $class .= $this->getClass($obj->single, $sec_prefix) . ', '; + $class .= $obj->max; + break; + case 'css_denyelementdecorator': + $class .= $this->getClass($obj->def, $sec_prefix) . ', '; + $class .= $obj->element; + break; + case 'css_importantdecorator': + $class .= $this->getClass($obj->def, $sec_prefix); + if ($obj->allow) { + $class .= ', !important'; + } + break; + } + $class .= ')'; + return $class; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php new file mode 100644 index 0000000..29505fe --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php @@ -0,0 +1,44 @@ +def = $config->getCSSDefinition(); + $ret = ''; + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + $ret .= $this->start('table'); + + $ret .= $this->element('caption', 'Properties ($info)'); + + $ret .= $this->start('thead'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Property', array('class' => 'heavy')); + $ret .= $this->element('th', 'Definition', array('class' => 'heavy', 'style' => 'width:auto;')); + $ret .= $this->end('tr'); + $ret .= $this->end('thead'); + + ksort($this->def->info); + foreach ($this->def->info as $property => $obj) { + $name = $this->getClass($obj, 'AttrDef_'); + $ret .= $this->row($property, $name); + } + + $ret .= $this->end('table'); + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css new file mode 100644 index 0000000..3ff1a88 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css @@ -0,0 +1,10 @@ + +.hp-config {} + +.hp-config tbody th {text-align:right; padding-right:0.5em;} +.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;} +.hp-config .namespace th {text-align:center;} +.hp-config .verbose {display:none;} +.hp-config .controls {text-align:center;} + +/* vim: et sw=4 sts=4 */ diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js new file mode 100644 index 0000000..cba00c9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js @@ -0,0 +1,5 @@ +function toggleWriteability(id_of_patient, checked) { + document.getElementById(id_of_patient).disabled = checked; +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php new file mode 100644 index 0000000..4c3ce17 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php @@ -0,0 +1,456 @@ +docURL = $doc_url; + $this->name = $name; + $this->compress = $compress; + // initialize sub-printers + $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default(); + $this->fields[HTMLPurifier_VarParser::C_BOOL] = new HTMLPurifier_Printer_ConfigForm_bool(); + } + + /** + * Sets default column and row size for textareas in sub-printers + * @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; + } + } + + /** + * Retrieves styling, in case it is not accessible by webserver + */ + 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() + { + return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js'); + } + + /** + * Returns HTML output for a configuration form + * @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 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) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + + $this->config = $config; + $this->genConfig = $gen_config; + $this->prepareGenerator($gen_config); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def); + $all = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $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->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->end('table'); + return $ret; + } + + /** + * Renders a single namespace + * @param $ns String namespace name + * @param array $directives array of directives to values + * @return string + */ + 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->end('tr'); + $ret .= $this->end('tbody'); + $ret .= $this->start('tbody'); + foreach ($directives as $directive => $value) { + $ret .= $this->start('tr'); + $ret .= $this->start('th'); + if ($this->docURL) { + $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; + } + + $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)); + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + } + $ret .= $this->end('tbody'); + return $ret; + } + +} + +/** + * Printer decorator for directives that accept null + */ +class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer +{ + /** + * Printer being decorated + * @type HTMLPurifier_Printer + */ + protected $obj; + + /** + * @param HTMLPurifier_Printer $obj Printer to decorate + */ + public function __construct($obj) + { + parent::__construct(); + $this->obj = $obj; + } + + /** + * @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]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + + $ret = ''; + $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Null/Disabled'); + $ret .= $this->end('label'); + $attr = array( + 'type' => 'checkbox', + 'value' => '1', + 'class' => 'null-toggle', + '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)"; + } + if ($value === null) { + $attr['checked'] = 'checked'; + } + $ret .= $this->elementEmpty('input', $attr); + $ret .= $this->text(' or '); + $ret .= $this->elementEmpty('br'); + $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config)); + return $ret; + } +} + +/** + * Swiss-army knife configuration form field printer + */ +class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer +{ + /** + * @type int + */ + public $cols = 18; + + /** + * @type int + */ + public $rows = 5; + + /** + * @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]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + // this should probably be split up a little + $ret = ''; + $def = $config->def->info["$ns.$directive"]; + if (is_int($def)) { + $type = abs($def); + } else { + $type = $def->type; + } + if (is_array($value)) { + switch ($type) { + case HTMLPurifier_VarParser::LOOKUP: + $array = $value; + $value = array(); + foreach ($array as $val => $b) { + $value[] = $val; + } + //TODO does this need a break? + case HTMLPurifier_VarParser::ALIST: + $value = implode(PHP_EOL, $value); + break; + case HTMLPurifier_VarParser::HASH: + $nvalue = ''; + foreach ($value as $i => $v) { + if (is_array($v)) { + // HACK + $v = implode(";", $v); + } + $nvalue .= "$i:$v" . PHP_EOL; + } + $value = $nvalue; + break; + default: + $value = ''; + } + } + if ($type === HTMLPurifier_VarParser::C_MIXED) { + return 'Not supported'; + $value = serialize($value); + } + $attr = array( + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:$ns.$directive" + ); + 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'; + } + $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) { + $attr['cols'] = $this->cols; + $attr['rows'] = $this->rows; + $ret .= $this->start('textarea', $attr); + $ret .= $this->text($value); + $ret .= $this->end('textarea'); + } else { + $attr['value'] = $value; + $attr['type'] = 'text'; + $ret .= $this->elementEmpty('input', $attr); + } + return $ret; + } +} + +/** + * Bool form field printer + */ +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]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + $ret = ''; + $ret .= $this->start('div', array('id' => "$name:$ns.$directive")); + + $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Yes'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:Yes_$ns.$directive", + 'value' => '1' + ); + 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")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' No'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:No_$ns.$directive", + 'value' => '0' + ); + if ($value === false) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + $ret .= $this->elementEmpty('input', $attr); + + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php new file mode 100644 index 0000000..ae86391 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php @@ -0,0 +1,324 @@ +config =& $config; + + $this->def = $config->getHTMLDefinition(); + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + + $ret .= $this->renderDoctype(); + $ret .= $this->renderEnvironment(); + $ret .= $this->renderContentSets(); + $ret .= $this->renderInfo(); + + $ret .= $this->end('div'); + + return $ret; + } + + /** + * Renders the Doctype table + * @return string + */ + protected function renderDoctype() + { + $doctype = $this->def->doctype; + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Doctype'); + $ret .= $this->row('Name', $doctype->name); + $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No'); + $ret .= $this->row('Default Modules', implode(', ', $doctype->modules)); + $ret .= $this->row('Default Tidy Modules', implode(', ', $doctype->tidyModules)); + $ret .= $this->end('table'); + return $ret; + } + + + /** + * Renders environment table, which is miscellaneous info + * @return string + */ + protected function renderEnvironment() + { + $def = $this->def; + + $ret = ''; + + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Environment'); + + $ret .= $this->row('Parent of fragment', $def->info_parent); + $ret .= $this->renderChildren($def->info_parent_def->child); + $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), 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->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->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->end('tr'); + + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Content Sets table + * @return string + */ + protected function renderContentSets() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Content Sets'); + foreach ($this->def->info_content_sets as $name => $lookup) { + $ret .= $this->heavyHeader($name); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($lookup)); + $ret .= $this->end('tr'); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Elements ($info) table + * @return string + */ + protected function renderInfo() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Elements ($info)'); + ksort($this->def->info); + $ret .= $this->heavyHeader('Allowed tags', 2); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2)); + $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->end('tr'); + $ret .= $this->start('tr'); + $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->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->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->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->end('tr'); + } + $ret .= $this->start('tr'); + $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)) { + $ret .= $this->row('Required attributes', $this->listify($def->required_attr)); + } + + $ret .= $this->renderChildren($def->child); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders a row describing the allowed children of an element + * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element + * @return string + */ + 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; + } + 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)), + 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 $array Tag lookup array in form of array('tagname' => true) + * @return string + */ + protected function listifyTagLookup($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $discard) { + if ($name !== '#PCDATA' && !isset($this->def->info[$name])) { + continue; + } + $list[] = $name; + } + return $this->listify($list); + } + + /** + * Listifies a list of objects by retrieving class names and internal state + * @param array $array List of objects + * @return string + * @todo Also add information about internal state + */ + protected function listifyObjectList($array) + { + ksort($array); + $list = array(); + foreach ($array as $obj) { + $list[] = $this->getClass($obj, 'AttrTransform_'); + } + return $this->listify($list); + } + + /** + * Listifies a hash of attributes to AttrDef classes + * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @return string + */ + protected function listifyAttr($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $obj) { + if ($obj === false) { + continue; + } + $list[] = "$name = " . $this->getClass($obj, 'AttrDef_') . ''; + } + return $this->listify($list); + } + + /** + * Creates a heavy header row + * @param string $text + * @param int $num + * @return string + */ + 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/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php new file mode 100644 index 0000000..189348f --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php @@ -0,0 +1,122 @@ +parent = $parent; + } + + /** + * Recursively retrieves the value for a key + * @param string $name + * @throws HTMLPurifier_Exception + */ + public function get($name) + { + if ($this->has($name)) { + return $this->data[$name]; + } + // possible performance bottleneck, convert to iterative if necessary + if ($this->parent) { + return $this->parent->get($name); + } + throw new HTMLPurifier_Exception("Key '$name' not found"); + } + + /** + * Sets the value of a key, for this plist + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * Returns true if a given key exists + * @param string $name + * @return bool + */ + public function has($name) + { + return array_key_exists($name, $this->data); + } + + /** + * Resets a value to the value of it's parent, usually the default. If + * no value is specified, the entire plist is reset. + * @param string $name + */ + public function reset($name = null) + { + if ($name == null) { + $this->data = array(); + } else { + unset($this->data[$name]); + } + } + + /** + * Squashes this property list and all of its property lists into a single + * array, and returns the array. This value is cached by default. + * @param bool $force If true, ignores the cache and regenerates the array. + * @return array + */ + public function squash($force = false) + { + if ($this->cache !== null && !$force) { + return $this->cache; + } + if ($this->parent) { + return $this->cache = array_merge($this->parent->squash($force), $this->data); + } else { + return $this->cache = $this->data; + } + } + + /** + * Returns the parent plist. + * @return HTMLPurifier_PropertyList + */ + public function getParent() + { + return $this->parent; + } + + /** + * Sets the parent plist. + * @param HTMLPurifier_PropertyList $plist Parent plist + */ + public function setParent($plist) + { + $this->parent = $plist; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php new file mode 100644 index 0000000..f68fc8c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php @@ -0,0 +1,43 @@ +l = strlen($filter); + $this->filter = $filter; + } + + /** + * @return bool + */ + #[\ReturnTypeWillChange] + public function accept() + { + $key = $this->getInnerIterator()->key(); + if (strncmp($key, $this->filter, $this->l) !== 0) { + return false; + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php new file mode 100644 index 0000000..f58db90 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php @@ -0,0 +1,56 @@ +input = $input; + $this->output = array(); + } + + /** + * Shifts an element off the front of the queue. + */ + public function shift() { + if (empty($this->output)) { + $this->output = array_reverse($this->input); + $this->input = array(); + } + if (empty($this->output)) { + return NULL; + } + return array_pop($this->output); + } + + /** + * Pushes an element onto the front of the queue. + */ + public function push($x) { + array_push($this->input, $x); + } + + /** + * Checks if it's empty. + */ + public function isEmpty() { + return empty($this->input) && empty($this->output); + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php new file mode 100644 index 0000000..e1ff3b7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php @@ -0,0 +1,26 @@ +strategies as $strategy) { + $tokens = $strategy->execute($tokens, $config, $context); + } + return $tokens; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php new file mode 100644 index 0000000..4414c17 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php @@ -0,0 +1,17 @@ +strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements(); + $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->strategies[] = new HTMLPurifier_Strategy_FixNesting(); + $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php new file mode 100644 index 0000000..6fa673d --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php @@ -0,0 +1,181 @@ +getHTMLDefinition(); + + $excludes_enabled = !$config->get('Core.DisableExcludes'); + + // 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 + // branch of the document tree + $is_inline = $definition->info_parent_def->descendants_are_inline; + $context->register('IsInline', $is_inline); + + // setup error collector + $e =& $context->get('ErrorCollector', true); + + //####################################################################// + // Loop initialization + + // 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($definition->info_parent_def->excludes); + + // variable that contains the start token while we are processing + // nodes. This enables error reporting to do its job + $node = $top_node; + // dummy token + list($token, $d) = $node->toTokenPair(); + $context->register('CurrentNode', $node); + $context->register('CurrentToken', $token); + + //####################################################################// + // Loop + + // 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) + ); + + 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; + } + }; + 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 { + // 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; + } + $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 { + $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'); + } + } + } + } + } + + //####################################################################// + // Post-processing + + // remove context variables + $context->destroy('IsInline'); + $context->destroy('CurrentNode'); + $context->destroy('CurrentToken'); + + //####################################################################// + // Return + + return HTMLPurifier_Arborize::flatten($node, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php new file mode 100644 index 0000000..a6eb09e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php @@ -0,0 +1,659 @@ +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 = $definition->info_parent_def->child->getAllowedElements($config); + $e = $context->get('ErrorCollector', true); + $i = false; // injector index + 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->tokens =& $tokens; + $this->token =& $token; + $this->zipper =& $zipper; + $this->config = $config; + $this->context = $context; + + // context variables + $context->register('CurrentNesting', $stack); + $context->register('InputZipper', $zipper); + $context->register('CurrentToken', $token); + + // -- begin INJECTOR -- + + $this->injectors = array(); + + $injectors = $config->getBatch('AutoFormat'); + $def_injectors = $definition->info_injector; + $custom_injectors = $injectors['Custom']; + 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; + } + $injector = "HTMLPurifier_Injector_$injector"; + if (!$b) { + continue; + } + $this->injectors[] = new $injector; + } + foreach ($def_injectors as $injector) { + // assumed to be objects + $this->injectors[] = $injector; + } + foreach ($custom_injectors as $injector) { + if (!$injector) { + continue; + } + if (is_string($injector)) { + $injector = "HTMLPurifier_Injector_$injector"; + $injector = new $injector; + } + $this->injectors[] = $injector; + } + + // give the injectors references to the definition and context + // variables for performance reasons + foreach ($this->injectors as $ix => $injector) { + $error = $injector->prepare($config, $context); + 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); + } + + // -- end INJECTOR -- + + // a note on reprocessing: + // In order to reduce code duplication, whenever some code needs + // to make HTML changes in order to make things "correct", the + // new HTML gets sent through the purifier, regardless of its + // status. This means that if we add a start token, because it + // was totally necessary, we don't have to update nesting; we just + // punt ($reprocess = true; continue;) and it does that for us. + + // isset is in loop because $tokens size changes during loop exec + 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)) { + // 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_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. See Note [Injector skips] + 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 ($token === NULL) { + // kill processing if stack is empty + if (empty($this->stack)) { + break; + } + + // peek + $top_nesting = array_pop($this->stack); + $this->stack[] = $top_nesting; + + // send error [TagClosedSuppress] + if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); + } + + // append, don't splice, since this is the end + $token = new HTMLPurifier_Token_End($top_nesting->name); + + // punt! + $reprocess = true; + continue; + } + + //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])) { + // See Note [Injector skips] + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + // XXX fuckup + $r = $token; + $injector->handleText($r); + $token = $this->processToken($r, $i); + $reprocess = true; + break; + } + } + // another possibility is a comment + continue; + } + + if (isset($definition->info[$token->name])) { + $type = $definition->info[$token->name]->child->type; + } else { + $type = false; // Type is unknown, treat accordingly + } + + // quick tag checks: anything that's *not* an end tag + $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 + ); + $ok = true; + } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { + // claims to be empty but really is a start tag + // 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; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // real empty token + $ok = true; + } elseif ($token instanceof HTMLPurifier_Token_Start) { + // start tag + + // ...unless they also have to close their parent + if (!empty($this->stack)) { + + // Performance note: you might think that it's rather + // inefficient, recalculating the autoclose information + // for every tag that a token closes (since when we + // do an autoclose, we push a new token into the + // stream and then /process/ that, before + // re-processing this token.) But this is + // necessary, because an injector can make an + // arbitrary transformations to the autoclosing + // tokens we introduce, so things may have changed + // in the meantime. Also, doing the inefficient thing is + // "easy" to reason about (for certain perverse definitions + // of "easy") + + $parent = array_pop($this->stack); + $this->stack[] = $parent; + + $parent_def = null; + $parent_elements = null; + $autoclose = false; + if (isset($definition->info[$parent->name])) { + $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 + // example,
      needs a
    • in between) + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $elements = $wrapdef->child->getAllowedElements($config); + if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { + $newtoken = new HTMLPurifier_Token_Start($wrapname); + $token = $this->insertBefore($newtoken); + $reprocess = true; + continue; + } + } + + $carryover = false; + if ($autoclose && $parent_def->formatting) { + $carryover = true; + } + + if ($autoclose) { + // check if this autoclose is doomed to fail + // (this rechecks $parent, which his harmless) + $autoclose_ok = isset($global_parent_allowed_elements[$token->name]); + if (!$autoclose_ok) { + foreach ($this->stack as $ancestor) { + $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config); + if (isset($elements[$token->name])) { + $autoclose_ok = true; + break; + } + if ($definition->info[$token->name]->wrap) { + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $wrap_elements = $wrapdef->child->getAllowedElements($config); + if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) { + $autoclose_ok = true; + break; + } + } + } + } + if ($autoclose_ok) { + // errors need to be updated + $new_token = new HTMLPurifier_Token_End($parent->name); + $new_token->start = $parent; + // [TagClosedSuppress] + if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { + if (!$carryover) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); + } else { + $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 { + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + } + $ok = true; + } + + if ($ok) { + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + // See Note [Injector skips] + 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 + 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' + ); + } + } + continue; + } + + // sanity check: we should be dealing with a closing tag + if (!$token instanceof HTMLPurifier_Token_End) { + throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier'); + } + + // 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'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // first, check for the simplest case: everything closes neatly. + // Eventually, everything passes through here; if there are problems + // we modify the input stream accordingly and then punt, so that + // the tokens get processed again. + $current_parent = array_pop($this->stack); + if ($current_parent->name == $token->name) { + $token->start = $current_parent; + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + // See Note [Injector skips] + 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; + } + continue; + } + + // okay, so we're trying to close the wrong tag + + // undo the pop previous pop + $this->stack[] = $current_parent; + + // scroll back the entire nest, trying to find our tag. + // (feature could be to specify how far you'd like to go) + $size = count($this->stack); + // -2 because -1 is the last element, but we already checked that + $skipped_tags = false; + for ($j = $size - 2; $j >= 0; $j--) { + if ($this->stack[$j]->name == $token->name) { + $skipped_tags = array_slice($this->stack, $j); + break; + } + } + + // we didn't find the tag, so remove + if ($skipped_tags === false) { + if ($escape_invalid_tags) { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // do errors, in REVERSE $j order: a,b,c with + $c = count($skipped_tags); + if ($e) { + for ($j = $c - 1; $j > 0; $j--) { + // notice we exclude $j == 0, i.e. the current ending tag, from + // the errors... [TagClosedSuppress] + if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]); + } + } + } + + // insert tags, in FORWARD $j order: c,b,a with + $replace = array($token); + for ($j = 1; $j < $c; $j++) { + // ...as well as from the insertions + $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name); + $new_token->start = $skipped_tags[$j]; + array_unshift($replace, $new_token); + if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) { + // [TagClosedAuto] + $element = clone $skipped_tags[$j]; + $element->carryover = true; + $element->armor['MakeWellFormed_TagClosedError'] = true; + $replace[] = $element; + } + } + $token = $this->processToken($replace); + $reprocess = true; + continue; + } + + $context->destroy('CurrentToken'); + $context->destroy('CurrentNesting'); + $context->destroy('InputZipper'); + + unset($this->injectors, $this->stack, $this->tokens); + return $zipper->toArray($token); + } + + /** + * Processes arbitrary token values for complicated substitution patterns. + * In general: + * + * If $token is an array, it is a list of tokens to substitute for the + * current token. These tokens then get individually processed. If there + * is a leading integer in the list, that integer determines how many + * tokens from the stream should be removed. + * + * If $token is a regular token, it is swapped with the current token. + * + * If $token is false, the current token is deleted. + * + * If $token is an integer, that number of tokens (with the first token + * being the current one) will be deleted. + * + * @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) + { + // Zend OpCache miscompiles $token = array($token), so + // avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108 + + // normalize forms of token + if (is_object($token)) { + $tmp = $token; + $token = array(1, $tmp); + } + if (is_int($token)) { + $tmp = $token; + $token = array($tmp); + } + 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); + list($old, $r) = $this->zipper->splice($this->token, $delete, $token); + + if ($injector > -1) { + // See Note [Injector skips] + // Determine appropriate skips. Here's what the code does: + // *If* we deleted one or more tokens, copy the skips + // of those tokens into the skips of the new tokens (in $token). + // Also, mark the newly inserted tokens as having come from + // $injector. + $oldskip = isset($old[0]) ? $old[0]->skip : array(); + foreach ($token as $object) { + $object->skip = $oldskip; + $object->skip[$injector] = true; + } + } + + 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) + { + // 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() + { + return $this->zipper->delete(); + } +} + +// Note [Injector skips] +// ~~~~~~~~~~~~~~~~~~~~~ +// When I originally designed this class, the idea behind the 'skip' +// property of HTMLPurifier_Token was to help avoid infinite loops +// in injector processing. For example, suppose you wrote an injector +// that bolded swear words. Naively, you might write it so that +// whenever you saw ****, you replaced it with ****. +// +// When this happens, we will reprocess all of the tokens with the +// other injectors. Now there is an opportunity for infinite loop: +// if we rerun the swear-word injector on these tokens, we might +// see **** and then reprocess again to get +// **** ad infinitum. +// +// Thus, the idea of a skip is that once we process a token with +// an injector, we mark all of those tokens as having "come from" +// the injector, and we never run the injector again on these +// tokens. +// +// There were two more complications, however: +// +// - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if +// you had , after you removed the , you +// really would like this injector to go back and reprocess +// the tag, discovering that it is now empty and can be +// removed. So we reintroduced the possibility of infinite looping +// by adding a "rewind" function, which let you go back to an +// earlier point in the token stream and reprocess it with injectors. +// Needless to say, we need to UN-skip the token so it gets +// reprocessed. +// +// - Suppose that you successfuly process a token, replace it with +// one with your skip mark, but now another injector wants to +// process the skipped token with another token. Should you continue +// to skip that new token, or reprocess it? If you reprocess, +// you can end up with an infinite loop where one injector converts +// to , and then another injector converts it back. So +// we inherit the skips, but for some reason, I thought that we +// should inherit the skip from the first token of the token +// that we deleted. Why? Well, it seems to work OK. +// +// If I were to redesign this functionality, I would absolutely not +// go about doing it this way: the semantics are just not very well +// defined, and in any case you probably wanted to operate on trees, +// not token streams. + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php new file mode 100644 index 0000000..1a8149e --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php @@ -0,0 +1,207 @@ +getHTMLDefinition(); + $generator = new HTMLPurifier_Generator($config, $context); + $result = array(); + + $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); + + // currently only used to determine if comments should be kept + $trusted = $config->get('HTML.Trusted'); + $comment_lookup = $config->get('HTML.AllowedComments'); + $comment_regexp = $config->get('HTML.AllowedCommentsRegexp'); + $check_comments = $comment_lookup !== array() || $comment_regexp !== null; + + $remove_script_contents = $config->get('Core.RemoveScriptContents'); + $hidden_elements = $config->get('Core.HiddenElements'); + + // remove script contents compatibility + if ($remove_script_contents === true) { + $hidden_elements['script'] = true; + } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) { + unset($hidden_elements['script']); + } + + $attr_validator = new HTMLPurifier_AttrValidator(); + + // removes tokens until it reaches a closing tag with its value + $remove_until = false; + + // converts comments into text tokens when this is equal to a tag name + $textify_comments = false; + + $token = false; + $context->register('CurrentToken', $token); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + foreach ($tokens as $token) { + if ($remove_until) { + if (empty($token->is_tag) || $token->name !== $remove_until) { + continue; + } + } + if (!empty($token->is_tag)) { + // DEFINITION CALL + + // before any processing, try to transform the element + 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); + } + } + + 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) && + $definition->info[$token->name]->required_attr && + ($token->name != 'img' || $remove_invalid_img) // ensure config option still works + ) { + $attr_validator->validateToken($token, $config, $context); + $ok = true; + foreach ($definition->info[$token->name]->required_attr as $name) { + if (!isset($token->attr[$name])) { + $ok = false; + break; + } + } + if (!$ok) { + if ($e) { + $e->send( + E_ERROR, + 'Strategy_RemoveForeignElements: Missing required attribute', + $name + ); + } + continue; + } + $token->armor['ValidateAttributes'] = true; + } + + if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) { + $textify_comments = $token->name; + } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) { + $textify_comments = false; + } + + } elseif ($escape_invalid_tags) { + // invalid tag, generate HTML representation and insert in + if ($e) { + $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + } + $token = new HTMLPurifier_Token_Text( + $generator->generateFromToken($token) + ); + } else { + // check if we need to destroy all of the tag's children + // CAN BE GENERICIZED + if (isset($hidden_elements[$token->name])) { + if ($token instanceof HTMLPurifier_Token_Start) { + $remove_until = $token->name; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // do nothing: we're still looking + } else { + $remove_until = false; + } + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + } + } else { + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + } + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Comment) { + // textify comments in script tags when they are allowed + if ($textify_comments !== false) { + $data = $token->data; + $token = new HTMLPurifier_Token_Text($data); + } elseif ($trusted || $check_comments) { + // always cleanup comments + $trailing_hyphen = false; + if ($e) { + // perform check whether or not there's a trailing hyphen + if (substr($token->data, -1) == '-') { + $trailing_hyphen = true; + } + } + $token->data = rtrim($token->data, '-'); + $found_double_hyphen = false; + while (strpos($token->data, '--') !== false) { + $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)))) { + // OK good + if ($e) { + if ($trailing_hyphen) { + $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'); + } + } + } else { + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } else { + // strip comments + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Text) { + } else { + continue; + } + $result[] = $token; + } + if ($remove_until && $e) { + // 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; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php new file mode 100644 index 0000000..fbb3d27 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php @@ -0,0 +1,45 @@ +register('CurrentToken', $token); + + foreach ($tokens as $key => $token) { + + // only process tokens that have attributes, + // namely start and empty tags + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { + continue; + } + + // skip tokens that are armored + if (!empty($token->armor['ValidateAttributes'])) { + continue; + } + + // note that we have no facilities here for removing tokens + $validator->validateToken($token, $config, $context); + } + $context->destroy('CurrentToken'); + return $tokens; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php new file mode 100644 index 0000000..c41ae3a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php @@ -0,0 +1,48 @@ +accessed[$index] = true; + return parent::offsetGet($index); + } + + /** + * Returns a lookup array of all array indexes that have been accessed. + * @return array in form array($index => true). + */ + public function getAccessed() + { + return $this->accessed; + } + + /** + * Resets the access array. + */ + public function resetAccessed() + { + $this->accessed = array(); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php new file mode 100644 index 0000000..7c73f80 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php @@ -0,0 +1,136 @@ + 'DefaultKeyValue', + * 'KEY' => 'Value', + * 'KEY2' => 'Value2', + * 'MULTILINE-KEY' => "Multiline\nvalue.\n", + * ) + * + * We use this as an easy to use file-format for configuration schema + * files, but the class itself is usage agnostic. + * + * You can use ---- to forcibly terminate parsing of a single string-hash; + * this marker is used in multi string-hashes to delimit boundaries. + */ +class HTMLPurifier_StringHashParser +{ + + /** + * @type string + */ + public $default = 'ID'; + + /** + * Parses a file that contains a single string-hash. + * @param string $file + * @return array + */ + public function parseFile($file) + { + if (!file_exists($file)) { + return false; + } + $fh = fopen($file, 'r'); + if (!$fh) { + return false; + } + $ret = $this->parseHandle($fh); + fclose($fh); + return $ret; + } + + /** + * Parses a file that contains multiple string-hashes delimited by '----' + * @param string $file + * @return array + */ + public function parseMultiFile($file) + { + if (!file_exists($file)) { + return false; + } + $ret = array(); + $fh = fopen($file, 'r'); + if (!$fh) { + return false; + } + while (!feof($fh)) { + $ret[] = $this->parseHandle($fh); + } + fclose($fh); + return $ret; + } + + /** + * Internal parser that acepts a file handle. + * @note While it's possible to simulate in-memory parsing by using + * custom stream wrappers, if such a use-case arises we should + * factor out the file handle into its own class. + * @param resource $fh File handle with pointer at start of valid string-hash + * block. + * @return array + */ + protected function parseHandle($fh) + { + $state = false; + $single = false; + $ret = array(); + do { + $line = fgets($fh); + if ($line === false) { + break; + } + $line = rtrim($line, "\n\r"); + if (!$state && $line === '') { + continue; + } + if ($line === '----') { + break; + } + if (strncmp('--#', $line, 3) === 0) { + // Comment + continue; + } elseif (strncmp('--', $line, 2) === 0) { + // Multiline declaration + $state = trim($line, '- '); + if (!isset($ret[$state])) { + $ret[$state] = ''; + } + continue; + } elseif (!$state) { + $single = true; + if (strpos($line, ':') !== false) { + // Single-line declaration + list($state, $line) = explode(':', $line, 2); + $line = trim($line); + } else { + // Use default declaration + $state = $this->default; + } + } + if ($single) { + $ret[$state] = $line; + $single = false; + $state = false; + } else { + $ret[$state] .= "$line\n"; + } + } while (!feof($fh)); + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php new file mode 100644 index 0000000..7b8d833 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php @@ -0,0 +1,37 @@ + 'xx-small', + '1' => 'xx-small', + '2' => 'small', + '3' => 'medium', + '4' => 'large', + '5' => 'x-large', + '6' => 'xx-large', + '7' => '300%', + '-1' => 'smaller', + '-2' => '60%', + '+1' => 'larger', + '+2' => '150%', + '+3' => '200%', + '+4' => '300%' + ); + + /** + * @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; + return $new_tag; + } + + $attr = $tag->attr; + $prepend_style = ''; + + // handle color transform + if (isset($attr['color'])) { + $prepend_style .= 'color:' . $attr['color'] . ';'; + unset($attr['color']); + } + + // handle face transform + if (isset($attr['face'])) { + $prepend_style .= 'font-family:' . $attr['face'] . ';'; + unset($attr['face']); + } + + // handle size transform + if (isset($attr['size'])) { + // 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'; + } + } else { + $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']] . ';'; + } + unset($attr['size']); + } + + if ($prepend_style) { + $attr['style'] = isset($attr['style']) ? + $prepend_style . $attr['style'] : + $prepend_style; + } + + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + $new_tag->attr = $attr; + + return $new_tag; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php new file mode 100644 index 0000000..71bf10b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php @@ -0,0 +1,44 @@ +transform_to = $transform_to; + $this->style = $style; + } + + /** + * @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) && + ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty) + ) { + $this->prependCSS($new_tag->attr, $this->style); + } + return $new_tag; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php new file mode 100644 index 0000000..84d3619 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php @@ -0,0 +1,100 @@ +line = $l; + $this->col = $c; + } + + /** + * Convenience function for DirectLex settings line/col position. + * @param int $l + * @param int $c + */ + public function rawPosition($l, $c) + { + if ($c === -1) { + $l++; + } + $this->line = $l; + $this->col = $c; + } + + /** + * Converts a token into its corresponding node. + */ + abstract public function toNode(); +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php new file mode 100644 index 0000000..23453c7 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php @@ -0,0 +1,38 @@ +data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php new file mode 100644 index 0000000..78a95f5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php @@ -0,0 +1,15 @@ +empty = true; + return $n; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php new file mode 100644 index 0000000..59b38fd --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php @@ -0,0 +1,24 @@ +toNode not supported!"); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php new file mode 100644 index 0000000..019f317 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php @@ -0,0 +1,10 @@ +!empty($obj->is_tag) + * without having to use a function call is_a(). + * @type bool + */ + public $is_tag = true; + + /** + * 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 tag's attributes. + * @type array + */ + public $attr = array(); + + /** + * Non-overloaded constructor, which lower-cases passed tag name. + * + * @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 + if (!ctype_lower($key)) { + $new_key = strtolower($key); + if (!isset($attr[$new_key])) { + $attr[$new_key] = $attr[$key]; + } + if ($new_key !== $key) { + unset($attr[$key]); + } + } + } + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toNode() { + return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php new file mode 100644 index 0000000..f26a1c2 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php @@ -0,0 +1,53 @@ +data = $data; + $this->is_whitespace = ctype_space($data); + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php new file mode 100644 index 0000000..dea2446 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php @@ -0,0 +1,118 @@ +p_start = new HTMLPurifier_Token_Start('', array()); + $this->p_end = new HTMLPurifier_Token_End(''); + $this->p_empty = new HTMLPurifier_Token_Empty('', array()); + $this->p_text = new HTMLPurifier_Token_Text(''); + $this->p_comment = new HTMLPurifier_Token_Comment(''); + } + + /** + * Creates a HTMLPurifier_Token_Start. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start + */ + public function createStart($name, $attr = array()) + { + $p = clone $this->p_start; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_End. + * @param string $name Tag name + * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End + */ + public function createEnd($name) + { + $p = clone $this->p_end; + $p->__construct($name); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Empty. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty + */ + public function createEmpty($name, $attr = array()) + { + $p = clone $this->p_empty; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Text. + * @param string $data Data of text token + * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text + */ + public function createText($data) + { + $p = clone $this->p_text; + $p->__construct($data); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Comment. + * @param string $data Data of comment token + * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment + */ + public function createComment($data) + { + $p = clone $this->p_comment; + $p->__construct($data); + return $p; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php new file mode 100644 index 0000000..9c5be39 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php @@ -0,0 +1,316 @@ +scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme); + $this->userinfo = $userinfo; + $this->host = $host; + $this->port = is_null($port) ? $port : (int)$port; + $this->path = $path; + $this->query = $query; + $this->fragment = $fragment; + } + + /** + * Retrieves a scheme object corresponding to the URI's scheme/default + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI + */ + public function getSchemeObj($config, $context) + { + $registry = HTMLPurifier_URISchemeRegistry::instance(); + if ($this->scheme !== null) { + $scheme_obj = $registry->getScheme($this->scheme, $config, $context); + if (!$scheme_obj) { + return false; + } // invalid scheme, clean it out + } else { + // no scheme: retrieve the default one + $def = $config->getDefinition('URI'); + $scheme_obj = $def->getDefaultScheme($config, $context); + if (!$scheme_obj) { + if ($def->defaultScheme !== null) { + // something funky happened to the default scheme object + trigger_error( + 'Default scheme object "' . $def->defaultScheme . '" was not readable', + E_USER_WARNING + ); + } // suppress error if it's null + return false; + } + } + return $scheme_obj; + } + + /** + * Generic validation method applicable for all schemes. May modify + * this URI in order to get it into a compliant form. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool True if validation/filtering succeeds, false if failure + */ + public function validate($config, $context) + { + // ABNF definitions from RFC 3986 + $chars_sub_delims = '!$&\'()*+,;='; + $chars_gen_delims = ':/?#[]@'; + $chars_pchar = $chars_sub_delims . ':@'; + + // validate host + if (!is_null($this->host)) { + $host_def = new HTMLPurifier_AttrDef_URI_Host(); + $this->host = $host_def->validate($this->host, $config, $context); + if ($this->host === false) { + $this->host = null; + } + } + + // validate scheme + // NOTE: It's not appropriate to check whether or not this + // scheme is in our registry, since a URIFilter may convert a + // URI that we don't allow into one we do. So instead, we just + // check if the scheme can be dropped because there is no host + // and it is our default scheme. + if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') { + // support for relative paths is pretty abysmal when the + // scheme is present, so axe it when possible + $def = $config->getDefinition('URI'); + if ($def->defaultScheme === $this->scheme) { + $this->scheme = null; + } + } + + // validate username + if (!is_null($this->userinfo)) { + $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':'); + $this->userinfo = $encoder->encode($this->userinfo); + } + + // validate port + if (!is_null($this->port)) { + if ($this->port < 1 || $this->port > 65535) { + $this->port = null; + } + } + + // validate path + $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/'); + if (!is_null($this->host)) { // this catches $this->host === '' + // path-abempty (hier and relative) + // http://www.example.com/my/path + // //www.example.com/my/path (looks odd, but works, and + // recognized by most browsers) + // (this set is valid or invalid on a scheme by scheme + // basis, so we'll deal with it later) + // file:///my/path + // ///my/path + $this->path = $segments_encoder->encode($this->path); + } elseif ($this->path !== '') { + if ($this->path[0] === '/') { + // path-absolute (hier and relative) + // http:/my/path + // /my/path + if (strlen($this->path) >= 2 && $this->path[1] === '/') { + // This could happen if both the host gets stripped + // out + // http://my/path + // //my/path + $this->path = ''; + } else { + $this->path = $segments_encoder->encode($this->path); + } + } elseif (!is_null($this->scheme)) { + // path-rootless (hier) + // http:my/path + // Short circuit evaluation means we don't need to check nz + $this->path = $segments_encoder->encode($this->path); + } else { + // path-noscheme (relative) + // my/path + // (once again, not checking nz) + $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@'); + $c = strpos($this->path, '/'); + if ($c !== false) { + $this->path = + $segment_nc_encoder->encode(substr($this->path, 0, $c)) . + $segments_encoder->encode(substr($this->path, $c)); + } else { + $this->path = $segment_nc_encoder->encode($this->path); + } + } + } else { + // path-empty (hier and relative) + $this->path = ''; // just to be safe + } + + // qf = query and fragment + $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?'); + + if (!is_null($this->query)) { + $this->query = $qf_encoder->encode($this->query); + } + + if (!is_null($this->fragment)) { + $this->fragment = $qf_encoder->encode($this->fragment); + } + return true; + } + + /** + * Convert URI back to string + * @return string URI appropriate for output + */ + public function toString() + { + // reconstruct authority + $authority = null; + // there is a rendering difference between a null authority + // (http:foo-bar) and an empty string authority + // (http:///foo-bar). + if (!is_null($this->host)) { + $authority = ''; + if (!is_null($this->userinfo)) { + $authority .= $this->userinfo . '@'; + } + $authority .= $this->host; + if (!is_null($this->port)) { + $authority .= ':' . $this->port; + } + } + + // Reconstruct the result + // One might wonder about parsing quirks from browsers after + // this reconstruction. Unfortunately, parsing behavior depends + // on what *scheme* was employed (file:///foo is handled *very* + // differently than http:///foo), so unfortunately we have to + // defer to the schemes to do the right thing. + $result = ''; + if (!is_null($this->scheme)) { + $result .= $this->scheme . ':'; + } + if (!is_null($authority)) { + $result .= '//' . $authority; + } + $result .= $this->path; + if (!is_null($this->query)) { + $result .= '?' . $this->query; + } + if (!is_null($this->fragment)) { + $result .= '#' . $this->fragment; + } + + return $result; + } + + /** + * Returns true if this URL might be considered a 'local' URL given + * the current context. This is true when the host is null, or + * when it matches the host supplied to the configuration. + * + * Note that this does not do any scheme checking, so it is mostly + * only appropriate for metadata that doesn't care about protocol + * security. isBenign is probably what you actually want. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isLocal($config, $context) + { + if ($this->host === null) { + return true; + } + $uri_def = $config->getDefinition('URI'); + if ($uri_def->host === $this->host) { + return true; + } + return false; + } + + /** + * Returns true if this URL should be considered a 'benign' URL, + * that is: + * + * - It is a local URL (isLocal), and + * - It has a equal or better level of security + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isBenign($config, $context) + { + if (!$this->isLocal($config, $context)) { + return false; + } + + $scheme_obj = $this->getSchemeObj($config, $context); + if (!$scheme_obj) { + return false; + } // conservative approach + + $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context); + if ($current_scheme_obj->secure) { + if (!$scheme_obj->secure) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php new file mode 100644 index 0000000..e0bd8bc --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php @@ -0,0 +1,112 @@ +registerFilter(new HTMLPurifier_URIFilter_DisableExternal()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist()); + $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe()); + $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute()); + $this->registerFilter(new HTMLPurifier_URIFilter_Munge()); + } + + public function registerFilter($filter) + { + $this->registeredFilters[$filter->name] = $filter; + } + + public function addFilter($filter, $config) + { + $r = $filter->prepare($config); + if ($r === false) return; // null is ok, for backwards compat + if ($filter->post) { + $this->postFilters[$filter->name] = $filter; + } else { + $this->filters[$filter->name] = $filter; + } + } + + protected function doSetup($config) + { + $this->setupMemberVariables($config); + $this->setupFilters($config); + } + + protected function setupFilters($config) + { + foreach ($this->registeredFilters as $name => $filter) { + if ($filter->always_load) { + $this->addFilter($filter, $config); + } else { + $conf = $config->get('URI.' . $name); + if ($conf !== false && $conf !== null) { + $this->addFilter($filter, $config); + } + } + } + unset($this->registeredFilters); + } + + protected function setupMemberVariables($config) + { + $this->host = $config->get('URI.Host'); + $base_uri = $config->get('URI.Base'); + if (!is_null($base_uri)) { + $parser = new HTMLPurifier_URIParser(); + $this->base = $parser->parse($base_uri); + $this->defaultScheme = $this->base->scheme; + if (is_null($this->host)) $this->host = $this->base->host; + } + if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme'); + } + + public function getDefaultScheme($config, $context) + { + return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context); + } + + public function filter(&$uri, $config, $context) + { + foreach ($this->filters as $name => $f) { + $result = $f->filter($uri, $config, $context); + if (!$result) return false; + } + return true; + } + + public function postFilter(&$uri, $config, $context) + { + foreach ($this->postFilters as $name => $f) { + $result = $f->filter($uri, $config, $context); + if (!$result) return false; + } + return true; + } + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php new file mode 100644 index 0000000..09724e9 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php @@ -0,0 +1,74 @@ +getDefinition('URI')->host; + if ($our_host !== null) { + $this->ourHostParts = array_reverse(explode('.', $our_host)); + } + } + + /** + * @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; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php new file mode 100644 index 0000000..c656216 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php @@ -0,0 +1,25 @@ +get('EmbeddedURI', true)) { + return true; + } + return parent::filter($uri, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php new file mode 100644 index 0000000..d5c412c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php @@ -0,0 +1,22 @@ +get('EmbeddedURI', true); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php new file mode 100644 index 0000000..32197c0 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php @@ -0,0 +1,46 @@ +blacklist = $config->get('URI.HostBlacklist'); + return true; + } + + /** + * @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 ($uri->host !== null && strpos($uri->host, $blacklisted_host_fragment) !== false) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php new file mode 100644 index 0000000..c507bbf --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php @@ -0,0 +1,158 @@ +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 + ); + return false; + } + $this->base->fragment = null; // fragment is invalid for base URI + $stack = explode('/', $this->base->path); + array_pop($stack); // discard last segment + $stack = $this->_collapseStack($stack); // do pre-parsing + $this->basePathStack = $stack; + return true; + } + + /** + * @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; + } + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + // scheme not recognized + return false; + } + if (!$scheme_obj->hierarchical) { + // non-hierarchal URI with explicit scheme, don't change + return true; + } + // special case: had a scheme but always is hierarchical and had no authority + } + if (!is_null($uri->host)) { + // network path, don't bother + return true; + } + if ($uri->path === '') { + $uri->path = $this->base->path; + } elseif ($uri->path[0] !== '/') { + // relative path, needs more complicated processing + $stack = explode('/', $uri->path); + $new_stack = array_merge($this->basePathStack, $stack); + if ($new_stack[0] !== '' && !is_null($this->base->host)) { + array_unshift($new_stack, ''); + } + $new_stack = $this->_collapseStack($new_stack); + $uri->path = implode('/', $new_stack); + } else { + // absolute path, but still we should collapse + $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path))); + } + // 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; + } + return true; + } + + /** + * Resolve dots and double-dots in a path stack + * @param array $stack + * @return array + */ + 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] == '..') { + if (!empty($result)) { + $segment = array_pop($result); + if ($segment === '' && empty($result)) { + // error case: attempted to back out too far: + // restore the leading slash + $result[] = ''; + } elseif ($segment === '..') { + $result[] = '..'; // cannot remove .. with .. + } + } else { + // relative path, preserve the double-dots + $result[] = '..'; + } + $is_folder = true; + continue; + } + if ($stack[$i] == '.') { + // silently absorb + $is_folder = true; + continue; + } + $result[] = $stack[$i]; + } + if ($is_folder) { + $result[] = ''; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php new file mode 100644 index 0000000..e1393de --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php @@ -0,0 +1,115 @@ +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; + } + + /** + * @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 + + $this->makeReplace($uri, $config, $context); + $this->replace = array_map('rawurlencode', $this->replace); + + $new_uri = strtr($this->target, $this->replace); + $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; + } + $uri = $new_uri; // overwrite + return true; + } + + /** + * @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; + $this->replace['%r'] = $context->get('EmbeddedURI', true) ?: ''; + $token = $context->get('CurrentToken', true) ?: ''; + $this->replace['%n'] = $token ? $token->name : ''; + $this->replace['%m'] = $context->get('CurrentAttr', true) ?: ''; + $this->replace['%p'] = $context->get('CurrentCSSProperty', true) ?: ''; + // not always available + if ($this->secretKey) { + $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php new file mode 100644 index 0000000..f609c47 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php @@ -0,0 +1,68 @@ +regexp = $config->get('URI.SafeIframeRegexp'); + return true; + } + + /** + * @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; + } + // check if the filter should actually trigger + if (!$context->get('EmbeddedURI', true)) { + return true; + } + $token = $context->get('CurrentToken', true); + if (!($token && $token->name == 'iframe')) { + return true; + } + // check if we actually have some whitelists enabled + if ($this->regexp === null) { + return false; + } + // actually check the whitelists + return preg_match($this->regexp, $uri->toString()); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php new file mode 100644 index 0000000..0e7381a --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php @@ -0,0 +1,71 @@ +percentEncoder = new HTMLPurifier_PercentEncoder(); + } + + /** + * Parses a URI. + * @param $uri string URI to parse + * @return HTMLPurifier_URI representation of URI. This representation has + * not been validated yet and may not conform to RFC. + */ + public function parse($uri) + { + $uri = $this->percentEncoder->normalize($uri); + + // Regexp is as per Appendix B. + // Note that ["<>] are an addition to the RFC's recommended + // characters, because they represent external delimeters. + $r_URI = '!'. + '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme + '(//([^/?#"<>]*))?'. // 4. Authority + '([^?#"<>]*)'. // 5. Path + '(\?([^#"<>]*))?'. // 7. Query + '(#([^"<>]*))?'. // 8. Fragment + '!'; + + $matches = array(); + $result = preg_match($r_URI, $uri, $matches); + + if (!$result) return false; // *really* invalid URI + + // seperate out parts + $scheme = !empty($matches[1]) ? $matches[2] : null; + $authority = !empty($matches[3]) ? $matches[4] : null; + $path = $matches[5]; // always present, can be empty + $query = !empty($matches[6]) ? $matches[7] : null; + $fragment = !empty($matches[8]) ? $matches[9] : null; + + // further parse authority + if ($authority !== null) { + $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/"; + $matches = array(); + preg_match($r_authority, $authority, $matches); + $userinfo = !empty($matches[1]) ? $matches[2] : null; + $host = !empty($matches[3]) ? $matches[3] : ''; + $port = !empty($matches[4]) ? (int) $matches[5] : null; + } else { + $port = $host = $userinfo = null; + } + + return new HTMLPurifier_URI( + $scheme, $userinfo, $host, $port, $path, $query, $fragment); + } + +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php new file mode 100644 index 0000000..fe9e82c --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php @@ -0,0 +1,102 @@ +, resolves edge cases + * with making relative URIs absolute + * @type bool + */ + public $hierarchical = false; + + /** + * Whether or not the URI may omit a hostname when the scheme is + * explicitly specified, ala file:///path/to/file. As of writing, + * 'file' is the only scheme that browsers support his properly. + * @type bool + */ + public $may_omit_host = false; + + /** + * Validates the components of a URI for a specific scheme. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + abstract public function doValidate(&$uri, $config, $context); + + /** + * Public interface for validating components of a URI. Performs a + * bunch of default actions. Don't overload this method. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + public function validate(&$uri, $config, $context) + { + if ($this->default_port == $uri->port) { + $uri->port = null; + } + // kludge: browsers do funny things when the scheme but not the + // authority is set + if (!$this->may_omit_host && + // if the scheme is present, a missing host is always in error + (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) || + // if the scheme is not present, a *blank* host is in error, + // since this translates into '///path' which most browsers + // interpret as being 'http://path'. + (is_null($uri->scheme) && $uri->host === '') + ) { + do { + if (is_null($uri->scheme)) { + if (substr($uri->path, 0, 2) != '//') { + $uri->host = null; + break; + } + // URI is '////path', so we cannot nullify the + // host to preserve semantics. Try expanding the + // hostname instead (fall through) + } + // first see if we can manually insert a hostname + $host = $config->get('URI.Host'); + if (!is_null($host)) { + $uri->host = $host; + } else { + // we can't do anything sensible, reject the URL. + return false; + } + } while (false); + } + return $this->doValidate($uri, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php new file mode 100644 index 0000000..41c49d5 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php @@ -0,0 +1,136 @@ + 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; + + /** + * @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; + $content_type = null; + if (count($result) == 2) { + list($metadata, $data) = $result; + // do some legwork on the metadata + $metas = explode(';', $metadata); + while (!empty($metas)) { + $cur = array_shift($metas); + if ($cur == 'base64') { + $is_base64 = true; + break; + } + if (substr($cur, 0, 8) == 'charset=') { + // doesn't match if there are arbitrary spaces, but + // whatever dude + if ($charset !== null) { + continue; + } // garbage + $charset = substr($cur, 8); // not used + } else { + if ($content_type !== null) { + continue; + } // garbage + $content_type = $cur; + } + } + } else { + $data = $result[0]; + } + if ($content_type !== null && empty($this->allowed_types[$content_type])) { + return false; + } + if ($charset !== null) { + // error; we don't allow plaintext stuff + $charset = null; + } + $data = rawurldecode($data); + if ($is_base64) { + $raw_data = base64_decode($data); + } else { + $raw_data = $data; + } + if ( strlen($raw_data) < 12 ) { + // error; exif_imagetype throws exception with small files, + // and this likely indicates a corrupt URI/failed parse anyway + return false; + } + // XXX probably want to refactor this into a general mechanism + // for filtering arbitrary content types + if (function_exists('sys_get_temp_dir')) { + $file = tempnam(sys_get_temp_dir(), ""); + } else { + $file = tempnam("/tmp", ""); + } + file_put_contents($file, $raw_data); + if (function_exists('exif_imagetype')) { + $image_code = exif_imagetype($file); + unlink($file); + } elseif (function_exists('getimagesize')) { + set_error_handler(array($this, 'muteErrorHandler')); + $info = getimagesize($file); + restore_error_handler(); + unlink($file); + if ($info == false) { + return false; + } + $image_code = $info[2]; + } else { + trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); + } + $real_content_type = image_type_to_mime_type($image_code); + 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; + } + $content_type = $real_content_type; + } + // ok, it's kosher, rewrite what we need + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->fragment = null; + $uri->query = null; + $uri->path = "$content_type;base64," . base64_encode($raw_data); + return true; + } + + /** + * @param int $errno + * @param string $errstr + */ + public function muteErrorHandler($errno, $errstr) + { + } +} diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php new file mode 100644 index 0000000..215be4b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php @@ -0,0 +1,44 @@ +userinfo = null; + // file:// makes no provisions for accessing the resource + $uri->port = null; + // While it seems to work on Firefox, the querystring has + // no possible effect and is thus stripped. + $uri->query = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php new file mode 100644 index 0000000..1eb43ee --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php @@ -0,0 +1,58 @@ +query = null; + + // typecode check + $semicolon_pos = strrpos($uri->path, ';'); // reverse + if ($semicolon_pos !== false) { + $type = substr($uri->path, $semicolon_pos + 1); // no semicolon + $uri->path = substr($uri->path, 0, $semicolon_pos); + $type_ret = ''; + if (strpos($type, '=') !== false) { + // figure out whether or not the declaration is correct + list($key, $typecode) = explode('=', $type, 2); + if ($key !== 'type') { + // invalid key, tack it back on encoded + $uri->path .= '%3B' . $type; + } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') { + $type_ret = ";type=$typecode"; + } + } else { + $uri->path .= '%3B' . $type; + } + $uri->path = str_replace(';', '%3B', $uri->path); + $uri->path .= $type_ret; + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php new file mode 100644 index 0000000..ce69ec4 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php @@ -0,0 +1,36 @@ +userinfo = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php new file mode 100644 index 0000000..0e96882 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php @@ -0,0 +1,18 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + // we need to validate path against RFC 2368's addr-spec + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php new file mode 100644 index 0000000..7490927 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php @@ -0,0 +1,35 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->query = null; + // typecode check needed on path + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php new file mode 100644 index 0000000..f211d71 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php @@ -0,0 +1,32 @@ +userinfo = null; + $uri->query = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php new file mode 100644 index 0000000..dfad8ef --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php @@ -0,0 +1,46 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + + // Delete all non-numeric characters, commas, and non-x characters + // from phone number, EXCEPT for a leading plus sign. + $uri->path = preg_replace('/(?!^\+)[^\dx,]/', '', + // Normalize e(x)tension to lower-case + str_replace('X', 'x', rawurldecode($uri->path))); + + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php new file mode 100644 index 0000000..4ac8a0b --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php @@ -0,0 +1,81 @@ +get('URI.AllowedSchemes'); + if (!$config->get('URI.OverrideAllowedSchemes') && + !isset($allowed_schemes[$scheme]) + ) { + return; + } + + if (isset($this->schemes[$scheme])) { + return $this->schemes[$scheme]; + } + if (!isset($allowed_schemes[$scheme])) { + return; + } + + $class = 'HTMLPurifier_URIScheme_' . $scheme; + if (!class_exists($class)) { + return; + } + $this->schemes[$scheme] = new $class(); + return $this->schemes[$scheme]; + } + + /** + * Registers a custom scheme to the cache, bypassing reflection. + * @param string $scheme Scheme name + * @param HTMLPurifier_URIScheme $scheme_obj + */ + public function register($scheme, $scheme_obj) + { + $this->schemes[$scheme] = $scheme_obj; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php new file mode 100644 index 0000000..b5a1eab --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php @@ -0,0 +1,307 @@ + array( + 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary + 'pt' => 4, + 'pc' => 48, + 'in' => 288, + self::METRIC => array('pt', '0.352777778', 'mm'), + ), + self::METRIC => array( + 'mm' => 1, + 'cm' => 10, + self::ENGLISH => array('mm', '2.83464567', 'pt'), + ), + ); + + /** + * Minimum bcmath precision for output. + * @type int + */ + protected $outputPrecision; + + /** + * Bcmath precision for internal calculations. + * @type int + */ + protected $internalPrecision; + + /** + * Whether or not BCMath is available. + * @type bool + */ + private $bcmath; + + public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false) + { + $this->outputPrecision = $output_precision; + $this->internalPrecision = $internal_precision; + $this->bcmath = !$force_no_bcmath && function_exists('bcmul'); + } + + /** + * Converts a length object of one unit into another unit. + * @param HTMLPurifier_Length $length + * Instance of HTMLPurifier_Length to convert. You must validate() + * it before passing it here! + * @param string $to_unit + * Unit to convert to. + * @return HTMLPurifier_Length|bool + * @note + * About precision: This conversion function pays very special + * attention to the incoming precision of values and attempts + * to maintain a number of significant figure. Results are + * fairly accurate up to nine digits. Some caveats: + * - If a number is zero-padded as a result of this significant + * figure tracking, the zeroes will be eliminated. + * - If a number contains less than four sigfigs ($outputPrecision) + * and this causes some decimals to be excluded, those + * decimals will be added on. + */ + public function convert($length, $to_unit) + { + if (!$length->isValid()) { + return false; + } + + $n = $length->getN(); + $unit = $length->getUnit(); + + if ($n === '0' || $unit === false) { + return new HTMLPurifier_Length('0', false); + } + + $state = $dest_state = false; + foreach (self::$units as $k => $x) { + if (isset($x[$unit])) { + $state = $k; + } + if (isset($x[$to_unit])) { + $dest_state = $k; + } + } + if (!$state || !$dest_state) { + return false; + } + + // Some calculations about the initial precision of the number; + // this will be useful when we need to do final rounding. + $sigfigs = $this->getSigFigs($n); + if ($sigfigs < $this->outputPrecision) { + $sigfigs = $this->outputPrecision; + } + + // BCMath's internal precision deals only with decimals. Use + // our default if the initial number has no decimals, or increase + // it by how ever many decimals, thus, the number of guard digits + // will always be greater than or equal to internalPrecision. + $log = (int)floor(log(abs($n), 10)); + $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision + + for ($i = 0; $i < 2; $i++) { + + // Determine what unit IN THIS SYSTEM we need to convert to + if ($dest_state === $state) { + // Simple conversion + $dest_unit = $to_unit; + } else { + // Convert to the smallest unit, pending a system shift + $dest_unit = self::$units[$state][$dest_state][0]; + } + + // Do the conversion if necessary + if ($dest_unit !== $unit) { + $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp); + $n = $this->mul($n, $factor, $cp); + $unit = $dest_unit; + } + + // Output was zero, so bail out early. Shouldn't ever happen. + if ($n === '') { + $n = '0'; + $unit = $to_unit; + break; + } + + // It was a simple conversion, so bail out + if ($dest_state === $state) { + break; + } + + if ($i !== 0) { + // Conversion failed! Apparently, the system we forwarded + // to didn't have this unit. This should never happen! + return false; + } + + // Pre-condition: $i == 0 + + // Perform conversion to next system of units + $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp); + $unit = self::$units[$state][$dest_state][2]; + $state = $dest_state; + + // One more loop around to convert the unit in the new system. + + } + + // Post-condition: $unit == $to_unit + if ($unit !== $to_unit) { + return false; + } + + // Useful for debugging: + //echo "
      n";
      +        //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n
      \n"; + + $n = $this->round($n, $sigfigs); + if (strpos($n, '.') !== false) { + $n = rtrim($n, '0'); + } + $n = rtrim($n, '.'); + + return new HTMLPurifier_Length($n, $unit); + } + + /** + * Returns the number of significant figures in a string number. + * @param string $n Decimal number + * @return int number of sigfigs + */ + public function getSigFigs($n) + { + $n = ltrim($n, '0+-'); + $dp = strpos($n, '.'); // decimal position + if ($dp === false) { + $sigfigs = strlen(rtrim($n, '0')); + } else { + $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character + if ($dp !== 0) { + $sigfigs--; + } + } + return $sigfigs; + } + + /** + * Adds two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function add($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcadd($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 + (float)$s2, $scale); + } + } + + /** + * Multiples two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function mul($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcmul($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 * (float)$s2, $scale); + } + } + + /** + * Divides two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function div($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcdiv($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 / (float)$s2, $scale); + } + } + + /** + * Rounds a number according to the number of sigfigs it should have, + * using arbitrary precision when available. + * @param float $n + * @param int $sigfigs + * @return string + */ + private function round($n, $sigfigs) + { + $new_log = (int)floor(log(abs((float)$n), 10)); // Number of digits left of decimal - 1 + $rp = $sigfigs - $new_log - 1; // Number of decimal places needed + $neg = $n < 0 ? '-' : ''; // Negative sign + if ($this->bcmath) { + if ($rp >= 0) { + $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1); + $n = bcdiv($n, '1', $rp); + } else { + // This algorithm partially depends on the standardized + // form of numbers that comes out of bcmath. + $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0); + $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1); + } + return $n; + } else { + return $this->scale(round((float)$n, $sigfigs - $new_log - 1), $rp + 1); + } + } + + /** + * Scales a float to $scale digits right of decimal point, like BCMath. + * @param float $r + * @param int $scale + * @return string + */ + private function scale($r, $scale) + { + if ($scale < 0) { + // The f sprintf type doesn't support negative numbers, so we + // need to cludge things manually. First get the string. + $r = sprintf('%.0f', (float)$r); + // Due to floating point precision loss, $r will more than likely + // look something like 4652999999999.9234. We grab one more digit + // than we need to precise from $r and then use that to round + // appropriately. + $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1); + // Now we return it, truncating the zero that was rounded off. + return substr($precise, 0, -1) . str_repeat('0', -$scale + 1); + } + return number_format((float)$r, $scale, '.', ''); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php new file mode 100644 index 0000000..0c97c82 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php @@ -0,0 +1,198 @@ + self::C_STRING, + 'istring' => self::ISTRING, + 'text' => self::TEXT, + 'itext' => self::ITEXT, + 'int' => self::C_INT, + 'float' => self::C_FLOAT, + 'bool' => self::C_BOOL, + 'lookup' => self::LOOKUP, + 'list' => self::ALIST, + 'hash' => self::HASH, + 'mixed' => self::C_MIXED + ); + + /** + * Lookup table of types that are string, and can have aliases or + * allowed value lists. + */ + public static $stringTypes = array( + self::C_STRING => true, + self::ISTRING => true, + self::TEXT => true, + self::ITEXT => true, + ); + + /** + * Validate a variable according to type. + * It may return NULL as a valid type if $allow_null is true. + * + * @param mixed $var Variable to validate + * @param int $type Type of variable, see HTMLPurifier_VarParser->types + * @param bool $allow_null Whether or not to permit null as a value + * @return string Validated and type-coerced variable + * @throws HTMLPurifier_VarParserException + */ + final public function parse($var, $type, $allow_null = false) + { + if (is_string($type)) { + if (!isset(HTMLPurifier_VarParser::$types[$type])) { + throw new HTMLPurifier_VarParserException("Invalid type '$type'"); + } else { + $type = HTMLPurifier_VarParser::$types[$type]; + } + } + $var = $this->parseImplementation($var, $type, $allow_null); + if ($allow_null && $var === null) { + return null; + } + // These are basic checks, to make sure nothing horribly wrong + // happened in our implementations. + switch ($type) { + case (self::C_STRING): + case (self::ISTRING): + case (self::TEXT): + case (self::ITEXT): + if (!is_string($var)) { + break; + } + if ($type == self::ISTRING || $type == self::ITEXT) { + $var = strtolower($var); + } + return $var; + case (self::C_INT): + if (!is_int($var)) { + break; + } + return $var; + case (self::C_FLOAT): + if (!is_float($var)) { + break; + } + return $var; + case (self::C_BOOL): + if (!is_bool($var)) { + break; + } + return $var; + case (self::LOOKUP): + case (self::ALIST): + case (self::HASH): + if (!is_array($var)) { + break; + } + if ($type === self::LOOKUP) { + foreach ($var as $k) { + if ($k !== true) { + $this->error('Lookup table contains value other than true'); + } + } + } elseif ($type === self::ALIST) { + $keys = array_keys($var); + if (array_keys($keys) !== $keys) { + $this->error('Indices for list are not uniform'); + } + } + return $var; + case (self::C_MIXED): + return $var; + default: + $this->errorInconsistent(get_class($this), $type); + } + $this->errorGeneric($var, $type); + } + + /** + * Actually implements the parsing. Base implementation does not + * do anything to $var. Subclasses should overload this! + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return string + */ + protected function parseImplementation($var, $type, $allow_null) + { + return $var; + } + + /** + * Throws an exception. + * @throws HTMLPurifier_VarParserException + */ + protected function error($msg) + { + throw new HTMLPurifier_VarParserException($msg); + } + + /** + * Throws an inconsistency exception. + * @note This should not ever be called. It would be called if we + * extend the allowed values of HTMLPurifier_VarParser without + * updating subclasses. + * @param string $class + * @param int $type + * @throws HTMLPurifier_Exception + */ + protected function errorInconsistent($class, $type) + { + throw new HTMLPurifier_Exception( + "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) . + " not implemented" + ); + } + + /** + * Generic error for if a type didn't work. + * @param mixed $var + * @param int $type + */ + protected function errorGeneric($var, $type) + { + $vtype = gettype($var); + $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype"); + } + + /** + * @param int $type + * @return string + */ + public static function getTypeName($type) + { + static $lookup; + if (!$lookup) { + // Lazy load the alternative lookup table + $lookup = array_flip(HTMLPurifier_VarParser::$types); + } + if (!isset($lookup[$type])) { + return 'unknown'; + } + return $lookup[$type]; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php new file mode 100644 index 0000000..3bfbe83 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php @@ -0,0 +1,130 @@ + $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; + } + $nvar[trim($c[0])] = trim($c[1]); + } + $var = $nvar; + } + } + if (!is_array($var)) { + break; + } + $keys = array_keys($var); + if ($keys === array_keys($keys)) { + if ($type == self::ALIST) { + return $var; + } elseif ($type == self::LOOKUP) { + $new = array(); + foreach ($var as $key) { + $new[$key] = true; + } + return $new; + } else { + break; + } + } + if ($type === self::ALIST) { + trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); + return array_values($var); + } + 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 + ); + } + $var[$key] = true; + } + } + return $var; + default: + $this->errorInconsistent(__CLASS__, $type); + } + $this->errorGeneric($var, $type); + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php new file mode 100644 index 0000000..f11c318 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php @@ -0,0 +1,38 @@ +evalExpression($var); + } + + /** + * @param string $expr + * @return mixed + * @throws HTMLPurifier_VarParserException + */ + protected function evalExpression($expr) + { + $var = null; + $result = eval("\$var = $expr;"); + if ($result === false) { + throw new HTMLPurifier_VarParserException("Fatal error in evaluated code"); + } + return $var; + } +} + +// vim: et sw=4 sts=4 diff --git a/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php new file mode 100644 index 0000000..5df3414 --- /dev/null +++ b/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php @@ -0,0 +1,11 @@ +front = $front; + $this->back = $back; + } + + /** + * Creates a zipper from an array, with a hole in the + * 0-index position. + * @param Array to zipper-ify. + * @return Tuple of zipper and element of first position. + */ + static public function fromArray($array) { + $z = new self(array(), array_reverse($array)); + $t = $z->delete(); // delete the "dummy hole" + return array($z, $t); + } + + /** + * Convert zipper back into a normal array, optionally filling in + * the hole with a value. (Usually you should supply a $t, unless you + * are at the end of the array.) + */ + public function toArray($t = NULL) { + $a = $this->front; + if ($t !== NULL) $a[] = $t; + for ($i = count($this->back)-1; $i >= 0; $i--) { + $a[] = $this->back[$i]; + } + return $a; + } + + /** + * Move hole to the next element. + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function next($t) { + if ($t !== NULL) array_push($this->front, $t); + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Iterated hole advancement. + * @param $t Element to fill hole with + * @param $i How many forward to advance hole + * @return Original contents of new hole, i away + */ + public function advance($t, $n) { + for ($i = 0; $i < $n; $i++) { + $t = $this->next($t); + } + return $t; + } + + /** + * Move hole to the previous element + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function prev($t) { + if ($t !== NULL) array_push($this->back, $t); + return empty($this->front) ? NULL : array_pop($this->front); + } + + /** + * Delete contents of current hole, shifting hole to + * next element. + * @return Original contents of new hole. + */ + public function delete() { + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Returns true if we are at the end of the list. + * @return bool + */ + public function done() { + return empty($this->back); + } + + /** + * Insert element before hole. + * @param Element to insert + */ + public function insertBefore($t) { + if ($t !== NULL) array_push($this->front, $t); + } + + /** + * Insert element after hole. + * @param Element to insert + */ + public function insertAfter($t) { + if ($t !== NULL) array_push($this->back, $t); + } + + /** + * Splice in multiple elements at hole. Functional specification + * in terms of array_splice: + * + * $arr1 = $arr; + * $old1 = array_splice($arr1, $i, $delete, $replacement); + * + * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr); + * $t = $z->advance($t, $i); + * list($old2, $t) = $z->splice($t, $delete, $replacement); + * $arr2 = $z->toArray($t); + * + * assert($old1 === $old2); + * assert($arr1 === $arr2); + * + * NB: the absolute index location after this operation is + * *unchanged!* + * + * @param Current contents of hole. + */ + public function splice($t, $delete, $replacement) { + // delete + $old = array(); + $r = $t; + for ($i = $delete; $i > 0; $i--) { + $old[] = $r; + $r = $this->delete(); + } + // insert + for ($i = count($replacement)-1; $i >= 0; $i--) { + $this->insertAfter($r); + $r = $replacement[$i]; + } + return array($old, $r); + } +} diff --git a/vendor/maennchen/zipstream-php/.github/FUNDING.yml b/vendor/maennchen/zipstream-php/.github/FUNDING.yml new file mode 100644 index 0000000..d807a3f --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: zipstream diff --git a/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..3c2e66d --- /dev/null +++ b/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,12 @@ +# Description of the problem + +Please be very descriptive and include as much details as possible. + +# Example code + +# Informations + +* ZipStream-PHP version: +* PHP version: + +Please include any supplemental information you deem relevant to this issue. diff --git a/vendor/maennchen/zipstream-php/.gitignore b/vendor/maennchen/zipstream-php/.gitignore new file mode 100644 index 0000000..5210738 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.gitignore @@ -0,0 +1,6 @@ +clover.xml +composer.lock +coverage.clover +.idea +phpunit.xml +vendor diff --git a/vendor/maennchen/zipstream-php/.travis.yml b/vendor/maennchen/zipstream-php/.travis.yml new file mode 100644 index 0000000..12c8a64 --- /dev/null +++ b/vendor/maennchen/zipstream-php/.travis.yml @@ -0,0 +1,12 @@ +language: php +dist: trusty +sudo: false +php: + - 7.1 + - 7.2 + - 7.3 +install: composer install +script: ./vendor/bin/phpunit --coverage-clover=coverage.clover +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/vendor/maennchen/zipstream-php/CHANGELOG.md b/vendor/maennchen/zipstream-php/CHANGELOG.md new file mode 100644 index 0000000..b1979ab --- /dev/null +++ b/vendor/maennchen/zipstream-php/CHANGELOG.md @@ -0,0 +1,51 @@ +# CHANGELOG for ZipStream-PHP + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [2.1.0] - 2020-06-01 +### Changed +- Don't execute ob_flush() when output buffering is not enabled (#152) +- Fix inconsistent return type on 32-bit systems (#149) Fix #144 +- Use mbstring polyfill (#151) +- Promote 7zip usage over unzip to avoid UTF-8 issues (#147) + +## [2.0.0] - 2020-02-22 +### Breaking change +- Only the self opened streams will be closed (#139) +If you were relying on ZipStream to close streams that the library didn't open, +you'll need to close them yourself now. + +### Changed +- Minor change to data descriptor (#136) + +## [1.2.0] - 2019-07-11 + +### Added +- Option to flush output buffer after every write (#122) + +## [1.1.0] - 2019-04-30 + +### Fixed +- Honor last-modified timestamps set via `ZipStream\Option\File::setTime()` (#106) +- Documentation regarding output of HTTP headers +- Test warnings with PHPUnit (#109) + +### Added +- Test for FileNotReadableException (#114) +- Size attribute to File options (#113) +- Tests on PHP 7.3 (#108) + +## [1.0.0] - 2019-04-17 + +### Breaking changes +- Mininum PHP version is now 7.1 +- Options are now passed to the ZipStream object via the Option\Archive object. See the wiki for available options and code examples + +### Added +- Add large file support with Zip64 headers + +### Changed +- Major refactoring and code cleanup diff --git a/vendor/maennchen/zipstream-php/CONTRIBUTING.md b/vendor/maennchen/zipstream-php/CONTRIBUTING.md new file mode 100644 index 0000000..f8ef5a5 --- /dev/null +++ b/vendor/maennchen/zipstream-php/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# ZipStream Readme for Contributors +## Code styling +### Indention +For spaces are used to indent code. The convention is [K&R](http://en.wikipedia.org/wiki/Indent_style#K&R) + +### Comments +Double Slashes are used for an one line comment. + +Classes, Variables, Methods etc: + +```php +/** + * My comment + * + * @myanotation like @param etc. + */ +``` + +## Pull requests +Feel free to submit pull requests. + +## Testing +For every new feature please write a new PHPUnit test. + +Before every commit execute `./vendor/bin/phpunit` to check if your changes wrecked something: diff --git a/vendor/maennchen/zipstream-php/LICENSE b/vendor/maennchen/zipstream-php/LICENSE new file mode 100644 index 0000000..ebe7fe2 --- /dev/null +++ b/vendor/maennchen/zipstream-php/LICENSE @@ -0,0 +1,24 @@ +MIT License + +Copyright (C) 2007-2009 Paul Duncan +Copyright (C) 2014 Jonatan Männchen +Copyright (C) 2014 Jesse G. Donat +Copyright (C) 2018 Nicolas CARPi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/maennchen/zipstream-php/README.md b/vendor/maennchen/zipstream-php/README.md new file mode 100644 index 0000000..c2e832b --- /dev/null +++ b/vendor/maennchen/zipstream-php/README.md @@ -0,0 +1,123 @@ +# ZipStream-PHP + +[![Build Status](https://travis-ci.org/maennchen/ZipStream-PHP.svg?branch=master)](https://travis-ci.org/maennchen/ZipStream-PHP) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/) +[![Code Coverage](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/) +[![Latest Stable Version](https://poser.pugx.org/maennchen/zipstream-php/v/stable)](https://packagist.org/packages/maennchen/zipstream-php) +[![Total Downloads](https://poser.pugx.org/maennchen/zipstream-php/downloads)](https://packagist.org/packages/maennchen/zipstream-php) +[![Financial Contributors on Open Collective](https://opencollective.com/zipstream/all/badge.svg?label=financial+contributors)](https://opencollective.com/zipstream) [![License](https://img.shields.io/github/license/maennchen/zipstream-php.svg)](LICENSE) + +## Overview + +A fast and simple streaming zip file downloader for PHP. Using this library will save you from having to write the Zip to disk. You can directly send it to the user, which is much faster. It can work with S3 buckets or any PSR7 Stream. + +Please see the [LICENSE](LICENSE) file for licensing and warranty information. + +## Installation + +Simply add a dependency on maennchen/zipstream-php to your project's composer.json file if you use Composer to manage the dependencies of your project. Use following command to add the package to your project's dependencies: + +```bash +composer require maennchen/zipstream-php +``` + +## Usage and options + +Here's a simple example: + +```php +// Autoload the dependencies +require 'vendor/autoload.php'; + +// enable output of HTTP headers +$options = new ZipStream\Option\Archive(); +$options->setSendHttpHeaders(true); + +// create a new zipstream object +$zip = new ZipStream\ZipStream('example.zip', $options); + +// create a file named 'hello.txt' +$zip->addFile('hello.txt', 'This is the contents of hello.txt'); + +// add a file named 'some_image.jpg' from a local file 'path/to/image.jpg' +$zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg'); + +// add a file named 'goodbye.txt' from an open stream resource +$fp = tmpfile(); +fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); +rewind($fp); +$zip->addFileFromStream('goodbye.txt', $fp); +fclose($fp); + +// finish the zip stream +$zip->finish(); +``` + +You can also add comments, modify file timestamps, and customize (or +disable) the HTTP headers. It is also possible to specify the storage method when adding files, +the current default storage method is 'deflate' i.e files are stored with Compression mode 0x08. + +See the [Wiki](https://github.com/maennchen/ZipStream-PHP/wiki) for details. + +## Known issue + +The native Mac OS archive extraction tool might not open archives in some conditions. A workaround is to disable the Zip64 feature with the option `$opt->setEnableZip64(false)`. This limits the archive to 4 Gb and 64k files but will allow Mac OS users to open them without issue. See #116. + +The linux `unzip` utility might not handle properly unicode characters. It is recommended to extract with another tool like [7-zip](https://www.7-zip.org/). See #146. + +## Upgrade to version 2.0.0 + +* Only the self opened streams will be closed (#139) +If you were relying on ZipStream to close streams that the library didn't open, +you'll need to close them yourself now. + +## Upgrade to version 1.0.0 + +* All options parameters to all function have been moved from an `array` to structured option objects. See [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Available-options) for examples. +* The whole library has been refactored. The minimal PHP requirement has been raised to PHP 7.1. + +## Usage with Symfony and S3 + +You can find example code on [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Symfony-example). + +## Contributing + +ZipStream-PHP is a collaborative project. Please take a look at the [CONTRIBUTING.md](CONTRIBUTING.md) file. + +## About the Authors + +* Paul Duncan - https://pablotron.org/ +* Jonatan Männchen - https://maennchen.dev +* Jesse G. Donat - https://donatstudios.com +* Nicolas CARPi - https://www.deltablot.com +* Nik Barham - https://www.brokencube.co.uk + +## Contributors + +### Code Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. +
      + +### Financial Contributors + +Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)] + +#### Individuals + + + +#### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/zipstream/contribute)] + + + + + + + + + + + diff --git a/vendor/maennchen/zipstream-php/composer.json b/vendor/maennchen/zipstream-php/composer.json new file mode 100644 index 0000000..103c78c --- /dev/null +++ b/vendor/maennchen/zipstream-php/composer.json @@ -0,0 +1,41 @@ +{ + "name": "maennchen/zipstream-php", + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": ["zip", "stream"], + "type": "library", + "license": "MIT", + "authors": [{ + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "require": { + "php": ">= 7.1", + "symfony/polyfill-mbstring": "^1.0", + "psr/http-message": "^1.0", + "myclabs/php-enum": "^1.5" + }, + "require-dev": { + "phpunit/phpunit": ">= 7.5", + "guzzlehttp/guzzle": ">= 6.3", + "ext-zip": "*", + "mikey179/vfsstream": "^1.6" + }, + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + } +} diff --git a/vendor/maennchen/zipstream-php/phpunit.xml.dist b/vendor/maennchen/zipstream-php/phpunit.xml.dist new file mode 100644 index 0000000..f6e7227 --- /dev/null +++ b/vendor/maennchen/zipstream-php/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + test + + + + + + + + + + src + + + diff --git a/vendor/maennchen/zipstream-php/psalm.xml b/vendor/maennchen/zipstream-php/psalm.xml new file mode 100644 index 0000000..42f355b --- /dev/null +++ b/vendor/maennchen/zipstream-php/psalm.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/maennchen/zipstream-php/src/Bigint.php b/vendor/maennchen/zipstream-php/src/Bigint.php new file mode 100644 index 0000000..52ccfd2 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Bigint.php @@ -0,0 +1,172 @@ +fillBytes($value, 0, 8); + } + + /** + * Fill the bytes field with int + * + * @param int $value + * @param int $start + * @param int $count + * @return void + */ + protected function fillBytes(int $value, int $start, int $count): void + { + for ($i = 0; $i < $count; $i++) { + $this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF; + $value >>= 8; + } + } + + /** + * Get an instance + * + * @param int $value + * @return Bigint + */ + public static function init(int $value = 0): self + { + return new self($value); + } + + /** + * Fill bytes from low to high + * + * @param int $low + * @param int $high + * @return Bigint + */ + public static function fromLowHigh(int $low, int $high): self + { + $bigint = new Bigint(); + $bigint->fillBytes($low, 0, 4); + $bigint->fillBytes($high, 4, 4); + return $bigint; + } + + /** + * Get high 32 + * + * @return int + */ + public function getHigh32(): int + { + return $this->getValue(4, 4); + } + + /** + * Get value from bytes array + * + * @param int $end + * @param int $length + * @return int + */ + public function getValue(int $end = 0, int $length = 8): int + { + $result = 0; + for ($i = $end + $length - 1; $i >= $end; $i--) { + $result <<= 8; + $result |= $this->bytes[$i]; + } + return $result; + } + + /** + * Get low FF + * + * @param bool $force + * @return float + */ + public function getLowFF(bool $force = false): float + { + if ($force || $this->isOver32()) { + return (float)0xFFFFFFFF; + } + return (float)$this->getLow32(); + } + + /** + * Check if is over 32 + * + * @param bool $force + * @return bool + */ + public function isOver32(bool $force = false): bool + { + // value 0xFFFFFFFF already needs a Zip64 header + return $force || + max(array_slice($this->bytes, 4, 4)) > 0 || + min(array_slice($this->bytes, 0, 4)) === 0xFF; + } + + /** + * Get low 32 + * + * @return int + */ + public function getLow32(): int + { + return $this->getValue(0, 4); + } + + /** + * Get hexadecimal + * + * @return string + */ + public function getHex64(): string + { + $result = '0x'; + for ($i = 7; $i >= 0; $i--) { + $result .= sprintf('%02X', $this->bytes[$i]); + } + return $result; + } + + /** + * Add + * + * @param Bigint $other + * @return Bigint + */ + public function add(Bigint $other): Bigint + { + $result = clone $this; + $overflow = false; + for ($i = 0; $i < 8; $i++) { + $result->bytes[$i] += $other->bytes[$i]; + if ($overflow) { + $result->bytes[$i]++; + $overflow = false; + } + if ($result->bytes[$i] & 0x100) { + $overflow = true; + $result->bytes[$i] &= 0xFF; + } + } + if ($overflow) { + throw new OverflowException; + } + return $result; + } +} diff --git a/vendor/maennchen/zipstream-php/src/DeflateStream.php b/vendor/maennchen/zipstream-php/src/DeflateStream.php new file mode 100644 index 0000000..d6c2728 --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/DeflateStream.php @@ -0,0 +1,70 @@ +filter) { + $this->removeDeflateFilter(); + $this->seek(0); + $this->addDeflateFilter($this->options); + } else { + rewind($this->stream); + } + } + + /** + * Remove the deflate filter + * + * @return void + */ + public function removeDeflateFilter(): void + { + if (!$this->filter) { + return; + } + stream_filter_remove($this->filter); + $this->filter = null; + } + + /** + * Add a deflate filter + * + * @param Option\File $options + * @return void + */ + public function addDeflateFilter(Option\File $options): void + { + $this->options = $options; + // parameter 4 for stream_filter_append expects array + // so we convert the option object in an array + $optionsArr = [ + 'comment' => $options->getComment(), + 'method' => $options->getMethod(), + 'deflateLevel' => $options->getDeflateLevel(), + 'time' => $options->getTime() + ]; + $this->filter = stream_filter_append( + $this->stream, + 'zlib.deflate', + STREAM_FILTER_READ, + $optionsArr + ); + } +} diff --git a/vendor/maennchen/zipstream-php/src/Exception.php b/vendor/maennchen/zipstream-php/src/Exception.php new file mode 100644 index 0000000..18ccfbb --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Exception.php @@ -0,0 +1,11 @@ +zip = $zip; + + $this->name = $name; + $this->opt = $opt ?: new FileOptions(); + $this->method = $this->opt->getMethod(); + $this->version = Version::STORE(); + $this->ofs = new Bigint(); + } + + public function processPath(string $path): void + { + if (!is_readable($path)) { + if (!file_exists($path)) { + throw new FileNotFoundException($path); + } + throw new FileNotReadableException($path); + } + if ($this->zip->isLargeFile($path) === false) { + $data = file_get_contents($path); + $this->processData($data); + } else { + $this->method = $this->zip->opt->getLargeFileMethod(); + + $stream = new DeflateStream(fopen($path, 'rb')); + $this->processStream($stream); + $stream->close(); + } + } + + public function processData(string $data): void + { + $this->len = new Bigint(strlen($data)); + $this->crc = crc32($data); + + // compress data if needed + if ($this->method->equals(Method::DEFLATE())) { + $data = gzdeflate($data); + } + + $this->zlen = new Bigint(strlen($data)); + $this->addFileHeader(); + $this->zip->send($data); + $this->addFileFooter(); + } + + /** + * Create and send zip header for this file. + * + * @return void + * @throws \ZipStream\Exception\EncodingException + */ + public function addFileHeader(): void + { + $name = static::filterFilename($this->name); + + // calculate name length + $nameLength = strlen($name); + + // create dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); + + $comment = $this->opt->getComment(); + + if (!mb_check_encoding($name, 'ASCII') || + !mb_check_encoding($comment, 'ASCII')) { + // Sets Bit 11: Language encoding flag (EFS). If this bit is set, + // the filename and comment fields for this file + // MUST be encoded using UTF-8. (see APPENDIX D) + if (!mb_check_encoding($name, 'UTF-8') || + !mb_check_encoding($comment, 'UTF-8')) { + throw new EncodingException( + 'File name and comment should use UTF-8 ' . + 'if one of them does not fit into ASCII range.' + ); + } + $this->bits |= self::BIT_EFS_UTF8; + } + + if ($this->method->equals(Method::DEFLATE())) { + $this->version = Version::DEFLATE(); + } + + $force = (boolean)($this->bits & self::BIT_ZERO_HEADER) && + $this->zip->opt->isEnableZip64(); + + $footer = $this->buildZip64ExtraBlock($force); + + // If this file will start over 4GB limit in ZIP file, + // CDR record will have to use Zip64 extension to describe offset + // to keep consistency we use the same value here + if ($this->zip->ofs->isOver32()) { + $this->version = Version::ZIP64(); + } + + $fields = [ + ['V', ZipStream::FILE_HEADER_SIGNATURE], + ['v', $this->version->getValue()], // Version needed to Extract + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 of data (0 -> moved to data descriptor footer) + ['V', $this->zlen->getLowFF($force)], // Length of compressed data (forced to 0xFFFFFFFF for zero header) + ['V', $this->len->getLowFF($force)], // Length of original data (forced to 0xFFFFFFFF for zero header) + ['v', $nameLength], // Length of filename + ['v', strlen($footer)], // Extra data (see above) + ]; + + // pack fields and calculate "total" length + $header = ZipStream::packFields($fields); + + // print header and filename + $data = $header . $name . $footer; + $this->zip->send($data); + + // save header length + $this->hlen = Bigint::init(strlen($data)); + } + + /** + * Strip characters that are not legal in Windows filenames + * to prevent compatibility issues + * + * @param string $filename Unprocessed filename + * @return string + */ + public static function filterFilename(string $filename): string + { + // strip leading slashes from file name + // (fixes bug in windows archive viewer) + $filename = preg_replace('/^\\/+/', '', $filename); + + return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename); + } + + /** + * Convert a UNIX timestamp to a DOS timestamp. + * + * @param int $when + * @return int DOS Timestamp + */ + final protected static function dosTime(int $when): int + { + // get date array for timestamp + $d = getdate($when); + + // set lower-bound on dates + if ($d['year'] < 1980) { + $d = array( + 'year' => 1980, + 'mon' => 1, + 'mday' => 1, + 'hours' => 0, + 'minutes' => 0, + 'seconds' => 0 + ); + } + + // remove extra years from 1980 + $d['year'] -= 1980; + + // return date string + return + ($d['year'] << 25) | + ($d['mon'] << 21) | + ($d['mday'] << 16) | + ($d['hours'] << 11) | + ($d['minutes'] << 5) | + ($d['seconds'] >> 1); + } + + protected function buildZip64ExtraBlock(bool $force = false): string + { + + $fields = []; + if ($this->len->isOver32($force)) { + $fields[] = ['P', $this->len]; // Length of original data + } + + if ($this->len->isOver32($force)) { + $fields[] = ['P', $this->zlen]; // Length of compressed data + } + + if ($this->ofs->isOver32()) { + $fields[] = ['P', $this->ofs]; // Offset of local header record + } + + if (!empty($fields)) { + if (!$this->zip->opt->isEnableZip64()) { + throw new OverflowException(); + } + + array_unshift( + $fields, + ['v', 0x0001], // 64 bit extension + ['v', count($fields) * 8] // Length of data block + ); + $this->version = Version::ZIP64(); + } + + return ZipStream::packFields($fields); + } + + /** + * Create and send data descriptor footer for this file. + * + * @return void + */ + + public function addFileFooter(): void + { + + if ($this->bits & self::BIT_ZERO_HEADER) { + // compressed and uncompressed size + $sizeFormat = 'V'; + if ($this->zip->opt->isEnableZip64()) { + $sizeFormat = 'P'; + } + $fields = [ + ['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE], + ['V', $this->crc], // CRC32 + [$sizeFormat, $this->zlen], // Length of compressed data + [$sizeFormat, $this->len], // Length of original data + ]; + + $footer = ZipStream::packFields($fields); + $this->zip->send($footer); + } else { + $footer = ''; + } + $this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer))); + $this->zip->addToCdr($this); + } + + public function processStream(StreamInterface $stream): void + { + $this->zlen = new Bigint(); + $this->len = new Bigint(); + + if ($this->zip->opt->isZeroHeader()) { + $this->processStreamWithZeroHeader($stream); + } else { + $this->processStreamWithComputedHeader($stream); + } + } + + protected function processStreamWithZeroHeader(StreamInterface $stream): void + { + $this->bits |= self::BIT_ZERO_HEADER; + $this->addFileHeader(); + $this->readStream($stream, self::COMPUTE | self::SEND); + $this->addFileFooter(); + } + + protected function readStream(StreamInterface $stream, ?int $options = null): void + { + $this->deflateInit(); + $total = 0; + $size = $this->opt->getSize(); + while (!$stream->eof() && ($size === 0 || $total < $size)) { + $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); + $total += strlen($data); + if ($size > 0 && $total > $size) { + $data = substr($data, 0 , strlen($data)-($total - $size)); + } + $this->deflateData($stream, $data, $options); + if ($options & self::SEND) { + $this->zip->send($data); + } + } + $this->deflateFinish($options); + } + + protected function deflateInit(): void + { + $this->hash = hash_init(self::HASH_ALGORITHM); + if ($this->method->equals(Method::DEFLATE())) { + $this->deflate = deflate_init( + ZLIB_ENCODING_RAW, + ['level' => $this->opt->getDeflateLevel()] + ); + } + } + + protected function deflateData(StreamInterface $stream, string &$data, ?int $options = null): void + { + if ($options & self::COMPUTE) { + $this->len = $this->len->add(Bigint::init(strlen($data))); + hash_update($this->hash, $data); + } + if ($this->deflate) { + $data = deflate_add( + $this->deflate, + $data, + $stream->eof() + ? ZLIB_FINISH + : ZLIB_NO_FLUSH + ); + } + if ($options & self::COMPUTE) { + $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); + } + } + + protected function deflateFinish(?int $options = null): void + { + if ($options & self::COMPUTE) { + $this->crc = hexdec(hash_final($this->hash)); + } + } + + protected function processStreamWithComputedHeader(StreamInterface $stream): void + { + $this->readStream($stream, self::COMPUTE); + $stream->rewind(); + + // incremental compression with deflate_add + // makes this second read unnecessary + // but it is only available from PHP 7.0 + if (!$this->deflate && $stream instanceof DeflateStream && $this->method->equals(Method::DEFLATE())) { + $stream->addDeflateFilter($this->opt); + $this->zlen = new Bigint(); + while (!$stream->eof()) { + $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); + $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); + } + $stream->rewind(); + } + + $this->addFileHeader(); + $this->readStream($stream, self::SEND); + $this->addFileFooter(); + } + + /** + * Send CDR record for specified file. + * + * @return string + */ + public function getCdrFile(): string + { + $name = static::filterFilename($this->name); + + // get attributes + $comment = $this->opt->getComment(); + + // get dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); + + $footer = $this->buildZip64ExtraBlock(); + + $fields = [ + ['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature + ['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version + ['v', $this->version->getValue()], // Extract by version + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 + ['V', $this->zlen->getLowFF()], // Compressed Data Length + ['V', $this->len->getLowFF()], // Original Data Length + ['v', strlen($name)], // Length of filename + ['v', strlen($footer)], // Extra data len (see above) + ['v', strlen($comment)], // Length of comment + ['v', 0], // Disk number + ['v', 0], // Internal File Attributes + ['V', 32], // External File Attributes + ['V', $this->ofs->getLowFF()] // Relative offset of local header + ]; + + // pack fields, then append name and comment + $header = ZipStream::packFields($fields); + + return $header . $name . $footer . $comment; + } + + /** + * @return Bigint + */ + public function getTotalLength(): Bigint + { + return $this->totalLength; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Option/Archive.php b/vendor/maennchen/zipstream-php/src/Option/Archive.php new file mode 100644 index 0000000..b6b95cc --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/Archive.php @@ -0,0 +1,261 @@ + 4 GB or file count > 64k) + * + * @var bool + */ + private $enableZip64 = true; + /** + * Enable streaming files with single read where + * general purpose bit 3 indicates local file header + * contain zero values in crc and size fields, + * these appear only after file contents + * in data descriptor block. + * + * @var bool + */ + private $zeroHeader = false; + /** + * Enable reading file stat for determining file size. + * When a 32-bit system reads file size that is + * over 2 GB, invalid value appears in file size + * due to integer overflow. Should be disabled on + * 32-bit systems with method addFileFromPath + * if any file may exceed 2 GB. In this case file + * will be read in blocks and correct size will be + * determined from content. + * + * @var bool + */ + private $statFiles = true; + /** + * Enable flush after every write to output stream. + * @var bool + */ + private $flushOutput = false; + /** + * HTTP Content-Disposition. Defaults to + * 'attachment', where + * FILENAME is the specified filename. + * + * Note that this does nothing if you are + * not sending HTTP headers. + * + * @var string + */ + private $contentDisposition = 'attachment'; + /** + * Note that this does nothing if you are + * not sending HTTP headers. + * + * @var string + */ + private $contentType = 'application/x-zip'; + /** + * @var int + */ + private $deflateLevel = 6; + + /** + * @var resource + */ + private $outputStream; + + /** + * Options constructor. + */ + public function __construct() + { + $this->largeFileMethod = Method::STORE(); + $this->outputStream = fopen('php://output', 'wb'); + } + + public function getComment(): string + { + return $this->comment; + } + + public function setComment(string $comment): void + { + $this->comment = $comment; + } + + public function getLargeFileSize(): int + { + return $this->largeFileSize; + } + + public function setLargeFileSize(int $largeFileSize): void + { + $this->largeFileSize = $largeFileSize; + } + + public function getLargeFileMethod(): Method + { + return $this->largeFileMethod; + } + + public function setLargeFileMethod(Method $largeFileMethod): void + { + $this->largeFileMethod = $largeFileMethod; + } + + public function isSendHttpHeaders(): bool + { + return $this->sendHttpHeaders; + } + + public function setSendHttpHeaders(bool $sendHttpHeaders): void + { + $this->sendHttpHeaders = $sendHttpHeaders; + } + + public function getHttpHeaderCallback(): Callable + { + return $this->httpHeaderCallback; + } + + public function setHttpHeaderCallback(Callable $httpHeaderCallback): void + { + $this->httpHeaderCallback = $httpHeaderCallback; + } + + public function isEnableZip64(): bool + { + return $this->enableZip64; + } + + public function setEnableZip64(bool $enableZip64): void + { + $this->enableZip64 = $enableZip64; + } + + public function isZeroHeader(): bool + { + return $this->zeroHeader; + } + + public function setZeroHeader(bool $zeroHeader): void + { + $this->zeroHeader = $zeroHeader; + } + + public function isFlushOutput(): bool + { + return $this->flushOutput; + } + + public function setFlushOutput(bool $flushOutput): void + { + $this->flushOutput = $flushOutput; + } + + public function isStatFiles(): bool + { + return $this->statFiles; + } + + public function setStatFiles(bool $statFiles): void + { + $this->statFiles = $statFiles; + } + + public function getContentDisposition(): string + { + return $this->contentDisposition; + } + + public function setContentDisposition(string $contentDisposition): void + { + $this->contentDisposition = $contentDisposition; + } + + public function getContentType(): string + { + return $this->contentType; + } + + public function setContentType(string $contentType): void + { + $this->contentType = $contentType; + } + + /** + * @return resource + */ + public function getOutputStream() + { + return $this->outputStream; + } + + /** + * @param resource $outputStream + */ + public function setOutputStream($outputStream): void + { + $this->outputStream = $outputStream; + } + + /** + * @return int + */ + public function getDeflateLevel(): int + { + return $this->deflateLevel; + } + + /** + * @param int $deflateLevel + */ + public function setDeflateLevel(int $deflateLevel): void + { + $this->deflateLevel = $deflateLevel; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Option/File.php b/vendor/maennchen/zipstream-php/src/Option/File.php new file mode 100644 index 0000000..7fd29ea --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/File.php @@ -0,0 +1,116 @@ +deflateLevel = $this->deflateLevel ?: $archiveOptions->getDeflateLevel(); + $this->time = $this->time ?: new DateTime(); + } + + /** + * @return string + */ + public function getComment(): string + { + return $this->comment; + } + + /** + * @param string $comment + */ + public function setComment(string $comment): void + { + $this->comment = $comment; + } + + /** + * @return Method + */ + public function getMethod(): Method + { + return $this->method ?: Method::DEFLATE(); + } + + /** + * @param Method $method + */ + public function setMethod(Method $method): void + { + $this->method = $method; + } + + /** + * @return int + */ + public function getDeflateLevel(): int + { + return $this->deflateLevel ?: Archive::DEFAULT_DEFLATE_LEVEL; + } + + /** + * @param int $deflateLevel + */ + public function setDeflateLevel(int $deflateLevel): void + { + $this->deflateLevel = $deflateLevel; + } + + /** + * @return DateTime + */ + public function getTime(): DateTime + { + return $this->time; + } + + /** + * @param DateTime $time + */ + public function setTime(DateTime $time): void + { + $this->time = $time; + } + + /** + * @return int + */ + public function getSize(): int + { + return $this->size; + } + + /** + * @param int $size + */ + public function setSize(int $size): void + { + $this->size = $size; + } +} diff --git a/vendor/maennchen/zipstream-php/src/Option/Method.php b/vendor/maennchen/zipstream-php/src/Option/Method.php new file mode 100644 index 0000000..bbec84c --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/Option/Method.php @@ -0,0 +1,19 @@ +stream = $stream; + } + + /** + * Closes the stream and any underlying resources. + * + * @return void + */ + public function close(): void + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->detach(); + } + + /** + * Separates any underlying resources from the stream. + * + * After the stream has been detached, the stream is in an unusable state. + * + * @return resource|null Underlying PHP stream, if any + */ + public function detach() + { + $result = $this->stream; + $this->stream = null; + return $result; + } + + /** + * Reads all data from the stream into a string, from the beginning to end. + * + * This method MUST attempt to seek to the beginning of the stream before + * reading data and read the stream until the end is reached. + * + * Warning: This could attempt to load a large amount of data into memory. + * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring + * @return string + */ + public function __toString(): string + { + try { + $this->seek(0); + } catch (\RuntimeException $e) {} + return (string) stream_get_contents($this->stream); + } + + /** + * Seek to a position in the stream. + * + * @link http://www.php.net/manual/en/function.fseek.php + * @param int $offset Stream offset + * @param int $whence Specifies how the cursor position will be calculated + * based on the seek offset. Valid values are identical to the built-in + * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to + * offset bytes SEEK_CUR: Set position to current location plus offset + * SEEK_END: Set position to end-of-stream plus offset. + * @throws \RuntimeException on failure. + */ + public function seek($offset, $whence = SEEK_SET): void + { + if (!$this->isSeekable()) { + throw new RuntimeException; + } + if (fseek($this->stream, $offset, $whence) !== 0) { + throw new RuntimeException; + } + } + + /** + * Returns whether or not the stream is seekable. + * + * @return bool + */ + public function isSeekable(): bool + { + return (bool)$this->getMetadata('seekable'); + } + + /** + * Get stream metadata as an associative array or retrieve a specific key. + * + * The keys returned are identical to the keys returned from PHP's + * stream_get_meta_data() function. + * + * @link http://php.net/manual/en/function.stream-get-meta-data.php + * @param string $key Specific metadata to retrieve. + * @return array|mixed|null Returns an associative array if no key is + * provided. Returns a specific key value if a key is provided and the + * value is found, or null if the key is not found. + */ + public function getMetadata($key = null) + { + $metadata = stream_get_meta_data($this->stream); + return $key !== null ? @$metadata[$key] : $metadata; + } + + /** + * Get the size of the stream if known. + * + * @return int|null Returns the size in bytes if known, or null if unknown. + */ + public function getSize(): ?int + { + $stats = fstat($this->stream); + return $stats['size']; + } + + /** + * Returns the current position of the file read/write pointer + * + * @return int Position of the file pointer + * @throws \RuntimeException on error. + */ + public function tell(): int + { + $position = ftell($this->stream); + if ($position === false) { + throw new RuntimeException; + } + return $position; + } + + /** + * Returns true if the stream is at the end of the stream. + * + * @return bool + */ + public function eof(): bool + { + return feof($this->stream); + } + + /** + * Seek to the beginning of the stream. + * + * If the stream is not seekable, this method will raise an exception; + * otherwise, it will perform a seek(0). + * + * @see seek() + * @link http://www.php.net/manual/en/function.fseek.php + * @throws \RuntimeException on failure. + */ + public function rewind(): void + { + $this->seek(0); + } + + /** + * Write data to the stream. + * + * @param string $string The string that is to be written. + * @return int Returns the number of bytes written to the stream. + * @throws \RuntimeException on failure. + */ + public function write($string): int + { + if (!$this->isWritable()) { + throw new RuntimeException; + } + if (fwrite($this->stream, $string) === false) { + throw new RuntimeException; + } + return \mb_strlen($string); + } + + /** + * Returns whether or not the stream is writable. + * + * @return bool + */ + public function isWritable(): bool + { + return preg_match('/[waxc+]/', $this->getMetadata('mode')) === 1; + } + + /** + * Read data from the stream. + * + * @param int $length Read up to $length bytes from the object and return + * them. Fewer than $length bytes may be returned if underlying stream + * call returns fewer bytes. + * @return string Returns the data read from the stream, or an empty string + * if no bytes are available. + * @throws \RuntimeException if an error occurs. + */ + public function read($length): string + { + if (!$this->isReadable()) { + throw new RuntimeException; + } + $result = fread($this->stream, $length); + if ($result === false) { + throw new RuntimeException; + } + return $result; + } + + /** + * Returns whether or not the stream is readable. + * + * @return bool + */ + public function isReadable(): bool + { + return preg_match('/[r+]/', $this->getMetadata('mode')) === 1; + } + + /** + * Returns the remaining contents in a string + * + * @return string + * @throws \RuntimeException if unable to read or an error occurs while + * reading. + */ + public function getContents(): string + { + if (!$this->isReadable()) { + throw new RuntimeException; + } + $result = stream_get_contents($this->stream); + if ($result === false) { + throw new RuntimeException; + } + return $result; + } +} diff --git a/vendor/maennchen/zipstream-php/src/ZipStream.php b/vendor/maennchen/zipstream-php/src/ZipStream.php new file mode 100644 index 0000000..e83038c --- /dev/null +++ b/vendor/maennchen/zipstream-php/src/ZipStream.php @@ -0,0 +1,599 @@ +addFile('some_file.gif', $data); + * + * * add second file + * $data = file_get_contents('some_file.gif'); + * $zip->addFile('another_file.png', $data); + * + * 3. Finish the zip stream: + * + * $zip->finish(); + * + * You can also add an archive comment, add comments to individual files, + * and adjust the timestamp of files. See the API documentation for each + * method below for additional information. + * + * Example: + * + * // create a new zip stream object + * $zip = new ZipStream('some_files.zip'); + * + * // list of local files + * $files = array('foo.txt', 'bar.jpg'); + * + * // read and add each file to the archive + * foreach ($files as $path) + * $zip->addFile($path, file_get_contents($path)); + * + * // write archive footer to stream + * $zip->finish(); + */ +class ZipStream +{ + /** + * This number corresponds to the ZIP version/OS used (2 bytes) + * From: https://www.iana.org/assignments/media-types/application/zip + * The upper byte (leftmost one) indicates the host system (OS) for the + * file. Software can use this information to determine + * the line record format for text files etc. The current + * mappings are: + * + * 0 - MS-DOS and OS/2 (F.A.T. file systems) + * 1 - Amiga 2 - VAX/VMS + * 3 - *nix 4 - VM/CMS + * 5 - Atari ST 6 - OS/2 H.P.F.S. + * 7 - Macintosh 8 - Z-System + * 9 - CP/M 10 thru 255 - unused + * + * The lower byte (rightmost one) indicates the version number of the + * software used to encode the file. The value/10 + * indicates the major version number, and the value + * mod 10 is the minor version number. + * Here we are using 6 for the OS, indicating OS/2 H.P.F.S. + * to prevent file permissions issues upon extract (see #84) + * 0x603 is 00000110 00000011 in binary, so 6 and 3 + */ + const ZIP_VERSION_MADE_BY = 0x603; + + /** + * The following signatures end with 0x4b50, which in ASCII is PK, + * the initials of the inventor Phil Katz. + * See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers + */ + const FILE_HEADER_SIGNATURE = 0x04034b50; + const CDR_FILE_SIGNATURE = 0x02014b50; + const CDR_EOF_SIGNATURE = 0x06054b50; + const DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; + const ZIP64_CDR_EOF_SIGNATURE = 0x06064b50; + const ZIP64_CDR_LOCATOR_SIGNATURE = 0x07064b50; + + /** + * Global Options + * + * @var ArchiveOptions + */ + public $opt; + + /** + * @var array + */ + public $files = []; + + /** + * @var Bigint + */ + public $cdr_ofs; + + /** + * @var Bigint + */ + public $ofs; + + /** + * @var bool + */ + protected $need_headers; + + /** + * @var null|String + */ + protected $output_name; + + /** + * Create a new ZipStream object. + * + * Parameters: + * + * @param String $name - Name of output file (optional). + * @param ArchiveOptions $opt - Archive Options + * + * Large File Support: + * + * By default, the method addFileFromPath() will send send files + * larger than 20 megabytes along raw rather than attempting to + * compress them. You can change both the maximum size and the + * compression behavior using the largeFile* options above, with the + * following caveats: + * + * * For "small" files (e.g. files smaller than largeFileSize), the + * memory use can be up to twice that of the actual file. In other + * words, adding a 10 megabyte file to the archive could potentially + * occupy 20 megabytes of memory. + * + * * Enabling compression on large files (e.g. files larger than + * large_file_size) is extremely slow, because ZipStream has to pass + * over the large file once to calculate header information, and then + * again to compress and send the actual data. + * + * Examples: + * + * // create a new zip file named 'foo.zip' + * $zip = new ZipStream('foo.zip'); + * + * // create a new zip file named 'bar.zip' with a comment + * $opt->setComment = 'this is a comment for the zip file.'; + * $zip = new ZipStream('bar.zip', $opt); + * + * Notes: + * + * In order to let this library send HTTP headers, a filename must be given + * _and_ the option `sendHttpHeaders` must be `true`. This behavior is to + * allow software to send its own headers (including the filename), and + * still use this library. + */ + public function __construct(?string $name = null, ?ArchiveOptions $opt = null) + { + $this->opt = $opt ?: new ArchiveOptions(); + + $this->output_name = $name; + $this->need_headers = $name && $this->opt->isSendHttpHeaders(); + + $this->cdr_ofs = new Bigint(); + $this->ofs = new Bigint(); + } + + /** + * addFile + * + * Add a file to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param String $data - contents of file + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * method - Storage method for file ("store" or "deflate") + * + * Examples: + * + * // add a file named 'foo.txt' + * $data = file_get_contents('foo.txt'); + * $zip->addFile('foo.txt', $data); + * + * // add a file named 'bar.jpg' with a comment and a last-modified + * // time of two hours ago + * $data = file_get_contents('bar.jpg'); + * $opt->setTime = time() - 2 * 3600; + * $opt->setComment = 'this is a comment about bar.jpg'; + * $zip->addFile('bar.jpg', $data, $opt); + */ + public function addFile(string $name, string $data, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processData($data); + } + + /** + * addFileFromPath + * + * Add a file at path to the archive. + * + * Note that large files may be compressed differently than smaller + * files; see the "Large File Support" section above for more + * information. + * + * @param String $name - name of file in archive (including directory path). + * @param String $path - path to file on disk (note: paths should be encoded using + * UNIX-style forward slashes -- e.g '/path/to/some/file'). + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * method - Storage method for file ("store" or "deflate") + * + * Examples: + * + * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' + * $zip->addFileFromPath('foo.txt', '/tmp/foo.txt'); + * + * // add a file named 'bigfile.rar' from the local file + * // '/usr/share/bigfile.rar' with a comment and a last-modified + * // time of two hours ago + * $path = '/usr/share/bigfile.rar'; + * $opt->setTime = time() - 2 * 3600; + * $opt->setComment = 'this is a comment about bar.jpg'; + * $zip->addFileFromPath('bigfile.rar', $path, $opt); + * + * @return void + * @throws \ZipStream\Exception\FileNotFoundException + * @throws \ZipStream\Exception\FileNotReadableException + */ + public function addFileFromPath(string $name, string $path, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processPath($path); + } + + /** + * addFileFromStream + * + * Add an open stream to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param resource $stream - contents of file as a stream resource + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * + * Examples: + * + * // create a temporary file stream and write text to it + * $fp = tmpfile(); + * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); + * + * // add a file named 'streamfile.txt' from the content of the stream + * $x->addFileFromStream('streamfile.txt', $fp); + * + * @return void + */ + public function addFileFromStream(string $name, $stream, ?FileOptions $options = null): void + { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processStream(new DeflateStream($stream)); + } + + /** + * addFileFromPsr7Stream + * + * Add an open stream to the archive. + * + * @param String $name - path of file in archive (including directory). + * @param StreamInterface $stream - contents of file as a stream resource + * @param FileOptions $options + * + * File Options: + * time - Last-modified timestamp (seconds since the epoch) of + * this file. Defaults to the current time. + * comment - Comment related to this file. + * + * Examples: + * + * // create a temporary file stream and write text to it + * $fp = tmpfile(); + * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); + * + * // add a file named 'streamfile.txt' from the content of the stream + * $x->addFileFromPsr7Stream('streamfile.txt', $fp); + * + * @return void + */ + public function addFileFromPsr7Stream( + string $name, + StreamInterface $stream, + ?FileOptions $options = null + ): void { + $options = $options ?: new FileOptions(); + $options->defaultTo($this->opt); + + $file = new File($this, $name, $options); + $file->processStream($stream); + } + + /** + * finish + * + * Write zip footer to stream. + * + * Example: + * + * // add a list of files to the archive + * $files = array('foo.txt', 'bar.jpg'); + * foreach ($files as $path) + * $zip->addFile($path, file_get_contents($path)); + * + * // write footer to stream + * $zip->finish(); + * @return void + * + * @throws OverflowException + */ + public function finish(): void + { + // add trailing cdr file records + foreach ($this->files as $cdrFile) { + $this->send($cdrFile); + $this->cdr_ofs = $this->cdr_ofs->add(Bigint::init(strlen($cdrFile))); + } + + // Add 64bit headers (if applicable) + if (count($this->files) >= 0xFFFF || + $this->cdr_ofs->isOver32() || + $this->ofs->isOver32()) { + if (!$this->opt->isEnableZip64()) { + throw new OverflowException(); + } + + $this->addCdr64Eof(); + $this->addCdr64Locator(); + } + + // add trailing cdr eof record + $this->addCdrEof(); + + // The End + $this->clear(); + } + + /** + * Send ZIP64 CDR EOF (Central Directory Record End-of-File) record. + * + * @return void + */ + protected function addCdr64Eof(): void + { + $num_files = count($this->files); + $cdr_length = $this->cdr_ofs; + $cdr_offset = $this->ofs; + + $fields = [ + ['V', static::ZIP64_CDR_EOF_SIGNATURE], // ZIP64 end of central file header signature + ['P', 44], // Length of data below this header (length of block - 12) = 44 + ['v', static::ZIP_VERSION_MADE_BY], // Made by version + ['v', Version::ZIP64], // Extract by version + ['V', 0x00], // disk number + ['V', 0x00], // no of disks + ['P', $num_files], // no of entries on disk + ['P', $num_files], // no of entries in cdr + ['P', $cdr_length], // CDR size + ['P', $cdr_offset], // CDR offset + ]; + + $ret = static::packFields($fields); + $this->send($ret); + } + + /** + * Create a format string and argument list for pack(), then call + * pack() and return the result. + * + * @param array $fields + * @return string + */ + public static function packFields(array $fields): string + { + $fmt = ''; + $args = []; + + // populate format string and argument list + foreach ($fields as [$format, $value]) { + if ($format === 'P') { + $fmt .= 'VV'; + if ($value instanceof Bigint) { + $args[] = $value->getLow32(); + $args[] = $value->getHigh32(); + } else { + $args[] = $value; + $args[] = 0; + } + } else { + if ($value instanceof Bigint) { + $value = $value->getLow32(); + } + $fmt .= $format; + $args[] = $value; + } + } + + // prepend format string to argument list + array_unshift($args, $fmt); + + // build output string from header and compressed data + return pack(...$args); + } + + /** + * Send string, sending HTTP headers if necessary. + * Flush output after write if configure option is set. + * + * @param String $str + * @return void + */ + public function send(string $str): void + { + if ($this->need_headers) { + $this->sendHttpHeaders(); + } + $this->need_headers = false; + + fwrite($this->opt->getOutputStream(), $str); + + if ($this->opt->isFlushOutput()) { + // flush output buffer if it is on and flushable + $status = ob_get_status(); + if (isset($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { + ob_flush(); + } + + // Flush system buffers after flushing userspace output buffer + flush(); + } + } + + /** + * Send HTTP headers for this stream. + * + * @return void + */ + protected function sendHttpHeaders(): void + { + // grab content disposition + $disposition = $this->opt->getContentDisposition(); + + if ($this->output_name) { + // Various different browsers dislike various characters here. Strip them all for safety. + $safe_output = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->output_name)); + + // Check if we need to UTF-8 encode the filename + $urlencoded = rawurlencode($safe_output); + $disposition .= "; filename*=UTF-8''{$urlencoded}"; + } + + $headers = array( + 'Content-Type' => $this->opt->getContentType(), + 'Content-Disposition' => $disposition, + 'Pragma' => 'public', + 'Cache-Control' => 'public, must-revalidate', + 'Content-Transfer-Encoding' => 'binary' + ); + + $call = $this->opt->getHttpHeaderCallback(); + foreach ($headers as $key => $val) { + $call("$key: $val"); + } + } + + /** + * Send ZIP64 CDR Locator (Central Directory Record Locator) record. + * + * @return void + */ + protected function addCdr64Locator(): void + { + $cdr_offset = $this->ofs->add($this->cdr_ofs); + + $fields = [ + ['V', static::ZIP64_CDR_LOCATOR_SIGNATURE], // ZIP64 end of central file header signature + ['V', 0x00], // Disc number containing CDR64EOF + ['P', $cdr_offset], // CDR offset + ['V', 1], // Total number of disks + ]; + + $ret = static::packFields($fields); + $this->send($ret); + } + + /** + * Send CDR EOF (Central Directory Record End-of-File) record. + * + * @return void + */ + protected function addCdrEof(): void + { + $num_files = count($this->files); + $cdr_length = $this->cdr_ofs; + $cdr_offset = $this->ofs; + + // grab comment (if specified) + $comment = $this->opt->getComment(); + + $fields = [ + ['V', static::CDR_EOF_SIGNATURE], // end of central file header signature + ['v', 0x00], // disk number + ['v', 0x00], // no of disks + ['v', min($num_files, 0xFFFF)], // no of entries on disk + ['v', min($num_files, 0xFFFF)], // no of entries in cdr + ['V', $cdr_length->getLowFF()], // CDR size + ['V', $cdr_offset->getLowFF()], // CDR offset + ['v', strlen($comment)], // Zip Comment size + ]; + + $ret = static::packFields($fields) . $comment; + $this->send($ret); + } + + /** + * Clear all internal variables. Note that the stream object is not + * usable after this. + * + * @return void + */ + protected function clear(): void + { + $this->files = []; + $this->ofs = new Bigint(); + $this->cdr_ofs = new Bigint(); + $this->opt = new ArchiveOptions(); + } + + /** + * Is this file larger than large_file_size? + * + * @param string $path + * @return bool + */ + public function isLargeFile(string $path): bool + { + if (!$this->opt->isStatFiles()) { + return false; + } + $stat = stat($path); + return $stat['size'] > $this->opt->getLargeFileSize(); + } + + /** + * Save file attributes for trailing CDR record. + * + * @param File $file + * @return void + */ + public function addToCdr(File $file): void + { + $file->ofs = $this->ofs; + $this->ofs = $this->ofs->add($file->getTotalLength()); + $this->files[] = $file->getCdrFile(); + } +} diff --git a/vendor/maennchen/zipstream-php/test/BigintTest.php b/vendor/maennchen/zipstream-php/test/BigintTest.php new file mode 100644 index 0000000..ac9c7c2 --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/BigintTest.php @@ -0,0 +1,65 @@ +assertSame('0x0000000012345678', $bigint->getHex64()); + $this->assertSame(0x12345678, $bigint->getLow32()); + $this->assertSame(0, $bigint->getHigh32()); + } + + public function testConstructLarge(): void + { + $bigint = new Bigint(0x87654321); + $this->assertSame('0x0000000087654321', $bigint->getHex64()); + $this->assertSame('87654321', bin2hex(pack('N', $bigint->getLow32()))); + $this->assertSame(0, $bigint->getHigh32()); + } + + public function testAddSmallValue(): void + { + $bigint = new Bigint(1); + $bigint = $bigint->add(Bigint::init(2)); + $this->assertSame(3, $bigint->getLow32()); + $this->assertFalse($bigint->isOver32()); + $this->assertTrue($bigint->isOver32(true)); + $this->assertSame($bigint->getLowFF(), (float)$bigint->getLow32()); + $this->assertSame($bigint->getLowFF(true), (float)0xFFFFFFFF); + } + + public function testAddWithOverflowAtLowestByte(): void + { + $bigint = new Bigint(0xFF); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertSame(0x100, $bigint->getLow32()); + } + + public function testAddWithOverflowAtInteger32(): void + { + $bigint = new Bigint(0xFFFFFFFE); + $this->assertFalse($bigint->isOver32()); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertTrue($bigint->isOver32()); + $bigint = $bigint->add(Bigint::init(0x01)); + $this->assertSame('0x0000000100000000', $bigint->getHex64()); + $this->assertTrue($bigint->isOver32()); + $this->assertSame((float)0xFFFFFFFF, $bigint->getLowFF()); + } + + public function testAddWithOverflowAtInteger64(): void + { + $bigint = Bigint::fromLowHigh(0xFFFFFFFF, 0xFFFFFFFF); + $this->assertSame('0xFFFFFFFFFFFFFFFF', $bigint->getHex64()); + $this->expectException(OverflowException::class); + $bigint->add(Bigint::init(1)); + } +} diff --git a/vendor/maennchen/zipstream-php/test/ZipStreamTest.php b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php new file mode 100644 index 0000000..6549b0b --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/ZipStreamTest.php @@ -0,0 +1,586 @@ +expectException(\ZipStream\Exception\FileNotFoundException::class); + // Get ZipStream Object + $zip = new ZipStream(); + + // Trigger error by adding a file which doesn't exist + $zip->addFileFromPath('foobar.php', '/foo/bar/foobar.php'); + } + + public function testFileNotReadableException(): void + { + // create new virtual filesystem + $root = vfsStream::setup('vfs'); + // create a virtual file with no permissions + $file = vfsStream::newFile('foo.txt', 0000)->at($root)->setContent('bar'); + $zip = new ZipStream(); + $this->expectException(\ZipStream\Exception\FileNotReadableException::class); + $zip->addFileFromPath('foo.txt', $file->url()); + } + + public function testDostime(): void + { + // Allows testing of protected method + $class = new \ReflectionClass(File::class); + $method = $class->getMethod('dostime'); + $method->setAccessible(true); + + $this->assertSame($method->invoke(null, 1416246368), 1165069764); + + // January 1 1980 - DOS Epoch. + $this->assertSame($method->invoke(null, 315532800), 2162688); + + // January 1 1970 -> January 1 1980 due to minimum DOS Epoch. @todo Throw Exception? + $this->assertSame($method->invoke(null, 0), 2162688); + } + + public function testAddFile(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(['sample.txt', 'test/sample.txt'], $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + /** + * @return array + */ + protected function getTmpFileStream(): array + { + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + $stream = fopen($tmp, 'wb+'); + + return array($tmp, $stream); + } + + /** + * @param string $tmp + * @return string + */ + protected function validateAndExtractZip($tmp): string + { + $tmpDir = $this->getTmpDir(); + + $zipArch = new \ZipArchive; + $res = $zipArch->open($tmp); + + if ($res !== true) { + $this->fail("Failed to open {$tmp}. Code: $res"); + + return $tmpDir; + } + + $this->assertEquals(0, $zipArch->status); + $this->assertEquals(0, $zipArch->statusSys); + + $zipArch->extractTo($tmpDir); + $zipArch->close(); + + return $tmpDir; + } + + protected function getTmpDir(): string + { + $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); + unlink($tmp); + mkdir($tmp) or $this->fail('Failed to make directory'); + + return $tmp; + } + + /** + * @param string $path + * @return string[] + */ + protected function getRecursiveFileList(string $path): array + { + $data = array(); + $path = (string)realpath($path); + $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); + + $pathLen = strlen($path); + foreach ($files as $file) { + $filePath = $file->getRealPath(); + if (!is_dir($filePath)) { + $data[] = substr($filePath, $pathLen + 1); + } + } + + sort($data); + + return $data; + } + + public function testAddFileUtf8NameComment(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'árvíztűrő tükörfúrógép.txt'; + $content = 'Sample String Data'; + $comment = + 'Filename has every special characters ' . + 'from Hungarian language in lowercase. ' . + 'In uppercase: ÁÍŰŐÜÖÚÓÉ'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); + + $zip->addFile($name, $content, $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array($name), $files); + $this->assertStringEqualsFile($tmpDir . '/' . $name, $content); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + $this->assertEquals($comment, $zipArch->getCommentName($name)); + } + + public function testAddFileUtf8NameNonUtfComment(): void + { + $this->expectException(\ZipStream\Exception\EncodingException::class); + + $stream = $this->getTmpFileStream()[1]; + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'á.txt'; + $content = 'any'; + $comment = 'á'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment(mb_convert_encoding($comment, 'ISO-8859-2', 'UTF-8')); + + $zip->addFile($name, $content, $fileOptions); + } + + public function testAddFileNonUtf8NameUtfComment(): void + { + $this->expectException(\ZipStream\Exception\EncodingException::class); + + $stream = $this->getTmpFileStream()[1]; + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $name = 'á.txt'; + $content = 'any'; + $comment = 'á'; + + $fileOptions = new FileOptions(); + $fileOptions->setComment($comment); + + $zip->addFile(mb_convert_encoding($name, 'ISO-8859-2', 'UTF-8'), $content, $fileOptions); + } + + public function testAddFileWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $zip->addFile('sample.txt', 'Sample String Data', $fileOptions); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $sample12 = $zipArch->statName('test/sample.txt'); + $this->assertEquals($sample1['comp_method'], Method::STORE); + $this->assertEquals($sample12['comp_method'], Method::DEFLATE); + + $zipArch->close(); + } + + public function testDecompressFileWithMacUnarchiver(): void + { + if (!file_exists(self::OSX_ARCHIVE_UTILITY)) { + $this->markTestSkipped('The Mac OSX Archive Utility is not available.'); + } + + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $folder = uniqid('', true); + + $zip->addFile($folder . '/sample.txt', 'Sample Data'); + $zip->finish(); + fclose($stream); + + exec(escapeshellarg(self::OSX_ARCHIVE_UTILITY) . ' ' . escapeshellarg($tmp), $output, $returnStatus); + + $this->assertEquals(0, $returnStatus); + $this->assertCount(0, $output); + + $this->assertFileExists(dirname($tmp) . '/' . $folder . '/sample.txt'); + $this->assertStringEqualsFile(dirname($tmp) . '/' . $folder . '/sample.txt', 'Sample Data'); + } + + public function testAddFileFromPath(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'Sample String Data'); + fclose($streamExample); + $zip->addFileFromPath('sample.txt', $tmpExample); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'More Simple Sample Data'); + fclose($streamExample); + $zip->addFileFromPath('test/sample.txt', $tmpExample); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt', 'test/sample.txt'), $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testAddFileFromPathWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'Sample String Data'); + fclose($streamExample); + $zip->addFileFromPath('sample.txt', $tmpExample, $fileOptions); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + fwrite($streamExample, 'More Simple Sample Data'); + fclose($streamExample); + $zip->addFileFromPath('test/sample.txt', $tmpExample); + + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $this->assertEquals(Method::STORE, $sample1['comp_method']); + + $sample2 = $zipArch->statName('test/sample.txt'); + $this->assertEquals(Method::DEFLATE, $sample2['comp_method']); + + $zipArch->close(); + } + + public function testAddLargeFileFromPath(): void + { + $methods = [Method::DEFLATE(), Method::STORE()]; + $falseTrue = [false, true]; + foreach ($methods as $method) { + foreach ($falseTrue as $zeroHeader) { + foreach ($falseTrue as $zip64) { + if ($zeroHeader && $method->equals(Method::DEFLATE())) { + continue; + } + $this->addLargeFileFileFromPath($method, $zeroHeader, $zip64); + } + } + } + } + + protected function addLargeFileFileFromPath($method, $zeroHeader, $zip64): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setLargeFileMethod($method); + $options->setLargeFileSize(5); + $options->setZeroHeader($zeroHeader); + $options->setEnableZip64($zip64); + + $zip = new ZipStream(null, $options); + + [$tmpExample, $streamExample] = $this->getTmpFileStream(); + for ($i = 0; $i <= 10000; $i++) { + fwrite($streamExample, sha1((string)$i)); + if ($i % 100 === 0) { + fwrite($streamExample, "\n"); + } + } + fclose($streamExample); + $shaExample = sha1_file($tmpExample); + $zip->addFileFromPath('sample.txt', $tmpExample); + unlink($tmpExample); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt'), $files); + + $this->assertEquals(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$method}"); + } + + public function testAddFileFromStream(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + // In this test we can't use temporary stream to feed data + // because zlib.deflate filter gives empty string before PHP 7 + // it works fine with file stream + $streamExample = fopen(__FILE__, 'rb'); + $zip->addFileFromStream('sample.txt', $streamExample); +// fclose($streamExample); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $streamExample2 = fopen('php://temp', 'wb+'); + fwrite($streamExample2, 'More Simple Sample Data'); + rewind($streamExample2); // move the pointer back to the beginning of file. + $zip->addFileFromStream('test/sample.txt', $streamExample2, $fileOptions); +// fclose($streamExample2); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.txt', 'test/sample.txt'), $files); + + $this->assertStringEqualsFile(__FILE__, file_get_contents($tmpDir . '/sample.txt')); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testAddFileFromStreamWithStorageMethod(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $streamExample = fopen('php://temp', 'wb+'); + fwrite($streamExample, 'Sample String Data'); + rewind($streamExample); // move the pointer back to the beginning of file. + $zip->addFileFromStream('sample.txt', $streamExample, $fileOptions); +// fclose($streamExample); + + $streamExample2 = fopen('php://temp', 'bw+'); + fwrite($streamExample2, 'More Simple Sample Data'); + rewind($streamExample2); // move the pointer back to the beginning of file. + $zip->addFileFromStream('test/sample.txt', $streamExample2); +// fclose($streamExample2); + + $zip->finish(); + fclose($stream); + + $zipArch = new \ZipArchive(); + $zipArch->open($tmp); + + $sample1 = $zipArch->statName('sample.txt'); + $this->assertEquals(Method::STORE, $sample1['comp_method']); + + $sample2 = $zipArch->statName('test/sample.txt'); + $this->assertEquals(Method::DEFLATE, $sample2['comp_method']); + + $zipArch->close(); + } + + public function testAddFileFromPsr7Stream(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $body = 'Sample String Data'; + $response = new Response(200, [], $body); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.json'), $files); + $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); + } + + public function testAddFileFromPsr7StreamWithFileSizeSet(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + + $zip = new ZipStream(null, $options); + + $body = 'Sample String Data'; + $fileSize = strlen($body); + // Add fake padding + $fakePadding = "\0\0\0\0\0\0"; + $response = new Response(200, [], $body . $fakePadding); + + $fileOptions = new FileOptions(); + $fileOptions->setMethod(Method::STORE()); + $fileOptions->setSize($fileSize); + $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(array('sample.json'), $files); + $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); + } + + public function testCreateArchiveWithFlushOptionSet(): void + { + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + $zip->addFile('test/sample.txt', 'More Simple Sample Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + + $files = $this->getRecursiveFileList($tmpDir); + $this->assertEquals(['sample.txt', 'test/sample.txt'], $files); + + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); + } + + public function testCreateArchiveWithOutputBufferingOffAndFlushOptionSet(): void + { + // WORKAROUND (1/2): remove phpunit's output buffer in order to run test without any buffering + ob_end_flush(); + $this->assertEquals(0, ob_get_level()); + + [$tmp, $stream] = $this->getTmpFileStream(); + + $options = new ArchiveOptions(); + $options->setOutputStream($stream); + $options->setFlushOutput(true); + + $zip = new ZipStream(null, $options); + + $zip->addFile('sample.txt', 'Sample String Data'); + + $zip->finish(); + fclose($stream); + + $tmpDir = $this->validateAndExtractZip($tmp); + $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); + + // WORKAROUND (2/2): add back output buffering so that PHPUnit doesn't complain that it is missing + ob_start(); + } +} diff --git a/vendor/maennchen/zipstream-php/test/bootstrap.php b/vendor/maennchen/zipstream-php/test/bootstrap.php new file mode 100644 index 0000000..b43c9bd --- /dev/null +++ b/vendor/maennchen/zipstream-php/test/bootstrap.php @@ -0,0 +1,6 @@ +setOutputStream(fopen('php://memory', 'wb')); + $fileOpt->setTime(clone $expectedTime); + + $zip = new ZipStream(null, $archiveOpt); + + $zip->addFile('sample.txt', 'Sample', $fileOpt); + + $zip->finish(); + + $this->assertEquals($expectedTime, $fileOpt->getTime()); + } +} diff --git a/vendor/markbaker/complex/.github/workflows/main.yml b/vendor/markbaker/complex/.github/workflows/main.yml new file mode 100644 index 0000000..8911533 --- /dev/null +++ b/vendor/markbaker/complex/.github/workflows/main.yml @@ -0,0 +1,153 @@ +name: main +on: [ push, pull_request ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + experimental: + - false + php-version: + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + + include: + - php-version: 'nightly' + experimental: true + + name: PHP ${{ matrix.php-version }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Delete composer lock file + id: composer-lock + if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' || matrix.php-version == '8.2' || matrix.php-version == 'nightly' }} + run: | + rm composer.lock + echo "::set-output name=flags::--ignore-platform-reqs" + + - name: Install dependencies + run: composer update --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: "Run PHPUnit tests (Experimental: ${{ matrix.experimental }})" + env: + FAILURE_ACTION: "${{ matrix.experimental == true }}" + run: vendor/bin/phpunit --verbose || $FAILURE_ACTION + + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code style with PHP_CodeSniffer + run: ./vendor/bin/phpcs -q --report=checkstyle classes/src/ | cs2pr + + versions: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code Version Compatibility check with PHP_CodeSniffer + run: ./vendor/bin/phpcs -q --report-width=200 --report=summary,full classes/src/ --standard=PHPCompatibility --runtime-set testVersion 7.2- + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: pcov + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Test Coverage + run: ./vendor/bin/phpunit --verbose --coverage-text diff --git a/vendor/markbaker/complex/README.md b/vendor/markbaker/complex/README.md new file mode 100644 index 0000000..76347af --- /dev/null +++ b/vendor/markbaker/complex/README.md @@ -0,0 +1,173 @@ +PHPComplex +========== + +--- + +PHP Class Library for working with Complex numbers + +[![Build Status](https://github.com/MarkBaker/PHPComplex/workflows/main/badge.svg)](https://github.com/MarkBaker/PHPComplex/actions) +[![Total Downloads](https://img.shields.io/packagist/dt/markbaker/complex)](https://packagist.org/packages/markbaker/complex) +[![Latest Stable Version](https://img.shields.io/github/v/release/MarkBaker/PHPComplex)](https://packagist.org/packages/markbaker/complex) +[![License](https://img.shields.io/github/license/MarkBaker/PHPComplex)](https://packagist.org/packages/markbaker/complex) + + +[![Complex Numbers](https://imgs.xkcd.com/comics/complex_numbers_2x.png)](https://xkcd.com/2028/) + +--- + +The library currently provides the following operations: + + - addition + - subtraction + - multiplication + - division + - division by + - division into + +together with functions for + + - theta (polar theta angle) + - rho (polar distance/radius) + - conjugate + * negative + - inverse (1 / complex) + - cos (cosine) + - acos (inverse cosine) + - cosh (hyperbolic cosine) + - acosh (inverse hyperbolic cosine) + - sin (sine) + - asin (inverse sine) + - sinh (hyperbolic sine) + - asinh (inverse hyperbolic sine) + - sec (secant) + - asec (inverse secant) + - sech (hyperbolic secant) + - asech (inverse hyperbolic secant) + - csc (cosecant) + - acsc (inverse cosecant) + - csch (hyperbolic secant) + - acsch (inverse hyperbolic secant) + - tan (tangent) + - atan (inverse tangent) + - tanh (hyperbolic tangent) + - atanh (inverse hyperbolic tangent) + - cot (cotangent) + - acot (inverse cotangent) + - coth (hyperbolic cotangent) + - acoth (inverse hyperbolic cotangent) + - sqrt (square root) + - exp (exponential) + - ln (natural log) + - log10 (base-10 log) + - log2 (base-2 log) + - pow (raised to the power of a real number) + + +--- + +# Installation + +```shell +composer require markbaker/complex:^1.0 +``` + +# Important BC Note + +If you've previously been using procedural calls to functions and operations using this library, then from version 3.0 you should use [MarkBaker/PHPComplexFunctions](https://github.com/MarkBaker/PHPComplexFunctions) instead (available on packagist as [markbaker/complex-functions](https://packagist.org/packages/markbaker/complex-functions)). + +You'll need to replace `markbaker/complex`in your `composer.json` file with the new library, but otherwise there should be no difference in the namespacing, or in the way that you have called the Complex functions in the past, so no actual code changes are required. + +```shell +composer require markbaker/complex-functions:^3.0 +``` + +You should not reference this library (`markbaker/complex`) in your `composer.json`, composer wil take care of that for you. + +# Usage + +To create a new complex object, you can provide either the real, imaginary and suffix parts as individual values, or as an array of values passed passed to the constructor; or a string representing the value. e.g + +```php +$real = 1.23; +$imaginary = -4.56; +$suffix = 'i'; + +$complexObject = new Complex\Complex($real, $imaginary, $suffix); +``` +or as an array +```php +$real = 1.23; +$imaginary = -4.56; +$suffix = 'i'; + +$arguments = [$real, $imaginary, $suffix]; + +$complexObject = new Complex\Complex($arguments); +``` +or as a string +```php +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +``` + +Complex objects are immutable: whenever you call a method or pass a complex value to a function that returns a complex value, a new Complex object will be returned, and the original will remain unchanged. +This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Complex result). + +## Performing Mathematical Operations + +To perform mathematical operations with Complex values, you can call the appropriate method against a complex value, passing other values as arguments + +```php +$complexString1 = '1.23-4.56i'; +$complexString2 = '2.34+5.67i'; + +$complexObject = new Complex\Complex($complexString1); +echo $complexObject->add($complexString2); +``` + +or use the static Operation methods +```php +$complexString1 = '1.23-4.56i'; +$complexString2 = '2.34+5.67i'; + +echo Complex\Operations::add($complexString1, $complexString2); +``` +If you want to perform the same operation against multiple values (e.g. to add three or more complex numbers), then you can pass multiple arguments to any of the operations. + +You can pass these arguments as Complex objects, or as an array, or string that will parse to a complex object. + +## Using functions + +When calling any of the available functions for a complex value, you can either call the relevant method for the Complex object +```php +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo $complexObject->sinh(); +``` + +or use the static Functions methods +```php +$complexString = '1.23-4.56i'; + +echo Complex\Functions::sinh($complexString); +``` +As with operations, you can pass these arguments as Complex objects, or as an array or string that will parse to a complex object. + + +In the case of the `pow()` function (the only implemented function that requires an additional argument) you need to pass both arguments when calling the function + +```php +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo Complex\Functions::pow($complexObject, 2); +``` +or pass the additional argument when calling the method +```php +$complexString = '1.23-4.56i'; + +$complexObject = new Complex\Complex($complexString); +echo $complexObject->pow(2); +``` diff --git a/vendor/markbaker/complex/classes/src/Complex.php b/vendor/markbaker/complex/classes/src/Complex.php new file mode 100644 index 0000000..25414ee --- /dev/null +++ b/vendor/markbaker/complex/classes/src/Complex.php @@ -0,0 +1,388 @@ +realPart = (float) $realPart; + $this->imaginaryPart = (float) $imaginaryPart; + $this->suffix = strtolower($suffix ?? ''); + } + + /** + * Gets the real part of this complex number + * + * @return Float + */ + public function getReal(): float + { + return $this->realPart; + } + + /** + * Gets the imaginary part of this complex number + * + * @return Float + */ + public function getImaginary(): float + { + return $this->imaginaryPart; + } + + /** + * Gets the suffix of this complex number + * + * @return String + */ + public function getSuffix(): string + { + return $this->suffix; + } + + /** + * Returns true if this is a real value, false if a complex value + * + * @return Bool + */ + public function isReal(): bool + { + return $this->imaginaryPart == 0.0; + } + + /** + * Returns true if this is a complex value, false if a real value + * + * @return Bool + */ + public function isComplex(): bool + { + return !$this->isReal(); + } + + public function format(): string + { + $str = ""; + if ($this->imaginaryPart != 0.0) { + if (\abs($this->imaginaryPart) != 1.0) { + $str .= $this->imaginaryPart . $this->suffix; + } else { + $str .= (($this->imaginaryPart < 0.0) ? '-' : '') . $this->suffix; + } + } + if ($this->realPart != 0.0) { + if (($str) && ($this->imaginaryPart > 0.0)) { + $str = "+" . $str; + } + $str = $this->realPart . $str; + } + if (!$str) { + $str = "0.0"; + } + + return $str; + } + + public function __toString(): string + { + return $this->format(); + } + + /** + * Validates whether the argument is a valid complex number, converting scalar or array values if possible + * + * @param mixed $complex The value to validate + * @return Complex + * @throws Exception If the argument isn't a Complex number or cannot be converted to one + */ + public static function validateComplexArgument($complex): Complex + { + if (is_scalar($complex) || is_array($complex)) { + $complex = new Complex($complex); + } elseif (!is_object($complex) || !($complex instanceof Complex)) { + throw new Exception('Value is not a valid complex number'); + } + + return $complex; + } + + /** + * Returns the reverse of this complex number + * + * @return Complex + */ + public function reverse(): Complex + { + return new Complex( + $this->imaginaryPart, + $this->realPart, + ($this->realPart == 0.0) ? null : $this->suffix + ); + } + + public function invertImaginary(): Complex + { + return new Complex( + $this->realPart, + $this->imaginaryPart * -1, + ($this->imaginaryPart == 0.0) ? null : $this->suffix + ); + } + + public function invertReal(): Complex + { + return new Complex( + $this->realPart * -1, + $this->imaginaryPart, + ($this->imaginaryPart == 0.0) ? null : $this->suffix + ); + } + + protected static $functions = [ + 'abs', + 'acos', + 'acosh', + 'acot', + 'acoth', + 'acsc', + 'acsch', + 'argument', + 'asec', + 'asech', + 'asin', + 'asinh', + 'atan', + 'atanh', + 'conjugate', + 'cos', + 'cosh', + 'cot', + 'coth', + 'csc', + 'csch', + 'exp', + 'inverse', + 'ln', + 'log2', + 'log10', + 'negative', + 'pow', + 'rho', + 'sec', + 'sech', + 'sin', + 'sinh', + 'sqrt', + 'tan', + 'tanh', + 'theta', + ]; + + protected static $operations = [ + 'add', + 'subtract', + 'multiply', + 'divideby', + 'divideinto', + ]; + + /** + * Returns the result of the function call or operation + * + * @return Complex|float + * @throws Exception|\InvalidArgumentException + */ + public function __call($functionName, $arguments) + { + $functionName = strtolower(str_replace('_', '', $functionName)); + + // Test for function calls + if (in_array($functionName, self::$functions, true)) { + return Functions::$functionName($this, ...$arguments); + } + // Test for operation calls + if (in_array($functionName, self::$operations, true)) { + return Operations::$functionName($this, ...$arguments); + } + throw new Exception('Complex Function or Operation does not exist'); + } +} diff --git a/vendor/markbaker/complex/classes/src/Exception.php b/vendor/markbaker/complex/classes/src/Exception.php new file mode 100644 index 0000000..a2beb73 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/Exception.php @@ -0,0 +1,13 @@ +getReal() - $invsqrt->getImaginary(), + $complex->getImaginary() + $invsqrt->getReal() + ); + $log = self::ln($adjust); + + return new Complex( + $log->getImaginary(), + -1 * $log->getReal() + ); + } + + /** + * Returns the inverse hyperbolic cosine of a complex number. + * + * Formula from Wolfram Alpha: + * cosh^(-1)z = ln(z + sqrt(z + 1) sqrt(z - 1)). + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic cosine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function acosh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal() && ($complex->getReal() > 1)) { + return new Complex(\acosh($complex->getReal())); + } + + $acosh = self::ln( + Operations::add( + $complex, + Operations::multiply( + self::sqrt(Operations::add($complex, 1)), + self::sqrt(Operations::subtract($complex, 1)) + ) + ) + ); + + return $acosh; + } + + /** + * Returns the inverse cotangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse cotangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function acot($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::atan(self::inverse($complex)); + } + + /** + * Returns the inverse hyperbolic cotangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic cotangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function acoth($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::atanh(self::inverse($complex)); + } + + /** + * Returns the inverse cosecant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse cosecant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function acsc($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::asin(self::inverse($complex)); + } + + /** + * Returns the inverse hyperbolic cosecant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic cosecant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function acsch($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::asinh(self::inverse($complex)); + } + + /** + * Returns the argument of a complex number. + * Also known as the theta of the complex number, i.e. the angle in radians + * from the real axis to the representation of the number in polar coordinates. + * + * This function is a synonym for theta() + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return float The argument (or theta) value of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * + * @see theta + */ + public static function argument($complex): float + { + return self::theta($complex); + } + + /** + * Returns the inverse secant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse secant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function asec($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::acos(self::inverse($complex)); + } + + /** + * Returns the inverse hyperbolic secant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic secant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function asech($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::acosh(self::inverse($complex)); + } + + /** + * Returns the inverse sine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse sine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function asin($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + $invsqrt = self::sqrt(Operations::subtract(1, Operations::multiply($complex, $complex))); + $adjust = new Complex( + $invsqrt->getReal() - $complex->getImaginary(), + $invsqrt->getImaginary() + $complex->getReal() + ); + $log = self::ln($adjust); + + return new Complex( + $log->getImaginary(), + -1 * $log->getReal() + ); + } + + /** + * Returns the inverse hyperbolic sine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic sine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function asinh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal() && ($complex->getReal() > 1)) { + return new Complex(\asinh($complex->getReal())); + } + + $asinh = clone $complex; + $asinh = $asinh->reverse() + ->invertReal(); + $asinh = self::asin($asinh); + + return $asinh->reverse() + ->invertImaginary(); + } + + /** + * Returns the inverse tangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse tangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function atan($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\atan($complex->getReal())); + } + + $t1Value = new Complex(-1 * $complex->getImaginary(), $complex->getReal()); + $uValue = new Complex(1, 0); + + $d1Value = clone $uValue; + $d1Value = Operations::subtract($d1Value, $t1Value); + $d2Value = Operations::add($t1Value, $uValue); + $uResult = $d1Value->divideBy($d2Value); + $uResult = self::ln($uResult); + + $realMultiplier = -0.5; + $imaginaryMultiplier = 0.5; + + if (abs($uResult->getImaginary()) === M_PI) { + // If we have an imaginary value at the max or min (PI or -PI), then we need to ensure + // that the primary is assigned for the correct quadrant. + $realMultiplier = ( + ($uResult->getImaginary() === M_PI && $uResult->getReal() > 0.0) || + ($uResult->getImaginary() === -M_PI && $uResult->getReal() < 0.0) + ) ? 0.5 : -0.5; + } + + return new Complex( + $uResult->getImaginary() * $realMultiplier, + $uResult->getReal() * $imaginaryMultiplier, + $complex->getSuffix() + ); + } + + /** + * Returns the inverse hyperbolic tangent of a complex number. + * + * Formula from Wolfram Alpha: + * tanh^(-1)z = 1/2 [ln(1 + z) - ln(1 - z)]. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse hyperbolic tangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function atanh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + $real = $complex->getReal(); + if ($real >= -1.0 && $real <= 1.0) { + return new Complex(\atanh($real)); + } else { + return new Complex(\atanh(1 / $real), (($real < 0.0) ? M_PI_2 : -1 * M_PI_2)); + } + } + + $atanh = Operations::multiply( + Operations::subtract( + self::ln(Operations::add(1.0, $complex)), + self::ln(Operations::subtract(1.0, $complex)) + ), + 0.5 + ); + + return $atanh; + } + + /** + * Returns the complex conjugate of a complex number + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The conjugate of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function conjugate($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return new Complex( + $complex->getReal(), + -1 * $complex->getImaginary(), + $complex->getSuffix() + ); + } + + /** + * Returns the cosine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The cosine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function cos($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\cos($complex->getReal())); + } + + return self::conjugate( + new Complex( + \cos($complex->getReal()) * \cosh($complex->getImaginary()), + \sin($complex->getReal()) * \sinh($complex->getImaginary()), + $complex->getSuffix() + ) + ); + } + + /** + * Returns the hyperbolic cosine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic cosine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function cosh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\cosh($complex->getReal())); + } + + return new Complex( + \cosh($complex->getReal()) * \cos($complex->getImaginary()), + \sinh($complex->getReal()) * \sin($complex->getImaginary()), + $complex->getSuffix() + ); + } + + /** + * Returns the cotangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The cotangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function cot($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::inverse(self::tan($complex)); + } + + /** + * Returns the hyperbolic cotangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic cotangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function coth($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::inverse(self::tanh($complex)); + } + + /** + * Returns the cosecant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The cosecant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function csc($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::inverse(self::sin($complex)); + } + + /** + * Returns the hyperbolic cosecant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic cosecant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function csch($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return new Complex(INF); + } + + return self::inverse(self::sinh($complex)); + } + + /** + * Returns the exponential of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The exponential of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function exp($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (($complex->getReal() == 0.0) && (\abs($complex->getImaginary()) == M_PI)) { + return new Complex(-1.0, 0.0); + } + + $rho = \exp($complex->getReal()); + + return new Complex( + $rho * \cos($complex->getImaginary()), + $rho * \sin($complex->getImaginary()), + $complex->getSuffix() + ); + } + + /** + * Returns the inverse of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The inverse of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If function would result in a division by zero + */ + public static function inverse($complex): Complex + { + $complex = clone Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + return $complex->divideInto(1.0); + } + + /** + * Returns the natural logarithm of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The natural logarithm of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If the real and the imaginary parts are both zero + */ + public static function ln($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new InvalidArgumentException(); + } + + return new Complex( + \log(self::rho($complex)), + self::theta($complex), + $complex->getSuffix() + ); + } + + /** + * Returns the base-2 logarithm of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The base-2 logarithm of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If the real and the imaginary parts are both zero + */ + public static function log2($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new InvalidArgumentException(); + } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { + return new Complex(\log($complex->getReal(), 2), 0.0, $complex->getSuffix()); + } + + return self::ln($complex) + ->multiply(\log(Complex::EULER, 2)); + } + + /** + * Returns the common logarithm (base 10) of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The common logarithm (base 10) of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If the real and the imaginary parts are both zero + */ + public static function log10($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (($complex->getReal() == 0.0) && ($complex->getImaginary() == 0.0)) { + throw new InvalidArgumentException(); + } elseif (($complex->getReal() > 0.0) && ($complex->getImaginary() == 0.0)) { + return new Complex(\log10($complex->getReal()), 0.0, $complex->getSuffix()); + } + + return self::ln($complex) + ->multiply(\log10(Complex::EULER)); + } + + /** + * Returns the negative of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The negative value of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * + * @see rho + * + */ + public static function negative($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return new Complex( + -1 * $complex->getReal(), + -1 * $complex->getImaginary(), + $complex->getSuffix() + ); + } + + /** + * Returns a complex number raised to a power. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @param float|integer $power The power to raise this value to + * @return Complex The complex argument raised to the real power. + * @throws Exception If the power argument isn't a valid real + */ + public static function pow($complex, $power): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if (!is_numeric($power)) { + throw new Exception('Power argument must be a real number'); + } + + if ($complex->getImaginary() == 0.0 && $complex->getReal() >= 0.0) { + return new Complex(\pow($complex->getReal(), $power)); + } + + $rValue = \sqrt(($complex->getReal() * $complex->getReal()) + ($complex->getImaginary() * $complex->getImaginary())); + $rPower = \pow($rValue, $power); + $theta = $complex->argument() * $power; + if ($theta == 0) { + return new Complex(1); + } + + return new Complex($rPower * \cos($theta), $rPower * \sin($theta), $complex->getSuffix()); + } + + /** + * Returns the rho of a complex number. + * This is the distance/radius from the centrepoint to the representation of the number in polar coordinates. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return float The rho value of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function rho($complex): float + { + $complex = Complex::validateComplexArgument($complex); + + return \sqrt( + ($complex->getReal() * $complex->getReal()) + + ($complex->getImaginary() * $complex->getImaginary()) + ); + } + + /** + * Returns the secant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The secant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function sec($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::inverse(self::cos($complex)); + } + + /** + * Returns the hyperbolic secant of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic secant of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function sech($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + return self::inverse(self::cosh($complex)); + } + + /** + * Returns the sine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The sine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function sin($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\sin($complex->getReal())); + } + + return new Complex( + \sin($complex->getReal()) * \cosh($complex->getImaginary()), + \cos($complex->getReal()) * \sinh($complex->getImaginary()), + $complex->getSuffix() + ); + } + + /** + * Returns the hyperbolic sine of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic sine of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function sinh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\sinh($complex->getReal())); + } + + return new Complex( + \sinh($complex->getReal()) * \cos($complex->getImaginary()), + \cosh($complex->getReal()) * \sin($complex->getImaginary()), + $complex->getSuffix() + ); + } + + /** + * Returns the square root of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The Square root of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function sqrt($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + $theta = self::theta($complex); + $delta1 = \cos($theta / 2); + $delta2 = \sin($theta / 2); + $rho = \sqrt(self::rho($complex)); + + return new Complex($delta1 * $rho, $delta2 * $rho, $complex->getSuffix()); + } + + /** + * Returns the tangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The tangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws InvalidArgumentException If function would result in a division by zero + */ + public static function tan($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->isReal()) { + return new Complex(\tan($complex->getReal())); + } + + $real = $complex->getReal(); + $imaginary = $complex->getImaginary(); + $divisor = 1 + \pow(\tan($real), 2) * \pow(\tanh($imaginary), 2); + if ($divisor == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + return new Complex( + \pow(self::sech($imaginary)->getReal(), 2) * \tan($real) / $divisor, + \pow(self::sec($real)->getReal(), 2) * \tanh($imaginary) / $divisor, + $complex->getSuffix() + ); + } + + /** + * Returns the hyperbolic tangent of a complex number. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return Complex The hyperbolic tangent of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + * @throws \InvalidArgumentException If function would result in a division by zero + */ + public static function tanh($complex): Complex + { + $complex = Complex::validateComplexArgument($complex); + $real = $complex->getReal(); + $imaginary = $complex->getImaginary(); + $divisor = \cos($imaginary) * \cos($imaginary) + \sinh($real) * \sinh($real); + if ($divisor == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + return new Complex( + \sinh($real) * \cosh($real) / $divisor, + 0.5 * \sin(2 * $imaginary) / $divisor, + $complex->getSuffix() + ); + } + + /** + * Returns the theta of a complex number. + * This is the angle in radians from the real axis to the representation of the number in polar coordinates. + * + * @param Complex|mixed $complex Complex number or a numeric value. + * @return float The theta value of the complex argument. + * @throws Exception If argument isn't a valid real or complex number. + */ + public static function theta($complex): float + { + $complex = Complex::validateComplexArgument($complex); + + if ($complex->getReal() == 0.0) { + if ($complex->isReal()) { + return 0.0; + } elseif ($complex->getImaginary() < 0.0) { + return M_PI / -2; + } + return M_PI / 2; + } elseif ($complex->getReal() > 0.0) { + return \atan($complex->getImaginary() / $complex->getReal()); + } elseif ($complex->getImaginary() < 0.0) { + return -(M_PI - \atan(\abs($complex->getImaginary()) / \abs($complex->getReal()))); + } + + return M_PI - \atan($complex->getImaginary() / \abs($complex->getReal())); + } +} diff --git a/vendor/markbaker/complex/classes/src/Operations.php b/vendor/markbaker/complex/classes/src/Operations.php new file mode 100644 index 0000000..b13a873 --- /dev/null +++ b/vendor/markbaker/complex/classes/src/Operations.php @@ -0,0 +1,210 @@ +isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = $result->getReal() + $complex->getReal(); + $imaginary = $result->getImaginary() + $complex->getImaginary(); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } + + /** + * Divides two or more complex numbers + * + * @param array of string|integer|float|Complex $complexValues The numbers to divide + * @return Complex + */ + public static function divideby(...$complexValues): Complex + { + if (count($complexValues) < 2) { + throw new \Exception('This function requires at least 2 arguments'); + } + + $base = array_shift($complexValues); + $result = clone Complex::validateComplexArgument($base); + + foreach ($complexValues as $complex) { + $complex = Complex::validateComplexArgument($complex); + + if ($result->isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + $delta1 = ($result->getReal() * $complex->getReal()) + + ($result->getImaginary() * $complex->getImaginary()); + $delta2 = ($result->getImaginary() * $complex->getReal()) - + ($result->getReal() * $complex->getImaginary()); + $delta3 = ($complex->getReal() * $complex->getReal()) + + ($complex->getImaginary() * $complex->getImaginary()); + + $real = $delta1 / $delta3; + $imaginary = $delta2 / $delta3; + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } + + /** + * Divides two or more complex numbers + * + * @param array of string|integer|float|Complex $complexValues The numbers to divide + * @return Complex + */ + public static function divideinto(...$complexValues): Complex + { + if (count($complexValues) < 2) { + throw new \Exception('This function requires at least 2 arguments'); + } + + $base = array_shift($complexValues); + $result = clone Complex::validateComplexArgument($base); + + foreach ($complexValues as $complex) { + $complex = Complex::validateComplexArgument($complex); + + if ($result->isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + if ($result->getReal() == 0.0 && $result->getImaginary() == 0.0) { + throw new InvalidArgumentException('Division by zero'); + } + + $delta1 = ($complex->getReal() * $result->getReal()) + + ($complex->getImaginary() * $result->getImaginary()); + $delta2 = ($complex->getImaginary() * $result->getReal()) - + ($complex->getReal() * $result->getImaginary()); + $delta3 = ($result->getReal() * $result->getReal()) + + ($result->getImaginary() * $result->getImaginary()); + + $real = $delta1 / $delta3; + $imaginary = $delta2 / $delta3; + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } + + /** + * Multiplies two or more complex numbers + * + * @param array of string|integer|float|Complex $complexValues The numbers to multiply + * @return Complex + */ + public static function multiply(...$complexValues): Complex + { + if (count($complexValues) < 2) { + throw new \Exception('This function requires at least 2 arguments'); + } + + $base = array_shift($complexValues); + $result = clone Complex::validateComplexArgument($base); + + foreach ($complexValues as $complex) { + $complex = Complex::validateComplexArgument($complex); + + if ($result->isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = ($result->getReal() * $complex->getReal()) - + ($result->getImaginary() * $complex->getImaginary()); + $imaginary = ($result->getReal() * $complex->getImaginary()) + + ($result->getImaginary() * $complex->getReal()); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } + + /** + * Subtracts two or more complex numbers + * + * @param array of string|integer|float|Complex $complexValues The numbers to subtract + * @return Complex + */ + public static function subtract(...$complexValues): Complex + { + if (count($complexValues) < 2) { + throw new \Exception('This function requires at least 2 arguments'); + } + + $base = array_shift($complexValues); + $result = clone Complex::validateComplexArgument($base); + + foreach ($complexValues as $complex) { + $complex = Complex::validateComplexArgument($complex); + + if ($result->isComplex() && $complex->isComplex() && + $result->getSuffix() !== $complex->getSuffix()) { + throw new Exception('Suffix Mismatch'); + } + + $real = $result->getReal() - $complex->getReal(); + $imaginary = $result->getImaginary() - $complex->getImaginary(); + + $result = new Complex( + $real, + $imaginary, + ($imaginary == 0.0) ? null : max($result->getSuffix(), $complex->getSuffix()) + ); + } + + return $result; + } +} diff --git a/vendor/markbaker/complex/composer.json b/vendor/markbaker/complex/composer.json new file mode 100644 index 0000000..246683c --- /dev/null +++ b/vendor/markbaker/complex/composer.json @@ -0,0 +1,40 @@ +{ + "name": "markbaker/complex", + "type": "library", + "description": "PHP Class for working with complex numbers", + "keywords": ["complex", "mathematics"], + "homepage": "https://github.com/MarkBaker/PHPComplex", + "license": "MIT", + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "config": { + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true, + "markbaker/ukraine": true + } + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "phpcompatibility/php-compatibility": "^9.3", + "dealerdirect/phpcodesniffer-composer-installer": "dev-master" + }, + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "scripts": { + "style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full classes/src/ unitTests/classes/src -n", + "versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full classes/src/ --runtime-set testVersion 7.2- -n" + }, + "minimum-stability": "dev" +} diff --git a/vendor/markbaker/complex/examples/complexTest.php b/vendor/markbaker/complex/examples/complexTest.php new file mode 100644 index 0000000..9a5e123 --- /dev/null +++ b/vendor/markbaker/complex/examples/complexTest.php @@ -0,0 +1,154 @@ +add(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->add(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->add(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->add(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->add(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->add(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Subtract', PHP_EOL; + +$x = new Complex(123); +$x->subtract(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->subtract(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->subtract(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->subtract(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->subtract(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->subtract(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Multiply', PHP_EOL; + +$x = new Complex(123); +$x->multiply(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->multiply(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->multiply(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->multiply(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->multiply(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->multiply(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Divide By', PHP_EOL; + +$x = new Complex(123); +$x->divideBy(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->divideBy(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideBy(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideBy(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideBy(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideBy(new Complex(0, -1)); +echo $x, PHP_EOL; + + +echo PHP_EOL, 'Divide Into', PHP_EOL; + +$x = new Complex(123); +$x->divideInto(456); +echo $x, PHP_EOL; + +$x = new Complex(123.456); +$x->divideInto(789.012); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideInto(new Complex(-987.654, -32.1)); +echo $x, PHP_EOL; + +$x = new Complex(123.456, 78.90); +$x->divideInto(-987.654); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideInto(new Complex(0, 1)); +echo $x, PHP_EOL; + +$x = new Complex(-987.654, -32.1); +$x->divideInto(new Complex(0, -1)); +echo $x, PHP_EOL; diff --git a/vendor/markbaker/complex/examples/testFunctions.php b/vendor/markbaker/complex/examples/testFunctions.php new file mode 100644 index 0000000..bad1c03 --- /dev/null +++ b/vendor/markbaker/complex/examples/testFunctions.php @@ -0,0 +1,52 @@ +getMessage(), PHP_EOL; + } + } + echo PHP_EOL; + } +} diff --git a/vendor/markbaker/complex/examples/testOperations.php b/vendor/markbaker/complex/examples/testOperations.php new file mode 100644 index 0000000..2b7e0ba --- /dev/null +++ b/vendor/markbaker/complex/examples/testOperations.php @@ -0,0 +1,35 @@ + ', $result, PHP_EOL; + +echo PHP_EOL; + +echo 'Subtraction', PHP_EOL; + +$result = Operations::subtract(...$values); +echo '=> ', $result, PHP_EOL; + +echo PHP_EOL; + +echo 'Multiplication', PHP_EOL; + +$result = Operations::multiply(...$values); +echo '=> ', $result, PHP_EOL; diff --git a/vendor/markbaker/complex/license.md b/vendor/markbaker/complex/license.md new file mode 100644 index 0000000..5b4b156 --- /dev/null +++ b/vendor/markbaker/complex/license.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2017` `Mark Baker` + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/markbaker/matrix/.github/workflows/main.yaml b/vendor/markbaker/matrix/.github/workflows/main.yaml new file mode 100644 index 0000000..5fa1bdb --- /dev/null +++ b/vendor/markbaker/matrix/.github/workflows/main.yaml @@ -0,0 +1,124 @@ +name: main +on: [ push, pull_request ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + + include: + - php-version: 'nightly' + experimental: true + + name: PHP ${{ matrix.php-version }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Delete composer lock file + id: composer-lock + if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' || matrix.php-version == '8.2' || matrix.php-version == 'nightly' }} + run: | + rm composer.lock + echo "::set-output name=flags::--ignore-platform-reqs" + + - name: Install dependencies + run: composer update --no-progress --prefer-dist --optimize-autoloader ${{ steps.composer-lock.outputs.flags }} + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Setup problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Test with PHPUnit + run: ./vendor/bin/phpunit + + phpcs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: none + tools: cs2pr + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Code style with PHP_CodeSniffer + run: ./vendor/bin/phpcs -q --report=checkstyle | cs2pr --graceful-warnings --colorize + + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP, with composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib + coverage: pcov + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: composer install --no-progress --prefer-dist --optimize-autoloader + + - name: Coverage + run: | + ./vendor/bin/phpunit --coverage-text diff --git a/vendor/markbaker/matrix/README.md b/vendor/markbaker/matrix/README.md new file mode 100644 index 0000000..d0dc914 --- /dev/null +++ b/vendor/markbaker/matrix/README.md @@ -0,0 +1,215 @@ +PHPMatrix +========== + +--- + +PHP Class for handling Matrices + +[![Build Status](https://github.com/MarkBaker/PHPMatrix/workflows/main/badge.svg)](https://github.com/MarkBaker/PHPMatrix/actions) +[![Total Downloads](https://img.shields.io/packagist/dt/markbaker/matrix)](https://packagist.org/packages/markbaker/matrix) +[![Latest Stable Version](https://img.shields.io/github/v/release/MarkBaker/PHPMatrix)](https://packagist.org/packages/markbaker/matrix) +[![License](https://img.shields.io/github/license/MarkBaker/PHPMatrix)](https://packagist.org/packages/markbaker/matrix) + + +[![Matrix Transform](https://imgs.xkcd.com/comics/matrix_transform.png)](https://xkcd.com/184/) + +Matrix Transform + +--- + +This library currently provides the following operations: + + - addition + - direct sum + - subtraction + - multiplication + - division (using [A].[B]-1) + - division by + - division into + +together with functions for + + - adjoint + - antidiagonal + - cofactors + - determinant + - diagonal + - identity + - inverse + - minors + - trace + - transpose + - solve + + Given Matrices A and B, calculate X for A.X = B + +and classes for + + - Decomposition + - LU Decomposition with partial row pivoting, + + such that [P].[A] = [L].[U] and [A] = [P]|.[L].[U] + - QR Decomposition + + such that [A] = [Q].[R] + +## TO DO + + - power() function + - Decomposition + - Cholesky Decomposition + - EigenValue Decomposition + - EigenValues + - EigenVectors + +--- + +# Installation + +```shell +composer require markbaker/matrix:^3.0 +``` + +# Important BC Note + +If you've previously been using procedural calls to functions and operations using this library, then from version 3.0 you should use [MarkBaker/PHPMatrixFunctions](https://github.com/MarkBaker/PHPMatrixFunctions) instead (available on packagist as [markbaker/matrix-functions](https://packagist.org/packages/markbaker/matrix-functions)). + +You'll need to replace `markbaker/matrix`in your `composer.json` file with the new library, but otherwise there should be no difference in the namespacing, or in the way that you have called the Matrix functions in the past, so no actual code changes are required. + +```shell +composer require markbaker/matrix-functions:^1.0 +``` + +You should not reference this library (`markbaker/matrix`) in your `composer.json`, composer wil take care of that for you. + +# Usage + +To create a new Matrix object, provide an array as the constructor argument + +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); +``` +The `Builder` class provides helper methods for creating specific matrices, specifically an identity matrix of a specified size; or a matrix of a specified dimensions, with every cell containing a set value. +```php +$matrix = Matrix\Builder::createFilledMatrix(1, 5, 3); +``` +Will create a matrix of 5 rows and 3 columns, filled with a `1` in every cell; while +```php +$matrix = Matrix\Builder::createIdentityMatrix(3); +``` +will create a 3x3 identity matrix. + + +Matrix objects are immutable: whenever you call a method or pass a grid to a function that returns a matrix value, a new Matrix object will be returned, and the original will remain unchanged. This also allows you to chain multiple methods as you would for a fluent interface (as long as they are methods that will return a Matrix result). + +## Performing Mathematical Operations + +To perform mathematical operations with Matrices, you can call the appropriate method against a matrix value, passing other values as arguments + +```php +$matrix1 = new Matrix\Matrix([ + [2, 7, 6], + [9, 5, 1], + [4, 3, 8], +]); +$matrix2 = new Matrix\Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +]); + +var_dump($matrix1->multiply($matrix2)->toArray()); +``` +or pass all values to the appropriate static method +```php +$matrix1 = new Matrix\Matrix([ + [2, 7, 6], + [9, 5, 1], + [4, 3, 8], +]); +$matrix2 = new Matrix\Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], +]); + +var_dump(Matrix\Operations::multiply($matrix1, $matrix2)->toArray()); +``` +You can pass in the arguments as Matrix objects, or as arrays. + +If you want to perform the same operation against multiple values (e.g. to add three or more matrices), then you can pass multiple arguments to any of the operations. + +## Using functions + +When calling any of the available functions for a matrix value, you can either call the relevant method for the Matrix object +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); + +echo $matrix->trace(); +``` +or you can call the static method, passing the Matrix object or array as an argument +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +$matrix = new Matrix\Matrix($grid); +echo Matrix\Functions::trace($matrix); +``` +```php +$grid = [ + [16, 3, 2, 13], + [ 5, 10, 11, 8], + [ 9, 6, 7, 12], + [ 4, 15, 14, 1], +]; + +echo Matrix\Functions::trace($grid); +``` + +## Decomposition + +The library also provides classes for matrix decomposition. You can access these using +```php +$grid = [ + [1, 2], + [3, 4], +]; + +$matrix = new Matrix\Matrix($grid); + +$decomposition = new Matrix\Decomposition\QR($matrix); +$Q = $decomposition->getQ(); +$R = $decomposition->getR(); +``` + +or alternatively us the `Decomposition` factory, identifying which form of decomposition you want to use +```php +$grid = [ + [1, 2], + [3, 4], +]; + +$matrix = new Matrix\Matrix($grid); + +$decomposition = Matrix\Decomposition\Decomposition::decomposition(Matrix\Decomposition\Decomposition::QR, $matrix); +$Q = $decomposition->getQ(); +$R = $decomposition->getR(); +``` diff --git a/vendor/markbaker/matrix/buildPhar.php b/vendor/markbaker/matrix/buildPhar.php new file mode 100644 index 0000000..e1b8f96 --- /dev/null +++ b/vendor/markbaker/matrix/buildPhar.php @@ -0,0 +1,62 @@ + 'Mark Baker ', + 'Description' => 'PHP Class for working with Matrix numbers', + 'Copyright' => 'Mark Baker (c) 2013-' . date('Y'), + 'Timestamp' => time(), + 'Version' => '0.1.0', + 'Date' => date('Y-m-d') +); + +// cleanup +if (file_exists($pharName)) { + echo "Removed: {$pharName}\n"; + unlink($pharName); +} + +echo "Building phar file...\n"; + +// the phar object +$phar = new Phar($pharName, null, 'Matrix'); +$phar->buildFromDirectory($sourceDir); +$phar->setStub( +<<<'EOT' +getMessage()); + exit(1); + } + + include 'phar://functions/sqrt.php'; + + __HALT_COMPILER(); +EOT +); +$phar->setMetadata($metaData); +$phar->compressFiles(Phar::GZ); + +echo "Complete.\n"; + +exit(); diff --git a/vendor/markbaker/matrix/classes/src/Builder.php b/vendor/markbaker/matrix/classes/src/Builder.php new file mode 100644 index 0000000..161bb68 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Builder.php @@ -0,0 +1,70 @@ +toArray(); + + for ($x = 0; $x < $dimensions; ++$x) { + $grid[$x][$x] = 1; + } + + return new Matrix($grid); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php b/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php new file mode 100644 index 0000000..f014448 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php @@ -0,0 +1,27 @@ +luMatrix = $matrix->toArray(); + $this->rows = $matrix->rows; + $this->columns = $matrix->columns; + + $this->buildPivot(); + } + + /** + * Get lower triangular factor. + * + * @return Matrix Lower triangular factor + */ + public function getL(): Matrix + { + $lower = []; + + $columns = min($this->rows, $this->columns); + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $columns; ++$column) { + if ($row > $column) { + $lower[$row][$column] = $this->luMatrix[$row][$column]; + } elseif ($row === $column) { + $lower[$row][$column] = 1.0; + } else { + $lower[$row][$column] = 0.0; + } + } + } + + return new Matrix($lower); + } + + /** + * Get upper triangular factor. + * + * @return Matrix Upper triangular factor + */ + public function getU(): Matrix + { + $upper = []; + + $rows = min($this->rows, $this->columns); + for ($row = 0; $row < $rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row <= $column) { + $upper[$row][$column] = $this->luMatrix[$row][$column]; + } else { + $upper[$row][$column] = 0.0; + } + } + } + + return new Matrix($upper); + } + + /** + * Return pivot permutation vector. + * + * @return Matrix Pivot matrix + */ + public function getP(): Matrix + { + $pMatrix = []; + + $pivots = $this->pivot; + $pivotCount = count($pivots); + foreach ($pivots as $row => $pivot) { + $pMatrix[$row] = array_fill(0, $pivotCount, 0); + $pMatrix[$row][$pivot] = 1; + } + + return new Matrix($pMatrix); + } + + /** + * Return pivot permutation vector. + * + * @return array Pivot vector + */ + public function getPivot(): array + { + return $this->pivot; + } + + /** + * Is the matrix nonsingular? + * + * @return bool true if U, and hence A, is nonsingular + */ + public function isNonsingular(): bool + { + for ($diagonal = 0; $diagonal < $this->columns; ++$diagonal) { + if ($this->luMatrix[$diagonal][$diagonal] === 0.0) { + return false; + } + } + + return true; + } + + private function buildPivot(): void + { + for ($row = 0; $row < $this->rows; ++$row) { + $this->pivot[$row] = $row; + } + + for ($column = 0; $column < $this->columns; ++$column) { + $luColumn = $this->localisedReferenceColumn($column); + + $this->applyTransformations($column, $luColumn); + + $pivot = $this->findPivot($column, $luColumn); + if ($pivot !== $column) { + $this->pivotExchange($pivot, $column); + } + + $this->computeMultipliers($column); + + unset($luColumn); + } + } + + private function localisedReferenceColumn($column): array + { + $luColumn = []; + + for ($row = 0; $row < $this->rows; ++$row) { + $luColumn[$row] = &$this->luMatrix[$row][$column]; + } + + return $luColumn; + } + + private function applyTransformations($column, array $luColumn): void + { + for ($row = 0; $row < $this->rows; ++$row) { + $luRow = $this->luMatrix[$row]; + // Most of the time is spent in the following dot product. + $kmax = min($row, $column); + $sValue = 0.0; + for ($kValue = 0; $kValue < $kmax; ++$kValue) { + $sValue += $luRow[$kValue] * $luColumn[$kValue]; + } + $luRow[$column] = $luColumn[$row] -= $sValue; + } + } + + private function findPivot($column, array $luColumn): int + { + $pivot = $column; + for ($row = $column + 1; $row < $this->rows; ++$row) { + if (abs($luColumn[$row]) > abs($luColumn[$pivot])) { + $pivot = $row; + } + } + + return $pivot; + } + + private function pivotExchange($pivot, $column): void + { + for ($kValue = 0; $kValue < $this->columns; ++$kValue) { + $tValue = $this->luMatrix[$pivot][$kValue]; + $this->luMatrix[$pivot][$kValue] = $this->luMatrix[$column][$kValue]; + $this->luMatrix[$column][$kValue] = $tValue; + } + + $lValue = $this->pivot[$pivot]; + $this->pivot[$pivot] = $this->pivot[$column]; + $this->pivot[$column] = $lValue; + } + + private function computeMultipliers($diagonal): void + { + if (($diagonal < $this->rows) && ($this->luMatrix[$diagonal][$diagonal] != 0.0)) { + for ($row = $diagonal + 1; $row < $this->rows; ++$row) { + $this->luMatrix[$row][$diagonal] /= $this->luMatrix[$diagonal][$diagonal]; + } + } + } + + private function pivotB(Matrix $B): array + { + $X = []; + foreach ($this->pivot as $rowId) { + $row = $B->getRows($rowId + 1)->toArray(); + $X[] = array_pop($row); + } + + return $X; + } + + /** + * Solve A*X = B. + * + * @param Matrix $B a Matrix with as many rows as A and any number of columns + * + * @throws Exception + * + * @return Matrix X so that L*U*X = B(piv,:) + */ + public function solve(Matrix $B): Matrix + { + if ($B->rows !== $this->rows) { + throw new Exception('Matrix row dimensions are not equal'); + } + + if ($this->rows !== $this->columns) { + throw new Exception('LU solve() only works on square matrices'); + } + + if (!$this->isNonsingular()) { + throw new Exception('Can only perform operation on singular matrix'); + } + + // Copy right hand side with pivoting + $nx = $B->columns; + $X = $this->pivotB($B); + + // Solve L*Y = B(piv,:) + for ($k = 0; $k < $this->columns; ++$k) { + for ($i = $k + 1; $i < $this->columns; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; + } + } + } + + // Solve U*X = Y; + for ($k = $this->columns - 1; $k >= 0; --$k) { + for ($j = 0; $j < $nx; ++$j) { + $X[$k][$j] /= $this->luMatrix[$k][$k]; + } + for ($i = 0; $i < $k; ++$i) { + for ($j = 0; $j < $nx; ++$j) { + $X[$i][$j] -= $X[$k][$j] * $this->luMatrix[$i][$k]; + } + } + } + + return new Matrix($X); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Decomposition/QR.php b/vendor/markbaker/matrix/classes/src/Decomposition/QR.php new file mode 100644 index 0000000..4b6106f --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Decomposition/QR.php @@ -0,0 +1,191 @@ +qrMatrix = $matrix->toArray(); + $this->rows = $matrix->rows; + $this->columns = $matrix->columns; + + $this->decompose(); + } + + public function getHouseholdVectors(): Matrix + { + $householdVectors = []; + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row >= $column) { + $householdVectors[$row][$column] = $this->qrMatrix[$row][$column]; + } else { + $householdVectors[$row][$column] = 0.0; + } + } + } + + return new Matrix($householdVectors); + } + + public function getQ(): Matrix + { + $qGrid = []; + + $rowCount = $this->rows; + for ($k = $this->columns - 1; $k >= 0; --$k) { + for ($i = 0; $i < $this->rows; ++$i) { + $qGrid[$i][$k] = 0.0; + } + $qGrid[$k][$k] = 1.0; + if ($this->columns > $this->rows) { + $qGrid = array_slice($qGrid, 0, $this->rows); + } + + for ($j = $k; $j < $this->columns; ++$j) { + if (isset($this->qrMatrix[$k], $this->qrMatrix[$k][$k]) && $this->qrMatrix[$k][$k] != 0.0) { + $s = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $s += $this->qrMatrix[$i][$k] * $qGrid[$i][$j]; + } + $s = -$s / $this->qrMatrix[$k][$k]; + for ($i = $k; $i < $this->rows; ++$i) { + $qGrid[$i][$j] += $s * $this->qrMatrix[$i][$k]; + } + } + } + } + + array_walk( + $qGrid, + function (&$row) use ($rowCount) { + $row = array_reverse($row); + $row = array_slice($row, 0, $rowCount); + } + ); + + return new Matrix($qGrid); + } + + public function getR(): Matrix + { + $rGrid = []; + + for ($row = 0; $row < $this->columns; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + if ($row < $column) { + $rGrid[$row][$column] = $this->qrMatrix[$row][$column] ?? 0.0; + } elseif ($row === $column) { + $rGrid[$row][$column] = $this->rDiagonal[$row] ?? 0.0; + } else { + $rGrid[$row][$column] = 0.0; + } + } + } + + if ($this->columns > $this->rows) { + $rGrid = array_slice($rGrid, 0, $this->rows); + } + + return new Matrix($rGrid); + } + + private function hypo($a, $b): float + { + if (abs($a) > abs($b)) { + $r = $b / $a; + $r = abs($a) * sqrt(1 + $r * $r); + } elseif ($b != 0.0) { + $r = $a / $b; + $r = abs($b) * sqrt(1 + $r * $r); + } else { + $r = 0.0; + } + + return $r; + } + + /** + * QR Decomposition computed by Householder reflections. + */ + private function decompose(): void + { + for ($k = 0; $k < $this->columns; ++$k) { + // Compute 2-norm of k-th column without under/overflow. + $norm = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $norm = $this->hypo($norm, $this->qrMatrix[$i][$k]); + } + if ($norm != 0.0) { + // Form k-th Householder vector. + if ($this->qrMatrix[$k][$k] < 0.0) { + $norm = -$norm; + } + for ($i = $k; $i < $this->rows; ++$i) { + $this->qrMatrix[$i][$k] /= $norm; + } + $this->qrMatrix[$k][$k] += 1.0; + // Apply transformation to remaining columns. + for ($j = $k + 1; $j < $this->columns; ++$j) { + $s = 0.0; + for ($i = $k; $i < $this->rows; ++$i) { + $s += $this->qrMatrix[$i][$k] * $this->qrMatrix[$i][$j]; + } + $s = -$s / $this->qrMatrix[$k][$k]; + for ($i = $k; $i < $this->rows; ++$i) { + $this->qrMatrix[$i][$j] += $s * $this->qrMatrix[$i][$k]; + } + } + } + $this->rDiagonal[$k] = -$norm; + } + } + + public function isFullRank(): bool + { + for ($j = 0; $j < $this->columns; ++$j) { + if ($this->rDiagonal[$j] == 0.0) { + return false; + } + } + + return true; + } + + /** + * Least squares solution of A*X = B. + * + * @param Matrix $B a Matrix with as many rows as A and any number of columns + * + * @throws Exception + * + * @return Matrix matrix that minimizes the two norm of Q*R*X-B + */ + public function solve(Matrix $B): Matrix + { + if ($B->rows !== $this->rows) { + throw new Exception('Matrix row dimensions are not equal'); + } + + if (!$this->isFullRank()) { + throw new Exception('Can only perform this operation on a full-rank matrix'); + } + + // Compute Y = transpose(Q)*B + $Y = $this->getQ()->transpose() + ->multiply($B); + // Solve R*X = Y; + return $this->getR()->inverse() + ->multiply($Y); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Div0Exception.php b/vendor/markbaker/matrix/classes/src/Div0Exception.php new file mode 100644 index 0000000..eba28f8 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Div0Exception.php @@ -0,0 +1,13 @@ +isSquare()) { + throw new Exception('Adjoint can only be calculated for a square matrix'); + } + + return self::getAdjoint($matrix); + } + + /** + * Calculate the cofactors of the matrix + * + * @param Matrix $matrix The matrix whose cofactors we wish to calculate + * @return Matrix + * + * @throws Exception + */ + private static function getCofactors(Matrix $matrix) + { + $cofactors = self::getMinors($matrix); + $dimensions = $matrix->rows; + + $cof = 1; + for ($i = 0; $i < $dimensions; ++$i) { + $cofs = $cof; + for ($j = 0; $j < $dimensions; ++$j) { + $cofactors[$i][$j] *= $cofs; + $cofs = -$cofs; + } + $cof = -$cof; + } + + return new Matrix($cofactors); + } + + /** + * Return the cofactors of this matrix + * + * @param Matrix|array $matrix The matrix whose cofactors we wish to calculate + * @return Matrix + * + * @throws Exception + */ + public static function cofactors($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Cofactors can only be calculated for a square matrix'); + } + + return self::getCofactors($matrix); + } + + /** + * @param Matrix $matrix + * @param int $row + * @param int $column + * @return float + * @throws Exception + */ + private static function getDeterminantSegment(Matrix $matrix, $row, $column) + { + $tmpMatrix = $matrix->toArray(); + unset($tmpMatrix[$row]); + array_walk( + $tmpMatrix, + function (&$row) use ($column) { + unset($row[$column]); + } + ); + + return self::getDeterminant(new Matrix($tmpMatrix)); + } + + /** + * Calculate the determinant of the matrix + * + * @param Matrix $matrix The matrix whose determinant we wish to calculate + * @return float + * + * @throws Exception + */ + private static function getDeterminant(Matrix $matrix) + { + $dimensions = $matrix->rows; + $determinant = 0; + + switch ($dimensions) { + case 1: + $determinant = $matrix->getValue(1, 1); + break; + case 2: + $determinant = $matrix->getValue(1, 1) * $matrix->getValue(2, 2) - + $matrix->getValue(1, 2) * $matrix->getValue(2, 1); + break; + default: + for ($i = 1; $i <= $dimensions; ++$i) { + $det = $matrix->getValue(1, $i) * self::getDeterminantSegment($matrix, 0, $i - 1); + if (($i % 2) == 0) { + $determinant -= $det; + } else { + $determinant += $det; + } + } + break; + } + + return $determinant; + } + + /** + * Return the determinant of this matrix + * + * @param Matrix|array $matrix The matrix whose determinant we wish to calculate + * @return float + * @throws Exception + **/ + public static function determinant($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Determinant can only be calculated for a square matrix'); + } + + return self::getDeterminant($matrix); + } + + /** + * Return the diagonal of this matrix + * + * @param Matrix|array $matrix The matrix whose diagonal we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function diagonal($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Diagonal can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) + ->toArray(); + + for ($i = 0; $i < $dimensions; ++$i) { + $grid[$i][$i] = $matrix->getValue($i + 1, $i + 1); + } + + return new Matrix($grid); + } + + /** + * Return the antidiagonal of this matrix + * + * @param Matrix|array $matrix The matrix whose antidiagonal we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function antidiagonal($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Anti-Diagonal can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions) + ->toArray(); + + for ($i = 0; $i < $dimensions; ++$i) { + $grid[$i][$dimensions - $i - 1] = $matrix->getValue($i + 1, $dimensions - $i); + } + + return new Matrix($grid); + } + + /** + * Return the identity matrix + * The identity matrix, or sometimes ambiguously called a unit matrix, of size n is the n × n square matrix + * with ones on the main diagonal and zeros elsewhere + * + * @param Matrix|array $matrix The matrix whose identity we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function identity($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Identity can only be created for a square matrix'); + } + + $dimensions = $matrix->rows; + + return Builder::createIdentityMatrix($dimensions); + } + + /** + * Return the inverse of this matrix + * + * @param Matrix|array $matrix The matrix whose inverse we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function inverse($matrix, string $type = 'inverse') + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception(ucfirst($type) . ' can only be calculated for a square matrix'); + } + + $determinant = self::getDeterminant($matrix); + if ($determinant == 0.0) { + throw new Div0Exception(ucfirst($type) . ' can only be calculated for a matrix with a non-zero determinant'); + } + + if ($matrix->rows == 1) { + return new Matrix([[1 / $matrix->getValue(1, 1)]]); + } + + return self::getAdjoint($matrix) + ->multiply(1 / $determinant); + } + + /** + * Calculate the minors of the matrix + * + * @param Matrix $matrix The matrix whose minors we wish to calculate + * @return array[] + * + * @throws Exception + */ + protected static function getMinors(Matrix $matrix) + { + $minors = $matrix->toArray(); + $dimensions = $matrix->rows; + if ($dimensions == 1) { + return $minors; + } + + for ($i = 0; $i < $dimensions; ++$i) { + for ($j = 0; $j < $dimensions; ++$j) { + $minors[$i][$j] = self::getDeterminantSegment($matrix, $i, $j); + } + } + + return $minors; + } + + /** + * Return the minors of the matrix + * The minor of a matrix A is the determinant of some smaller square matrix, cut down from A by removing one or + * more of its rows or columns. + * Minors obtained by removing just one row and one column from square matrices (first minors) are required for + * calculating matrix cofactors, which in turn are useful for computing both the determinant and inverse of + * square matrices. + * + * @param Matrix|array $matrix The matrix whose minors we wish to calculate + * @return Matrix + * @throws Exception + **/ + public static function minors($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Minors can only be calculated for a square matrix'); + } + + return new Matrix(self::getMinors($matrix)); + } + + /** + * Return the trace of this matrix + * The trace is defined as the sum of the elements on the main diagonal (the diagonal from the upper left to the lower right) + * of the matrix + * + * @param Matrix|array $matrix The matrix whose trace we wish to calculate + * @return float + * @throws Exception + **/ + public static function trace($matrix) + { + $matrix = self::validateMatrix($matrix); + + if (!$matrix->isSquare()) { + throw new Exception('Trace can only be extracted from a square matrix'); + } + + $dimensions = $matrix->rows; + $result = 0; + for ($i = 1; $i <= $dimensions; ++$i) { + $result += $matrix->getValue($i, $i); + } + + return $result; + } + + /** + * Return the transpose of this matrix + * + * @param Matrix|\a $matrix The matrix whose transpose we wish to calculate + * @return Matrix + **/ + public static function transpose($matrix) + { + $matrix = self::validateMatrix($matrix); + + $array = array_values(array_merge([null], $matrix->toArray())); + $grid = call_user_func_array( + 'array_map', + $array + ); + + return new Matrix($grid); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Matrix.php b/vendor/markbaker/matrix/classes/src/Matrix.php new file mode 100644 index 0000000..95f55e7 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Matrix.php @@ -0,0 +1,423 @@ +buildFromArray(array_values($grid)); + } + + /* + * Create a new Matrix object from an array of values + * + * @param array $grid + */ + protected function buildFromArray(array $grid): void + { + $this->rows = count($grid); + $columns = array_reduce( + $grid, + function ($carry, $value) { + return max($carry, is_array($value) ? count($value) : 1); + } + ); + $this->columns = $columns; + + array_walk( + $grid, + function (&$value) use ($columns) { + if (!is_array($value)) { + $value = [$value]; + } + $value = array_pad(array_values($value), $columns, null); + } + ); + + $this->grid = $grid; + } + + /** + * Validate that a row number is a positive integer + * + * @param int $row + * @return int + * @throws Exception + */ + public static function validateRow(int $row): int + { + if ((!is_numeric($row)) || (intval($row) < 1)) { + throw new Exception('Invalid Row'); + } + + return (int)$row; + } + + /** + * Validate that a column number is a positive integer + * + * @param int $column + * @return int + * @throws Exception + */ + public static function validateColumn(int $column): int + { + if ((!is_numeric($column)) || (intval($column) < 1)) { + throw new Exception('Invalid Column'); + } + + return (int)$column; + } + + /** + * Validate that a row number falls within the set of rows for this matrix + * + * @param int $row + * @return int + * @throws Exception + */ + protected function validateRowInRange(int $row): int + { + $row = static::validateRow($row); + if ($row > $this->rows) { + throw new Exception('Requested Row exceeds matrix size'); + } + + return $row; + } + + /** + * Validate that a column number falls within the set of columns for this matrix + * + * @param int $column + * @return int + * @throws Exception + */ + protected function validateColumnInRange(int $column): int + { + $column = static::validateColumn($column); + if ($column > $this->columns) { + throw new Exception('Requested Column exceeds matrix size'); + } + + return $column; + } + + /** + * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows + * A $rowCount value of 0 will return all rows of the matrix from $row + * A negative $rowCount value will return rows until that many rows from the end of the matrix + * + * Note that row numbers start from 1, not from 0 + * + * @param int $row + * @param int $rowCount + * @return static + * @throws Exception + */ + public function getRows(int $row, int $rowCount = 1): Matrix + { + $row = $this->validateRowInRange($row); + if ($rowCount === 0) { + $rowCount = $this->rows - $row + 1; + } + + return new static(array_slice($this->grid, $row - 1, (int)$rowCount)); + } + + /** + * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns + * A $columnCount value of 0 will return all columns of the matrix from $column + * A negative $columnCount value will return columns until that many columns from the end of the matrix + * + * Note that column numbers start from 1, not from 0 + * + * @param int $column + * @param int $columnCount + * @return Matrix + * @throws Exception + */ + public function getColumns(int $column, int $columnCount = 1): Matrix + { + $column = $this->validateColumnInRange($column); + if ($columnCount < 1) { + $columnCount = $this->columns + $columnCount - $column + 1; + } + + $grid = []; + for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) { + $grid[] = array_column($this->grid, $i); + } + + return (new static($grid))->transpose(); + } + + /** + * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row, + * and $rowCount rows + * A negative $rowCount value will drop rows until that many rows from the end of the matrix + * A $rowCount value of 0 will remove all rows of the matrix from $row + * + * Note that row numbers start from 1, not from 0 + * + * @param int $row + * @param int $rowCount + * @return static + * @throws Exception + */ + public function dropRows(int $row, int $rowCount = 1): Matrix + { + $this->validateRowInRange($row); + if ($rowCount === 0) { + $rowCount = $this->rows - $row + 1; + } + + $grid = $this->grid; + array_splice($grid, $row - 1, (int)$rowCount); + + return new static($grid); + } + + /** + * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column, + * and $columnCount columns + * A negative $columnCount value will drop columns until that many columns from the end of the matrix + * A $columnCount value of 0 will remove all columns of the matrix from $column + * + * Note that column numbers start from 1, not from 0 + * + * @param int $column + * @param int $columnCount + * @return static + * @throws Exception + */ + public function dropColumns(int $column, int $columnCount = 1): Matrix + { + $this->validateColumnInRange($column); + if ($columnCount < 1) { + $columnCount = $this->columns + $columnCount - $column + 1; + } + + $grid = $this->grid; + array_walk( + $grid, + function (&$row) use ($column, $columnCount) { + array_splice($row, $column - 1, (int)$columnCount); + } + ); + + return new static($grid); + } + + /** + * Return a value from this matrix, from the "cell" identified by the row and column numbers + * Note that row and column numbers start from 1, not from 0 + * + * @param int $row + * @param int $column + * @return mixed + * @throws Exception + */ + public function getValue(int $row, int $column) + { + $row = $this->validateRowInRange($row); + $column = $this->validateColumnInRange($column); + + return $this->grid[$row - 1][$column - 1]; + } + + /** + * Returns a Generator that will yield each row of the matrix in turn as a vector matrix + * or the value of each cell if the matrix is a column vector + * + * @return Generator|Matrix[]|mixed[] + */ + public function rows(): Generator + { + foreach ($this->grid as $i => $row) { + yield $i + 1 => ($this->columns == 1) + ? $row[0] + : new static([$row]); + } + } + + /** + * Returns a Generator that will yield each column of the matrix in turn as a vector matrix + * or the value of each cell if the matrix is a row vector + * + * @return Generator|Matrix[]|mixed[] + */ + public function columns(): Generator + { + for ($i = 0; $i < $this->columns; ++$i) { + yield $i + 1 => ($this->rows == 1) + ? $this->grid[0][$i] + : new static(array_column($this->grid, $i)); + } + } + + /** + * Identify if the row and column dimensions of this matrix are equal, + * i.e. if it is a "square" matrix + * + * @return bool + */ + public function isSquare(): bool + { + return $this->rows === $this->columns; + } + + /** + * Identify if this matrix is a vector + * i.e. if it comprises only a single row or a single column + * + * @return bool + */ + public function isVector(): bool + { + return $this->rows === 1 || $this->columns === 1; + } + + /** + * Return the matrix as a 2-dimensional array + * + * @return array + */ + public function toArray(): array + { + return $this->grid; + } + + /** + * Solve A*X = B. + * + * @param Matrix $B Right hand side + * + * @throws Exception + * + * @return Matrix ... Solution if A is square, least squares solution otherwise + */ + public function solve(Matrix $B): Matrix + { + if ($this->columns === $this->rows) { + return (new LU($this))->solve($B); + } + + return (new QR($this))->solve($B); + } + + protected static $getters = [ + 'rows', + 'columns', + ]; + + /** + * Access specific properties as read-only (no setters) + * + * @param string $propertyName + * @return mixed + * @throws Exception + */ + public function __get(string $propertyName) + { + $propertyName = strtolower($propertyName); + + // Test for function calls + if (in_array($propertyName, self::$getters)) { + return $this->$propertyName; + } + + throw new Exception('Property does not exist'); + } + + protected static $functions = [ + 'adjoint', + 'antidiagonal', + 'cofactors', + 'determinant', + 'diagonal', + 'identity', + 'inverse', + 'minors', + 'trace', + 'transpose', + ]; + + protected static $operations = [ + 'add', + 'subtract', + 'multiply', + 'divideby', + 'divideinto', + 'directsum', + ]; + + /** + * Returns the result of the function call or operation + * + * @param string $functionName + * @param mixed[] $arguments + * @return Matrix|float + * @throws Exception + */ + public function __call(string $functionName, $arguments) + { + $functionName = strtolower(str_replace('_', '', $functionName)); + + // Test for function calls + if (in_array($functionName, self::$functions, true)) { + return Functions::$functionName($this, ...$arguments); + } + // Test for operation calls + if (in_array($functionName, self::$operations, true)) { + return Operations::$functionName($this, ...$arguments); + } + throw new Exception('Function or Operation does not exist'); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operations.php b/vendor/markbaker/matrix/classes/src/Operations.php new file mode 100644 index 0000000..e3d88d6 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operations.php @@ -0,0 +1,157 @@ +execute($matrix); + } + + return $result->result(); + } + + public static function directsum(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('DirectSum operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('DirectSum arguments must be Matrix or array'); + } + + $result = new DirectSum($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } + + public static function divideby(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('Division operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Division arguments must be Matrix or array'); + } + + $result = new Division($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } + + public static function divideinto(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('Division operation requires at least 2 arguments'); + } + + $matrix = array_pop($matrixValues); + $matrixValues = array_reverse($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Division arguments must be Matrix or array'); + } + + $result = new Division($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } + + public static function multiply(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('Multiplication operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Multiplication arguments must be Matrix or array'); + } + + $result = new Multiplication($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } + + public static function subtract(...$matrixValues): Matrix + { + if (count($matrixValues) < 2) { + throw new Exception('Subtraction operation requires at least 2 arguments'); + } + + $matrix = array_shift($matrixValues); + + if (is_array($matrix)) { + $matrix = new Matrix($matrix); + } + if (!$matrix instanceof Matrix) { + throw new Exception('Subtraction arguments must be Matrix or array'); + } + + $result = new Subtraction($matrix); + + foreach ($matrixValues as $matrix) { + $result->execute($matrix); + } + + return $result->result(); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Addition.php b/vendor/markbaker/matrix/classes/src/Operators/Addition.php new file mode 100644 index 0000000..543f56e --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Addition.php @@ -0,0 +1,68 @@ +addMatrix($value); + } elseif (is_numeric($value)) { + return $this->addScalar($value); + } + + throw new Exception('Invalid argument for addition'); + } + + /** + * Execute the addition for a scalar + * + * @param mixed $value The numeric value to add to the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + protected function addScalar($value): Operator + { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] += $value; + } + } + + return $this; + } + + /** + * Execute the addition for a matrix + * + * @param Matrix $value The numeric value to add to the current base value + * @return $this The operation object, allowing multiple additions to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function addMatrix(Matrix $value): Operator + { + $this->validateMatchingDimensions($value); + + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] += $value->getValue($row + 1, $column + 1); + } + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php b/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php new file mode 100644 index 0000000..cc51ef9 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php @@ -0,0 +1,64 @@ +directSumMatrix($value); + } + + throw new Exception('Invalid argument for addition'); + } + + /** + * Execute the direct sum for a matrix + * + * @param Matrix $value The numeric value to concatenate/direct sum with the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + private function directSumMatrix($value): Operator + { + $originalColumnCount = count($this->matrix[0]); + $originalRowCount = count($this->matrix); + $valColumnCount = $value->columns; + $valRowCount = $value->rows; + $value = $value->toArray(); + + for ($row = 0; $row < $this->rows; ++$row) { + $this->matrix[$row] = array_merge($this->matrix[$row], array_fill(0, $valColumnCount, 0)); + } + + $this->matrix = array_merge( + $this->matrix, + array_fill(0, $valRowCount, array_fill(0, $originalColumnCount, 0)) + ); + + for ($row = $originalRowCount; $row < $originalRowCount + $valRowCount; ++$row) { + array_splice( + $this->matrix[$row], + $originalColumnCount, + $valColumnCount, + $value[$row - $originalRowCount] + ); + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Division.php b/vendor/markbaker/matrix/classes/src/Operators/Division.php new file mode 100644 index 0000000..dbfec9d --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Division.php @@ -0,0 +1,35 @@ +multiplyMatrix($value, $type); + } elseif (is_numeric($value)) { + return $this->multiplyScalar(1 / $value, $type); + } + + throw new Exception('Invalid argument for division'); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php b/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php new file mode 100644 index 0000000..0761e46 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php @@ -0,0 +1,86 @@ +multiplyMatrix($value, $type); + } elseif (is_numeric($value)) { + return $this->multiplyScalar($value, $type); + } + + throw new Exception("Invalid argument for $type"); + } + + /** + * Execute the multiplication for a scalar + * + * @param mixed $value The numeric value to multiply with the current base value + * @return $this The operation object, allowing multiple mutiplications to be chained + **/ + protected function multiplyScalar($value, string $type = 'multiplication'): Operator + { + try { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] *= $value; + } + } + } catch (Throwable $e) { + throw new Exception("Invalid argument for $type"); + } + + return $this; + } + + /** + * Execute the multiplication for a matrix + * + * @param Matrix $value The numeric value to multiply with the current base value + * @return $this The operation object, allowing multiple mutiplications to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function multiplyMatrix(Matrix $value, string $type = 'multiplication'): Operator + { + $this->validateReflectingDimensions($value); + + $newRows = $this->rows; + $newColumns = $value->columns; + $matrix = Builder::createFilledMatrix(0, $newRows, $newColumns) + ->toArray(); + try { + for ($row = 0; $row < $newRows; ++$row) { + for ($column = 0; $column < $newColumns; ++$column) { + $columnData = $value->getColumns($column + 1)->toArray(); + foreach ($this->matrix[$row] as $key => $valueData) { + $matrix[$row][$column] += $valueData * $columnData[$key][0]; + } + } + } + } catch (Throwable $e) { + throw new Exception("Invalid argument for $type"); + } + $this->matrix = $matrix; + + return $this; + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Operator.php b/vendor/markbaker/matrix/classes/src/Operators/Operator.php new file mode 100644 index 0000000..39e36c6 --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Operator.php @@ -0,0 +1,78 @@ +rows = $matrix->rows; + $this->columns = $matrix->columns; + $this->matrix = $matrix->toArray(); + } + + /** + * Compare the dimensions of the matrices being operated on to see if they are valid for addition/subtraction + * + * @param Matrix $matrix The second Matrix object on which the operation will be performed + * @throws Exception + */ + protected function validateMatchingDimensions(Matrix $matrix): void + { + if (($this->rows != $matrix->rows) || ($this->columns != $matrix->columns)) { + throw new Exception('Matrices have mismatched dimensions'); + } + } + + /** + * Compare the dimensions of the matrices being operated on to see if they are valid for multiplication/division + * + * @param Matrix $matrix The second Matrix object on which the operation will be performed + * @throws Exception + */ + protected function validateReflectingDimensions(Matrix $matrix): void + { + if ($this->columns != $matrix->rows) { + throw new Exception('Matrices have mismatched dimensions'); + } + } + + /** + * Return the result of the operation + * + * @return Matrix + */ + public function result(): Matrix + { + return new Matrix($this->matrix); + } +} diff --git a/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php b/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php new file mode 100644 index 0000000..b7e14fa --- /dev/null +++ b/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php @@ -0,0 +1,68 @@ +subtractMatrix($value); + } elseif (is_numeric($value)) { + return $this->subtractScalar($value); + } + + throw new Exception('Invalid argument for subtraction'); + } + + /** + * Execute the subtraction for a scalar + * + * @param mixed $value The numeric value to subtracted from the current base value + * @return $this The operation object, allowing multiple additions to be chained + **/ + protected function subtractScalar($value): Operator + { + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] -= $value; + } + } + + return $this; + } + + /** + * Execute the subtraction for a matrix + * + * @param Matrix $value The numeric value to subtract from the current base value + * @return $this The operation object, allowing multiple subtractions to be chained + * @throws Exception If the provided argument is not appropriate for the operation + **/ + protected function subtractMatrix(Matrix $value): Operator + { + $this->validateMatchingDimensions($value); + + for ($row = 0; $row < $this->rows; ++$row) { + for ($column = 0; $column < $this->columns; ++$column) { + $this->matrix[$row][$column] -= $value->getValue($row + 1, $column + 1); + } + } + + return $this; + } +} diff --git a/vendor/markbaker/matrix/composer.json b/vendor/markbaker/matrix/composer.json new file mode 100644 index 0000000..e86a1e0 --- /dev/null +++ b/vendor/markbaker/matrix/composer.json @@ -0,0 +1,52 @@ +{ + "name": "markbaker/matrix", + "type": "library", + "description": "PHP Class for working with matrices", + "keywords": ["matrix", "vector", "mathematics"], + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "license": "MIT", + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "phpdocumentor/phpdocumentor": "2.*", + "phpmd/phpmd": "2.*", + "sebastian/phpcpd": "^4.0", + "phploc/phploc": "^4.0", + "squizlabs/php_codesniffer": "^3.7", + "phpcompatibility/php-compatibility": "^9.3", + "dealerdirect/phpcodesniffer-composer-installer": "dev-master" + }, + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "autoload-dev": { + "psr-4": { + "MatrixTest\\": "unitTests/classes/src/" + } + }, + "scripts": { + "style": "phpcs --report-width=200 --standard=PSR2 --report=summary,full classes/src/ unitTests/classes/src -n", + "test": "phpunit -c phpunit.xml.dist", + "mess": "phpmd classes/src/ xml codesize,unusedcode,design,naming -n", + "lines": "phploc classes/src/ -n", + "cpd": "phpcpd classes/src/ -n", + "versions": "phpcs --report-width=200 --standard=PHPCompatibility --report=summary,full classes/src/ --runtime-set testVersion 7.2- -n", + "coverage": "phpunit -c phpunit.xml.dist --coverage-text --coverage-html ./build/coverage" + }, + "minimum-stability": "dev", + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + } +} diff --git a/vendor/markbaker/matrix/examples/test.php b/vendor/markbaker/matrix/examples/test.php new file mode 100644 index 0000000..071dae9 --- /dev/null +++ b/vendor/markbaker/matrix/examples/test.php @@ -0,0 +1,33 @@ +solve($target); + +echo 'X', PHP_EOL; +var_export($X->toArray()); +echo PHP_EOL; + +$resolve = $matrix->multiply($X); + +echo 'Resolve', PHP_EOL; +var_export($resolve->toArray()); +echo PHP_EOL; diff --git a/vendor/markbaker/matrix/infection.json.dist b/vendor/markbaker/matrix/infection.json.dist new file mode 100644 index 0000000..eddaa70 --- /dev/null +++ b/vendor/markbaker/matrix/infection.json.dist @@ -0,0 +1,17 @@ +{ + "timeout": 1, + "source": { + "directories": [ + "classes\/src" + ] + }, + "logs": { + "text": "build/infection/text.log", + "summary": "build/infection/summary.log", + "debug": "build/infection/debug.log", + "perMutator": "build/infection/perMutator.md" + }, + "mutators": { + "@default": true + } +} diff --git a/vendor/markbaker/matrix/license.md b/vendor/markbaker/matrix/license.md new file mode 100644 index 0000000..7329058 --- /dev/null +++ b/vendor/markbaker/matrix/license.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2018` `Mark Baker` + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/markbaker/matrix/phpstan.neon b/vendor/markbaker/matrix/phpstan.neon new file mode 100644 index 0000000..3d90d49 --- /dev/null +++ b/vendor/markbaker/matrix/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - '#Property [A-Za-z\\]+::\$[A-Za-z]+ has no typehint specified#' + - '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has no return typehint specified#' + - '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has parameter \$[A-Za-z0-9]+ with no typehint specified#' + checkMissingIterableValueType: false diff --git a/vendor/myclabs/php-enum/LICENSE b/vendor/myclabs/php-enum/LICENSE new file mode 100644 index 0000000..2a8cf22 --- /dev/null +++ b/vendor/myclabs/php-enum/LICENSE @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2015 My C-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/myclabs/php-enum/README.md b/vendor/myclabs/php-enum/README.md new file mode 100644 index 0000000..681d55e --- /dev/null +++ b/vendor/myclabs/php-enum/README.md @@ -0,0 +1,194 @@ +# PHP Enum implementation inspired from SplEnum + +[![GitHub Actions][GA Image]][GA Link] +[![Latest Stable Version](https://poser.pugx.org/myclabs/php-enum/version.png)](https://packagist.org/packages/myclabs/php-enum) +[![Total Downloads](https://poser.pugx.org/myclabs/php-enum/downloads.png)](https://packagist.org/packages/myclabs/php-enum) +[![Psalm Shepherd][Shepherd Image]][Shepherd Link] + +Maintenance for this project is [supported via Tidelift](https://tidelift.com/subscription/pkg/packagist-myclabs-php-enum?utm_source=packagist-myclabs-php-enum&utm_medium=referral&utm_campaign=readme). + +## Why? + +First, and mainly, `SplEnum` is not integrated to PHP, you have to install the extension separately. + +Using an enum instead of class constants provides the following advantages: + +- You can use an enum as a parameter type: `function setAction(Action $action) {` +- You can use an enum as a return type: `function getAction() : Action {` +- You can enrich the enum with methods (e.g. `format`, `parse`, …) +- You can extend the enum to add new values (make your enum `final` to prevent it) +- You can get a list of all the possible values (see below) + +This Enum class is not intended to replace class constants, but only to be used when it makes sense. + +## Installation + +``` +composer require myclabs/php-enum +``` + +## Declaration + +```php +use MyCLabs\Enum\Enum; + +/** + * Action enum + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Usage + +```php +$action = Action::VIEW(); + +// or with a dynamic key: +$action = Action::$key(); +// or with a dynamic value: +$action = Action::from($value); +// or +$action = new Action($value); +``` + +As you can see, static methods are automatically implemented to provide quick access to an enum value. + +One advantage over using class constants is to be able to use an enum as a parameter type: + +```php +function setAction(Action $action) { + // ... +} +``` + +## Documentation + +- `__construct()` The constructor checks that the value exist in the enum +- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) +- `getValue()` Returns the current value of the enum +- `getKey()` Returns the key of the current value on Enum +- `equals()` Tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise) + +Static methods: + +- `from()` Creates an Enum instance, checking that the value exist in the enum +- `toArray()` method Returns all possible values as an array (constant name in key, constant value in value) +- `keys()` Returns the names (keys) of all constants in the Enum class +- `values()` Returns instances of the Enum class of all Enum constants (constant name in key, Enum instance in value) +- `isValid()` Check if tested value is valid on enum set +- `isValidKey()` Check if tested key is valid on enum set +- `assertValidValue()` Assert the value is valid on enum set, throwing exception otherwise +- `search()` Return key for searched value + +### Static methods + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} + +// Static method: +$action = Action::VIEW(); +$action = Action::EDIT(); +``` + +Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). + +If you care about IDE autocompletion, you can either implement the static methods yourself: + +```php +final class Action extends Enum +{ + private const VIEW = 'view'; + + /** + * @return Action + */ + public static function VIEW() { + return new Action(self::VIEW); + } +} +``` + +or you can use phpdoc (this is supported in PhpStorm for example): + +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + +## Native enums and migration +Native enum arrived to PHP in version 8.1: https://www.php.net/enumerations +If your project is running PHP 8.1+ or your library has it as a minimum requirement you should use it instead of this library. + +When migrating from `myclabs/php-enum`, the effort should be small if the usage was in the recommended way: +- private constants +- final classes +- no method overridden + +Changes for migration: +- Class definition should be changed from +```php +/** + * @method static Action VIEW() + * @method static Action EDIT() + */ +final class Action extends Enum +{ + private const VIEW = 'view'; + private const EDIT = 'edit'; +} +``` + to +```php +enum Action: string +{ + case VIEW = 'view'; + case EDIT = 'edit'; +} +``` +All places where the class was used as a type will continue to work. + +Usages and the change needed: + +| Operation | myclabs/php-enum | native enum | +|----------------------------------------------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Obtain an instance will change from | `$enumCase = Action::VIEW()` | `$enumCase = Action::VIEW` | +| Create an enum from a backed value | `$enumCase = new Action('view')` | `$enumCase = Action::from('view')` | +| Get the backed value of the enum instance | `$enumCase->getValue()` | `$enumCase->value` | +| Compare two enum instances | `$enumCase1 == $enumCase2`
      or
      `$enumCase1->equals($enumCase2)` | `$enumCase1 === $enumCase2` | +| Get the key/name of the enum instance | `$enumCase->getKey()` | `$enumCase->name` | +| Get a list of all the possible instances of the enum | `Action::values()` | `Action::cases()` | +| Get a map of possible instances of the enum mapped by name | `Action::values()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), Action::cases())`
      or
      `(new ReflectionEnum(Action::class))->getConstants()` | +| Get a list of all possible names of the enum | `Action::keys()` | `array_map(fn($case) => $case->name, Action::cases())` | +| Get a list of all possible backed values of the enum | `Action::toArray()` | `array_map(fn($case) => $case->value, Action::cases())` | +| Get a map of possible backed values of the enum mapped by name | `Action::toArray()` | `array_combine(array_map(fn($case) => $case->name, Action::cases()), array_map(fn($case) => $case->value, Action::cases()))`
      or
      `array_map(fn($case) => $case->value, (new ReflectionEnum(Action::class))->getConstants()))` | + +## Related projects + +- [PHP 8.1+ native enum](https://www.php.net/enumerations) +- [Doctrine enum mapping](https://github.com/acelaya/doctrine-enum-type) +- [Symfony ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) +- [PHPStan integration](https://github.com/timeweb/phpstan-enum) + + +[GA Image]: https://github.com/myclabs/php-enum/workflows/CI/badge.svg + +[GA Link]: https://github.com/myclabs/php-enum/actions?query=workflow%3A%22CI%22+branch%3Amaster + +[Shepherd Image]: https://shepherd.dev/github/myclabs/php-enum/coverage.svg + +[Shepherd Link]: https://shepherd.dev/github/myclabs/php-enum diff --git a/vendor/myclabs/php-enum/SECURITY.md b/vendor/myclabs/php-enum/SECURITY.md new file mode 100644 index 0000000..84fd4e3 --- /dev/null +++ b/vendor/myclabs/php-enum/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +Only the latest stable release is supported. + +## Reporting a Vulnerability + +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). + +Tidelift will coordinate the fix and disclosure. diff --git a/vendor/myclabs/php-enum/composer.json b/vendor/myclabs/php-enum/composer.json new file mode 100644 index 0000000..978cb19 --- /dev/null +++ b/vendor/myclabs/php-enum/composer.json @@ -0,0 +1,36 @@ +{ + "name": "myclabs/php-enum", + "type": "library", + "description": "PHP Enum implementation", + "keywords": ["enum"], + "homepage": "http://github.com/myclabs/php-enum", + "license": "MIT", + "authors": [ + { + "name": "PHP Enum contributors", + "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" + } + ], + "autoload": { + "psr-4": { + "MyCLabs\\Enum\\": "src/" + }, + "classmap": [ + "stubs/Stringable.php" + ] + }, + "autoload-dev": { + "psr-4": { + "MyCLabs\\Tests\\Enum\\": "tests/" + } + }, + "require": { + "php": "^7.3 || ^8.0", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "1.*", + "vimeo/psalm": "^4.6.2" + } +} diff --git a/vendor/myclabs/php-enum/src/Enum.php b/vendor/myclabs/php-enum/src/Enum.php new file mode 100644 index 0000000..4c94cf6 --- /dev/null +++ b/vendor/myclabs/php-enum/src/Enum.php @@ -0,0 +1,318 @@ + + * @author Daniel Costa + * @author Mirosław Filip + * + * @psalm-template T + * @psalm-immutable + * @psalm-consistent-constructor + */ +abstract class Enum implements \JsonSerializable, \Stringable +{ + /** + * Enum value + * + * @var mixed + * @psalm-var T + */ + protected $value; + + /** + * Enum key, the constant name + * + * @var string + */ + private $key; + + /** + * Store existing constants in a static cache per object. + * + * + * @var array + * @psalm-var array> + */ + protected static $cache = []; + + /** + * Cache of instances of the Enum class + * + * @var array + * @psalm-var array> + */ + protected static $instances = []; + + /** + * Creates a new value of some type + * + * @psalm-pure + * @param mixed $value + * + * @psalm-param T $value + * @throws \UnexpectedValueException if incompatible type is given. + */ + public function __construct($value) + { + if ($value instanceof static) { + /** @psalm-var T */ + $value = $value->getValue(); + } + + /** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */ + $this->key = static::assertValidValueReturningKey($value); + + /** @psalm-var T */ + $this->value = $value; + } + + /** + * This method exists only for the compatibility reason when deserializing a previously serialized version + * that didn't had the key property + */ + public function __wakeup() + { + /** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */ + if ($this->key === null) { + /** + * @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm + * @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed + */ + $this->key = static::search($this->value); + } + } + + /** + * @param mixed $value + * @return static + */ + public static function from($value): self + { + $key = static::assertValidValueReturningKey($value); + + return self::__callStatic($key, []); + } + + /** + * @psalm-pure + * @return mixed + * @psalm-return T + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the enum key (i.e. the constant name). + * + * @psalm-pure + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @psalm-pure + * @psalm-suppress InvalidCast + * @return string + */ + public function __toString() + { + return (string)$this->value; + } + + /** + * Determines if Enum should be considered equal with the variable passed as a parameter. + * Returns false if an argument is an object of different class or not an object. + * + * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 + * + * @psalm-pure + * @psalm-param mixed $variable + * @return bool + */ + final public function equals($variable = null): bool + { + return $variable instanceof self + && $this->getValue() === $variable->getValue() + && static::class === \get_class($variable); + } + + /** + * Returns the names (keys) of all constants in the Enum class + * + * @psalm-pure + * @psalm-return list + * @return array + */ + public static function keys() + { + return \array_keys(static::toArray()); + } + + /** + * Returns instances of the Enum class of all Enum constants + * + * @psalm-pure + * @psalm-return array + * @return static[] Constant name in key, Enum instance in value + */ + public static function values() + { + $values = array(); + + /** @psalm-var T $value */ + foreach (static::toArray() as $key => $value) { + $values[$key] = new static($value); + } + + return $values; + } + + /** + * Returns all possible values as an array + * + * @psalm-pure + * @psalm-suppress ImpureStaticProperty + * + * @psalm-return array + * @return array Constant name in key, constant value in value + */ + public static function toArray() + { + $class = static::class; + + if (!isset(static::$cache[$class])) { + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + $reflection = new \ReflectionClass($class); + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + static::$cache[$class] = $reflection->getConstants(); + } + + return static::$cache[$class]; + } + + /** + * Check if is valid enum value + * + * @param $value + * @psalm-param mixed $value + * @psalm-pure + * @psalm-assert-if-true T $value + * @return bool + */ + public static function isValid($value) + { + return \in_array($value, static::toArray(), true); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + */ + public static function assertValidValue($value): void + { + self::assertValidValueReturningKey($value); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + * @return string + */ + private static function assertValidValueReturningKey($value): string + { + if (false === ($key = static::search($value))) { + throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); + } + + return $key; + } + + /** + * Check if is valid enum key + * + * @param $key + * @psalm-param string $key + * @psalm-pure + * @return bool + */ + public static function isValidKey($key) + { + $array = static::toArray(); + + return isset($array[$key]) || \array_key_exists($key, $array); + } + + /** + * Return key for value + * + * @param mixed $value + * + * @psalm-param mixed $value + * @psalm-pure + * @return string|false + */ + public static function search($value) + { + return \array_search($value, static::toArray(), true); + } + + /** + * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant + * + * @param string $name + * @param array $arguments + * + * @return static + * @throws \BadMethodCallException + * + * @psalm-pure + */ + public static function __callStatic($name, $arguments) + { + $class = static::class; + if (!isset(self::$instances[$class][$name])) { + $array = static::toArray(); + if (!isset($array[$name]) && !\array_key_exists($name, $array)) { + $message = "No static method or enum constant '$name' in class " . static::class; + throw new \BadMethodCallException($message); + } + return self::$instances[$class][$name] = new static($array[$name]); + } + return clone self::$instances[$class][$name]; + } + + /** + * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() + * natively. + * + * @return mixed + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @psalm-pure + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getValue(); + } +} diff --git a/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php new file mode 100644 index 0000000..302bf80 --- /dev/null +++ b/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php @@ -0,0 +1,54 @@ +register(new \MyCLabs\Enum\PHPUnit\Comparator()); + */ +final class Comparator extends \SebastianBergmann\Comparator\Comparator +{ + public function accepts($expected, $actual) + { + return $expected instanceof Enum && ( + $actual instanceof Enum || $actual === null + ); + } + + /** + * @param Enum $expected + * @param Enum|null $actual + * + * @return void + */ + public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) + { + if ($expected->equals($actual)) { + return; + } + + throw new ComparisonFailure( + $expected, + $actual, + $this->formatEnum($expected), + $this->formatEnum($actual), + false, + 'Failed asserting that two Enums are equal.' + ); + } + + private function formatEnum(Enum $enum = null) + { + if ($enum === null) { + return "null"; + } + + return get_class($enum)."::{$enum->getKey()}()"; + } +} diff --git a/vendor/myclabs/php-enum/stubs/Stringable.php b/vendor/myclabs/php-enum/stubs/Stringable.php new file mode 100644 index 0000000..4811af7 --- /dev/null +++ b/vendor/myclabs/php-enum/stubs/Stringable.php @@ -0,0 +1,11 @@ + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/vendor/phpmailer/phpmailer/README.md b/vendor/phpmailer/phpmailer/README.md new file mode 100644 index 0000000..e3e4ecf --- /dev/null +++ b/vendor/phpmailer/phpmailer/README.md @@ -0,0 +1,231 @@ +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://supportukrainenow.org/) + +![PHPMailer](https://raw.github.com/PHPMailer/PHPMailer/master/examples/images/phpmailer.png) + +# PHPMailer – A full-featured email creation and transfer class for PHP + +[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) +[![codecov.io](https://codecov.io/gh/PHPMailer/PHPMailer/branch/master/graph/badge.svg?token=iORZpwmYmM)](https://codecov.io/gh/PHPMailer/PHPMailer) +[![Latest Stable Version](https://poser.pugx.org/phpmailer/phpmailer/v/stable.svg)](https://packagist.org/packages/phpmailer/phpmailer) +[![Total Downloads](https://poser.pugx.org/phpmailer/phpmailer/downloads)](https://packagist.org/packages/phpmailer/phpmailer) +[![License](https://poser.pugx.org/phpmailer/phpmailer/license.svg)](https://packagist.org/packages/phpmailer/phpmailer) +[![API Docs](https://github.com/phpmailer/phpmailer/workflows/Docs/badge.svg)](https://phpmailer.github.io/PHPMailer/) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer/badge)](https://api.securityscorecards.dev/projects/github.com/PHPMailer/PHPMailer) + +## Features +- Probably the world's most popular code for sending email from PHP! +- Used by many open-source projects: WordPress, Drupal, 1CRM, SugarCRM, Yii, Joomla! and many more +- Integrated SMTP support – send without a local mail server +- Send emails with multiple To, CC, BCC, and Reply-to addresses +- Multipart/alternative emails for mail clients that do not read HTML email +- Add attachments, including inline +- Support for UTF-8 content and 8bit, base64, binary, and quoted-printable encodings +- SMTP authentication with LOGIN, PLAIN, CRAM-MD5, and XOAUTH2 mechanisms over SMTPS and SMTP+STARTTLS transports +- Validates email addresses automatically +- Protects against header injection attacks +- Error messages in over 50 languages! +- DKIM and S/MIME signing support +- Compatible with PHP 5.5 and later, including PHP 8.2 +- Namespaced to prevent name clashes +- Much more! + +## Why you might need it +Many PHP developers need to send email from their code. The only PHP function that supports this directly is [`mail()`](https://www.php.net/manual/en/function.mail.php). However, it does not provide any assistance for making use of popular features such as encryption, authentication, HTML messages, and attachments. + +Formatting email correctly is surprisingly difficult. There are myriad overlapping (and conflicting) standards, requiring tight adherence to horribly complicated formatting and encoding rules – the vast majority of code that you'll find online that uses the `mail()` function directly is just plain wrong, if not unsafe! + +The PHP `mail()` function usually sends via a local mail server, typically fronted by a `sendmail` binary on Linux, BSD, and macOS platforms, however, Windows usually doesn't include a local mail server; PHPMailer's integrated SMTP client allows email sending on all platforms without needing a local mail server. Be aware though, that the `mail()` function should be avoided when possible; it's both faster and [safer](https://exploitbox.io/paper/Pwning-PHP-Mail-Function-For-Fun-And-RCE.html) to use SMTP to localhost. + +*Please* don't be tempted to do it yourself – if you don't use PHPMailer, there are many other excellent libraries that +you should look at before rolling your own. Try [SwiftMailer](https://swiftmailer.symfony.com/) +, [Laminas/Mail](https://docs.laminas.dev/laminas-mail/), [ZetaComponents](https://github.com/zetacomponents/Mail), etc. + +## License +This software is distributed under the [LGPL 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) license, along with the [GPL Cooperation Commitment](https://gplcc.github.io/gplcc/). Please read [LICENSE](https://github.com/PHPMailer/PHPMailer/blob/master/LICENSE) for information on the software availability and distribution. + +## Installation & loading +PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file: + +```json +"phpmailer/phpmailer": "^6.9.1" +``` + +or run + +```sh +composer require phpmailer/phpmailer +``` + +Note that the `vendor` folder and the `vendor/autoload.php` script are generated by Composer; they are not part of PHPMailer. + +If you want to use XOAUTH2 authentication, you will also need to add a dependency on the `league/oauth2-client` and appropriate service adapters package in your `composer.json`, or take a look at +by @decomplexity's [SendOauth2 wrapper](https://github.com/decomplexity/SendOauth2), especially if you're using Microsoft services. + +Alternatively, if you're not using Composer, you +can [download PHPMailer as a zip file](https://github.com/PHPMailer/PHPMailer/archive/master.zip), (note that docs and examples are not included in the zip file), then copy the contents of the PHPMailer folder into one of the `include_path` directories specified in your PHP configuration and load each class file manually: + +```php +SMTPDebug = SMTP::DEBUG_SERVER; //Enable verbose debug output + $mail->isSMTP(); //Send using SMTP + $mail->Host = 'smtp.example.com'; //Set the SMTP server to send through + $mail->SMTPAuth = true; //Enable SMTP authentication + $mail->Username = 'user@example.com'; //SMTP username + $mail->Password = 'secret'; //SMTP password + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; //Enable implicit TLS encryption + $mail->Port = 465; //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS` + + //Recipients + $mail->setFrom('from@example.com', 'Mailer'); + $mail->addAddress('joe@example.net', 'Joe User'); //Add a recipient + $mail->addAddress('ellen@example.com'); //Name is optional + $mail->addReplyTo('info@example.com', 'Information'); + $mail->addCC('cc@example.com'); + $mail->addBCC('bcc@example.com'); + + //Attachments + $mail->addAttachment('/var/tmp/file.tar.gz'); //Add attachments + $mail->addAttachment('/tmp/image.jpg', 'new.jpg'); //Optional name + + //Content + $mail->isHTML(true); //Set email format to HTML + $mail->Subject = 'Here is the subject'; + $mail->Body = 'This is the HTML message body in bold!'; + $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + + $mail->send(); + echo 'Message has been sent'; +} catch (Exception $e) { + echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}"; +} +``` + +You'll find plenty to play with in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder, which covers many common scenarios including sending through Gmail, building contact forms, sending to mailing lists, and more. + +If you are re-using the instance (e.g. when sending to a mailing list), you may need to clear the recipient list to avoid sending duplicate messages. See [the mailing list example](https://github.com/PHPMailer/PHPMailer/blob/master/examples/mailing_list.phps) for further guidance. + +That's it. You should now be ready to use PHPMailer! + +## Localization +PHPMailer defaults to English, but in the [language](https://github.com/PHPMailer/PHPMailer/tree/master/language/) folder, you'll find many translations for PHPMailer error messages that you may encounter. Their filenames contain [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) language code for the translations, for example `fr` for French. To specify a language, you need to tell PHPMailer which one to use, like this: + +```php +//To load the French version +$mail->setLanguage('fr', '/optional/path/to/language/directory/'); +``` + +We welcome corrections and new languages – if you're looking for corrections, run the [Language/TranslationCompletenessTest.php](https://github.com/PHPMailer/PHPMailer/blob/master/test/Language/TranslationCompletenessTest.php) script in the tests folder and it will show any missing translations. + +## Documentation +Start reading at the [GitHub wiki](https://github.com/PHPMailer/PHPMailer/wiki). If you're having trouble, head for [the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting) as it's frequently updated. + +Examples of how to use PHPMailer for common scenarios can be found in the [examples](https://github.com/PHPMailer/PHPMailer/tree/master/examples) folder. If you're looking for a good starting point, we recommend you start with [the Gmail example](https://github.com/PHPMailer/PHPMailer/tree/master/examples/gmail.phps). + +To reduce PHPMailer's deployed code footprint, examples are not included if you load PHPMailer via Composer or via [GitHub's zip file download](https://github.com/PHPMailer/PHPMailer/archive/master.zip), so you'll need to either clone the git repository or use the above links to get to the examples directly. + +Complete generated API documentation is [available online](https://phpmailer.github.io/PHPMailer/). + +You can generate complete API-level documentation by running `phpdoc` in the top-level folder, and documentation will appear in the `docs` folder, though you'll need to have [PHPDocumentor](http://www.phpdoc.org) installed. You may find [the unit tests](https://github.com/PHPMailer/PHPMailer/blob/master/test/PHPMailerTest.php) a good reference for how to do various operations such as encryption. + +If the documentation doesn't cover what you need, search the [many questions on Stack Overflow](http://stackoverflow.com/questions/tagged/phpmailer), and before you ask a question about "SMTP Error: Could not connect to SMTP host.", [read the troubleshooting guide](https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting). + +## Tests +[PHPMailer tests](https://github.com/PHPMailer/PHPMailer/tree/master/test/) use PHPUnit 9, with [a polyfill](https://github.com/Yoast/PHPUnit-Polyfills) to let 9-style tests run on older PHPUnit and PHP versions. + +[![Test status](https://github.com/PHPMailer/PHPMailer/workflows/Tests/badge.svg)](https://github.com/PHPMailer/PHPMailer/actions) + +If this isn't passing, is there something you can do to help? + +## Security +Please disclose any vulnerabilities found responsibly – report security issues to the maintainers privately. + +See [SECURITY](https://github.com/PHPMailer/PHPMailer/tree/master/SECURITY.md) and [PHPMailer's security advisories on GitHub](https://github.com/PHPMailer/PHPMailer/security). + +## Contributing +Please submit bug reports, suggestions, and pull requests to the [GitHub issue tracker](https://github.com/PHPMailer/PHPMailer/issues). + +We're particularly interested in fixing edge cases, expanding test coverage, and updating translations. + +If you found a mistake in the docs, or want to add something, go ahead and amend the wiki – anyone can edit it. + +If you have git clones from prior to the move to the PHPMailer GitHub organisation, you'll need to update any remote URLs referencing the old GitHub location with a command like this from within your clone: + +```sh +git remote set-url upstream https://github.com/PHPMailer/PHPMailer.git +``` + +Please *don't* use the SourceForge or Google Code projects any more; they are obsolete and no longer maintained. + +## Sponsorship +Development time and resources for PHPMailer are provided by [Smartmessages.net](https://info.smartmessages.net/), the world's only privacy-first email marketing system. + +Smartmessages.net privacy-first email marketing logo + +Donations are very welcome, whether in beer 🍺, T-shirts 👕, or cold, hard cash 💰. Sponsorship through GitHub is a simple and convenient way to say "thank you" to PHPMailer's maintainers and contributors – just click the "Sponsor" button [on the project page](https://github.com/PHPMailer/PHPMailer). If your company uses PHPMailer, consider taking part in Tidelift's enterprise support programme. + +## PHPMailer For Enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of PHPMailer and thousands of other packages are working with Tidelift to deliver commercial +support and maintenance for the open-source packages you use to build your applications. Save time, reduce risk, and +improve code health, while paying the maintainers of the exact packages you +use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-phpmailer-phpmailer?utm_source=packagist-phpmailer-phpmailer&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) + +## Changelog +See [changelog](changelog.md). + +## History +- PHPMailer was originally written in 2001 by Brent R. Matzelle as a [SourceForge project](http://sourceforge.net/projects/phpmailer/). +- [Marcus Bointon](https://github.com/Synchro) (`coolbru` on SF) and Andy Prevost (`codeworxtech`) took over the project in 2004. +- Became an Apache incubator project on Google Code in 2010, managed by Jim Jagielski. +- Marcus created [his fork on GitHub](https://github.com/Synchro/PHPMailer) in 2008. +- Jim and Marcus decide to join forces and use GitHub as the canonical and official repo for PHPMailer in 2013. +- PHPMailer moves to [the PHPMailer organisation](https://github.com/PHPMailer) on GitHub in 2013. + +### What's changed since moving from SourceForge? +- Official successor to the SourceForge and Google Code projects. +- Test suite. +- Continuous integration with GitHub Actions. +- Composer support. +- Public development. +- Additional languages and language strings. +- CRAM-MD5 authentication support. +- Preserves full repo history of authors, commits, and branches from the original SourceForge project. diff --git a/vendor/phpmailer/phpmailer/SECURITY.md b/vendor/phpmailer/phpmailer/SECURITY.md new file mode 100644 index 0000000..035a87f --- /dev/null +++ b/vendor/phpmailer/phpmailer/SECURITY.md @@ -0,0 +1,37 @@ +# Security notices relating to PHPMailer + +Please disclose any security issues or vulnerabilities found through [Tidelift's coordinated disclosure system](https://tidelift.com/security) or to the maintainers privately. + +PHPMailer 6.4.1 and earlier contain a vulnerability that can result in untrusted code being called (if such code is injected into the host project's scope by other means). If the `$patternselect` parameter to `validateAddress()` is set to `'php'` (the default, defined by `PHPMailer::$validator`), and the global namespace contains a function called `php`, it will be called in preference to the built-in validator of the same name. Mitigated in PHPMailer 6.5.0 by denying the use of simple strings as validator function names. Recorded as [CVE-2021-3603](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-3603). Reported by [Vikrant Singh Chauhan](mailto:vi@hackberry.xyz) via [huntr.dev](https://www.huntr.dev/). + +PHPMailer versions 6.4.1 and earlier contain a possible remote code execution vulnerability through the `$lang_path` parameter of the `setLanguage()` method. If the `$lang_path` parameter is passed unfiltered from user input, it can be set to [a UNC path](https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths), and if an attacker is also able to persuade the server to load a file from that UNC path, a script file under their control may be executed. This vulnerability only applies to systems that resolve UNC paths, typically only Microsoft Windows. +PHPMailer 6.5.0 mitigates this by no longer treating translation files as PHP code, but by parsing their text content directly. This approach avoids the possibility of executing unknown code while retaining backward compatibility. This isn't ideal, so the current translation format is deprecated and will be replaced in the next major release. Recorded as [CVE-2021-34551](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-34551). Reported by [Jilin Diting Information Technology Co., Ltd](https://listensec.com) via Tidelift. + +PHPMailer versions between 6.1.8 and 6.4.0 contain a regression of the earlier CVE-2018-19296 object injection vulnerability as a result of [a fix for Windows UNC paths in 6.1.8](https://github.com/PHPMailer/PHPMailer/commit/e2e07a355ee8ff36aba21d0242c5950c56e4c6f9). Recorded as [CVE-2020-36326](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-36326). Reported by Fariskhi Vidyan via Tidelift. 6.4.1 fixes this issue, and also enforces stricter checks for URL schemes in local path contexts. + +PHPMailer versions 6.1.5 and earlier contain an output escaping bug that occurs in `Content-Type` and `Content-Disposition` when filenames passed into `addAttachment` and other methods that accept attachment names contain double quote characters, in contravention of RFC822 3.4.1. No specific vulnerability has been found relating to this, but it could allow file attachments to bypass attachment filters that are based on matching filename extensions. Recorded as [CVE-2020-13625](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-13625). Reported by Elar Lang of Clarified Security. + +PHPMailer versions prior to 6.0.6 and 5.2.27 are vulnerable to an object injection attack by passing `phar://` paths into `addAttachment()` and other functions that may receive unfiltered local paths, possibly leading to RCE. Recorded as [CVE-2018-19296](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2018-19296). See [this article](https://knasmueller.net/5-answers-about-php-phar-exploitation) for more info on this type of vulnerability. Mitigated by blocking the use of paths containing URL-protocol style prefixes such as `phar://`. Reported by Sehun Oh of cyberone.kr. + +PHPMailer versions prior to 5.2.24 (released July 26th 2017) have an XSS vulnerability in one of the code examples, [CVE-2017-11503](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-11503). The `code_generator.phps` example did not filter user input prior to output. This file is distributed with a `.phps` extension, so it it not normally executable unless it is explicitly renamed, and the file is not included when PHPMailer is loaded through composer, so it is safe by default. There was also an undisclosed potential XSS vulnerability in the default exception handler (unused by default). Patches for both issues kindly provided by Patrick Monnerat of the Fedora Project. + +PHPMailer versions prior to 5.2.22 (released January 9th 2017) have a local file disclosure vulnerability, [CVE-2017-5223](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2017-5223). If content passed into `msgHTML()` is sourced from unfiltered user input, relative paths can map to absolute local file paths and added as attachments. Also note that `addAttachment` (just like `file_get_contents`, `passthru`, `unlink`, etc) should not be passed user-sourced params either! Reported by Yongxiang Li of Asiasecurity. + +PHPMailer versions prior to 5.2.20 (released December 28th 2016) are vulnerable to [CVE-2016-10045](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10045) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](https://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass.html), and patched by Paul Buonopane (@Zenexer). + +PHPMailer versions prior to 5.2.18 (released December 2016) are vulnerable to [CVE-2016-10033](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-10033) a remote code execution vulnerability, responsibly reported by [Dawid Golunski](http://legalhackers.com/advisories/PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10033-Vuln.html). + +PHPMailer versions prior to 5.2.14 (released November 2015) are vulnerable to [CVE-2015-8476](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-8476) an SMTP CRLF injection bug permitting arbitrary message sending. + +PHPMailer versions prior to 5.2.10 (released May 2015) are vulnerable to [CVE-2008-5619](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2008-5619), a remote code execution vulnerability in the bundled html2text library. This file was removed in 5.2.10, so if you are using a version prior to that and make use of the html2text function, it's vitally important that you upgrade and remove this file. + +PHPMailer versions prior to 2.0.7 and 2.2.1 are vulnerable to [CVE-2012-0796](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2012-0796), an email header injection attack. + +Joomla 1.6.0 uses PHPMailer in an unsafe way, allowing it to reveal local file paths, reported in [CVE-2011-3747](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2011-3747). + +PHPMailer didn't sanitise the `$lang_path` parameter in `SetLanguage`. This wasn't a problem in itself, but some apps (PHPClassifieds, ATutor) also failed to sanitise user-provided parameters passed to it, permitting semi-arbitrary local file inclusion, reported in [CVE-2010-4914](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2010-4914), [CVE-2007-2021](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-2021) and [CVE-2006-5734](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2006-5734). + +PHPMailer 1.7.2 and earlier contained a possible DDoS vulnerability reported in [CVE-2005-1807](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2005-1807). + +PHPMailer 1.7 and earlier (June 2003) have a possible vulnerability in the `SendmailSend` method where shell commands may not be sanitised. Reported in [CVE-2007-3215](https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2007-3215). + diff --git a/vendor/phpmailer/phpmailer/VERSION b/vendor/phpmailer/phpmailer/VERSION new file mode 100644 index 0000000..dc3829f --- /dev/null +++ b/vendor/phpmailer/phpmailer/VERSION @@ -0,0 +1 @@ +6.9.1 diff --git a/vendor/phpmailer/phpmailer/composer.json b/vendor/phpmailer/phpmailer/composer.json new file mode 100644 index 0000000..fa170a0 --- /dev/null +++ b/vendor/phpmailer/phpmailer/composer.json @@ -0,0 +1,79 @@ +{ + "name": "phpmailer/phpmailer", + "type": "library", + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "require": { + "php": ">=5.5.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.7.2", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "PHPMailer\\Test\\": "test/" + } + }, + "license": "LGPL-2.1-only", + "scripts": { + "check": "./vendor/bin/phpcs", + "test": "./vendor/bin/phpunit --no-coverage", + "coverage": "./vendor/bin/phpunit", + "lint": [ + "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . --show-deprecated -e php,phps --exclude vendor --exclude .git --exclude build" + ] + } +} diff --git a/vendor/phpmailer/phpmailer/get_oauth_token.php b/vendor/phpmailer/phpmailer/get_oauth_token.php new file mode 100644 index 0000000..cda0445 --- /dev/null +++ b/vendor/phpmailer/phpmailer/get_oauth_token.php @@ -0,0 +1,182 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/** + * Get an OAuth2 token from an OAuth2 provider. + * * Install this script on your server so that it's accessible + * as [https/http]:////get_oauth_token.php + * e.g.: http://localhost/phpmailer/get_oauth_token.php + * * Ensure dependencies are installed with 'composer install' + * * Set up an app in your Google/Yahoo/Microsoft account + * * Set the script address as the app's redirect URL + * If no refresh token is obtained when running this file, + * revoke access to your app and run the script again. + */ + +namespace PHPMailer\PHPMailer; + +/** + * Aliases for League Provider Classes + * Make sure you have added these to your composer.json and run `composer install` + * Plenty to choose from here: + * @see http://oauth2-client.thephpleague.com/providers/thirdparty/ + */ +//@see https://github.com/thephpleague/oauth2-google +use League\OAuth2\Client\Provider\Google; +//@see https://packagist.org/packages/hayageek/oauth2-yahoo +use Hayageek\OAuth2\Client\Provider\Yahoo; +//@see https://github.com/stevenmaguire/oauth2-microsoft +use Stevenmaguire\OAuth2\Client\Provider\Microsoft; +//@see https://github.com/greew/oauth2-azure-provider +use Greew\OAuth2\Client\Provider\Azure; + +if (!isset($_GET['code']) && !isset($_POST['provider'])) { + ?> + + +
      +

      Select Provider

      + +
      + +
      + +
      + +
      +

      Enter id and secret

      +

      These details are obtained by setting up an app in your provider's developer console. +

      +

      ClientId:

      +

      ClientSecret:

      +

      TenantID (only relevant for Azure):

      + +
      + + + $clientId, + 'clientSecret' => $clientSecret, + 'redirectUri' => $redirectUri, + 'accessType' => 'offline' +]; + +$options = []; +$provider = null; + +switch ($providerName) { + case 'Google': + $provider = new Google($params); + $options = [ + 'scope' => [ + 'https://mail.google.com/' + ] + ]; + break; + case 'Yahoo': + $provider = new Yahoo($params); + break; + case 'Microsoft': + $provider = new Microsoft($params); + $options = [ + 'scope' => [ + 'wl.imap', + 'wl.offline_access' + ] + ]; + break; + case 'Azure': + $params['tenantId'] = $tenantId; + + $provider = new Azure($params); + $options = [ + 'scope' => [ + 'https://outlook.office.com/SMTP.Send', + 'offline_access' + ] + ]; + break; +} + +if (null === $provider) { + exit('Provider missing'); +} + +if (!isset($_GET['code'])) { + //If we don't have an authorization code then get one + $authUrl = $provider->getAuthorizationUrl($options); + $_SESSION['oauth2state'] = $provider->getState(); + header('Location: ' . $authUrl); + exit; + //Check given state against previously stored one to mitigate CSRF attack +} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) { + unset($_SESSION['oauth2state']); + unset($_SESSION['provider']); + exit('Invalid state'); +} else { + unset($_SESSION['provider']); + //Try to get an access token (using the authorization code grant) + $token = $provider->getAccessToken( + 'authorization_code', + [ + 'code' => $_GET['code'] + ] + ); + //Use this to interact with an API on the users behalf + //Use this to get a new access token if the old one expires + echo 'Refresh Token: ', $token->getRefreshToken(); +} diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php new file mode 100644 index 0000000..0b2a72d --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-af.php @@ -0,0 +1,26 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'خطأ SMTP : لا يمكن تأكيد الهوية.'; +$PHPMAILER_LANG['connect_host'] = 'خطأ SMTP: لا يمكن الاتصال بالخادم SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'خطأ SMTP: لم يتم قبول المعلومات .'; +$PHPMAILER_LANG['empty_message'] = 'نص الرسالة فارغ'; +$PHPMAILER_LANG['encoding'] = 'ترميز غير معروف: '; +$PHPMAILER_LANG['execute'] = 'لا يمكن تنفيذ : '; +$PHPMAILER_LANG['file_access'] = 'لا يمكن الوصول للملف: '; +$PHPMAILER_LANG['file_open'] = 'خطأ في الملف: لا يمكن فتحه: '; +$PHPMAILER_LANG['from_failed'] = 'خطأ على مستوى عنوان المرسل : '; +$PHPMAILER_LANG['instantiate'] = 'لا يمكن توفير خدمة البريد.'; +$PHPMAILER_LANG['invalid_address'] = 'الإرسال غير ممكن لأن عنوان البريد الإلكتروني غير صالح: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' برنامج الإرسال غير مدعوم.'; +$PHPMAILER_LANG['provide_address'] = 'يجب توفير عنوان البريد الإلكتروني لمستلم واحد على الأقل.'; +$PHPMAILER_LANG['recipients_failed'] = 'خطأ SMTP: الأخطاء التالية فشل في الارسال لكل من : '; +$PHPMAILER_LANG['signing'] = 'خطأ في التوقيع: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() غير ممكن.'; +$PHPMAILER_LANG['smtp_error'] = 'خطأ على مستوى الخادم SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'لا يمكن تعيين أو إعادة تعيين متغير: '; +$PHPMAILER_LANG['extension_missing'] = 'الإضافة غير موجودة: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php new file mode 100644 index 0000000..327dfba --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-as.php @@ -0,0 +1,35 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP ত্ৰুটি: প্ৰমাণীকৰণ কৰিব নোৱাৰি'; +$PHPMAILER_LANG['buggy_php'] = 'আপোনাৰ PHP সংস্কৰণ এটা বাগৰ দ্বাৰা প্ৰভাৱিত হয় যাৰ ফলত নষ্ট বাৰ্তা হব পাৰে । ইয়াক সমাধান কৰিবলে, প্ৰেৰণ কৰিবলে SMTP ব্যৱহাৰ কৰক, আপোনাৰ php.ini ত mail.add_x_header বিকল্প নিষ্ক্ৰিয় কৰক, MacOS বা Linux লৈ সলনি কৰক, বা আপোনাৰ PHP সংস্কৰণ 7.0.17+ বা 7.1.3+ লৈ সলনি কৰক ।'; +$PHPMAILER_LANG['connect_host'] = 'SMTP ত্ৰুটি: SMTP চাৰ্ভাৰৰ সৈতে সংযোগ কৰিবলে অক্ষম'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্ৰুটি: তথ্য গ্ৰহণ কৰা হোৱা নাই'; +$PHPMAILER_LANG['empty_message'] = 'বাৰ্তাৰ মূখ্য অংশ খালী।'; +$PHPMAILER_LANG['encoding'] = 'অজ্ঞাত এনকোডিং: '; +$PHPMAILER_LANG['execute'] = 'এক্সিকিউট কৰিব নোৱাৰি: '; +$PHPMAILER_LANG['extension_missing'] = 'সম্প্ৰসাৰণ নোহোৱা হৈছে: '; +$PHPMAILER_LANG['file_access'] = 'ফাইল অভিগম কৰিবলে অক্ষম: '; +$PHPMAILER_LANG['file_open'] = 'ফাইল ত্ৰুটি: ফাইল খোলিবলৈ অক্ষম: '; +$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্ৰেৰকৰ ঠিকনা(সমূহ) ব্যৰ্থ: '; +$PHPMAILER_LANG['instantiate'] = 'মেইল ফাংচনৰ এটা উদাহৰণ সৃষ্টি কৰিবলে অক্ষম'; +$PHPMAILER_LANG['invalid_address'] = 'প্ৰেৰণ কৰিব নোৱাৰি: অবৈধ ইমেইল ঠিকনা: '; +$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডাৰৰ নাম বা মান'; +$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোষ্টেন্ট্ৰি: '; +$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হস্ট:'; +$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলাৰ সমৰ্থিত নহয়।'; +$PHPMAILER_LANG['provide_address'] = 'আপুনি অন্ততঃ এটা গন্তব্য ইমেইল ঠিকনা দিব লাগিব'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্ৰুটি: নিম্নলিখিত গন্তব্যস্থানসমূহ ব্যৰ্থ: '; +$PHPMAILER_LANG['signing'] = 'স্বাক্ষৰ কৰাত ব্যৰ্থ: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP কড: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'অতিৰিক্ত SMTP তথ্য: '; +$PHPMAILER_LANG['smtp_detail'] = 'বিৱৰণ:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যৰ্থ'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP চাৰ্ভাৰৰ ত্ৰুটি: '; +$PHPMAILER_LANG['variable_set'] = 'চলক নিৰ্ধাৰণ কৰিব পৰা নগল: '; +$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত সম্প্ৰসাৰণ: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php new file mode 100644 index 0000000..552167e --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-az.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela prijava.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Nije moguće spojiti se sa SMTP serverom.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznata kriptografija: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje sa navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedene e-mail adrese nije uspjelo: '; +$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.'; +$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definišite barem jednu adresu primaoca.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP server nije uspjelo.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP greška: '; +$PHPMAILER_LANG['variable_set'] = 'Nije moguće postaviti varijablu ili je vratiti nazad: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje ekstenzija: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php new file mode 100644 index 0000000..9e92dda --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-be.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Памылка SMTP: памылка ідэнтыфікацыі.'; +$PHPMAILER_LANG['connect_host'] = 'Памылка SMTP: нельга ўстанавіць сувязь з SMTP-серверам.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Памылка SMTP: звесткі непрынятыя.'; +$PHPMAILER_LANG['empty_message'] = 'Пустое паведамленне.'; +$PHPMAILER_LANG['encoding'] = 'Невядомая кадыроўка тэксту: '; +$PHPMAILER_LANG['execute'] = 'Нельга выканаць каманду: '; +$PHPMAILER_LANG['file_access'] = 'Няма доступу да файла: '; +$PHPMAILER_LANG['file_open'] = 'Нельга адкрыць файл: '; +$PHPMAILER_LANG['from_failed'] = 'Няправільны адрас адпраўніка: '; +$PHPMAILER_LANG['instantiate'] = 'Нельга прымяніць функцыю mail().'; +$PHPMAILER_LANG['invalid_address'] = 'Нельга даслаць паведамленне, няправільны email атрымальніка: '; +$PHPMAILER_LANG['provide_address'] = 'Запоўніце, калі ласка, правільны email атрымальніка.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - паштовы сервер не падтрымліваецца.'; +$PHPMAILER_LANG['recipients_failed'] = 'Памылка SMTP: няправільныя атрымальнікі: '; +$PHPMAILER_LANG['signing'] = 'Памылка подпісу паведамлення: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Памылка сувязі з SMTP-серверам.'; +$PHPMAILER_LANG['smtp_error'] = 'Памылка SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Нельга ўстанавіць або перамяніць значэнне пераменнай: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php new file mode 100644 index 0000000..c41f675 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-bg.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: Не може да се удостовери пред сървъра.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: Не може да се свърже с SMTP хоста.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: данните не са приети.'; +$PHPMAILER_LANG['empty_message'] = 'Съдържанието на съобщението е празно'; +$PHPMAILER_LANG['encoding'] = 'Неизвестно кодиране: '; +$PHPMAILER_LANG['execute'] = 'Не може да се изпълни: '; +$PHPMAILER_LANG['file_access'] = 'Няма достъп до файл: '; +$PHPMAILER_LANG['file_open'] = 'Файлова грешка: Не може да се отвори файл: '; +$PHPMAILER_LANG['from_failed'] = 'Следните адреси за подател са невалидни: '; +$PHPMAILER_LANG['instantiate'] = 'Не може да се инстанцира функцията mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Невалиден адрес: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' - пощенски сървър не се поддържа.'; +$PHPMAILER_LANG['provide_address'] = 'Трябва да предоставите поне един email адрес за получател.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: Следните адреси за Получател са невалидни: '; +$PHPMAILER_LANG['signing'] = 'Грешка при подписване: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP провален connect().'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP сървърна грешка: '; +$PHPMAILER_LANG['variable_set'] = 'Не може да се установи или възстанови променлива: '; +$PHPMAILER_LANG['extension_missing'] = 'Липсва разширение: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php new file mode 100644 index 0000000..4736510 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-bn.php @@ -0,0 +1,35 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP ত্রুটি: প্রমাণীকরণ করতে অক্ষম৷'; +$PHPMAILER_LANG['buggy_php'] = 'আপনার PHP সংস্করণ একটি বাগ দ্বারা প্রভাবিত হয় যার ফলে দূষিত বার্তা হতে পারে। এটি ঠিক করতে, পাঠাতে SMTP ব্যবহার করুন, আপনার php.ini এ mail.add_x_header বিকল্পটি নিষ্ক্রিয় করুন, MacOS বা Linux-এ স্যুইচ করুন, অথবা আপনার PHP সংস্করণকে 7.0.17+ বা 7.1.3+ এ পরিবর্তন করুন।'; +$PHPMAILER_LANG['connect_host'] = 'SMTP ত্রুটি: SMTP সার্ভারের সাথে সংযোগ করতে অক্ষম৷'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP ত্রুটি: ডেটা গ্রহণ করা হয়নি৷'; +$PHPMAILER_LANG['empty_message'] = 'বার্তার অংশটি খালি।'; +$PHPMAILER_LANG['encoding'] = 'অজানা এনকোডিং: '; +$PHPMAILER_LANG['execute'] = 'নির্বাহ করতে অক্ষম: '; +$PHPMAILER_LANG['extension_missing'] = 'এক্সটেনশন অনুপস্থিত:'; +$PHPMAILER_LANG['file_access'] = 'ফাইল অ্যাক্সেস করতে অক্ষম: '; +$PHPMAILER_LANG['file_open'] = 'ফাইল ত্রুটি: ফাইল খুলতে অক্ষম: '; +$PHPMAILER_LANG['from_failed'] = 'নিম্নলিখিত প্রেরকের ঠিকানা(গুলি) ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['instantiate'] = 'মেল ফাংশনের একটি উদাহরণ তৈরি করতে অক্ষম৷'; +$PHPMAILER_LANG['invalid_address'] = 'পাঠাতে অক্ষম: অবৈধ ইমেল ঠিকানা: '; +$PHPMAILER_LANG['invalid_header'] = 'অবৈধ হেডার নাম বা মান'; +$PHPMAILER_LANG['invalid_hostentry'] = 'অবৈধ হোস্টেন্ট্রি: '; +$PHPMAILER_LANG['invalid_host'] = 'অবৈধ হোস্ট:'; +$PHPMAILER_LANG['mailer_not_supported'] = 'মেইলার সমর্থিত নয়।'; +$PHPMAILER_LANG['provide_address'] = 'আপনাকে অবশ্যই অন্তত একটি গন্তব্য ইমেল ঠিকানা প্রদান করতে হবে৷'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP ত্রুটি: নিম্নলিখিত গন্তব্যগুলি ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['signing'] = 'স্বাক্ষর করতে ব্যর্থ হয়েছে: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP কোড: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'অতিরিক্ত SMTP তথ্য:'; +$PHPMAILER_LANG['smtp_detail'] = 'বর্ণনা: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP সংযোগ() ব্যর্থ হয়েছে৷'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP সার্ভার ত্রুটি: '; +$PHPMAILER_LANG['variable_set'] = 'পরিবর্তনশীল সেট করা যায়নি: '; +$PHPMAILER_LANG['extension_missing'] = 'অনুপস্থিত এক্সটেনশন: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php new file mode 100644 index 0000000..3468485 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ca.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Error SMTP: No s’ha pogut autenticar.'; +$PHPMAILER_LANG['connect_host'] = 'Error SMTP: No es pot connectar al servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Dades no acceptades.'; +$PHPMAILER_LANG['empty_message'] = 'El cos del missatge està buit.'; +$PHPMAILER_LANG['encoding'] = 'Codificació desconeguda: '; +$PHPMAILER_LANG['execute'] = 'No es pot executar: '; +$PHPMAILER_LANG['file_access'] = 'No es pot accedir a l’arxiu: '; +$PHPMAILER_LANG['file_open'] = 'Error d’Arxiu: No es pot obrir l’arxiu: '; +$PHPMAILER_LANG['from_failed'] = 'La(s) següent(s) adreces de remitent han fallat: '; +$PHPMAILER_LANG['instantiate'] = 'No s’ha pogut crear una instància de la funció Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Adreça d’email invalida: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no està suportat'; +$PHPMAILER_LANG['provide_address'] = 'S’ha de proveir almenys una adreça d’email com a destinatari.'; +$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Els següents destinataris han fallat: '; +$PHPMAILER_LANG['signing'] = 'Error al signar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ha fallat el SMTP Connect().'; +$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'No s’ha pogut establir o restablir la variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php new file mode 100644 index 0000000..e770a1a --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-cs.php @@ -0,0 +1,28 @@ + + * Rewrite and extension of the work by Mikael Stokkebro + * + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fejl: Login mislykkedes.'; +$PHPMAILER_LANG['buggy_php'] = 'Din version af PHP er berørt af en fejl, som gør at dine beskeder muligvis vises forkert. For at rette dette kan du skifte til SMTP, slå mail.add_x_header headeren i din php.ini fil fra, skifte til MacOS eller Linux eller opgradere din version af PHP til 7.0.17+ eller 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fejl: Forbindelse til SMTP serveren kunne ikke oprettes.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fejl: Data blev ikke accepteret.'; +$PHPMAILER_LANG['empty_message'] = 'Meddelelsen er uden indhold'; +$PHPMAILER_LANG['encoding'] = 'Ukendt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunne ikke afvikle: '; +$PHPMAILER_LANG['extension_missing'] = 'Udvidelse mangler: '; +$PHPMAILER_LANG['file_access'] = 'Kunne ikke tilgå filen: '; +$PHPMAILER_LANG['file_open'] = 'Fil fejl: Kunne ikke åbne filen: '; +$PHPMAILER_LANG['from_failed'] = 'Følgende afsenderadresse er forkert: '; +$PHPMAILER_LANG['instantiate'] = 'Email funktionen kunne ikke initialiseres.'; +$PHPMAILER_LANG['invalid_address'] = 'Udgyldig adresse: '; +$PHPMAILER_LANG['invalid_header'] = 'Ugyldig header navn eller værdi'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Ugyldig hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'Ugyldig vært: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer understøttes ikke.'; +$PHPMAILER_LANG['provide_address'] = 'Indtast mindst en modtagers email adresse.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fejl: Følgende modtagere fejlede: '; +$PHPMAILER_LANG['signing'] = 'Signeringsfejl: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP kode: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Yderligere SMTP info: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fejlede.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detalje: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP server fejl: '; +$PHPMAILER_LANG['variable_set'] = 'Kunne ikke definere eller nulstille variablen: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php new file mode 100644 index 0000000..e7e59d2 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-de.php @@ -0,0 +1,28 @@ + + * @author Crystopher Glodzienski Cardoso + */ + +$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.'; +$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.'; +$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.'; +$PHPMAILER_LANG['encoding'] = 'Codificación desconocida: '; +$PHPMAILER_LANG['execute'] = 'Imposible ejecutar: '; +$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: '; +$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: '; +$PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: '; +$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer no está soportado.'; +$PHPMAILER_LANG['provide_address'] = 'Debe proporcionar al menos una dirección de email de destino.'; +$PHPMAILER_LANG['recipients_failed'] = 'Error SMTP: Los siguientes destinos fallaron: '; +$PHPMAILER_LANG['signing'] = 'Error al firmar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.'; +$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: '; +$PHPMAILER_LANG['smtp_code'] = 'Código del servidor SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Información adicional del servidor SMTP: '; +$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido'; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php new file mode 100644 index 0000000..93addc9 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-et.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Viga: Autoriseerimise viga.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Viga: Ei õnnestunud luua ühendust SMTP serveriga.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Viga: Vigased andmed.'; +$PHPMAILER_LANG['empty_message'] = 'Tühi kirja sisu'; +$PHPMAILER_LANG["encoding"] = 'Tundmatu kodeering: '; +$PHPMAILER_LANG['execute'] = 'Tegevus ebaõnnestus: '; +$PHPMAILER_LANG['file_access'] = 'Pole piisavalt õiguseid järgneva faili avamiseks: '; +$PHPMAILER_LANG['file_open'] = 'Faili Viga: Faili avamine ebaõnnestus: '; +$PHPMAILER_LANG['from_failed'] = 'Järgnev saatja e-posti aadress on vigane: '; +$PHPMAILER_LANG['instantiate'] = 'mail funktiooni käivitamine ebaõnnestus.'; +$PHPMAILER_LANG['invalid_address'] = 'Saatmine peatatud, e-posti address vigane: '; +$PHPMAILER_LANG['provide_address'] = 'Te peate määrama vähemalt ühe saaja e-posti aadressi.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' maileri tugi puudub.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Viga: Järgnevate saajate e-posti aadressid on vigased: '; +$PHPMAILER_LANG["signing"] = 'Viga allkirjastamisel: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() ebaõnnestus.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP serveri viga: '; +$PHPMAILER_LANG['variable_set'] = 'Ei õnnestunud määrata või lähtestada muutujat: '; +$PHPMAILER_LANG['extension_missing'] = 'Nõutud laiendus on puudu: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php new file mode 100644 index 0000000..295a47f --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-fa.php @@ -0,0 +1,28 @@ + + * @author Mohammad Hossein Mojtahedi + */ + +$PHPMAILER_LANG['authenticate'] = 'خطای SMTP: احراز هویت با شکست مواجه شد.'; +$PHPMAILER_LANG['connect_host'] = 'خطای SMTP: اتصال به سرور SMTP برقرار نشد.'; +$PHPMAILER_LANG['data_not_accepted'] = 'خطای SMTP: داده‌ها نا‌درست هستند.'; +$PHPMAILER_LANG['empty_message'] = 'بخش متن پیام خالی است.'; +$PHPMAILER_LANG['encoding'] = 'کد‌گذاری نا‌شناخته: '; +$PHPMAILER_LANG['execute'] = 'امکان اجرا وجود ندارد: '; +$PHPMAILER_LANG['file_access'] = 'امکان دسترسی به فایل وجود ندارد: '; +$PHPMAILER_LANG['file_open'] = 'خطای File: امکان بازکردن فایل وجود ندارد: '; +$PHPMAILER_LANG['from_failed'] = 'آدرس فرستنده اشتباه است: '; +$PHPMAILER_LANG['instantiate'] = 'امکان معرفی تابع ایمیل وجود ندارد.'; +$PHPMAILER_LANG['invalid_address'] = 'آدرس ایمیل معتبر نیست: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer پشتیبانی نمی‌شود.'; +$PHPMAILER_LANG['provide_address'] = 'باید حداقل یک آدرس گیرنده وارد کنید.'; +$PHPMAILER_LANG['recipients_failed'] = 'خطای SMTP: ارسال به آدرس گیرنده با خطا مواجه شد: '; +$PHPMAILER_LANG['signing'] = 'خطا در امضا: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'خطا در اتصال به SMTP.'; +$PHPMAILER_LANG['smtp_error'] = 'خطا در SMTP Server: '; +$PHPMAILER_LANG['variable_set'] = 'امکان ارسال یا ارسال مجدد متغیر‌ها وجود ندارد: '; +$PHPMAILER_LANG['extension_missing'] = 'افزونه موجود نیست: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php new file mode 100644 index 0000000..6d1e637 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-fi.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP feilur: Kundi ikki góðkenna.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP feilur: Kundi ikki knýta samband við SMTP vert.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP feilur: Data ikki góðkent.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Ókend encoding: '; +$PHPMAILER_LANG['execute'] = 'Kundi ikki útføra: '; +$PHPMAILER_LANG['file_access'] = 'Kundi ikki tilganga fílu: '; +$PHPMAILER_LANG['file_open'] = 'Fílu feilur: Kundi ikki opna fílu: '; +$PHPMAILER_LANG['from_failed'] = 'fylgjandi Frá/From adressa miseydnaðist: '; +$PHPMAILER_LANG['instantiate'] = 'Kuni ikki instantiera mail funktión.'; +//$PHPMAILER_LANG['invalid_address'] = 'Invalid address: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' er ikki supporterað.'; +$PHPMAILER_LANG['provide_address'] = 'Tú skal uppgeva minst móttakara-emailadressu(r).'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Feilur: Fylgjandi móttakarar miseydnaðust: '; +//$PHPMAILER_LANG['signing'] = 'Signing Error: '; +//$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +//$PHPMAILER_LANG['smtp_error'] = 'SMTP server error: '; +//$PHPMAILER_LANG['variable_set'] = 'Cannot set or reset variable: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php new file mode 100644 index 0000000..0d367fc --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-fr.php @@ -0,0 +1,37 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro SMTP: Non puido ser autentificado.'; +$PHPMAILER_LANG['connect_host'] = 'Erro SMTP: Non puido conectar co servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro SMTP: Datos non aceptados.'; +$PHPMAILER_LANG['empty_message'] = 'Corpo da mensaxe vacía'; +$PHPMAILER_LANG['encoding'] = 'Codificación descoñecida: '; +$PHPMAILER_LANG['execute'] = 'Non puido ser executado: '; +$PHPMAILER_LANG['file_access'] = 'Nob puido acceder ó arquivo: '; +$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: No puido abrir o arquivo: '; +$PHPMAILER_LANG['from_failed'] = 'A(s) seguinte(s) dirección(s) de remitente(s) deron erro: '; +$PHPMAILER_LANG['instantiate'] = 'Non puido crear unha instancia da función Mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Non puido envia-lo correo: dirección de email inválida: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer non está soportado.'; +$PHPMAILER_LANG['provide_address'] = 'Debe engadir polo menos unha dirección de email coma destino.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro SMTP: Os seguintes destinos fallaron: '; +$PHPMAILER_LANG['signing'] = 'Erro ó firmar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro do servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Non puidemos axustar ou reaxustar a variábel: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php new file mode 100644 index 0000000..b123aa5 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-he.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'שגיאת SMTP: פעולת האימות נכשלה.'; +$PHPMAILER_LANG['connect_host'] = 'שגיאת SMTP: לא הצלחתי להתחבר לשרת SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'שגיאת SMTP: מידע לא התקבל.'; +$PHPMAILER_LANG['empty_message'] = 'גוף ההודעה ריק'; +$PHPMAILER_LANG['invalid_address'] = 'כתובת שגויה: '; +$PHPMAILER_LANG['encoding'] = 'קידוד לא מוכר: '; +$PHPMAILER_LANG['execute'] = 'לא הצלחתי להפעיל את: '; +$PHPMAILER_LANG['file_access'] = 'לא ניתן לגשת לקובץ: '; +$PHPMAILER_LANG['file_open'] = 'שגיאת קובץ: לא ניתן לגשת לקובץ: '; +$PHPMAILER_LANG['from_failed'] = 'כתובות הנמענים הבאות נכשלו: '; +$PHPMAILER_LANG['instantiate'] = 'לא הצלחתי להפעיל את פונקציית המייל.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' אינה נתמכת.'; +$PHPMAILER_LANG['provide_address'] = 'חובה לספק לפחות כתובת אחת של מקבל המייל.'; +$PHPMAILER_LANG['recipients_failed'] = 'שגיאת SMTP: הנמענים הבאים נכשלו: '; +$PHPMAILER_LANG['signing'] = 'שגיאת חתימה: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() failed.'; +$PHPMAILER_LANG['smtp_error'] = 'שגיאת שרת SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'לא ניתן לקבוע או לשנות את המשתנה: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php new file mode 100644 index 0000000..d2856e0 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-hi.php @@ -0,0 +1,35 @@ + + * Rewrite and extension of the work by Jayanti Suthar + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP त्रुटि: प्रामाणिकता की जांच नहीं हो सका। '; +$PHPMAILER_LANG['buggy_php'] = 'PHP का आपका संस्करण एक बग से प्रभावित है जिसके परिणामस्वरूप संदेश दूषित हो सकते हैं. इसे ठीक करने हेतु, भेजने के लिए SMTP का उपयोग करे, अपने php.ini में mail.add_x_header विकल्प को अक्षम करें, MacOS या Linux पर जाए, या अपने PHP संस्करण को 7.0.17+ या 7.1.3+ बदले.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP त्रुटि: SMTP सर्वर से कनेक्ट नहीं हो सका। '; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP त्रुटि: डेटा स्वीकार नहीं किया जाता है। '; +$PHPMAILER_LANG['empty_message'] = 'संदेश खाली है। '; +$PHPMAILER_LANG['encoding'] = 'अज्ञात एन्कोडिंग प्रकार। '; +$PHPMAILER_LANG['execute'] = 'आदेश को निष्पादित करने में विफल। '; +$PHPMAILER_LANG['extension_missing'] = 'एक्सटेन्षन गायब है: '; +$PHPMAILER_LANG['file_access'] = 'फ़ाइल उपलब्ध नहीं है। '; +$PHPMAILER_LANG['file_open'] = 'फ़ाइल त्रुटि: फाइल को खोला नहीं जा सका। '; +$PHPMAILER_LANG['from_failed'] = 'प्रेषक का पता गलत है। '; +$PHPMAILER_LANG['instantiate'] = 'मेल फ़ंक्शन कॉल नहीं कर सकता है।'; +$PHPMAILER_LANG['invalid_address'] = 'पता गलत है। '; +$PHPMAILER_LANG['invalid_header'] = 'अमान्य हेडर नाम या मान'; +$PHPMAILER_LANG['invalid_hostentry'] = 'अमान्य hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'अमान्य होस्ट: '; +$PHPMAILER_LANG['mailer_not_supported'] = 'मेल सर्वर के साथ काम नहीं करता है। '; +$PHPMAILER_LANG['provide_address'] = 'आपको कम से कम एक प्राप्तकर्ता का ई-मेल पता प्रदान करना होगा।'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP त्रुटि: निम्न प्राप्तकर्ताओं को पते भेजने में विफल। '; +$PHPMAILER_LANG['signing'] = 'साइनअप त्रुटि: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP कोड: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'अतिरिक्त SMTP जानकारी: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP का connect () फ़ंक्शन विफल हुआ। '; +$PHPMAILER_LANG['smtp_detail'] = 'विवरण: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP सर्वर त्रुटि। '; +$PHPMAILER_LANG['variable_set'] = 'चर को बना या संशोधित नहीं किया जा सकता। '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php new file mode 100644 index 0000000..cacb6c3 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-hr.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela autentikacija.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Ne mogu se spojiti na SMTP poslužitelj.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznati encoding: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje s navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedenih e-mail adresa nije uspjelo: '; +$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.'; +$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definirajte barem jednu adresu primatelja.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP poslužitelj nije uspjelo.'; +$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP poslužitelja: '; +$PHPMAILER_LANG['variable_set'] = 'Ne mogu postaviti varijablu niti ju vratiti nazad: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php new file mode 100644 index 0000000..e6b58b0 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-hu.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP -ի սխալ: չհաջողվեց ստուգել իսկությունը.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP -ի սխալ: չհաջողվեց կապ հաստատել SMTP սերվերի հետ.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP -ի սխալ: տվյալները ընդունված չեն.'; +$PHPMAILER_LANG['empty_message'] = 'Հաղորդագրությունը դատարկ է'; +$PHPMAILER_LANG['encoding'] = 'Կոդավորման անհայտ տեսակ: '; +$PHPMAILER_LANG['execute'] = 'Չհաջողվեց իրականացնել հրամանը: '; +$PHPMAILER_LANG['file_access'] = 'Ֆայլը հասանելի չէ: '; +$PHPMAILER_LANG['file_open'] = 'Ֆայլի սխալ: ֆայլը չհաջողվեց բացել: '; +$PHPMAILER_LANG['from_failed'] = 'Ուղարկողի հետևյալ հասցեն սխալ է: '; +$PHPMAILER_LANG['instantiate'] = 'Հնարավոր չէ կանչել mail ֆունկցիան.'; +$PHPMAILER_LANG['invalid_address'] = 'Հասցեն սխալ է: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' փոստային սերվերի հետ չի աշխատում.'; +$PHPMAILER_LANG['provide_address'] = 'Անհրաժեշտ է տրամադրել գոնե մեկ ստացողի e-mail հասցե.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP -ի սխալ: չի հաջողվել ուղարկել հետևյալ ստացողների հասցեներին: '; +$PHPMAILER_LANG['signing'] = 'Ստորագրման սխալ: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP -ի connect() ֆունկցիան չի հաջողվել'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP սերվերի սխալ: '; +$PHPMAILER_LANG['variable_set'] = 'Չի հաջողվում ստեղծել կամ վերափոխել փոփոխականը: '; +$PHPMAILER_LANG['extension_missing'] = 'Հավելվածը բացակայում է: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php new file mode 100644 index 0000000..212a11f --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-id.php @@ -0,0 +1,31 @@ + + * @author @januridp + * @author Ian Mustafa + */ + +$PHPMAILER_LANG['authenticate'] = 'Kesalahan SMTP: Tidak dapat mengotentikasi.'; +$PHPMAILER_LANG['connect_host'] = 'Kesalahan SMTP: Tidak dapat terhubung ke host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Kesalahan SMTP: Data tidak diterima.'; +$PHPMAILER_LANG['empty_message'] = 'Isi pesan kosong'; +$PHPMAILER_LANG['encoding'] = 'Pengkodean karakter tidak dikenali: '; +$PHPMAILER_LANG['execute'] = 'Tidak dapat menjalankan proses: '; +$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses berkas: '; +$PHPMAILER_LANG['file_open'] = 'Kesalahan Berkas: Berkas tidak dapat dibuka: '; +$PHPMAILER_LANG['from_failed'] = 'Alamat pengirim berikut mengakibatkan kesalahan: '; +$PHPMAILER_LANG['instantiate'] = 'Tidak dapat menginisialisasi fungsi surel.'; +$PHPMAILER_LANG['invalid_address'] = 'Gagal terkirim, alamat surel tidak sesuai: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Gagal terkirim, entri host tidak sesuai: '; +$PHPMAILER_LANG['invalid_host'] = 'Gagal terkirim, host tidak sesuai: '; +$PHPMAILER_LANG['provide_address'] = 'Harus tersedia minimal satu alamat tujuan'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tidak didukung'; +$PHPMAILER_LANG['recipients_failed'] = 'Kesalahan SMTP: Alamat tujuan berikut menyebabkan kesalahan: '; +$PHPMAILER_LANG['signing'] = 'Kesalahan dalam penandatangan SSL: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() gagal.'; +$PHPMAILER_LANG['smtp_error'] = 'Kesalahan pada pelayan SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tidak dapat mengatur atau mengatur ulang variabel: '; +$PHPMAILER_LANG['extension_missing'] = 'Ekstensi PHP tidak tersedia: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php new file mode 100644 index 0000000..08a6b73 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-it.php @@ -0,0 +1,28 @@ + + * @author Stefano Sabatini + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Impossibile autenticarsi.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Impossibile connettersi all\'host SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dati non accettati dal server.'; +$PHPMAILER_LANG['empty_message'] = 'Il corpo del messaggio è vuoto'; +$PHPMAILER_LANG['encoding'] = 'Codifica dei caratteri sconosciuta: '; +$PHPMAILER_LANG['execute'] = 'Impossibile eseguire l\'operazione: '; +$PHPMAILER_LANG['file_access'] = 'Impossibile accedere al file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Impossibile aprire il file: '; +$PHPMAILER_LANG['from_failed'] = 'I seguenti indirizzi mittenti hanno generato errore: '; +$PHPMAILER_LANG['instantiate'] = 'Impossibile istanziare la funzione mail'; +$PHPMAILER_LANG['invalid_address'] = 'Impossibile inviare, l\'indirizzo email non è valido: '; +$PHPMAILER_LANG['provide_address'] = 'Deve essere fornito almeno un indirizzo ricevente'; +$PHPMAILER_LANG['mailer_not_supported'] = 'Mailer non supportato'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: I seguenti indirizzi destinatari hanno generato un errore: '; +$PHPMAILER_LANG['signing'] = 'Errore nella firma: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() fallita.'; +$PHPMAILER_LANG['smtp_error'] = 'Errore del server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Impossibile impostare o resettare la variabile: '; +$PHPMAILER_LANG['extension_missing'] = 'Estensione mancante: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php new file mode 100644 index 0000000..c76f526 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ja.php @@ -0,0 +1,29 @@ + + * @author Yoshi Sakai + * @author Arisophy + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTPエラー: 認証できませんでした。'; +$PHPMAILER_LANG['connect_host'] = 'SMTPエラー: SMTPホストに接続できませんでした。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTPエラー: データが受け付けられませんでした。'; +$PHPMAILER_LANG['empty_message'] = 'メール本文が空です。'; +$PHPMAILER_LANG['encoding'] = '不明なエンコーディング: '; +$PHPMAILER_LANG['execute'] = '実行できませんでした: '; +$PHPMAILER_LANG['file_access'] = 'ファイルにアクセスできません: '; +$PHPMAILER_LANG['file_open'] = 'ファイルエラー: ファイルを開けません: '; +$PHPMAILER_LANG['from_failed'] = 'Fromアドレスを登録する際にエラーが発生しました: '; +$PHPMAILER_LANG['instantiate'] = 'メール関数が正常に動作しませんでした。'; +$PHPMAILER_LANG['invalid_address'] = '不正なメールアドレス: '; +$PHPMAILER_LANG['provide_address'] = '少なくとも1つメールアドレスを 指定する必要があります。'; +$PHPMAILER_LANG['mailer_not_supported'] = ' メーラーがサポートされていません。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTPエラー: 次の受信者アドレスに 間違いがあります: '; +$PHPMAILER_LANG['signing'] = '署名エラー: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP接続に失敗しました。'; +$PHPMAILER_LANG['smtp_error'] = 'SMTPサーバーエラー: '; +$PHPMAILER_LANG['variable_set'] = '変数が存在しません: '; +$PHPMAILER_LANG['extension_missing'] = '拡張機能が見つかりません: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php new file mode 100644 index 0000000..51fe403 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ka.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP შეცდომა: ავტორიზაცია შეუძლებელია.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP შეცდომა: SMTP სერვერთან დაკავშირება შეუძლებელია.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP შეცდომა: მონაცემები არ იქნა მიღებული.'; +$PHPMAILER_LANG['encoding'] = 'კოდირების უცნობი ტიპი: '; +$PHPMAILER_LANG['execute'] = 'შეუძლებელია შემდეგი ბრძანების შესრულება: '; +$PHPMAILER_LANG['file_access'] = 'შეუძლებელია წვდომა ფაილთან: '; +$PHPMAILER_LANG['file_open'] = 'ფაილური სისტემის შეცდომა: არ იხსნება ფაილი: '; +$PHPMAILER_LANG['from_failed'] = 'გამგზავნის არასწორი მისამართი: '; +$PHPMAILER_LANG['instantiate'] = 'mail ფუნქციის გაშვება ვერ ხერხდება.'; +$PHPMAILER_LANG['provide_address'] = 'გთხოვთ მიუთითოთ ერთი ადრესატის e-mail მისამართი მაინც.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - საფოსტო სერვერის მხარდაჭერა არ არის.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP შეცდომა: შემდეგ მისამართებზე გაგზავნა ვერ მოხერხდა: '; +$PHPMAILER_LANG['empty_message'] = 'შეტყობინება ცარიელია'; +$PHPMAILER_LANG['invalid_address'] = 'არ გაიგზავნა, e-mail მისამართის არასწორი ფორმატი: '; +$PHPMAILER_LANG['signing'] = 'ხელმოწერის შეცდომა: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'შეცდომა SMTP სერვერთან დაკავშირებისას'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP სერვერის შეცდომა: '; +$PHPMAILER_LANG['variable_set'] = 'შეუძლებელია შემდეგი ცვლადის შექმნა ან შეცვლა: '; +$PHPMAILER_LANG['extension_missing'] = 'ბიბლიოთეკა არ არსებობს: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php new file mode 100644 index 0000000..8c97dd9 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ko.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 오류: 인증할 수 없습니다.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 오류: SMTP 호스트에 접속할 수 없습니다.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 오류: 데이터가 받아들여지지 않았습니다.'; +$PHPMAILER_LANG['empty_message'] = '메세지 내용이 없습니다'; +$PHPMAILER_LANG['encoding'] = '알 수 없는 인코딩: '; +$PHPMAILER_LANG['execute'] = '실행 불가: '; +$PHPMAILER_LANG['file_access'] = '파일 접근 불가: '; +$PHPMAILER_LANG['file_open'] = '파일 오류: 파일을 열 수 없습니다: '; +$PHPMAILER_LANG['from_failed'] = '다음 From 주소에서 오류가 발생했습니다: '; +$PHPMAILER_LANG['instantiate'] = 'mail 함수를 인스턴스화할 수 없습니다'; +$PHPMAILER_LANG['invalid_address'] = '잘못된 주소: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' 메일러는 지원되지 않습니다.'; +$PHPMAILER_LANG['provide_address'] = '적어도 한 개 이상의 수신자 메일 주소를 제공해야 합니다.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 오류: 다음 수신자에서 오류가 발생했습니다: '; +$PHPMAILER_LANG['signing'] = '서명 오류: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 연결을 실패하였습니다.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP 서버 오류: '; +$PHPMAILER_LANG['variable_set'] = '변수 설정 및 초기화 불가: '; +$PHPMAILER_LANG['extension_missing'] = '확장자 없음: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php new file mode 100644 index 0000000..4f115b1 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-lt.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP klaida: autentifikacija nepavyko.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP klaida: nepavyksta prisijungti prie SMTP stoties.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP klaida: duomenys nepriimti.'; +$PHPMAILER_LANG['empty_message'] = 'Laiško turinys tuščias'; +$PHPMAILER_LANG['encoding'] = 'Neatpažinta koduotė: '; +$PHPMAILER_LANG['execute'] = 'Nepavyko įvykdyti komandos: '; +$PHPMAILER_LANG['file_access'] = 'Byla nepasiekiama: '; +$PHPMAILER_LANG['file_open'] = 'Bylos klaida: Nepavyksta atidaryti: '; +$PHPMAILER_LANG['from_failed'] = 'Neteisingas siuntėjo adresas: '; +$PHPMAILER_LANG['instantiate'] = 'Nepavyko paleisti mail funkcijos.'; +$PHPMAILER_LANG['invalid_address'] = 'Neteisingas adresas: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' pašto stotis nepalaikoma.'; +$PHPMAILER_LANG['provide_address'] = 'Nurodykite bent vieną gavėjo adresą.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP klaida: nepavyko išsiųsti šiems gavėjams: '; +$PHPMAILER_LANG['signing'] = 'Prisijungimo klaida: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP susijungimo klaida'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP stoties klaida: '; +$PHPMAILER_LANG['variable_set'] = 'Nepavyko priskirti reikšmės kintamajam: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php new file mode 100644 index 0000000..679b18c --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-lv.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP kļūda: Autorizācija neizdevās.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Kļūda: Nevar izveidot savienojumu ar SMTP serveri.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Kļūda: Nepieņem informāciju.'; +$PHPMAILER_LANG['empty_message'] = 'Ziņojuma teksts ir tukšs'; +$PHPMAILER_LANG['encoding'] = 'Neatpazīts kodējums: '; +$PHPMAILER_LANG['execute'] = 'Neizdevās izpildīt komandu: '; +$PHPMAILER_LANG['file_access'] = 'Fails nav pieejams: '; +$PHPMAILER_LANG['file_open'] = 'Faila kļūda: Nevar atvērt failu: '; +$PHPMAILER_LANG['from_failed'] = 'Nepareiza sūtītāja adrese: '; +$PHPMAILER_LANG['instantiate'] = 'Nevar palaist sūtīšanas funkciju.'; +$PHPMAILER_LANG['invalid_address'] = 'Nepareiza adrese: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' sūtītājs netiek atbalstīts.'; +$PHPMAILER_LANG['provide_address'] = 'Lūdzu, norādiet vismaz vienu adresātu.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP kļūda: neizdevās nosūtīt šādiem saņēmējiem: '; +$PHPMAILER_LANG['signing'] = 'Autorizācijas kļūda: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP savienojuma kļūda'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP servera kļūda: '; +$PHPMAILER_LANG['variable_set'] = 'Nevar piešķirt mainīgā vērtību: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php new file mode 100644 index 0000000..8a94f6a --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-mg.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Hadisoana SMTP: Tsy nahomby ny fanamarinana.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Tsy afaka mampifandray amin\'ny mpampiantrano SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP diso: tsy voarakitra ny angona.'; +$PHPMAILER_LANG['empty_message'] = 'Tsy misy ny votoaty mailaka.'; +$PHPMAILER_LANG['encoding'] = 'Tsy fantatra encoding: '; +$PHPMAILER_LANG['execute'] = 'Tsy afaka manatanteraka ity baiko manaraka ity: '; +$PHPMAILER_LANG['file_access'] = 'Tsy nahomby ny fidirana amin\'ity rakitra ity: '; +$PHPMAILER_LANG['file_open'] = 'Hadisoana diso: Tsy afaka nanokatra ity file manaraka ity: '; +$PHPMAILER_LANG['from_failed'] = 'Ny adiresy iraka manaraka dia diso: '; +$PHPMAILER_LANG['instantiate'] = 'Tsy afaka nanomboka ny hetsika mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Tsy mety ny adiresy: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer tsy manohana.'; +$PHPMAILER_LANG['provide_address'] = 'Alefaso azafady iray adiresy iray farafahakeliny.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Tsy mety ireo mpanaraka ireto: '; +$PHPMAILER_LANG['signing'] = 'Error nandritra ny sonia:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Tsy nahomby ny fifandraisana tamin\'ny server SMTP.'; +$PHPMAILER_LANG['smtp_error'] = 'Fahadisoana tamin\'ny server SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tsy azo atao ny mametraka na mamerina ny variable: '; +$PHPMAILER_LANG['extension_missing'] = 'Tsy hita ny ampahany: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php new file mode 100644 index 0000000..04d262c --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-mn.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Ralat SMTP: Tidak dapat pengesahan.'; +$PHPMAILER_LANG['connect_host'] = 'Ralat SMTP: Tidak dapat menghubungi hos pelayan SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ralat SMTP: Data tidak diterima oleh pelayan.'; +$PHPMAILER_LANG['empty_message'] = 'Tiada isi untuk mesej'; +$PHPMAILER_LANG['encoding'] = 'Pengekodan tidak diketahui: '; +$PHPMAILER_LANG['execute'] = 'Tidak dapat melaksanakan: '; +$PHPMAILER_LANG['file_access'] = 'Tidak dapat mengakses fail: '; +$PHPMAILER_LANG['file_open'] = 'Ralat Fail: Tidak dapat membuka fail: '; +$PHPMAILER_LANG['from_failed'] = 'Berikut merupakan ralat dari alamat e-mel: '; +$PHPMAILER_LANG['instantiate'] = 'Tidak dapat memberi contoh fungsi e-mel.'; +$PHPMAILER_LANG['invalid_address'] = 'Alamat emel tidak sah: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' jenis penghantar emel tidak disokong.'; +$PHPMAILER_LANG['provide_address'] = 'Anda perlu menyediakan sekurang-kurangnya satu alamat e-mel penerima.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ralat SMTP: Penerima e-mel berikut telah gagal: '; +$PHPMAILER_LANG['signing'] = 'Ralat pada tanda tangan: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() telah gagal.'; +$PHPMAILER_LANG['smtp_error'] = 'Ralat pada pelayan SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Tidak boleh menetapkan atau menetapkan semula pembolehubah: '; +$PHPMAILER_LANG['extension_missing'] = 'Sambungan hilang: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php new file mode 100644 index 0000000..c9621a1 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-nb.php @@ -0,0 +1,33 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP-fout: authenticatie mislukt.'; +$PHPMAILER_LANG['buggy_php'] = 'PHP versie gededecteerd die onderhavig is aan een bug die kan resulteren in gecorrumpeerde berichten. Om dit te voorkomen, gebruik SMTP voor het verzenden van berichten, zet de mail.add_x_header optie in uw php.ini file uit, gebruik MacOS of Linux, of pas de gebruikte PHP versie aan naar versie 7.0.17+ or 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP-fout: kon niet verbinden met SMTP-host.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP-fout: data niet geaccepteerd.'; +$PHPMAILER_LANG['empty_message'] = 'Berichttekst is leeg'; +$PHPMAILER_LANG['encoding'] = 'Onbekende codering: '; +$PHPMAILER_LANG['execute'] = 'Kon niet uitvoeren: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensie afwezig: '; +$PHPMAILER_LANG['file_access'] = 'Kreeg geen toegang tot bestand: '; +$PHPMAILER_LANG['file_open'] = 'Bestandsfout: kon bestand niet openen: '; +$PHPMAILER_LANG['from_failed'] = 'Het volgende afzendersadres is mislukt: '; +$PHPMAILER_LANG['instantiate'] = 'Kon mailfunctie niet initialiseren.'; +$PHPMAILER_LANG['invalid_address'] = 'Ongeldig adres: '; +$PHPMAILER_LANG['invalid_header'] = 'Ongeldige header naam of waarde'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Ongeldige hostentry: '; +$PHPMAILER_LANG['invalid_host'] = 'Ongeldige host: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer wordt niet ondersteund.'; +$PHPMAILER_LANG['provide_address'] = 'Er moet minstens één ontvanger worden opgegeven.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP-fout: de volgende ontvangers zijn mislukt: '; +$PHPMAILER_LANG['signing'] = 'Signeerfout: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP code: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Aanvullende SMTP informatie: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Verbinding mislukt.'; +$PHPMAILER_LANG['smtp_detail'] = 'Detail: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfout: '; +$PHPMAILER_LANG['variable_set'] = 'Kan de volgende variabele niet instellen of resetten: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php new file mode 100644 index 0000000..cb7b2c2 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-pl.php @@ -0,0 +1,33 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro do SMTP: Não foi possível realizar a autenticação.'; +$PHPMAILER_LANG['connect_host'] = 'Erro do SMTP: Não foi possível realizar ligação com o servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro do SMTP: Os dados foram rejeitados.'; +$PHPMAILER_LANG['empty_message'] = 'A mensagem no e-mail está vazia.'; +$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; +$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível aceder o ficheiro: '; +$PHPMAILER_LANG['file_open'] = 'Abertura do ficheiro: Não foi possível abrir o ficheiro: '; +$PHPMAILER_LANG['from_failed'] = 'Ocorreram falhas nos endereços dos seguintes remententes: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível iniciar uma instância da função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Não foi enviado nenhum e-mail para o endereço de e-mail inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Tem de fornecer pelo menos um endereço como destinatário do e-mail.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro do SMTP: O endereço do seguinte destinatário falhou: '; +$PHPMAILER_LANG['signing'] = 'Erro ao assinar: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; +$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensão em falta: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php new file mode 100644 index 0000000..5239865 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-pt_br.php @@ -0,0 +1,38 @@ + + * @author Lucas Guimarães + * @author Phelipe Alves + * @author Fabio Beneditto + * @author Geidson Benício Coelho + */ + +$PHPMAILER_LANG['authenticate'] = 'Erro de SMTP: Não foi possível autenticar.'; +$PHPMAILER_LANG['buggy_php'] = 'Sua versão do PHP é afetada por um bug que por resultar em messagens corrompidas. Para corrigir, mude para enviar usando SMTP, desative a opção mail.add_x_header em seu php.ini, mude para MacOS ou Linux, ou atualize seu PHP para versão 7.0.17+ ou 7.1.3+ '; +$PHPMAILER_LANG['connect_host'] = 'Erro de SMTP: Não foi possível conectar ao servidor SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Erro de SMTP: Dados rejeitados.'; +$PHPMAILER_LANG['empty_message'] = 'Mensagem vazia'; +$PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: '; +$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; +$PHPMAILER_LANG['extension_missing'] = 'Extensão não existe: '; +$PHPMAILER_LANG['file_access'] = 'Não foi possível acessar o arquivo: '; +$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: Não foi possível abrir o arquivo: '; +$PHPMAILER_LANG['from_failed'] = 'Os seguintes remetentes falharam: '; +$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.'; +$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: '; +$PHPMAILER_LANG['invalid_header'] = 'Nome ou valor de cabeçalho inválido'; +$PHPMAILER_LANG['invalid_hostentry'] = 'hostentry inválido: '; +$PHPMAILER_LANG['invalid_host'] = 'host inválido: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Você deve informar pelo menos um destinatário.'; +$PHPMAILER_LANG['recipients_failed'] = 'Erro de SMTP: Os seguintes destinatários falharam: '; +$PHPMAILER_LANG['signing'] = 'Erro de Assinatura: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falhou.'; +$PHPMAILER_LANG['smtp_code'] = 'Código do servidor SMTP: '; +$PHPMAILER_LANG['smtp_error'] = 'Erro de servidor SMTP: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Informações adicionais do servidor SMTP: '; +$PHPMAILER_LANG['smtp_detail'] = 'Detalhes do servidor SMTP: '; +$PHPMAILER_LANG['variable_set'] = 'Não foi possível definir ou redefinir a variável: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php new file mode 100644 index 0000000..45bef91 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-ro.php @@ -0,0 +1,33 @@ + + * @author Foster Snowhill + */ + +$PHPMAILER_LANG['authenticate'] = 'Ошибка SMTP: ошибка авторизации.'; +$PHPMAILER_LANG['connect_host'] = 'Ошибка SMTP: не удается подключиться к SMTP-серверу.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Ошибка SMTP: данные не приняты.'; +$PHPMAILER_LANG['encoding'] = 'Неизвестная кодировка: '; +$PHPMAILER_LANG['execute'] = 'Невозможно выполнить команду: '; +$PHPMAILER_LANG['file_access'] = 'Нет доступа к файлу: '; +$PHPMAILER_LANG['file_open'] = 'Файловая ошибка: не удаётся открыть файл: '; +$PHPMAILER_LANG['from_failed'] = 'Неверный адрес отправителя: '; +$PHPMAILER_LANG['instantiate'] = 'Невозможно запустить функцию mail().'; +$PHPMAILER_LANG['provide_address'] = 'Пожалуйста, введите хотя бы один email-адрес получателя.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' — почтовый сервер не поддерживается.'; +$PHPMAILER_LANG['recipients_failed'] = 'Ошибка SMTP: не удалась отправка таким адресатам: '; +$PHPMAILER_LANG['empty_message'] = 'Пустое сообщение'; +$PHPMAILER_LANG['invalid_address'] = 'Не отправлено из-за неправильного формата email-адреса: '; +$PHPMAILER_LANG['signing'] = 'Ошибка подписи: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ошибка соединения с SMTP-сервером'; +$PHPMAILER_LANG['smtp_error'] = 'Ошибка SMTP-сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Невозможно установить или сбросить переменную: '; +$PHPMAILER_LANG['extension_missing'] = 'Расширение отсутствует: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php new file mode 100644 index 0000000..dce502a --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-si.php @@ -0,0 +1,34 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP දෝෂය: සත්‍යාපනය අසාර්ථක විය.'; +$PHPMAILER_LANG['buggy_php'] = 'ඔබගේ PHP version එකෙහි පවතින දෝෂයක් නිසා email පණිවිඩ දෝෂ සහගත වීමේ හැකියාවක් ඇත. මෙය විසදීම සදහා SMTP භාවිතා කිරීම, mail.add_x_header INI setting එක අක්‍රීය කිරීම, MacOS හෝ Linux වලට මාරු වීම, හෝ ඔබගේ PHP version එක 7.0.17+ හෝ 7.1.3+ වලට අලුත් කිරීම කරගන්න.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP දෝෂය: සම්බන්ධ වීමට නොහැකි විය.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP දෝෂය: දත්ත පිළිගනු නොලැබේ.'; +$PHPMAILER_LANG['empty_message'] = 'පණිවිඩ අන්තර්ගතය හිස්'; +$PHPMAILER_LANG['encoding'] = 'නොදන්නා කේතනය: '; +$PHPMAILER_LANG['execute'] = 'ක්‍රියාත්මක කළ නොහැකි විය: '; +$PHPMAILER_LANG['extension_missing'] = 'Extension එක නොමැත: '; +$PHPMAILER_LANG['file_access'] = 'File එකට ප්‍රවේශ විය නොහැකි විය: '; +$PHPMAILER_LANG['file_open'] = 'File දෝෂය: File එක විවෘත කළ නොහැක: '; +$PHPMAILER_LANG['from_failed'] = 'පහත From ලිපිනයන් අසාර්ථක විය: '; +$PHPMAILER_LANG['instantiate'] = 'mail function එක ක්‍රියාත්මක කළ නොහැක.'; +$PHPMAILER_LANG['invalid_address'] = 'වලංගු නොවන ලිපිනය: '; +$PHPMAILER_LANG['invalid_header'] = 'වලංගු නොවන header නාමයක් හෝ අගයක්'; +$PHPMAILER_LANG['invalid_hostentry'] = 'වලංගු නොවන hostentry එකක්: '; +$PHPMAILER_LANG['invalid_host'] = 'වලංගු නොවන host එකක්: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer සහාය නොදක්වයි.'; +$PHPMAILER_LANG['provide_address'] = 'ඔබ අවම වශයෙන් එක් ලබන්නෙකුගේ ඊමේල් ලිපිනයක් සැපයිය යුතුය.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP දෝෂය: පහත ලබන්නන් අසමත් විය: '; +$PHPMAILER_LANG['signing'] = 'Sign කිරීමේ දෝෂය: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP කේතය: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'අමතර SMTP තොරතුරු: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP සම්බන්ධය අසාර්ථක විය.'; +$PHPMAILER_LANG['smtp_detail'] = 'තොරතුරු: '; +$PHPMAILER_LANG['smtp_error'] = 'SMTP දෝෂය: '; +$PHPMAILER_LANG['variable_set'] = 'Variable එක සැකසීමට හෝ නැවත සැකසීමට නොහැක: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php new file mode 100644 index 0000000..028f5bc --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sk.php @@ -0,0 +1,30 @@ + + * @author Peter Orlický + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Chyba autentifikácie.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Nebolo možné nadviazať spojenie so SMTP serverom.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Dáta neboli prijaté'; +$PHPMAILER_LANG['empty_message'] = 'Prázdne telo správy.'; +$PHPMAILER_LANG['encoding'] = 'Neznáme kódovanie: '; +$PHPMAILER_LANG['execute'] = 'Nedá sa vykonať: '; +$PHPMAILER_LANG['file_access'] = 'Súbor nebol nájdený: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Súbor sa otvoriť pre čítanie: '; +$PHPMAILER_LANG['from_failed'] = 'Následujúca adresa From je nesprávna: '; +$PHPMAILER_LANG['instantiate'] = 'Nedá sa vytvoriť inštancia emailovej funkcie.'; +$PHPMAILER_LANG['invalid_address'] = 'Neodoslané, emailová adresa je nesprávna: '; +$PHPMAILER_LANG['invalid_hostentry'] = 'Záznam hostiteľa je nesprávny: '; +$PHPMAILER_LANG['invalid_host'] = 'Hostiteľ je nesprávny: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' emailový klient nieje podporovaný.'; +$PHPMAILER_LANG['provide_address'] = 'Musíte zadať aspoň jednu emailovú adresu príjemcu.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Adresy príjemcov niesu správne '; +$PHPMAILER_LANG['signing'] = 'Chyba prihlasovania: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() zlyhalo.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP chyba serveru: '; +$PHPMAILER_LANG['variable_set'] = 'Nemožno nastaviť alebo resetovať premennú: '; +$PHPMAILER_LANG['extension_missing'] = 'Chýba rozšírenie: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php new file mode 100644 index 0000000..3e00c25 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sl.php @@ -0,0 +1,36 @@ + + * @author Filip Š + * @author Blaž Oražem + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP napaka: Avtentikacija ni uspela.'; +$PHPMAILER_LANG['buggy_php'] = 'Na vašo PHP različico vpliva napaka, ki lahko povzroči poškodovana sporočila. Če želite težavo odpraviti, preklopite na pošiljanje prek SMTP, onemogočite možnost mail.add_x_header v vaši php.ini datoteki, preklopite na MacOS ali Linux, ali nadgradite vašo PHP zaličico na 7.0.17+ ali 7.1.3+.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP napaka: Vzpostavljanje povezave s SMTP gostiteljem ni uspelo.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP napaka: Strežnik zavrača podatke.'; +$PHPMAILER_LANG['empty_message'] = 'E-poštno sporočilo nima vsebine.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznan tip kodiranja: '; +$PHPMAILER_LANG['execute'] = 'Operacija ni uspela: '; +$PHPMAILER_LANG['extension_missing'] = 'Manjkajoča razširitev: '; +$PHPMAILER_LANG['file_access'] = 'Nimam dostopa do datoteke: '; +$PHPMAILER_LANG['file_open'] = 'Ne morem odpreti datoteke: '; +$PHPMAILER_LANG['from_failed'] = 'Neveljaven e-naslov pošiljatelja: '; +$PHPMAILER_LANG['instantiate'] = 'Ne morem inicializirati mail funkcije.'; +$PHPMAILER_LANG['invalid_address'] = 'E-poštno sporočilo ni bilo poslano. E-naslov je neveljaven: '; +$PHPMAILER_LANG['invalid_header'] = 'Neveljavno ime ali vrednost glave'; +$PHPMAILER_LANG['invalid_hostentry'] = 'Neveljaven vnos gostitelja: '; +$PHPMAILER_LANG['invalid_host'] = 'Neveljaven gostitelj: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer ni podprt.'; +$PHPMAILER_LANG['provide_address'] = 'Prosimo, vnesite vsaj enega naslovnika.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP napaka: Sledeči naslovniki so neveljavni: '; +$PHPMAILER_LANG['signing'] = 'Napaka pri podpisovanju: '; +$PHPMAILER_LANG['smtp_code'] = 'SMTP koda: '; +$PHPMAILER_LANG['smtp_code_ex'] = 'Dodatne informacije o SMTP: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ne morem vzpostaviti povezave s SMTP strežnikom.'; +$PHPMAILER_LANG['smtp_detail'] = 'Podrobnosti: '; +$PHPMAILER_LANG['smtp_error'] = 'Napaka SMTP strežnika: '; +$PHPMAILER_LANG['variable_set'] = 'Ne morem nastaviti oz. ponastaviti spremenljivke: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php new file mode 100644 index 0000000..0b5280f --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr.php @@ -0,0 +1,28 @@ + + * @author Miloš Milanović + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP грешка: аутентификација није успела.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP грешка: повезивање са SMTP сервером није успело.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP грешка: подаци нису прихваћени.'; +$PHPMAILER_LANG['empty_message'] = 'Садржај поруке је празан.'; +$PHPMAILER_LANG['encoding'] = 'Непознато кодирање: '; +$PHPMAILER_LANG['execute'] = 'Није могуће извршити наредбу: '; +$PHPMAILER_LANG['file_access'] = 'Није могуће приступити датотеци: '; +$PHPMAILER_LANG['file_open'] = 'Није могуће отворити датотеку: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP грешка: слање са следећих адреса није успело: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP грешка: слање на следеће адресе није успело: '; +$PHPMAILER_LANG['instantiate'] = 'Није могуће покренути mail функцију.'; +$PHPMAILER_LANG['invalid_address'] = 'Порука није послата. Неисправна адреса: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' мејлер није подржан.'; +$PHPMAILER_LANG['provide_address'] = 'Дефинишите бар једну адресу примаоца.'; +$PHPMAILER_LANG['signing'] = 'Грешка приликом пријаве: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Повезивање са SMTP сервером није успело.'; +$PHPMAILER_LANG['smtp_error'] = 'Грешка SMTP сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Није могуће задати нити ресетовати променљиву: '; +$PHPMAILER_LANG['extension_missing'] = 'Недостаје проширење: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php new file mode 100644 index 0000000..6213832 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sr_latn.php @@ -0,0 +1,28 @@ + + * @author Miloš Milanović + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP greška: autentifikacija nije uspela.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP greška: povezivanje sa SMTP serverom nije uspelo.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP greška: podaci nisu prihvaćeni.'; +$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.'; +$PHPMAILER_LANG['encoding'] = 'Nepoznato kodiranje: '; +$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: '; +$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: '; +$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: '; +$PHPMAILER_LANG['from_failed'] = 'SMTP greška: slanje sa sledećih adresa nije uspelo: '; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP greška: slanje na sledeće adrese nije uspelo: '; +$PHPMAILER_LANG['instantiate'] = 'Nije moguće pokrenuti mail funkciju.'; +$PHPMAILER_LANG['invalid_address'] = 'Poruka nije poslata. Neispravna adresa: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' majler nije podržan.'; +$PHPMAILER_LANG['provide_address'] = 'Definišite bar jednu adresu primaoca.'; +$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Povezivanje sa SMTP serverom nije uspelo.'; +$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP servera: '; +$PHPMAILER_LANG['variable_set'] = 'Nije moguće zadati niti resetovati promenljivu: '; +$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php new file mode 100644 index 0000000..9872c19 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-sv.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP fel: Kunde inte autentisera.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP fel: Kunde inte ansluta till SMTP-server.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP fel: Data accepterades inte.'; +//$PHPMAILER_LANG['empty_message'] = 'Message body empty'; +$PHPMAILER_LANG['encoding'] = 'Okänt encode-format: '; +$PHPMAILER_LANG['execute'] = 'Kunde inte köra: '; +$PHPMAILER_LANG['file_access'] = 'Ingen åtkomst till fil: '; +$PHPMAILER_LANG['file_open'] = 'Fil fel: Kunde inte öppna fil: '; +$PHPMAILER_LANG['from_failed'] = 'Följande avsändaradress är felaktig: '; +$PHPMAILER_LANG['instantiate'] = 'Kunde inte initiera e-postfunktion.'; +$PHPMAILER_LANG['invalid_address'] = 'Felaktig adress: '; +$PHPMAILER_LANG['provide_address'] = 'Du måste ange minst en mottagares e-postadress.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' mailer stöds inte.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP fel: Följande mottagare är felaktig: '; +$PHPMAILER_LANG['signing'] = 'Signeringsfel: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() misslyckades.'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP serverfel: '; +$PHPMAILER_LANG['variable_set'] = 'Kunde inte definiera eller återställa variabel: '; +$PHPMAILER_LANG['extension_missing'] = 'Tillägg ej tillgängligt: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php new file mode 100644 index 0000000..d15bed1 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-tl.php @@ -0,0 +1,28 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP Error: Hindi mapatotohanan.'; +$PHPMAILER_LANG['connect_host'] = 'SMTP Error: Hindi makakonekta sa SMTP host.'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Error: Ang datos ay hindi naitanggap.'; +$PHPMAILER_LANG['empty_message'] = 'Walang laman ang mensahe'; +$PHPMAILER_LANG['encoding'] = 'Hindi alam ang encoding: '; +$PHPMAILER_LANG['execute'] = 'Hindi maisasagawa: '; +$PHPMAILER_LANG['file_access'] = 'Hindi ma-access ang file: '; +$PHPMAILER_LANG['file_open'] = 'File Error: Hindi mabuksan ang file: '; +$PHPMAILER_LANG['from_failed'] = 'Ang sumusunod na address ay nabigo: '; +$PHPMAILER_LANG['instantiate'] = 'Hindi maisimulan ang instance ng mail function.'; +$PHPMAILER_LANG['invalid_address'] = 'Hindi wasto ang address na naibigay: '; +$PHPMAILER_LANG['mailer_not_supported'] = 'Ang mailer ay hindi suportado.'; +$PHPMAILER_LANG['provide_address'] = 'Kailangan mong magbigay ng kahit isang email address na tatanggap.'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP Error: Ang mga sumusunod na tatanggap ay nabigo: '; +$PHPMAILER_LANG['signing'] = 'Hindi ma-sign: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Ang SMTP connect() ay nabigo.'; +$PHPMAILER_LANG['smtp_error'] = 'Ang server ng SMTP ay nabigo: '; +$PHPMAILER_LANG['variable_set'] = 'Hindi matatakda o ma-reset ang mga variables: '; +$PHPMAILER_LANG['extension_missing'] = 'Nawawala ang extension: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php new file mode 100644 index 0000000..f938f80 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-tr.php @@ -0,0 +1,31 @@ + + * @fixed by Boris Yurchenko + */ + +$PHPMAILER_LANG['authenticate'] = 'Помилка SMTP: помилка авторизації.'; +$PHPMAILER_LANG['connect_host'] = 'Помилка SMTP: не вдається під\'єднатися до SMTP-серверу.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Помилка SMTP: дані не прийнято.'; +$PHPMAILER_LANG['encoding'] = 'Невідоме кодування: '; +$PHPMAILER_LANG['execute'] = 'Неможливо виконати команду: '; +$PHPMAILER_LANG['file_access'] = 'Немає доступу до файлу: '; +$PHPMAILER_LANG['file_open'] = 'Помилка файлової системи: не вдається відкрити файл: '; +$PHPMAILER_LANG['from_failed'] = 'Невірна адреса відправника: '; +$PHPMAILER_LANG['instantiate'] = 'Неможливо запустити функцію mail().'; +$PHPMAILER_LANG['provide_address'] = 'Будь ласка, введіть хоча б одну email-адресу отримувача.'; +$PHPMAILER_LANG['mailer_not_supported'] = ' - поштовий сервер не підтримується.'; +$PHPMAILER_LANG['recipients_failed'] = 'Помилка SMTP: не вдалося відправлення для таких отримувачів: '; +$PHPMAILER_LANG['empty_message'] = 'Пусте повідомлення'; +$PHPMAILER_LANG['invalid_address'] = 'Не відправлено через неправильний формат email-адреси: '; +$PHPMAILER_LANG['signing'] = 'Помилка підпису: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Помилка з\'єднання з SMTP-сервером'; +$PHPMAILER_LANG['smtp_error'] = 'Помилка SMTP-сервера: '; +$PHPMAILER_LANG['variable_set'] = 'Неможливо встановити або скинути змінну: '; +$PHPMAILER_LANG['extension_missing'] = 'Розширення відсутнє: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php new file mode 100644 index 0000000..d65576e --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-vi.php @@ -0,0 +1,27 @@ + + */ + +$PHPMAILER_LANG['authenticate'] = 'Lỗi SMTP: Không thể xác thực.'; +$PHPMAILER_LANG['connect_host'] = 'Lỗi SMTP: Không thể kết nối máy chủ SMTP.'; +$PHPMAILER_LANG['data_not_accepted'] = 'Lỗi SMTP: Dữ liệu không được chấp nhận.'; +$PHPMAILER_LANG['empty_message'] = 'Không có nội dung'; +$PHPMAILER_LANG['encoding'] = 'Mã hóa không xác định: '; +$PHPMAILER_LANG['execute'] = 'Không thực hiện được: '; +$PHPMAILER_LANG['file_access'] = 'Không thể truy cập tệp tin '; +$PHPMAILER_LANG['file_open'] = 'Lỗi Tập tin: Không thể mở tệp tin: '; +$PHPMAILER_LANG['from_failed'] = 'Lỗi địa chỉ gửi đi: '; +$PHPMAILER_LANG['instantiate'] = 'Không dùng được các hàm gửi thư.'; +$PHPMAILER_LANG['invalid_address'] = 'Đại chỉ emai không đúng: '; +$PHPMAILER_LANG['mailer_not_supported'] = ' trình gửi thư không được hỗ trợ.'; +$PHPMAILER_LANG['provide_address'] = 'Bạn phải cung cấp ít nhất một địa chỉ người nhận.'; +$PHPMAILER_LANG['recipients_failed'] = 'Lỗi SMTP: lỗi địa chỉ người nhận: '; +$PHPMAILER_LANG['signing'] = 'Lỗi đăng nhập: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'Lỗi kết nối với SMTP'; +$PHPMAILER_LANG['smtp_error'] = 'Lỗi máy chủ smtp '; +$PHPMAILER_LANG['variable_set'] = 'Không thể thiết lập hoặc thiết lập lại biến: '; +//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php new file mode 100644 index 0000000..35e4e70 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh.php @@ -0,0 +1,29 @@ + + * @author Peter Dave Hello <@PeterDaveHello/> + * @author Jason Chiang + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 錯誤:登入失敗。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 錯誤:無法連線到 SMTP 主機。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 錯誤:無法接受的資料。'; +$PHPMAILER_LANG['empty_message'] = '郵件內容為空'; +$PHPMAILER_LANG['encoding'] = '未知編碼: '; +$PHPMAILER_LANG['execute'] = '無法執行:'; +$PHPMAILER_LANG['file_access'] = '無法存取檔案:'; +$PHPMAILER_LANG['file_open'] = '檔案錯誤:無法開啟檔案:'; +$PHPMAILER_LANG['from_failed'] = '發送地址錯誤:'; +$PHPMAILER_LANG['instantiate'] = '未知函數呼叫。'; +$PHPMAILER_LANG['invalid_address'] = '因為電子郵件地址無效,無法傳送: '; +$PHPMAILER_LANG['mailer_not_supported'] = '不支援的發信客戶端。'; +$PHPMAILER_LANG['provide_address'] = '必須提供至少一個收件人地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 錯誤:以下收件人地址錯誤:'; +$PHPMAILER_LANG['signing'] = '電子簽章錯誤: '; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP 連線失敗'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP 伺服器錯誤: '; +$PHPMAILER_LANG['variable_set'] = '無法設定或重設變數: '; +$PHPMAILER_LANG['extension_missing'] = '遺失模組 Extension: '; diff --git a/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php b/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php new file mode 100644 index 0000000..03d4911 --- /dev/null +++ b/vendor/phpmailer/phpmailer/language/phpmailer.lang-zh_cn.php @@ -0,0 +1,36 @@ + + * @author young + * @author Teddysun + */ + +$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。'; +$PHPMAILER_LANG['buggy_php'] = '您的 PHP 版本存在漏洞,可能会导致消息损坏。为修复此问题,请切换到使用 SMTP 发送,在您的 php.ini 中禁用 mail.add_x_header 选项。切换到 MacOS 或 Linux,或将您的 PHP 升级到 7.0.17+ 或 7.1.3+ 版本。'; +$PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。'; +$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。'; +$PHPMAILER_LANG['empty_message'] = '邮件正文为空。'; +$PHPMAILER_LANG['encoding'] = '未知编码:'; +$PHPMAILER_LANG['execute'] = '无法执行:'; +$PHPMAILER_LANG['extension_missing'] = '缺少扩展名:'; +$PHPMAILER_LANG['file_access'] = '无法访问文件:'; +$PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:'; +$PHPMAILER_LANG['from_failed'] = '发送地址错误:'; +$PHPMAILER_LANG['instantiate'] = '未知函数调用。'; +$PHPMAILER_LANG['invalid_address'] = '发送失败,电子邮箱地址是无效的:'; +$PHPMAILER_LANG['mailer_not_supported'] = '发信客户端不被支持。'; +$PHPMAILER_LANG['provide_address'] = '必须提供至少一个收件人地址。'; +$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:'; +$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP服务器连接失败。'; +$PHPMAILER_LANG['smtp_error'] = 'SMTP服务器出错:'; +$PHPMAILER_LANG['variable_set'] = '无法设置或重置变量:'; +$PHPMAILER_LANG['invalid_header'] = '无效的标题名称或值'; +$PHPMAILER_LANG['invalid_hostentry'] = '无效的hostentry: '; +$PHPMAILER_LANG['invalid_host'] = '无效的主机:'; +$PHPMAILER_LANG['signing'] = '签名错误:'; +$PHPMAILER_LANG['smtp_code'] = 'SMTP代码: '; +$PHPMAILER_LANG['smtp_code_ex'] = '附加SMTP信息: '; +$PHPMAILER_LANG['smtp_detail'] = '详情:'; diff --git a/vendor/phpmailer/phpmailer/src/DSNConfigurator.php b/vendor/phpmailer/phpmailer/src/DSNConfigurator.php new file mode 100644 index 0000000..566c961 --- /dev/null +++ b/vendor/phpmailer/phpmailer/src/DSNConfigurator.php @@ -0,0 +1,245 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2023 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * Configure PHPMailer with DSN string. + * + * @see https://en.wikipedia.org/wiki/Data_source_name + * + * @author Oleg Voronkovich + */ +class DSNConfigurator +{ + /** + * Create new PHPMailer instance configured by DSN. + * + * @param string $dsn DSN + * @param bool $exceptions Should we throw external exceptions? + * + * @return PHPMailer + */ + public static function mailer($dsn, $exceptions = null) + { + static $configurator = null; + + if (null === $configurator) { + $configurator = new DSNConfigurator(); + } + + return $configurator->configure(new PHPMailer($exceptions), $dsn); + } + + /** + * Configure PHPMailer instance with DSN string. + * + * @param PHPMailer $mailer PHPMailer instance + * @param string $dsn DSN + * + * @return PHPMailer + */ + public function configure(PHPMailer $mailer, $dsn) + { + $config = $this->parseDSN($dsn); + + $this->applyConfig($mailer, $config); + + return $mailer; + } + + /** + * Parse DSN string. + * + * @param string $dsn DSN + * + * @throws Exception If DSN is malformed + * + * @return array Configuration + */ + private function parseDSN($dsn) + { + $config = $this->parseUrl($dsn); + + if (false === $config || !isset($config['scheme']) || !isset($config['host'])) { + throw new Exception('Malformed DSN'); + } + + if (isset($config['query'])) { + parse_str($config['query'], $config['query']); + } + + return $config; + } + + /** + * Apply configuration to mailer. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $config Configuration + * + * @throws Exception If scheme is invalid + */ + private function applyConfig(PHPMailer $mailer, $config) + { + switch ($config['scheme']) { + case 'mail': + $mailer->isMail(); + break; + case 'sendmail': + $mailer->isSendmail(); + break; + case 'qmail': + $mailer->isQmail(); + break; + case 'smtp': + case 'smtps': + $mailer->isSMTP(); + $this->configureSMTP($mailer, $config); + break; + default: + throw new Exception( + sprintf( + 'Invalid scheme: "%s". Allowed values: "mail", "sendmail", "qmail", "smtp", "smtps".', + $config['scheme'] + ) + ); + } + + if (isset($config['query'])) { + $this->configureOptions($mailer, $config['query']); + } + } + + /** + * Configure SMTP. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $config Configuration + */ + private function configureSMTP($mailer, $config) + { + $isSMTPS = 'smtps' === $config['scheme']; + + if ($isSMTPS) { + $mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } + + $mailer->Host = $config['host']; + + if (isset($config['port'])) { + $mailer->Port = $config['port']; + } elseif ($isSMTPS) { + $mailer->Port = SMTP::DEFAULT_SECURE_PORT; + } + + $mailer->SMTPAuth = isset($config['user']) || isset($config['pass']); + + if (isset($config['user'])) { + $mailer->Username = $config['user']; + } + + if (isset($config['pass'])) { + $mailer->Password = $config['pass']; + } + } + + /** + * Configure options. + * + * @param PHPMailer $mailer PHPMailer instance + * @param array $options Options + * + * @throws Exception If option is unknown + */ + private function configureOptions(PHPMailer $mailer, $options) + { + $allowedOptions = get_object_vars($mailer); + + unset($allowedOptions['Mailer']); + unset($allowedOptions['SMTPAuth']); + unset($allowedOptions['Username']); + unset($allowedOptions['Password']); + unset($allowedOptions['Hostname']); + unset($allowedOptions['Port']); + unset($allowedOptions['ErrorInfo']); + + $allowedOptions = \array_keys($allowedOptions); + + foreach ($options as $key => $value) { + if (!in_array($key, $allowedOptions)) { + throw new Exception( + sprintf( + 'Unknown option: "%s". Allowed values: "%s"', + $key, + implode('", "', $allowedOptions) + ) + ); + } + + switch ($key) { + case 'AllowEmpty': + case 'SMTPAutoTLS': + case 'SMTPKeepAlive': + case 'SingleTo': + case 'UseSendmailOptions': + case 'do_verp': + case 'DKIM_copyHeaderFields': + $mailer->$key = (bool) $value; + break; + case 'Priority': + case 'SMTPDebug': + case 'WordWrap': + $mailer->$key = (int) $value; + break; + default: + $mailer->$key = $value; + break; + } + } + } + + /** + * Parse a URL. + * Wrapper for the built-in parse_url function to work around a bug in PHP 5.5. + * + * @param string $url URL + * + * @return array|false + */ + protected function parseUrl($url) + { + if (\PHP_VERSION_ID >= 50600 || false === strpos($url, '?')) { + return parse_url($url); + } + + $chunks = explode('?', $url); + if (is_array($chunks)) { + $result = parse_url($chunks[0]); + if (is_array($result)) { + $result['query'] = $chunks[1]; + } + return $result; + } + + return false; + } +} diff --git a/vendor/phpmailer/phpmailer/src/Exception.php b/vendor/phpmailer/phpmailer/src/Exception.php new file mode 100644 index 0000000..52eaf95 --- /dev/null +++ b/vendor/phpmailer/phpmailer/src/Exception.php @@ -0,0 +1,40 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer exception handler. + * + * @author Marcus Bointon + */ +class Exception extends \Exception +{ + /** + * Prettify error message output. + * + * @return string + */ + public function errorMessage() + { + return '' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "
      \n"; + } +} diff --git a/vendor/phpmailer/phpmailer/src/OAuth.php b/vendor/phpmailer/phpmailer/src/OAuth.php new file mode 100644 index 0000000..c1d5b77 --- /dev/null +++ b/vendor/phpmailer/phpmailer/src/OAuth.php @@ -0,0 +1,139 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +use League\OAuth2\Client\Grant\RefreshToken; +use League\OAuth2\Client\Provider\AbstractProvider; +use League\OAuth2\Client\Token\AccessToken; + +/** + * OAuth - OAuth2 authentication wrapper class. + * Uses the oauth2-client package from the League of Extraordinary Packages. + * + * @see http://oauth2-client.thephpleague.com + * + * @author Marcus Bointon (Synchro/coolbru) + */ +class OAuth implements OAuthTokenProvider +{ + /** + * An instance of the League OAuth Client Provider. + * + * @var AbstractProvider + */ + protected $provider; + + /** + * The current OAuth access token. + * + * @var AccessToken + */ + protected $oauthToken; + + /** + * The user's email address, usually used as the login ID + * and also the from address when sending email. + * + * @var string + */ + protected $oauthUserEmail = ''; + + /** + * The client secret, generated in the app definition of the service you're connecting to. + * + * @var string + */ + protected $oauthClientSecret = ''; + + /** + * The client ID, generated in the app definition of the service you're connecting to. + * + * @var string + */ + protected $oauthClientId = ''; + + /** + * The refresh token, used to obtain new AccessTokens. + * + * @var string + */ + protected $oauthRefreshToken = ''; + + /** + * OAuth constructor. + * + * @param array $options Associative array containing + * `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements + */ + public function __construct($options) + { + $this->provider = $options['provider']; + $this->oauthUserEmail = $options['userName']; + $this->oauthClientSecret = $options['clientSecret']; + $this->oauthClientId = $options['clientId']; + $this->oauthRefreshToken = $options['refreshToken']; + } + + /** + * Get a new RefreshToken. + * + * @return RefreshToken + */ + protected function getGrant() + { + return new RefreshToken(); + } + + /** + * Get a new AccessToken. + * + * @return AccessToken + */ + protected function getToken() + { + return $this->provider->getAccessToken( + $this->getGrant(), + ['refresh_token' => $this->oauthRefreshToken] + ); + } + + /** + * Generate a base64-encoded OAuth token. + * + * @return string + */ + public function getOauth64() + { + //Get a new token if it's not available or has expired + if (null === $this->oauthToken || $this->oauthToken->hasExpired()) { + $this->oauthToken = $this->getToken(); + } + + return base64_encode( + 'user=' . + $this->oauthUserEmail . + "\001auth=Bearer " . + $this->oauthToken . + "\001\001" + ); + } +} diff --git a/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php b/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php new file mode 100644 index 0000000..1155507 --- /dev/null +++ b/vendor/phpmailer/phpmailer/src/OAuthTokenProvider.php @@ -0,0 +1,44 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * OAuthTokenProvider - OAuth2 token provider interface. + * Provides base64 encoded OAuth2 auth strings for SMTP authentication. + * + * @see OAuth + * @see SMTP::authenticate() + * + * @author Peter Scopes (pdscopes) + * @author Marcus Bointon (Synchro/coolbru) + */ +interface OAuthTokenProvider +{ + /** + * Generate a base64-encoded OAuth token ensuring that the access token has not expired. + * The string to be base 64 encoded should be in the form: + * "user=\001auth=Bearer \001\001" + * + * @return string + */ + public function getOauth64(); +} diff --git a/vendor/phpmailer/phpmailer/src/PHPMailer.php b/vendor/phpmailer/phpmailer/src/PHPMailer.php new file mode 100644 index 0000000..ba4bcd4 --- /dev/null +++ b/vendor/phpmailer/phpmailer/src/PHPMailer.php @@ -0,0 +1,5252 @@ + + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + * @copyright 2012 - 2020 Marcus Bointon + * @copyright 2010 - 2012 Jim Jagielski + * @copyright 2004 - 2009 Andy Prevost + * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License + * @note This program is distributed in the hope that it will be useful - WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +namespace PHPMailer\PHPMailer; + +/** + * PHPMailer - PHP email creation and transport class. + * + * @author Marcus Bointon (Synchro/coolbru) + * @author Jim Jagielski (jimjag) + * @author Andy Prevost (codeworxtech) + * @author Brent R. Matzelle (original founder) + */ +class PHPMailer +{ + const CHARSET_ASCII = 'us-ascii'; + const CHARSET_ISO88591 = 'iso-8859-1'; + const CHARSET_UTF8 = 'utf-8'; + + const CONTENT_TYPE_PLAINTEXT = 'text/plain'; + const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; + const CONTENT_TYPE_TEXT_HTML = 'text/html'; + const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; + const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; + const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; + + const ENCODING_7BIT = '7bit'; + const ENCODING_8BIT = '8bit'; + const ENCODING_BASE64 = 'base64'; + const ENCODING_BINARY = 'binary'; + const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; + + const ENCRYPTION_STARTTLS = 'tls'; + const ENCRYPTION_SMTPS = 'ssl'; + + const ICAL_METHOD_REQUEST = 'REQUEST'; + const ICAL_METHOD_PUBLISH = 'PUBLISH'; + const ICAL_METHOD_REPLY = 'REPLY'; + const ICAL_METHOD_ADD = 'ADD'; + const ICAL_METHOD_CANCEL = 'CANCEL'; + const ICAL_METHOD_REFRESH = 'REFRESH'; + const ICAL_METHOD_COUNTER = 'COUNTER'; + const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; + + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * + * @var int|null + */ + public $Priority; + + /** + * The character set of the message. + * + * @var string + */ + public $CharSet = self::CHARSET_ISO88591; + + /** + * The MIME Content-type of the message. + * + * @var string + */ + public $ContentType = self::CONTENT_TYPE_PLAINTEXT; + + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * + * @var string + */ + public $Encoding = self::ENCODING_8BIT; + + /** + * Holds the most recent mailer error message. + * + * @var string + */ + public $ErrorInfo = ''; + + /** + * The From email address for the message. + * + * @var string + */ + public $From = ''; + + /** + * The From name of the message. + * + * @var string + */ + public $FromName = ''; + + /** + * The envelope sender of the message. + * This will usually be turned into a Return-Path header by the receiver, + * and is the address that bounces will be sent to. + * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. + * + * @var string + */ + public $Sender = ''; + + /** + * The Subject of the message. + * + * @var string + */ + public $Subject = ''; + + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * + * @var string + */ + public $Body = ''; + + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * + * @var string + */ + public $AltBody = ''; + + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. + * + * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @see http://kigkonsult.se/iCalcreator/ + * + * @var string + */ + public $Ical = ''; + + /** + * Value-array of "method" in Contenttype header "text/calendar" + * + * @var string[] + */ + protected static $IcalMethods = [ + self::ICAL_METHOD_REQUEST, + self::ICAL_METHOD_PUBLISH, + self::ICAL_METHOD_REPLY, + self::ICAL_METHOD_ADD, + self::ICAL_METHOD_CANCEL, + self::ICAL_METHOD_REFRESH, + self::ICAL_METHOD_COUNTER, + self::ICAL_METHOD_DECLINECOUNTER, + ]; + + /** + * The complete compiled MIME message body. + * + * @var string + */ + protected $MIMEBody = ''; + + /** + * The complete compiled MIME message headers. + * + * @var string + */ + protected $MIMEHeader = ''; + + /** + * Extra headers that createHeader() doesn't fold in. + * + * @var string + */ + protected $mailHeader = ''; + + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * + * @see static::STD_LINE_LENGTH + * + * @var int + */ + public $WordWrap = 0; + + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * + * @var string + */ + public $Mailer = 'mail'; + + /** + * The path to the sendmail program. + * + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * + * @var bool + */ + public $UseSendmailOptions = true; + + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * + * @var string + */ + public $ConfirmReadingTo = ''; + + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * + * @see PHPMailer::$Helo + * + * @var string + */ + public $Hostname = ''; + + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * You can set your own, but it must be in the format "", + * as defined in RFC5322 section 3.6.4 or it will be ignored. + * + * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 + * + * @var string + */ + public $MessageID = ''; + + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * + * @var string + */ + public $MessageDate = ''; + + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * + * @var string + */ + public $Host = 'localhost'; + + /** + * The default SMTP server port. + * + * @var int + */ + public $Port = 25; + + /** + * The SMTP HELO/EHLO name used for the SMTP connection. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * + * @see PHPMailer::$Hostname + * + * @var string + */ + public $Helo = ''; + + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. + * + * @var string + */ + public $SMTPSecure = ''; + + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * + * @var bool + */ + public $SMTPAutoTLS = true; + + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * + * @see PHPMailer::$Username + * @see PHPMailer::$Password + * + * @var bool + */ + public $SMTPAuth = false; + + /** + * Options array passed to stream_context_create when connecting via SMTP. + * + * @var array + */ + public $SMTPOptions = []; + + /** + * SMTP username. + * + * @var string + */ + public $Username = ''; + + /** + * SMTP password. + * + * @var string + */ + public $Password = ''; + + /** + * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. + * If not specified, the first one from that list that the server supports will be selected. + * + * @var string + */ + public $AuthType = ''; + + /** + * SMTP SMTPXClient command attibutes + * + * @var array + */ + protected $SMTPXClient = []; + + /** + * An implementation of the PHPMailer OAuthTokenProvider interface. + * + * @var OAuthTokenProvider + */ + protected $oauth; + + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. + * + * @var int + */ + public $Timeout = 300; + + /** + * Comma separated list of DSN notifications + * 'NEVER' under no circumstances a DSN must be returned to the sender. + * If you use NEVER all other notifications will be ignored. + * 'SUCCESS' will notify you when your mail has arrived at its destination. + * 'FAILURE' will arrive if an error occurred during delivery. + * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual + * delivery's outcome (success or failure) is not yet decided. + * + * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY + */ + public $dsn = ''; + + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * @see SMTP::DEBUG_OFF: No output + * @see SMTP::DEBUG_CLIENT: Client messages + * @see SMTP::DEBUG_SERVER: Client and server messages + * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * + * @see SMTP::$do_debug + * + * @var int + */ + public $SMTPDebug = 0; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
      `, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * ```php + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * ``` + * + * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` + * level output is used: + * + * ```php + * $mail->Debugoutput = new myPsr3Logger; + * ``` + * + * @see SMTP::$Debugoutput + * + * @var string|callable|\Psr\Log\LoggerInterface + */ + public $Debugoutput = 'echo'; + + /** + * Whether to keep the SMTP connection open after each message. + * If this is set to true then the connection will remain open after a send, + * and closing the connection will require an explicit call to smtpClose(). + * It's a good idea to use this if you are sending multiple messages as it reduces overhead. + * See the mailing list example for how to use it. + * + * @var bool + */ + public $SMTPKeepAlive = false; + + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * + * @var bool + * + * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! + */ + public $SingleTo = false; + + /** + * Storage for addresses when SingleTo is enabled. + * + * @var array + */ + protected $SingleToArray = []; + + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * + * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @see http://www.postfix.org/VERP_README.html Postfix VERP info + * + * @var bool + */ + public $do_verp = false; + + /** + * Whether to allow sending messages with an empty body. + * + * @var bool + */ + public $AllowEmpty = false; + + /** + * DKIM selector. + * + * @var string + */ + public $DKIM_selector = ''; + + /** + * DKIM Identity. + * Usually the email address used as the source of the email. + * + * @var string + */ + public $DKIM_identity = ''; + + /** + * DKIM passphrase. + * Used if your key is encrypted. + * + * @var string + */ + public $DKIM_passphrase = ''; + + /** + * DKIM signing domain name. + * + * @example 'example.com' + * + * @var string + */ + public $DKIM_domain = ''; + + /** + * DKIM Copy header field values for diagnostic use. + * + * @var bool + */ + public $DKIM_copyHeaderFields = true; + + /** + * DKIM Extra signing headers. + * + * @example ['List-Unsubscribe', 'List-Help'] + * + * @var array + */ + public $DKIM_extraHeaders = []; + + /** + * DKIM private key file path. + * + * @var string + */ + public $DKIM_private = ''; + + /** + * DKIM private key string. + * + * If set, takes precedence over `$DKIM_private`. + * + * @var string + */ + public $DKIM_private_string = ''; + + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * bool $result result of the send action + * array $to email addresses of the recipients + * array $cc cc email addresses + * array $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * string $extra extra information of possible use + * "smtp_transaction_id' => last smtp transaction id + * + * @var string + */ + public $action_function = ''; + + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. + * + * @var string|null + */ + public $XMailer = ''; + + /** + * Which validator to use by default when validating email addresses. + * May be a callable to inject your own validator, but there are several built-in validators. + * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. + * + * @see PHPMailer::validateAddress() + * + * @var string|callable + */ + public static $validator = 'php'; + + /** + * An instance of the SMTP sender class. + * + * @var SMTP + */ + protected $smtp; + + /** + * The array of 'to' names and addresses. + * + * @var array + */ + protected $to = []; + + /** + * The array of 'cc' names and addresses. + * + * @var array + */ + protected $cc = []; + + /** + * The array of 'bcc' names and addresses. + * + * @var array + */ + protected $bcc = []; + + /** + * The array of reply-to names and addresses. + * + * @var array + */ + protected $ReplyTo = []; + + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * + * @var array + */ + protected $all_recipients = []; + + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$to + * @see PHPMailer::$cc + * @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + * + * @var array + */ + protected $RecipientsQueue = []; + + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * + * @see PHPMailer::$ReplyTo + * + * @var array + */ + protected $ReplyToQueue = []; + + /** + * The array of attachments. + * + * @var array + */ + protected $attachment = []; + + /** + * The array of custom headers. + * + * @var array + */ + protected $CustomHeader = []; + + /** + * The most recent Message-ID (including angular brackets). + * + * @var string + */ + protected $lastMessageID = ''; + + /** + * The message's MIME type. + * + * @var string + */ + protected $message_type = ''; + + /** + * The array of MIME boundary strings. + * + * @var array + */ + protected $boundary = []; + + /** + * The array of available text strings for the current language. + * + * @var array + */ + protected $language = []; + + /** + * The number of errors encountered. + * + * @var int + */ + protected $error_count = 0; + + /** + * The S/MIME certificate file path. + * + * @var string + */ + protected $sign_cert_file = ''; + + /** + * The S/MIME key file path. + * + * @var string + */ + protected $sign_key_file = ''; + + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * + * @var string + */ + protected $sign_extracerts_file = ''; + + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * + * @var string + */ + protected $sign_key_pass = ''; + + /** + * Whether to throw exceptions for errors. + * + * @var bool + */ + protected $exceptions = false; + + /** + * Unique ID used for message ID and boundaries. + * + * @var string + */ + protected $uniqueid = ''; + + /** + * The PHPMailer Version number. + * + * @var string + */ + const VERSION = '6.9.1'; + + /** + * Error severity: message only, continue processing. + * + * @var int + */ + const STOP_MESSAGE = 0; + + /** + * Error severity: message, likely ok to continue processing. + * + * @var int + */ + const STOP_CONTINUE = 1; + + /** + * Error severity: message, plus full stop, critical error reached. + * + * @var int + */ + const STOP_CRITICAL = 2; + + /** + * The SMTP standard CRLF line break. + * If you want to change line break format, change static::$LE, not this. + */ + const CRLF = "\r\n"; + + /** + * "Folding White Space" a white space string used for line folding. + */ + const FWS = ' '; + + /** + * SMTP RFC standard line ending; Carriage Return, Line Feed. + * + * @var string + */ + protected static $LE = self::CRLF; + + /** + * The maximum line length supported by mail(). + * + * Background: mail() will sometimes corrupt messages + * with headers longer than 65 chars, see #818. + * + * @var int + */ + const MAIL_MAX_LINE_LENGTH = 63; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1. + * + * @var int + */ + const MAX_LINE_LENGTH = 998; + + /** + * The lower maximum line length allowed by RFC 2822 section 2.1.1. + * This length does NOT include the line break + * 76 means that lines will be 77 or 78 chars depending on whether + * the line break format is LF or CRLF; both are valid. + * + * @var int + */ + const STD_LINE_LENGTH = 76; + + /** + * Constructor. + * + * @param bool $exceptions Should we throw external exceptions? + */ + public function __construct($exceptions = null) + { + if (null !== $exceptions) { + $this->exceptions = (bool) $exceptions; + } + //Pick an appropriate debug output format automatically + $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); + } + + /** + * Destructor. + */ + public function __destruct() + { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do). + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string|null $params Params + * + * @return bool + */ + private function mailPassthru($to, $subject, $body, $header, $params) + { + //Check overloading of mail function to avoid double-encoding + if ((int)ini_get('mbstring.func_overload') & 1) { + $subject = $this->secureHeader($subject); + } else { + $subject = $this->encodeHeader($this->secureHeader($subject)); + } + //Calling mail() with null params breaks + $this->edebug('Sending with mail()'); + $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); + $this->edebug("Envelope sender: {$this->Sender}"); + $this->edebug("To: {$to}"); + $this->edebug("Subject: {$subject}"); + $this->edebug("Headers: {$header}"); + if (!$this->UseSendmailOptions || null === $params) { + $result = @mail($to, $subject, $body, $header); + } else { + $this->edebug("Additional params: {$params}"); + $result = @mail($to, $subject, $body, $header, $params); + } + $this->edebug('Result: ' . ($result ? 'true' : 'false')); + return $result; + } + + /** + * Output debugging info via a user-defined method. + * Only generates output if debug output is enabled. + * + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug($str) + { + if ($this->SMTPDebug <= 0) { + return; + } + //Is this a PSR-3 logger? + if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { + $this->Debugoutput->debug($str); + + return; + } + //Avoid clash with built-in function names + if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); + + return; + } + switch ($this->Debugoutput) { + case 'error_log': + //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ + error_log($str); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ), "
      \n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace('/\r\n|\r/m', "\n", $str); + echo gmdate('Y-m-d H:i:s'), + "\t", + //Trim trailing space + trim( + //Indent for readability, except for trailing break + str_replace( + "\n", + "\n \t ", + trim($str) + ) + ), + "\n"; + } + } + + /** + * Sets message type to HTML or plain. + * + * @param bool $isHtml True for HTML mode + */ + public function isHTML($isHtml = true) + { + if ($isHtml) { + $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; + } else { + $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; + } + } + + /** + * Send messages using SMTP. + */ + public function isSMTP() + { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + */ + public function isMail() + { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + */ + public function isSendmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'sendmail')) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + */ + public function isQmail() + { + $ini_sendmail_path = ini_get('sendmail_path'); + + if (false === stripos($ini_sendmail_path, 'qmail')) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addAddress($address, $name = '') + { + return $this->addOrEnqueueAnAddress('to', $address, $name); + } + + /** + * Add a "CC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('cc', $address, $name); + } + + /** + * Add a "BCC" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addBCC($address, $name = '') + { + return $this->addOrEnqueueAnAddress('bcc', $address, $name); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + public function addReplyTo($address, $name = '') + { + return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address + * @param string $name An optional username associated with the address + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addOrEnqueueAnAddress($kind, $address, $name) + { + $pos = false; + if ($address !== null) { + $address = trim($address); + $pos = strrpos($address, '@'); + } + if (false === $pos) { + //At-sign is missing. + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ($name !== null && is_string($name)) { + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + } else { + $name = ''; + } + $params = [$kind, $address, $name]; + //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + //Domain is assumed to be whatever is after the last @ symbol in the address + if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { + if ('Reply-To' !== $kind) { + if (!array_key_exists($address, $this->RecipientsQueue)) { + $this->RecipientsQueue[$address] = $params; + + return true; + } + } elseif (!array_key_exists($address, $this->ReplyToQueue)) { + $this->ReplyToQueue[$address] = $params; + + return true; + } + + return false; + } + + //Immediately add standard addresses without IDN. + return call_user_func_array([$this, 'addAnAddress'], $params); + } + + /** + * Set the boundaries to use for delimiting MIME parts. + * If you override this, ensure you set all 3 boundaries to unique values. + * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, + * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 + * + * @return void + */ + public function setBoundaries() + { + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1=_' . $this->uniqueid; + $this->boundary[2] = 'b2=_' . $this->uniqueid; + $this->boundary[3] = 'b3=_' . $this->uniqueid; + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws Exception + * + * @return bool true on success, false if address already used or invalid in some way + */ + protected function addAnAddress($kind, $address, $name = '') + { + if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { + $error_message = sprintf( + '%s: %s', + $this->lang('Invalid recipient kind'), + $kind + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if (!static::validateAddress($address)) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $kind, + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + if ('Reply-To' !== $kind) { + if (!array_key_exists(strtolower($address), $this->all_recipients)) { + $this->{$kind}[] = [$address, $name]; + $this->all_recipients[strtolower($address)] = true; + + return true; + } + } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { + $this->ReplyTo[strtolower($address)] = [$address, $name]; + + return true; + } + + return false; + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
      " into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * @param string $charset The charset to use when decoding the address list string. + * + * @return array + */ + public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) + { + $addresses = []; + if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist($addrstr, ''); + // Clear any potential IMAP errors to get rid of notices being thrown at end of script. + imap_errors(); + foreach ($list as $address) { + if ( + '.SYNTAX-ERROR.' !== $address->host && + static::validateAddress($address->mailbox . '@' . $address->host) + ) { + //Decode the name part if it's present and encoded + if ( + property_exists($address, 'personal') && + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + defined('MB_CASE_UPPER') && + preg_match('/^=\?.*\?=$/s', $address->personal) + ) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $address->personal = str_replace('_', '=20', $address->personal); + //Decode the name + $address->personal = mb_decode_mimeheader($address->personal); + mb_internal_encoding($origCharset); + } + + $addresses[] = [ + 'name' => (property_exists($address, 'personal') ? $address->personal : ''), + 'address' => $address->mailbox . '@' . $address->host, + ]; + } + } + } else { + //Use this simpler parser + $list = explode(',', $addrstr); + foreach ($list as $address) { + $address = trim($address); + //Is there a separate name part? + if (strpos($address, '<') === false) { + //No separate name, just use the whole thing + if (static::validateAddress($address)) { + $addresses[] = [ + 'name' => '', + 'address' => $address, + ]; + } + } else { + list($name, $email) = explode('<', $address); + $email = trim(str_replace('>', '', $email)); + $name = trim($name); + if (static::validateAddress($email)) { + //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled + //If this name is encoded, decode it + if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { + $origCharset = mb_internal_encoding(); + mb_internal_encoding($charset); + //Undo any RFC2047-encoded spaces-as-underscores + $name = str_replace('_', '=20', $name); + //Decode the name + $name = mb_decode_mimeheader($name); + mb_internal_encoding($origCharset); + } + $addresses[] = [ + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), + 'address' => $email, + ]; + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param bool $auto Whether to also set the Sender address, defaults to true + * + * @throws Exception + * + * @return bool + */ + public function setFrom($address, $name = '', $auto = true) + { + $address = trim((string)$address); + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + //Don't validate now addresses with IDN. Will be done in send(). + $pos = strrpos($address, '@'); + if ( + (false === $pos) + || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) + && !static::validateAddress($address)) + ) { + $error_message = sprintf( + '%s (From): %s', + $this->lang('invalid_address'), + $address + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ($auto && empty($this->Sender)) { + $this->Sender = $address; + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * + * @return string + */ + public function getLastMessageID() + { + return $this->lastMessageID; + } + + /** + * Check that a string looks like an email address. + * Validation patterns supported: + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * Alternatively you may pass in a callable to inject your own validator, for example: + * + * ```php + * PHPMailer::validateAddress('user@example.com', function($address) { + * return (strpos($address, '@') !== false); + * }); + * ``` + * + * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. + * + * @param string $address The email address to check + * @param string|callable $patternselect Which pattern to use + * + * @return bool + */ + public static function validateAddress($address, $patternselect = null) + { + if (null === $patternselect) { + $patternselect = static::$validator; + } + //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 + if (is_callable($patternselect) && !is_string($patternselect)) { + return call_user_func($patternselect, $address); + } + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { + return false; + } + switch ($patternselect) { + case 'pcre': //Kept for BC + case 'pcre8': + /* + * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL + * is based. + * In addition to the addresses allowed by filter_var, also permits: + * * dotless domains: `a@b` + * * comments: `1234 @ local(blah) .machine .example` + * * quoted elements: `'"test blah"@example.org'` + * * numeric TLDs: `a@b.123` + * * unbracketed IPv4 literals: `a@192.168.0.1` + * * IPv6 literals: 'first.last@[IPv6:a1::]' + * Not all of these will necessarily work for sending! + * + * @see http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (bool) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'html5': + /* + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) + */ + return (bool) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'php': + default: + return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; + } + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * `intl` and `mbstring` PHP extensions. + * + * @return bool `true` if required functions for IDN support are present + */ + public static function idnSupported() + { + return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain contains characters not allowed in an IDN). + * + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress($address) + { + //Verify we have required functions, CharSet, and at-sign. + $pos = strrpos($address, '@'); + if ( + !empty($this->CharSet) && + false !== $pos && + static::idnSupported() + ) { + $domain = substr($address, ++$pos); + //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { + //Convert the domain from whatever charset it's in to UTF-8 + $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); + //Ignore IDE complaints about this line - method signature changed in PHP 5.4 + $errorcode = 0; + if (defined('INTL_IDNA_VARIANT_UTS46')) { + //Use the current punycode standard (appeared in PHP 7.2) + $punycode = idn_to_ascii( + $domain, + \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | + \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, + \INTL_IDNA_VARIANT_UTS46 + ); + } elseif (defined('INTL_IDNA_VARIANT_2003')) { + //Fall back to this old, deprecated/removed encoding + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); + } else { + //Fall back to a default we don't know about + $punycode = idn_to_ascii($domain, $errorcode); + } + if (false !== $punycode) { + return substr($address, 0, $pos) . $punycode; + } + } + } + + return $address; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * + * @throws Exception + * + * @return bool false on error - See the ErrorInfo property for details of the error + */ + public function send() + { + try { + if (!$this->preSend()) { + return false; + } + + return $this->postSend(); + } catch (Exception $exc) { + $this->mailHeader = ''; + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * + * @throws Exception + * + * @return bool + */ + public function preSend() + { + if ( + 'smtp' === $this->Mailer + || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) + ) { + //SMTP mandates RFC-compliant line endings + //and it's also used with mail() on Windows + static::setLE(self::CRLF); + } else { + //Maintain backward compatibility with legacy Linux command line mailers + static::setLE(PHP_EOL); + } + //Check for buggy PHP versions that add a header with an incorrect line break + if ( + 'mail' === $this->Mailer + && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) + || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) + && ini_get('mail.add_x_header') === '1' + && stripos(PHP_OS, 'WIN') === 0 + ) { + trigger_error($this->lang('buggy_php'), E_USER_WARNING); + } + + try { + $this->error_count = 0; //Reset errors + $this->mailHeader = ''; + + //Dequeue recipient and Reply-To addresses with IDN + foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { + $params[1] = $this->punyencodeAddress($params[1]); + call_user_func_array([$this, 'addAnAddress'], $params); + } + if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { + throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); + } + + //Validate From, Sender, and ConfirmReadingTo addresses + foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { + if ($this->{$address_kind} === null) { + $this->{$address_kind} = ''; + continue; + } + $this->{$address_kind} = trim($this->{$address_kind}); + if (empty($this->{$address_kind})) { + continue; + } + $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); + if (!static::validateAddress($this->{$address_kind})) { + $error_message = sprintf( + '%s (%s): %s', + $this->lang('invalid_address'), + $address_kind, + $this->{$address_kind} + ); + $this->setError($error_message); + $this->edebug($error_message); + if ($this->exceptions) { + throw new Exception($error_message); + } + + return false; + } + } + + //Set whether the message is multipart/alternative + if ($this->alternativeExists()) { + $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; + } + + $this->setMessageType(); + //Refuse to send an empty message unless we are specifically allowing it + if (!$this->AllowEmpty && empty($this->Body)) { + throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); + } + + //Trim subject consistently + $this->Subject = trim($this->Subject); + //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + //createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + //To capture the complete message when using mail(), create + //an extra header list which createHeader() doesn't fold in + if ('mail' === $this->Mailer) { + if (count($this->to) > 0) { + $this->mailHeader .= $this->addrAppend('To', $this->to); + } else { + $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader($this->secureHeader($this->Subject)) + ); + } + + //Sign with DKIM if enabled + if ( + !empty($this->DKIM_domain) + && !empty($this->DKIM_selector) + && (!empty($this->DKIM_private_string) + || (!empty($this->DKIM_private) + && static::isPermittedPath($this->DKIM_private) + && file_exists($this->DKIM_private) + ) + ) + ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader($this->secureHeader($this->Subject)), + $this->MIMEBody + ); + $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . + static::normalizeBreaks($header_dkim) . static::$LE; + } + + return true; + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + if ($this->exceptions) { + throw $exc; + } + + return false; + } + } + + /** + * Actually send a message via the selected mechanism. + * + * @throws Exception + * + * @return bool + */ + public function postSend() + { + try { + //Choose the mailer and send through it + switch ($this->Mailer) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); + case 'smtp': + return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); + case 'mail': + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + default: + $sendMethod = $this->Mailer . 'Send'; + if (method_exists($this, $sendMethod)) { + return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); + } + + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); + } + } catch (Exception $exc) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { + $this->smtp->reset(); + } + if ($this->exceptions) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @see PHPMailer::$Sendmail + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function sendmailSend($header, $body) + { + if ($this->Mailer === 'qmail') { + $this->edebug('Sending with qmail'); + } else { + $this->edebug('Sending with sendmail'); + } + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { + if ($this->Mailer === 'qmail') { + $sendmailFmt = '%s -f%s'; + } else { + $sendmailFmt = '%s -oi -f%s -t'; + } + } else { + //allow sendmail to choose a default envelope sender. It may + //seem preferable to force it to use the From header as with + //SMTP, but that introduces new problems (see + //), and + //it has historically worked this way. + $sendmailFmt = '%s -oi -t'; + } + + $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $this->edebug('Sendmail path: ' . $this->Sendmail); + $this->edebug('Sendmail command: ' . $sendmail); + $this->edebug('Envelope sender: ' . $this->Sender); + $this->edebug("Headers: {$header}"); + + if ($this->SingleTo) { + foreach ($this->SingleToArray as $toAddr) { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + $this->edebug("To: {$toAddr}"); + fwrite($mail, 'To: ' . $toAddr . "\n"); + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + ($result === 0), + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + } else { + $mail = @popen($sendmail, 'w'); + if (!$mail) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + fwrite($mail, $header); + fwrite($mail, $body); + $result = pclose($mail); + $this->doCallback( + ($result === 0), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); + if (0 !== $result) { + throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); + } + } + + return true; + } + + /** + * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. + * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. + * + * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report + * + * @param string $string The string to be validated + * + * @return bool + */ + protected static function isShellSafe($string) + { + //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, + //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, + //so we don't. + if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { + return false; + } + + if ( + escapeshellcmd($string) !== $string + || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) + ) { + return false; + } + + $length = strlen($string); + + for ($i = 0; $i < $length; ++$i) { + $c = $string[$i]; + + //All other characters have a special meaning in at least one common shell, including = and +. + //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + //Note that this does permit non-Latin alphanumeric characters based on the current locale. + if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { + return false; + } + } + + return true; + } + + /** + * Check whether a file path is of a permitted type. + * Used to reject URLs and phar files from functions that access local file paths, + * such as addAttachment. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function isPermittedPath($path) + { + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!static::isPermittedPath($path)) { + return false; + } + $readable = is_file($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; + } + + /** + * Send mail using the PHP mail() function. + * + * @see http://www.php.net/manual/en/book.mail.php + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function mailSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + + $toArr = []; + foreach ($this->to as $toaddr) { + $toArr[] = $this->addrFormat($toaddr); + } + $to = trim(implode(', ', $toArr)); + + //If there are no To-addresses (e.g. when sending only to BCC-addresses) + //the following should be added to get a correct DKIM-signature. + //Compare with $this->preSend() + if ($to === '') { + $to = 'undisclosed-recipients:;'; + } + + $params = null; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); + } + if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } + $old_from = ini_get('sendmail_from'); + ini_set('sendmail_from', $this->Sender); + } + $result = false; + if ($this->SingleTo && count($toArr) > 1) { + foreach ($toArr as $toAddr) { + $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + $result, + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); + } + } else { + $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + } + if (isset($old_from)) { + ini_set('sendmail_from', $old_from); + } + if (!$result) { + throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); + } + + return true; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation, + * or set one with setSMTPInstance. + * + * @return SMTP + */ + public function getSMTPInstance() + { + if (!is_object($this->smtp)) { + $this->smtp = new SMTP(); + } + + return $this->smtp; + } + + /** + * Provide an instance to use for SMTP operations. + * + * @return SMTP + */ + public function setSMTPInstance(SMTP $smtp) + { + $this->smtp = $smtp; + + return $this->smtp; + } + + /** + * Provide SMTP XCLIENT attributes + * + * @param string $name Attribute name + * @param ?string $value Attribute value + * + * @return bool + */ + public function setSMTPXclientAttribute($name, $value) + { + if (!in_array($name, SMTP::$xclient_allowed_attributes)) { + return false; + } + if (isset($this->SMTPXClient[$name]) && $value === null) { + unset($this->SMTPXClient[$name]); + } elseif ($value !== null) { + $this->SMTPXClient[$name] = $value; + } + + return true; + } + + /** + * Get SMTP XCLIENT attributes + * + * @return array + */ + public function getSMTPXclientAttributes() + { + return $this->SMTPXClient; + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * + * @see PHPMailer::setSMTPInstance() to use a different class. + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws Exception + * + * @return bool + */ + protected function smtpSend($header, $body) + { + $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + $bad_rcpt = []; + if (!$this->smtpConnect($this->SMTPOptions)) { + throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); + } + //Sender already validated in preSend() + if ('' === $this->Sender) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if (count($this->SMTPXClient)) { + $this->smtp->xclient($this->SMTPXClient); + } + if (!$this->smtp->mail($smtp_from)) { + $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); + throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); + } + + $callbacks = []; + //Attempt to send to all recipients + foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { + foreach ($togroup as $to) { + if (!$this->smtp->recipient($to[0], $this->dsn)) { + $error = $this->smtp->getError(); + $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; + $isSent = false; + } else { + $isSent = true; + } + + $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; + } + } + + //Only send the DATA command if we have viable recipients + if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { + throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); + } + + $smtp_transaction_id = $this->smtp->getLastTransactionID(); + + if ($this->SMTPKeepAlive) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + + foreach ($callbacks as $cb) { + $this->doCallback( + $cb['issent'], + [[$cb['to'], $cb['name']]], + [], + [], + $this->Subject, + $body, + $this->From, + ['smtp_transaction_id' => $smtp_transaction_id] + ); + } + + //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { + $errstr = ''; + foreach ($bad_rcpt as $bad) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @throws Exception + * + * @uses \PHPMailer\PHPMailer\SMTP + * + * @return bool + */ + public function smtpConnect($options = null) + { + if (null === $this->smtp) { + $this->smtp = $this->getSMTPInstance(); + } + + //If no options are provided, use whatever is set in the instance + if (null === $options) { + $options = $this->SMTPOptions; + } + + //Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + if ($this->Host === null) { + $this->Host = 'localhost'; + } + $hosts = explode(';', $this->Host); + $lastexception = null; + + foreach ($hosts as $hostentry) { + $hostinfo = []; + if ( + !preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + ) + ) { + $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); + //Not a valid host entry + continue; + } + //$hostinfo[1]: optional ssl or tls prefix + //$hostinfo[2]: the hostname + //$hostinfo[3]: optional port number + //The host string prefix can temporarily override the current setting for SMTPSecure + //If it's not specified, the default value is used + + //Check the host name is a valid name or IP address before trying to use it + if (!static::isValidHost($hostinfo[2])) { + $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); + continue; + } + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); + if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; //Can't have SSL and TLS at the same time + $secure = static::ENCRYPTION_SMTPS; + } elseif ('tls' === $hostinfo[1]) { + $tls = true; + //TLS doesn't use a prefix + $secure = static::ENCRYPTION_STARTTLS; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA256'); + if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[2]; + $port = $this->Port; + if ( + array_key_exists(3, $hostinfo) && + is_numeric($hostinfo[3]) && + $hostinfo[3] > 0 && + $hostinfo[3] < 65536 + ) { + $port = (int) $hostinfo[3]; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + //* it's not disabled + //* we are not connecting to localhost + //* we have openssl extension + //* we are not already using SSL + //* the server offers STARTTLS + if ( + $this->SMTPAutoTLS && + $this->Host !== 'localhost' && + $sslext && + $secure !== 'ssl' && + $this->smtp->getServerExt('STARTTLS') + ) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + //We must resend EHLO after TLS negotiation + $this->smtp->hello($hello); + } + if ( + $this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { + throw new Exception($this->lang('authenticate')); + } + + return true; + } catch (Exception $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + //We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + //If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + //As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions && null !== $lastexception) { + throw $lastexception; + } + if ($this->exceptions) { + // no exception was thrown, likely $this->smtp->connect() failed + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); + } + + return false; + } + + /** + * Close the active SMTP session if one exists. + */ + public function smtpClose() + { + if ((null !== $this->smtp) && $this->smtp->connected()) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + + /** + * Set the language for error messages. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * Optionally, the language code can be enhanced with a 4-character + * script annotation and/or a 2-character country annotation. + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * Do not set this from user input! + * + * @return bool Returns true if the requested language was loaded, false otherwise. + */ + public function setLanguage($langcode = 'en', $lang_path = '') + { + //Backwards compatibility for renamed language codes + $renamed_langcodes = [ + 'br' => 'pt_br', + 'cz' => 'cs', + 'dk' => 'da', + 'no' => 'nb', + 'se' => 'sv', + 'rs' => 'sr', + 'tg' => 'tl', + 'am' => 'hy', + ]; + + if (array_key_exists($langcode, $renamed_langcodes)) { + $langcode = $renamed_langcodes[$langcode]; + } + + //Define full set of translatable strings in English + $PHPMAILER_LANG = [ + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'extension_missing' => 'Extension missing: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'invalid_header' => 'Invalid header name or value', + 'invalid_hostentry' => 'Invalid hostentry: ', + 'invalid_host' => 'Invalid host: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_code' => 'SMTP code: ', + 'smtp_code_ex' => 'Additional SMTP info: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_detail' => 'Detail: ', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + ]; + if (empty($lang_path)) { + //Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + + //Validate $langcode + $foundlang = true; + $langcode = strtolower($langcode); + if ( + !preg_match('/^(?P[a-z]{2})(?P +JS; + return $js; + } + + protected function console($type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['trace_tabs']); + $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) + ? "console.group('{$type}');" + : "console.groupCollapsed('{$type}');"; + + foreach ((array) $msg as $key => $m) { + switch ($type) { + case '调试': + $var_type = gettype($m); + if (in_array($var_type, ['array', 'string'])) { + $line[] = "console.log(" . json_encode($m) . ");"; + } else { + $line[] = "console.log(" . json_encode(var_export($m, 1)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', json_encode($m)); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', $m); + $style = "color:#009bb4;"; + $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; + break; + default: + $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; + $msg = json_encode($m); + $line[] = "console.log({$msg});"; + break; + } + } + $line[] = "console.groupEnd();"; + return implode(PHP_EOL, $line); + } + +} diff --git a/vendor/topthink/framework/library/think/debug/Html.php b/vendor/topthink/framework/library/think/debug/Html.php new file mode 100644 index 0000000..b6be7ad --- /dev/null +++ b/vendor/topthink/framework/library/think/debug/Html.php @@ -0,0 +1,111 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'trace_file' => '', + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + // 页面Trace信息 + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['trace_file']; + return ob_get_clean(); + } + +} diff --git a/vendor/topthink/framework/library/think/exception/ClassNotFoundException.php b/vendor/topthink/framework/library/think/exception/ClassNotFoundException.php new file mode 100644 index 0000000..eb22e73 --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/ClassNotFoundException.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ClassNotFoundException extends \RuntimeException +{ + protected $class; + public function __construct($message, $class = '') + { + $this->message = $message; + $this->class = $class; + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/vendor/topthink/framework/library/think/exception/DbException.php b/vendor/topthink/framework/library/think/exception/DbException.php new file mode 100644 index 0000000..0ae80ad --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/DbException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct($message, array $config, $sql, $code = 10500) + { + $this->message = $message; + $this->code = $code; + + $this->setData('Database Status', [ + 'Error Code' => $code, + 'Error Message' => $message, + 'Error SQL' => $sql, + ]); + + unset($config['username'], $config['password']); + $this->setData('Database Config', $config); + } + +} diff --git a/vendor/topthink/framework/library/think/exception/ErrorException.php b/vendor/topthink/framework/library/think/exception/ErrorException.php new file mode 100644 index 0000000..b3a9a30 --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/ErrorException.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * ThinkPHP错误异常 + * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 + * 除开从 think\Exception 继承的功能 + * 其他和PHP系统\ErrorException功能基本一样 + */ +class ErrorException extends Exception +{ + /** + * 用于保存错误级别 + * @var integer + */ + protected $severity; + + /** + * 错误异常构造函数 + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + * @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 + */ + public function __construct($severity, $message, $file, $line, array $context = []) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + + empty($context) || $this->setData('Error Context', $context); + } + + /** + * 获取错误级别 + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/vendor/topthink/framework/library/think/exception/Handle.php b/vendor/topthink/framework/library/think/exception/Handle.php new file mode 100644 index 0000000..f523db0 --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/Handle.php @@ -0,0 +1,282 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Exception; +use think\App; +use think\Config; +use think\console\Output; +use think\Lang; +use think\Log; +use think\Response; + +class Handle +{ + protected $render; + protected $ignoreReport = [ + '\\think\\exception\\HttpException', + ]; + + public function setRender($render) + { + $this->render = $render; + } + + /** + * Report or log an exception. + * + * @param \Exception $exception + * @return void + */ + public function report(Exception $exception) + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if (App::$debug) { + $data = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'code' => $this->getCode($exception), + ]; + $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; + } else { + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + $log = "[{$data['code']}]{$data['message']}"; + } + + if (Config::get('record_trace')) { + $log .= "\r\n" . $exception->getTraceAsString(); + } + + Log::record($log, 'error'); + } + } + + protected function isIgnoreReport(Exception $exception) + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @param \Exception $e + * @return Response + */ + public function render(Exception $e) + { + if ($this->render && $this->render instanceof \Closure) { + $result = call_user_func_array($this->render, [$e]); + if ($result) { + return $result; + } + } + + if ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Output $output + * @param Exception $e + */ + public function renderForConsole(Output $output, Exception $e) + { + if (App::$debug) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + $output->renderException($e); + } + + /** + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + $template = Config::get('http_exception_template'); + if (!App::$debug && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Exception $exception + * @return Response + */ + protected function convertExceptionToResponse(Exception $exception) + { + // 收集异常数据 + if (App::$debug) { + // 调试模式,获取详细的错误信息 + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'trace' => $exception->getTrace(), + 'code' => $this->getCode($exception), + 'source' => $this->getSourceCode($exception), + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => $this->getConst(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!Config::get('show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = Config::get('error_message'); + } + } + + //保留一层 + while (ob_get_level() > 1) { + ob_end_clean(); + } + + $data['echo'] = ob_get_clean(); + + ob_start(); + extract($data); + include Config::get('exception_tmpl'); + // 获取并清空缓存 + $content = ob_get_clean(); + $response = new Response($content, 'html'); + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + if (!isset($statusCode)) { + $statusCode = 500; + } + $response->code($statusCode); + return $response; + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return integer 错误编码 + */ + protected function getCode(Exception $exception) + { + $code = $exception->getCode(); + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return string 错误信息 + */ + protected function getMessage(Exception $exception) + { + $message = $exception->getMessage(); + if (IS_CLI) { + return $message; + } + + if (strpos($message, ':')) { + $name = strstr($message, ':', true); + $message = Lang::has($name) ? Lang::get($name) . strstr($message, ':') : $message; + } elseif (strpos($message, ',')) { + $name = strstr($message, ',', true); + $message = Lang::has($name) ? Lang::get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif (Lang::has($message)) { + $message = Lang::get($message); + } + return $message; + } + + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @param \Exception $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Exception $exception) + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()); + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19), + ]; + } catch (Exception $e) { + $source = []; + } + return $source; + } + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Exception $exception) + { + $data = []; + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + return $data; + } + + /** + * 获取常量列表 + * @return array 常量列表 + */ + private static function getConst() + { + return get_defined_constants(true)['user']; + } +} diff --git a/vendor/topthink/framework/library/think/exception/HttpException.php b/vendor/topthink/framework/library/think/exception/HttpException.php new file mode 100644 index 0000000..01a27fc --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/HttpException.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/topthink/framework/library/think/exception/HttpResponseException.php b/vendor/topthink/framework/library/think/exception/HttpResponseException.php new file mode 100644 index 0000000..5297286 --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/HttpResponseException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Response; + +class HttpResponseException extends \RuntimeException +{ + /** + * @var Response + */ + protected $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse() + { + return $this->response; + } + +} diff --git a/vendor/topthink/framework/library/think/exception/PDOException.php b/vendor/topthink/framework/library/think/exception/PDOException.php new file mode 100644 index 0000000..044f82a --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/PDOException.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) + { + $error = $exception->errorInfo; + + $this->setData('PDO Error Info', [ + 'SQLSTATE' => $error[0], + 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, + 'Driver Error Message' => isset($error[2]) ? $error[2] : '', + ]); + + parent::__construct($exception->getMessage(), $config, $sql, $code); + } +} diff --git a/vendor/topthink/framework/library/think/exception/RouteNotFoundException.php b/vendor/topthink/framework/library/think/exception/RouteNotFoundException.php new file mode 100644 index 0000000..d22e3a6 --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/RouteNotFoundException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/vendor/topthink/framework/library/think/exception/TemplateNotFoundException.php b/vendor/topthink/framework/library/think/exception/TemplateNotFoundException.php new file mode 100644 index 0000000..4202069 --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct($message, $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate() + { + return $this->template; + } +} diff --git a/vendor/topthink/framework/library/think/exception/ThrowableError.php b/vendor/topthink/framework/library/think/exception/ThrowableError.php new file mode 100644 index 0000000..87b6b9d --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/ThrowableError.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ThrowableError extends \ErrorException +{ + public function __construct(\Throwable $e) + { + + if ($e instanceof \ParseError) { + $message = 'Parse error: ' . $e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: ' . $e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = 'Fatal error: ' . $e->getMessage(); + $severity = E_ERROR; + } + + parent::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/vendor/topthink/framework/library/think/exception/ValidateException.php b/vendor/topthink/framework/library/think/exception/ValidateException.php new file mode 100644 index 0000000..b368416 --- /dev/null +++ b/vendor/topthink/framework/library/think/exception/ValidateException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error) + { + $this->error = $error; + $this->message = is_array($error) ? implode("\n\r", $error) : $error; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/vendor/topthink/framework/library/think/log/driver/File.php b/vendor/topthink/framework/library/think/log/driver/File.php new file mode 100644 index 0000000..bace4c2 --- /dev/null +++ b/vendor/topthink/framework/library/think/log/driver/File.php @@ -0,0 +1,272 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; +use think\Request; + +/** + * 本地化调试输出到文件 + */ +class File +{ + protected $config = [ + 'time_format' => ' c ', + 'single' => false, + 'file_size' => 2097152, + 'path' => LOG_PATH, + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; + } + + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + + $this->write($info[$type], $filename, true, $append); + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination, false, $append); + } + + return true; + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile() + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + $destination = $this->config['path'] . $name . $cli . '.log'; + } else { + if ($this->config['max_files']) { + $filename = date('Ymd') . $cli . '.log'; + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + } + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + } elseif ($this->config['max_files']) { + $name = date('Ymd'); + } else { + $name = date('d'); + } + + return $path . DIRECTORY_SEPARATOR . $name . '_' . $type . $cli . '.log'; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $msg = is_array($msg) ? implode("\r\n", $msg) : $msg; + if (PHP_SAPI == 'cli') { + $info['msg'] = $msg; + $info['type'] = $type; + } else { + $info[$type] = $msg; + } + } + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); + } + + return error_log($message, 3, $destination); + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + } + } + + /** + * CLI日志解析 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseCliLog($info) + { + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); + + $message = implode("\r\n", $info); + + $message = "[{$now}]" . $message . "\r\n"; + } + + return $message; + } + + /** + * 解析日志 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseLog($info) + { + $request = Request::instance(); + $requestInfo = [ + 'ip' => $request->ip(), + 'method' => $request->method(), + 'host' => $request->host(), + 'uri' => $request->url(), + ]; + + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } + + array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); + + return implode("\r\n", $info) . "\r\n"; + } + + protected function getDebugLog(&$info, $append, $apart) + { + if (App::$debug && $append) { + + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; + + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + array_unshift($info, $time_str . $memory_str . $file_load); + } + } + } +} diff --git a/vendor/topthink/framework/library/think/log/driver/Socket.php b/vendor/topthink/framework/library/think/log/driver/Socket.php new file mode 100644 index 0000000..4f62915 --- /dev/null +++ b/vendor/topthink/framework/library/think/log/driver/Socket.php @@ -0,0 +1,250 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket +{ + public $port = 1116; //SocketLog 服务的http的端口号 + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + ]; + + protected $css = [ + 'sql' => 'color:#009bb4;', + 'sql_warn' => 'color:#009bb4;font-size:14px;', + 'error' => 'color:#f4006b;font-size:14px;', + 'page' => 'color:#40e2ff;background:#171717;', + 'big' => 'font-size:20px;color:red;', + ]; + + protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + + /** + * 构造函数 + * @param array $config 缓存参数 + * @access public + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + if (!$this->check()) { + return false; + } + $trace = []; + if (App::$debug) { + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; + } + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => isset($this->css[$type]) ? $this->css[$type] : '', + ]; + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + $trace[] = [ + 'type' => 'log', + 'msg' => $msg, + 'css' => '', + ]; + } + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + if ($this->config['show_included_files']) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ file ]', + 'css' => '', + ]; + $trace[] = [ + 'type' => 'log', + 'msg' => implode("\n", get_included_files()), + 'css' => '', + ]; + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + + $tabid = $this->getClientArg('tabid'); + if (!$client_id = $this->getClientArg('client_id')) { + $client_id = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $force_client_id) { + $client_id = $force_client_id; + $this->sendToClient($tabid, $client_id, $trace, $force_client_id); + } + } else { + $this->sendToClient($tabid, $client_id, $trace, ''); + } + return true; + } + + /** + * 发送给指定客户端 + * @author Zjmainstay + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id + */ + protected function sendToClient($tabid, $client_id, $logs, $force_client_id) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $client_id, + 'logs' => $logs, + 'force_client_id' => $force_client_id, + ]; + $msg = @json_encode($logs); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + $this->send($this->config['host'], $msg, $address); + } + + protected function check() + { + $tabid = $this->getClientArg('tabid'); + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + //用户认证 + $allow_client_ids = $this->config['allow_client_ids']; + if (!empty($allow_client_ids)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $client_id = $this->getClientArg('client_id'); + if (!in_array($client_id, $allow_client_ids)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + return true; + } + + protected function getClientArg($name) + { + static $args = []; + + $key = 'HTTP_USER_AGENT'; + + if (isset($_SERVER['HTTP_SOCKETLOG'])) { + $key = 'HTTP_SOCKETLOG'; + } + + if (!isset($_SERVER[$key])) { + return; + } + if (empty($args)) { + if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { + $args = ['tabid' => null]; + return; + } + parse_str($match[1], $args); + } + if (isset($args[$name])) { + return $args[$name]; + } + return; + } + + /** + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $this->port . $address; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $message); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $headers = [ + "Content-Type: application/json;charset=UTF-8", + ]; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header + return curl_exec($ch); + } + +} diff --git a/vendor/topthink/framework/library/think/log/driver/Test.php b/vendor/topthink/framework/library/think/log/driver/Test.php new file mode 100644 index 0000000..7f66338 --- /dev/null +++ b/vendor/topthink/framework/library/think/log/driver/Test.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +/** + * 模拟测试输出 + */ +class Test +{ + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = []) + { + return true; + } + +} diff --git a/vendor/topthink/framework/library/think/model/Collection.php b/vendor/topthink/framework/library/think/model/Collection.php new file mode 100644 index 0000000..0406533 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/Collection.php @@ -0,0 +1,79 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + return $this; + } + + /** + * 设置需要输出的属性 + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model && $model->append($append, $override); + }); + return $this; + } + +} diff --git a/vendor/topthink/framework/library/think/model/Merge.php b/vendor/topthink/framework/library/think/model/Merge.php new file mode 100644 index 0000000..4a9da81 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/Merge.php @@ -0,0 +1,322 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Db; +use think\db\Query; +use think\Model; + +class Merge extends Model +{ + + protected $relationModel = []; // HAS ONE 关联的模型列表 + protected $fk = ''; // 外键名 默认为主表名_id + protected $mapFields = []; // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) + + /** + * 构造函数 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + parent::__construct($data); + + // 设置默认外键名 仅支持单一外键 + if (empty($this->fk)) { + $this->fk = strtolower($this->name) . '_id'; + } + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param string|array $with 关联预查询 + * @param bool $cache 是否缓存 + * @return \think\Model + */ + public static function get($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->find($data); + } + + /** + * 附加查询表达式 + * @access protected + * @param \think\db\Query $query 查询对象 + * @return \think\db\Query + */ + protected static function attachQuery($query) + { + $class = new static(); + $master = $class->name; + $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); + $query->alias($master)->field($fields); + + foreach ($class->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $query->getTable($name) : $model; + $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); + $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); + $query->field($fields); + } + return $query; + } + + /** + * 获取关联模型的字段 并解决混淆 + * @access protected + * @param \think\db\Query $query 查询对象 + * @param string $name 模型名称 + * @param string $table 关联表名称 + * @param array $map 字段映射 + * @param array $fields 查询字段 + * @return array + */ + protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) + { + // 获取模型的字段信息 + $fields = $fields ?: $query->getTableInfo($table, 'fields'); + $array = []; + foreach ($fields as $field) { + if ($key = array_search($name . '.' . $field, $map)) { + // 需要处理映射字段 + $array[] = $name . '.' . $field . ' AS ' . $key; + } else { + $array[] = $field; + } + } + return $array; + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache + * @return array|false|string + */ + public static function all($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->select($data); + } + + /** + * 处理写入的模型数据 + * @access public + * @param string $model 模型名称 + * @param array $data 数据 + * @return array + */ + protected function parseData($model, $data) + { + $item = []; + foreach ($data as $key => $val) { + if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { + list($name, $key) = explode('.', $this->mapFields[$key]); + if ($model == $name) { + $item[$key] = $val; + } + } else { + $item[$key] = $val; + } + } + return $item; + } + + /** + * 保存模型数据 以及关联数据 + * @access public + * @param mixed $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return false|int + * @throws \Exception + */ + public function save($data = [], $where = [], $sequence = null) + { + if (!empty($data)) { + // 数据自动验证 + if (!$this->validateData($data)) { + return false; + } + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + if (!empty($where)) { + $this->isUpdate = true; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 自动写入更新时间 + if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { + $this->setAttr($this->updateTime, null); + } + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + $pk = $this->getPk(); + try { + if ($this->isUpdate) { + // 自动写入 + $this->autoCompleteData($this->update); + + if (false === $this->trigger('before_update', $this)) { + return false; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + // 处理模型数据 + $data = $this->parseData($this->name, $data); + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } + // 写入主表数据 + $result = $db->strict(false)->where($where)->update($data); + + // 写入附表数据 + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $data); + if (Db::table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { + $result = 1; + } + } + + // 新增回调 + $this->trigger('after_update', $this); + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间 + if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { + $this->setAttr($this->createTime, null); + } + + if (false === $this->trigger('before_insert', $this)) { + return false; + } + + // 处理模型数据 + $data = $this->parseData($this->name, $this->data); + // 写入主表数据 + $result = $db->name($this->name)->strict(false)->insert($data); + if ($result) { + $insertId = $db->getLastInsID($sequence); + // 写入外键数据 + if ($insertId) { + if (is_string($pk)) { + $this->data[$pk] = $insertId; + } + $this->data[$this->fk] = $insertId; + } + + // 写入附表数据 + $source = $this->data; + if ($insertId && is_string($pk) && isset($source[$pk]) && $this->fk != $pk) { + unset($source[$pk]); + } + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $source); + Db::table($table)->strict(false)->insert($data); + } + } + // 标记为更新 + $this->isUpdate = true; + // 新增回调 + $this->trigger('after_insert', $this); + } + $db->commit(); + // 写入回调 + $this->trigger('after_write', $this); + + $this->origin = $this->data; + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 删除当前的记录 并删除关联数据 + * @access public + * @return int + * @throws \Exception + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + try { + $result = $db->delete($this->data); + if ($result) { + // 获取主键数据 + $pk = $this->data[$this->getPk()]; + + // 删除关联数据 + foreach ($this->relationModel as $key => $model) { + $table = is_int($key) ? $db->getTable($model) : $model; + $query = new Query; + $query->table($table)->where($this->fk, $pk)->delete(); + } + } + $this->trigger('after_delete', $this); + $db->commit(); + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + +} diff --git a/vendor/topthink/framework/library/think/model/Pivot.php b/vendor/topthink/framework/library/think/model/Pivot.php new file mode 100644 index 0000000..13525cd --- /dev/null +++ b/vendor/topthink/framework/library/think/model/Pivot.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Model; + +class Pivot extends Model +{ + + /** @var Model */ + public $parent; + + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct($data = [], Model $parent = null, $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/vendor/topthink/framework/library/think/model/Relation.php b/vendor/topthink/framework/library/think/model/Relation.php new file mode 100644 index 0000000..25fe88d --- /dev/null +++ b/vendor/topthink/framework/library/think/model/Relation.php @@ -0,0 +1,155 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\db\Query; +use think\Exception; +use think\Model; + +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation +{ + // 父模型对象 + protected $parent; + /** @var Model 当前关联的模型类 */ + protected $model; + /** @var Query 关联模型查询对象 */ + protected $query; + // 关联表外键 + protected $foreignKey; + // 关联表主键 + protected $localKey; + // 基础查询 + protected $baseQuery; + // 是否为自关联 + protected $selfRelation; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前的关联模型对象实例 + * @access public + * @return Model + */ + public function getModel() + { + return $this->query->getModel(); + } + + /** + * 获取关联的查询对象 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 设置当前关联为自关联 + * @access public + * @param bool $self 是否自关联 + * @return $this + */ + public function selfRelation($self = true) + { + $this->selfRelation = $self; + return $this; + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation() + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @return mixed + */ + protected function resultSetBuild($resultSet) + { + return (new $this->model)->toCollection($resultSet); + } + + protected function getQueryFields($model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, $model) + { + if ($fields) { + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + } else { + $fields = $model . '.*'; + } + + return $fields; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof Query) { + return $this; + } else { + $this->baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/vendor/topthink/framework/library/think/model/relation/BelongsTo.php b/vendor/topthink/framework/library/think/model/relation/BelongsTo.php new file mode 100644 index 0000000..c1cbab9 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/BelongsTo.php @@ -0,0 +1,243 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class BelongsTo extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $joinType JOIN类型 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + $this->relation = $relation; + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @access public + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere($this->query, [ + $localKey => [ + 'in', + $range, + ], + ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere($this->query, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $foreignKey = $this->foreignKey; + $pk = $model->getPk(); + + $this->parent->setAttr($foreignKey, $model->$pk); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $foreignKey = $this->foreignKey; + + $this->parent->setAttr($foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/vendor/topthink/framework/library/think/model/relation/BelongsToMany.php b/vendor/topthink/framework/library/think/model/relation/BelongsToMany.php new file mode 100644 index 0000000..a41c45c --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,644 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Collection; +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +class BelongsToMany extends Relation +{ + // 中间表表名 + protected $middle; + // 中间表模型名称 + protected $pivotName; + // 中间表模型对象 + protected $pivot; + // 中间表数据名称 + protected $pivotDataName = 'pivot'; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + if (false !== strpos($table, '\\')) { + $this->pivotName = $table; + $this->middle = basename(str_replace('\\', '/', $table)); + } else { + $this->middle = $table; + } + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + + if ('think\model\Pivot' == get_class($this->pivot)) { + $this->pivot->name($this->middle); + } + } + + /** + * 设置中间表模型 + * @param $pivot + * @return $this + */ + public function pivot($pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function pivotDataName($name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 获取中间表更新条件 + * @param $data + * @return array + */ + protected function getUpdateWhere($data) + { + return [ + $this->localKey => $data[$this->localKey], + $this->foreignKey => $data[$this->foreignKey], + ]; + } + + /** + * 实例化中间表模型 + * @param array $data + * @param bool $isUpdate + * @return Pivot + * @throws Exception + */ + protected function newPivot($data = [], $isUpdate = false) + { + $class = $this->pivotName ?: '\\think\\model\\Pivot'; + $pivot = new $class($data, $this->parent, $this->middle); + if ($pivot instanceof Pivot) { + return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 合成中间表模型 + * @param array|Collection|Paginator $models + */ + protected function hydratePivot($models) + { + foreach ($models as $model) { + $pivot = []; + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + } + } + + /** + * 创建关联查询Query对象 + * @return Query + */ + protected function buildQuery() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $pk = $this->parent->getPk(); + // 关联查询 + $condition['pivot.' . $localKey] = $this->parent->$pk; + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $result = $this->buildQuery()->relation($subRelation)->select(); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载select方法 + * @param null $data + * @return false|\PDOStatement|string|Collection + */ + public function select($data = null) + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载paginate方法 + * @param null $listRows + * @param bool $simple + * @param array $config + * @return Paginator + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + $result = $this->buildQuery()->paginate($listRows, $simple, $config); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载find方法 + * @param null $data + * @return array|false|\PDOStatement|string|Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + if ($result) { + $this->hydratePivot([$result]); + } + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @param $field + * @param null $op + * @param null $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $field = 'pivot.' . $field; + $this->query->where($field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $resultSet[0]->getPk(); + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + 'pivot.' . $localKey => [ + 'in', + $range, + ], + ], $relation, $subRelation); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + $pk = $result->$pk; + $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + } + return $count; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + 'pivot.' . $this->localKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->count(); + } + + /** + * 多对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + $data[$pivot[$this->localKey]][] = $set; + } + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access public + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->getTable(); + $fields = $this->getQueryFields($tableName); + + $query = $this->query->field($fields) + ->field(true, false, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + return $query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return integer + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = false; + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + $result = $this->attach($data, $pivotData); + } + return $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $model->save($data); + $id = $model->getLastInsID(); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->insert($pivot, true); + $result[] = $this->newPivot($pivot, true); + } + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot + * @throws Exception + */ + public function attached($data) + { + if ($data instanceof Model) { + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } else { + $id = $data; + } + + $pk = $this->parent->getPk(); + + $pivot = $this->pivot->where($this->localKey, $this->parent->$pk)->where($this->foreignKey, $id)->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + if (isset($id)) { + $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + } + $this->pivot->where($pivot)->delete(); + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + } + + /** + * 数据同步 + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + $pk = $this->parent->getPk(); + $current = $this->pivot->where($this->localKey, $this->parent->$pk) + ->column($this->foreignKey); + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && + $this->attach($id, $attributes) + ) { + $changes['updated'][] = $id; + } + } + + return $changes; + + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $table = $this->pivot->getTable(); + $this->query->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/framework/library/think/model/relation/HasMany.php b/vendor/topthink/framework/library/think/model/relation/HasMany.php new file mode 100644 index 0000000..ebab051 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/HasMany.php @@ -0,0 +1,318 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasMany extends Relation +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany($this->query, [ + $this->foreignKey => [ + 'in', + $range, + ], + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $data = $this->eagerlyOneToMany($this->query, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + // 关联数据封装 + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $localKey = $this->localKey; + $count = 0; + if (isset($result->$localKey)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where($this->foreignKey, $result->$localKey)->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + $localKey = $this->localKey ?: $this->parent->getPk(); + return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count(); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure + * @return array + */ + protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + { + $foreignKey = $this->foreignKey; + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + } + $list = $model->removeWhereField($foreignKey)->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + $model = new $this->model(); + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->group($model . '.' . $this->localKey) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->where($where); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + } + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/framework/library/think/model/relation/HasManyThrough.php b/vendor/topthink/framework/library/think/model/relation/HasManyThrough.php new file mode 100644 index 0000000..3a9a548 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,157 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 关联主键 + */ + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->through = $through; + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + {} + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + {} + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + {} + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $through = $this->through; + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $through::getTable(); + $pk = (new $through)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $this->query->field($alias . '.*')->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/framework/library/think/model/relation/HasOne.php b/vendor/topthink/framework/library/think/model/relation/HasOne.php new file mode 100644 index 0000000..db74e4a --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/HasOne.php @@ -0,0 +1,215 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param string $joinType JOIN类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + // 执行关联定义方法 + $localKey = $this->localKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @return Query + */ + public function has() + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation])->field($relation . '.' . $foreignKey)->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere($this->query, [ + $foreignKey => [ + 'in', + $range, + ], + ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere($this->query, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/vendor/topthink/framework/library/think/model/relation/MorphMany.php b/vendor/topthink/framework/library/think/model/relation/MorphMany.php new file mode 100644 index 0000000..2755d57 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/MorphMany.php @@ -0,0 +1,314 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToMany([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $data = $this->eagerlyMorphToMany([ + $this->morphKey => $result->$pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + if ($closure) { + $return = call_user_func_array($closure, [ & $this->query]); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query->where([ + $this->morphKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + $this->morphType => $this->type, + ])->fetchSql()->count(); + } + + /** + * 多态一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + $model = new $this->model(); + + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/framework/library/think/model/relation/MorphOne.php b/vendor/topthink/framework/library/think/model/relation/MorphOne.php new file mode 100644 index 0000000..5ec7172 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/MorphOne.php @@ -0,0 +1,263 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphOne extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + $this->morphKey => $pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->find(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + $model = new $this->model(); + + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/vendor/topthink/framework/library/think/model/relation/MorphTo.php b/vendor/topthink/framework/library/think/model/relation/MorphTo.php new file mode 100644 index 0000000..7d45265 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/MorphTo.php @@ -0,0 +1,299 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + protected $relation; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + return (new $model); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return mixed + */ + public function getRelation($subRelation = '', $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + // 主键数据 + $pk = $this->parent->$morphKey; + $relationModel = (new $model)->relation($subRelation)->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + throw new Exception('relation data not exists :' . $this->model); + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + $result->setRelation($attr, $relationModel); + } + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 多态MorphTo 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param string $relation 关联名 + * @param $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + if ($data) { + $data->setParent(clone $result); + $data->isUpdate(true); + } + $result->setRelation(Loader::parseName($relation), $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate($model, $type = '') + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, $type ?: get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/vendor/topthink/framework/library/think/model/relation/OneToOne.php b/vendor/topthink/framework/library/think/model/relation/OneToOne.php new file mode 100644 index 0000000..353ce21 --- /dev/null +++ b/vendor/topthink/framework/library/think/model/relation/OneToOne.php @@ -0,0 +1,337 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + // 关联方法名 + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($query->getModel())))); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $name); + $field = null; + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + } else { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + } + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [ & $query]); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } + $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr() + { + return $this->bindAttr; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } else { + $relationModel = null; + } + $result->setRelation(Loader::parseName($relation), $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @param array $bindAttr 绑定属性 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result, $bindAttr) + { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($result->$key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $result->setAttr($key, $model ? $model->$attr : null); + } + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure + * @return array + */ + protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + { + $this->baseQuery = true; + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + if ($field = $model->getOptions('with_field')) { + $model->field($field)->removeOption('with_field'); + } + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$key] = $set; + } + return $data; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $name 统计数据别名 + * @return string + */ + public function getRelationCountQuery($closure, &$name = null) + { + throw new Exception('relation not support: withCount'); + } +} diff --git a/vendor/topthink/framework/library/think/paginator/driver/Bootstrap.php b/vendor/topthink/framework/library/think/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..c5ac60d --- /dev/null +++ b/vendor/topthink/framework/library/think/paginator/driver/Bootstrap.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton($text = "«") + { + + if ($this->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton($text = '»') + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks() + { + if ($this->simple) + return ''; + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null + ]; + + $side = 3; + $window = $side * 2; + + if ($this->lastPage < $window + 6) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render() + { + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
        %s %s
      ', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
        %s %s %s
      ', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getAvailablePageWrapper($url, $page) + { + return '
    • ' . $page . '
    • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
    • ' . $text . '
    • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
    • ' . $text . '
    • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots() + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls) + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getPageLinkWrapper($url, $page) + { + if ($page == $this->currentPage()) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/vendor/topthink/framework/library/think/process/Builder.php b/vendor/topthink/framework/library/think/process/Builder.php new file mode 100644 index 0000000..da56163 --- /dev/null +++ b/vendor/topthink/framework/library/think/process/Builder.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +use think\Process; + +class Builder +{ + private $arguments; + private $cwd; + private $env = null; + private $input; + private $timeout = 60; + private $options = []; + private $inheritEnv = true; + private $prefix = []; + private $outputDisabled = false; + + /** + * 构造方法 + * @param string[] $arguments 参数 + */ + public function __construct(array $arguments = []) + { + $this->arguments = $arguments; + } + + /** + * 创建一个实例 + * @param string[] $arguments 参数 + * @return self + */ + public static function create(array $arguments = []) + { + return new static($arguments); + } + + /** + * 添加一个参数 + * @param string $argument 参数 + * @return self + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * 添加一个前缀 + * @param string|array $prefix + * @return self + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : [$prefix]; + + return $this; + } + + /** + * 设置参数 + * @param string[] $arguments + * @return self + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * 设置工作目录 + * @param null|string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * 是否初始化环境变量 + * @param bool $inheritEnv + * @return self + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * 设置环境变量 + * @param string $name + * @param null|string $value + * @return self + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * 添加环境变量 + * @param array $variables + * @return self + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) + { + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 设置超时时间 + * @param float|null $timeout + * @return self + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * 设置proc_open选项 + * @param string $name + * @param string $value + * @return self + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + /** + * 禁止输出 + * @return self + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启输出 + * @return self + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * 创建一个Process实例 + * @return Process + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new \LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = array_merge($this->prefix, $this->arguments); + $script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); + + if ($this->inheritEnv) { + // include $_ENV for BC purposes + $env = array_replace($_ENV, $_SERVER, $this->env); + } else { + $env = $this->env; + } + + $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/vendor/topthink/framework/library/think/process/Utils.php b/vendor/topthink/framework/library/think/process/Utils.php new file mode 100644 index 0000000..f94c648 --- /dev/null +++ b/vendor/topthink/framework/library/think/process/Utils.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +class Utils +{ + + /** + * 转义字符串 + * @param string $argument + * @return string + */ + public static function escapeArgument($argument) + { + + if ('' === $argument) { + return escapeshellarg($argument); + } + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"' . $escapedArgument . '"'; + } + return $escapedArgument; + } + + /** + * 验证并进行规范化Process输入。 + * @param string $caller + * @param mixed $input + * @return string + * @throws \InvalidArgumentException + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + } + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } + +} diff --git a/vendor/topthink/framework/library/think/process/exception/Failed.php b/vendor/topthink/framework/library/think/process/exception/Failed.php new file mode 100644 index 0000000..5295082 --- /dev/null +++ b/vendor/topthink/framework/library/think/process/exception/Failed.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/vendor/topthink/framework/library/think/process/exception/Timeout.php b/vendor/topthink/framework/library/think/process/exception/Timeout.php new file mode 100644 index 0000000..d5f1162 --- /dev/null +++ b/vendor/topthink/framework/library/think/process/exception/Timeout.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Timeout extends \RuntimeException +{ + + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf('The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout())); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/vendor/topthink/framework/library/think/process/pipes/Pipes.php b/vendor/topthink/framework/library/think/process/pipes/Pipes.php new file mode 100644 index 0000000..82396b8 --- /dev/null +++ b/vendor/topthink/framework/library/think/process/pipes/Pipes.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +abstract class Pipes +{ + + /** @var array */ + public $pipes = []; + + /** @var string */ + protected $inputBuffer = ''; + /** @var resource|null */ + protected $input; + + /** @var bool */ + private $blocked = true; + + const CHUNK_SIZE = 16384; + + /** + * 返回用于 proc_open 描述符的数组 + * @return array + */ + abstract public function getDescriptors(); + + /** + * 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 + * @return string[] + */ + abstract public function getFiles(); + + /** + * 文件句柄和管道中读取数据。 + * @param bool $blocking 是否使用阻塞调用 + * @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 + * @return string[] + */ + abstract public function readAndWrite($blocking, $close = false); + + /** + * 返回当前状态如果有打开的文件句柄或管道。 + * @return bool + */ + abstract public function areOpen(); + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = []; + } + + /** + * 检查系统调用已被中断 + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (null !== $this->input) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } +} diff --git a/vendor/topthink/framework/library/think/process/pipes/Unix.php b/vendor/topthink/framework/library/think/process/pipes/Unix.php new file mode 100644 index 0000000..fd99a5d --- /dev/null +++ b/vendor/topthink/framework/library/think/process/pipes/Unix.php @@ -0,0 +1,196 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Unix extends Pipes +{ + + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $disableOutput; + + public function __construct($ttyMode, $ptyMode, $input, $disableOutput) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + + if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + if (empty($this->pipes)) { + return []; + } + + $this->unblock(); + + $read = []; + + if (null !== $this->input) { + $r = array_merge($this->pipes, ['input' => $this->input]); + } else { + $r = $this->pipes; + } + + unset($r[0]); + + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + + if (0 === $n) { + return $read; + } + + foreach ($r as $pipe) { + + $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; + $data = ''; + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { + $data .= $dataread; + } + + if ('' !== $data) { + if ('input' === $type) { + $this->inputBuffer .= $data; + } else { + $read[$type] = $data; + } + } + + if (false === $data || (true === $close && feof($pipe) && '' === $data)) { + if ('input' === $type) { + $this->input = null; + } else { + fclose($this->pipes[$type]); + unset($this->pipes[$type]); + } + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } + + /** + * 创建一个新的 UnixPipes 实例 + * @param Process $process + * @param string|resource $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); + } +} diff --git a/vendor/topthink/framework/library/think/process/pipes/Windows.php b/vendor/topthink/framework/library/think/process/pipes/Windows.php new file mode 100644 index 0000000..1b8b0d4 --- /dev/null +++ b/vendor/topthink/framework/library/think/process/pipes/Windows.php @@ -0,0 +1,228 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Windows extends Pipes +{ + + /** @var array */ + private $files = []; + /** @var array */ + private $fileHandles = []; + /** @var array */ + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + /** @var bool */ + private $disableOutput; + + public function __construct($disableOutput, $input) + { + $this->disableOutput = (bool) $disableOutput; + + if (!$this->disableOutput) { + + $this->files = [ + Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), + Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), + ]; + foreach ($this->files as $offset => $file) { + $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); + if (false === $this->fileHandles[$offset]) { + throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + } + } + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = $input; + } + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->write($blocking, $close); + + $read = []; + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + if (0 !== fseek($fileHandle, $this->readBytes[$type])) { + continue; + } + $data = ''; + $dataread = null; + while (!feof($fileHandle)) { + if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { + $data .= $dataread; + } + } + if (0 < $length = strlen($data)) { + $this->readBytes[$type] += $length; + $read[$type] = $data; + } + + if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { + fclose($this->fileHandles[$type]); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes && (bool) $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = []; + } + + /** + * 创建一个新的 WindowsPipes 实例。 + * @param Process $process + * @param $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isOutputDisabled(), $input); + } + + /** + * 删除临时文件 + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = []; + } + + /** + * 写入到 stdin 输入 + * @param bool $blocking + * @param bool $close + */ + private function write($blocking, $close) + { + if (empty($this->pipes)) { + return; + } + + $this->unblock(); + + $r = null !== $this->input ? ['input' => $this->input] : null; + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return; + } + + if (0 === $n) { + return; + } + + if (null !== $r && 0 < count($r)) { + $data = ''; + while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { + $data .= $dataread; + } + + $this->inputBuffer .= $data; + + if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { + $this->input = null; + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + } +} diff --git a/vendor/topthink/framework/library/think/response/Json.php b/vendor/topthink/framework/library/think/response/Json.php new file mode 100644 index 0000000..c906bfc --- /dev/null +++ b/vendor/topthink/framework/library/think/response/Json.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/vendor/topthink/framework/library/think/response/Jsonp.php b/vendor/topthink/framework/library/think/response/Jsonp.php new file mode 100644 index 0000000..404bacb --- /dev/null +++ b/vendor/topthink/framework/library/think/response/Jsonp.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\Response; + +class Jsonp extends Response +{ + // 输出参数 + protected $options = [ + 'var_jsonp_handler' => 'callback', + 'default_jsonp_handler' => 'jsonpReturn', + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/javascript'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/vendor/topthink/framework/library/think/response/Redirect.php b/vendor/topthink/framework/library/think/response/Redirect.php new file mode 100644 index 0000000..91694cb --- /dev/null +++ b/vendor/topthink/framework/library/think/response/Redirect.php @@ -0,0 +1,105 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\Response; +use think\Session; +use think\Url; + +class Redirect extends Response +{ + + protected $options = []; + + // URL参数 + protected $params = []; + + public function __construct($data = '', $code = 302, array $header = [], array $options = []) + { + parent::__construct($data, $code, $header, $options); + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + $this->header['Location'] = $this->getTargetUrl(); + return; + } + + /** + * 重定向传值(通过Session) + * @access protected + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 + * @return $this + */ + public function with($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + Session::flash($key, $val); + } + } else { + Session::flash($name, $value); + } + return $this; + } + + /** + * 获取跳转地址 + * @return string + */ + public function getTargetUrl() + { + if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { + return $this->data; + } else { + return Url::build($this->data, $this->params); + } + } + + public function params($params = []) + { + $this->params = $params; + return $this; + } + + /** + * 记住当前url后跳转 + * @return $this + */ + public function remember() + { + Session::set('redirect_url', Request::instance()->url()); + return $this; + } + + /** + * 跳转到上次记住的url + * @return $this + */ + public function restore() + { + if (Session::has('redirect_url')) { + $this->data = Session::get('redirect_url'); + Session::delete('redirect_url'); + } + return $this; + } +} diff --git a/vendor/topthink/framework/library/think/response/View.php b/vendor/topthink/framework/library/think/response/View.php new file mode 100644 index 0000000..48f944a --- /dev/null +++ b/vendor/topthink/framework/library/think/response/View.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Config; +use think\Response; +use think\View as ViewTemplate; + +class View extends Response +{ + // 输出参数 + protected $options = []; + protected $vars = []; + protected $replace = []; + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // 渲染模板输出 + return ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch($data, $this->vars, $this->replace); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars($name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return isset($this->vars[$name]) ? $this->vars[$name] : null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + return $this; + } else { + $this->vars[$name] = $value; + } + return $this; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + +} diff --git a/vendor/topthink/framework/library/think/response/Xml.php b/vendor/topthink/framework/library/think/response/Xml.php new file mode 100644 index 0000000..3bdc052 --- /dev/null +++ b/vendor/topthink/framework/library/think/response/Xml.php @@ -0,0 +1,102 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Collection; +use think\Model; +use think\Response; + +class Xml extends Response +{ + // 输出参数 + protected $options = [ + // 根节点名 + 'root_node' => 'think', + // 根节点属性 + 'root_attr' => '', + //数字索引的子节点名 + 'item_node' => 'item', + // 数字索引子节点key转换的属性名 + 'item_key' => 'id', + // 数据编码 + 'encoding' => 'utf-8', + ]; + + protected $contentType = 'text/xml'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // XML数据转换 + return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); + } + + /** + * XML编码 + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) + { + if (is_array($attr)) { + $array = []; + foreach ($attr as $key => $value) { + $array[] = "{$key}=\"{$value}\""; + } + $attr = implode(' ', $array); + } + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = ""; + $xml .= "<{$root}{$attr}>"; + $xml .= $this->dataToXml($data, $item, $id); + $xml .= ""; + return $xml; + } + + /** + * 数据XML编码 + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, $item, $id) + { + $xml = $attr = ''; + + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + $xml .= "<{$key}{$attr}>"; + $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; + $xml .= ""; + } + return $xml; + } +} diff --git a/vendor/topthink/framework/library/think/session/driver/Memcache.php b/vendor/topthink/framework/library/think/session/driver/Memcache.php new file mode 100644 index 0000000..877d7bd --- /dev/null +++ b/vendor/topthink/framework/library/think/session/driver/Memcache.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcache extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'persistent' => true, // 长连接 + 'session_name' => '', // memcache key前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcache')) { + throw new Exception('not support:memcache'); + } + $this->handler = new \Memcache; + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->config['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : + $this->handler->addServer($host, $port, $this->config['persistent'], 1); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/vendor/topthink/framework/library/think/session/driver/Memcached.php b/vendor/topthink/framework/library/think/session/driver/Memcached.php new file mode 100644 index 0000000..2994a07 --- /dev/null +++ b/vendor/topthink/framework/library/think/session/driver/Memcached.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcached extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'session_name' => '', // memcache key前缀 + 'username' => '', //账号 + 'password' => '', //密码 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcached')) { + throw new Exception('not support:memcached'); + } + $this->handler = new \Memcached; + // 设置连接超时时间(单位:毫秒) + if ($this->config['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); + } + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + $this->handler->addServers($servers); + if ('' != $this->config['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->quit(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/vendor/topthink/framework/library/think/session/driver/Redis.php b/vendor/topthink/framework/library/think/session/driver/Redis.php new file mode 100644 index 0000000..8d05126 --- /dev/null +++ b/vendor/topthink/framework/library/think/session/driver/Redis.php @@ -0,0 +1,128 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Redis extends SessionHandler +{ + /** @var \Redis */ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // redis主机 + 'port' => 6379, // redis端口 + 'password' => '', // 密码 + 'select' => 0, // 操作库 + 'expire' => 3600, // 有效期(秒) + 'timeout' => 0, // 超时时间(秒) + 'persistent' => true, // 是否长连接 + 'session_name' => '', // sessionkey前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + * @return bool + * @throws Exception + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('redis')) { + throw new Exception('not support:redis'); + } + $this->handler = new \Redis; + + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } + + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + if ($this->config['expire'] > 0) { + return $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); + } else { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData); + } + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID) > 0; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return bool + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/vendor/topthink/framework/library/think/template/TagLib.php b/vendor/topthink/framework/library/think/template/TagLib.php new file mode 100644 index 0000000..c5b72f9 --- /dev/null +++ b/vendor/topthink/framework/library/think/template/TagLib.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use think\Exception; + +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +class TagLib +{ + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = []; // 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib = ''; + + /** + * 标签库标签列表 + * @var array + * @access protected + */ + protected $tagList = []; + + /** + * 标签库分析数组 + * @var array + * @access protected + */ + protected $parse = []; + + /** + * 标签库是否有效 + * @var bool + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + protected $tpl; + + protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; + + /** + * 构造函数 + * @access public + * @param \stdClass $template 模板引擎对象 + */ + public function __construct($template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(&$content, $lib = '') + { + $tags = []; + $lib = $lib ? strtolower($lib) . ':' : ''; + foreach ($this->tags as $name => $val) { + $close = !isset($val['close']) || $val['close'] ? 1 : 0; + $tags[$close][$lib . $name] = $name; + if (isset($val['alias'])) { + // 别名设置 + $array = (array) $val['alias']; + foreach (explode(',', $array[0]) as $v) { + $tags[$close][$lib . $v] = $name; + } + } + } + + // 闭合标签 + if (!empty($tags[1])) { + $nodes = []; + $regex = $this->getRegex(array_keys($tags[1]), 1); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if ('' == $match[1][0]) { + $name = strtolower($match[2][0]); + // 如果有没闭合的标签头则取出最后一个 + if (!empty($right[$name])) { + // $match[0][1]为标签结束符在模板中的位置 + $nodes[$match[0][1]] = [ + 'name' => $name, + 'begin' => array_pop($right[$name]), // 标签开始符 + 'end' => $match[0], // 标签结束符 + ]; + } + } else { + // 标签头压入栈 + $right[strtolower($match[1][0])][] = $match[0]; + } + } + unset($right, $matches); + // 按标签在模板中的位置从后向前排序 + krsort($nodes); + } + + $break = ''; + if ($nodes) { + $beginArray = []; + // 标签替换 从后向前 + foreach ($nodes as $pos => $node) { + // 对应的标签名 + $name = $tags[1][$node['name']]; + $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($node['begin'][0], $name, $alias); + $method = 'tag' . $name; + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 + $replace = explode($break, $this->$method($attrs, $break)); + if (count($replace) > 1) { + while ($beginArray) { + $begin = end($beginArray); + // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 + if ($node['end'][1] > $begin['pos']) { + break; + } else { + // 不为子标签时,取出栈中最后一个标签头 + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + // 替换标签尾部 + $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); + // 把标签头压入栈 + $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; + } + } + while ($beginArray) { + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + } + // 自闭合标签 + if (!empty($tags[0])) { + $regex = $this->getRegex(array_keys($tags[0]), 0); + $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { + // 对应的标签名 + $name = $tags[0][strtolower($matches[1])]; + $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($matches[0], $name, $alias); + $method = 'tag' . $name; + return $this->$method($attrs, ''); + }, $content); + } + return; + } + + /** + * 按标签生成正则 + * @access private + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, $close) + { + $begin = $this->tpl->config('taglib_begin'); + $end = $this->tpl->config('taglib_end'); + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + $tagName = is_array($tags) ? implode('|', $tags) : $tags; + if ($single) { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; + } + } else { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; + } + } + return '/' . $regex . '/is'; + } + + /** + * 分析标签属性 正则方式 + * @access public + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 + * @return array + */ + public function parseAttr($str, $name, $alias = '') + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $result = []; + if (preg_match_all($regex, $str, $matches)) { + foreach ($matches['name'] as $key => $val) { + $result[$val] = $matches['value'][$key]; + } + if (!isset($this->tags[$name])) { + // 检测是否存在别名定义 + foreach ($this->tags as $key => $val) { + if (isset($val['alias'])) { + $array = (array) $val['alias']; + if (in_array($name, explode(',', $array[0]))) { + $tag = $val; + $type = !empty($array[1]) ? $array[1] : 'type'; + $result[$type] = $name; + break; + } + } + } + } else { + $tag = $this->tags[$name]; + // 设置了标签别名 + if (!empty($alias) && isset($tag['alias'])) { + $type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type'; + $result[$type] = $alias; + } + } + if (!empty($tag['must'])) { + $must = explode(',', $tag['must']); + foreach ($must as $name) { + if (!isset($result[$name])) { + throw new Exception('tag attr must:' . $name); + } + } + } + } else { + // 允许直接使用表达式的标签 + if (!empty($this->tags[$name]['expression'])) { + static $_taglibs; + if (!isset($_taglibs[$name])) { + $_taglibs[$name][0] = strlen($this->tpl->config('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->config('taglib_end_origin')); + } + $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); + // 清除自闭合标签尾部/ + $result['expression'] = rtrim($result['expression'], '/'); + $result['expression'] = trim($result['expression']); + } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) { + throw new Exception('tag error:' . $name); + } + } + return $result; + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return string + */ + public function parseCondition($condition) + { + if (strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(&$name) + { + $flag = substr($name, 0, 1); + if (':' == $flag) { + // 以:开头为函数调用,解析前去掉: + $name = substr($name, 1); + } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { + // XXX: 这句的写法可能还需要改进 + // 常量不需要解析 + if (defined($name)) { + return $name; + } + // 不以$开头并且也不是常量,自动补上$前缀 + $name = '$' . $name; + } + $this->tpl->parseVar($name); + $this->tpl->parseVarFunction($name); + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + // 获取标签定义 + public function getTags() + { + return $this->tags; + } +} diff --git a/vendor/topthink/framework/library/think/template/driver/File.php b/vendor/topthink/framework/library/think/template/driver/File.php new file mode 100644 index 0000000..a9a86bf --- /dev/null +++ b/vendor/topthink/framework/library/think/template/driver/File.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use think\Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void|array + */ + public function write($cacheFile, $content) + { + // 检测模板目录 + $dir = dirname($cacheFile); + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + // 生成模板缓存文件 + if (false === file_put_contents($cacheFile, $content)) { + throw new Exception('cache write error:' . $cacheFile, 11602); + } + } + + /** + * 读取编译编译 + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read($cacheFile, $vars = []) + { + $this->cacheFile = $cacheFile; + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return boolean + */ + public function check($cacheFile, $cacheTime) + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + if (0 != $cacheTime && $_SERVER['REQUEST_TIME'] > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + return true; + } +} diff --git a/vendor/topthink/framework/library/think/template/taglib/Cx.php b/vendor/topthink/framework/library/think/template/taglib/Cx.php new file mode 100644 index 0000000..31e0698 --- /dev/null +++ b/vendor/topthink/framework/library/think/template/taglib/Cx.php @@ -0,0 +1,673 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\taglib; + +use think\template\TagLib; + +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +class Cx extends Taglib +{ + + // 标签定义 + protected $tags = [ + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => ['attr' => ''], + 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'], + 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true], + 'if' => ['attr' => 'condition', 'expression' => true], + 'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true], + 'else' => ['attr' => '', 'close' => 0], + 'switch' => ['attr' => 'name', 'expression' => true], + 'case' => ['attr' => 'value,break', 'expression' => true], + 'default' => ['attr' => '', 'close' => 0], + 'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']], + 'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']], + 'empty' => ['attr' => 'name'], + 'notempty' => ['attr' => 'name'], + 'present' => ['attr' => 'name'], + 'notpresent' => ['attr' => 'name'], + 'defined' => ['attr' => 'name'], + 'notdefined' => ['attr' => 'name'], + 'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']], + 'assign' => ['attr' => 'name,value', 'close' => 0], + 'define' => ['attr' => 'name,value', 'close' => 0], + 'for' => ['attr' => 'start,end,name,comparison,step'], + 'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true], + 'function' => ['attr' => 'name,vars,use,call'], + ]; + + /** + * php标签解析 + * 格式: + * {php}echo $name{/php} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPhp($tag, $content) + { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * {volist name="userList" id="user" empty=""} + * {user.username} + * {user.email} + * {/volist} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagVolist($tag, $content) + { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $key = !empty($tag['key']) ? $tag['key'] : 'i'; + $mod = isset($tag['mod']) ? $tag['mod'] : '2'; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + $parseStr .= '$_result=' . $name . ';'; + $name = '$_result'; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $parseStr .= ' $__LIST__ = ' . $name . ';'; + } + $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; + $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );'; + $parseStr .= '++$' . $key . ';?>'; + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * foreach标签解析 循环输出数据集 + * 格式: + * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""} + * {user.username} + * {/foreach} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagForeach($tag, $content) + { + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + $name = $tag['name']; + $key = !empty($tag['key']) ? $tag['key'] : 'key'; + $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + + $parseStr = 'autoBuildVar($name); + $parseStr .= $var . '=' . $name . '; '; + $name = $var; + } else { + $name = $this->autoBuildVar($name); + } + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + if (!isset($var)) { + $var = '$_' . uniqid(); + } + $parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $var = &$name; + } + + $parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + $parseStr .= '$' . $index . '=0; '; + } + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + if (isset($tag['mod'])) { + $mod = (int) $tag['mod']; + $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); '; + } + $parseStr .= '++$' . $index . '; '; + } + $parseStr .= '?>'; + // 循环体中的内容 + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * if标签解析 + * 格式: + * {if condition=" $a eq 1"} + * {elseif condition="$a eq 2" /} + * {else /} + * {/if} + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagIf($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * elseif标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagElseif($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = ''; + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagElse($tag) + { + $parseStr = ''; + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * {switch name="a.name"} + * {case value="1" break="false"}1{/case} + * {case value="2" }2{/case} + * {default /}other + * {/switch} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagSwitch($tag, $content) + { + $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCase($tag, $content) + { + $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $value = 'case ' . $value . ':'; + } elseif (strpos($value, '|')) { + $values = explode('|', $value); + $value = ''; + foreach ($values as $val) { + $value .= 'case "' . addslashes($val) . '":'; + } + } else { + $value = 'case "' . $value . '":'; + } + $parseStr = '' . $content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if ('' == $isBreak || $isBreak) { + $parseStr .= ''; + } + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: {default /}ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefault($tag) + { + $parseStr = ''; + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: {compare name="" type="eq" value="" }content{/compare} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCompare($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } else { + $value = '\'' . $value . '\''; + } + switch ($type) { + case 'equal': + $type = 'eq'; + break; + case 'notequal': + $type = 'neq'; + break; + } + $type = $this->parseCondition(' ' . $type . ' '); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} + * example: {range name="a" value="1,2,3" type='in' }content{/range} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagRange($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型 + + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; + } else { + $value = '"' . $value . '"'; + $str = 'explode(\',\',' . $value . ')'; + } + if ('between' == $type) { + $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; + } elseif ('notbetween' == $type) { + $parseStr = '$_RANGE_VAR_[1]):?>' . $content . ''; + } else { + $fun = ('in' == $type) ? 'in_array' : '!in_array'; + $parseStr = '' . $content . ''; + } + return $parseStr; + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: {present name="" }content{/present} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: {notpresent name="" }content{/notpresent} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotpresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: {empty name="" }content{/empty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagEmpty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty())): ?>' . $content . ''; + return $parseStr; + } + + /** + * notempty标签解析 + * 如果某个变量不为empty 则输出内容 + * 格式: {notempty name="" }content{/notempty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotempty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * load 标签解析 {load file="/static/js/base.js" /} + * 格式:{load file="/static/css/base.css" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagLoad($tag, $content) + { + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + $parseStr = ''; + $endStr = ''; + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $name = $tag['value']; + $name = $this->autoBuildVar($name); + $name = 'isset(' . $name . ')'; + $parseStr .= ''; + $endStr = ''; + } + + // 文件方式导入 + $array = explode(',', $file); + foreach ($array as $val) { + $type = strtolower(substr(strrchr($val, '.'), 1)); + switch ($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + return $parseStr . $endStr; + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: {assign name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagAssign($tag, $content) + { + $name = $this->autoBuildVar($tag['name']); + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: {define name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefine($tag, $content) + { + $name = '\'' . $tag['name'] . '\''; + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * {for start="" end="" comparison="" step="" name=""} + * content + * {/for} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFor($tag, $content) + { + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + //获取属性 + foreach ($tag as $key => $value) { + $value = trim($value); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } + + switch ($key) { + case 'start': + $start = $value; + break; + case 'end': + $end = $value; + break; + case 'step': + $step = $value; + break; + case 'comparison': + $comparison = $value; + break; + case 'name': + $name = $value; + break; + } + } + + $parseStr = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + + /** + * url函数的tag标签 + * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagUrl($tag, $content) + { + $url = isset($tag['link']) ? $tag['link'] : ''; + $vars = isset($tag['vars']) ? $tag['vars'] : ''; + $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; + $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + return ''; + } + + /** + * function标签解析 匿名函数,可实现递归 + * 使用: + * {function name="func" vars="$data" call="$list" use="&$a,&$b"} + * {if is_array($data)} + * {foreach $data as $val} + * {~func($val) /} + * {/foreach} + * {else /} + * {$data} + * {/if} + * {/function} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFunction($tag, $content) + { + $name = !empty($tag['name']) ? $tag['name'] : 'func'; + $vars = !empty($tag['vars']) ? $tag['vars'] : ''; + $call = !empty($tag['call']) ? $tag['call'] : ''; + $use = ['&$' . $name]; + if (!empty($tag['use'])) { + foreach (explode(',', $tag['use']) as $val) { + $use[] = '&' . ltrim(trim($val), '&'); + } + } + $parseStr = '' . $content . '' : '?>'; + return $parseStr; + } +} diff --git a/vendor/topthink/framework/library/think/view/driver/Php.php b/vendor/topthink/framework/library/think/view/driver/Php.php new file mode 100644 index 0000000..f594a43 --- /dev/null +++ b/vendor/topthink/framework/library/think/view/driver/Php.php @@ -0,0 +1,160 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; + +class Php +{ + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DS, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + protected $template; + protected $content; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch($template, $data = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + $this->template = $template; + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + + extract($data, EXTR_OVERWRITE); + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display($content, $data = []) + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return void + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return isset($this->config[$name]) ? $this->config[$name] : null; + } else { + $this->config[$name] = $value; + } + } + +} diff --git a/vendor/topthink/framework/library/think/view/driver/Think.php b/vendor/topthink/framework/library/think/view/driver/Think.php new file mode 100644 index 0000000..a314ad6 --- /dev/null +++ b/vendor/topthink/framework/library/think/view/driver/Think.php @@ -0,0 +1,167 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; +use think\Template; + +class Think +{ + // 模板引擎实例 + private $template; + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $this->template = new Template($this->config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $data = [], $config = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + $this->template->fetch($template, $data, $config); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($template, $data = [], $config = []) + { + $this->template->display($template, $data, $config); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + // 分析模板文件规则 + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置或者获取模板引擎参数 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->template->config($name); + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return $this->template->config($name); + } else { + $this->template->$name = $value; + $this->config[$name] = $value; + } + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } +} diff --git a/vendor/topthink/framework/library/traits/controller/Jump.php b/vendor/topthink/framework/library/traits/controller/Jump.php new file mode 100644 index 0000000..6a57224 --- /dev/null +++ b/vendor/topthink/framework/library/traits/controller/Jump.php @@ -0,0 +1,167 @@ +error(); + * $this->redirect(); + * } + * } + */ +namespace traits\controller; + +use think\Config; +use think\exception\HttpResponseException; +use think\Request; +use think\Response; +use think\response\Redirect; +use think\Url; +use think\View as ViewTemplate; + +trait Jump +{ + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url) && !is_null(Request::instance()->server('HTTP_REFERER'))) { + $url = Request::instance()->server('HTTP_REFERER'); + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_success_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url)) { + $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_error_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 返回封装后的 API 数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param int $code 返回的 code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'time' => Request::instance()->server('REQUEST_TIME'), + 'data' => $data, + ]; + $type = $type ?: $this->getResponseType(); + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * URL 重定向 + * @access protected + * @param string $url 跳转的 URL 表达式 + * @param array|int $params 其它 URL 参数 + * @param int $code http code + * @param array $with 隐式传参 + * @return void + * @throws HttpResponseException + */ + protected function redirect($url, $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + $response = new Redirect($url); + $response->code($code)->params($params)->with($with); + + throw new HttpResponseException($response); + } + + /** + * 获取当前的 response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + return Request::instance()->isAjax() + ? Config::get('default_ajax_return') + : Config::get('default_return_type'); + } +} diff --git a/vendor/topthink/framework/library/traits/model/SoftDelete.php b/vendor/topthink/framework/library/traits/model/SoftDelete.php new file mode 100644 index 0000000..70f31ba --- /dev/null +++ b/vendor/topthink/framework/library/traits/model/SoftDelete.php @@ -0,0 +1,200 @@ +getDeleteTimeField(); + + if ($field && !empty($this->data[$field])) { + return true; + } + return false; + } + + /** + * 查询包含软删除的数据 + * @access public + * @return Query + */ + public static function withTrashed() + { + return (new static )->getQuery(); + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed() + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model->getQuery()->useSoftDelete($field, ['not null', '']); + } else { + return $model->getQuery(); + } + } + + /** + * 删除当前的记录 + * @access public + * @param bool $force 是否强制删除 + * @return integer + */ + public function delete($force = false) + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $name = $this->getDeleteTimeField(); + if ($name && !$force) { + // 软删除 + $this->data[$name] = $this->autoWriteTimestamp($name); + $result = $this->isUpdate()->save(); + } else { + // 强制删除当前模型数据 + $result = $this->getQuery()->where($this->getWhere())->delete(); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name); + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection || is_array($result)) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } + + $this->trigger('after_delete', $this); + + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表(支持闭包查询条件) + * @param bool $force 是否强制删除 + * @return integer 成功删除的记录数 + */ + public static function destroy($data, $force = false) + { + if (is_null($data)) { + return 0; + } + + // 包含软删除数据 + $query = (new static())->db(false); + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } + + $count = 0; + if ($resultSet = $query->select($data)) { + foreach ($resultSet as $data) { + $result = $data->delete($force); + $count += $result; + } + } + + return $count; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return integer + */ + public function restore($where = []) + { + if (empty($where)) { + $pk = $this->getPk(); + $where[$pk] = $this->getData($pk); + } + + $name = $this->getDeleteTimeField(); + + if ($name) { + // 恢复删除 + return $this->getQuery() + ->useSoftDelete($name, ['not null', '']) + ->where($where) + ->update([$name => null]); + } else { + return 0; + } + } + + /** + * 查询默认不包含软删除数据 + * @access protected + * @param Query $query 查询对象 + * @return Query + */ + protected function base($query) + { + $field = $this->getDeleteTimeField(true); + return $field ? $query->useSoftDelete($field) : $query; + } + + /** + * 获取软删除字段 + * @access public + * @param bool $read 是否查询操作(写操作的时候会自动去掉表别名) + * @return string + */ + protected function getDeleteTimeField($read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? + $this->deleteTime : + 'delete_time'; + + if (false === $field) { + return false; + } + + if (!strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } +} diff --git a/vendor/topthink/framework/library/traits/think/Instance.php b/vendor/topthink/framework/library/traits/think/Instance.php new file mode 100644 index 0000000..428c8fd --- /dev/null +++ b/vendor/topthink/framework/library/traits/think/Instance.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- + +namespace traits\think; + +use think\Exception; + +trait Instance +{ + /** + * @var null|static 实例对象 + */ + protected static $instance = null; + + /** + * 获取示例 + * @param array $options 实例配置 + * @return static + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) self::$instance = new self($options); + + return self::$instance; + } + + /** + * 静态调用 + * @param string $method 调用方法 + * @param array $params 调用参数 + * @return mixed + * @throws Exception + */ + public static function __callStatic($method, array $params) + { + if (is_null(self::$instance)) self::$instance = new self(); + + $call = substr($method, 1); + + if (0 !== strpos($method, '_') || !is_callable([self::$instance, $call])) { + throw new Exception("method not exists:" . $method); + } + + return call_user_func_array([self::$instance, $call], $params); + } +} diff --git a/vendor/topthink/framework/logo.png b/vendor/topthink/framework/logo.png new file mode 100644 index 0000000..25fd059 Binary files /dev/null and b/vendor/topthink/framework/logo.png differ diff --git a/vendor/topthink/framework/phpunit.xml b/vendor/topthink/framework/phpunit.xml new file mode 100644 index 0000000..7c6ef03 --- /dev/null +++ b/vendor/topthink/framework/phpunit.xml @@ -0,0 +1,35 @@ + + + + + ./tests/thinkphp/ + + + + + + + + ./ + + tests + vendor + + + + + + + + + + diff --git a/vendor/topthink/framework/start.php b/vendor/topthink/framework/start.php new file mode 100644 index 0000000..adb1bc6 --- /dev/null +++ b/vendor/topthink/framework/start.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 1. 加载基础文件 +require __DIR__ . '/base.php'; + +// 2. 执行应用 +App::run()->send(); diff --git a/vendor/topthink/framework/tests/.gitignore b/vendor/topthink/framework/tests/.gitignore new file mode 100644 index 0000000..a0306ec --- /dev/null +++ b/vendor/topthink/framework/tests/.gitignore @@ -0,0 +1,4 @@ +/runtime/ +/application/common.php +/application/demo/ +/application/runtime/ \ No newline at end of file diff --git a/vendor/topthink/framework/tests/README.md b/vendor/topthink/framework/tests/README.md new file mode 100644 index 0000000..0c1d086 --- /dev/null +++ b/vendor/topthink/framework/tests/README.md @@ -0,0 +1,132 @@ +## 测试目录结构 + +测试文件主要在 tests 文件下面,主要有以下几个文件夹 + +- conf 测试环境配置文件。 +- script 测试环境配置脚本。 +- thinkphp 测试用例和相关文件,与项目文件夹结构一致。 +- mock.php 测试入口文件。 + +## 主要测试流程 + +thinkphp5 的测试的主要流程是跟 thinkphp 的系统流程是相似的,大体的流程为: + +1. 引用 mock.php 文件加载框架 + +2. 根据文件目录,添加测试文件 + +3. 执行单元测试,输出结果 + +## 测试举例 + +例如测试 thinkphp 里的 apc 缓存,将分为以下几个过程: + +1. 创建 apcTest.php 文件 + +该文件应与 apc.php 目录路径 `thinkphp/library/think/cache/driver` 一致,命名空间与目录所在一致,并引用 `PHPUnit_Framework_TestCase`。 + + ```php + markTestSkipped('apc扩展不可用!'); + }; + ``` + + - 编写测试用例 + + *具体写法参照 [PHPUnit 官方文档](https://phpunit.de/manual/4.8/zh_cn/index.html)* + + ```php + public function testGet() + { + App::run(); + $this->assertInstanceOf( + '\think\cache\driver\Apc', + Cache::connect(['type' => 'apc', 'expire' => 1]) + ); + $this->assertTrue(Cache::set('key', 'value')); + $this->assertEquals('value', Cache::get('key')); + $this->assertTrue(Cache::rm('key')); + $this->assertFalse(Cache::get('key')); + $this->assertTrue(Cache::clear('key')); + Config::reset(); + } + ``` + +3. 执行单元测试命令 + + 在项目根目录执行 + + ```bash + $ phpunit + ``` + + 若想看到所有结果,请添加-v参数 + + ```bash + $ phpunit -v + ``` + +4. 输出结果 + +## 相关文档 + +[各个部分单元测试说明](http://www.kancloud.cn/brother_simon/tp5_test/96971 "各部分单元测试说明") + +## 大家一起来 + +单元测试的内容会跟框架同步,测试内容方方面面,是一个相对复杂的模块,同时也是一个值得重视的部分。希望大家能够多多提出意见,多多参与。如果你有任何问题或想法,可以随时提 issue,我们期待着收到听大家的质疑和讨论。 + +## 任务进度 + +单元测试任务进度,请大家认领模块 + +|模块|认领人|进度| +|---|---|---| +|Base||| +|App|Haotong Lin|√| +|Build|刘志淳|| +|Config|Haotong Lin|√| +|Cache||| +|Controller|Haotong Lin|√| +|Cookie|Haotong Lin|√| +|Db||| +|Debug|大漠|√| +|Error|大漠|| +|Exception|Haotong Lin|√| +|Hook|流年|√| +|Input|Haotong Lin|√| +|Lang|流年|√| +|Loader|流年|| +|Log||| +|Model||| +|Response|大漠|√| +|Route|流年|| +|Session|大漠|√| +|Template|oldrind|| +|Url|流年|| +|View|mahuan|| diff --git a/vendor/topthink/framework/tests/application/config.php b/vendor/topthink/framework/tests/application/config.php new file mode 100644 index 0000000..d94a864 --- /dev/null +++ b/vendor/topthink/framework/tests/application/config.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +return [ + 'url_route_on' => true, + 'log' => [ + 'type' => 'file', // 支持 socket trace file + ], + 'view' => [ + // 模板引擎 + 'engine_type' => 'think', + // 模板引擎配置 + 'engine_config' => [ + // 模板路径 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => '.html', + // 模板文件名分隔符 + 'view_depr' => DS, + ], + // 输出字符串替换 + 'parse_str' => [], + ], +]; diff --git a/vendor/topthink/framework/tests/application/database.php b/vendor/topthink/framework/tests/application/database.php new file mode 100644 index 0000000..24434ef --- /dev/null +++ b/vendor/topthink/framework/tests/application/database.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +return [ + // 数据库类型 + 'type' => 'mysql', + // 数据库连接DSN配置 + 'dsn' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '', + // 数据库连接端口 + 'hostport' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', +]; diff --git a/vendor/topthink/framework/tests/application/index/controller/Index.php b/vendor/topthink/framework/tests/application/index/controller/Index.php new file mode 100644 index 0000000..803fe95 --- /dev/null +++ b/vendor/topthink/framework/tests/application/index/controller/Index.php @@ -0,0 +1,10 @@ +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

      :)

      ThinkPHP V5
      十年磨一剑 - 为API开发设计的高性能框架

      [ V5.0 版本由 七牛云 独家赞助发布 ]
      '; + } +} diff --git a/vendor/topthink/framework/tests/application/route.php b/vendor/topthink/framework/tests/application/route.php new file mode 100644 index 0000000..45bcf79 --- /dev/null +++ b/vendor/topthink/framework/tests/application/route.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +return [ + '__pattern__' => [ + 'name' => '\w+', + ], + '[hello]' => [ + ':id' => ['index/hello', ['method' => 'get'], ['id' => '\d+']], + ':name' => ['index/hello', ['method' => 'post']], + ], + +]; diff --git a/vendor/topthink/framework/tests/application/views/display.html b/vendor/topthink/framework/tests/application/views/display.html new file mode 100644 index 0000000..e99d302 --- /dev/null +++ b/vendor/topthink/framework/tests/application/views/display.html @@ -0,0 +1 @@ +{$name??'default'} \ No newline at end of file diff --git a/vendor/topthink/framework/tests/application/views/display.phtml b/vendor/topthink/framework/tests/application/views/display.phtml new file mode 100644 index 0000000..e99d302 --- /dev/null +++ b/vendor/topthink/framework/tests/application/views/display.phtml @@ -0,0 +1 @@ +{$name??'default'} \ No newline at end of file diff --git a/vendor/topthink/framework/tests/application/views/extend.html b/vendor/topthink/framework/tests/application/views/extend.html new file mode 100644 index 0000000..922192d --- /dev/null +++ b/vendor/topthink/framework/tests/application/views/extend.html @@ -0,0 +1,2 @@ +{extend name="extend2" /} +{block name="head"}header{/block} \ No newline at end of file diff --git a/vendor/topthink/framework/tests/application/views/extend2.html b/vendor/topthink/framework/tests/application/views/extend2.html new file mode 100644 index 0000000..eb22e1d --- /dev/null +++ b/vendor/topthink/framework/tests/application/views/extend2.html @@ -0,0 +1,17 @@ +{layout name="layout2" replace="[__REPLACE__]" /} +{block name="head"}{/block} +
      + {include file="include" name="info" value="$info.value" /} +{block name="main"} +{block name="side"} + side +{/block} +{block name="mainbody"} + +{/block} +{/block} +{literal} + {$name} +{/literal} + +
      \ No newline at end of file diff --git a/vendor/topthink/framework/tests/application/views/include.html b/vendor/topthink/framework/tests/application/views/include.html new file mode 100644 index 0000000..e01ae9e --- /dev/null +++ b/vendor/topthink/framework/tests/application/views/include.html @@ -0,0 +1,2 @@ + +{include file="include2" /} \ No newline at end of file diff --git a/vendor/topthink/framework/tests/application/views/include2.html b/vendor/topthink/framework/tests/application/views/include2.html new file mode 100644 index 0000000..24bdacb --- /dev/null +++ b/vendor/topthink/framework/tests/application/views/include2.html @@ -0,0 +1 @@ +{$info.value}: \ No newline at end of file diff --git a/vendor/topthink/framework/tests/application/views/layout.html b/vendor/topthink/framework/tests/application/views/layout.html new file mode 100644 index 0000000..be8d4a0 --- /dev/null +++ b/vendor/topthink/framework/tests/application/views/layout.html @@ -0,0 +1,2 @@ +
      {__CONTENT__} +
      \ No newline at end of file diff --git a/vendor/topthink/framework/tests/application/views/layout2.html b/vendor/topthink/framework/tests/application/views/layout2.html new file mode 100644 index 0000000..a5b7572 --- /dev/null +++ b/vendor/topthink/framework/tests/application/views/layout2.html @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/vendor/topthink/framework/tests/conf/memcached.ini b/vendor/topthink/framework/tests/conf/memcached.ini new file mode 100644 index 0000000..7d09664 --- /dev/null +++ b/vendor/topthink/framework/tests/conf/memcached.ini @@ -0,0 +1 @@ +extension=memcached.so diff --git a/vendor/topthink/framework/tests/conf/redis.ini b/vendor/topthink/framework/tests/conf/redis.ini new file mode 100644 index 0000000..6aecae4 --- /dev/null +++ b/vendor/topthink/framework/tests/conf/redis.ini @@ -0,0 +1 @@ +extension=redis.so diff --git a/vendor/topthink/framework/tests/conf/timezone.ini b/vendor/topthink/framework/tests/conf/timezone.ini new file mode 100644 index 0000000..ced3e0d --- /dev/null +++ b/vendor/topthink/framework/tests/conf/timezone.ini @@ -0,0 +1 @@ +date.timezone = UTC \ No newline at end of file diff --git a/vendor/topthink/framework/tests/mock.php b/vendor/topthink/framework/tests/mock.php new file mode 100644 index 0000000..0c29e59 --- /dev/null +++ b/vendor/topthink/framework/tests/mock.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- + +// 测试入口文件 +$_SERVER['REQUEST_METHOD'] = 'GET'; +// 定义项目测试基础路径 +define('TEST_PATH', __DIR__ . '/'); +// 定义项目路径 +define('APP_PATH', __DIR__ . '/application/'); +// 加载框架基础文件 +require __DIR__ . '/../base.php'; +\think\Loader::addNamespace('tests', TEST_PATH); diff --git a/vendor/topthink/framework/tests/script/install.sh b/vendor/topthink/framework/tests/script/install.sh new file mode 100644 index 0000000..8344169 --- /dev/null +++ b/vendor/topthink/framework/tests/script/install.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ $(phpenv version-name) != "hhvm" ]; then + cp tests/extensions/$(phpenv version-name)/*.so $(php-config --extension-dir) + + phpenv config-add tests/conf/memcached.ini + phpenv config-add tests/conf/redis.ini + + phpenv config-add tests/conf/timezone.ini +fi + +composer install --no-interaction --ignore-platform-reqs +composer update diff --git a/vendor/topthink/framework/tests/thinkphp/baseTest.php b/vendor/topthink/framework/tests/thinkphp/baseTest.php new file mode 100644 index 0000000..18f1d3d --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/baseTest.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +/** + * 保证运行环境正常 + */ +class baseTest extends \PHPUnit_Framework_TestCase +{ + public function testConstants() + { + $this->assertNotEmpty(THINK_START_TIME); + $this->assertNotEmpty(THINK_START_MEM); + $this->assertNotEmpty(THINK_VERSION); + $this->assertNotEmpty(DS); + $this->assertNotEmpty(THINK_PATH); + $this->assertNotEmpty(LIB_PATH); + $this->assertNotEmpty(EXTEND_PATH); + $this->assertNotEmpty(CORE_PATH); + $this->assertNotEmpty(TRAIT_PATH); + $this->assertNotEmpty(APP_PATH); + $this->assertNotEmpty(RUNTIME_PATH); + $this->assertNotEmpty(LOG_PATH); + $this->assertNotEmpty(CACHE_PATH); + $this->assertNotEmpty(TEMP_PATH); + $this->assertNotEmpty(VENDOR_PATH); + $this->assertNotEmpty(EXT); + $this->assertNotEmpty(ENV_PREFIX); + $this->assertTrue(!is_null(IS_WIN)); + $this->assertTrue(!is_null(IS_CLI)); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/appTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/appTest.php new file mode 100644 index 0000000..016bbf9 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/appTest.php @@ -0,0 +1,90 @@ + +// +---------------------------------------------------------------------- + +/** + * app类测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use think\App; +use think\Config; +use think\Request; + +function func_trim($value) +{ + return trim($value); +} + +function func_strpos($haystack, $needle) +{ + return strpos($haystack, $needle); +} + +class AppInvokeMethodTestClass +{ + public static function staticRun($string) + { + return $string; + } + + public function run($string) + { + return $string; + } +} + +class appTest extends \PHPUnit_Framework_TestCase +{ + public function testRun() + { + $response = App::run(Request::create("http://www.example.com")); + + $expectOutputString = '

      :)

      ThinkPHP V5
      十年磨一剑 - 为API开发设计的高性能框架

      [ V5.0 版本由 七牛云 独家赞助发布 ]
      '; + + $this->assertEquals($expectOutputString, $response->getContent()); + $this->assertEquals(200, $response->getCode()); + + $this->assertEquals(true, function_exists('lang')); + $this->assertEquals(true, function_exists('config')); + $this->assertEquals(true, function_exists('input')); + + $this->assertEquals(Config::get('default_timezone'), date_default_timezone_get()); + + } + + // function调度 + public function testInvokeFunction() + { + $args1 = ['a b c ']; + $this->assertEquals( + trim($args1[0]), + App::invokeFunction('tests\thinkphp\library\think\func_trim', $args1) + ); + + $args2 = ['abcdefg', 'g']; + $this->assertEquals( + strpos($args2[0], $args2[1]), + App::invokeFunction('tests\thinkphp\library\think\func_strpos', $args2) + ); + } + + // 类method调度 + public function testInvokeMethod() + { + $result = App::invokeMethod(['tests\thinkphp\library\think\AppInvokeMethodTestClass', 'run'], ['thinkphp']); + $this->assertEquals('thinkphp', $result); + + $result = App::invokeMethod('tests\thinkphp\library\think\AppInvokeMethodTestClass::staticRun', ['thinkphp']); + $this->assertEquals('thinkphp', $result); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/behavior/One.php b/vendor/topthink/framework/tests/thinkphp/library/think/behavior/One.php new file mode 100644 index 0000000..1ec6e66 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/behavior/One.php @@ -0,0 +1,15 @@ + +// +---------------------------------------------------------------------- + +/** + * build测试 + * @author 刘志淳 + */ + +namespace tests\thinkphp\library\think; + +use think\Build; + +class buildTest extends \PHPUnit_Framework_TestCase +{ + public function testRun() + { + $build = [ + // Test run directory + '__dir__' => ['runtime/cache', 'runtime/log', 'runtime/temp', 'runtime/template'], + '__file__' => ['common.php'], + + // Test generation module + 'demo' => [ + '__file__' => ['common.php'], + '__dir__' => ['behavior', 'controller', 'model', 'view', 'service'], + 'controller' => ['Index', 'Test', 'UserType'], + 'model' => ['User', 'UserType'], + 'service' => ['User', 'UserType'], + 'view' => ['index/index'], + ], + ]; + Build::run($build); + + $this->buildFileExists($build); + } + + protected function buildFileExists($build) + { + foreach ($build as $module => $list) { + if ('__dir__' == $module || '__file__' == $module) { + foreach ($list as $file) { + $this->assertFileExists(APP_PATH . $file); + } + } else { + foreach ($list as $path => $moduleList) { + if ('__file__' == $path || '__dir__' == $path) { + foreach ($moduleList as $file) { + $this->assertFileExists(APP_PATH . $module . '/' . $file); + } + } else { + foreach ($moduleList as $file) { + if ('view' == $path) { + $file_name = APP_PATH . $module . '/' . $path . '/' . $file . '.html'; + } else { + $file_name = APP_PATH . $module . '/' . $path . '/' . $file . EXT; + } + $this->assertFileExists($file_name); + } + } + } + $this->assertFileExists(APP_PATH . ($module ? $module . DS : '') . 'config.php'); + } + } + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/cacheTestCase.php b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/cacheTestCase.php new file mode 100644 index 0000000..5b2f2a3 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/cacheTestCase.php @@ -0,0 +1,207 @@ + +// +---------------------------------------------------------------------- + +/** + * 缓存抽象类,提供一些测试 + * @author simon + */ + +namespace tests\thinkphp\library\think\cache\driver; + +use think\Cache; + +abstract class cacheTestCase extends \PHPUnit_Framework_TestCase +{ + + /** + * 获取缓存句柄,子类必须有 + * @access protected + */ + abstract protected function getCacheInstance(); + + /** + * tearDown函数 + */ + protected function tearDown() + { + } + + /** + * 设定一组测试值,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function prepare() + { + $cache = $this->getCacheInstance(); + $cache->clear(); + $cache->set('string_test', 'string_test'); + $cache->set('number_test', 11); + $cache->set('array_test', ['array_test' => 'array_test']); + return $cache; + } + + /** + * 测试缓存设置,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testSet() + { + $cache = $this->getCacheInstance(); + $this->assertTrue($cache->set('string_test', 'string_test')); + $this->assertTrue($cache->set('number_test', 11)); + $this->assertTrue($cache->set('array_test', ['array_test' => 'array_test'])); + } + + /** + * 测试缓存自增 + * @return mixed + * @access public + */ + public function testInc() + { + $cache = $this->getCacheInstance(); + $this->assertEquals(14, $cache->inc('number_test', 3)); + } + + /** + * 测试缓存自减 + * @return mixed + * @access public + */ + public function testDec() + { + $cache = $this->getCacheInstance(); + $this->assertEquals(8, $cache->dec('number_test', 6)); + } + + /** + * 测试缓存读取,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testGet() + { + $cache = $this->prepare(); + $this->assertEquals('string_test', $cache->get('string_test')); + $this->assertEquals(11, $cache->get('number_test')); + $array = $cache->get('array_test'); + $this->assertArrayHasKey('array_test', $array); + $this->assertEquals('array_test', $array['array_test']); + + $result = $cache->set('no_expire', 1, 0); + $this->assertTrue($result); + } + + /** + * 测试缓存存在情况,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testExists() + { + $cache = $this->prepare(); + $this->assertNotEmpty($cache->has('string_test')); + $this->assertNotEmpty($cache->has('number_test')); + $this->assertFalse($cache->has('not_exists')); + } + + /** + * 测试缓存不存在情况,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testGetNonExistent() + { + $cache = $this->getCacheInstance(); + $this->assertFalse($cache->get('non_existent_key', false)); + } + + /** + * 测试特殊值缓存,包括测试字符串、整数、数组和对象 + * @return mixed + * @access public + */ + public function testStoreSpecialValues() + { + $cache = $this->getCacheInstance(); + $cache->set('null_value', null); + //清空缓存后,返回null而不是false + $this->assertTrue(is_null($cache->get('null_value'))); + } + + /** + * 缓存过期测试 + * @return mixed + * @access public + */ + public function testExpire() + { + $cache = $this->getCacheInstance(); + $this->assertTrue($cache->set('expire_test', 'expire_test', 1)); + usleep(600000); + $this->assertEquals('expire_test', $cache->get('expire_test')); + usleep(800000); + $this->assertFalse($cache->get('expire_test')); + } + + /** + * 删除缓存测试 + * @return mixed + * @access public + */ + public function testDelete() + { + $cache = $this->prepare(); + $this->assertNotNull($cache->rm('number_test')); + $this->assertFalse($cache->get('number_test')); + } + + /** + * 获取并删除缓存测试 + * @return mixed + * @access public + */ + public function testPull() + { + $cache = $this->prepare(); + $this->assertEquals(11, $cache->pull('number_test')); + $this->assertFalse($cache->get('number_test')); + } + + /** + * 清空缓存测试 + * @return mixed + * @access public + */ + public function testClear() + { + $cache = $this->prepare(); + $this->assertTrue($cache->clear()); + $this->assertFalse($cache->get('number_test')); + } + + public function testStaticCall() + { + $this->assertTrue(Cache::set('a', 1)); + $this->assertEquals(1, Cache::get('a')); + $this->assertEquals(2, Cache::inc('a')); + $this->assertEquals(4, Cache::inc('a', 2)); + $this->assertEquals(4, Cache::get('a')); + $this->assertEquals(3, Cache::dec('a')); + $this->assertEquals(1, Cache::dec('a', 2)); + $this->assertEquals(1, Cache::get('a')); + $this->assertNotNull(Cache::rm('a')); + $this->assertNotNull(Cache::clear()); + } + +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/fileTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/fileTest.php new file mode 100644 index 0000000..eb2099c --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/fileTest.php @@ -0,0 +1,46 @@ + +// +---------------------------------------------------------------------- + +/** + * File缓存驱动测试 + * @author 刘志淳 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +class fileTest extends cacheTestCase +{ + private $_cacheInstance = null; + + /** + * 基境缓存类型 + */ + protected function setUp() + { + \think\Cache::connect(['type' => 'File', 'path' => CACHE_PATH]); + } + + /** + * @return FileCache + */ + protected function getCacheInstance() + { + if (null === $this->_cacheInstance) { + $this->_cacheInstance = new \think\cache\driver\File(); + } + return $this->_cacheInstance; + } + + // skip testExpire + public function testExpire() + { + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/liteTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/liteTest.php new file mode 100644 index 0000000..19cbb9e --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/liteTest.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- + +/** + * Lite缓存驱动测试 + * @author 刘志淳 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +use think\Cache; + +class liteTest extends \PHPUnit_Framework_TestCase +{ + protected function getCacheInstance() + { + return Cache::connect(['type' => 'Lite', 'path' => CACHE_PATH]); + } + + /** + * 测试缓存读取 + * @return mixed + * @access public + */ + public function testGet() + { + $cache = $this->getCacheInstance(); + $this->assertFalse($cache->get('test')); + } + + /** + * 测试缓存设置 + * @return mixed + * @access public + */ + public function testSet() + { + $cache = $this->getCacheInstance(); + $this->assertNotEmpty($cache->set('test', 'test')); + } + + /** + * 删除缓存测试 + * @return mixed + * @access public + */ + public function testRm() + { + $cache = $this->getCacheInstance(); + $this->assertTrue($cache->rm('test')); + } + + /** + * 清空缓存测试 + * @return mixed + * @access public + */ + public function testClear() + { + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/memcacheTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/memcacheTest.php new file mode 100644 index 0000000..8975fa2 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/memcacheTest.php @@ -0,0 +1,49 @@ + +// +---------------------------------------------------------------------- + +/** + * Memcache缓存驱动测试 + * @author 刘志淳 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +class memcacheTest extends cacheTestCase +{ + private $_cacheInstance = null; + + /** + * 基境缓存类型 + */ + protected function setUp() + { + if (!extension_loaded('memcache')) { + $this->markTestSkipped("Memcache没有安装,已跳过测试!"); + } + \think\Cache::connect(['type' => 'memcache', 'expire' => 2]); + } + + /** + * @return ApcCache + */ + protected function getCacheInstance() + { + if (null === $this->_cacheInstance) { + $this->_cacheInstance = new \think\cache\driver\Memcache(['length' => 3]); + } + return $this->_cacheInstance; + } + + // skip testExpire + public function testExpire() + { + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/memcachedTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/memcachedTest.php new file mode 100644 index 0000000..d528dd2 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/memcachedTest.php @@ -0,0 +1,72 @@ + +// +---------------------------------------------------------------------- + +/** + * Memcached缓存驱动测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +class memcachedTest extends cacheTestCase +{ + private $_cacheInstance = null; + /** + * 基境缓存类型 + */ + protected function setUp() + { + if (!extension_loaded("memcached") && !extension_loaded('memcache')) { + $this->markTestSkipped("Memcached或Memcache没有安装,已跳过测试!"); + } + \think\Cache::connect(array('type' => 'memcached', 'expire' => 2)); + } + /** + * @return ApcCache + */ + protected function getCacheInstance() + { + if (null === $this->_cacheInstance) { + $this->_cacheInstance = new \think\cache\driver\Memcached(['length' => 3]); + } + return $this->_cacheInstance; + } + /** + * 缓存过期测试《提出来测试,因为目前看通不过缓存过期测试,所以还需研究》 + * @return mixed + * @access public + */ + public function testExpire() + { + } + + public function testStaticCall() + { + } + + /** + * 测试缓存自增 + * @return mixed + * @access public + */ + public function testInc() + { + } + + /** + * 测试缓存自减 + * @return mixed + * @access public + */ + public function testDec() + { + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/redisTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/redisTest.php new file mode 100644 index 0000000..2000ee4 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/cache/driver/redisTest.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- + +/** + * Redis缓存驱动测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\cache\driver; + +class redisTest extends cacheTestCase +{ + private $_cacheInstance = null; + + protected function setUp() + { + if (!extension_loaded("redis")) { + $this->markTestSkipped("Redis没有安装,已跳过测试!"); + } + \think\Cache::connect(array('type' => 'redis', 'expire' => 2)); + } + + protected function getCacheInstance() + { + if (null === $this->_cacheInstance) { + $this->_cacheInstance = new \think\cache\driver\Redis(['length' => 3]); + } + return $this->_cacheInstance; + } + + public function testGet() + { + $cache = $this->prepare(); + $this->assertEquals('string_test', $cache->get('string_test')); + $this->assertEquals(11, $cache->get('number_test')); + $result = $cache->get('array_test'); + $this->assertEquals('array_test', $result['array_test']); + } + + public function testStoreSpecialValues() + { + $redis = new \think\cache\driver\Redis(['length' => 3]); + $redis->set('key', 'value'); + $redis->get('key'); + + $redis->handler()->setnx('key', 'value'); + $value = $redis->handler()->get('key'); + $this->assertEquals('value', $value); + + $redis->handler()->hset('hash', 'key', 'value'); + $value = $redis->handler()->hget('hash', 'key'); + $this->assertEquals('value', $value); + } + + public function testExpire() + { + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/cacheTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/cacheTest.php new file mode 100644 index 0000000..dbcd705 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/cacheTest.php @@ -0,0 +1,315 @@ + +// +---------------------------------------------------------------------- + +namespace tests\thinkphp\library\think; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\Cache; +use think\Config; + +class cacheTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait { + ConfigInitTrait::tearDown as ConfigTearDown; + } + + public function tearDown() + { + $this->ConfigTearDown(); + + call_user_func(\Closure::bind(function () { + Cache::$handler = null; + Cache::$instance = []; + Cache::$readTimes = 0; + Cache::$writeTimes = 0; + }, null, '\think\Cache')); + } + + /** + * @dataProvider provideTestConnect + */ + public function testConnect($options, $expected) + { + $connection = Cache::connect($options); + $this->assertInstanceOf($expected, $connection); + $this->assertSame($connection, Cache::connect($options)); + + $instance = $this->getPropertyVal('instance'); + $this->assertArrayHasKey(md5(serialize($options)), $instance); + + $newConnection = Cache::connect($options, true); + $newInstance = $this->getPropertyVal('instance'); + $this->assertInstanceOf($expected, $connection); + $this->assertNotSame($connection, $newConnection); + $this->assertEquals($instance, $newInstance); + } + + /** + * @dataProvider provideTestInit + */ + public function testInit($options, $expected) + { + $connection = Cache::init($options); + $this->assertInstanceOf($expected, $connection); + + $connectionNew = Cache::init(['type' => 'foo']); + $this->assertSame($connection, $connectionNew); + } + + public function testStore() + { + Config::set('cache.redis', ['type' => 'redis']); + + $connectionDefault = Cache::store(); + $this->assertInstanceOf('\think\cache\driver\File', $connectionDefault); + + Config::set('cache.type', false); + $connectionNotRedis = Cache::store('redis'); + $this->assertSame($connectionDefault, $connectionNotRedis); + + Config::set('cache.type', 'complex'); + $connectionRedis = Cache::store('redis'); + $this->assertNotSame($connectionNotRedis, $connectionRedis); + $this->assertInstanceOf('\think\cache\driver\Redis', $connectionRedis); + + // 即便成功切换到其他存储类型,也不影响原先的操作句柄 + $this->assertSame($connectionDefault, Cache::store()); + } + + public function testHas() + { + $key = $this->buildTestKey('Has'); + + $this->assertFalse(Cache::has($key)); + + Cache::set($key, 5); + $this->assertTrue(Cache::has($key)); + } + + public function testGet() + { + $key = $this->buildTestKey('Get'); + + $this->assertFalse(Cache::get($key)); + + $this->assertEquals('default', Cache::get($key, 'default')); + + Cache::set($key, 5); + $this->assertSame(5, Cache::get($key)); + } + + public function testSet() + { + $key = $this->buildTestKey('Set'); + + $this->assertTrue(Cache::set(null, null)); + $this->assertTrue(Cache::set($key, 'ThinkPHP3.2')); + $this->assertTrue(Cache::set($key, 'ThinkPHP5.0', null)); + $this->assertTrue(Cache::set($key, 'ThinkPHP5.0', -1)); + $this->assertTrue(Cache::set($key, 'ThinkPHP5.0', 0)); + $this->assertTrue(Cache::set($key, 'ThinkPHP5.0', 7200)); + } + + public function testInc() + { + $key = $this->buildTestKey('Inc'); + + Cache::inc($key); + $this->assertEquals(1, Cache::get($key)); + + Cache::inc($key, '2'); + $this->assertEquals(3, Cache::get($key)); + + Cache::inc($key, -1); + $this->assertEquals(2, Cache::get($key)); + + Cache::inc($key, null); + $this->assertEquals(2, Cache::get($key)); + + Cache::inc($key, true); + $this->assertEquals(3, Cache::get($key)); + + Cache::inc($key, false); + $this->assertEquals(3, Cache::get($key)); + + Cache::inc($key, 0.789); + $this->assertEquals(3.789, Cache::get($key)); + } + public function testDec() + { + $key = $this->buildTestKey('Dec'); + + Cache::dec($key); + $this->assertEquals(-1, Cache::get($key)); + + Cache::dec($key, '2'); + $this->assertEquals(-3, Cache::get($key)); + + Cache::dec($key, -1); + $this->assertEquals(-2, Cache::get($key)); + + Cache::dec($key, null); + $this->assertEquals(-2, Cache::get($key)); + + Cache::dec($key, true); + $this->assertEquals(-3, Cache::get($key)); + + Cache::dec($key, false); + $this->assertEquals(-3, Cache::get($key)); + + Cache::dec($key, 0.359); + $this->assertEquals(-3.359, Cache::get($key)); + } + + public function testRm() + { + $key = $this->buildTestKey('Rm'); + + $this->assertFalse(Cache::rm($key)); + + Cache::set($key, 'ThinkPHP'); + $this->assertTrue(Cache::rm($key)); + } + + public function testClear() + { + $key1 = $this->buildTestKey('Clear1'); + $key2 = $this->buildTestKey('Clear2'); + + Cache::set($key1, 'ThinkPHP3.2'); + Cache::set($key2, 'ThinkPHP5.0'); + + $this->assertEquals('ThinkPHP3.2', Cache::get($key1)); + $this->assertEquals('ThinkPHP5.0', Cache::get($key2)); + Cache::clear(); + $this->assertFalse(Cache::get($key1)); + $this->assertFalse(Cache::get($key2)); + } + + public function testPull() + { + $key = $this->buildTestKey('Pull'); + + $this->assertNull(Cache::pull($key)); + + Cache::set($key, 'ThinkPHP'); + $this->assertEquals('ThinkPHP', Cache::pull($key)); + $this->assertFalse(Cache::get($key)); + } + + public function testRemember() + { + $key1 = $this->buildTestKey('Remember1'); + $key2 = $this->buildTestKey('Remember2'); + + $this->assertEquals('ThinkPHP3.2', Cache::remember($key1, 'ThinkPHP3.2')); + $this->assertEquals('ThinkPHP3.2', Cache::remember($key1, 'ThinkPHP5.0')); + + $this->assertEquals('ThinkPHP5.0', Cache::remember($key2, function () { + return 'ThinkPHP5.0'; + })); + $this->assertEquals('ThinkPHP5.0', Cache::remember($key2, function () { + return 'ThinkPHP3.2'; + })); + } + + public function testTag() + { + $key = $this->buildTestKey('Tag'); + + $cacheTagWithoutName = Cache::tag(null); + $this->assertInstanceOf('think\cache\Driver', $cacheTagWithoutName); + + $cacheTagWithKey = Cache::tag($key, [1, 2, 3]); + $this->assertSame($cacheTagWithoutName, $cacheTagWithKey); + } + + protected function getPropertyVal($name) + { + static $reflectionClass; + if (empty($reflectionClass)) { + $reflectionClass = new \ReflectionClass('\think\Cache'); + } + + $property = $reflectionClass->getProperty($name); + $property->setAccessible(true); + + return $property->getValue(); + } + + public function provideTestConnect() + { + $provideData = []; + + $options = ['type' => null]; + $expected = '\think\cache\driver\File'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'File']; + $expected = '\think\cache\driver\File'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'Lite']; + $expected = '\think\cache\driver\Lite'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'Memcached']; + $expected = '\think\cache\driver\Memcached'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'Redis']; + $expected = '\think\cache\driver\Redis'; + $provideData[] = [$options, $expected]; + + // TODO + // $options = ['type' => 'Memcache']; + // $expected = '\think\cache\driver\Memcache'; + // $provideData[] = [$options, $expected]; + // + // $options = ['type' => 'Wincache']; + // $expected = '\think\cache\driver\Wincache'; + // $provideData[] = [$options, $expected]; + + // $options = ['type' => 'Sqlite']; + // $expected = '\think\cache\driver\Sqlite'; + // $provideData[] = [$options, $expected]; + // + // $options = ['type' => 'Xcache']; + // $expected = '\think\cache\driver\Xcache'; + // $provideData[] = [$options, $expected]; + + return $provideData; + } + + public function provideTestInit() + { + $provideData = []; + + $options = []; + $expected = '\think\cache\driver\File'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'File']; + $expected = '\think\cache\driver\File'; + $provideData[] = [$options, $expected]; + + $options = ['type' => 'Lite']; + $expected = '\think\cache\driver\Lite'; + $provideData[] = [$options, $expected]; + + return $provideData; + } + + protected function buildTestKey($tag) + { + return sprintf('ThinkPHP_Test_%s_%d_%d', $tag, time(), rand(0, 10000)); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/config/ConfigInitTrait.php b/vendor/topthink/framework/tests/thinkphp/library/think/config/ConfigInitTrait.php new file mode 100644 index 0000000..ce2b397 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/config/ConfigInitTrait.php @@ -0,0 +1,52 @@ + + + + 1 + + \ No newline at end of file diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/iniTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/iniTest.php new file mode 100644 index 0000000..66b38d9 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/iniTest.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +/** + * Ini配置测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\config\driver; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\config; + +class iniTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function testParse() + { + Config::parse('inistring=1', 'ini'); + $this->assertEquals(1, Config::get('inistring')); + Config::reset(); + Config::parse(__DIR__ . '/fixtures/config.ini'); + $this->assertTrue(Config::has('inifile')); + $this->assertEquals(1, Config::get('inifile')); + Config::reset(); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/jsonTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/jsonTest.php new file mode 100644 index 0000000..583e6dd --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/jsonTest.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +/** + * Xml配置测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\config\driver; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\config; + +class jsonTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function testParse() + { + Config::parse('{"jsonstring":1}', 'json'); + $this->assertEquals(1, Config::get('jsonstring')); + Config::reset(); + Config::parse(__DIR__ . '/fixtures/config.json'); + $this->assertTrue(Config::has('jsonstring')); + $this->assertEquals(1, Config::get('jsonstring')); + Config::reset(); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/xmlTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/xmlTest.php new file mode 100644 index 0000000..e2225f7 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/config/driver/xmlTest.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +/** + * Xml配置测试 + * @author 7IN0SAN9 + */ + +namespace tests\thinkphp\library\think\config\driver; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\config; + +class xmlTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function testParse() + { + Config::parse('1', 'xml'); + $this->assertEquals(1, Config::get('xmlstring')); + Config::reset(); + Config::parse(__DIR__ . '/fixtures/config.xml'); + $this->assertTrue(Config::has('xmlfile.istrue')); + $this->assertEquals(1, Config::get('xmlfile.istrue')); + Config::reset(); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/configTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/configTest.php new file mode 100644 index 0000000..4c92df5 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/configTest.php @@ -0,0 +1,149 @@ + +// +---------------------------------------------------------------------- + +/** + * 配置测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\Config; + +class configTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function testRange() + { + // test default range + $this->assertEquals('_sys_', call_user_func(self::$internalRangeFoo)); + + $this->assertTrue(is_array(call_user_func(self::$internalConfigFoo))); + // test range initialization + Config::range('_test_'); + $this->assertEquals('_test_', call_user_func(self::$internalRangeFoo)); + $this->assertEquals([], call_user_func(self::$internalConfigFoo)['_test_']); + } + + // public function testParse() + // { + // see \think\config\driver\...Test.php + // } + + public function testLoad() + { + $file = APP_PATH . 'config' . EXT; + $config = array_change_key_case(include $file); + $name = '_name_'; + $range = '_test_'; + + $this->assertEquals($config, Config::load($file, $name, $range)); + $this->assertNotEquals(null, Config::load($file, $name, $range)); + } + + public function testHas() + { + $range = '_test_'; + $this->assertFalse(Config::has('abcd', $range)); + + call_user_func(self::$internalConfigFoo, [ + $range => ['abcd' => 'value'], + ]); + $this->assertTrue(Config::has('abcd', $range)); + + // else ... + $this->assertFalse(Config::has('abcd.efg', $range)); + + call_user_func(self::$internalConfigFoo, [ + $range => ['abcd' => ['efg' => 'value']], + ]); + $this->assertTrue(Config::has('abcd.efg', $range)); + } + + public function testGet() + { + $range = '_test_'; + call_user_func(self::$internalConfigFoo, [ + $range => [] + ]); + $this->assertEquals([], Config::get(null, $range)); + $this->assertEquals(null, Config::get(null, 'does_not_exist')); + $value = 'value'; + // test getting configuration + call_user_func(self::$internalConfigFoo, [ + $range => ['abcd' => 'efg'] + ]); + $this->assertEquals('efg', Config::get('abcd', $range)); + $this->assertEquals(null, Config::get('does_not_exist', $range)); + $this->assertEquals(null, Config::get('abcd', 'does_not_exist')); + // test getting configuration with dot syntax + call_user_func(self::$internalConfigFoo, [ + $range => ['one' => ['two' => $value]] + ]); + $this->assertEquals($value, Config::get('one.two', $range)); + $this->assertEquals(null, Config::get('one.does_not_exist', $range)); + $this->assertEquals(null, Config::get('one.two', 'does_not_exist')); + } + + public function testSet() + { + $range = '_test_'; + + // without dot syntax + $name = 'name'; + $value = 'value'; + Config::set($name, $value, $range); + $config = call_user_func(self::$internalConfigFoo); + $this->assertEquals($value, $config[$range][$name]); + // with dot syntax + $name = 'one.two'; + $value = 'dot value'; + Config::set($name, $value, $range); + $config = call_user_func(self::$internalConfigFoo); + $this->assertEquals($value, $config[$range]['one']['two']); + // if (is_array($name)): + // see testLoad() + // ... + // test getting all configurations...? + // return self::$config[$range]; ?? + $value = ['all' => 'configuration']; + call_user_func(self::$internalConfigFoo, [$range => $value]); + $this->assertEquals($value, Config::set(null, null, $range)); + $this->assertNotEquals(null, Config::set(null, null, $range)); + } + + public function testReset() + { + $range = '_test_'; + call_user_func(self::$internalConfigFoo, [$range => ['abcd' => 'efg']]); + + // clear all configurations + Config::reset(true); + $config = call_user_func(self::$internalConfigFoo); + $this->assertEquals([], $config); + // clear the configuration in range of parameter. + call_user_func(self::$internalConfigFoo, [ + $range => [ + 'abcd' => 'efg', + 'hijk' => 'lmn', + ], + 'a' => 'b', + ]); + Config::reset($range); + $config = call_user_func(self::$internalConfigFoo); + $this->assertEquals([ + $range => [], + 'a' => 'b', + ], $config); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/controller/.gitignore b/vendor/topthink/framework/tests/thinkphp/library/think/controller/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/controller/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/controllerTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/controllerTest.php new file mode 100644 index 0000000..4164770 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/controllerTest.php @@ -0,0 +1,194 @@ + +// +---------------------------------------------------------------------- + +/** + * 控制器测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use ReflectionClass; +use think\Controller; +use think\Request; +use think\View; + +require_once CORE_PATH . '../../helper.php'; + +class Foo extends Controller +{ + public $test = 'test'; + + public function _initialize() + { + $this->test = 'abcd'; + } + + public function assignTest() + { + $this->assign('abcd', 'dcba'); + $this->assign(['key1' => 'value1', 'key2' => 'value2']); + } + + public function fetchTest() + { + $template = APP_PATH . 'views' . DS .'display.html'; + return $this->fetch($template, ['name' => 'ThinkPHP']); + } + + public function displayTest() + { + $template = APP_PATH . 'views' . DS .'display.html'; + return $this->display($template, ['name' => 'ThinkPHP']); + } + public function test() + { + $data = [ + 'username' => 'username', + 'nickname' => 'nickname', + 'password' => '123456', + 'repassword' => '123456', + 'email' => 'abc@abc.com', + 'sex' => '0', + 'age' => '20', + 'code' => '1234', + ]; + + $validate = [ + ['username', 'length:5,15', '用户名长度为5到15个字符'], + ['nickname', 'require', '请填昵称'], + ['password', '[\w-]{6,15}', '密码长度为6到15个字符'], + ['repassword', 'confirm:password', '两次密码不一到致'], + ['email', 'filter:validate_email', '邮箱格式错误'], + ['sex', 'in:0,1', '性别只能为为男或女'], + ['age', 'between:1,80', '年龄只能在10-80之间'], + ]; + return $this->validate($data, $validate); + } +} + +class Bar extends Controller +{ + public $test = 1; + + public $beforeActionList = ['action1', 'action2']; + + public function action1() + { + $this->test += 2; + return 'action1'; + } + + public function action2() + { + $this->test += 4; + return 'action2'; + } +} + +class Baz extends Controller +{ + public $test = 1; + + public $beforeActionList = [ + 'action1' => ['only' => 'index'], + 'action2' => ['except' => 'index'], + 'action3' => ['only' => 'abcd'], + 'action4' => ['except' => 'abcd'], + ]; + + public function action1() + { + $this->test += 2; + return 'action1'; + } + + public function action2() + { + $this->test += 4; + return 'action2'; + } + + public function action3() + { + $this->test += 8; + return 'action2'; + } + + public function action4() + { + $this->test += 16; + return 'action2'; + } +} + +class controllerTest extends \PHPUnit_Framework_TestCase +{ + public function testInitialize() + { + $foo = new Foo(Request::instance()); + $this->assertEquals('abcd', $foo->test); + } + + public function testBeforeAction() + { + $obj = new Bar(Request::instance()); + $this->assertEquals(7, $obj->test); + + $obj = new Baz(Request::instance()); + $this->assertEquals(19, $obj->test); + } + + private function getView($controller) + { + $view = new View(); + $rc = new ReflectionClass(get_class($controller)); + $property = $rc->getProperty('view'); + $property->setAccessible(true); + $property->setValue($controller, $view); + return $view; + } + + public function testFetch() + { + $controller = new Foo(Request::instance()); + $view = $this->getView($controller); + $template = APP_PATH . 'views' . DS .'display.html'; + $viewFetch = $view->fetch($template, ['name' => 'ThinkPHP']); + $this->assertEquals($controller->fetchTest(), $viewFetch); + } + + public function testDisplay() + { + $controller = new Foo; + $view = $this->getView($controller); + $template = APP_PATH . 'views' . DS .'display.html'; + $viewFetch = $view->display($template, ['name' => 'ThinkPHP']); + + $this->assertEquals($controller->displayTest(), $viewFetch); + } + + public function testAssign() + { + $controller = new Foo(Request::instance()); + $view = $this->getView($controller); + $controller->assignTest(); + $expect = ['abcd' => 'dcba', 'key1' => 'value1', 'key2' => 'value2']; + $this->assertAttributeEquals($expect, 'data', $view); + } + + public function testValidate() + { + $controller = new Foo(Request::instance()); + $result = $controller->test(); + $this->assertTrue($result); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/cookieTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/cookieTest.php new file mode 100644 index 0000000..24cb153 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/cookieTest.php @@ -0,0 +1,150 @@ + +// +---------------------------------------------------------------------- + +/** + * Cookie测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use ReflectionClass; +use think\Cookie; + +class cookieTest extends \PHPUnit_Framework_TestCase +{ + protected $ref; + + protected $default = [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => false, + ]; + + protected function setUp() + { + $reflectedClass = new ReflectionClass('\think\Cookie'); + $reflectedPropertyConfig = $reflectedClass->getProperty('config'); + $reflectedPropertyConfig->setAccessible(true); + $reflectedPropertyConfig->setValue($this->default); + $this->ref = $reflectedPropertyConfig; + } + + public function testInit() + { + $config = [ + // cookie 名称前缀 + 'prefix' => 'think_', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/path/to/test/', + // cookie 有效域名 + 'domain' => '.thinkphp.cn', + // cookie 启用安全传输 + 'secure' => true, + // httponly设置 + 'httponly' => '1', + ]; + Cookie::init($config); + + $this->assertEquals( + array_merge($this->default, array_change_key_case($config)), + $this->ref->getValue() + ); + } + + public function testPrefix() + { + $this->assertEquals($this->default['prefix'], Cookie::prefix()); + + $prefix = '_test_'; + $this->assertNotEquals($prefix, Cookie::prefix()); + Cookie::prefix($prefix); + + $config = $this->ref->getValue(); + $this->assertEquals($prefix, $config['prefix']); + } + + public function testSet() + { + $value = 'value'; + + $name = 'name1'; + Cookie::set($name, $value, 10); + $this->assertEquals($value, $_COOKIE[$this->default['prefix'] . $name]); + + $name = 'name2'; + Cookie::set($name, $value, null); + $this->assertEquals($value, $_COOKIE[$this->default['prefix'] . $name]); + + $name = 'name3'; + Cookie::set($name, $value, 'expire=100&prefix=pre_'); + $this->assertEquals($value, $_COOKIE['pre_' . $name]); + + $name = 'name4'; + $value = ['_test_中文_']; + Cookie::set($name, $value); + $this->assertEquals('think:' . json_encode([urlencode('_test_中文_')]), $_COOKIE[$name]); + } + + public function testGet() + { + $_COOKIE = [ + 'a' => 'b', + 'pre_abc' => 'c', + 'd' => 'think:' . json_encode([urlencode('_test_中文_')]), + ]; + $this->assertEquals('b', Cookie::get('a')); + $this->assertEquals(null, Cookie::get('does_not_exist')); + $this->assertEquals('c', Cookie::get('abc', 'pre_')); + $this->assertEquals(['_test_中文_'], Cookie::get('d')); + } + + public function testDelete() + { + $_COOKIE = [ + 'a' => 'b', + 'pre_abc' => 'c', + ]; + $this->assertEquals('b', Cookie::get('a')); + Cookie::delete('a'); + $this->assertEquals(null, Cookie::get('a')); + + $this->assertEquals('c', Cookie::get('abc', 'pre_')); + Cookie::delete('abc', 'pre_'); + $this->assertEquals(null, Cookie::get('abc', 'pre_')); + } + + public function testClear() + { + $_COOKIE = []; + $this->assertEquals(null, Cookie::clear()); + + $_COOKIE = [ + 'a' => 'b', + 'pre_abc' => 'c', + ]; + Cookie::clear('pre_'); + $this->assertEquals(['a' => 'b'], $_COOKIE); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/db/driver/.gitignore b/vendor/topthink/framework/tests/thinkphp/library/think/db/driver/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/db/driver/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/dbTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/dbTest.php new file mode 100644 index 0000000..5724ee2 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/dbTest.php @@ -0,0 +1,352 @@ + +// +---------------------------------------------------------------------- + +/** + * Db类测试 + * @author: 刘志淳 + */ + +namespace tests\thinkphp\library\think; + +use think\Db; + +class dbTest extends \PHPUnit_Framework_TestCase +{ + // 获取测试数据库配置 + private function getConfig() + { + return [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'test', + // 用户名 + 'username' => 'root', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => 'tp_', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 array 数组 collection Collection对象 + 'resultset_type' => 'array', + // 是否自动写入时间戳字段 + 'auto_timestamp' => false, + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ]; + } + + // 获取创建数据库 SQL + private function getCreateTableSql() + { + $sql[] = <<getConfig(); + $result = Db::connect($config)->execute('show databases'); + $this->assertNotEmpty($result); + } + + public function testExecute() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + foreach ($sql as $one) { + Db::connect($config)->execute($one); + } + $tableNum = Db::connect($config)->execute("show tables;"); + $this->assertEquals(4, $tableNum); + } + + public function testQuery() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + Db::connect($config)->batchQuery($sql); + + $tableQueryResult = Db::connect($config)->query("show tables;"); + + $this->assertTrue(is_array($tableQueryResult)); + + $tableNum = count($tableQueryResult); + $this->assertEquals(4, $tableNum); + } + + public function testBatchQuery() + { + $config = $this->getConfig(); + $sql = $this->getCreateTableSql(); + Db::connect($config)->batchQuery($sql); + + $tableNum = Db::connect($config)->execute("show tables;"); + $this->assertEquals(4, $tableNum); + } + + public function testTable() + { + $config = $this->getConfig(); + $tableName = 'tp_user'; + $result = Db::connect($config)->table($tableName); + $this->assertEquals($tableName, $result->getOptions()['table']); + } + + public function testName() + { + $config = $this->getConfig(); + $tableName = 'user'; + $result = Db::connect($config)->name($tableName); + $this->assertEquals($config['prefix'] . $tableName, $result->getTable()); + } + + public function testInsert() + { + $config = $this->getConfig(); + $data = [ + 'username' => 'chunice', + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time(), + ]; + $result = Db::connect($config)->name('user')->insert($data); + $this->assertEquals(1, $result); + } + + public function testUpdate() + { + $config = $this->getConfig(); + $data = [ + 'username' => 'chunice_update', + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time(), + ]; + $result = Db::connect($config)->name('user')->where('username', 'chunice')->update($data); + $this->assertEquals(1, $result); + } + + public function testFind() + { + $config = $this->getConfig(); + $mustFind = Db::connect($config)->name('user')->where('username', 'chunice_update')->find(); + $this->assertNotEmpty($mustFind); + $mustNotFind = Db::connect($config)->name('user')->where('username', 'chunice')->find(); + $this->assertEmpty($mustNotFind); + } + + public function testInsertAll() + { + $config = $this->getConfig(); + + $data = [ + ['username' => 'foo', 'password' => md5('foo'), 'status' => 1, 'create_time' => time()], + ['username' => 'bar', 'password' => md5('bar'), 'status' => 1, 'create_time' => time()], + ]; + + $insertNum = Db::connect($config)->name('user')->insertAll($data); + $this->assertEquals(count($data), $insertNum); + } + + public function testSelect() + { + $config = $this->getConfig(); + $mustFound = Db::connect($config)->name('user')->where('status', 1)->select(); + $this->assertNotEmpty($mustFound); + $mustNotFound = Db::connect($config)->name('user')->where('status', 0)->select(); + $this->assertEmpty($mustNotFound); + } + + public function testValue() + { + $config = $this->getConfig(); + $username = Db::connect($config)->name('user')->where('id', 1)->value('username'); + $this->assertEquals('chunice_update', $username); + $usernameNull = Db::connect($config)->name('user')->where('id', 0)->value('username'); + $this->assertEmpty($usernameNull); + } + + public function testColumn() + { + $config = $this->getConfig(); + $username = Db::connect($config)->name('user')->where('status', 1)->column('username'); + $this->assertNotEmpty($username); + $usernameNull = Db::connect($config)->name('user')->where('status', 0)->column('username'); + $this->assertEmpty($usernameNull); + + } + + public function testInsertGetId() + { + $config = $this->getConfig(); + $id = Db::connect($config)->name('user')->order('id', 'desc')->value('id'); + + $data = [ + 'username' => uniqid(), + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time(), + ]; + $lastId = Db::connect($config)->name('user')->insertGetId($data); + $this->assertEquals($id + 1, $lastId); + + } + + public function testGetLastInsId() + { + $config = $this->getConfig(); + $data = [ + 'username' => uniqid(), + 'password' => md5('chunice'), + 'status' => 1, + 'create_time' => time(), + ]; + $lastId = Db::connect($config)->name('user')->insertGetId($data); + + $lastInsId = Db::connect($config)->name('user')->getLastInsID(); + $this->assertEquals($lastId, $lastInsId); + } + + public function testSetField() + { + $config = $this->getConfig(); + + $setFieldNum = Db::connect($config)->name('user')->where('id', 1)->setField('username', 'chunice_setField'); + $this->assertEquals(1, $setFieldNum); + + $setFieldNum = Db::connect($config)->name('user')->where('id', 1)->setField('username', 'chunice_setField'); + $this->assertEquals(0, $setFieldNum); + } + + public function testSetInc() + { + $config = $this->getConfig(); + $originCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + Db::connect($config)->name('user')->where('id', 1)->setInc('create_time'); + $newCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + $this->assertEquals($originCreateTime + 1, $newCreateTime); + + } + + public function testSetDec() + { + $config = $this->getConfig(); + $originCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + Db::connect($config)->name('user')->where('id', 1)->setDec('create_time'); + $newCreateTime = Db::connect($config)->name('user')->where('id', 1)->value('create_time'); + $this->assertEquals($originCreateTime - 1, $newCreateTime); + } + + public function testDelete() + { + $config = $this->getConfig(); + Db::connect($config)->name('user')->where('id', 1)->delete(); + $result = Db::connect($config)->name('user')->where('id', 1)->find(); + $this->assertEmpty($result); + } + + public function testChunk() + { + // todo 暂未想到测试方法 + } + + public function testCache() + { + $config = $this->getConfig(); + $result = Db::connect($config)->name('user')->where('id', 1)->cache('key', 60)->find(); + $cache = \think\Cache::get('key'); + $this->assertEquals($result, $cache); + + $updateCache = Db::connect($config)->name('user')->cache('key')->find(1); + $this->assertEquals($cache, $updateCache); + } + +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/debugTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/debugTest.php new file mode 100644 index 0000000..36e1f04 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/debugTest.php @@ -0,0 +1,220 @@ + +// +---------------------------------------------------------------------- + +/** + * Debug测试 + * @author 大漠 + */ + +namespace tests\thinkphp\library\think; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\Config; +use think\Debug; +use think\Response; +use think\response\Redirect; + +class debugTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + /** + * + * @var Debug + */ + protected $object; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->object = new Debug(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + {} + + /** + * @covers \think\Debug::remark + * @todo Implement testRemark(). + */ + public function testRemark() + { + $name = "testremarkkey"; + Debug::remark($name); + } + + /** + * @covers \think\Debug::getRangeTime + * @todo Implement testGetRangeTime(). + */ + public function testGetRangeTime() + { + $start = "testGetRangeTimeStart"; + $end = "testGetRangeTimeEnd"; + Debug::remark($start); + usleep(20000); + // \think\Debug::remark($end); + + $time = Debug::getRangeTime($start, $end); + $this->assertLessThan(0.03, $time); + //$this->assertEquals(0.03, ceil($time)); + } + + /** + * @covers \think\Debug::getUseTime + * @todo Implement testGetUseTime(). + */ + public function testGetUseTime() + { + $time = Debug::getUseTime(); + $this->assertLessThan(30, $time); + } + + /** + * @covers \think\Debug::getThroughputRate + * @todo Implement testGetThroughputRate(). + */ + public function testGetThroughputRate() + { + usleep(100000); + $throughputRate = Debug::getThroughputRate(); + $this->assertLessThan(10, $throughputRate); + } + + /** + * @covers \think\Debug::getRangeMem + * @todo Implement testGetRangeMem(). + */ + public function testGetRangeMem() + { + $start = "testGetRangeMemStart"; + $end = "testGetRangeMemEnd"; + Debug::remark($start); + $str = ""; + for ($i = 0; $i < 10000; $i++) { + $str .= "mem"; + } + + $rangeMem = Debug::getRangeMem($start, $end); + + $this->assertLessThan(33, explode(" ", $rangeMem)[0]); + } + + /** + * @covers \think\Debug::getUseMem + * @todo Implement testGetUseMem(). + */ + public function testGetUseMem() + { + $useMem = Debug::getUseMem(); + + $this->assertLessThan(35, explode(" ", $useMem)[0]); + } + + /** + * @covers \think\Debug::getMemPeak + * @todo Implement testGetMemPeak(). + */ + public function testGetMemPeak() + { + $start = "testGetMemPeakStart"; + $end = "testGetMemPeakEnd"; + Debug::remark($start); + $str = ""; + for ($i = 0; $i < 100000; $i++) { + $str .= "mem"; + } + $memPeak = Debug::getMemPeak($start, $end); + $this->assertLessThan(500, explode(" ", $memPeak)[0]); + } + + /** + * @covers \think\Debug::getFile + * @todo Implement testGetFile(). + */ + public function testGetFile() + { + $count = Debug::getFile(); + + $this->assertEquals(count(get_included_files()), $count); + + $info = Debug::getFile(true); + $this->assertEquals(count(get_included_files()), count($info)); + + $this->assertContains("KB", $info[0]); + } + + /** + * @covers \think\Debug::dump + * @todo Implement testDump(). + */ + public function testDump() + { + if (strstr(PHP_VERSION, 'hhvm')) { + return; + } + + $var = []; + $var["key"] = "val"; + $output = Debug::dump($var, false, $label = "label"); + $array = explode("array", json_encode($output)); + if (IS_WIN) { + $this->assertEquals("(1) {\\n [\\\"key\\\"] => string(3) \\\"val\\\"\\n}\\n\\r\\n\"", end($array)); + } elseif (strstr(PHP_OS, 'Darwin')) { + $this->assertEquals("(1) {\\n [\\\"key\\\"] => string(3) \\\"val\\\"\\n}\\n\\n\"", end($array)); + } else { + $this->assertEquals("(1) {\\n 'key' =>\\n string(3) \\\"val\\\"\\n}\\n\\n\"", end($array)); + } + } + + /** + * @expectedException \think\exception\ClassNotFoundException + */ + public function testInjectWithErrorType() + { + Config::set('trace', ['type' => 'NullDebug']); + + $response = new Response(); + $context = 'TestWithErrorType'; + + Debug::inject($response, $context); + } + + public function testInject() + { + Config::set('trace', ['type' => 'Console']); + + $response = new Response(); + $context = 'TestWithoutBodyTag'; + Debug::inject($response, $context); + $this->assertNotEquals('TestWithoutBodyTag', $context); + $this->assertStringStartsWith('TestWithoutBodyTag', $context); + + $response = new Response(); + $context = ''; + Debug::inject($response, $context); + $this->assertNotEquals('', $context); + $this->assertStringStartsWith('', $context); + $this->assertStringEndsWith('', $context); + + $response = new Redirect(); + $context = ''; + Debug::inject($response, $context); + $this->assertEquals('', $context); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/exceptionTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/exceptionTest.php new file mode 100644 index 0000000..66957ce --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/exceptionTest.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- + +/** + * exception类测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think; + +use ReflectionMethod; +use think\Exception as ThinkException; +use think\exception\HttpException; + +class MyException extends ThinkException +{ + +} + +class exceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetHttpStatus() + { + try { + throw new HttpException(404, "Error Processing Request"); + } catch (HttpException $e) { + $this->assertEquals(404, $e->getStatusCode()); + } + } + + public function testDebugData() + { + $data = ['a' => 'b', 'c' => 'd']; + try { + $e = new MyException("Error Processing Request", 1); + $method = new ReflectionMethod($e, 'setData'); + $method->setAccessible(true); + $method->invokeArgs($e, ['test', $data]); + throw $e; + } catch (MyException $e) { + $this->assertEquals(['test' => $data], $e->getData()); + } + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/hookTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/hookTest.php new file mode 100644 index 0000000..930ecf5 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/hookTest.php @@ -0,0 +1,67 @@ + +// +---------------------------------------------------------------------- + +/** + * Hook类测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use think\Hook; + +class hookTest extends \PHPUnit_Framework_TestCase +{ + + public function testRun() + { + Hook::add('my_pos', '\tests\thinkphp\library\think\behavior\One'); + Hook::add('my_pos', ['\tests\thinkphp\library\think\behavior\Two']); + Hook::add('my_pos', '\tests\thinkphp\library\think\behavior\Three', true); + $data['id'] = 0; + $data['name'] = 'thinkphp'; + Hook::listen('my_pos', $data); + $this->assertEquals(2, $data['id']); + $this->assertEquals('thinkphp', $data['name']); + $this->assertEquals([ + '\tests\thinkphp\library\think\behavior\Three', + '\tests\thinkphp\library\think\behavior\One', + '\tests\thinkphp\library\think\behavior\Two'], + Hook::get('my_pos')); + } + + public function testImport() + { + Hook::import(['my_pos' => [ + '\tests\thinkphp\library\think\behavior\One', + '\tests\thinkphp\library\think\behavior\Three'], + ]); + Hook::import(['my_pos' => ['\tests\thinkphp\library\think\behavior\Two']], false); + Hook::import(['my_pos' => ['\tests\thinkphp\library\think\behavior\Three', '_overlay' => true]]); + $data['id'] = 0; + $data['name'] = 'thinkphp'; + Hook::listen('my_pos', $data); + $this->assertEquals(3, $data['id']); + + } + + public function testExec() + { + $data['id'] = 0; + $data['name'] = 'thinkphp'; + $this->assertEquals(true, Hook::exec('\tests\thinkphp\library\think\behavior\One')); + $this->assertEquals(false, Hook::exec('\tests\thinkphp\library\think\behavior\One', 'test', $data)); + $this->assertEquals('test', $data['name']); + $this->assertEquals('Closure', Hook::exec(function (&$data) {$data['name'] = 'Closure';return 'Closure';})); + + } + +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/lang/lang.php b/vendor/topthink/framework/tests/thinkphp/library/think/lang/lang.php new file mode 100644 index 0000000..96880b1 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/lang/lang.php @@ -0,0 +1,4 @@ +'加载', +]; diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/langTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/langTest.php new file mode 100644 index 0000000..360ee43 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/langTest.php @@ -0,0 +1,76 @@ + +// +---------------------------------------------------------------------- + +/** + * Lang测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use think\Config; +use think\Lang; + +class langTest extends \PHPUnit_Framework_TestCase +{ + + public function testSetAndGet() + { + Lang::set('hello,%s', '欢迎,%s'); + $this->assertEquals('欢迎,ThinkPHP', Lang::get('hello,%s', ['ThinkPHP'])); + Lang::set('hello,%s', '歡迎,%s', 'zh-tw'); + $this->assertEquals('歡迎,ThinkPHP', Lang::get('hello,%s', ['ThinkPHP'], 'zh-tw')); + Lang::set(['hello' => '欢迎', 'use' => '使用']); + $this->assertEquals('欢迎', Lang::get('hello')); + $this->assertEquals('欢迎', Lang::get('HELLO')); + $this->assertEquals('使用', Lang::get('use')); + + Lang::set('hello,{:name}', '欢迎,{:name}'); + $this->assertEquals('欢迎,liu21st', Lang::get('hello,{:name}', ['name' => 'liu21st'])); + } + + public function testLoad() + { + Lang::load(__DIR__ . DS . 'lang' . DS . 'lang.php'); + $this->assertEquals('加载', Lang::get('load')); + Lang::load(__DIR__ . DS . 'lang' . DS . 'lang.php', 'test'); + $this->assertEquals('加载', Lang::get('load', [], 'test')); + } + + public function testDetect() + { + + Config::set('lang_list', ['zh-cn', 'zh-tw']); + Lang::set('hello', '欢迎', 'zh-cn'); + Lang::set('hello', '歡迎', 'zh-tw'); + + Config::set('lang_detect_var', 'lang'); + Config::set('lang_cookie_var', 'think_cookie'); + + $_GET['lang'] = 'zh-tw'; + Lang::detect(); + $this->assertEquals('歡迎', Lang::get('hello')); + + $_GET['lang'] = 'zh-cn'; + Lang::detect(); + $this->assertEquals('欢迎', Lang::get('hello')); + + } + + public function testRange() + { + $this->assertEquals('zh-cn', Lang::range()); + Lang::set('hello', '欢迎', 'test'); + Lang::range('test'); + $this->assertEquals('test', Lang::range()); + $this->assertEquals('欢迎', Lang::get('hello')); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/loader/test/Hello.php b/vendor/topthink/framework/tests/thinkphp/library/think/loader/test/Hello.php new file mode 100644 index 0000000..c141456 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/loader/test/Hello.php @@ -0,0 +1,7 @@ + +// +---------------------------------------------------------------------- + +/** + * Loader测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use think\Loader; + +class loaderTest extends \PHPUnit_Framework_TestCase +{ + + public function testAutoload() + { + $this->assertEquals(false, Loader::autoload('\think\Url')); + $this->assertEquals(false, Loader::autoload('think\Test')); + $this->assertEquals(false, Loader::autoload('my\HelloTest')); + } + + public function testAddClassMap() + { + Loader::addClassMap('my\hello\Test', __DIR__ . DS . 'loader' . DS . 'Test.php'); + } + + public function testAddNamespace() + { + Loader::addNamespace('top', __DIR__ . DS . 'loader' . DS); + $this->assertEquals(true, Loader::autoload('top\test\Hello')); + } + + public function testAddNamespaceAlias() + { + Loader::addNamespaceAlias('top', 'top\test'); + Loader::addNamespaceAlias(['top' => 'top\test', 'app' => 'app\index']); + //$this->assertEquals(true, Loader::autoload('top\Hello')); + } + + public function testTable() + { + Loader::db('mysql://root@127.0.0.1/test#utf8'); + } + + public function testImport() + { + $this->assertEquals(false, Loader::import('think.log.driver.MyTest')); + } + + public function testParseName() + { + $this->assertEquals('HelloTest', Loader::parseName('hello_test', 1)); + $this->assertEquals('hello_test', Loader::parseName('HelloTest', 0)); + } + + public function testParseClass() + { + $this->assertEquals('app\index\controller\User', Loader::parseClass('index', 'controller', 'user')); + $this->assertEquals('app\index\controller\user\Type', Loader::parseClass('index', 'controller', 'user.type')); + $this->assertEquals('app\admin\model\UserType', Loader::parseClass('admin', 'model', 'user_type')); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/log/driver/fileTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/log/driver/fileTest.php new file mode 100644 index 0000000..ad6dd22 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/log/driver/fileTest.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- + +/** + * Test File Log + */ +namespace tests\thinkphp\library\think\log\driver; + +use think\Log; + +class fileTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Log::init(['type' => 'file']); + } + + public function testRecord() + { + $record_msg = 'record'; + Log::record($record_msg, 'notice'); + $logs = Log::getLog(); + + $this->assertEquals([], $logs); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/logTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/logTest.php new file mode 100644 index 0000000..e786b17 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/logTest.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +/** + * Log测试 + * @author liu21st + */ +namespace tests\thinkphp\library\think; + +use think\Log; + +class logTest extends \PHPUnit_Framework_TestCase +{ + + public function testSave() + { + Log::init(['type' => 'test']); + Log::clear(); + Log::record('test'); + Log::record([1, 2, 3]); + $this->assertTrue(Log::save()); + } + + public function testWrite() + { + Log::init(['type' => 'test']); + Log::clear(); + $this->assertTrue(Log::write('hello', 'info')); + $this->assertTrue(Log::write([1, 2, 3], 'log')); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/model/.gitignore b/vendor/topthink/framework/tests/thinkphp/library/think/model/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/model/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/paginateTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/paginateTest.php new file mode 100644 index 0000000..8cd4550 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/paginateTest.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace tests\thinkphp\library\think; + +use think\paginator\driver\Bootstrap; + +class paginateTest extends \PHPUnit_Framework_TestCase +{ + public function testPaginatorInfo() + { + $p = Bootstrap::make($array = ['item3', 'item4'], 2, 2, 4); + + $this->assertEquals(4, $p->total()); + + $this->assertEquals(2, $p->listRows()); + + $this->assertEquals(2, $p->currentPage()); + + $p2 = Bootstrap::make($array2 = ['item3', 'item4'], 2, 2, 2); + $this->assertEquals(1, $p2->currentPage()); + } + + public function testPaginatorRender() + { + $p = Bootstrap::make($array = ['item3', 'item4'], 2, 2, 100); + $render = ''; + + $this->assertEquals($render, $p->render()); + } + +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/requestTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/requestTest.php new file mode 100644 index 0000000..3da94a2 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/requestTest.php @@ -0,0 +1,203 @@ + +// +---------------------------------------------------------------------- + +/** + * Db类测试 + */ + +namespace tests\thinkphp\library\think; + +use think\Config; +use think\Request; + +class requestTest extends \PHPUnit_Framework_TestCase +{ + protected $request; + + public function setUp() + { + //$request = Request::create('http://www.domain.com/index/index/hello/?name=thinkphp'); + + } + + public function testCreate() + { + $request = Request::create('http://www.thinkphp.cn/index/index/hello.html?name=thinkphp'); + $this->assertEquals('http://www.thinkphp.cn', $request->domain()); + $this->assertEquals('/index/index/hello.html?name=thinkphp', $request->url()); + $this->assertEquals('/index/index/hello.html', $request->baseurl()); + $this->assertEquals('index/index/hello.html', $request->pathinfo()); + $this->assertEquals('index/index/hello', $request->path()); + $this->assertEquals('html', $request->ext()); + $this->assertEquals('name=thinkphp', $request->query()); + $this->assertEquals('www.thinkphp.cn', $request->host()); + $this->assertEquals(80, $request->port()); + $this->assertEquals($_SERVER['REQUEST_TIME'], $request->time()); + $this->assertEquals($_SERVER['REQUEST_TIME_FLOAT'], $request->time(true)); + $this->assertEquals('GET', $request->method()); + $this->assertEquals(['name' => 'thinkphp'], $request->param()); + $this->assertFalse($request->isSsl()); + $this->assertEquals('http', $request->scheme()); + } + + public function testDomain() + { + $request = Request::instance(); + $request->domain('http://thinkphp.cn'); + $this->assertEquals('http://thinkphp.cn', $request->domain()); + } + + public function testUrl() + { + $request = Request::instance(); + $request->url('/index.php/index/hello?name=thinkphp'); + $this->assertEquals('/index.php/index/hello?name=thinkphp', $request->url()); + $this->assertEquals('http://thinkphp.cn/index.php/index/hello?name=thinkphp', $request->url(true)); + } + + public function testBaseUrl() + { + $request = Request::instance(); + $request->baseurl('/index.php/index/hello'); + $this->assertEquals('/index.php/index/hello', $request->baseurl()); + $this->assertEquals('http://thinkphp.cn/index.php/index/hello', $request->baseurl(true)); + } + + public function testbaseFile() + { + $request = Request::instance(); + $request->basefile('/index.php'); + $this->assertEquals('/index.php', $request->basefile()); + $this->assertEquals('http://thinkphp.cn/index.php', $request->basefile(true)); + } + + public function testroot() + { + $request = Request::instance(); + $request->root('/index.php'); + $this->assertEquals('/index.php', $request->root()); + $this->assertEquals('http://thinkphp.cn/index.php', $request->root(true)); + } + + public function testType() + { + $request = Request::instance(); + $request->server(['HTTP_ACCEPT' => 'application/json']); + + $this->assertEquals('json', $request->type()); + $request->mimeType('test', 'application/test'); + $request->mimeType(['test' => 'application/test']); + $request->server(['HTTP_ACCEPT' => 'application/test']); + + $this->assertEquals('test', $request->type()); + } + + public function testmethod() + { + $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] = 'DELETE'; + + $request = Request::create('', ''); + $this->assertEquals('DELETE', $request->method()); + $this->assertEquals('GET', $request->method(true)); + + Config::set('var_method', '_method'); + $_POST['_method'] = 'POST'; + $request = Request::create('', ''); + $this->assertEquals('POST', $request->method()); + $this->assertEquals('GET', $request->method(true)); + $this->assertTrue($request->isPost()); + $this->assertFalse($request->isGet()); + $this->assertFalse($request->isPut()); + $this->assertFalse($request->isDelete()); + $this->assertFalse($request->isHead()); + $this->assertFalse($request->isPatch()); + $this->assertFalse($request->isOptions()); + } + + public function testCli() + { + $request = Request::instance(); + $this->assertTrue($request->isCli()); + } + + public function testVar() + { + Config::set('app_multi_module', true); + $request = Request::create(''); + $request->route(['name' => 'thinkphp', 'id' => 6]); + $request->get(['id' => 10]); + $request->post(['id' => 8]); + $request->put(['id' => 7]); + $request->request(['test' => 'value']); + $this->assertEquals(['name' => 'thinkphp', 'id' => 6], $request->route()); + //$this->assertEquals(['id' => 10], $request->get()); + $this->assertEquals('thinkphp', $request->route('name')); + $this->assertEquals('default', $request->route('test', 'default')); + $this->assertEquals(10, $request->get('id')); + $this->assertEquals(0, $request->get('ids', 0)); + $this->assertEquals(8, $request->post('id')); + $this->assertEquals(7, $request->put('id')); + $this->assertEquals('value', $request->request('test')); + $this->assertEquals('thinkphp', $request->param('name')); + $this->assertEquals(6, $request->param('id')); + $this->assertFalse($request->has('user_id')); + $this->assertTrue($request->has('test', 'request')); + $this->assertEquals(['id' => 6], $request->only('id')); + $this->assertEquals(['name' => 'thinkphp', 'lang' => 'zh-cn'], $request->except('id')); + $this->assertEquals('THINKPHP', $request->param('name', '', 'strtoupper')); + } + + public function testIsAjax() + { + $request = Request::create(''); + + $this->assertFalse($request->isAjax()); + + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'xmlhttprequest'; + $this->assertFalse($request->isAjax()); + $this->assertFalse($request->isAjax(true)); + + $request->server(['HTTP_X_REQUESTED_WITH' => 'xmlhttprequest']); + $this->assertTrue($request->isAjax()); + } + + public function testIsPjax() + { + $request = Request::create(''); + + $this->assertFalse($request->isPjax()); + + $_SERVER['HTTP_X_PJAX'] = true; + $this->assertFalse($request->isPjax()); + $this->assertFalse($request->isPjax(true)); + + $request->server(['HTTP_X_PJAX' => true]); + $this->assertTrue($request->isPjax()); + $request->server(['HTTP_X_PJAX' => false]); + $this->assertTrue($request->isPjax()); + } + + public function testIsMobile() + { + $request = Request::create(''); + $_SERVER['HTTP_VIA'] = 'wap'; + $this->assertTrue($request->isMobile()); + } + + public function testBind() + { + $request = Request::create(''); + $request->user = 'User1'; + $request->bind(['user' => 'User2']); + $this->assertEquals('User2', $request->user); + } + +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/responseTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/responseTest.php new file mode 100644 index 0000000..62d1574 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/responseTest.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- + +/** + * Response测试 + * @author 大漠 + */ + +namespace tests\thinkphp\library\think; + +use think\Config; +use think\Request; +use think\Response; + +class responseTest extends \PHPUnit_Framework_TestCase +{ + + /** + * + * @var \think\Response + */ + protected $object; + + protected $default_return_type; + + protected $default_ajax_return; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + // 1. + // restore_error_handler(); + // Warning: Cannot modify header information - headers already sent by (output started at PHPUnit\Util\Printer.php:173) + // more see in https://www.analysisandsolutions.com/blog/html/writing-phpunit-tests-for-wordpress-plugins-wp-redirect-and-continuing-after-php-errors.htm + + // 2. + // the Symfony used the HeaderMock.php + + // 3. + // not run the eclipse will held, and travis-ci.org Searching for coverage reports + // **> Python coverage not found + // **> No coverage report found. + // add the + // /** + // * @runInSeparateProcess + // */ + if (!$this->default_return_type) { + $this->default_return_type = Config::get('default_return_type'); + } + if (!$this->default_ajax_return) { + $this->default_ajax_return = Config::get('default_ajax_return'); + } + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + Config::set('default_ajax_return', $this->default_ajax_return); + Config::set('default_return_type', $this->default_return_type); + } + + /** + * @covers think\Response::send + * @todo Implement testSend(). + */ + public function testSend() + { + $dataArr = []; + $dataArr["key"] = "value"; + + $response = Response::create($dataArr, 'json'); + $result = $response->getContent(); + $this->assertEquals('{"key":"value"}', $result); + $request = Request::instance(); + $request->get(['callback' => 'callback']); + $response = Response::create($dataArr, 'jsonp'); + $result = $response->getContent(); + $this->assertEquals('callback({"key":"value"});', $result); + } + +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/routeTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/routeTest.php new file mode 100644 index 0000000..31e1b86 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/routeTest.php @@ -0,0 +1,287 @@ + +// +---------------------------------------------------------------------- + +/** + * Route测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use think\Config; +use think\Request; +use think\Route; + +class routeTest extends \PHPUnit_Framework_TestCase +{ + + protected function setUp() + { + Config::set('app_multi_module', true); + } + + public function testRegister() + { + $request = Request::instance(); + Route::get('hello/:name', 'index/hello'); + Route::get(['hello/:name' => 'index/hello']); + Route::post('hello/:name', 'index/post'); + Route::put('hello/:name', 'index/put'); + Route::delete('hello/:name', 'index/delete'); + Route::patch('hello/:name', 'index/patch'); + Route::any('user/:id', 'index/user'); + $result = Route::check($request, 'hello/thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $this->assertEquals(['hello' => true, 'user/:id' => true, 'hello/:name' => ['rule' => 'hello/:name', 'route' => 'index/hello', 'var' => ['name' => 1], 'option' => [], 'pattern' => []]], Route::rules('GET')); + Route::rule('type1/:name', 'index/type', 'PUT|POST'); + Route::rule(['type2/:name' => 'index/type1']); + Route::rule([['type3/:name', 'index/type2', ['method' => 'POST']]]); + Route::rule(['name', 'type4/:name'], 'index/type4'); + } + + public function testImport() + { + $rule = [ + '__domain__' => ['subdomain2.thinkphp.cn' => 'blog1'], + '__alias__' => ['blog1' => 'blog1'], + '__rest__' => ['res' => ['index/blog']], + 'bbb' => ['index/blog1', ['method' => 'POST']], + 'ddd' => '', + ['hello1/:ddd', 'index/hello1', ['method' => 'POST']], + ]; + Route::import($rule); + } + + public function testResource() + { + $request = Request::instance(); + Route::resource('res', 'index/blog'); + Route::resource(['res' => ['index/blog']]); + $result = Route::check($request, 'res'); + $this->assertEquals(['index', 'blog', 'index'], $result['module']); + $result = Route::check($request, 'res/create'); + $this->assertEquals(['index', 'blog', 'create'], $result['module']); + $result = Route::check($request, 'res/8'); + $this->assertEquals(['index', 'blog', 'read'], $result['module']); + $result = Route::check($request, 'res/8/edit'); + $this->assertEquals(['index', 'blog', 'edit'], $result['module']); + + Route::resource('blog.comment', 'index/comment'); + $result = Route::check($request, 'blog/8/comment/10'); + $this->assertEquals(['index', 'comment', 'read'], $result['module']); + $result = Route::check($request, 'blog/8/comment/10/edit'); + $this->assertEquals(['index', 'comment', 'edit'], $result['module']); + + } + + public function testRest() + { + $request = Request::instance(); + Route::rest('read', ['GET', '/:id', 'look']); + Route::rest('create', ['GET', '/create', 'add']); + Route::rest(['read' => ['GET', '/:id', 'look'], 'create' => ['GET', '/create', 'add']]); + Route::resource('res', 'index/blog'); + $result = Route::check($request, 'res/create'); + $this->assertEquals(['index', 'blog', 'add'], $result['module']); + $result = Route::check($request, 'res/8'); + $this->assertEquals(['index', 'blog', 'look'], $result['module']); + + } + + public function testMixVar() + { + $request = Request::instance(); + Route::get('hello-', 'index/hello', [], ['name' => '\w+']); + $result = Route::check($request, 'hello-thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + Route::get('hello-', 'index/hello', [], ['name' => '\w+', 'id' => '\d+']); + $result = Route::check($request, 'hello-thinkphp2016'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + Route::get('hello-/[:id]', 'index/hello', [], ['name' => '\w+', 'id' => '\d+']); + $result = Route::check($request, 'hello-thinkphp/2016'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + } + + public function testParseUrl() + { + $result = Route::parseUrl('hello'); + $this->assertEquals(['hello', null, null], $result['module']); + $result = Route::parseUrl('index/hello'); + $this->assertEquals(['index', 'hello', null], $result['module']); + $result = Route::parseUrl('index/hello?name=thinkphp'); + $this->assertEquals(['index', 'hello', null], $result['module']); + $result = Route::parseUrl('index/user/hello'); + $this->assertEquals(['index', 'user', 'hello'], $result['module']); + $result = Route::parseUrl('index/user/hello/name/thinkphp'); + $this->assertEquals(['index', 'user', 'hello'], $result['module']); + $result = Route::parseUrl('index-index-hello', '-'); + $this->assertEquals(['index', 'index', 'hello'], $result['module']); + } + + public function testCheckRoute() + { + Route::get('hello/:name', 'index/hello'); + Route::get('blog/:id', 'blog/read', [], ['id' => '\d+']); + $request = Request::instance(); + $this->assertEquals(false, Route::check($request, 'test/thinkphp')); + $this->assertEquals(false, Route::check($request, 'blog/thinkphp')); + $result = Route::check($request, 'blog/5'); + $this->assertEquals([null, 'blog', 'read'], $result['module']); + $result = Route::check($request, 'hello/thinkphp/abc/test'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + } + + public function testCheckRouteGroup() + { + $request = Request::instance(); + Route::pattern(['id' => '\d+']); + Route::pattern('name', '\w{6,25}'); + Route::group('group', [':id' => 'index/hello', ':name' => 'index/say']); + $this->assertEquals(false, Route::check($request, 'empty/think')); + $result = Route::check($request, 'group/think'); + $this->assertEquals(false, $result['module']); + $result = Route::check($request, 'group/10'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $result = Route::check($request, 'group/thinkphp'); + $this->assertEquals([null, 'index', 'say'], $result['module']); + Route::group('group2', function () { + Route::group('group3', [':id' => 'index/hello', ':name' => 'index/say']); + Route::rule(':name', 'index/hello'); + Route::auto('index'); + }); + $result = Route::check($request, 'group2/thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $result = Route::check($request, 'group2/think'); + $this->assertEquals(['index', 'group2', 'think'], $result['module']); + $result = Route::check($request, 'group2/group3/thinkphp'); + $this->assertEquals([null, 'index', 'say'], $result['module']); + Route::group('group4', function () { + Route::group('group3', [':id' => 'index/hello', ':name' => 'index/say']); + Route::rule(':name', 'index/hello'); + Route::miss('index/__miss__'); + }); + $result = Route::check($request, 'group4/thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $result = Route::check($request, 'group4/think'); + $this->assertEquals([null, 'index', '__miss__'], $result['module']); + + Route::group(['prefix' => 'prefix/'], function () { + Route::rule('hello4/:name', 'hello'); + }); + Route::group(['prefix' => 'prefix/'], [ + 'hello4/:name' => 'hello', + ]); + $result = Route::check($request, 'hello4/thinkphp'); + $this->assertEquals([null, 'prefix', 'hello'], $result['module']); + Route::group('group5', [ + [':name', 'hello', ['method' => 'GET|POST']], + ':id' => 'hello', + ], ['prefix' => 'prefix/']); + $result = Route::check($request, 'group5/thinkphp'); + $this->assertEquals([null, 'prefix', 'hello'], $result['module']); + } + + public function testControllerRoute() + { + $request = Request::instance(); + Route::controller('controller', 'index/Blog'); + $result = Route::check($request, 'controller/info'); + $this->assertEquals(['index', 'Blog', 'getinfo'], $result['module']); + Route::setMethodPrefix('GET', 'read'); + Route::setMethodPrefix(['get' => 'read']); + Route::controller('controller', 'index/Blog'); + $result = Route::check($request, 'controller/phone'); + $this->assertEquals(['index', 'Blog', 'readphone'], $result['module']); + } + + public function testAliasRoute() + { + $request = Request::instance(); + Route::alias('alias', 'index/Alias'); + $result = Route::check($request, 'alias/info'); + $this->assertEquals('index/Alias/info', $result['module']); + } + + public function testRouteToModule() + { + $request = Request::instance(); + Route::get('hello/:name', 'index/hello'); + Route::get('blog/:id', 'blog/read', [], ['id' => '\d+']); + $this->assertEquals(false, Route::check($request, 'test/thinkphp')); + $this->assertEquals(false, Route::check($request, 'blog/thinkphp')); + $result = Route::check($request, 'hello/thinkphp'); + $this->assertEquals([null, 'index', 'hello'], $result['module']); + $result = Route::check($request, 'blog/5'); + $this->assertEquals([null, 'blog', 'read'], $result['module']); + } + + public function testRouteToController() + { + $request = Request::instance(); + Route::get('say/:name', '@index/hello'); + $this->assertEquals(['type' => 'controller', 'controller' => 'index/hello', 'var' => []], Route::check($request, 'say/thinkphp')); + } + + public function testRouteToMethod() + { + $request = Request::instance(); + Route::get('user/:name', '\app\index\service\User::get', [], ['name' => '\w+']); + Route::get('info/:name', '\app\index\model\Info@getInfo', [], ['name' => '\w+']); + $this->assertEquals(['type' => 'method', 'method' => '\app\index\service\User::get', 'var' => []], Route::check($request, 'user/thinkphp')); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\model\Info', 'getInfo'], 'var' => []], Route::check($request, 'info/thinkphp')); + } + + public function testRouteToRedirect() + { + $request = Request::instance(); + Route::get('art/:id', '/article/read/id/:id', [], ['id' => '\d+']); + $this->assertEquals(['type' => 'redirect', 'url' => '/article/read/id/8', 'status' => 301], Route::check($request, 'art/8')); + } + + public function testBind() + { + $request = Request::instance(); + Route::bind('index/blog'); + Route::get('blog/:id', 'index/blog/read'); + $result = Route::check($request, 'blog/10'); + $this->assertEquals(['index', 'blog', 'read'], $result['module']); + $result = Route::parseUrl('test'); + $this->assertEquals(['index', 'blog', 'test'], $result['module']); + + Route::bind('\app\index\controller', 'namespace'); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\Blog', 'read'], 'var' => []], Route::check($request, 'blog/read')); + + Route::bind('\app\index\controller\Blog', 'class'); + $this->assertEquals(['type' => 'method', 'method' => ['\app\index\controller\Blog', 'read'], 'var' => []], Route::check($request, 'read')); + } + + public function testDomain() + { + $request = Request::create('http://subdomain.thinkphp.cn'); + Route::domain('subdomain.thinkphp.cn', 'sub?abc=test&status=1'); + $rules = Route::rules('GET'); + Route::checkDomain($request, $rules); + $this->assertEquals('sub', Route::getbind('module')); + $this->assertEquals('test', $_GET['abc']); + $this->assertEquals(1, $_GET['status']); + + Route::domain('subdomain.thinkphp.cn', '\app\index\controller'); + $rules = Route::rules('GET'); + Route::checkDomain($request, $rules); + $this->assertEquals('\app\index\controller', Route::getbind('namespace')); + + Route::domain(['subdomain.thinkphp.cn' => '@\app\index\controller\blog']); + $rules = Route::rules('GET'); + Route::checkDomain($request, $rules); + $this->assertEquals('\app\index\controller\blog', Route::getbind('class')); + + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/session/.gitignore b/vendor/topthink/framework/tests/thinkphp/library/think/session/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/session/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/sessionTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/sessionTest.php new file mode 100644 index 0000000..5fd95f1 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/sessionTest.php @@ -0,0 +1,319 @@ + +// +---------------------------------------------------------------------- + +/** + * Session测试 + * @author 大漠 + */ + +namespace tests\thinkphp\library\think; + +use think\Session; + +class sessionTest extends \PHPUnit_Framework_TestCase +{ + + /** + * + * @var \think\Session + */ + protected $object; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + // $this->object = new Session (); + // register_shutdown_function ( function () { + // } ); // 此功能无法取消,需要回调函数配合。 + set_exception_handler(function () {}); + set_error_handler(function () {}); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + register_shutdown_function('think\Error::appShutdown'); + set_error_handler('think\Error::appError'); + set_exception_handler('think\Error::appException'); + } + + /** + * @covers think\Session::prefix + * + * @todo Implement testPrefix(). + */ + public function testPrefix() + { + Session::prefix(null); + Session::prefix('think_'); + + $this->assertEquals('think_', Session::prefix()); + } + + /** + * @covers think\Session::init + * + * @todo Implement testInit(). + */ + public function testInit() + { + Session::prefix(null); + $config = [ + // cookie 名称前缀 + 'prefix' => 'think_', + // cookie 保存时间 + 'expire' => 60, + // cookie 保存路径 + 'path' => '/path/to/test/session/', + // cookie 有效域名 + 'domain' => '.thinkphp.cn', + 'var_session_id' => 'sessionidtest', + 'id' => 'sess_8fhgkjuakhatbeg2fa14lo84q1', + 'name' => 'session_name', + 'use_trans_sid' => '1', + 'use_cookies' => '1', + 'cache_limiter' => '60', + 'cache_expire' => '60', + 'type' => '', // memcache + 'namespace' => '\\think\\session\\driver\\', // ? + 'auto_start' => '1', + ]; + + $_REQUEST[$config['var_session_id']] = $config['id']; + Session::init($config); + + // 开始断言 + $this->assertEquals($config['prefix'], Session::prefix()); + $this->assertEquals($config['id'], $_REQUEST[$config['var_session_id']]); + $this->assertEquals($config['name'], session_name()); + + $this->assertEquals($config['path'], session_save_path()); + $this->assertEquals($config['use_cookies'], ini_get('session.use_cookies')); + $this->assertEquals($config['domain'], ini_get('session.cookie_domain')); + $this->assertEquals($config['expire'], ini_get('session.gc_maxlifetime')); + $this->assertEquals($config['expire'], ini_get('session.cookie_lifetime')); + + $this->assertEquals($config['cache_limiter'], session_cache_limiter($config['cache_limiter'])); + $this->assertEquals($config['cache_expire'], session_cache_expire($config['cache_expire'])); + + // 检测分支 + $_REQUEST[$config['var_session_id']] = null; + session_write_close(); + session_destroy(); + + Session::init($config); + + // 测试auto_start + // PHP_SESSION_DISABLED + // PHP_SESSION_NONE + // PHP_SESSION_ACTIVE + // session_status() + if (strstr(PHP_VERSION, 'hhvm')) { + $this->assertEquals('', ini_get('session.auto_start')); + } else { + $this->assertEquals(0, ini_get('session.auto_start')); + } + + $this->assertEquals($config['use_trans_sid'], ini_get('session.use_trans_sid')); + + Session::init($config); + $this->assertEquals($config['id'], session_id()); + } + + /** + * 单独重现异常 + * @expectedException \think\Exception + */ + public function testException() + { + $config = [ + // cookie 名称前缀 + 'prefix' => 'think_', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/path/to/test/session/', + // cookie 有效域名 + 'domain' => '.thinkphp.cn', + 'var_session_id' => 'sessionidtest', + 'id' => 'sess_8fhgkjuakhatbeg2fa14lo84q1', + 'name' => 'session_name', + 'use_trans_sid' => '1', + 'use_cookies' => '1', + 'cache_limiter' => '60', + 'cache_expire' => '60', + 'type' => '\\think\\session\\driver\\Memcache', // + 'auto_start' => '1', + ]; + + // 测试session驱动是否存在 + // @expectedException 异常类名 + $this->setExpectedException('\think\exception\ClassNotFoundException', 'error session handler'); + + Session::init($config); + } + + /** + * @covers think\Session::set + * + * @todo Implement testSet(). + */ + public function testSet() + { + Session::prefix(null); + Session::set('sessionname', 'sessionvalue'); + $this->assertEquals('sessionvalue', $_SESSION['sessionname']); + + Session::set('sessionnamearr.subname', 'sessionvalue'); + $this->assertEquals('sessionvalue', $_SESSION['sessionnamearr']['subname']); + + Session::set('sessionnameper', 'sessionvalue', 'think_'); + $this->assertEquals('sessionvalue', $_SESSION['think_']['sessionnameper']); + + Session::set('sessionnamearrper.subname', 'sessionvalue', 'think_'); + $this->assertEquals('sessionvalue', $_SESSION['think_']['sessionnamearrper']['subname']); + } + + /** + * @covers think\Session::get + * + * @todo Implement testGet(). + */ + public function testGet() + { + Session::prefix(null); + + Session::set('sessionnameget', 'sessionvalue'); + $this->assertEquals(Session::get('sessionnameget'), $_SESSION['sessionnameget']); + + Session::set('sessionnamegetarr.subname', 'sessionvalue'); + $this->assertEquals(Session::get('sessionnamegetarr.subname'), $_SESSION['sessionnamegetarr']['subname']); + + Session::set('sessionnamegetarrperall', 'sessionvalue', 'think_'); + $this->assertEquals(Session::get('', 'think_')['sessionnamegetarrperall'], $_SESSION['think_']['sessionnamegetarrperall']); + + Session::set('sessionnamegetper', 'sessionvalue', 'think_'); + $this->assertEquals(Session::get('sessionnamegetper', 'think_'), $_SESSION['think_']['sessionnamegetper']); + + Session::set('sessionnamegetarrper.subname', 'sessionvalue', 'think_'); + $this->assertEquals(Session::get('sessionnamegetarrper.subname', 'think_'), $_SESSION['think_']['sessionnamegetarrper']['subname']); + } + + public function testPull() + { + Session::prefix(null); + Session::set('sessionnamedel', 'sessionvalue'); + $this->assertEquals('sessionvalue', Session::pull('sessionnameget')); + $this->assertNull(Session::get('sessionnameget')); + } + + /** + * @covers think\Session::delete + * + * @todo Implement testDelete(). + */ + public function testDelete() + { + Session::prefix(null); + Session::set('sessionnamedel', 'sessionvalue'); + Session::delete('sessionnamedel'); + $this->assertEmpty($_SESSION['sessionnamedel']); + + Session::set('sessionnamedelarr.subname', 'sessionvalue'); + Session::delete('sessionnamedelarr.subname'); + $this->assertEmpty($_SESSION['sessionnamedelarr']['subname']); + + Session::set('sessionnamedelper', 'sessionvalue', 'think_'); + Session::delete('sessionnamedelper', 'think_'); + $this->assertEmpty($_SESSION['think_']['sessionnamedelper']); + + Session::set('sessionnamedelperarr.subname', 'sessionvalue', 'think_'); + Session::delete('sessionnamedelperarr.subname', 'think_'); + $this->assertEmpty($_SESSION['think_']['sessionnamedelperarr']['subname']); + } + + /** + * @covers think\Session::clear + * + * @todo Implement testClear(). + */ + public function testClear() + { + Session::prefix(null); + + Session::set('sessionnameclsper', 'sessionvalue1', 'think_'); + Session::clear('think_'); + $this->assertNull($_SESSION['think_']); + + Session::set('sessionnameclsper', 'sessionvalue1', 'think_'); + Session::clear(); + $this->assertEmpty($_SESSION); + } + + /** + * @covers think\Session::has + * + * @todo Implement testHas(). + */ + public function testHas() + { + Session::prefix(null); + Session::set('sessionnamehas', 'sessionvalue'); + $this->assertTrue(Session::has('sessionnamehas')); + + Session::set('sessionnamehasarr.subname', 'sessionvalue'); + $this->assertTrue(Session::has('sessionnamehasarr.subname')); + + Session::set('sessionnamehasper', 'sessionvalue', 'think_'); + $this->assertTrue(Session::has('sessionnamehasper', 'think_')); + + Session::set('sessionnamehasarrper.subname', 'sessionvalue', 'think_'); + $this->assertTrue(Session::has('sessionnamehasarrper.subname', 'think_')); + } + + /** + * @covers think\Session::pause + * + * @todo Implement testPause(). + */ + public function testPause() + { + Session::pause(); + } + + /** + * @covers think\Session::start + * + * @todo Implement testStart(). + */ + public function testStart() + { + Session::start(); + } + + /** + * @covers think\Session::destroy + * + * @todo Implement testDestroy(). + */ + public function testDestroy() + { + Session::set('sessionnamedestroy', 'sessionvalue'); + Session::destroy(); + $this->assertEmpty($_SESSION['sessionnamedestroy']); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/template/driver/.gitignore b/vendor/topthink/framework/tests/thinkphp/library/think/template/driver/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/template/driver/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/template/taglib/cxTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/template/taglib/cxTest.php new file mode 100644 index 0000000..8aee392 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/template/taglib/cxTest.php @@ -0,0 +1,575 @@ + +// +---------------------------------------------------------------------- + +/** + * 模板测试 + * @author Haotong Lin + */ + +namespace tests\thinkphp\library\think\tempplate\taglib; + +use think\Template; +use think\template\taglib\Cx; + +class cxTest extends \PHPUnit_Framework_TestCase +{ + public function testPhp() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testVolist() + { + $template = new template(); + $cx = new Cx($template); + + $content = <<\$vo): \$mod = (\$key % 2 );++\$key;?> + + +EOF; + $cx->parseTag($content); + $this->assertEquals($data, $content); + + $content = <<display($content, ['list' => [1, 2, 3, 4, 5]]); + $this->expectOutputString('234'); + } + + public function testForeach() + { + $template = new template(); + $cx = new Cx($template); + + $content = <<\$val} + +{/foreach} +EOF; + $data = <<\$val): ?> + + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<\$val): ?> + + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<display($content); + $this->expectOutputString('234'); + } + + public function testIf() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +one + +two + +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testSwitch() + { + $template = new template(); + $cx = new Cx($template); + + $content = << + +a + + +b + + +d + + +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testCompare() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << '0'): ?> +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<= '0'): ?> +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + } + + public function testRange() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<display($content); + $this->expectOutputString('yesno'); + } + + public function testPresent() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testEmpty() + { + $template = new template(); + $cx = new Cx($template); + + $content = <<isEmpty())): ?> +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = <<isEmpty()))): ?> +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testDefined() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +default + +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testImport() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testAssign() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testDefine() + { + $template = new template(); + $cx = new Cx($template); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + + $content = << +EOF; + $cx->parseTag($content); + $this->assertEquals($content, $data); + } + + public function testFor() + { + $template = new template(); + + $content = <<display($content); + $this->expectOutputString('123456789'); + } + public function testUrl() + { + $template = new template(); + $content = <<display($content); + $this->expectOutputString(\think\Url::build('Index/index')); + } + + public function testFunction() + { + $template = new template(); + $data = [ + 'list' => ['language' => 'php', 'version' => ['5.4', '5.5']], + 'a' => '[', + 'b' => ']', + ]; + + $content = <<\$val} +{if is_array(\$val)} +{~\$func(\$val)} +{else} +{if !is_numeric(\$key)} +{\$key.':'.\$val.','} +{else} +{\$a.\$val.\$b} +{/if} +{/if} +{/foreach} +{/function} +EOF; + $template->display($content, $data); + $this->expectOutputString("language:php,[5.4][5.5]"); + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/templateTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/templateTest.php new file mode 100644 index 0000000..1135830 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/templateTest.php @@ -0,0 +1,415 @@ + +// +---------------------------------------------------------------------- + +/** + * 模板测试 + * @author oldrind + */ + +namespace tests\thinkphp\library\think; + +use think\Cache; +use think\Template; + +class templateTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Template + */ + protected $template; + + public function setUp() + { + $this->template = new Template(); + } + + public function testAssign() + { + $reflectProperty = new \ReflectionProperty(get_class($this->template), 'data'); + $reflectProperty->setAccessible(true); + + $this->template->assign('version', 'ThinkPHP3.2'); + $data = $reflectProperty->getValue($this->template); + $this->assertEquals('ThinkPHP3.2', $data['version']); + + $this->template->assign(['name' => 'Gao', 'version' => 'ThinkPHP5']); + $data = $reflectProperty->getValue($this->template); + $this->assertEquals('Gao', $data['name']); + $this->assertEquals('ThinkPHP5', $data['version']); + } + + public function testGet() + { + $this->template = new Template(); + $data = [ + 'project' => 'ThinkPHP', + 'version' => [ + 'ThinkPHP5' => ['Think5.0', 'Think5.1'] + ] + ]; + $this->template->assign($data); + + $this->assertSame($data, $this->template->get()); + $this->assertSame('ThinkPHP', $this->template->get('project')); + $this->assertSame(['Think5.0', 'Think5.1'], $this->template->get('version.ThinkPHP5')); + $this->assertNull($this->template->get('version.ThinkPHP3.2')); + } + + /** + * @dataProvider provideTestParseWithVar + */ + public function testParseWithVar($content, $expected) + { + $this->template = new Template(); + + $this->template->parse($content); + $this->assertEquals($expected, $content); + } + + /** + * @dataProvider provideTestParseWithVarFunction + */ + public function testParseWithVarFunction($content, $expected) + { + $this->template = new Template(); + + $this->template->parse($content); + $this->assertEquals($expected, $content); + } + + /** + * @dataProvider provideTestParseWithVarIdentify + */ + public function testParseWithVarIdentify($content, $expected, $config) + { + $this->template = new Template($config); + + $this->template->parse($content); + $this->assertEquals($expected, $content); + } + + /** + * @dataProvider provideTestParseWithThinkVar + */ + public function testParseWithThinkVar($content, $expected) + { + $config['tpl_begin'] = '{'; + $config['tpl_end'] = '}'; + $this->template = new Template($config); + + $_SERVER['SERVER_NAME'] = 'server_name'; + $_GET['action'] = 'action'; + $_POST['action'] = 'action'; + $_COOKIE['name'] = 'name'; + $_SESSION['action'] = ['name' => 'name']; + + $this->template->parse($content); + $this->assertEquals($expected, $content); + } + + /** + * @expectedException \think\exception\TemplateNotFoundException + */ + public function testFetchWithEmptyTemplate() + { + $this->template = new Template(); + + $this->template->fetch('Foo'); + } + + /** + * @dataProvider provideTestFetchWithNoCache + */ + public function testFetchWithNoCache($data, $expected) + { + $this->template = new Template(); + + $this->template->fetch($data['template'], $data['vars'], $data['config']); + + $this->expectOutputString($expected); + } + + public function testFetchWithCache() + { + $this->template = new Template(); + + $data = [ + 'name' => 'value' + ]; + $config = [ + 'cache_id' => 'TEST_FETCH_WITH_CACHE', + 'display_cache' => true, + ]; + + $this->template->fetch(APP_PATH . 'views' . DS .'display.html', $data, $config); + + $this->expectOutputString('value'); + $this->assertEquals('value', Cache::get($config['cache_id'])); + } + + public function testDisplay() + { + $config = [ + 'view_path' => APP_PATH . DS . 'views' . DS, + 'view_suffix' => '.html', + 'layout_on' => true, + 'layout_name' => 'layout' + ]; + + $this->template = new Template($config); + + $this->template->assign('files', ['extend' => 'extend', 'include' => 'include']); + $this->template->assign('user', ['name' => 'name', 'account' => 100]); + $this->template->assign('message', 'message'); + $this->template->assign('info', ['value' => 'value']); + + $content = << +header +
      + +value: + +main + + + side + + +value: + message{\$message} + + + mainbody + + + + {\$name} + + php code
      + +EOF; + $this->template->display($content); + $this->expectOutputString($expected); + } + + /** + * @dataProvider provideTestLayout + */ + public function testLayout($data, $expected) + { + $this->template = new Template(); + + $this->template->layout($data['name'], $data['replace']); + + $this->assertSame($expected['layout_on'], $this->template->config('layout_on')); + $this->assertSame($expected['layout_name'], $this->template->config('layout_name')); + $this->assertSame($expected['layout_item'], $this->template->config('layout_item')); + } + + public function testParseAttr() + { + $attributes = $this->template->parseAttr(""); + $this->assertSame(['version' => 'ThinkPHP', 'name' => 'Gao'], $attributes); + + $attributes = $this->template->parseAttr("TestCase", 'version'); + $this->assertSame('ThinkPHP', $attributes); + } + + public function testIsCache() + { + $this->template = new Template(); + $config = [ + 'cache_id' => rand(0, 10000) . rand(0, 10000) . time(), + 'display_cache' => true + ]; + + $this->assertFalse($this->template->isCache($config['cache_id'])); + + $this->template->fetch(APP_PATH . 'views' . DS .'display.html', [], $config); + $this->assertTrue($this->template->isCache($config['cache_id'])); + } + + public function provideTestParseWithVar() + { + return [ + ["{\$name.a.b}", ""], + ["{\$name.a??'test'}", ""], + ["{\$name.a?='test'}", ""], + ["{\$name.a?:'test'}", ""], + ["{\$name.a?\$name.b:'no'}", ""], + ["{\$name.a==\$name.b?='test'}", ""], + ["{\$name.a==\$name.b?'a':'b'}", ""], + ["{\$name.a|default='test'==\$name.b?'a':'b'}", ""], + ["{\$name.a|trim==\$name.b?='eq'}", ""], + ["{:ltrim(rtrim(\$name.a))}", ""], + ["{~echo(trim(\$name.a))}", ""], + ["{++\$name.a}", ""], + ["{/*\$name*/}", ""], + ["{\$0a}", "{\$0a}"] + ]; + } + + public function provideTestParseWithVarFunction() + { + return [ + ["{\$name.a.b|default='test'}", ""], + ["{\$create_time|date=\"y-m-d\",###}", ""], + ["{\$name}\n{\$name|trim|substr=0,3}", "\n"] + ]; + } + + public function provideTestParseWithVarIdentify() + { + $config['tpl_begin'] = '<#'; + $config['tpl_end'] = '#>'; + $config['tpl_var_identify'] = ''; + + return [ + [ + "<#\$info.a??'test'#>", + "a)) ? (is_array(\$info)?\$info['a']:\$info->a) : 'test'; ?>", + $config + ], + [ + "<#\$info.a?='test'#>", + "a)) echo 'test'; ?>", + $config + ], + [ + "<#\$info.a==\$info.b?='test'#>", + "a)==(is_array(\$info)?\$info['b']:\$info->b)) echo 'test'; ?>", + $config + ], + [ + "<#\$info.a|default='test'?'yes':'no'#>", + "a) ?: 'test')?'yes':'no'; ?>", + $config + ], + [ + "{\$info2.b|trim?'yes':'no'}", + "b)?'yes':'no'; ?>", + array_merge(['tpl_var_identify' => 'obj']) + ] + ]; + } + + public function provideTestParseWithThinkVar() + { + return [ + ["{\$Think.SERVER.SERVER_NAME}
      ", "server('SERVER_NAME'); ?>
      "], + ["{\$Think.GET.action}
      ", "get('action'); ?>
      "], + ["{\$Think.POST.action}
      ", "post('action'); ?>
      "], + ["{\$Think.COOKIE.action}
      ", "
      "], + ["{\$Think.COOKIE.action.name}
      ", "
      "], + ["{\$Think.SESSION.action}
      ", "
      "], + ["{\$Think.SESSION.action.name}
      ", "
      "], + ["{\$Think.ENV.OS}
      ", "env('OS'); ?>
      "], + ["{\$Think.REQUEST.action}
      ", "request('action'); ?>
      "], + ["{\$Think.CONST.THINK_VERSION}
      ", "
      "], + ["{\$Think.LANG.action}
      ", "
      "], + ["{\$Think.CONFIG.action.name}
      ", "
      "], + ["{\$Think.NOW}
      ", "
      "], + ["{\$Think.VERSION}
      ", "
      "], + ["{\$Think.LDELIM}
      ", "
      "], + ["{\$Think.RDELIM}
      ", "
      "], + ["{\$Think.THINK_VERSION}
      ", "
      "], + ["{\$Think.SITE.URL}", ""] + ]; + } + + public function provideTestFetchWithNoCache() + { + $provideData = []; + + $this->template = [ + 'template' => APP_PATH . 'views' . DS .'display.html', + 'vars' => [], + 'config' => [] + ]; + $expected = 'default'; + $provideData[] = [$this->template, $expected]; + + $this->template = [ + 'template' => APP_PATH . 'views' . DS .'display.html', + 'vars' => ['name' => 'ThinkPHP5'], + 'config' => [] + ]; + $expected = 'ThinkPHP5'; + $provideData[] = [$this->template, $expected]; + + $this->template = [ + 'template' => 'views@display', + 'vars' => [], + 'config' => [ + 'view_suffix' => 'html' + ] + ]; + $expected = 'default'; + $provideData[] = [$this->template, $expected]; + + $this->template = [ + 'template' => 'views@/display', + 'vars' => ['name' => 'ThinkPHP5'], + 'config' => [ + 'view_suffix' => 'phtml' + ] + ]; + $expected = 'ThinkPHP5'; + $provideData[] = [$this->template, $expected]; + + $this->template = [ + 'template' => 'display', + 'vars' => ['name' => 'ThinkPHP5'], + 'config' => [ + 'view_suffix' => 'html', + 'view_base' => APP_PATH . 'views' . DS + ] + ]; + $expected = 'ThinkPHP5'; + $provideData[] = [$this->template, $expected]; + + return $provideData; + } + + public function provideTestLayout() + { + $provideData = []; + + $data = ['name' => false, 'replace' => '']; + $expected = ['layout_on' => false, 'layout_name' => 'layout', 'layout_item' => '{__CONTENT__}']; + $provideData[] = [$data, $expected]; + + $data = ['name' => null, 'replace' => '']; + $expected = ['layout_on' => true, 'layout_name' => 'layout', 'layout_item' => '{__CONTENT__}']; + $provideData[] = [$data, $expected]; + + $data = ['name' => 'ThinkName', 'replace' => 'ThinkReplace']; + $expected = ['layout_on' => true, 'layout_name' => 'ThinkName', 'layout_item' => 'ThinkReplace']; + $provideData[] = [$data, $expected]; + + return $provideData; + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/urlTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/urlTest.php new file mode 100644 index 0000000..401d973 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/urlTest.php @@ -0,0 +1,129 @@ + +// +---------------------------------------------------------------------- + +/** + * Url测试 + * @author liu21st + */ + +namespace tests\thinkphp\library\think; + +use tests\thinkphp\library\think\config\ConfigInitTrait; +use think\Config; +use think\Route; +use think\Url; + +class urlTest extends \PHPUnit_Framework_TestCase +{ + use ConfigInitTrait; + + public function setUp() + { + Route::rules(['get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], + '*' => [], + 'alias' => [], + 'domain' => [], + 'pattern' => [], + 'name' => []]); + Route::name([]); + } + + public function testBuildModule() + { + + Route::get('blog/:name', 'index/blog'); + Route::get('blog/:id', 'index/blog'); + Config::set('pathinfo_depr', '/'); + Config::set('url_html_suffix', ''); + + $this->assertEquals('/blog/thinkphp', Url::build('index/blog?name=thinkphp')); + $this->assertEquals('/blog/thinkphp.html', Url::build('index/blog', 'name=thinkphp', 'html')); + $this->assertEquals('/blog/10', Url::build('index/blog?id=10')); + $this->assertEquals('/blog/10.html', Url::build('index/blog', 'id=10', 'html')); + + Route::get('item-', 'blog/item', [], ['name' => '\w+', 'id' => '\d+']); + $this->assertEquals('/item-thinkphp', Url::build('blog/item?name=thinkphp')); + $this->assertEquals('/item-thinkphp2016', Url::build('blog/item?name=thinkphp&id=2016')); + } + + public function testBuildController() + { + Config::set('url_html_suffix', ''); + Route::get('blog/:id', '@index/blog/read'); + $this->assertEquals('/blog/10.html', Url::build('@index/blog/read', 'id=10', 'html')); + + Route::get('foo/bar', '@foo/bar/index'); + $this->assertEquals('/foo/bar', Url::build('@foo/bar/index')); + + Route::get('foo/bar/baz', '@foo/bar.BAZ/index'); + $this->assertEquals('/foo/bar/baz', Url::build('@foo/bar.BAZ/index')); + } + + public function testBuildMethod() + { + Route::get('blog/:id', '\app\index\controller\blog@read'); + $this->assertEquals('/blog/10.html', Url::build('\app\index\controller\blog@read', 'id=10', 'html')); + } + + public function testBuildRoute() + { + Route::get('blog/:id', 'index/blog'); + Config::set('url_html_suffix', 'shtml'); + $this->assertNotEquals('/blog/10.html', Url::build('/blog/10')); + $this->assertEquals('/blog/10.shtml', Url::build('/blog/10')); + } + + public function testBuildNameRoute() + { + Route::get(['name', 'blog/:id'], 'index/blog'); + $this->assertEquals([['blog/:id', ['id' => 1], null, null]], Route::name('name')); + Config::set('url_html_suffix', 'shtml'); + $this->assertEquals('/blog/10.shtml', Url::build('name?id=10')); + } + + public function testBuildAnchor() + { + Route::get('blog/:id', 'index/blog'); + Config::set('url_html_suffix', 'shtml'); + $this->assertEquals('/blog/10.shtml#detail', Url::build('index/blog#detail', 'id=10')); + + Config::set('url_common_param', true); + $this->assertEquals('/blog/10.shtml?foo=bar#detail', Url::build('index/blog#detail', "id=10&foo=bar")); + } + + public function testBuildDomain() + { + Config::set('url_domain_deploy', true); + Route::domain('subdomain.thinkphp.cn', 'admin'); + $this->assertEquals('http://subdomain.thinkphp.cn/blog/10.html', Url::build('/blog/10')); + Route::domain('subdomain.thinkphp.cn', [ + 'hello/:name' => 'index/hello', + ]); + $this->assertEquals('http://subdomain.thinkphp.cn/hello/thinkphp.html', Url::build('index/hello?name=thinkphp')); + } + + public function testRoot() + { + Config::set('url_domain_deploy', false); + Config::set('url_common_param', false); + Url::root('/index.php'); + Route::get('blog/:id', 'index/blog/read'); + Config::set('url_html_suffix', 'shtml'); + $this->assertEquals('/index.php/blog/10/name/thinkphp.shtml', Url::build('index/blog/read?id=10&name=thinkphp')); + + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/validateTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/validateTest.php new file mode 100644 index 0000000..b5f4333 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/validateTest.php @@ -0,0 +1,200 @@ + +// +---------------------------------------------------------------------- + +/** + * Validate类测试 + */ + +namespace tests\thinkphp\library\think; + +use think\File; +use think\Validate; + +class validateTest extends \PHPUnit_Framework_TestCase +{ + + public function testCheck() + { + $rule = [ + 'name' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ]; + $msg = [ + 'name.require' => '名称必须', + 'name.max' => '名称最多不能超过25个字符', + 'age.number' => '年龄必须是数字', + 'age.between' => '年龄只能在1-120之间', + 'email' => '邮箱格式错误', + ]; + $data = [ + 'name' => 'thinkphp', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + ]; + $validate = new Validate($rule, $msg); + $result = $validate->check($data); + $this->assertEquals(true, $result); + } + + public function testRule() + { + $rule = [ + 'name' => 'require|method:get|alphaNum|max:25|expire:2016-1-1,2026-1-1', + 'account' => 'requireIf:name,thinkphp|alphaDash|min:4|length:4,30', + 'age' => 'number|between:1,120', + 'email' => 'requireWith:name|email', + 'host' => 'activeUrl|activeUrl:A', + 'url' => 'url', + 'ip' => 'ip|ip:ipv4', + 'score' => 'float|gt:60|notBetween:90,100|notIn:70,80|lt:100|elt:100|egt:60', + 'status' => 'integer|in:0,1,2', + 'begin_time' => 'after:2016-3-18|beforeWith:end_time', + 'end_time' => 'before:2016-10-01|afterWith:begin_time', + 'info' => 'require|array|length:4|max:5|min:2', + 'info.name' => 'require|length:8|alpha|same:thinkphp', + 'value' => 'same:100|different:status', + 'bool' => 'boolean', + 'title' => 'chsAlpha', + 'city' => 'chs', + 'nickname' => 'chsDash', + 'aliasname' => 'chsAlphaNum', + 'file' => 'file|fileSize:20480', + 'image' => 'image|fileMime:image/png|image:80,80,png', + 'test' => 'test', + ]; + $data = [ + 'name' => 'thinkphp', + 'account' => 'liuchen', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + 'host' => 'thinkphp.cn', + 'url' => 'http://thinkphp.cn/topic', + 'ip' => '114.34.54.5', + 'score' => '89.15', + 'status' => 1, + 'begin_time' => '2016-3-20', + 'end_time' => '2016-5-1', + 'info' => [1, 2, 3, 'name' => 'thinkphp'], + 'zip' => '200000', + 'date' => '16-3-8', + 'ok' => 'yes', + 'value' => 100, + 'bool' => true, + 'title' => '流年ThinkPHP', + 'city' => '上海', + 'nickname' => '流年ThinkPHP_2016', + 'aliasname' => '流年Think2016', + 'file' => new File(THINK_PATH . 'base.php'), + 'image' => new File(THINK_PATH . 'logo.png'), + 'test' => 'test', + ]; + $validate = new Validate($rule); + $validate->extend('test', function ($value) {return 'test' == $value ? true : false;}); + $validate->rule('zip', '/^\d{6}$/'); + $validate->rule([ + 'ok' => 'require|accepted', + 'date' => 'date|dateFormat:y-m-d', + ]); + $result = $validate->batch()->check($data); + $this->assertEquals(true, $result); + } + + public function testMsg() + { + $validate = new Validate(); + $validate->message('name.require', '名称必须'); + $validate->message([ + 'name.require' => '名称必须', + 'name.max' => '名称最多不能超过25个字符', + 'age.number' => '年龄必须是数字', + 'age.between' => '年龄只能在1-120之间', + 'email' => '邮箱格式错误', + ]); + } + + public function testMake() + { + $rule = [ + 'name' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ]; + $msg = [ + 'name.require' => '名称必须', + 'name.max' => '名称最多不能超过25个字符', + 'age.number' => '年龄必须是数字', + 'age.between' => '年龄只能在1-120之间', + 'email' => '邮箱格式错误', + ]; + $validate = Validate::make($rule, $msg); + } + + public function testExtend() + { + $validate = new Validate(['name' => 'check:1']); + $validate->extend('check', function ($value, $rule) {return $rule == $value ? true : false;}); + $validate->extend(['check' => function ($value, $rule) {return $rule == $value ? true : false;}]); + $data = ['name' => 1]; + $result = $validate->check($data); + $this->assertEquals(true, $result); + } + + public function testScene() + { + $rule = [ + 'name' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ]; + $msg = [ + 'name.require' => '名称必须', + 'name.max' => '名称最多不能超过25个字符', + 'age.number' => '年龄必须是数字', + 'age.between' => '年龄只能在1-120之间', + 'email' => '邮箱格式错误', + ]; + $data = [ + 'name' => 'thinkphp', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + ]; + $validate = new Validate($rule); + $validate->scene(['edit' => ['name', 'age']]); + $validate->scene('edit', ['name', 'age']); + $validate->scene('edit'); + $result = $validate->check($data); + $this->assertEquals(true, $result); + } + + public function testSetTypeMsg() + { + $rule = [ + 'name|名称' => 'require|max:25', + 'age' => 'number|between:1,120', + 'email' => 'email', + ['sex', 'in:1,2', '性别错误'], + ]; + $data = [ + 'name' => '', + 'age' => 10, + 'email' => 'thinkphp@qq.com', + 'sex' => '3', + ]; + $validate = new Validate($rule); + $validate->setTypeMsg('require', ':attribute必须'); + $validate->setTypeMsg(['require' => ':attribute必须']); + $result = $validate->batch()->check($data); + $this->assertFalse($result); + $this->assertEquals(['name' => '名称必须', 'sex' => '性别错误'], $validate->getError()); + } + +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/view/driver/.gitignore b/vendor/topthink/framework/tests/thinkphp/library/think/view/driver/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/view/driver/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/view/theme/index/template.html b/vendor/topthink/framework/tests/thinkphp/library/think/view/theme/index/template.html new file mode 100644 index 0000000..5f4548b --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/view/theme/index/template.html @@ -0,0 +1,14 @@ + + + + + + + + + Document + + + + + diff --git a/vendor/topthink/framework/tests/thinkphp/library/think/viewTest.php b/vendor/topthink/framework/tests/thinkphp/library/think/viewTest.php new file mode 100644 index 0000000..5bb7de1 --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/think/viewTest.php @@ -0,0 +1,76 @@ + +// +---------------------------------------------------------------------- + +/** + * view测试 + * @author mahuan + */ + +namespace tests\thinkphp\library\think; + +class viewTest extends \PHPUnit_Framework_TestCase +{ + + /** + * 句柄测试 + * @return mixed + * @access public + */ + public function testGetInstance() + { + \think\Cookie::get('a'); + $view_instance = \think\View::instance(); + $this->assertInstanceOf('\think\view', $view_instance, 'instance方法返回错误'); + } + + /** + * 测试变量赋值 + * @return mixed + * @access public + */ + public function testAssign() + { + $view_instance = \think\View::instance(); + $view_instance->key = 'value'; + $this->assertTrue(isset($view_instance->key)); + $this->assertEquals('value', $view_instance->key); + $data = $view_instance->assign(array('key' => 'value')); + $data = $view_instance->assign('key2', 'value2'); + //测试私有属性 + $expect_data = array('key' => 'value', 'key2' => 'value2'); + $this->assertAttributeEquals($expect_data, 'data', $view_instance); + } + + /** + * 测试引擎设置 + * @return mixed + * @access public + */ + public function testEngine() + { + $view_instance = \think\View::instance(); + $data = $view_instance->engine('php'); + $data = $view_instance->engine(['type' => 'php', 'view_path' => '', 'view_suffix' => '.php', 'view_depr' => DS]); + $php_engine = new \think\view\driver\Php(['view_path' => '', 'view_suffix' => '.php', 'view_depr' => DS]); + $this->assertAttributeEquals($php_engine, 'engine', $view_instance); + //测试模板引擎驱动 + $data = $view_instance->engine(['type' => 'think', 'view_path' => '', 'view_suffix' => '.html', 'view_depr' => DS]); + $think_engine = new \think\view\driver\Think(['view_path' => '', 'view_suffix' => '.html', 'view_depr' => DS]); + $this->assertAttributeEquals($think_engine, 'engine', $view_instance); + } + + public function testReplace() + { + $view_instance = \think\View::instance(); + $view_instance->replace('string', 'replace')->display('string'); + } + +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/traits/controller/jumpTest.php b/vendor/topthink/framework/tests/thinkphp/library/traits/controller/jumpTest.php new file mode 100644 index 0000000..47e9dbf --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/traits/controller/jumpTest.php @@ -0,0 +1,339 @@ +testClass = new testClassWithJump(); + $this->request = Request::create(''); + + $this->originServerData = Request::instance()->server(); + } + + public function tearDown() + { + Request::instance()->server($this->originServerData); + } + + /** + * @dataProvider provideTestSuccess + */ + public function testSuccess($arguments, $expected, array $extra) + { + if (isset($extra['server'])) { + $this->request->server($extra['server']); + } + + $mock = $this->getMockBuilder(get_class($this->testClass))->setMethods(['getResponseType'])->getMock(); + $mock->expects($this->any())->method('getResponseType')->willReturn($extra['return']); + + try { + call_user_func_array([$mock, 'success'], $arguments); + $this->setExpectedException('\think\exception\HttpResponseException'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\exception\HttpResponseException', $e); + + /** @var Response $response */ + $response = $e->getResponse(); + + $this->assertInstanceOf('\Think\Response', $response); + $this->assertEquals($expected['header'], $response->getHeader()); + $this->assertEquals($expected['data'], $response->getData()); + } + } + + /** + * @dataProvider provideTestError + */ + public function testError($arguments, $expected, array $extra) + { + if (isset($extra['server'])) { + $this->request->server($extra['server']); + } + + $mock = $this->getMockBuilder(get_class($this->testClass))->setMethods(['getResponseType'])->getMock(); + $mock->expects($this->any())->method('getResponseType')->willReturn($extra['return']); + + try { + call_user_func_array([$mock, 'error'], $arguments); + $this->setExpectedException('\think\exception\HttpResponseException'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\exception\HttpResponseException', $e); + + /** @var Response $response */ + $response = $e->getResponse(); + + $this->assertInstanceOf('\Think\Response', $response); + $this->assertEquals($expected['header'], $response->getHeader()); + $this->assertEquals($expected['data'], $response->getData()); + } + } + + /** + * @dataProvider provideTestResult + */ + public function testResult($arguments, $expected, array $extra) + { + if (isset($extra['server'])) { + $this->request->server($extra['server']); + } + + $mock = $this->getMockBuilder(get_class($this->testClass))->setMethods(['getResponseType'])->getMock(); + $mock->expects($this->any())->method('getResponseType')->willReturn($extra['return']); + + try { + call_user_func_array([$mock, 'result'], $arguments); + $this->setExpectedException('\think\exception\HttpResponseException'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\exception\HttpResponseException', $e); + + /** @var Response $response */ + $response = $e->getResponse(); + + $this->assertInstanceOf('\Think\Response', $response); + $this->assertEquals($expected['header'], $response->getHeader()); + $this->assertEquals($expected['data'], $response->getData()); + } + } + + /** + * @dataProvider provideTestRedirect + */ + public function testRedirect($arguments, $expected) + { + try { + call_user_func_array([$this->testClass, 'redirect'], $arguments); + $this->setExpectedException('\think\exception\HttpResponseException'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\exception\HttpResponseException', $e); + + /** @var Redirect $response */ + $response = $e->getResponse(); + + $this->assertInstanceOf('\think\response\Redirect', $response); + $this->assertEquals($expected['url'], $response->getTargetUrl()); + $this->assertEquals($expected['code'], $response->getCode()); + } + } + + public function testGetResponseType() + { + Request::instance()->server(['HTTP_X_REQUESTED_WITH' => null]); + $this->assertEquals('html', $this->testClass->getResponseType()); + + Request::instance()->server(['HTTP_X_REQUESTED_WITH' => true]); + $this->assertEquals('html', $this->testClass->getResponseType()); + + Request::instance()->server(['HTTP_X_REQUESTED_WITH' => 'xmlhttprequest']); + $this->assertEquals('json', $this->testClass->getResponseType()); + } + + public function provideTestSuccess() + { + $provideData = []; + + $arguments = ['', null, '', 3, []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=utf-8' + ], + 'data' => View::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch(Config::get('dispatch_error_tmpl'), [ + 'code' => 1, + 'msg' => '', + 'data' => '', + 'url' => '/index.php/', + 'wait' => 3, + ]) + ]; + $provideData[] = [$arguments, $expected, ['server' => ['HTTP_REFERER' => null], 'return' => 'html']]; + + $arguments = ['thinkphp', null, ['foo'], 4, ['Power-By' => 'thinkphp', 'Content-Type' => 'text/html; charset=gbk']]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=gbk', + 'Power-By' => 'thinkphp' + ], + 'data' => View::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch(Config::get('dispatch_error_tmpl'), [ + 'code' => 1, + 'msg' => 'thinkphp', + 'data' => ['foo'], + 'url' => 'http://www.thinkphp.cn', + 'wait' => 4, + ]) + ]; + $provideData[] = [$arguments, $expected, ['server' => ['HTTP_REFERER' => 'http://www.thinkphp.cn'], 'return' => 'html']]; + + $arguments = ['thinkphp', 'index', ['foo'], 5, []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'application/json; charset=utf-8' + ], + 'data' => [ + 'code' => 1, + 'msg' => 'thinkphp', + 'data' => ['foo'], + 'url' => '/index.php/index.html', + 'wait' => 5, + ] + ]; + $provideData[] = [$arguments, $expected, ['server' => ['HTTP_REFERER' => null], 'return' => 'json']]; + + return $provideData; + } + + public function provideTestError() + { + $provideData = []; + + $arguments = ['', null, '', 3, []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=utf-8' + ], + 'data' => View::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch(Config::get('dispatch_error_tmpl'), [ + 'code' => 0, + 'msg' => '', + 'data' => '', + 'url' => 'javascript:history.back(-1);', + 'wait' => 3, + ]) + ]; + $provideData[] = [$arguments, $expected, ['return' => 'html']]; + + $arguments = ['thinkphp', 'http://www.thinkphp.cn', ['foo'], 4, ['Power-By' => 'thinkphp', 'Content-Type' => 'text/html; charset=gbk']]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=gbk', + 'Power-By' => 'thinkphp' + ], + 'data' => View::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch(Config::get('dispatch_error_tmpl'), [ + 'code' => 0, + 'msg' => 'thinkphp', + 'data' => ['foo'], + 'url' => 'http://www.thinkphp.cn', + 'wait' => 4, + ]) + ]; + $provideData[] = [$arguments, $expected, ['return' => 'html']]; + + $arguments = ['thinkphp', '', ['foo'], 5, []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'application/json; charset=utf-8' + ], + 'data' => [ + 'code' => 0, + 'msg' => 'thinkphp', + 'data' => ['foo'], + 'url' => '', + 'wait' => 5, + ] + ]; + $provideData[] = [$arguments, $expected, ['return' => 'json']]; + + return $provideData; + } + + public function provideTestResult() + { + $provideData = []; + + $arguments = [null, 0, '', '', []]; + $expected = [ + 'header' => [ + 'Content-Type' => 'text/html; charset=utf-8' + ], + 'data' => [ + 'code' => 0, + 'msg' => '', + 'time' => Request::create('')->server('REQUEST_TIME'), + 'data' => null, + ] + ]; + $provideData[] = [$arguments, $expected, ['return' => 'html']]; + + $arguments = [['foo'], 200, 'thinkphp', 'json', ['Power-By' => 'thinkphp']]; + $expected = [ + 'header' => [ + 'Power-By' => 'thinkphp', + 'Content-Type' => 'application/json; charset=utf-8' + ], + 'data' => [ + 'code' => 200, + 'msg' => 'thinkphp', + 'time' => 1000, + 'data' => ['foo'], + ] + ]; + + $provideData[] = [$arguments, $expected, ['server' => ['REQUEST_TIME' => 1000], 'return' => 'json']]; + + return $provideData; + } + + public function provideTestRedirect() + { + $provideData = []; + + $arguments = ['', [], 302, []]; + $expected = [ + 'code'=> 302, + 'url' => '/index.php/' + ]; + $provideData[] = [$arguments, $expected, []]; + + $arguments = ['index', 302, null, []]; + $expected = [ + 'code'=> 302, + 'url' => '/index.php/index.html' + ]; + $provideData[] = [$arguments, $expected, []]; + + $arguments = ['http://www.thinkphp.cn', 301, 302, []]; + $expected = [ + 'code'=> 301, + 'url' => 'http://www.thinkphp.cn' + ]; + $provideData[] = [$arguments, $expected, []]; + + return $provideData; + } +} + +class testClassWithJump +{ + use Jump { + success as public; + error as public; + result as public; + redirect as public; + getResponseType as public; + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/traits/model/softDeleteTest.php b/vendor/topthink/framework/tests/thinkphp/library/traits/model/softDeleteTest.php new file mode 100644 index 0000000..8410efa --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/traits/model/softDeleteTest.php @@ -0,0 +1,179 @@ +connection; + + $sql[] = <<execute($one); + } + } + + public function testTrashed() + { + /** @var testClassWithSoftDelete[] $selections */ + $selections = testClassWithSoftDelete::withTrashed()->select(); + + $this->assertFalse($selections[0]->trashed()); + $this->assertTrue($selections[1]->trashed()); + $this->assertTrue($selections[2]->trashed()); + } + + public function testDefaultTrashed() + { + $this->assertCount(3, testClassWithSoftDelete::all()); + } + + public function testWithTrashed() + { + $this->assertCount(5, testClassWithSoftDelete::withTrashed()->select()); + } + + public function testOnlyTrashed() + { + $this->assertCount(2, testClassWithSoftDelete::onlyTrashed()->select()); + } + + public function testSoftDelete() + { + $this->assertEquals(1, testClassWithSoftDelete::get(1)->delete()); + $this->assertNotNull(testClassWithSoftDelete::withTrashed()->find(1)->getData('delete_time')); + } + + public function testForceDelete() + { + $this->assertEquals(1, testClassWithSoftDelete::get(1)->delete(true)); + $this->assertNull(testClassWithSoftDelete::get(1)); + } + + public function testSoftDestroy() + { + $this->assertEquals(5, testClassWithSoftDelete::destroy([1, 2, 3, 4, 5, 6])); + $this->assertNotNull(testClassWithSoftDelete::withTrashed()->find(2)->getData('delete_time')); + $this->assertNotEquals(self::TEST_TIME, testClassWithSoftDelete::withTrashed()->find(2)->getData('delete_time')); + $this->assertNotEquals(self::TEST_TIME, testClassWithSoftDelete::withTrashed()->find(3)->getData('delete_time')); + $this->assertNotNull(testClassWithSoftDelete::withTrashed()->find(4)->getData('delete_time')); + $this->assertNotNull(testClassWithSoftDelete::withTrashed()->find(5)->getData('delete_time')); + } + + public function testForceDestroy() + { + $this->assertEquals(5, testClassWithSoftDelete::destroy([1, 2, 3, 4, 5, 6], true)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(1)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(2)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(3)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(4)); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(5)); + } + + public function testRestore() + { + /** @var testClassWithSoftDelete[] $selections */ + $selections = testClassWithSoftDelete::withTrashed()->select(); + + $this->assertEquals(0, $selections[0]->restore()); + $this->assertEquals(1, $selections[1]->restore()); + $this->assertEquals(1, $selections[2]->restore()); + $this->assertEquals(0, $selections[3]->restore()); + $this->assertEquals(0, $selections[4]->restore()); + + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(1)->getData('delete_time')); + $this->assertNull(testClassWithSoftDelete::withTrashed()->find(2)->getData('delete_time')); + } + + public function testGetDeleteTimeField() + { + $testClass = new testClassWithSoftDelete(); + + $this->assertEquals('delete_time', $testClass->getDeleteTimeField()); + + $testClass->deleteTime = 'create_time'; + $this->assertEquals('create_time', $testClass->getDeleteTimeField()); + + $testClass->deleteTime = 'test.create_time'; + $this->assertEquals('create_time', $testClass->getDeleteTimeField()); + + $testClass->deleteTime = 'create_time'; + $this->assertEquals('__TABLE__.create_time', $testClass->getDeleteTimeField(true)); + } +} + +class testClassWithSoftDelete extends Model +{ + public $table = 'tp_soft_delete'; + + public $deleteTime = 'delete_time'; + + public $connection = [ + // 数据库类型 + 'type' => 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'test', + // 用户名 + 'username' => 'root', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 array 数组 collection Collection对象 + 'resultset_type' => 'array', + // 是否自动写入时间戳字段 + 'auto_timestamp' => false, + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ]; + + use SoftDelete { + getDeleteTimeField as public; + } +} diff --git a/vendor/topthink/framework/tests/thinkphp/library/traits/think/instanceTest.php b/vendor/topthink/framework/tests/thinkphp/library/traits/think/instanceTest.php new file mode 100644 index 0000000..f93f22b --- /dev/null +++ b/vendor/topthink/framework/tests/thinkphp/library/traits/think/instanceTest.php @@ -0,0 +1,60 @@ +assertInstanceOf('\tests\thinkphp\library\traits\think\InstanceTestFather', $father); + $this->assertEquals([], $father->options); + + $son = InstanceTestFather::instance(['son']); + $this->assertSame($father, $son); + } + + public function testCallStatic() + { + $father = InstanceTestFather::instance(); + $this->assertEquals([], $father->options); + + $this->assertEquals($father::__protectedStaticFunc(['thinkphp']), 'protectedStaticFunc["thinkphp"]'); + + try { + $father::_protectedStaticFunc(); + $this->setExpectedException('\think\Exception'); + } catch (\Exception $e) { + $this->assertInstanceOf('\think\Exception', $e); + } + } + + protected function tearDown() + { + call_user_func(\Closure::bind(function () { + InstanceTestFather::$instance = null; + }, null, '\tests\thinkphp\library\traits\think\InstanceTestFather')); + } +} + +class InstanceTestFather +{ + use Instance; + + public $options = null; + + public function __construct($options) + { + $this->options = $options; + } + + protected static function _protectedStaticFunc($params) + { + return 'protectedStaticFunc' . json_encode($params); + } +} + +class InstanceTestSon extends InstanceTestFather +{ +} diff --git a/vendor/topthink/framework/tpl/default_index.tpl b/vendor/topthink/framework/tpl/default_index.tpl new file mode 100644 index 0000000..8538b4d --- /dev/null +++ b/vendor/topthink/framework/tpl/default_index.tpl @@ -0,0 +1,10 @@ +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

      :)

      ThinkPHP V5
      十年磨一剑 - 为API开发设计的高性能框架

      [ V5.0 版本由 七牛云 独家赞助发布 ]
      '; + } +} diff --git a/vendor/topthink/framework/tpl/dispatch_jump.tpl b/vendor/topthink/framework/tpl/dispatch_jump.tpl new file mode 100644 index 0000000..583376b --- /dev/null +++ b/vendor/topthink/framework/tpl/dispatch_jump.tpl @@ -0,0 +1,49 @@ +{__NOLAYOUT__} + + + + + 跳转提示 + + + +
      + + +

      :)

      +

      + + +

      :(

      +

      + + +

      +

      + 页面自动 跳转 等待时间: +

      +
      + + + diff --git a/vendor/topthink/framework/tpl/page_trace.tpl b/vendor/topthink/framework/tpl/page_trace.tpl new file mode 100644 index 0000000..7c5df6f --- /dev/null +++ b/vendor/topthink/framework/tpl/page_trace.tpl @@ -0,0 +1,71 @@ +
      + + +
      +
      +
      + +
      + + diff --git a/vendor/topthink/framework/tpl/think_exception.tpl b/vendor/topthink/framework/tpl/think_exception.tpl new file mode 100644 index 0000000..21bbafc --- /dev/null +++ b/vendor/topthink/framework/tpl/think_exception.tpl @@ -0,0 +1,537 @@ +'.end($names).''; + } + } + + if(!function_exists('parse_file')){ + function parse_file($file, $line) + { + return ''.basename($file)." line {$line}".''; + } + } + + if(!function_exists('parse_args')){ + function parse_args($args) + { + $result = []; + + foreach ($args as $key => $item) { + switch (true) { + case is_object($item): + $value = sprintf('object(%s)', parse_class(get_class($item))); + break; + case is_array($item): + if(count($item) > 3){ + $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); + } else { + $value = sprintf('[%s]', parse_args($item)); + } + break; + case is_string($item): + if(strlen($item) > 20){ + $value = sprintf( + '\'%s...\'', + htmlentities($item), + htmlentities(substr($item, 0, 20)) + ); + } else { + $value = sprintf("'%s'", htmlentities($item)); + } + break; + case is_int($item): + case is_float($item): + $value = $item; + break; + case is_null($item): + $value = 'null'; + break; + case is_bool($item): + $value = '' . ($item ? 'true' : 'false') . ''; + break; + case is_resource($item): + $value = 'resource'; + break; + default: + $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); + break; + } + + $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; + } + + return implode(', ', $result); + } + } +?> + + + + + <?php echo \think\Lang::get('System Error'); ?> + + + + + +
      + +
      + +
      +
      + +
      +
      +

      [

      +
      +

      +
      + +
      + +
      +
        $value) { ?>
      +
      + +
      +

      Call Stack

      +
        +
      1. + +
      2. + +
      3. + +
      +
      +
      + +
      + +

      + +
      + + + +
      +

      Exception Datas

      + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
      empty
      + +
      + +
      + + + +
      +

      Environment Variables

      + $value) { ?> +
      + +
      +
      +
      empty
      +
      + +

      +
      + $val) { ?> +
      +
      +
      + +
      +
      + +
      + +
      + +
      + + + + + + + + diff --git a/vendor/topthink/think-installer/.gitignore b/vendor/topthink/think-installer/.gitignore new file mode 100644 index 0000000..8f4c02d --- /dev/null +++ b/vendor/topthink/think-installer/.gitignore @@ -0,0 +1,3 @@ +/.idea +composer.lock +/vendor \ No newline at end of file diff --git a/vendor/topthink/think-installer/composer.json b/vendor/topthink/think-installer/composer.json new file mode 100644 index 0000000..08bc72b --- /dev/null +++ b/vendor/topthink/think-installer/composer.json @@ -0,0 +1,25 @@ +{ + "name": "topthink/think-installer", + "type": "composer-plugin", + "require": { + "composer-plugin-api": "^1.0||^2.0" + }, + "require-dev": { + "composer/composer": "^1.0||^2.0" + }, + "license": "Apache-2.0", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "autoload": { + "psr-4": { + "think\\composer\\": "src" + } + }, + "extra": { + "class": "think\\composer\\Plugin" + } +} diff --git a/vendor/topthink/think-installer/src/LibraryInstaller.php b/vendor/topthink/think-installer/src/LibraryInstaller.php new file mode 100644 index 0000000..45c05bc --- /dev/null +++ b/vendor/topthink/think-installer/src/LibraryInstaller.php @@ -0,0 +1,28 @@ +makePromise(parent::install($repo, $package)); + } + + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + return $this->makePromise(parent::update($repo, $initial, $target)); + } + + protected function makePromise($promise) + { + if ($promise instanceof PromiseInterface) { + return $promise; + } + return new Promise(); + } +} diff --git a/vendor/topthink/think-installer/src/Plugin.php b/vendor/topthink/think-installer/src/Plugin.php new file mode 100644 index 0000000..4415232 --- /dev/null +++ b/vendor/topthink/think-installer/src/Plugin.php @@ -0,0 +1,34 @@ +getInstallationManager(); + + //框架核心 + $manager->addInstaller(new ThinkFramework($io, $composer)); + + //单元测试 + $manager->addInstaller(new ThinkTesting($io, $composer)); + + //扩展 + $manager->addInstaller(new ThinkExtend($io, $composer)); + } + + public function deactivate(Composer $composer, IOInterface $io) + { + + } + + public function uninstall(Composer $composer, IOInterface $io) + { + + } +} diff --git a/vendor/topthink/think-installer/src/Promise.php b/vendor/topthink/think-installer/src/Promise.php new file mode 100644 index 0000000..6bfed5d --- /dev/null +++ b/vendor/topthink/think-installer/src/Promise.php @@ -0,0 +1,11 @@ + +// +---------------------------------------------------------------------- + +namespace think\composer; + +use Composer\Package\PackageInterface; +use Composer\Repository\InstalledRepositoryInterface; + +class ThinkExtend extends LibraryInstaller +{ + + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + return parent::install($repo, $package)->then(function () use ($package) { + $this->copyExtraFiles($package); + }); + } + + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + return parent::update($repo, $initial, $target)->then(function () use ($target) { + $this->copyExtraFiles($target); + }); + } + + protected function copyExtraFiles(PackageInterface $package) + { + if ($this->composer->getPackage()->getType() == 'project') { + + $extra = $package->getExtra(); + + if (!empty($extra['think-config'])) { + + $composerExtra = $this->composer->getPackage()->getExtra(); + + $appDir = !empty($composerExtra['app-path']) ? $composerExtra['app-path'] : 'application'; + + if (is_dir($appDir)) { + + $extraDir = $appDir . DIRECTORY_SEPARATOR . 'extra'; + $this->filesystem->ensureDirectoryExists($extraDir); + + //配置文件 + foreach ((array) $extra['think-config'] as $name => $config) { + $target = $extraDir . DIRECTORY_SEPARATOR . $name . '.php'; + $source = $this->getInstallPath($package) . DIRECTORY_SEPARATOR . $config; + + if (is_file($target)) { + $this->io->write("File {$target} exist!"); + continue; + } + + if (!is_file($source)) { + $this->io->write("File {$target} not exist!"); + continue; + } + + copy($source, $target); + } + } + } + } + } + + public function supports($packageType) + { + return 'think-extend' === $packageType; + } +} diff --git a/vendor/topthink/think-installer/src/ThinkFramework.php b/vendor/topthink/think-installer/src/ThinkFramework.php new file mode 100644 index 0000000..66bd47e --- /dev/null +++ b/vendor/topthink/think-installer/src/ThinkFramework.php @@ -0,0 +1,62 @@ +then(function () use ($package) { + if ($this->composer->getPackage() + ->getType() == 'project' && $package->getInstallationSource() != 'source') { + //remove tests dir + $this->filesystem->removeDirectory($this->getInstallPath($package) . DIRECTORY_SEPARATOR . 'tests'); + } + }); + } + + /** + * {@inheritDoc} + */ + public function getInstallPath(PackageInterface $package) + { + if ('topthink/framework' !== $package->getPrettyName()) { + throw new InvalidArgumentException('Unable to install this library!'); + } + + if ($this->composer->getPackage()->getType() !== 'project') { + return parent::getInstallPath($package); + } + + if ($this->composer->getPackage()) { + $extra = $this->composer->getPackage()->getExtra(); + if (!empty($extra['think-path'])) { + return $extra['think-path']; + } + } + + return 'thinkphp'; + } + + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + return parent::update($repo, $initial, $target)->then(function () use ($target) { + if ($this->composer->getPackage()->getType() == 'project' && $target->getInstallationSource() != 'source') { + //remove tests dir + $this->filesystem->removeDirectory($this->getInstallPath($target) . DIRECTORY_SEPARATOR . 'tests'); + } + }); + } + + /** + * {@inheritDoc} + */ + public function supports($packageType) + { + return 'think-framework' === $packageType; + } +} diff --git a/vendor/topthink/think-installer/src/ThinkTesting.php b/vendor/topthink/think-installer/src/ThinkTesting.php new file mode 100644 index 0000000..bbaf04d --- /dev/null +++ b/vendor/topthink/think-installer/src/ThinkTesting.php @@ -0,0 +1,64 @@ + +// +---------------------------------------------------------------------- + +namespace think\composer; + +use Composer\Package\PackageInterface; +use Composer\Repository\InstalledRepositoryInterface; +use InvalidArgumentException; + +class ThinkTesting extends LibraryInstaller +{ + /** + * {@inheritDoc} + */ + public function getInstallPath(PackageInterface $package) + { + if ('topthink/think-testing' !== $package->getPrettyName()) { + throw new InvalidArgumentException('Unable to install this library!'); + } + + return parent::getInstallPath($package); + } + + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + parent::install($repo, $package)->then(function () use ($package) { + $this->copyTestDir($package); + }); + } + + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + return parent::update($repo, $initial, $target)->then(function () use ($target) { + $this->copyTestDir($target); + }); + } + + private function copyTestDir(PackageInterface $package) + { + $appDir = dirname($this->vendorDir); + $source = $this->getInstallPath($package) . DIRECTORY_SEPARATOR . 'example'; + if (!is_file($appDir . DIRECTORY_SEPARATOR . 'phpunit.xml')) { + $this->filesystem->copyThenRemove($source, $appDir); + } else { + $this->filesystem->removeDirectoryPhp($source); + } + } + + /** + * {@inheritDoc} + */ + public function supports($packageType) + { + return 'think-testing' === $packageType; + } +}