From 9c6b46a69d7cff6922cbbaced837a8e936d13389 Mon Sep 17 00:00:00 2001 From: x Date: Mon, 7 Dec 2015 21:09:01 +0800 Subject: [PATCH] QueryList 3.0 --- QueryList.class.php | 267 +- demo/Searcher.class.php | 152 - demo/demo.php | 68 - demo/thanks.png | Bin 8815 -> 0 bytes demo/一个完整的DEMO项目.zip | Bin 201147 -> 0 bytes extensions/AQuery.php | 19 + extensions/Login.php | 44 + extensions/Multi.php | 73 + extensions/Request.php | 37 + extensions/vendors/CurlMulti.php | 698 +++ .../vendors/Http.php | 0 phpQuery/phpQuery.php | 4372 ++++++++++++++++- phpQuery/phpQuery/Callback.php | 152 - phpQuery/phpQuery/DOMDocumentWrapper.php | 677 --- phpQuery/phpQuery/DOMEvent.php | 107 - phpQuery/phpQuery/Zend/Exception.php | 30 - phpQuery/phpQuery/Zend/Http/Client.php | 1186 ----- .../Zend/Http/Client/Adapter/Exception.php | 33 - .../Zend/Http/Client/Adapter/Interface.php | 78 - .../Zend/Http/Client/Adapter/Proxy.php | 268 - .../Zend/Http/Client/Adapter/Socket.php | 332 -- .../Zend/Http/Client/Adapter/Test.php | 193 - .../phpQuery/Zend/Http/Client/Exception.php | 33 - phpQuery/phpQuery/Zend/Http/Cookie.php | 327 -- phpQuery/phpQuery/Zend/Http/CookieJar.php | 350 -- phpQuery/phpQuery/Zend/Http/Exception.php | 33 - phpQuery/phpQuery/Zend/Http/Response.php | 625 --- phpQuery/phpQuery/Zend/Json/Decoder.php | 457 -- phpQuery/phpQuery/Zend/Json/Encoder.php | 431 -- phpQuery/phpQuery/Zend/Json/Exception.php | 36 - phpQuery/phpQuery/Zend/Loader.php | 258 - phpQuery/phpQuery/Zend/Registry.php | 195 - phpQuery/phpQuery/Zend/Uri.php | 164 - phpQuery/phpQuery/Zend/Uri/Exception.php | 37 - phpQuery/phpQuery/Zend/Uri/Http.php | 702 --- phpQuery/phpQuery/Zend/Validate/Abstract.php | 348 -- phpQuery/phpQuery/Zend/Validate/Alnum.php | 120 - phpQuery/phpQuery/Zend/Validate/Alpha.php | 120 - phpQuery/phpQuery/Zend/Validate/Barcode.php | 99 - .../phpQuery/Zend/Validate/Barcode/Ean13.php | 100 - .../phpQuery/Zend/Validate/Barcode/UpcA.php | 100 - phpQuery/phpQuery/Zend/Validate/Between.php | 200 - phpQuery/phpQuery/Zend/Validate/Ccnum.php | 111 - phpQuery/phpQuery/Zend/Validate/Date.php | 250 - phpQuery/phpQuery/Zend/Validate/Digits.php | 100 - .../phpQuery/Zend/Validate/EmailAddress.php | 245 - phpQuery/phpQuery/Zend/Validate/Exception.php | 37 - .../phpQuery/Zend/Validate/File/Count.php | 229 - .../phpQuery/Zend/Validate/File/Exists.php | 192 - .../phpQuery/Zend/Validate/File/Extension.php | 204 - .../phpQuery/Zend/Validate/File/FilesSize.php | 156 - .../phpQuery/Zend/Validate/File/ImageSize.php | 335 -- .../phpQuery/Zend/Validate/File/MimeType.php | 200 - .../phpQuery/Zend/Validate/File/NotExists.php | 86 - phpQuery/phpQuery/Zend/Validate/File/Size.php | 308 -- .../phpQuery/Zend/Validate/File/Upload.php | 216 - phpQuery/phpQuery/Zend/Validate/Float.php | 75 - .../phpQuery/Zend/Validate/GreaterThan.php | 114 - phpQuery/phpQuery/Zend/Validate/Hex.php | 74 - phpQuery/phpQuery/Zend/Validate/Hostname.php | 444 -- .../phpQuery/Zend/Validate/Hostname/At.php | 50 - .../phpQuery/Zend/Validate/Hostname/Ch.php | 50 - .../phpQuery/Zend/Validate/Hostname/De.php | 58 - .../phpQuery/Zend/Validate/Hostname/Fi.php | 50 - .../phpQuery/Zend/Validate/Hostname/Hu.php | 50 - .../Zend/Validate/Hostname/Interface.php | 52 - .../phpQuery/Zend/Validate/Hostname/Li.php | 50 - .../phpQuery/Zend/Validate/Hostname/No.php | 52 - .../phpQuery/Zend/Validate/Hostname/Se.php | 50 - phpQuery/phpQuery/Zend/Validate/Identical.php | 117 - phpQuery/phpQuery/Zend/Validate/InArray.php | 138 - phpQuery/phpQuery/Zend/Validate/Int.php | 75 - phpQuery/phpQuery/Zend/Validate/Interface.php | 71 - phpQuery/phpQuery/Zend/Validate/Ip.php | 70 - phpQuery/phpQuery/Zend/Validate/LessThan.php | 113 - phpQuery/phpQuery/Zend/Validate/NotEmpty.php | 74 - phpQuery/phpQuery/Zend/Validate/Regex.php | 125 - .../phpQuery/Zend/Validate/StringLength.php | 180 - phpQuery/phpQuery/bootstrap.example.php | 14 - phpQuery/phpQuery/compat/mbstring.php | 88 - phpQuery/phpQuery/phpQueryEvents.php | 158 - phpQuery/phpQuery/phpQueryObject.php | 3180 ------------ phpQuery/phpQuery/plugins/Scripts.php | 72 - .../plugins/Scripts/__config.example.php | 10 - phpQuery/phpQuery/plugins/Scripts/example.php | 14 - .../phpQuery/plugins/Scripts/fix_webroot.php | 16 - .../phpQuery/plugins/Scripts/google_login.php | 47 - .../phpQuery/plugins/Scripts/print_source.php | 9 - .../plugins/Scripts/print_websafe.php | 13 - phpQuery/phpQuery/plugins/WebBrowser.php | 405 -- phpQuery/phpQuery/plugins/example.php | 75 - 91 files changed, 5428 insertions(+), 16995 deletions(-) delete mode 100644 demo/Searcher.class.php delete mode 100644 demo/demo.php delete mode 100644 demo/thanks.png delete mode 100644 demo/一个完整的DEMO项目.zip create mode 100644 extensions/AQuery.php create mode 100644 extensions/Login.php create mode 100644 extensions/Multi.php create mode 100644 extensions/Request.php create mode 100644 extensions/vendors/CurlMulti.php rename demo/tools/http.class.php => extensions/vendors/Http.php (100%) delete mode 100644 phpQuery/phpQuery/Callback.php delete mode 100644 phpQuery/phpQuery/DOMDocumentWrapper.php delete mode 100644 phpQuery/phpQuery/DOMEvent.php delete mode 100644 phpQuery/phpQuery/Zend/Exception.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Client.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Client/Adapter/Exception.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Client/Adapter/Interface.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Client/Adapter/Proxy.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Client/Adapter/Socket.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Client/Adapter/Test.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Client/Exception.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Cookie.php delete mode 100644 phpQuery/phpQuery/Zend/Http/CookieJar.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Exception.php delete mode 100644 phpQuery/phpQuery/Zend/Http/Response.php delete mode 100644 phpQuery/phpQuery/Zend/Json/Decoder.php delete mode 100644 phpQuery/phpQuery/Zend/Json/Encoder.php delete mode 100644 phpQuery/phpQuery/Zend/Json/Exception.php delete mode 100644 phpQuery/phpQuery/Zend/Loader.php delete mode 100644 phpQuery/phpQuery/Zend/Registry.php delete mode 100644 phpQuery/phpQuery/Zend/Uri.php delete mode 100644 phpQuery/phpQuery/Zend/Uri/Exception.php delete mode 100644 phpQuery/phpQuery/Zend/Uri/Http.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Abstract.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Alnum.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Alpha.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Barcode.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Barcode/Ean13.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Barcode/UpcA.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Between.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Ccnum.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Date.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Digits.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/EmailAddress.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Exception.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/Count.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/Exists.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/Extension.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/FilesSize.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/ImageSize.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/MimeType.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/NotExists.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/Size.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/File/Upload.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Float.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/GreaterThan.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hex.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/At.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/Ch.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/De.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/Fi.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/Hu.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/Interface.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/Li.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/No.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Hostname/Se.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Identical.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/InArray.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Int.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Interface.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Ip.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/LessThan.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/NotEmpty.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/Regex.php delete mode 100644 phpQuery/phpQuery/Zend/Validate/StringLength.php delete mode 100644 phpQuery/phpQuery/bootstrap.example.php delete mode 100644 phpQuery/phpQuery/compat/mbstring.php delete mode 100644 phpQuery/phpQuery/phpQueryEvents.php delete mode 100644 phpQuery/phpQuery/phpQueryObject.php delete mode 100644 phpQuery/phpQuery/plugins/Scripts.php delete mode 100644 phpQuery/phpQuery/plugins/Scripts/__config.example.php delete mode 100644 phpQuery/phpQuery/plugins/Scripts/example.php delete mode 100644 phpQuery/phpQuery/plugins/Scripts/fix_webroot.php delete mode 100644 phpQuery/phpQuery/plugins/Scripts/google_login.php delete mode 100644 phpQuery/phpQuery/plugins/Scripts/print_source.php delete mode 100644 phpQuery/phpQuery/plugins/Scripts/print_websafe.php delete mode 100644 phpQuery/phpQuery/plugins/WebBrowser.php delete mode 100644 phpQuery/phpQuery/plugins/example.php diff --git a/QueryList.class.php b/QueryList.class.php index 3666f2d..3d7484f 100644 --- a/QueryList.class.php +++ b/QueryList.class.php @@ -7,13 +7,13 @@ * @author Jaeger * @email 734708094@qq.com * @link http://git.oschina.net/jae/QueryList - * @version 2.2.1 + * @version 3.0 * * @example * //获取CSDN移动开发栏目下的文章列表标题 $hj = QueryList::Query('http://mobile.csdn.net/',array("title"=>array('.unit h1','text'))); -print_r($hj->jsonArr); +print_r($hj->data); //回调函数1 function callfun1($content,$key) @@ -36,26 +36,29 @@ $reg = array( 'callback'=>array('HJ','callfun2') //调用回调函数2作为全局回调函数 ); $rang = '.left'; -$hj = QueryList::Query($url,$reg,$rang,'curl'); -print_r($hj->jsonArr); +$hj = QueryList::Query($url,$reg,$rang); +print_r($hj->data); //继续获取右边相关热门文章列表的标题以及链接地址 $hj->setQuery(array('title'=>array('','text'),'url'=>array('a','href')),'#con_two_2 li'); -//输出json数据 -echo $hj->getJson(); +//输出数据 +echo $hj->getData(); */ require 'phpQuery/phpQuery.php'; class QueryList { private $regArr; - public $jsonArr; + public $data; private $regRange; - private $html; - private $outputEncoding; + public $html; + private $pqHtml; + private $outputEncoding = false; + private $inputEncoding = false; private $htmlEncoding; - private static $ql; - private function __construct() { + public static $instances; + + public function __construct() { } /** * 静态方法,访问入口 @@ -67,72 +70,114 @@ class QueryList * 【回调函数】/【全局回调函数】:可选,字符串(函数名) 或 数组(array("类名","类的静态方法")),回调函数应有俩个参数,第一个参数是选择到的内容,第二个参数是选择器数组下标,回调函数会覆盖全局回调函数 * * @param string $regRange 【块选择器】:指 先按照规则 选出 几个大块 ,然后再分别再在块里面 进行相关的选择 - * @param string $getHtmlWay 【源码获取方式】指是通过curl抓取源码,还是通过file_get_contents抓取源码 * @param string $outputEncoding【输出编码格式】指要以什么编码输出(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则不改变原字符串编码 + * @param string $inputEncoding 【输入编码格式】明确指定输入的页面编码格式(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则自动识别 + * @param bool|false $removeHead 【是否移除页面头部区域】 乱码终极解决方案 + * @return mixed */ - public static function Query($page, $regArr, $regRange = '', $getHtmlWay = 'curl', $outputEncoding = false) + public static function Query($page,array $regArr, $regRange = '', $outputEncoding = null, $inputEncoding = null,$removeHead = false) { - if(!(self::$ql instanceof self)) - { - self::$ql = new self(); - } - self::$ql->_query($page, $regArr, $regRange, $getHtmlWay, $outputEncoding); - return self::$ql; + return self::getInstance()->_query($page, $regArr, $regRange, $outputEncoding, $inputEncoding,$removeHead); } + + /** + * 运行QueryList扩展 + * @param $class + * @param array $args + * @return mixed + * @throws QueryList_Exception + */ + public static function run($class,$args = array()) + { + $extension = self::getInstance($class); + return $extension->run($args); + } + + /** + * 获取任意实例 + * @return mixed + * @throws QueryList_Exception + */ + public static function getInstance() + { + $args = func_get_args(); + count($args) || $args = array(self::class); + $key = md5(serialize($args)); + $className = array_shift($args); + if(!class_exists($className)) { + throw new QueryList_Exception("no class {$className}"); + } + if(!isset(self::$instances[$key])) { + $rc = new ReflectionClass($className); + self::$instances[$key] = $rc->newInstanceArgs($args); + } + return self::$instances[$key]; + } + + /** + * 获取目标页面源码(主要用于调试) + * @param bool|true $rel + * @return string + */ + public function getHtml($rel = true) + { + return $rel?$this->qpHtml:$this->html; + } + + /** + * 获取采集结果数据 + * @param callback $callback + * @return array + */ + public function getData($callback = null) + { + if(is_callable($callback)){ + return array_map($callback,$this->data); + } + return $this->data; + } + /** * 重新设置选择器 - * @param array $regArr 选择器数组 - * @param string $regRange 块选择器 + * @param $regArr + * @param string $regRange + * @param string $outputEncoding + * @param string $inputEncoding + * @param bool|false $removeHead + * @return QueryList */ - public function setQuery($regArr, $regRange = '') + public function setQuery(array $regArr, $regRange = '',$outputEncoding = null, $inputEncoding = null,$removeHead = false) { - $this->jsonArr = array(); + return $this->_query($this->html,$regArr, $regRange, $outputEncoding, $inputEncoding,$removeHead); + } + + private function _query($page,array $regArr, $regRange, $outputEncoding, $inputEncoding,$removeHead) + { + $this->data = array(); + $this->html = $this->_isURL($page)?$this->_request($page):$page; + $outputEncoding && $this->outputEncoding = $outputEncoding; + $inputEncoding && $this->inputEncoding = $inputEncoding; + $removeHead && $this->html = $this->_removeHead($this->html); + $this->pqHtml = ''; + if(empty($this->html)){ + trigger_error("The received content is empty!",E_USER_NOTICE); + } + //获取编码格式 + $this->htmlEncoding = $this->inputEncoding?$this->inputEncoding:$this->_getEncode($this->html); + // $this->html = $this->_removeTags($this->html,array('script','style')); $this->regArr = $regArr; $this->regRange = $regRange; $this->_getList(); + return $this; } - /** - * 得到JSON结构的结果 - * @return string - */ - public function getJSON() - { - return json_encode($this->jsonArr); - } - private function _query($page, $regArr, $regRange, $getHtmlWay, $outputEncoding) - { - $this->jsonArr = array(); - $this->outputEncoding = $outputEncoding; - if ($this->_isURL($page)) { - if ($getHtmlWay == 'curl') { - //为了能获取https:// - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $page); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - $this->html = curl_exec($ch); - curl_close($ch); - } else { - $this->html = file_get_contents($page); - } - } else { - $this->html = $page; - } - //获取编码格式 - $this->htmlEncoding = $this->_getEncode($this->html); - // $this->html = $this->_removeTags($this->html,array('script','style')); - if (!empty($regArr)) { - $this->regArr = $regArr; - $this->regRange = $regRange; - $this->_getList(); - } - } + private function _getList() { - $hobj = phpQuery::newDocumentHTML($this->html); + $this->inputEncoding && phpQuery::$defaultCharset = $this->inputEncoding; + $document = phpQuery::newDocumentHTML($this->html); + $this->qpHtml = $document->htmlOuter(); if (!empty($this->regRange)) { - $robj = pq($hobj)->find($this->regRange); + $robj = pq($document)->find($this->regRange); $i = 0; foreach ($robj as $item) { while (list($key, $reg_value) = each($this->regArr)) { @@ -142,20 +187,20 @@ class QueryList switch ($reg_value[1]) { case 'text': - $this->jsonArr[$i][$key] = $this->_allowTags(pq($iobj)->html(),$tags); + $this->data[$i][$key] = $this->_allowTags(pq($iobj)->html(),$tags); break; case 'html': - $this->jsonArr[$i][$key] = $this->_stripTags(pq($iobj)->html(),$tags); + $this->data[$i][$key] = $this->_stripTags(pq($iobj)->html(),$tags); break; default: - $this->jsonArr[$i][$key] = pq($iobj)->attr($reg_value[1]); + $this->data[$i][$key] = pq($iobj)->attr($reg_value[1]); break; } if(isset($reg_value[3])){ - $this->jsonArr[$i][$key] = call_user_func($reg_value[3],$this->jsonArr[$i][$key],$key); + $this->data[$i][$key] = call_user_func($reg_value[3],$this->data[$i][$key],$key); }else if(isset($this->regArr['callback'])){ - $this->jsonArr[$i][$key] = call_user_func($this->regArr['callback'],$this->jsonArr[$i][$key],$key); + $this->data[$i][$key] = call_user_func($this->regArr['callback'],$this->data[$i][$key],$key); } } //重置数组指针 @@ -165,27 +210,27 @@ class QueryList } else { while (list($key, $reg_value) = each($this->regArr)) { if($key=='callback')continue; - $hobj = phpQuery::newDocumentHTML($this->html); + $document = phpQuery::newDocumentHTML($this->html); $tags = isset($reg_value[2])?$reg_value[2]:''; - $lobj = pq($hobj)->find($reg_value[0]); + $lobj = pq($document)->find($reg_value[0]); $i = 0; foreach ($lobj as $item) { switch ($reg_value[1]) { case 'text': - $this->jsonArr[$i][$key] = $this->_allowTags(pq($item)->html(),$tags); + $this->data[$i][$key] = $this->_allowTags(pq($item)->html(),$tags); break; case 'html': - $this->jsonArr[$i][$key] = $this->_stripTags(pq($item)->html(),$tags); + $this->data[$i][$key] = $this->_stripTags(pq($item)->html(),$tags); break; default: - $this->jsonArr[$i][$key] = pq($item)->attr($reg_value[1]); + $this->data[$i][$key] = pq($item)->attr($reg_value[1]); break; } if(isset($reg_value[3])){ - $this->jsonArr[$i][$key] = call_user_func($reg_value[3],$this->jsonArr[$i][$key],$key); + $this->data[$i][$key] = call_user_func($reg_value[3],$this->data[$i][$key],$key); }else if(isset($this->regArr['callback'])){ - $this->jsonArr[$i][$key] = call_user_func($this->regArr['callback'],$this->jsonArr[$i][$key],$key); + $this->data[$i][$key] = call_user_func($this->regArr['callback'],$this->data[$i][$key],$key); } $i++; @@ -194,10 +239,52 @@ class QueryList } if ($this->outputEncoding) { //编码转换 - $this->jsonArr = $this->_arrayConvertEncoding($this->jsonArr, $this->outputEncoding, $this->htmlEncoding); + $this->data = $this->_arrayConvertEncoding($this->data, $this->outputEncoding, $this->htmlEncoding); } phpQuery::$documents = array(); } + + /** + * URL请求 + * @param $url + * @return string + */ + private function _request($url) + { + if(function_exists('curl_init')){ + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_AUTOREFERER, true); + curl_setopt($ch, CURLOPT_REFERER, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $result = curl_exec($ch); + curl_close($ch); + }elseif(version_compare(PHP_VERSION, '5.0.0')>=0){ + $opts = array( + 'http' => array( + 'header' => "Referer:{$url}" + ) + ); + $result = file_get_contents($url,false,stream_context_create($opts)); + }else{ + $result = file_get_contents($url); + } + return $result; + } + + /** + * 移除页面head区域代码 + * @param $html + * @return mixed + */ + private function _removeHead($html) + { + return preg_replace('/.+<\/head>/is','',$html); + } + /** * 获取文件编码 * @param $string @@ -207,6 +294,7 @@ class QueryList { return mb_detect_encoding($string, array('ASCII', 'GB2312', 'GBK', 'UTF-8')); } + /** * 转换数组值的编码格式 * @param array $arr @@ -219,6 +307,7 @@ class QueryList eval('$arr = '.iconv($fromEncoding, $toEncoding.'//IGNORE', var_export($arr,TRUE)).';'); return $arr; } + /** * 简单的判断一下参数是否为一个URL链接 * @param string $str @@ -231,6 +320,7 @@ class QueryList } return false; } + /** * 去除特定的html标签 * @param string $html @@ -248,6 +338,7 @@ class QueryList $html = preg_replace($p,"",trim($html)); return $html; } + /** * 保留特定的html标签 * @param string $html @@ -264,6 +355,7 @@ class QueryList } return strip_tags(trim($html),$allow); } + private function _tag($tags_str) { $tagArr = preg_split("/\s+/",$tags_str,-1,PREG_SPLIT_NO_EMPTY); @@ -279,6 +371,7 @@ class QueryList } return $tags; } + /** * 移除特定的html标签 * @param string $html @@ -293,7 +386,7 @@ class QueryList foreach ($tags as $tag) { $tag_str .= $tag_str?','.$tag:$tag; } - phpQuery::$defaultCharset = $this->htmlEncoding; + phpQuery::$defaultCharset = $this->inputEncoding?$this->inputEncoding:$this->htmlEncoding; $doc = phpQuery::newDocumentHTML($html); pq($doc)->find($tag_str)->remove(); $html = pq($doc)->htmlOuter(); @@ -303,5 +396,27 @@ class QueryList } } +class QueryList_Exception extends Exception{ +} + +class Autoload +{ + public static function load($className) + { + $files = array( + sprintf('%s/extensions/%s.php',__DIR__,$className), + sprintf('%s/extensions/vendors/%s.php',__DIR__,$className) + ); + foreach ($files as $file) { + if(is_file($file)){ + require $file; + return true; + } + } + return false; + } +} + +spl_autoload_register(array('Autoload','load')); diff --git a/demo/Searcher.class.php b/demo/Searcher.class.php deleted file mode 100644 index 42ef808..0000000 --- a/demo/Searcher.class.php +++ /dev/null @@ -1,152 +0,0 @@ -jsonArr); - - $json = Searcher::S('QueryList交流')->getJSON(); - print_r($json); - */ -require '../QueryList.class.php'; - class Searcher - { - private $searcher; - private $key; - private $num; - private $page; - private $regArr ; - private $regRange ; - private $regZnum; - public $jsonArr; - private static $s; - - public static function S($key,$searcher = 'baidu',$num = 10,$page = 1) - { - if(!(self::$s instanceof self)) - { - self::$s = new self(); - } - self::$s->query($key,$searcher,$num,$page); - return self::$s; - } - private function query($key,$searcher,$num,$page) - { - if($searcher=='baidu') - { - $this->regArr = array("title"=>array("h3.t a,#ting_singlesong_box a","text"),"tCon"=>array("div.c-abstract,font:slice(0,2),div#weibo,table tr:eq(0),div.c-abstract-size p:eq(0),div.vd_sitcom_new_tinfo","text"),"url"=>array("h3.t a,#ting_singlesong_box a","href"),"host"=>array("div.f13>span.g","text")); - $this->regRange = '.result,.result-op'; - $this->regZnum=array("zNum"=>array("span.nums","text")); - } - else if($searcher=='google') - { - $this->regArr = array("title"=>array("h3.r a","text"),"tCon"=>array("span.st","text"),"url"=>array("h3.r a","href")); - $this->regRange = 'li.g'; - $this->regZnum=array("zNum"=>array("div#resultStats","text")); - } - else if($searcher=='sogou') - { - $this->regArr = array("title"=>array("h3 a","text"),"tCon"=>array("div.ft","text"),"url"=>array("h3 a","href")); - $this->regRange = '[id^=rb_]'; - $this->regZnum=array("zNum"=>array("div.mun","text")); - } - $this->searcher = $searcher; - $this->key = $key; - $this->num = $num; - $this->page = $page-1; - $this->getList(); - } - private function getList() - { - $s = urlencode($this->key); - $num = $this->num; - $getHtmlWay = 'get'; - $start = $this->num*$this->page; - if($this->searcher=='baidu') - { - $url = "http://www.baidu.com/s?pn=$start&rn=$num&wd=$s"; - $reg_znum='/[\d,]+/'; - } - else if($this->searcher=='google') - { - $url="https://www.google.com.hk/search?filter=0&lr=&newwindow=1&safe=images&hl=en&as_qdr=all&num=$num&start=$start&q=$s"; - $reg_znum='/([\d,]+) result(s)?/'; - $getHtmlWay = 'curl'; - } - else if($this->searcher=='sogou') - { - $url="http://www.sogou.com/web?query=$s&num=$num&page=".$this->page; - $reg_znum='/[\d,]+/'; - } - $searcherObj = QueryList::Query($url,$this->regArr,$this->regRange,$getHtmlWay,false); - for($i=0;$ijsonArr);$i++) - { - if($this->searcher=='baidu') - { - // $searcherObj->jsonArr[$i]['url'] = $this->getBaiduRealURL($searcherObj->jsonArr[$i]['url']); - } - else if($this->searcher=='google') - { - $searcherObj->jsonArr[$i]['url'] = $this->getGoogleRealURL($searcherObj->jsonArr[$i]['url']); - } - } - $this->jsonArr = $searcherObj->jsonArr ; - - //获取总共结果条数 - - $searcherObj->setQuery($this->regZnum); - $zNum = $searcherObj->jsonArr[0]['zNum']; - preg_match($reg_znum,$zNum,$arr)?$zNum=$arr[0]:$zNum=0; - $zNum = (int)str_replace(',','',$zNum); - //计算总页数 - $zPage = ceil($zNum/$this->num); - $this->jsonArr=array('num'=>$this->num,'page'=>((int)$this->page+1),'zNum'=>$zNum,'zPage'=>$zPage,"s"=>"$this->key",'other'=>array('author'=>'JAE','QQ'=>'734708094','blog'=>'http://blog.jaekj.com'),'data'=>$this->jsonArr); - - - } - public function getJSON() - { - return json_encode($this->jsonArr); - } - private function getBaiduRealURL($url) - { - //得到百度跳转的真正地址 - $header = get_headers($url,1); - if (strpos($header[0],'301') || strpos($header[0],'302')) - { - if(is_array($header['Location'])) - { - //return $header['Location'][count($header['Location'])-1]; - return $header['Location'][0]; - } - else - { - return $header['Location']; - } - } - else - { - return $url; - } - } - private function getGoogleRealURL($url) - { - $reg_url = '/q=(.+)&/U'; - return preg_match($reg_url,$url,$arr)?urldecode($arr[1]):$url; - - } - } - - - - $hj = Searcher::S('site:pan.baidu.com torrent','sogou',20,2); - print_r( $hj->jsonArr); \ No newline at end of file diff --git a/demo/demo.php b/demo/demo.php deleted file mode 100644 index dc4c7c6..0000000 --- a/demo/demo.php +++ /dev/null @@ -1,68 +0,0 @@ -array(".code_title a:eq(0)","text"),"url"=>array(".code_title a:eq(0)","href"),"author"=>array("img","title")); -$rang = ".code_list li"; -//使用curl抓取源码并以GBK编码格式输出 -$hj = QueryList::Query($url,$reg,$rang,'curl','GBK'); -$arr = $hj->jsonArr; -echo "
";
-print_r($arr);
-echo "

"; - -echo '上面的是GBK格式输出的,而页面是UTF-8格式的,所以会看到输出是乱码!'; -echo '
'; - -//如果还想采当前页面右边的 TOP40活跃贡献者 图像,得到JSON数据,可以这样写 -$reg = array("portrait"=>array(".hot_top img","src")); -$hj->setQuery($reg); -$json = $hj->getJSON(); -echo $json . "
"; - -//采OSC内容页内容 -$url = "http://www.oschina.net/code/snippet_186288_23816"; -$reg = array("title"=>array(".QTitle h1","text"),"con"=>array(".Content","html")); -$hj = QueryList::Query($url,$reg); -$arr = $hj->jsonArr; -echo "
";
-print_r($arr);
-echo "

"; - -//抓取网站基本信息 -//设置规则 -$reg = array( - //抓取网站keywords - "kw" => array("meta[name=keywords]","content"), - //抓取网站描述 - "desc" => array("meta[name=description]","content"), - //抓取网站标题 - "title" => array("title","text"), - //抓取网站第一个css link的链接 - "css1" => array("link:eq(0)","href"), - //抓取网站第二个js link的链接 - "js2" => array("script[src]:eq(1)","src") - ); -//抓取的目标站 -$url = 'http://x.44i.cc/'; -//抓取 -$data = QueryList::Query($url,$reg)->jsonArr; -print_r($data); - -//下面单独演示回调函数的用法 -//抓取网站keywords并分离每个关键词 -$reg = array( - //抓取网站keywords,并调用自定义函数fun - "kw" => array("meta[name=keywords]","content",'','fun') - ); -//自定义回调函数 -function fun($content,$key){ - //分离关键词 - return explode(',', $content); -} -//抓取的目标站 -$url = 'http://x.44i.cc/'; -//抓取 -$data = QueryList::Query($url,$reg)->jsonArr; -print_r($data); \ No newline at end of file diff --git a/demo/thanks.png b/demo/thanks.png deleted file mode 100644 index b5c72a72746ec64a0ff730644f853994e38f63d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8815 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 diff --git a/extensions/AQuery.php b/extensions/AQuery.php new file mode 100644 index 0000000..cbb936b --- /dev/null +++ b/extensions/AQuery.php @@ -0,0 +1,19 @@ + + * @Date: 2015-11-11 17:52:40 + * @Last Modified by: Jaeger + * @Last Modified time: 2015-11-16 09:57:56 + * @version 1.0 + * 扩展基类 + */ +abstract class AQuery +{ + abstract function run(array $args); + + public function getInstance($className = 'QueryList', $params = null) + { + return QueryList::getInstance($className,$params); + } + +} \ No newline at end of file diff --git a/extensions/Login.php b/extensions/Login.php new file mode 100644 index 0000000..93c059e --- /dev/null +++ b/extensions/Login.php @@ -0,0 +1,44 @@ + + * @Date: 2015-11-11 17:52:40 + * @Last Modified by: Jaeger + * @Last Modified time: 2015-11-16 09:57:58 + * @version 1.0 + * 模拟登陆扩展 + */ +class Login extends Request +{ + private $http; + public $html; + + public function run(array $args) + { + $this->http = $this->hq($args); + $this->html = $this->http->result; + return $this; + } + + public function get($url,$callback = null,$args = null) + { + $result = $this->http->get($url); + return $this->getQL($result,$callback,$args); + } + + public function post($url,$data=array(),$callback = null,$args = null) + { + $result = $this->http->post($url,$data); + return $this->getQL($result,$callback,$args); + } + + private function getQL($html,$callback = null,$args = null) + { + if(is_callable($callback)){ + $result = call_user_func($callback,$result,$args); + } + $ql = $this->getInstance(); + $ql->html = $html; + return $ql; + } + +} \ No newline at end of file diff --git a/extensions/Multi.php b/extensions/Multi.php new file mode 100644 index 0000000..2466421 --- /dev/null +++ b/extensions/Multi.php @@ -0,0 +1,73 @@ + + * @Date: 2015-11-11 17:52:40 + * @Last Modified by: Jaeger + * @Last Modified time: 2015-11-16 09:57:14 + * @version 1.0 + * 多线程扩展 + */ +class Multi extends AQuery +{ + private $curl; + private $args; + + public function run(array $args) + { + $default = array( + 'curl' => array( + 'opt' => array( + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_AUTOREFERER => true, + ), + 'maxThread' => 100, + 'maxTry' => 3 + ), + 'list' => array(), + 'success' => function(){}, + 'error' => function(){}, + 'start' => true + ); + + $this->args = array_merge($default,$args); + + $this->curl = $this->getInstance('CurlMulti'); + if(isset($this->args['curl'])){ + foreach ($this->args['curl'] as $k => $v) { + $this->curl->$k = $v; + } + } + $this->add($this->args['list']); + + return $this->args['start']?$this->start():$this; + } + + public function add($urls,$success = false,$error = false) + { + if(!is_array($urls)){ + $urls = array($urls); + } + foreach ($urls as $url) { + $this->curl->add( + array( + 'url' => $url, + 'args' => $this, + 'opt' => array( + CURLOPT_REFERER => $url + ) + ), + $success?$success:$this->args['success'], + $error?$error:$this->args['error'] + ); + } + return $this; + } + + public function start() + { + $this->curl->start(); + return $this; + } +} \ No newline at end of file diff --git a/extensions/Request.php b/extensions/Request.php new file mode 100644 index 0000000..423b099 --- /dev/null +++ b/extensions/Request.php @@ -0,0 +1,37 @@ + + * @Date: 2015-07-15 23:27:52 + * @Last Modified by: Jaeger + * @Last Modified time: 2015-11-16 11:01:19 + * @version 1.0 + * 网络操作扩展 + */ + +class Request extends AQuery +{ + + protected function hq(array $args) + { + $args = array( + 'http' => isset($args['http'])?$args['http']:$args, + 'callback' => isset($args['callback'])?$args['callback']:'', + 'args' => isset($args['args'])?$args['args']:'' + ); + $http = $this->getInstance('Http'); + $http->initialize($args['http']); + $http->execute(); + if(!empty($args['callback'])){ + $http->result = call_user_func($args['callback'],$http->result,$args['args']); + } + return $http; + } + + public function run(array $args) + { + $http = $this->hq($args); + $ql = $this->getInstance(); + $ql->html = $http->result; + return $ql; + } +} \ No newline at end of file diff --git a/extensions/vendors/CurlMulti.php b/extensions/vendors/CurlMulti.php new file mode 100644 index 0000000..ab75a3d --- /dev/null +++ b/extensions/vendors/CurlMulti.php @@ -0,0 +1,698 @@ +maxThreadNoType is maxThread-sum(maxThreadType).If less than 0 $this->maxThreadNoType is set to 0. + public $maxThreadType = array (); + // retry time(s) when task failed + public $maxTry = 3; + // operation, class level curl opt + public $opt = array (); + // cache options,dirLevel values is less than 3 + public $cache = array ( + 'enable' => false, + 'enableDownload' => false, + 'compress' => false, + 'dir' => null, + 'expire' => 86400, + 'dirLevel' => 1 + ); + // stack or queue + public $taskPoolType = 'stack'; + // eliminate duplicate for taskpool, will delete previous task and add new one + public $taskOverride = false; + // task callback,add() should be called in callback, $cbTask[0] is callback, $cbTask[1] is param. + public $cbTask = null; + // status callback + public $cbInfo = null; + // user callback + public $cbUser = null; + // common fail callback, called if no one specified + public $cbFail = null; + + // is the loop running + protected $isRunning = false; + // max thread num no type + protected $maxThreadNoType = null; + // all added task was saved here first + protected $taskPool = array (); + // taskPool with high priority + protected $taskPoolAhead = array (); + // running task(s) + protected $taskRunning = array (); + // failed task need to retry + protected $taskFail = array (); + + // handle of multi-thread curl + private $mh = null; + // user error + private $userError = null; + // if __construct called + private $isConstructCalled = false; + // running info + private $info = array ( + 'all' => array ( + // process start time + 'startTime' => null, + // download start time + 'startTimeDownload' => null, + // the real multi-thread num + 'activeNum' => null, + // finished task in the queue + 'queueNum' => null, + // byte + 'downloadSize' => 0, + // finished task number,include failed task and cache + 'finishNum' => 0, + // The number of cache used + 'cacheNum' => 0, + // completely failed task number + 'failNum' => 0, + // task num has added + 'taskNum' => 0, + // task running num by type, + 'taskRunningNumType' => array (), + // task ruuning num no type + 'taskRunningNumNoType' => 0, + // $this->taskPool size + 'taskPoolNum' => 0, + // $this->taskRunning size + 'taskRunningNum' => 0, + // $this->taskFail size + 'taskFailNum' => 0, + // finish percent + 'finishPercent' => 0, + // time cost + 'timeSpent' => 0, + // download time cost + 'timeSpentDownload' => 0, + // curl task speed + 'taskSpeedNoCache' => 0, + // network speed, bytes + 'downloadSpeed' => 0 + ), + 'running' => array () + ); + function __construct() { + $this->isConstructCalled = true; + if (version_compare ( PHP_VERSION, '5.1.0' ) < 0) { + throw new CurlMulti_Exception ( 'PHP 5.1.0+ is needed' ); + } + } + + /** + * add a task to taskPool + * + * @param array $item + * array('url'=>'',['file'=>'',['opt'=>array(),['args'=>array(),['ctl'=>array('type'=>'','ahead'=>false,'cache'=>array('enable'=>bool,'expire'=>0),'close'=>true))]]]]) + * @param mixed $process + * success callback,for callback first param array('info'=>,'content'=>), second param $item[args] + * @param mixed $fail + * curl fail callback,for callback first param array('error'=>array(0=>code,1=>msg),'info'=>array),second param $item[args]; + * @throws CurlMulti_Exception + * @return \frame\lib\CurlMulti + */ + function add(array $item, $process = null, $fail = null) { + // check + if (! is_array ( $item )) { + user_error ( 'item must be array, item is ' . gettype ( $item ), E_USER_WARNING ); + } else { + $item ['url'] = trim ( $item ['url'] ); + if (empty ( $item ['url'] )) { + user_error ( "url can't be empty, url=$item[url]", E_USER_WARNING ); + } else { + // replace space with + to avoid some curl problems + $item ['url'] = str_replace ( ' ', '+', $item ['url'] ); + // fix + if (empty ( $item ['file'] )) + $item ['file'] = null; + if (empty ( $item ['opt'] )) + $item ['opt'] = array (); + if (empty ( $item ['args'] )) + $item ['args'] = array (); + if (empty ( $item ['ctl'] )) { + $item ['ctl'] = array (); + } + if (! isset ( $item ['ctl'] ['cache'] ) || ! isset ( $item ['ctl'] ['cache'] ['enable'] )) { + $item ['ctl'] ['cache'] = array ( + 'enable' => false, + 'expire' => 0 + ); + } + if (! isset ( $item ['ctl'] ['ahead'] )) { + $item ['ctl'] ['ahead'] = false; + } + if (empty ( $process )) { + $process = null; + } + if (empty ( $fail )) { + $fail = null; + } + $task = array (); + $task [self::TASK_ITEM_URL] = $item ['url']; + $task [self::TASK_ITEM_FILE] = $item ['file']; + $task [self::TASK_ITEM_ARGS] = array ( + $item ['args'] + ); + $task [self::TASK_ITEM_OPT] = $item ['opt']; + $task [self::TASK_ITEM_CTL] = $item ['ctl']; + $task [self::TASK_PROCESS] = $process; + $task [self::TASK_FAIL] = $fail; + $task [self::TASK_TRYED] = 0; + $task [self::TASK_CH] = null; + $this->addTaskPool ( $task ); + $this->info ['all'] ['taskNum'] ++; + } + } + return $this; + } + + /** + * add task to taskPool + * + * @param unknown $task + */ + private function addTaskPool($task) { + // uniq + if ($this->taskOverride) { + foreach ( array ( + 'taskPoolAhead', + 'taskPool' + ) as $v ) { + foreach ( $this->$v as $k1 => $v1 ) { + if ($v1 [self::TASK_ITEM_URL] == $task [self::TASK_ITEM_URL]) { + $t = &$this->$v; + unset ( $t [$k1] ); + } + } + } + } + // add + if (true == $task [self::TASK_ITEM_CTL] ['ahead']) { + $this->taskPoolAhead [] = $task; + } else { + if ($this->taskPoolType == 'queue') { + $this->taskPool [] = $task; + } elseif ($this->taskPoolType == 'stack') { + array_unshift ( $this->taskPool, $task ); + } else { + throw new CurlMulti_Exception ( 'taskPoolType not found, taskPoolType=' . $this->taskPoolType ); + } + } + } + + /** + * Perform the actual task(s). + */ + function start() { + if ($this->isRunning) { + throw new CurlMulti_Exception ( __CLASS__ . ' is running !' ); + } + if (false === $this->isConstructCalled) { + throw new CurlMulti_Exception ( __CLASS__ . ' __construct is not called' ); + } + $this->mh = curl_multi_init (); + $this->info ['all'] ['startTime'] = time (); + $this->info ['all'] ['timeStartDownload'] = null; + $this->info ['all'] ['downloadSize'] = 0; + $this->info ['all'] ['finishNum'] = 0; + $this->info ['all'] ['cacheNum'] = 0; + $this->info ['all'] ['failNum'] = 0; + $this->info ['all'] ['taskNum'] = 0; + $this->info ['all'] ['taskRunningNumNoType'] = 0; + $this->setThreadData (); + $this->isRunning = true; + $this->addTask (); + do { + $this->exec (); + curl_multi_select ( $this->mh ); + $this->callCbInfo (); + if (isset ( $this->cbUser )) { + call_user_func ( $this->cbUser ); + } + while ( false != ($curlInfo = curl_multi_info_read ( $this->mh, $this->info ['all'] ['queueNum'] )) ) { + $ch = $curlInfo ['handle']; + $task = $this->taskRunning [( int ) $ch]; + $info = curl_getinfo ( $ch ); + $this->info ['all'] ['downloadSize'] += $info ['size_download']; + if (isset ( $task [self::TASK_FP] )) { + fclose ( $task [self::TASK_FP] ); + } + if ($curlInfo ['result'] == CURLE_OK) { + $param = array (); + $param ['info'] = $info; + $param ['ext'] = array ( + 'ch' => $ch + ); + if (! isset ( $task [self::TASK_ITEM_FILE] )) { + $param ['content'] = curl_multi_getcontent ( $ch ); + } + } + curl_multi_remove_handle ( $this->mh, $ch ); + // must close first,other wise download may be not commpleted in process callback + if (! array_key_exists ( 'close', $task [self::TASK_ITEM_CTL] ) || $task [self::TASK_ITEM_CTL] ['close'] == true) { + curl_close ( $ch ); + } + if ($curlInfo ['result'] == CURLE_OK) { + $this->process ( $task, $param, false ); + } + // error handle + $callFail = false; + if ($curlInfo ['result'] !== CURLE_OK || isset ( $this->userError )) { + if ($task [self::TASK_TRYED] >= $this->maxTry) { + // user error + if (isset ( $this->userError )) { + $err = array ( + 'error' => $this->userError + ); + } else { + $err = array ( + 'error' => array ( + $curlInfo ['result'], + curl_error ( $ch ) + ) + ); + } + $err ['info'] = $info; + if (isset ( $task [self::TASK_FAIL] ) || isset ( $this->cbFail )) { + array_unshift ( $task [self::TASK_ITEM_ARGS], $err ); + $callFail = true; + } else { + echo "\nError " . implode ( ', ', $err ['error'] ) . ", url=$info[url]\n"; + } + $this->info ['all'] ['failNum'] ++; + } else { + $task [self::TASK_TRYED] ++; + $task [self::TASK_ITEM_CTL] ['useCache'] = false; + $this->taskFail [] = $task; + $this->info ['all'] ['taskNum'] ++; + } + if (isset ( $this->userError )) { + unset ( $this->userError ); + } + } + if ($callFail) { + if (isset ( $task [self::TASK_FAIL] )) { + call_user_func_array ( $task [self::TASK_FAIL], $task [self::TASK_ITEM_ARGS] ); + } elseif (isset ( $this->cbFail )) { + call_user_func_array ( $this->cbFail, $task [self::TASK_ITEM_ARGS] ); + } + } + unset ( $this->taskRunning [( int ) $ch] ); + if (array_key_exists ( 'type', $task [self::TASK_ITEM_CTL] )) { + $this->info ['all'] ['taskRunningNumType'] [$task [self::TASK_ITEM_CTL] ['type']] --; + } else { + $this->info ['all'] ['taskRunningNumNoType'] --; + } + $this->addTask (); + $this->info ['all'] ['finishNum'] ++; + // if $this->info['all']['queueNum'] grow very fast there will be no efficiency lost,because outer $this->exec() won't be executed. + $this->exec (); + $this->callCbInfo (); + if (isset ( $this->cbUser )) { + call_user_func ( $this->cbUser ); + } + } + } while ( $this->info ['all'] ['activeNum'] || $this->info ['all'] ['queueNum'] || ! empty ( $this->taskFail ) || ! empty ( $this->taskRunning ) || ! empty ( $this->taskPool ) ); + $this->callCbInfo ( true ); + curl_multi_close ( $this->mh ); + unset ( $this->mh ); + $this->isRunning = false; + } + + /** + * call $this->cbInfo + */ + private function callCbInfo($force = false) { + static $lastTime; + if (! isset ( $lastTime )) { + $lastTime = time (); + } + $now = time (); + if (($force || $now - $lastTime > 0) && isset ( $this->cbInfo )) { + $lastTime = $now; + $this->info ['all'] ['taskPoolNum'] = count ( $this->taskPool ); + $this->info ['all'] ['taskRunningNum'] = count ( $this->taskRunning ); + $this->info ['all'] ['taskFailNum'] = count ( $this->taskFail ); + if ($this->info ['all'] ['taskNum'] > 0) { + $this->info ['all'] ['finishPercent'] = round ( $this->info ['all'] ['finishNum'] / $this->info ['all'] ['taskNum'], 4 ); + } + $this->info ['all'] ['timeSpent'] = time () - $this->info ['all'] ['startTime']; + if (isset ( $this->info ['all'] ['timeStartDownload'] )) { + $this->info ['all'] ['timeSpentDownload'] = time () - $this->info ['all'] ['timeStartDownload']; + } + if ($this->info ['all'] ['timeSpentDownload'] > 0) { + $this->info ['all'] ['taskSpeedNoCache'] = round ( ($this->info ['all'] ['finishNum'] - $this->info ['all'] ['cacheNum']) / $this->info ['all'] ['timeSpentDownload'], 2 ); + $this->info ['all'] ['downloadSpeed'] = round ( $this->info ['all'] ['downloadSize'] / $this->info ['all'] ['timeSpentDownload'], 2 ); + } + // running + $this->info ['running'] = array (); + foreach ( $this->taskRunning as $k => $v ) { + $this->info ['running'] [$k] = curl_getinfo ( $v [self::TASK_CH] ); + } + call_user_func_array ( $this->cbInfo, array ( + $this->info + ) ); + } + } + + /** + * set $this->maxThreadNoType, $this->info['all']['taskRunningNumType'], $this->info['all']['taskRunningNumNoType'] etc + */ + private function setThreadData() { + $this->maxThreadNoType = $this->maxThread - array_sum ( $this->maxThreadType ); + if ($this->maxThreadNoType < 0) { + $this->maxThreadNoType = 0; + } + // unset none exitst type num + foreach ( $this->info ['all'] ['taskRunningNumType'] as $k => $v ) { + if ($v == 0 && ! array_key_exists ( $k, $this->maxThreadType )) { + unset ( $this->info ['all'] ['taskRunningNumType'] [$k] ); + } + } + // init type num + foreach ( $this->maxThreadType as $k => $v ) { + if ($v == 0) { + user_error ( 'maxThreadType[' . $k . '] is 0, task of this type will never be added!', E_USER_WARNING ); + } + if (! array_key_exists ( $k, $this->info ['all'] ['taskRunningNumType'] )) { + $this->info ['all'] ['taskRunningNumType'] [$k] = 0; + } + } + } + + /** + * curl_multi_exec() + */ + private function exec() { + while ( curl_multi_exec ( $this->mh, $this->info ['all'] ['activeNum'] ) === CURLM_CALL_MULTI_PERFORM ) { + } + } + + /** + * add a task to curl, keep $this->maxThread concurrent automatically + */ + private function addTask() { + $c = $this->maxThread - count ( $this->taskRunning ); + while ( $c > 0 ) { + $task = array (); + // search failed first + if (! empty ( $this->taskFail )) { + $task = array_pop ( $this->taskFail ); + } else { + // cbTask + if (0 < ($this->maxThread - count ( $this->taskPool )) and ! empty ( $this->cbTask )) { + if (! isset ( $this->cbTask [1] )) { + $this->cbTask [1] = array (); + } + call_user_func_array ( $this->cbTask [0], array ( + $this->cbTask [1] + ) ); + } + if (! empty ( $this->taskPoolAhead )) { + $task = array_pop ( $this->taskPoolAhead ); + } elseif (! empty ( $this->taskPool )) { + if ($this->taskPoolType == 'stack') { + $task = array_pop ( $this->taskPool ); + } elseif ($this->taskPoolType == 'queue') { + $task = array_shift ( $this->taskPool ); + } else { + throw new CurlMulti_Exception ( 'taskPoolType not found, taskPoolType=' . $this->taskPoolType ); + } + } + } + $noAdd = false; + $cache = null; + if (! empty ( $task )) { + if (true == $task [self::TASK_ITEM_CTL] ['cache'] ['enable'] || $this->cache ['enable']) { + $cache = $this->cache ( $task ); + if (null !== $cache) { + if (isset ( $task [self::TASK_ITEM_FILE] )) { + file_put_contents ( $task [self::TASK_ITEM_FILE], $cache ['content'], LOCK_EX ); + unset ( $cache ['content'] ); + } + $this->process ( $task, $cache, true ); + $this->info ['all'] ['cacheNum'] ++; + $this->info ['all'] ['finishNum'] ++; + $this->callCbInfo (); + } + } + if (! $cache) { + $this->setThreadData (); + if (array_key_exists ( 'type', $task [self::TASK_ITEM_CTL] ) && ! array_key_exists ( $task [self::TASK_ITEM_CTL] ['type'], $this->maxThreadType )) { + user_error ( 'task was set to notype because type was not set in $this->maxThreadType, type=' . $task [self::TASK_ITEM_CTL] ['type'], E_USER_WARNING ); + unset ( $task [self::TASK_ITEM_CTL] ['type'] ); + } + if (array_key_exists ( 'type', $task [self::TASK_ITEM_CTL] )) { + $maxThread = $this->maxThreadType [$task [self::TASK_ITEM_CTL] ['type']]; + $isNoType = false; + } else { + $maxThread = $this->maxThreadNoType; + $isNoType = true; + } + if ($isNoType && $maxThread == 0) { + user_error ( 'task was disgarded because maxThreadNoType=0, url=' . $task [self::TASK_ITEM_URL], E_USER_WARNING ); + } + if (($isNoType && $this->info ['all'] ['taskRunningNumNoType'] < $maxThread) || (! $isNoType && $this->info ['all'] ['taskRunningNumType'] [$task [self::TASK_ITEM_CTL] ['type']] < $maxThread)) { + $task [self::TASK_CH] = $this->curlInit ( $task [self::TASK_ITEM_URL] ); + // is a download task? + if (isset ( $task [self::TASK_ITEM_FILE] )) { + // curl can create the last level directory + $dir = dirname ( $task [self::TASK_ITEM_FILE] ); + if (! file_exists ( $dir )) + mkdir ( $dir, 0777 ); + $task [self::TASK_FP] = fopen ( $task [self::TASK_ITEM_FILE], 'w' ); + curl_setopt ( $task [self::TASK_CH], CURLOPT_FILE, $task [self::TASK_FP] ); + } + // single task curl option + if (isset ( $task [self::TASK_ITEM_OPT] )) { + foreach ( $task [self::TASK_ITEM_OPT] as $k => $v ) { + curl_setopt ( $task [self::TASK_CH], $k, $v ); + } + } + $this->taskRunning [( int ) $task [self::TASK_CH]] = $task; + if (! isset ( $this->info ['all'] ['timeStartDownload'] )) { + $this->info ['all'] ['timeStartDownload'] = time (); + } + if ($isNoType) { + $this->info ['all'] ['taskRunningNumNoType'] ++; + } else { + $this->info ['all'] ['taskRunningNumType'] [$task [self::TASK_ITEM_CTL] ['type']] ++; + } + curl_multi_add_handle ( $this->mh, $task [self::TASK_CH] ); + } else { + // rotate task to pool + if ($task [self::TASK_TRYED] > 0) { + array_unshift ( $this->taskFail, $task ); + } else { + array_unshift ( $this->taskPool, $task ); + } + $noAdd = true; + } + } + } + if (! $cache || $noAdd) { + $c --; + } + } + } + + /** + * do process + * + * @param unknown $task + * @param unknown $r + * @param unknown $isCache + */ + private function process($task, $r, $isCache) { + array_unshift ( $task [self::TASK_ITEM_ARGS], $r ); + if (isset ( $task [self::TASK_PROCESS] )) { + $userRes = call_user_func_array ( $task [self::TASK_PROCESS], $task [self::TASK_ITEM_ARGS] ); + } + if (! isset ( $userRes )) { + $userRes = true; + } + array_shift ( $task [self::TASK_ITEM_ARGS] ); + // backoff + if (false === $userRes) { + if (false == $this->cache ['enable'] && false == $task [self::TASK_ITEM_CTL] ['cache'] ['enable']) { + $task [self::TASK_ITEM_CTL] ['cache'] = array ( + 'enable' => true, + 'expire' => 3600 + ); + } + $this->addTaskPool ( $task ); + } + // write cache + if (false == $isCache && false == isset ( $this->userError ) && (true == $task [self::TASK_ITEM_CTL] ['cache'] ['enable']) || $this->cache ['enable']) { + $this->cache ( $task, $r ); + } + } + + /** + * set or get file cache + * + * @param string $url + * @param mixed $content + * array('info','content') + * @return return array|null|boolean + */ + private function cache($task, $content = null) { + if (! isset ( $this->cache ['dir'] )) + throw new CurlMulti_Exception ( 'Cache dir is not defined' ); + $url = $task [self::TASK_ITEM_URL]; + $key = md5 ( $url ); + $isDownload = isset ( $task [self::TASK_ITEM_FILE] ); + $file = rtrim ( $this->cache ['dir'], '/' ) . '/'; + if (isset ( $this->cache ['dirLevel'] ) && $this->cache ['dirLevel'] != 0) { + if ($this->cache ['dirLevel'] == 1) { + $file .= substr ( $key, 0, 3 ) . '/' . substr ( $key, 3 ); + } elseif ($this->cache ['dirLevel'] == 2) { + $file .= substr ( $key, 0, 3 ) . '/' . substr ( $key, 3, 3 ) . '/' . substr ( $key, 6 ); + } else { + throw new CurlMulti_Exception ( 'cache dirLevel is invalid, dirLevel=' . $this->cache ['dirLevel'] ); + } + } else { + $file .= $key; + } + $r = null; + if (! isset ( $content )) { + if (file_exists ( $file )) { + if (true == $task [self::TASK_ITEM_CTL] ['cache'] ['enable']) { + $expire = $task [self::TASK_ITEM_CTL] ['cache'] ['expire']; + } else { + $expire = $this->cache ['expire']; + } + if (time () - filemtime ( $file ) < $expire) { + $r = file_get_contents ( $file ); + if ($this->cache ['compress']) { + $r = gzuncompress ( $r ); + } + $r = unserialize ( $r ); + if ($isDownload) { + $r ['content'] = base64_decode ( $r ['content'] ); + } + } + } + } else { + $r = false; + // check main cache directory + if (! is_dir ( $this->cache ['dir'] )) { + throw new CurlMulti_Exception ( "Cache dir doesn't exists" ); + } else { + $dir = dirname ( $file ); + // level 1 subdir + if (isset ( $this->cache ['dirLevel'] ) && $this->cache ['dirLevel'] > 1) { + $dir1 = dirname ( $dir ); + if (! is_dir ( $dir1 ) && ! mkdir ( $dir1 )) { + throw new CurlMulti_Exception ( 'Create dir failed, dir=' . $dir1 ); + } + } + if (! is_dir ( $dir ) && ! mkdir ( $dir )) { + throw new CurlMulti_Exception ( 'Create dir failed, dir=' . $dir ); + } + if ($isDownload) { + $content ['content'] = base64_encode ( file_get_contents ( $task [self::TASK_ITEM_FILE] ) ); + } + $content = serialize ( $content ); + if ($this->cache ['compress']) { + $content = gzcompress ( $content ); + } + if (file_put_contents ( $file, $content, LOCK_EX )) { + $r = true; + } else { + throw new CurlMulti_Exception ( 'Write cache file failed' ); + } + } + } + return $r; + } + + /** + * user error for current callback + * not curl error + * must be called in process callback + * + * @param unknown $msg + */ + function error($msg) { + $this->userError = array ( + CURLE_OK, + $msg + ); + } + + /** + * return a default $ch initialized with global opt + * + * @param unknown $url + * @return resource + */ + function getch($url = null) { + return $this->curlInit ( $url ); + } + + /** + * get curl handle + * + * @param string $url + * @return resource + */ + private function curlInit($url = null) { + $ch = curl_init (); + $opt = array (); + if (isset ( $url )) { + $opt [CURLOPT_URL] = $url; + } + $opt [CURLOPT_HEADER] = false; + $opt [CURLOPT_CONNECTTIMEOUT] = 10; + $opt [CURLOPT_TIMEOUT] = 30; + $opt [CURLOPT_AUTOREFERER] = true; + $opt [CURLOPT_USERAGENT] = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11'; + $opt [CURLOPT_RETURNTRANSFER] = true; + $opt [CURLOPT_FOLLOWLOCATION] = true; + $opt [CURLOPT_MAXREDIRS] = 10; + // user defined opt + if (! empty ( $this->opt )) { + foreach ( $this->opt as $k => $v ) { + $opt [$k] = $v; + } + } + curl_setopt_array ( $ch, $opt ); + return $ch; + } +} + +class CurlMulti_Exception extends Exception { +} \ No newline at end of file diff --git a/demo/tools/http.class.php b/extensions/vendors/Http.php similarity index 100% rename from demo/tools/http.class.php rename to extensions/vendors/Http.php diff --git a/phpQuery/phpQuery.php b/phpQuery/phpQuery.php index 5d214ad..708b7a3 100644 --- a/phpQuery/phpQuery.php +++ b/phpQuery/phpQuery.php @@ -18,12 +18,4372 @@ define('DOMDOCUMENT', 'DOMDocument'); define('DOMELEMENT', 'DOMElement'); define('DOMNODELIST', 'DOMNodeList'); define('DOMNODE', 'DOMNode'); -require_once(dirname(__FILE__).'/phpQuery/DOMEvent.php'); -require_once(dirname(__FILE__).'/phpQuery/DOMDocumentWrapper.php'); -require_once(dirname(__FILE__).'/phpQuery/phpQueryEvents.php'); -require_once(dirname(__FILE__).'/phpQuery/Callback.php'); -require_once(dirname(__FILE__).'/phpQuery/phpQueryObject.php'); -require_once(dirname(__FILE__).'/phpQuery/compat/mbstring.php'); + +/** + * DOMEvent class. + * + * Based on + * @link http://developer.mozilla.org/En/DOM:event + * @author Tobiasz Cudnik + * @package phpQuery + * @todo implement ArrayAccess ? + */ +class DOMEvent { + /** + * Returns a boolean indicating whether the event bubbles up through the DOM or not. + * + * @var unknown_type + */ + public $bubbles = true; + /** + * Returns a boolean indicating whether the event is cancelable. + * + * @var unknown_type + */ + public $cancelable = true; + /** + * Returns a reference to the currently registered target for the event. + * + * @var unknown_type + */ + public $currentTarget; + /** + * Returns detail about the event, depending on the type of event. + * + * @var unknown_type + * @link http://developer.mozilla.org/en/DOM/event.detail + */ + public $detail; // ??? + /** + * Used to indicate which phase of the event flow is currently being evaluated. + * + * NOT IMPLEMENTED + * + * @var unknown_type + * @link http://developer.mozilla.org/en/DOM/event.eventPhase + */ + public $eventPhase; // ??? + /** + * The explicit original target of the event (Mozilla-specific). + * + * NOT IMPLEMENTED + * + * @var unknown_type + */ + public $explicitOriginalTarget; // moz only + /** + * The original target of the event, before any retargetings (Mozilla-specific). + * + * NOT IMPLEMENTED + * + * @var unknown_type + */ + public $originalTarget; // moz only + /** + * Identifies a secondary target for the event. + * + * @var unknown_type + */ + public $relatedTarget; + /** + * Returns a reference to the target to which the event was originally dispatched. + * + * @var unknown_type + */ + public $target; + /** + * Returns the time that the event was created. + * + * @var unknown_type + */ + public $timeStamp; + /** + * Returns the name of the event (case-insensitive). + */ + public $type; + public $runDefault = true; + public $data = null; + public function __construct($data) { + foreach($data as $k => $v) { + $this->$k = $v; + } + if (! $this->timeStamp) + $this->timeStamp = time(); + } + /** + * Cancels the event (if it is cancelable). + * + */ + public function preventDefault() { + $this->runDefault = false; + } + /** + * Stops the propagation of events further along in the DOM. + * + */ + public function stopPropagation() { + $this->bubbles = false; + } +} + + +/** + * DOMDocumentWrapper class simplifies work with DOMDocument. + * + * Know bug: + * - in XHTML fragments,
changes to
+ * + * @todo check XML catalogs compatibility + * @author Tobiasz Cudnik + * @package phpQuery + */ +class DOMDocumentWrapper { + /** + * @var DOMDocument + */ + public $document; + public $id; + /** + * @todo Rewrite as method and quess if null. + * @var unknown_type + */ + public $contentType = ''; + public $xpath; + public $uuid = 0; + public $data = array(); + public $dataNodes = array(); + public $events = array(); + public $eventsNodes = array(); + public $eventsGlobal = array(); + /** + * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28 + * @var unknown_type + */ + public $frames = array(); + /** + * Document root, by default equals to document itself. + * Used by documentFragments. + * + * @var DOMNode + */ + public $root; + public $isDocumentFragment; + public $isXML = false; + public $isXHTML = false; + public $isHTML = false; + public $charset; + public function __construct($markup = null, $contentType = null, $newDocumentID = null) { + if (isset($markup)) + $this->load($markup, $contentType, $newDocumentID); + $this->id = $newDocumentID + ? $newDocumentID + : md5(microtime()); + } + public function load($markup, $contentType = null, $newDocumentID = null) { +// phpQuery::$documents[$id] = $this; + $this->contentType = strtolower($contentType); + if ($markup instanceof DOMDOCUMENT) { + $this->document = $markup; + $this->root = $this->document; + $this->charset = $this->document->encoding; + // TODO isDocumentFragment + } else { + $loaded = $this->loadMarkup($markup); + } + if ($loaded) { +// $this->document->formatOutput = true; + $this->document->preserveWhiteSpace = true; + $this->xpath = new DOMXPath($this->document); + $this->afterMarkupLoad(); + return true; + // remember last loaded document +// return phpQuery::selectDocument($id); + } + return false; + } + protected function afterMarkupLoad() { + if ($this->isXHTML) { + $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml"); + } + } + protected function loadMarkup($markup) { + $loaded = false; + if ($this->contentType) { + self::debug("Load markup for content type {$this->contentType}"); + // content determined by contentType + list($contentType, $charset) = $this->contentTypeToArray($this->contentType); + switch($contentType) { + case 'text/html': + phpQuery::debug("Loading HTML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupHTML($markup, $charset); + break; + case 'text/xml': + case 'application/xhtml+xml': + phpQuery::debug("Loading XML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupXML($markup, $charset); + break; + default: + // for feeds or anything that sometimes doesn't use text/xml + if (strpos('xml', $this->contentType) !== false) { + phpQuery::debug("Loading XML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupXML($markup, $charset); + } else + phpQuery::debug("Could not determine document type from content type '{$this->contentType}'"); + } + } else { + // content type autodetection + if ($this->isXML($markup)) { + phpQuery::debug("Loading XML, isXML() == true"); + $loaded = $this->loadMarkupXML($markup); + if (! $loaded && $this->isXHTML) { + phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true'); + $loaded = $this->loadMarkupHTML($markup); + } + } else { + phpQuery::debug("Loading HTML, isXML() == false"); + $loaded = $this->loadMarkupHTML($markup); + } + } + return $loaded; + } + protected function loadMarkupReset() { + $this->isXML = $this->isXHTML = $this->isHTML = false; + } + protected function documentCreate($charset, $version = '1.0') { + if (! $version) + $version = '1.0'; + $this->document = new DOMDocument($version, $charset); + $this->charset = $this->document->encoding; +// $this->document->encoding = $charset; + $this->document->formatOutput = true; + $this->document->preserveWhiteSpace = true; + } + protected function loadMarkupHTML($markup, $requestedCharset = null) { + if (phpQuery::$debug) + phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250)); + $this->loadMarkupReset(); + $this->isHTML = true; + if (!isset($this->isDocumentFragment)) + $this->isDocumentFragment = self::isDocumentFragmentHTML($markup); + $charset = null; + $documentCharset = $this->charsetFromHTML($markup); + $addDocumentCharset = false; + if ($documentCharset) { + $charset = $documentCharset; + $markup = $this->charsetFixHTML($markup); + } else if ($requestedCharset) { + $charset = $requestedCharset; + } + if (! $charset) + $charset = phpQuery::$defaultCharset; + // HTTP 1.1 says that the default charset is ISO-8859-1 + // @see http://www.w3.org/International/O-HTTP-charset + if (! $documentCharset) { + $documentCharset = 'ISO-8859-1'; + $addDocumentCharset = true; + } + // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' + // Worse, some pages can have mixed encodings... we'll try not to worry about that + $requestedCharset = strtoupper($requestedCharset); + $documentCharset = strtoupper($documentCharset); + phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset"); + if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) { + phpQuery::debug("CHARSET CONVERT"); + // Document Encoding Conversion + // http://code.google.com/p/phpquery/issues/detail?id=86 + if (function_exists('mb_detect_encoding')) { + $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO'); + $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets)); + if (! $docEncoding) + $docEncoding = $documentCharset; // ok trust the document + phpQuery::debug("DETECTED '$docEncoding'"); + // Detected does not match what document says... + if ($docEncoding !== $documentCharset) { + // Tricky.. + } + if ($docEncoding !== $requestedCharset) { + phpQuery::debug("CONVERT $docEncoding => $requestedCharset"); + $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding); + $markup = $this->charsetAppendToHTML($markup, $requestedCharset); + $charset = $requestedCharset; + } + } else { + phpQuery::debug("TODO: charset conversion without mbstring..."); + } + } + $return = false; + if ($this->isDocumentFragment) { + phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'"); + $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); + } else { + if ($addDocumentCharset) { + phpQuery::debug("Full markup load (HTML), appending charset: '$charset'"); + $markup = $this->charsetAppendToHTML($markup, $charset); + } + phpQuery::debug("Full markup load (HTML), documentCreate('$charset')"); + $this->documentCreate($charset); + $return = phpQuery::$debug === 2 + ? $this->document->loadHTML($markup) + : @$this->document->loadHTML($markup); + if ($return) + $this->root = $this->document; + } + if ($return && ! $this->contentType) + $this->contentType = 'text/html'; + return $return; + } + protected function loadMarkupXML($markup, $requestedCharset = null) { + if (phpQuery::$debug) + phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250)); + $this->loadMarkupReset(); + $this->isXML = true; + // check agains XHTML in contentType or markup + $isContentTypeXHTML = $this->isXHTML(); + $isMarkupXHTML = $this->isXHTML($markup); + if ($isContentTypeXHTML || $isMarkupXHTML) { + self::debug('Full markup load (XML), XHTML detected'); + $this->isXHTML = true; + } + // determine document fragment + if (! isset($this->isDocumentFragment)) + $this->isDocumentFragment = $this->isXHTML + ? self::isDocumentFragmentXHTML($markup) + : self::isDocumentFragmentXML($markup); + // this charset will be used + $charset = null; + // charset from XML declaration @var string + $documentCharset = $this->charsetFromXML($markup); + if (! $documentCharset) { + if ($this->isXHTML) { + // this is XHTML, try to get charset from content-type meta header + $documentCharset = $this->charsetFromHTML($markup); + if ($documentCharset) { + phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'"); + $this->charsetAppendToXML($markup, $documentCharset); + $charset = $documentCharset; + } + } + if (! $documentCharset) { + // if still no document charset... + $charset = $requestedCharset; + } + } else if ($requestedCharset) { + $charset = $requestedCharset; + } + if (! $charset) { + $charset = phpQuery::$defaultCharset; + } + if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) { + // TODO place for charset conversion +// $charset = $requestedCharset; + } + $return = false; + if ($this->isDocumentFragment) { + phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'"); + $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); + } else { + // FIXME ??? + if ($isContentTypeXHTML && ! $isMarkupXHTML) + if (! $documentCharset) { + phpQuery::debug("Full markup load (XML), appending charset '$charset'"); + $markup = $this->charsetAppendToXML($markup, $charset); + } + // see http://pl2.php.net/manual/en/book.dom.php#78929 + // LIBXML_DTDLOAD (>= PHP 5.1) + // does XML ctalogues works with LIBXML_NONET + // $this->document->resolveExternals = true; + // TODO test LIBXML_COMPACT for performance improvement + // create document + $this->documentCreate($charset); + if (phpversion() < 5.1) { + $this->document->resolveExternals = true; + $return = phpQuery::$debug === 2 + ? $this->document->loadXML($markup) + : @$this->document->loadXML($markup); + } else { + /** @link http://pl2.php.net/manual/en/libxml.constants.php */ + $libxmlStatic = phpQuery::$debug === 2 + ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET + : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR; + $return = $this->document->loadXML($markup, $libxmlStatic); +// if (! $return) +// $return = $this->document->loadHTML($markup); + } + if ($return) + $this->root = $this->document; + } + if ($return) { + if (! $this->contentType) { + if ($this->isXHTML) + $this->contentType = 'application/xhtml+xml'; + else + $this->contentType = 'text/xml'; + } + return $return; + } else { + throw new Exception("Error loading XML markup"); + } + } + protected function isXHTML($markup = null) { + if (! isset($markup)) { + return strpos($this->contentType, 'xhtml') !== false; + } + // XXX ok ? + return strpos($markup, "doctype) && is_object($dom->doctype) +// ? $dom->doctype->publicId +// : self::$defaultDoctype; + } + protected function isXML($markup) { +// return strpos($markup, ']+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', + $markup, $matches + ); + if (! isset($matches[0])) + return array(null, null); + // get attr 'content' + preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches); + if (! isset($matches[0])) + return array(null, null); + return $this->contentTypeToArray($matches[2]); + } + protected function charsetFromHTML($markup) { + $contentType = $this->contentTypeFromHTML($markup); + return $contentType[1]; + } + protected function charsetFromXML($markup) { + $matches; + // find declaration + preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i', + $markup, $matches + ); + return isset($matches[2]) + ? strtolower($matches[2]) + : null; + } + /** + * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug. + * + * @link http://code.google.com/p/phpquery/issues/detail?id=80 + * @param $html + */ + protected function charsetFixHTML($markup) { + $matches = array(); + // find meta tag + preg_match('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', + $markup, $matches, PREG_OFFSET_CAPTURE + ); + if (! isset($matches[0])) + return; + $metaContentType = $matches[0][0]; + $markup = substr($markup, 0, $matches[0][1]) + .substr($markup, $matches[0][1]+strlen($metaContentType)); + $headStart = stripos($markup, ''); + $markup = substr($markup, 0, $headStart+6).$metaContentType + .substr($markup, $headStart+6); + return $markup; + } + protected function charsetAppendToHTML($html, $charset, $xhtml = false) { + // remove existing meta[type=content-type] + $html = preg_replace('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html); + $meta = ''; + if (strpos($html, ')@s', + "{$meta}", + $html + ); + } + } else { + return preg_replace( + '@)@s', + ''.$meta, + $html + ); + } + } + protected function charsetAppendToXML($markup, $charset) { + $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>'; + return $declaration.$markup; + } + public static function isDocumentFragmentHTML($markup) { + return stripos($markup, 'documentFragmentCreate($node, $sourceCharset); +// if ($fake === false) +// throw new Exception("Error loading documentFragment markup"); +// else +// $return = array_merge($return, +// $this->import($fake->root->childNodes) +// ); +// } else { +// $return[] = $this->document->importNode($node, true); +// } +// } +// return $return; +// } else { +// // string markup +// $fake = $this->documentFragmentCreate($source, $sourceCharset); +// if ($fake === false) +// throw new Exception("Error loading documentFragment markup"); +// else +// return $this->import($fake->root->childNodes); +// } + if (is_array($source) || $source instanceof DOMNODELIST) { + // dom nodes + self::debug('Importing nodes to document'); + foreach($source as $node) + $return[] = $this->document->importNode($node, true); + } else { + // string markup + $fake = $this->documentFragmentCreate($source, $sourceCharset); + if ($fake === false) + throw new Exception("Error loading documentFragment markup"); + else + return $this->import($fake->root->childNodes); + } + return $return; + } + /** + * Creates new document fragment. + * + * @param $source + * @return DOMDocumentWrapper + */ + protected function documentFragmentCreate($source, $charset = null) { + $fake = new DOMDocumentWrapper(); + $fake->contentType = $this->contentType; + $fake->isXML = $this->isXML; + $fake->isHTML = $this->isHTML; + $fake->isXHTML = $this->isXHTML; + $fake->root = $fake->document; + if (! $charset) + $charset = $this->charset; +// $fake->documentCreate($this->charset); + if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) + $source = array($source); + if (is_array($source) || $source instanceof DOMNODELIST) { + // dom nodes + // load fake document + if (! $this->documentFragmentLoadMarkup($fake, $charset)) + return false; + $nodes = $fake->import($source); + foreach($nodes as $node) + $fake->root->appendChild($node); + } else { + // string markup + $this->documentFragmentLoadMarkup($fake, $charset, $source); + } + return $fake; + } + /** + * + * @param $document DOMDocumentWrapper + * @param $markup + * @return $document + */ + private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) { + // TODO error handling + // TODO copy doctype + // tempolary turn off + $fragment->isDocumentFragment = false; + if ($fragment->isXML) { + if ($fragment->isXHTML) { + // add FAKE element to set default namespace + $fragment->loadMarkupXML('' + .'' + .''.$markup.''); + $fragment->root = $fragment->document->firstChild->nextSibling; + } else { + $fragment->loadMarkupXML(''.$markup.''); + $fragment->root = $fragment->document->firstChild; + } + } else { + $markup2 = phpQuery::$defaultDoctype.''; + $noBody = strpos($markup, 'loadMarkupHTML($markup2); + // TODO resolv body tag merging issue + $fragment->root = $noBody + ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling + : $fragment->document->firstChild->nextSibling->firstChild->nextSibling; + } + if (! $fragment->root) + return false; + $fragment->isDocumentFragment = true; + return true; + } + protected function documentFragmentToMarkup($fragment) { + phpQuery::debug('documentFragmentToMarkup'); + $tmp = $fragment->isDocumentFragment; + $fragment->isDocumentFragment = false; + $markup = $fragment->markup(); + if ($fragment->isXML) { + $markup = substr($markup, 0, strrpos($markup, '')); + if ($fragment->isXHTML) { + $markup = substr($markup, strpos($markup, '')+6); + } + } else { + $markup = substr($markup, strpos($markup, '')+6); + $markup = substr($markup, 0, strrpos($markup, '')); + } + $fragment->isDocumentFragment = $tmp; + if (phpQuery::$debug) + phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150)); + return $markup; + } + /** + * Return document markup, starting with optional $nodes as root. + * + * @param $nodes DOMNode|DOMNodeList + * @return string + */ + public function markup($nodes = null, $innerMarkup = false) { + if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT) + $nodes = null; + if (isset($nodes)) { + $markup = ''; + if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) ) + $nodes = array($nodes); + if ($this->isDocumentFragment && ! $innerMarkup) + foreach($nodes as $i => $node) + if ($node->isSameNode($this->root)) { + // var_dump($node); + $nodes = array_slice($nodes, 0, $i) + + phpQuery::DOMNodeListToArray($node->childNodes) + + array_slice($nodes, $i+1); + } + if ($this->isXML && ! $innerMarkup) { + self::debug("Getting outerXML with charset '{$this->charset}'"); + // we need outerXML, so we can benefit from + // $node param support in saveXML() + foreach($nodes as $node) + $markup .= $this->document->saveXML($node); + } else { + $loop = array(); + if ($innerMarkup) + foreach($nodes as $node) { + if ($node->childNodes) + foreach($node->childNodes as $child) + $loop[] = $child; + else + $loop[] = $node; + } + else + $loop = $nodes; + self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment"); + $fake = $this->documentFragmentCreate($loop); + $markup = $this->documentFragmentToMarkup($fake); + } + if ($this->isXHTML) { + self::debug("Fixing XHTML"); + $markup = self::markupFixXHTML($markup); + } + self::debug("Markup: ".substr($markup, 0, 250)); + return $markup; + } else { + if ($this->isDocumentFragment) { + // documentFragment, html only... + self::debug("Getting markup, DocumentFragment detected"); +// return $this->markup( +//// $this->document->getElementsByTagName('body')->item(0) +// $this->document->root, true +// ); + $markup = $this->documentFragmentToMarkup($this); + // no need for markupFixXHTML, as it's done thought markup($nodes) method + return $markup; + } else { + self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'"); + $markup = $this->isXML + ? $this->document->saveXML() + : $this->document->saveHTML(); + if ($this->isXHTML) { + self::debug("Fixing XHTML"); + $markup = self::markupFixXHTML($markup); + } + self::debug("Markup: ".substr($markup, 0, 250)); + return $markup; + } + } + } + protected static function markupFixXHTML($markup) { + $markup = self::expandEmptyTag('script', $markup); + $markup = self::expandEmptyTag('select', $markup); + $markup = self::expandEmptyTag('textarea', $markup); + return $markup; + } + public static function debug($text) { + phpQuery::debug($text); + } + /** + * expandEmptyTag + * + * @param $tag + * @param $xml + * @return unknown_type + * @author mjaque at ilkebenson dot com + * @link http://php.net/manual/en/domdocument.savehtml.php#81256 + */ + public static function expandEmptyTag($tag, $xml){ + $indice = 0; + while ($indice< strlen($xml)){ + $pos = strpos($xml, "<$tag ", $indice); + if ($pos){ + $posCierre = strpos($xml, ">", $pos); + if ($xml[$posCierre-1] == "/"){ + $xml = substr_replace($xml, ">", $posCierre-1, 2); + } + $indice = $posCierre; + } + else break; + } + return $xml; + } +} + +/** + * Event handling class. + * + * @author Tobiasz Cudnik + * @package phpQuery + * @static + */ +abstract class phpQueryEvents { + /** + * Trigger a type of event on every matched element. + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $data + * + * @TODO exclusive events (with !) + * @TODO global events (test) + * @TODO support more than event in $type (space-separated) + */ + public static function trigger($document, $type, $data = array(), $node = null) { + // trigger: function(type, data, elem, donative, extra) { + $documentID = phpQuery::getDocumentID($document); + $namespace = null; + if (strpos($type, '.') !== false) + list($name, $namespace) = explode('.', $type); + else + $name = $type; + if (! $node) { + if (self::issetGlobal($documentID, $type)) { + $pq = phpQuery::getDocument($documentID); + // TODO check add($pq->document) + $pq->find('*')->add($pq->document) + ->trigger($type, $data); + } + } else { + if (isset($data[0]) && $data[0] instanceof DOMEvent) { + $event = $data[0]; + $event->relatedTarget = $event->target; + $event->target = $node; + $data = array_slice($data, 1); + } else { + $event = new DOMEvent(array( + 'type' => $type, + 'target' => $node, + 'timeStamp' => time(), + )); + } + $i = 0; + while($node) { + // TODO whois + phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on " + ."node \n");//.phpQueryObject::whois($node)."\n"); + $event->currentTarget = $node; + $eventNode = self::getNode($documentID, $node); + if (isset($eventNode->eventHandlers)) { + foreach($eventNode->eventHandlers as $eventType => $handlers) { + $eventNamespace = null; + if (strpos($type, '.') !== false) + list($eventName, $eventNamespace) = explode('.', $eventType); + else + $eventName = $eventType; + if ($name != $eventName) + continue; + if ($namespace && $eventNamespace && $namespace != $eventNamespace) + continue; + foreach($handlers as $handler) { + phpQuery::debug("Calling event handler\n"); + $event->data = $handler['data'] + ? $handler['data'] + : null; + $params = array_merge(array($event), $data); + $return = phpQuery::callbackRun($handler['callback'], $params); + if ($return === false) { + $event->bubbles = false; + } + } + } + } + // to bubble or not to bubble... + if (! $event->bubbles) + break; + $node = $node->parentNode; + $i++; + } + } + } + /** + * Binds a handler to one or more events (like click) for each matched element. + * Can also bind custom events. + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $data Optional + * @param unknown_type $callback + * + * @TODO support '!' (exclusive) events + * @TODO support more than event in $type (space-separated) + * @TODO support binding to global events + */ + public static function add($document, $node, $type, $data, $callback = null) { + phpQuery::debug("Binding '$type' event"); + $documentID = phpQuery::getDocumentID($document); +// if (is_null($callback) && is_callable($data)) { +// $callback = $data; +// $data = null; +// } + $eventNode = self::getNode($documentID, $node); + if (! $eventNode) + $eventNode = self::setNode($documentID, $node); + if (!isset($eventNode->eventHandlers[$type])) + $eventNode->eventHandlers[$type] = array(); + $eventNode->eventHandlers[$type][] = array( + 'callback' => $callback, + 'data' => $data, + ); + } + /** + * Enter description here... + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $callback + * + * @TODO namespace events + * @TODO support more than event in $type (space-separated) + */ + public static function remove($document, $node, $type = null, $callback = null) { + $documentID = phpQuery::getDocumentID($document); + $eventNode = self::getNode($documentID, $node); + if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) { + if ($callback) { + foreach($eventNode->eventHandlers[$type] as $k => $handler) + if ($handler['callback'] == $callback) + unset($eventNode->eventHandlers[$type][$k]); + } else { + unset($eventNode->eventHandlers[$type]); + } + } + } + protected static function getNode($documentID, $node) { + foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) { + if ($node->isSameNode($eventNode)) + return $eventNode; + } + } + protected static function setNode($documentID, $node) { + phpQuery::$documents[$documentID]->eventsNodes[] = $node; + return phpQuery::$documents[$documentID]->eventsNodes[ + count(phpQuery::$documents[$documentID]->eventsNodes)-1 + ]; + } + protected static function issetGlobal($documentID, $type) { + return isset(phpQuery::$documents[$documentID]) + ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal) + : false; + } +} + + +interface ICallbackNamed { + function hasName(); + function getName(); +} +/** + * Callback class introduces currying-like pattern. + * + * Example: + * function foo($param1, $param2, $param3) { + * var_dump($param1, $param2, $param3); + * } + * $fooCurried = new Callback('foo', + * 'param1 is now statically set', + * new CallbackParam, new CallbackParam + * ); + * phpQuery::callbackRun($fooCurried, + * array('param2 value', 'param3 value' + * ); + * + * Callback class is supported in all phpQuery methods which accepts callbacks. + * + * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures + * @author Tobiasz Cudnik + * + * @TODO??? return fake forwarding function created via create_function + * @TODO honor paramStructure + */ +class Callback + implements ICallbackNamed { + public $callback = null; + public $params = null; + protected $name; + public function __construct($callback, $param1 = null, $param2 = null, + $param3 = null) { + $params = func_get_args(); + $params = array_slice($params, 1); + if ($callback instanceof Callback) { + // TODO implement recurention + } else { + $this->callback = $callback; + $this->params = $params; + } + } + public function getName() { + return 'Callback: '.$this->name; + } + public function hasName() { + return isset($this->name) && $this->name; + } + public function setName($name) { + $this->name = $name; + return $this; + } + // TODO test me +// public function addParams() { +// $params = func_get_args(); +// return new Callback($this->callback, $this->params+$params); +// } +} +/** + * Shorthand for new Callback(create_function(...), ...); + * + * @author Tobiasz Cudnik + */ +class CallbackBody extends Callback { + public function __construct($paramList, $code, $param1 = null, $param2 = null, + $param3 = null) { + $params = func_get_args(); + $params = array_slice($params, 2); + $this->callback = create_function($paramList, $code); + $this->params = $params; + } +} +/** + * Callback type which on execution returns reference passed during creation. + * + * @author Tobiasz Cudnik + */ +class CallbackReturnReference extends Callback + implements ICallbackNamed { + protected $reference; + public function __construct(&$reference, $name = null){ + $this->reference =& $reference; + $this->callback = array($this, 'callback'); + } + public function callback() { + return $this->reference; + } + public function getName() { + return 'Callback: '.$this->name; + } + public function hasName() { + return isset($this->name) && $this->name; + } +} +/** + * Callback type which on execution returns value passed during creation. + * + * @author Tobiasz Cudnik + */ +class CallbackReturnValue extends Callback + implements ICallbackNamed { + protected $value; + protected $name; + public function __construct($value, $name = null){ + $this->value =& $value; + $this->name = $name; + $this->callback = array($this, 'callback'); + } + public function callback() { + return $this->value; + } + public function __toString() { + return $this->getName(); + } + public function getName() { + return 'Callback: '.$this->name; + } + public function hasName() { + return isset($this->name) && $this->name; + } +} +/** + * CallbackParameterToReference can be used when we don't really want a callback, + * only parameter passed to it. CallbackParameterToReference takes first + * parameter's value and passes it to reference. + * + * @author Tobiasz Cudnik + */ +class CallbackParameterToReference extends Callback { + /** + * @param $reference + * @TODO implement $paramIndex; + * param index choose which callback param will be passed to reference + */ + public function __construct(&$reference){ + $this->callback =& $reference; + } +} +//class CallbackReference extends Callback { +// /** +// * +// * @param $reference +// * @param $paramIndex +// * @todo implement $paramIndex; param index choose which callback param will be passed to reference +// */ +// public function __construct(&$reference, $name = null){ +// $this->callback =& $reference; +// } +//} +class CallbackParam {} + +/** + * Class representing phpQuery objects. + * + * @author Tobiasz Cudnik + * @package phpQuery + * @method phpQueryObject clone() clone() + * @method phpQueryObject empty() empty() + * @method phpQueryObject next() next($selector = null) + * @method phpQueryObject prev() prev($selector = null) + * @property Int $length + */ +class phpQueryObject + implements Iterator, Countable, ArrayAccess { + public $documentID = null; + /** + * DOMDocument class. + * + * @var DOMDocument + */ + public $document = null; + public $charset = null; + /** + * + * @var DOMDocumentWrapper + */ + public $documentWrapper = null; + /** + * XPath interface. + * + * @var DOMXPath + */ + public $xpath = null; + /** + * Stack of selected elements. + * @TODO refactor to ->nodes + * @var array + */ + public $elements = array(); + /** + * @access private + */ + protected $elementsBackup = array(); + /** + * @access private + */ + protected $previous = null; + /** + * @access private + * @TODO deprecate + */ + protected $root = array(); + /** + * Indicated if doument is just a fragment (no tag). + * + * Every document is realy a full document, so even documentFragments can + * be queried against , but getDocument(id)->htmlOuter() will return + * only contents of . + * + * @var bool + */ + public $documentFragment = true; + /** + * Iterator interface helper + * @access private + */ + protected $elementsInterator = array(); + /** + * Iterator interface helper + * @access private + */ + protected $valid = false; + /** + * Iterator interface helper + * @access private + */ + protected $current = null; + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function __construct($documentID) { +// if ($documentID instanceof self) +// var_dump($documentID->getDocumentID()); + $id = $documentID instanceof self + ? $documentID->getDocumentID() + : $documentID; +// var_dump($id); + if (! isset(phpQuery::$documents[$id] )) { +// var_dump(phpQuery::$documents); + throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first."); + } + $this->documentID = $id; + $this->documentWrapper =& phpQuery::$documents[$id]; + $this->document =& $this->documentWrapper->document; + $this->xpath =& $this->documentWrapper->xpath; + $this->charset =& $this->documentWrapper->charset; + $this->documentFragment =& $this->documentWrapper->isDocumentFragment; + // TODO check $this->DOM->documentElement; +// $this->root = $this->document->documentElement; + $this->root =& $this->documentWrapper->root; +// $this->toRoot(); + $this->elements = array($this->root); + } + /** + * + * @access private + * @param $attr + * @return unknown_type + */ + public function __get($attr) { + switch($attr) { + // FIXME doesnt work at all ? + case 'length': + return $this->size(); + break; + default: + return $this->$attr; + } + } + /** + * Saves actual object to $var by reference. + * Useful when need to break chain. + * @param phpQueryObject $var + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function toReference(&$var) { + return $var = $this; + } + public function documentFragment($state = null) { + if ($state) { + phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state; + return $this; + } + return $this->documentFragment; + } + /** + * @access private + * @TODO documentWrapper + */ + protected function isRoot( $node) { +// return $node instanceof DOMDOCUMENT || $node->tagName == 'html'; + return $node instanceof DOMDOCUMENT + || ($node instanceof DOMELEMENT && $node->tagName == 'html') + || $this->root->isSameNode($node); + } + /** + * @access private + */ + protected function stackIsRoot() { + return $this->size() == 1 && $this->isRoot($this->elements[0]); + } + /** + * Enter description here... + * NON JQUERY METHOD + * + * Watch out, it doesn't creates new instance, can be reverted with end(). + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function toRoot() { + $this->elements = array($this->root); + return $this; +// return $this->newInstance(array($this->root)); + } + /** + * Saves object's DocumentID to $var by reference. + * + * $myDocumentId; + * phpQuery::newDocument('
') + * ->getDocumentIDRef($myDocumentId) + * ->find('div')->... + * + * + * @param unknown_type $domId + * @see phpQuery::newDocument + * @see phpQuery::newDocumentFile + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocumentIDRef(&$documentID) { + $documentID = $this->getDocumentID(); + return $this; + } + /** + * Returns object with stack set to document root. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocument() { + return phpQuery::getDocument($this->getDocumentID()); + } + /** + * + * @return DOMDocument + */ + public function getDOMDocument() { + return $this->document; + } + /** + * Get object's Document ID. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocumentID() { + return $this->documentID; + } + /** + * Unloads whole document from memory. + * CAUTION! None further operations will be possible on this document. + * All objects refering to it will be useless. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function unloadDocument() { + phpQuery::unloadDocuments($this->getDocumentID()); + } + public function isHTML() { + return $this->documentWrapper->isHTML; + } + public function isXHTML() { + return $this->documentWrapper->isXHTML; + } + public function isXML() { + return $this->documentWrapper->isXML; + } + /** + * Enter description here... + * + * @link http://docs.jquery.com/Ajax/serialize + * @return string + */ + public function serialize() { + return phpQuery::param($this->serializeArray()); + } + /** + * Enter description here... + * + * @link http://docs.jquery.com/Ajax/serializeArray + * @return array + */ + public function serializeArray($submit = null) { + $source = $this->filter('form, input, select, textarea') + ->find('input, select, textarea') + ->andSelf() + ->not('form'); + $return = array(); +// $source->dumpDie(); + foreach($source as $input) { + $input = phpQuery::pq($input); + if ($input->is('[disabled]')) + continue; + if (!$input->is('[name]')) + continue; + if ($input->is('[type=checkbox]') && !$input->is('[checked]')) + continue; + // jquery diff + if ($submit && $input->is('[type=submit]')) { + if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit)) + continue; + else if (is_string($submit) && $input->attr('name') != $submit) + continue; + } + $return[] = array( + 'name' => $input->attr('name'), + 'value' => $input->val(), + ); + } + return $return; + } + /** + * @access private + */ + protected function debug($in) { + if (! phpQuery::$debug ) + return; + print('
');
+		print_r($in);
+		// file debug
+//		file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
+		// quite handy debug trace
+//		if ( is_array($in))
+//			print_r(array_slice(debug_backtrace(), 3));
+		print("
\n"); + } + /** + * @access private + */ + protected function isRegexp($pattern) { + return in_array( + $pattern[ mb_strlen($pattern)-1 ], + array('^','*','$') + ); + } + /** + * Determines if $char is really a char. + * + * @param string $char + * @return bool + * @todo rewrite me to charcode range ! ;) + * @access private + */ + protected function isChar($char) { + return extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? mb_eregi('\w', $char) + : preg_match('@\w@', $char); + } + /** + * @access private + */ + protected function parseSelector($query) { + // clean spaces + // TODO include this inside parsing ? + $query = trim( + preg_replace('@\s+@', ' ', + preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) + ) + ); + $queries = array(array()); + if (! $query) + return $queries; + $return =& $queries[0]; + $specialChars = array('>',' '); +// $specialCharsMapping = array('/' => '>'); + $specialCharsMapping = array(); + $strlen = mb_strlen($query); + $classChars = array('.', '-'); + $pseudoChars = array('-'); + $tagChars = array('*', '|', '-'); + // split multibyte string + // http://code.google.com/p/phpquery/issues/detail?id=76 + $_query = array(); + for ($i=0; $i<$strlen; $i++) + $_query[] = mb_substr($query, $i, 1); + $query = $_query; + // it works, but i dont like it... + $i = 0; + while( $i < $strlen) { + $c = $query[$i]; + $tmp = ''; + // TAG + if ($this->isChar($c) || in_array($c, $tagChars)) { + while(isset($query[$i]) + && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) { + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // IDs + } else if ( $c == '#') { + $i++; + while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) { + $tmp .= $query[$i]; + $i++; + } + $return[] = '#'.$tmp; + // SPECIAL CHARS + } else if (in_array($c, $specialChars)) { + $return[] = $c; + $i++; + // MAPPED SPECIAL MULTICHARS +// } else if ( $c.$query[$i+1] == '//') { +// $return[] = ' '; +// $i = $i+2; + // MAPPED SPECIAL CHARS + } else if ( isset($specialCharsMapping[$c])) { + $return[] = $specialCharsMapping[$c]; + $i++; + // COMMA + } else if ( $c == ',') { + $queries[] = array(); + $return =& $queries[ count($queries)-1 ]; + $i++; + while( isset($query[$i]) && $query[$i] == ' ') + $i++; + // CLASSES + } else if ($c == '.') { + while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // ~ General Sibling Selector + } else if ($c == '~') { + $spaceAllowed = true; + $tmp .= $query[$i++]; + while( isset($query[$i]) + && ($this->isChar($query[$i]) + || in_array($query[$i], $classChars) + || $query[$i] == '*' + || ($query[$i] == ' ' && $spaceAllowed) + )) { + if ($query[$i] != ' ') + $spaceAllowed = false; + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // + Adjacent sibling selectors + } else if ($c == '+') { + $spaceAllowed = true; + $tmp .= $query[$i++]; + while( isset($query[$i]) + && ($this->isChar($query[$i]) + || in_array($query[$i], $classChars) + || $query[$i] == '*' + || ($spaceAllowed && $query[$i] == ' ') + )) { + if ($query[$i] != ' ') + $spaceAllowed = false; + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // ATTRS + } else if ($c == '[') { + $stack = 1; + $tmp .= $c; + while( isset($query[++$i])) { + $tmp .= $query[$i]; + if ( $query[$i] == '[') { + $stack++; + } else if ( $query[$i] == ']') { + $stack--; + if (! $stack ) + break; + } + } + $return[] = $tmp; + $i++; + // PSEUDO CLASSES + } else if ($c == ':') { + $stack = 1; + $tmp .= $query[$i++]; + while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { + $tmp .= $query[$i]; + $i++; + } + // with arguments ? + if ( isset($query[$i]) && $query[$i] == '(') { + $tmp .= $query[$i]; + $stack = 1; + while( isset($query[++$i])) { + $tmp .= $query[$i]; + if ( $query[$i] == '(') { + $stack++; + } else if ( $query[$i] == ')') { + $stack--; + if (! $stack ) + break; + } + } + $return[] = $tmp; + $i++; + } else { + $return[] = $tmp; + } + } else { + $i++; + } + } + foreach($queries as $k => $q) { + if (isset($q[0])) { + if (isset($q[0][0]) && $q[0][0] == ':') + array_unshift($queries[$k], '*'); + if ($q[0] != '>') + array_unshift($queries[$k], ' '); + } + } + return $queries; + } + /** + * Return matched DOM nodes. + * + * @param int $index + * @return array|DOMElement Single DOMElement or array of DOMElement. + */ + public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { + $return = isset($index) + ? (isset($this->elements[$index]) ? $this->elements[$index] : null) + : $this->elements; + // pass thou callbacks + $args = func_get_args(); + $args = array_slice($args, 1); + foreach($args as $callback) { + if (is_array($return)) + foreach($return as $k => $v) + $return[$k] = phpQuery::callbackRun($callback, array($v)); + else + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + /** + * Return matched DOM nodes. + * jQuery difference. + * + * @param int $index + * @return array|string Returns string if $index != null + * @todo implement callbacks + * @todo return only arrays ? + * @todo maybe other name... + */ + public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { + if ($index) + $return = $this->eq($index)->text(); + else { + $return = array(); + for($i = 0; $i < $this->size(); $i++) { + $return[] = $this->eq($i)->text(); + } + } + // pass thou callbacks + $args = func_get_args(); + $args = array_slice($args, 1); + foreach($args as $callback) { + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + /** + * Return matched DOM nodes. + * jQuery difference. + * + * @param int $index + * @return array|string Returns string if $index != null + * @todo implement callbacks + * @todo return only arrays ? + * @todo maybe other name... + */ + public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { + if ($index) + $return = $this->eq($index)->text(); + else { + $return = array(); + for($i = 0; $i < $this->size(); $i++) { + $return[] = $this->eq($i)->text(); + } + // pass thou callbacks + $args = func_get_args(); + $args = array_slice($args, 1); + } + foreach($args as $callback) { + if (is_array($return)) + foreach($return as $k => $v) + $return[$k] = phpQuery::callbackRun($callback, array($v)); + else + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + /** + * Returns new instance of actual class. + * + * @param array $newStack Optional. Will replace old stack with new and move old one to history.c + */ + public function newInstance($newStack = null) { + $class = get_class($this); + // support inheritance by passing old object to overloaded constructor + $new = $class != 'phpQuery' + ? new $class($this, $this->getDocumentID()) + : new phpQueryObject($this->getDocumentID()); + $new->previous = $this; + if (is_null($newStack)) { + $new->elements = $this->elements; + if ($this->elementsBackup) + $this->elements = $this->elementsBackup; + } else if (is_string($newStack)) { + $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack(); + } else { + $new->elements = $newStack; + } + return $new; + } + /** + * Enter description here... + * + * In the future, when PHP will support XLS 2.0, then we would do that this way: + * contains(tokenize(@class, '\s'), "something") + * @param unknown_type $class + * @param unknown_type $node + * @return boolean + * @access private + */ + protected function matchClasses($class, $node) { + // multi-class + if ( mb_strpos($class, '.', 1)) { + $classes = explode('.', substr($class, 1)); + $classesCount = count( $classes ); + $nodeClasses = explode(' ', $node->getAttribute('class') ); + $nodeClassesCount = count( $nodeClasses ); + if ( $classesCount > $nodeClassesCount ) + return false; + $diff = count( + array_diff( + $classes, + $nodeClasses + ) + ); + if (! $diff ) + return true; + // single-class + } else { + return in_array( + // strip leading dot from class name + substr($class, 1), + // get classes for element as array + explode(' ', $node->getAttribute('class') ) + ); + } + } + /** + * @access private + */ + protected function runQuery($XQuery, $selector = null, $compare = null) { + if ($compare && ! method_exists($this, $compare)) + return false; + $stack = array(); + if (! $this->elements) + $this->debug('Stack empty, skipping...'); +// var_dump($this->elements[0]->nodeType); + // element, document + foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) { + $detachAfter = false; + // to work on detached nodes we need temporary place them somewhere + // thats because context xpath queries sucks ;] + $testNode = $stackNode; + while ($testNode) { + if (! $testNode->parentNode && ! $this->isRoot($testNode)) { + $this->root->appendChild($testNode); + $detachAfter = $testNode; + break; + } + $testNode = isset($testNode->parentNode) + ? $testNode->parentNode + : null; + } + // XXX tmp ? + $xpath = $this->documentWrapper->isXHTML + ? $this->getNodeXpath($stackNode, 'html') + : $this->getNodeXpath($stackNode); + // FIXME pseudoclasses-only query, support XML + $query = $XQuery == '//' && $xpath == '/html[1]' + ? '//*' + : $xpath.$XQuery; + $this->debug("XPATH: {$query}"); + // run query, get elements + $nodes = $this->xpath->query($query); + $this->debug("QUERY FETCHED"); + if (! $nodes->length ) + $this->debug('Nothing found'); + $debug = array(); + foreach($nodes as $node) { + $matched = false; + if ( $compare) { + phpQuery::$debug ? + $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") + : null; + $phpQueryDebug = phpQuery::$debug; + phpQuery::$debug = false; + // TODO ??? use phpQuery::callbackRun() + if (call_user_func_array(array($this, $compare), array($selector, $node))) + $matched = true; + phpQuery::$debug = $phpQueryDebug; + } else { + $matched = true; + } + if ( $matched) { + if (phpQuery::$debug) + $debug[] = $this->whois( $node ); + $stack[] = $node; + } + } + if (phpQuery::$debug) { + $this->debug("Matched ".count($debug).": ".implode(', ', $debug)); + } + if ($detachAfter) + $this->root->removeChild($detachAfter); + } + $this->elements = $stack; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function find($selectors, $context = null, $noHistory = false) { + if (!$noHistory) + // backup last stack /for end()/ + $this->elementsBackup = $this->elements; + // allow to define context + // TODO combine code below with phpQuery::pq() context guessing code + // as generic function + if ($context) { + if (! is_array($context) && $context instanceof DOMELEMENT) + $this->elements = array($context); + else if (is_array($context)) { + $this->elements = array(); + foreach ($context as $c) + if ($c instanceof DOMELEMENT) + $this->elements[] = $c; + } else if ( $context instanceof self ) + $this->elements = $context->elements; + } + $queries = $this->parseSelector($selectors); + $this->debug(array('FIND', $selectors, $queries)); + $XQuery = ''; + // remember stack state because of multi-queries + $oldStack = $this->elements; + // here we will be keeping found elements + $stack = array(); + foreach($queries as $selector) { + $this->elements = $oldStack; + $delimiterBefore = false; + foreach($selector as $s) { + // TAG + $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*' + : preg_match('@^[\w|\||-]+$@', $s) || $s == '*'; + if ($isTag) { + if ($this->isXML()) { + // namespace support + if (mb_strpos($s, '|') !== false) { + $ns = $tag = null; + list($ns, $tag) = explode('|', $s); + $XQuery .= "$ns:$tag"; + } else if ($s == '*') { + $XQuery .= "*"; + } else { + $XQuery .= "*[local-name()='$s']"; + } + } else { + $XQuery .= $s; + } + // ID + } else if ($s[0] == '#') { + if ($delimiterBefore) + $XQuery .= '*'; + $XQuery .= "[@id='".substr($s, 1)."']"; + // ATTRIBUTES + } else if ($s[0] == '[') { + if ($delimiterBefore) + $XQuery .= '*'; + // strip side brackets + $attr = trim($s, ']['); + $execute = false; + // attr with specifed value + if (mb_strpos($s, '=')) { + $value = null; + list($attr, $value) = explode('=', $attr); + $value = trim($value, "'\""); + if ($this->isRegexp($attr)) { + // cut regexp character + $attr = substr($attr, 0, -1); + $execute = true; + $XQuery .= "[@{$attr}]"; + } else { + $XQuery .= "[@{$attr}='{$value}']"; + } + // attr without specified value + } else { + $XQuery .= "[@{$attr}]"; + } + if ($execute) { + $this->runQuery($XQuery, $s, 'is'); + $XQuery = ''; + if (! $this->length()) + break; + } + // CLASSES + } else if ($s[0] == '.') { + // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]"); + // thx wizDom ;) + if ($delimiterBefore) + $XQuery .= '*'; + $XQuery .= '[@class]'; + $this->runQuery($XQuery, $s, 'matchClasses'); + $XQuery = ''; + if (! $this->length() ) + break; + // ~ General Sibling Selector + } else if ($s[0] == '~') { + $this->runQuery($XQuery); + $XQuery = ''; + $this->elements = $this + ->siblings( + substr($s, 1) + )->elements; + if (! $this->length() ) + break; + // + Adjacent sibling selectors + } else if ($s[0] == '+') { + // TODO /following-sibling:: + $this->runQuery($XQuery); + $XQuery = ''; + $subSelector = substr($s, 1); + $subElements = $this->elements; + $this->elements = array(); + foreach($subElements as $node) { + // search first DOMElement sibling + $test = $node->nextSibling; + while($test && ! ($test instanceof DOMELEMENT)) + $test = $test->nextSibling; + if ($test && $this->is($subSelector, $test)) + $this->elements[] = $test; + } + if (! $this->length() ) + break; + // PSEUDO CLASSES + } else if ($s[0] == ':') { + // TODO optimization for :first :last + if ($XQuery) { + $this->runQuery($XQuery); + $XQuery = ''; + } + if (! $this->length()) + break; + $this->pseudoClasses($s); + if (! $this->length()) + break; + // DIRECT DESCENDANDS + } else if ($s == '>') { + $XQuery .= '/'; + $delimiterBefore = 2; + // ALL DESCENDANDS + } else if ($s == ' ') { + $XQuery .= '//'; + $delimiterBefore = 2; + // ERRORS + } else { + phpQuery::debug("Unrecognized token '$s'"); + } + $delimiterBefore = $delimiterBefore === 2; + } + // run query if any + if ($XQuery && $XQuery != '//') { + $this->runQuery($XQuery); + $XQuery = ''; + } + foreach($this->elements as $node) + if (! $this->elementsContainsNode($node, $stack)) + $stack[] = $node; + } + $this->elements = $stack; + return $this->newInstance(); + } + /** + * @todo create API for classes with pseudoselectors + * @access private + */ + protected function pseudoClasses($class) { + // TODO clean args parsing ? + $class = ltrim($class, ':'); + $haveArgs = mb_strpos($class, '('); + if ($haveArgs !== false) { + $args = substr($class, $haveArgs+1, -1); + $class = substr($class, 0, $haveArgs); + } + switch($class) { + case 'even': + case 'odd': + $stack = array(); + foreach($this->elements as $i => $node) { + if ($class == 'even' && ($i%2) == 0) + $stack[] = $node; + else if ( $class == 'odd' && $i % 2 ) + $stack[] = $node; + } + $this->elements = $stack; + break; + case 'eq': + $k = intval($args); + $this->elements = isset( $this->elements[$k] ) + ? array( $this->elements[$k] ) + : array(); + break; + case 'gt': + $this->elements = array_slice($this->elements, $args+1); + break; + case 'lt': + $this->elements = array_slice($this->elements, 0, $args+1); + break; + case 'first': + if (isset($this->elements[0])) + $this->elements = array($this->elements[0]); + break; + case 'last': + if ($this->elements) + $this->elements = array($this->elements[count($this->elements)-1]); + break; + /*case 'parent': + $stack = array(); + foreach($this->elements as $node) { + if ( $node->childNodes->length ) + $stack[] = $node; + } + $this->elements = $stack; + break;*/ + case 'contains': + $text = trim($args, "\"'"); + $stack = array(); + foreach($this->elements as $node) { + if (mb_stripos($node->textContent, $text) === false) + continue; + $stack[] = $node; + } + $this->elements = $stack; + break; + case 'not': + $selector = self::unQuote($args); + $this->elements = $this->not($selector)->stack(); + break; + case 'slice': + // TODO jQuery difference ? + $args = explode(',', + str_replace(', ', ',', trim($args, "\"'")) + ); + $start = $args[0]; + $end = isset($args[1]) + ? $args[1] + : null; + if ($end > 0) + $end = $end-$start; + $this->elements = array_slice($this->elements, $start, $end); + break; + case 'has': + $selector = trim($args, "\"'"); + $stack = array(); + foreach($this->stack(1) as $el) { + if ($this->find($selector, $el, true)->length) + $stack[] = $el; + } + $this->elements = $stack; + break; + case 'submit': + case 'reset': + $this->elements = phpQuery::merge( + $this->map(array($this, 'is'), + "input[type=$class]", new CallbackParam() + ), + $this->map(array($this, 'is'), + "button[type=$class]", new CallbackParam() + ) + ); + break; +// $stack = array(); +// foreach($this->elements as $node) +// if ($node->is('input[type=submit]') || $node->is('button[type=submit]')) +// $stack[] = $el; +// $this->elements = $stack; + case 'input': + $this->elements = $this->map( + array($this, 'is'), + 'input', new CallbackParam() + )->elements; + break; + case 'password': + case 'checkbox': + case 'radio': + case 'hidden': + case 'image': + case 'file': + $this->elements = $this->map( + array($this, 'is'), + "input[type=$class]", new CallbackParam() + )->elements; + break; + case 'parent': + $this->elements = $this->map( + create_function('$node', ' + return $node instanceof DOMELEMENT && $node->childNodes->length + ? $node : null;') + )->elements; + break; + case 'empty': + $this->elements = $this->map( + create_function('$node', ' + return $node instanceof DOMELEMENT && $node->childNodes->length + ? null : $node;') + )->elements; + break; + case 'disabled': + case 'selected': + case 'checked': + $this->elements = $this->map( + array($this, 'is'), + "[$class]", new CallbackParam() + )->elements; + break; + case 'enabled': + $this->elements = $this->map( + create_function('$node', ' + return pq($node)->not(":disabled") ? $node : null;') + )->elements; + break; + case 'header': + $this->elements = $this->map( + create_function('$node', + '$isHeader = isset($node->tagName) && in_array($node->tagName, array( + "h1", "h2", "h3", "h4", "h5", "h6", "h7" + )); + return $isHeader + ? $node + : null;') + )->elements; +// $this->elements = $this->map( +// create_function('$node', '$node = pq($node); +// return $node->is("h1") +// || $node->is("h2") +// || $node->is("h3") +// || $node->is("h4") +// || $node->is("h5") +// || $node->is("h6") +// || $node->is("h7") +// ? $node +// : null;') +// )->elements; + break; + case 'only-child': + $this->elements = $this->map( + create_function('$node', + 'return pq($node)->siblings()->size() == 0 ? $node : null;') + )->elements; + break; + case 'first-child': + $this->elements = $this->map( + create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;') + )->elements; + break; + case 'last-child': + $this->elements = $this->map( + create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;') + )->elements; + break; + case 'nth-child': + $param = trim($args, "\"'"); + if (! $param) + break; + // nth-child(n+b) to nth-child(1n+b) + if ($param{0} == 'n') + $param = '1'.$param; + // :nth-child(index/even/odd/equation) + if ($param == 'even' || $param == 'odd') + $mapped = $this->map( + create_function('$node, $param', + '$index = pq($node)->prevAll()->size()+1; + if ($param == "even" && ($index%2) == 0) + return $node; + else if ($param == "odd" && $index%2 == 1) + return $node; + else + return null;'), + new CallbackParam(), $param + ); + else if (mb_strlen($param) > 1 && $param{1} == 'n') + // an+b + $mapped = $this->map( + create_function('$node, $param', + '$prevs = pq($node)->prevAll()->size(); + $index = 1+$prevs; + $b = mb_strlen($param) > 3 + ? $param{3} + : 0; + $a = $param{0}; + if ($b && $param{2} == "-") + $b = -$b; + if ($a > 0) { + return ($index-$b)%$a == 0 + ? $node + : null; + phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs"); + return $a*floor($index/$a)+$b-1 == $prevs + ? $node + : null; + } else if ($a == 0) + return $index == $b + ? $node + : null; + else + // negative value + return $index <= $b + ? $node + : null; +// if (! $b) +// return $index%$a == 0 +// ? $node +// : null; +// else +// return ($index-$b)%$a == 0 +// ? $node +// : null; + '), + new CallbackParam(), $param + ); + else + // index + $mapped = $this->map( + create_function('$node, $index', + '$prevs = pq($node)->prevAll()->size(); + if ($prevs && $prevs == $index-1) + return $node; + else if (! $prevs && $index == 1) + return $node; + else + return null;'), + new CallbackParam(), $param + ); + $this->elements = $mapped->elements; + break; + default: + $this->debug("Unknown pseudoclass '{$class}', skipping..."); + } + } + /** + * @access private + */ + protected function __pseudoClassParam($paramsString) { + // TODO; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function is($selector, $nodes = null) { + phpQuery::debug(array("Is:", $selector)); + if (! $selector) + return false; + $oldStack = $this->elements; + $returnArray = false; + if ($nodes && is_array($nodes)) { + $this->elements = $nodes; + } else if ($nodes) + $this->elements = array($nodes); + $this->filter($selector, true); + $stack = $this->elements; + $this->elements = $oldStack; + if ($nodes) + return $stack ? $stack : null; + return (bool)count($stack); + } + /** + * Enter description here... + * jQuery difference. + * + * Callback: + * - $index int + * - $node DOMNode + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @link http://docs.jquery.com/Traversing/filter + */ + public function filterCallback($callback, $_skipHistory = false) { + if (! $_skipHistory) { + $this->elementsBackup = $this->elements; + $this->debug("Filtering by callback"); + } + $newStack = array(); + foreach($this->elements as $index => $node) { + $result = phpQuery::callbackRun($callback, array($index, $node)); + if (is_null($result) || (! is_null($result) && $result)) + $newStack[] = $node; + } + $this->elements = $newStack; + return $_skipHistory + ? $this + : $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @link http://docs.jquery.com/Traversing/filter + */ + public function filter($selectors, $_skipHistory = false) { + if ($selectors instanceof Callback OR $selectors instanceof Closure) + return $this->filterCallback($selectors, $_skipHistory); + if (! $_skipHistory) + $this->elementsBackup = $this->elements; + $notSimpleSelector = array(' ', '>', '~', '+', '/'); + if (! is_array($selectors)) + $selectors = $this->parseSelector($selectors); + if (! $_skipHistory) + $this->debug(array("Filtering:", $selectors)); + $finalStack = array(); + foreach($selectors as $selector) { + $stack = array(); + if (! $selector) + break; + // avoid first space or / + if (in_array($selector[0], $notSimpleSelector)) + $selector = array_slice($selector, 1); + // PER NODE selector chunks + foreach($this->stack() as $node) { + $break = false; + foreach($selector as $s) { + if (!($node instanceof DOMELEMENT)) { + // all besides DOMElement + if ( $s[0] == '[') { + $attr = trim($s, '[]'); + if ( mb_strpos($attr, '=')) { + list( $attr, $val ) = explode('=', $attr); + if ($attr == 'nodeType' && $node->nodeType != $val) + $break = true; + } + } else + $break = true; + } else { + // DOMElement only + // ID + if ( $s[0] == '#') { + if ( $node->getAttribute('id') != substr($s, 1) ) + $break = true; + // CLASSES + } else if ( $s[0] == '.') { + if (! $this->matchClasses( $s, $node ) ) + $break = true; + // ATTRS + } else if ( $s[0] == '[') { + // strip side brackets + $attr = trim($s, '[]'); + if (mb_strpos($attr, '=')) { + list($attr, $val) = explode('=', $attr); + $val = self::unQuote($val); + if ($attr == 'nodeType') { + if ($val != $node->nodeType) + $break = true; + } else if ($this->isRegexp($attr)) { + $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? quotemeta(trim($val, '"\'')) + : preg_quote(trim($val, '"\''), '@'); + // switch last character + switch( substr($attr, -1)) { + // quotemeta used insted of preg_quote + // http://code.google.com/p/phpquery/issues/detail?id=76 + case '^': + $pattern = '^'.$val; + break; + case '*': + $pattern = '.*'.$val.'.*'; + break; + case '$': + $pattern = '.*'.$val.'$'; + break; + } + // cut last character + $attr = substr($attr, 0, -1); + $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? mb_ereg_match($pattern, $node->getAttribute($attr)) + : preg_match("@{$pattern}@", $node->getAttribute($attr)); + if (! $isMatch) + $break = true; + } else if ($node->getAttribute($attr) != $val) + $break = true; + } else if (! $node->hasAttribute($attr)) + $break = true; + // PSEUDO CLASSES + } else if ( $s[0] == ':') { + // skip + // TAG + } else if (trim($s)) { + if ($s != '*') { + // TODO namespaces + if (isset($node->tagName)) { + if ($node->tagName != $s) + $break = true; + } else if ($s == 'html' && ! $this->isRoot($node)) + $break = true; + } + // AVOID NON-SIMPLE SELECTORS + } else if (in_array($s, $notSimpleSelector)) { + $break = true; + $this->debug(array('Skipping non simple selector', $selector)); + } + } + if ($break) + break; + } + // if element passed all chunks of selector - add it to new stack + if (! $break ) + $stack[] = $node; + } + $tmpStack = $this->elements; + $this->elements = $stack; + // PER ALL NODES selector chunks + foreach($selector as $s) + // PSEUDO CLASSES + if ($s[0] == ':') + $this->pseudoClasses($s); + foreach($this->elements as $node) + // XXX it should be merged without duplicates + // but jQuery doesnt do that + $finalStack[] = $node; + $this->elements = $tmpStack; + } + $this->elements = $finalStack; + if ($_skipHistory) { + return $this; + } else { + $this->debug("Stack length after filter(): ".count($finalStack)); + return $this->newInstance(); + } + } + /** + * + * @param $value + * @return unknown_type + * @TODO implement in all methods using passed parameters + */ + protected static function unQuote($value) { + return $value[0] == '\'' || $value[0] == '"' + ? substr($value, 1, -1) + : $value; + } + /** + * Enter description here... + * + * @link http://docs.jquery.com/Ajax/load + * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo Support $selector + */ + public function load($url, $data = null, $callback = null) { + if ($data && ! is_array($data)) { + $callback = $data; + $data = null; + } + if (mb_strpos($url, ' ') !== false) { + $matches = null; + if (extension_loaded('mbstring') && phpQuery::$mbstringSupport) + mb_ereg('^([^ ]+) (.*)$', $url, $matches); + else + preg_match('^([^ ]+) (.*)$', $url, $matches); + $url = $matches[1]; + $selector = $matches[2]; + // FIXME this sucks, pass as callback param + $this->_loadSelector = $selector; + } + $ajax = array( + 'url' => $url, + 'type' => $data ? 'POST' : 'GET', + 'data' => $data, + 'complete' => $callback, + 'success' => array($this, '__loadSuccess') + ); + phpQuery::ajax($ajax); + return $this; + } + /** + * @access private + * @param $html + * @return unknown_type + */ + public function __loadSuccess($html) { + if ($this->_loadSelector) { + $html = phpQuery::newDocument($html)->find($this->_loadSelector); + unset($this->_loadSelector); + } + foreach($this->stack(1) as $node) { + phpQuery::pq($node, $this->getDocumentID()) + ->markup($html); + } + } + /** + * Enter description here... + * + * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo + */ + public function css() { + // TODO + return $this; + } + /** + * @todo + * + */ + public function show(){ + // TODO + return $this; + } + /** + * @todo + * + */ + public function hide(){ + // TODO + return $this; + } + /** + * Trigger a type of event on every matched element. + * + * @param unknown_type $type + * @param unknown_type $data + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO support more than event in $type (space-separated) + */ + public function trigger($type, $data = array()) { + foreach($this->elements as $node) + phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node); + return $this; + } + /** + * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions. + * + * @param unknown_type $type + * @param unknown_type $data + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO + */ + public function triggerHandler($type, $data = array()) { + // TODO; + } + /** + * Binds a handler to one or more events (like click) for each matched element. + * Can also bind custom events. + * + * @param unknown_type $type + * @param unknown_type $data Optional + * @param unknown_type $callback + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO support '!' (exclusive) events + * @TODO support more than event in $type (space-separated) + */ + public function bind($type, $data, $callback = null) { + // TODO check if $data is callable, not using is_callable + if (! isset($callback)) { + $callback = $data; + $data = null; + } + foreach($this->elements as $node) + phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback); + return $this; + } + /** + * Enter description here... + * + * @param unknown_type $type + * @param unknown_type $callback + * @return unknown + * @TODO namespace events + * @TODO support more than event in $type (space-separated) + */ + public function unbind($type = null, $callback = null) { + foreach($this->elements as $node) + phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function change($callback = null) { + if ($callback) + return $this->bind('change', $callback); + return $this->trigger('change'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function submit($callback = null) { + if ($callback) + return $this->bind('submit', $callback); + return $this->trigger('submit'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function click($callback = null) { + if ($callback) + return $this->bind('click', $callback); + return $this->trigger('click'); + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapAllOld($wrapper) { + $wrapper = pq($wrapper)->_clone(); + if (! $wrapper->length() || ! $this->length() ) + return $this; + $wrapper->insertBefore($this->elements[0]); + $deepest = $wrapper->elements[0]; + while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) + $deepest = $deepest->firstChild; + pq($deepest)->append($this); + return $this; + } + /** + * Enter description here... + * + * TODO testme... + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapAll($wrapper) { + if (! $this->length()) + return $this; + return phpQuery::pq($wrapper, $this->getDocumentID()) + ->clone() + ->insertBefore($this->get(0)) + ->map(array($this, '___wrapAllCallback')) + ->append($this); + } + /** + * + * @param $node + * @return unknown_type + * @access private + */ + public function ___wrapAllCallback($node) { + $deepest = $node; + while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) + $deepest = $deepest->firstChild; + return $deepest; + } + /** + * Enter description here... + * NON JQUERY METHOD + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapAllPHP($codeBefore, $codeAfter) { + return $this + ->slice(0, 1) + ->beforePHP($codeBefore) + ->end() + ->slice(-1) + ->afterPHP($codeAfter) + ->end(); + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrap($wrapper) { + foreach($this->stack() as $node) + phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper); + return $this; + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapPHP($codeBefore, $codeAfter) { + foreach($this->stack() as $node) + phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter); + return $this; + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapInner($wrapper) { + foreach($this->stack() as $node) + phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper); + return $this; + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function wrapInnerPHP($codeBefore, $codeAfter) { + foreach($this->stack(1) as $node) + phpQuery::pq($node, $this->getDocumentID())->contents() + ->wrapAllPHP($codeBefore, $codeAfter); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @testme Support for text nodes + */ + public function contents() { + $stack = array(); + foreach($this->stack(1) as $el) { + // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56 +// if (! isset($el->childNodes)) +// continue; + foreach($el->childNodes as $node) { + $stack[] = $node; + } + } + return $this->newInstance($stack); + } + /** + * Enter description here... + * + * jQuery difference. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function contentsUnwrap() { + foreach($this->stack(1) as $node) { + if (! $node->parentNode ) + continue; + $childNodes = array(); + // any modification in DOM tree breaks childNodes iteration, so cache them first + foreach($node->childNodes as $chNode ) + $childNodes[] = $chNode; + foreach($childNodes as $chNode ) +// $node->parentNode->appendChild($chNode); + $node->parentNode->insertBefore($chNode, $node); + $node->parentNode->removeChild($node); + } + return $this; + } + /** + * Enter description here... + * + * jQuery difference. + */ + public function switchWith($markup) { + $markup = pq($markup, $this->getDocumentID()); + $content = null; + foreach($this->stack(1) as $node) { + pq($node) + ->contents()->toReference($content)->end() + ->replaceWith($markup->clone()->append($content)); + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function eq($num) { + $oldStack = $this->elements; + $this->elementsBackup = $this->elements; + $this->elements = array(); + if ( isset($oldStack[$num]) ) + $this->elements[] = $oldStack[$num]; + return $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function size() { + return count($this->elements); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @deprecated Use length as attribute + */ + public function length() { + return $this->size(); + } + public function count() { + return $this->size(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo $level + */ + public function end($level = 1) { +// $this->elements = array_pop( $this->history ); +// return $this; +// $this->previous->DOM = $this->DOM; +// $this->previous->XPath = $this->XPath; + return $this->previous + ? $this->previous + : $this; + } + /** + * Enter description here... + * Normal use ->clone() . + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function _clone() { + $newStack = array(); + //pr(array('copy... ', $this->whois())); + //$this->dumpHistory('copy'); + $this->elementsBackup = $this->elements; + foreach($this->elements as $node) { + $newStack[] = $node->cloneNode(true); + } + $this->elements = $newStack; + return $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function replaceWithPHP($code) { + return $this->replaceWith(phpQuery::php($code)); + } + /** + * Enter description here... + * + * @param String|phpQuery $content + * @link http://docs.jquery.com/Manipulation/replaceWith#content + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function replaceWith($content) { + return $this->after($content)->remove(); + } + /** + * Enter description here... + * + * @param String $selector + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo this works ? + */ + public function replaceAll($selector) { + foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node) + phpQuery::pq($node, $this->getDocumentID()) + ->after($this->_clone()) + ->remove(); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function remove($selector = null) { + $loop = $selector + ? $this->filter($selector)->elements + : $this->elements; + foreach($loop as $node) { + if (! $node->parentNode ) + continue; + if (isset($node->tagName)) + $this->debug("Removing '{$node->tagName}'"); + $node->parentNode->removeChild($node); + // Mutation event + $event = new DOMEvent(array( + 'target' => $node, + 'type' => 'DOMNodeRemoved' + )); + phpQueryEvents::trigger($this->getDocumentID(), + $event->type, array($event), $node + ); + } + return $this; + } + protected function markupEvents($newMarkup, $oldMarkup, $node) { + if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) { + $event = new DOMEvent(array( + 'target' => $node, + 'type' => 'change' + )); + phpQueryEvents::trigger($this->getDocumentID(), + $event->type, array($event), $node + ); + } + } + /** + * jQuey difference + * + * @param $markup + * @return unknown_type + * @TODO trigger change event for textarea + */ + public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) { + $args = func_get_args(); + if ($this->documentWrapper->isXML) + return call_user_func_array(array($this, 'xml'), $args); + else + return call_user_func_array(array($this, 'html'), $args); + } + /** + * jQuey difference + * + * @param $markup + * @return unknown_type + */ + public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) { + $args = func_get_args(); + if ($this->documentWrapper->isXML) + return call_user_func_array(array($this, 'xmlOuter'), $args); + else + return call_user_func_array(array($this, 'htmlOuter'), $args); + } + /** + * Enter description here... + * + * @param unknown_type $html + * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO force html result + */ + public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) { + if (isset($html)) { + // INSERT + $nodes = $this->documentWrapper->import($html); + $this->empty(); + foreach($this->stack(1) as $alreadyAdded => $node) { + // for now, limit events for textarea + if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') + $oldHtml = pq($node, $this->getDocumentID())->markup(); + foreach($nodes as $newNode) { + $node->appendChild($alreadyAdded + ? $newNode->cloneNode(true) + : $newNode + ); + } + // for now, limit events for textarea + if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') + $this->markupEvents($html, $oldHtml, $node); + } + return $this; + } else { + // FETCH + $return = $this->documentWrapper->markup($this->elements, true); + $args = func_get_args(); + foreach(array_slice($args, 1) as $callback) { + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + } + /** + * @TODO force xml result + */ + public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) { + $args = func_get_args(); + return call_user_func_array(array($this, 'html'), $args); + } + /** + * Enter description here... + * @TODO force html result + * + * @return String + */ + public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { + $markup = $this->documentWrapper->markup($this->elements); + // pass thou callbacks + $args = func_get_args(); + foreach($args as $callback) { + $markup = phpQuery::callbackRun($callback, array($markup)); + } + return $markup; + } + /** + * @TODO force xml result + */ + public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { + $args = func_get_args(); + return call_user_func_array(array($this, 'htmlOuter'), $args); + } + public function __toString() { + return $this->markupOuter(); + } + /** + * Just like html(), but returns markup with VALID (dangerous) PHP tags. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo support returning markup with PHP tags when called without param + */ + public function php($code = null) { + return $this->markupPHP($code); + } + /** + * Enter description here... + * + * @param $code + * @return unknown_type + */ + public function markupPHP($code = null) { + return isset($code) + ? $this->markup(phpQuery::php($code)) + : phpQuery::markupToPHP($this->markup()); + } + /** + * Enter description here... + * + * @param $code + * @return unknown_type + */ + public function markupOuterPHP() { + return phpQuery::markupToPHP($this->markupOuter()); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function children($selector = null) { + $stack = array(); + foreach($this->stack(1) as $node) { +// foreach($node->getElementsByTagName('*') as $newNode) { + foreach($node->childNodes as $newNode) { + if ($newNode->nodeType != 1) + continue; + if ($selector && ! $this->is($selector, $newNode)) + continue; + if ($this->elementsContainsNode($newNode, $stack)) + continue; + $stack[] = $newNode; + } + } + $this->elementsBackup = $this->elements; + $this->elements = $stack; + return $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function ancestors($selector = null) { + return $this->children( $selector ); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function append( $content) { + return $this->insert($content, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function appendPHP( $content) { + return $this->insert("", 'append'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function appendTo( $seletor) { + return $this->insert($seletor, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function prepend( $content) { + return $this->insert($content, __FUNCTION__); + } + /** + * Enter description here... + * + * @todo accept many arguments, which are joined, arrays maybe also + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function prependPHP( $content) { + return $this->insert("", 'prepend'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function prependTo( $seletor) { + return $this->insert($seletor, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function before($content) { + return $this->insert($content, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function beforePHP( $content) { + return $this->insert("", 'before'); + } + /** + * Enter description here... + * + * @param String|phpQuery + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function insertBefore( $seletor) { + return $this->insert($seletor, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function after( $content) { + return $this->insert($content, __FUNCTION__); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function afterPHP( $content) { + return $this->insert("", 'after'); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function insertAfter( $seletor) { + return $this->insert($seletor, __FUNCTION__); + } + /** + * Internal insert method. Don't use it. + * + * @param unknown_type $target + * @param unknown_type $type + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function insert($target, $type) { + $this->debug("Inserting data with '{$type}'"); + $to = false; + switch( $type) { + case 'appendTo': + case 'prependTo': + case 'insertBefore': + case 'insertAfter': + $to = true; + } + switch(gettype($target)) { + case 'string': + $insertFrom = $insertTo = array(); + if ($to) { + // INSERT TO + $insertFrom = $this->elements; + if (phpQuery::isMarkup($target)) { + // $target is new markup, import it + $insertTo = $this->documentWrapper->import($target); + // insert into selected element + } else { + // $tagret is a selector + $thisStack = $this->elements; + $this->toRoot(); + $insertTo = $this->find($target)->elements; + $this->elements = $thisStack; + } + } else { + // INSERT FROM + $insertTo = $this->elements; + $insertFrom = $this->documentWrapper->import($target); + } + break; + case 'object': + $insertFrom = $insertTo = array(); + // phpQuery + if ($target instanceof self) { + if ($to) { + $insertTo = $target->elements; + if ($this->documentFragment && $this->stackIsRoot()) + // get all body children +// $loop = $this->find('body > *')->elements; + // TODO test it, test it hard... +// $loop = $this->newInstance($this->root)->find('> *')->elements; + $loop = $this->root->childNodes; + else + $loop = $this->elements; + // import nodes if needed + $insertFrom = $this->getDocumentID() == $target->getDocumentID() + ? $loop + : $target->documentWrapper->import($loop); + } else { + $insertTo = $this->elements; + if ( $target->documentFragment && $target->stackIsRoot() ) + // get all body children +// $loop = $target->find('body > *')->elements; + $loop = $target->root->childNodes; + else + $loop = $target->elements; + // import nodes if needed + $insertFrom = $this->getDocumentID() == $target->getDocumentID() + ? $loop + : $this->documentWrapper->import($loop); + } + // DOMNODE + } elseif ($target instanceof DOMNODE) { + // import node if needed +// if ( $target->ownerDocument != $this->DOM ) +// $target = $this->DOM->importNode($target, true); + if ( $to) { + $insertTo = array($target); + if ($this->documentFragment && $this->stackIsRoot()) + // get all body children + $loop = $this->root->childNodes; +// $loop = $this->find('body > *')->elements; + else + $loop = $this->elements; + foreach($loop as $fromNode) + // import nodes if needed + $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument) + ? $target->ownerDocument->importNode($fromNode, true) + : $fromNode; + } else { + // import node if needed + if (! $target->ownerDocument->isSameNode($this->document)) + $target = $this->document->importNode($target, true); + $insertTo = $this->elements; + $insertFrom[] = $target; + } + } + break; + } + phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes"); + foreach($insertTo as $insertNumber => $toNode) { + // we need static relative elements in some cases + switch( $type) { + case 'prependTo': + case 'prepend': + $firstChild = $toNode->firstChild; + break; + case 'insertAfter': + case 'after': + $nextSibling = $toNode->nextSibling; + break; + } + foreach($insertFrom as $fromNode) { + // clone if inserted already before + $insert = $insertNumber + ? $fromNode->cloneNode(true) + : $fromNode; + switch($type) { + case 'appendTo': + case 'append': +// $toNode->insertBefore( +// $fromNode, +// $toNode->lastChild->nextSibling +// ); + $toNode->appendChild($insert); + $eventTarget = $insert; + break; + case 'prependTo': + case 'prepend': + $toNode->insertBefore( + $insert, + $firstChild + ); + break; + case 'insertBefore': + case 'before': + if (! $toNode->parentNode) + throw new Exception("No parentNode, can't do {$type}()"); + else + $toNode->parentNode->insertBefore( + $insert, + $toNode + ); + break; + case 'insertAfter': + case 'after': + if (! $toNode->parentNode) + throw new Exception("No parentNode, can't do {$type}()"); + else + $toNode->parentNode->insertBefore( + $insert, + $nextSibling + ); + break; + } + // Mutation event + $event = new DOMEvent(array( + 'target' => $insert, + 'type' => 'DOMNodeInserted' + )); + phpQueryEvents::trigger($this->getDocumentID(), + $event->type, array($event), $insert + ); + } + } + return $this; + } + /** + * Enter description here... + * + * @return Int + */ + public function index($subject) { + $index = -1; + $subject = $subject instanceof phpQueryObject + ? $subject->elements[0] + : $subject; + foreach($this->newInstance() as $k => $node) { + if ($node->isSameNode($subject)) + $index = $k; + } + return $index; + } + /** + * Enter description here... + * + * @param unknown_type $start + * @param unknown_type $end + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @testme + */ + public function slice($start, $end = null) { +// $last = count($this->elements)-1; +// $end = $end +// ? min($end, $last) +// : $last; +// if ($start < 0) +// $start = $last+$start; +// if ($start > $last) +// return array(); + if ($end > 0) + $end = $end-$start; + return $this->newInstance( + array_slice($this->elements, $start, $end) + ); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function reverse() { + $this->elementsBackup = $this->elements; + $this->elements = array_reverse($this->elements); + return $this->newInstance(); + } + /** + * Return joined text content. + * @return String + */ + public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) { + if (isset($text)) + return $this->html(htmlspecialchars($text)); + $args = func_get_args(); + $args = array_slice($args, 1); + $return = ''; + foreach($this->elements as $node) { + $text = $node->textContent; + if (count($this->elements) > 1 && $text) + $text .= "\n"; + foreach($args as $callback) { + $text = phpQuery::callbackRun($callback, array($text)); + } + $return .= $text; + } + return $return; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function plugin($class, $file = null) { + phpQuery::plugin($class, $file); + return $this; + } + /** + * Deprecated, use $pq->plugin() instead. + * + * @deprecated + * @param $class + * @param $file + * @return unknown_type + */ + public static function extend($class, $file = null) { + return $this->plugin($class, $file); + } + /** + * + * @access private + * @param $method + * @param $args + * @return unknown_type + */ + public function __call($method, $args) { + $aliasMethods = array('clone', 'empty'); + if (isset(phpQuery::$extendMethods[$method])) { + array_unshift($args, $this); + return phpQuery::callbackRun( + phpQuery::$extendMethods[$method], $args + ); + } else if (isset(phpQuery::$pluginsMethods[$method])) { + array_unshift($args, $this); + $class = phpQuery::$pluginsMethods[$method]; + $realClass = "phpQueryObjectPlugin_$class"; + $return = call_user_func_array( + array($realClass, $method), + $args + ); + // XXX deprecate ? + return is_null($return) + ? $this + : $return; + } else if (in_array($method, $aliasMethods)) { + return call_user_func_array(array($this, '_'.$method), $args); + } else + throw new Exception("Method '{$method}' doesnt exist"); + } + /** + * Safe rename of next(). + * + * Use it ONLY when need to call next() on an iterated object (in same time). + * Normaly there is no need to do such thing ;) + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function _next($selector = null) { + return $this->newInstance( + $this->getElementSiblings('nextSibling', $selector, true) + ); + } + /** + * Use prev() and next(). + * + * @deprecated + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function _prev($selector = null) { + return $this->prev($selector); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function prev($selector = null) { + return $this->newInstance( + $this->getElementSiblings('previousSibling', $selector, true) + ); + } + /** + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo + */ + public function prevAll($selector = null) { + return $this->newInstance( + $this->getElementSiblings('previousSibling', $selector) + ); + } + /** + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo FIXME: returns source elements insted of next siblings + */ + public function nextAll($selector = null) { + return $this->newInstance( + $this->getElementSiblings('nextSibling', $selector) + ); + } + /** + * @access private + */ + protected function getElementSiblings($direction, $selector = null, $limitToOne = false) { + $stack = array(); + $count = 0; + foreach($this->stack() as $node) { + $test = $node; + while( isset($test->{$direction}) && $test->{$direction}) { + $test = $test->{$direction}; + if (! $test instanceof DOMELEMENT) + continue; + $stack[] = $test; + if ($limitToOne) + break; + } + } + if ($selector) { + $stackOld = $this->elements; + $this->elements = $stack; + $stack = $this->filter($selector, true)->stack(); + $this->elements = $stackOld; + } + return $stack; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function siblings($selector = null) { + $stack = array(); + $siblings = array_merge( + $this->getElementSiblings('previousSibling', $selector), + $this->getElementSiblings('nextSibling', $selector) + ); + foreach($siblings as $node) { + if (! $this->elementsContainsNode($node, $stack)) + $stack[] = $node; + } + return $this->newInstance($stack); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function not($selector = null) { + if (is_string($selector)) + phpQuery::debug(array('not', $selector)); + else + phpQuery::debug('not'); + $stack = array(); + if ($selector instanceof self || $selector instanceof DOMNODE) { + foreach($this->stack() as $node) { + if ($selector instanceof self) { + $matchFound = false; + foreach($selector->stack() as $notNode) { + if ($notNode->isSameNode($node)) + $matchFound = true; + } + if (! $matchFound) + $stack[] = $node; + } else if ($selector instanceof DOMNODE) { + if (! $selector->isSameNode($node)) + $stack[] = $node; + } else { + if (! $this->is($selector)) + $stack[] = $node; + } + } + } else { + $orgStack = $this->stack(); + $matched = $this->filter($selector, true)->stack(); +// $matched = array(); +// // simulate OR in filter() instead of AND 5y +// foreach($this->parseSelector($selector) as $s) { +// $matched = array_merge($matched, +// $this->filter(array($s))->stack() +// ); +// } + foreach($orgStack as $node) + if (! $this->elementsContainsNode($node, $matched)) + $stack[] = $node; + } + return $this->newInstance($stack); + } + /** + * Enter description here... + * + * @param string|phpQueryObject + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function add($selector = null) { + if (! $selector) + return $this; + $stack = array(); + $this->elementsBackup = $this->elements; + $found = phpQuery::pq($selector, $this->getDocumentID()); + $this->merge($found->elements); + return $this->newInstance(); + } + /** + * @access private + */ + protected function merge() { + foreach(func_get_args() as $nodes) + foreach($nodes as $newNode ) + if (! $this->elementsContainsNode($newNode) ) + $this->elements[] = $newNode; + } + /** + * @access private + * TODO refactor to stackContainsNode + */ + protected function elementsContainsNode($nodeToCheck, $elementsStack = null) { + $loop = ! is_null($elementsStack) + ? $elementsStack + : $this->elements; + foreach($loop as $node) { + if ( $node->isSameNode( $nodeToCheck ) ) + return true; + } + return false; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function parent($selector = null) { + $stack = array(); + foreach($this->elements as $node ) + if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) ) + $stack[] = $node->parentNode; + $this->elementsBackup = $this->elements; + $this->elements = $stack; + if ( $selector ) + $this->filter($selector, true); + return $this->newInstance(); + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function parents($selector = null) { + $stack = array(); + if (! $this->elements ) + $this->debug('parents() - stack empty'); + foreach($this->elements as $node) { + $test = $node; + while( $test->parentNode) { + $test = $test->parentNode; + if ($this->isRoot($test)) + break; + if (! $this->elementsContainsNode($test, $stack)) { + $stack[] = $test; + continue; + } + } + } + $this->elementsBackup = $this->elements; + $this->elements = $stack; + if ( $selector ) + $this->filter($selector, true); + return $this->newInstance(); + } + /** + * Internal stack iterator. + * + * @access private + */ + public function stack($nodeTypes = null) { + if (!isset($nodeTypes)) + return $this->elements; + if (!is_array($nodeTypes)) + $nodeTypes = array($nodeTypes); + $return = array(); + foreach($this->elements as $node) { + if (in_array($node->nodeType, $nodeTypes)) + $return[] = $node; + } + return $return; + } + // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes + protected function attrEvents($attr, $oldAttr, $oldValue, $node) { + // skip events for XML documents + if (! $this->isXHTML() && ! $this->isHTML()) + return; + $event = null; + // identify + $isInputValue = $node->tagName == 'input' + && ( + in_array($node->getAttribute('type'), + array('text', 'password', 'hidden')) + || !$node->getAttribute('type') + ); + $isRadio = $node->tagName == 'input' + && $node->getAttribute('type') == 'radio'; + $isCheckbox = $node->tagName == 'input' + && $node->getAttribute('type') == 'checkbox'; + $isOption = $node->tagName == 'option'; + if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) { + $event = new DOMEvent(array( + 'target' => $node, + 'type' => 'change' + )); + } else if (($isRadio || $isCheckbox) && $attr == 'checked' && ( + // check + (! $oldAttr && $node->hasAttribute($attr)) + // un-check + || (! $node->hasAttribute($attr) && $oldAttr) + )) { + $event = new DOMEvent(array( + 'target' => $node, + 'type' => 'change' + )); + } else if ($isOption && $node->parentNode && $attr == 'selected' && ( + // select + (! $oldAttr && $node->hasAttribute($attr)) + // un-select + || (! $node->hasAttribute($attr) && $oldAttr) + )) { + $event = new DOMEvent(array( + 'target' => $node->parentNode, + 'type' => 'change' + )); + } + if ($event) { + phpQueryEvents::trigger($this->getDocumentID(), + $event->type, array($event), $node + ); + } + } + public function attr($attr = null, $value = null) { + foreach($this->stack(1) as $node) { + if (! is_null($value)) { + $loop = $attr == '*' + ? $this->getNodeAttrs($node) + : array($attr); + foreach($loop as $a) { + $oldValue = $node->getAttribute($a); + $oldAttr = $node->hasAttribute($a); + // TODO raises an error when charset other than UTF-8 + // while document's charset is also not UTF-8 + @$node->setAttribute($a, $value); + $this->attrEvents($a, $oldAttr, $oldValue, $node); + } + } else if ($attr == '*') { + // jQuery difference + $return = array(); + foreach($node->attributes as $n => $v) + $return[$n] = $v->value; + return $return; + } else + return $node->hasAttribute($attr) + ? $node->getAttribute($attr) + : null; + } + return is_null($value) + ? '' : $this; + } + /** + * @access private + */ + protected function getNodeAttrs($node) { + $return = array(); + foreach($node->attributes as $n => $o) + $return[] = $n; + return $return; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo check CDATA ??? + */ + public function attrPHP($attr, $code) { + if (! is_null($code)) { + $value = '<'.'?php '.$code.' ?'.'>'; + // TODO tempolary solution + // http://code.google.com/p/phpquery/issues/detail?id=17 +// if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII') +// $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES'); + } + foreach($this->stack(1) as $node) { + if (! is_null($code)) { +// $attrNode = $this->DOM->createAttribute($attr); + $node->setAttribute($attr, $value); +// $attrNode->value = $value; +// $node->appendChild($attrNode); + } else if ( $attr == '*') { + // jQuery diff + $return = array(); + foreach($node->attributes as $n => $v) + $return[$n] = $v->value; + return $return; + } else + return $node->getAttribute($attr); + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function removeAttr($attr) { + foreach($this->stack(1) as $node) { + $loop = $attr == '*' + ? $this->getNodeAttrs($node) + : array($attr); + foreach($loop as $a) { + $oldValue = $node->getAttribute($a); + $node->removeAttribute($a); + $this->attrEvents($a, $oldValue, null, $node); + } + } + return $this; + } + /** + * Return form element value. + * + * @return String Fields value. + */ + public function val($val = null) { + if (! isset($val)) { + if ($this->eq(0)->is('select')) { + $selected = $this->eq(0)->find('option[selected=selected]'); + if ($selected->is('[value]')) + return $selected->attr('value'); + else + return $selected->text(); + } else if ($this->eq(0)->is('textarea')) + return $this->eq(0)->markup(); + else + return $this->eq(0)->attr('value'); + } else { + $_val = null; + foreach($this->stack(1) as $node) { + $node = pq($node, $this->getDocumentID()); + if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) { + $isChecked = in_array($node->attr('value'), $val) + || in_array($node->attr('name'), $val); + if ($isChecked) + $node->attr('checked', 'checked'); + else + $node->removeAttr('checked'); + } else if ($node->get(0)->tagName == 'select') { + if (! isset($_val)) { + $_val = array(); + if (! is_array($val)) + $_val = array((string)$val); + else + foreach($val as $v) + $_val[] = $v; + } + foreach($node['option']->stack(1) as $option) { + $option = pq($option, $this->getDocumentID()); + $selected = false; + // XXX: workaround for string comparsion, see issue #96 + // http://code.google.com/p/phpquery/issues/detail?id=96 + $selected = is_null($option->attr('value')) + ? in_array($option->markup(), $_val) + : in_array($option->attr('value'), $_val); +// $optionValue = $option->attr('value'); +// $optionText = $option->text(); +// $optionTextLenght = mb_strlen($optionText); +// foreach($_val as $v) +// if ($optionValue == $v) +// $selected = true; +// else if ($optionText == $v && $optionTextLenght == mb_strlen($v)) +// $selected = true; + if ($selected) + $option->attr('selected', 'selected'); + else + $option->removeAttr('selected'); + } + } else if ($node->get(0)->tagName == 'textarea') + $node->markup($val); + else + $node->attr('value', $val); + } + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function andSelf() { + if ( $this->previous ) + $this->elements = array_merge($this->elements, $this->previous->elements); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function addClass( $className) { + if (! $className) + return $this; + foreach($this->stack(1) as $node) { + if (! $this->is(".$className", $node)) + $node->setAttribute( + 'class', + trim($node->getAttribute('class').' '.$className) + ); + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function addClassPHP( $className) { + foreach($this->stack(1) as $node) { + $classes = $node->getAttribute('class'); + $newValue = $classes + ? $classes.' <'.'?php '.$className.' ?'.'>' + : '<'.'?php '.$className.' ?'.'>'; + $node->setAttribute('class', $newValue); + } + return $this; + } + /** + * Enter description here... + * + * @param string $className + * @return bool + */ + public function hasClass($className) { + foreach($this->stack(1) as $node) { + if ( $this->is(".$className", $node)) + return true; + } + return false; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function removeClass($className) { + foreach($this->stack(1) as $node) { + $classes = explode( ' ', $node->getAttribute('class')); + if ( in_array($className, $classes)) { + $classes = array_diff($classes, array($className)); + if ( $classes ) + $node->setAttribute('class', implode(' ', $classes)); + else + $node->removeAttribute('class'); + } + } + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function toggleClass($className) { + foreach($this->stack(1) as $node) { + if ( $this->is( $node, '.'.$className )) + $this->removeClass($className); + else + $this->addClass($className); + } + return $this; + } + /** + * Proper name without underscore (just ->empty()) also works. + * + * Removes all child nodes from the set of matched elements. + * + * Example: + * pq("p")._empty() + * + * HTML: + *

Hello, Person and person

+ * + * Result: + * [

] + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @access private + */ + public function _empty() { + foreach($this->stack(1) as $node) { + // thx to 'dave at dgx dot cz' + $node->nodeValue = ''; + } + return $this; + } + /** + * Enter description here... + * + * @param array|string $callback Expects $node as first param, $index as second + * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope) + * @param array $arg1 Will ba passed as third and futher args to callback. + * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on... + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function each($callback, $param1 = null, $param2 = null, $param3 = null) { + $paramStructure = null; + if (func_num_args() > 1) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 1); + } + foreach($this->elements as $v) + phpQuery::callbackRun($callback, array($v), $paramStructure); + return $this; + } + /** + * Run callback on actual object. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function callback($callback, $param1 = null, $param2 = null, $param3 = null) { + $params = func_get_args(); + $params[0] = $this; + phpQuery::callbackRun($callback, $params); + return $this; + } + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @todo add $scope and $args as in each() ??? + */ + public function map($callback, $param1 = null, $param2 = null, $param3 = null) { +// $stack = array(); +//// foreach($this->newInstance() as $node) { +// foreach($this->newInstance() as $node) { +// $result = call_user_func($callback, $node); +// if ($result) +// $stack[] = $result; +// } + $params = func_get_args(); + array_unshift($params, $this->elements); + return $this->newInstance( + call_user_func_array(array('phpQuery', 'map'), $params) +// phpQuery::map($this->elements, $callback) + ); + } + /** + * Enter description here... + * + * @param $key + * @param $value + */ + public function data($key, $value = null) { + if (! isset($value)) { + // TODO? implement specific jQuery behavior od returning parent values + // is child which we look up doesn't exist + return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID()); + } else { + foreach($this as $node) + phpQuery::data($node, $key, $value, $this->getDocumentID()); + return $this; + } + } + /** + * Enter description here... + * + * @param $key + */ + public function removeData($key) { + foreach($this as $node) + phpQuery::removeData($node, $key, $this->getDocumentID()); + return $this; + } + // INTERFACE IMPLEMENTATIONS + + // ITERATOR INTERFACE + /** + * @access private + */ + public function rewind(){ + $this->debug('iterating foreach'); +// phpQuery::selectDocument($this->getDocumentID()); + $this->elementsBackup = $this->elements; + $this->elementsInterator = $this->elements; + $this->valid = isset( $this->elements[0] ) + ? 1 : 0; +// $this->elements = $this->valid +// ? array($this->elements[0]) +// : array(); + $this->current = 0; + } + /** + * @access private + */ + public function current(){ + return $this->elementsInterator[ $this->current ]; + } + /** + * @access private + */ + public function key(){ + return $this->current; + } + /** + * Double-function method. + * + * First: main iterator interface method. + * Second: Returning next sibling, alias for _next(). + * + * Proper functionality is choosed automagicaly. + * + * @see phpQueryObject::_next() + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function next($cssSelector = null){ +// if ($cssSelector || $this->valid) +// return $this->_next($cssSelector); + $this->valid = isset( $this->elementsInterator[ $this->current+1 ] ) + ? true + : false; + if (! $this->valid && $this->elementsInterator) { + $this->elementsInterator = null; + } else if ($this->valid) { + $this->current++; + } else { + return $this->_next($cssSelector); + } + } + /** + * @access private + */ + public function valid(){ + return $this->valid; + } + // ITERATOR INTERFACE END + // ARRAYACCESS INTERFACE + /** + * @access private + */ + public function offsetExists($offset) { + return $this->find($offset)->size() > 0; + } + /** + * @access private + */ + public function offsetGet($offset) { + return $this->find($offset); + } + /** + * @access private + */ + public function offsetSet($offset, $value) { +// $this->find($offset)->replaceWith($value); + $this->find($offset)->html($value); + } + /** + * @access private + */ + public function offsetUnset($offset) { + // empty + throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML."); + } + // ARRAYACCESS INTERFACE END + /** + * Returns node's XPath. + * + * @param unknown_type $oneNode + * @return string + * @TODO use native getNodePath is avaible + * @access private + */ + protected function getNodeXpath($oneNode = null, $namespace = null) { + $return = array(); + $loop = $oneNode + ? array($oneNode) + : $this->elements; +// if ($namespace) +// $namespace .= ':'; + foreach($loop as $node) { + if ($node instanceof DOMDOCUMENT) { + $return[] = ''; + continue; + } + $xpath = array(); + while(! ($node instanceof DOMDOCUMENT)) { + $i = 1; + $sibling = $node; + while($sibling->previousSibling) { + $sibling = $sibling->previousSibling; + $isElement = $sibling instanceof DOMELEMENT; + if ($isElement && $sibling->tagName == $node->tagName) + $i++; + } + $xpath[] = $this->isXML() + ? "*[local-name()='{$node->tagName}'][{$i}]" + : "{$node->tagName}[{$i}]"; + $node = $node->parentNode; + } + $xpath = join('/', array_reverse($xpath)); + $return[] = '/'.$xpath; + } + return $oneNode + ? $return[0] + : $return; + } + // HELPERS + public function whois($oneNode = null) { + $return = array(); + $loop = $oneNode + ? array( $oneNode ) + : $this->elements; + foreach($loop as $node) { + if (isset($node->tagName)) { + $tag = in_array($node->tagName, array('php', 'js')) + ? strtoupper($node->tagName) + : $node->tagName; + $return[] = $tag + .($node->getAttribute('id') + ? '#'.$node->getAttribute('id'):'') + .($node->getAttribute('class') + ? '.'.join('.', split(' ', $node->getAttribute('class'))):'') + .($node->getAttribute('name') + ? '[name="'.$node->getAttribute('name').'"]':'') + .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false + ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'') + .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false + ? '[value=PHP]':'') + .($node->getAttribute('selected') + ? '[selected]':'') + .($node->getAttribute('checked') + ? '[checked]':'') + ; + } else if ($node instanceof DOMTEXT) { + if (trim($node->textContent)) + $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15); + } else { + + } + } + return $oneNode && isset($return[0]) + ? $return[0] + : $return; + } + /** + * Dump htmlOuter and preserve chain. Usefull for debugging. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * + */ + public function dump() { + print 'DUMP #'.(phpQuery::$dumpCount++).' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; +// print __FILE__.':'.__LINE__."\n"; + var_dump($this->htmlOuter()); + return $this; + } + public function dumpWhois() { + print 'DUMP #'.(phpQuery::$dumpCount++).' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; +// print __FILE__.':'.__LINE__."\n"; + var_dump('whois', $this->whois()); + phpQuery::$debug = $debug; + return $this; + } + public function dumpLength() { + print 'DUMP #'.(phpQuery::$dumpCount++).' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; +// print __FILE__.':'.__LINE__."\n"; + var_dump('length', $this->length()); + phpQuery::$debug = $debug; + return $this; + } + public function dumpTree($html = true, $title = true) { + $output = $title + ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : ''; + $debug = phpQuery::$debug; + phpQuery::$debug = false; + foreach($this->stack() as $node) + $output .= $this->__dumpTree($node); + phpQuery::$debug = $debug; + print $html + ? nl2br(str_replace(' ', ' ', $output)) + : $output; + return $this; + } + private function __dumpTree($node, $intend = 0) { + $whois = $this->whois($node); + $return = ''; + if ($whois) + $return .= str_repeat(' - ', $intend).$whois."\n"; + if (isset($node->childNodes)) + foreach($node->childNodes as $chNode) + $return .= $this->__dumpTree($chNode, $intend+1); + return $return; + } + /** + * Dump htmlOuter and stop script execution. Usefull for debugging. + * + */ + public function dumpDie() { + print __FILE__.':'.__LINE__; + var_dump($this->htmlOuter()); + die(); + } +} + + +// -- Multibyte Compatibility functions --------------------------------------- +// http://svn.iphonewebdev.com/lace/lib/mb_compat.php + +/** + * mb_internal_encoding() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_internal_encoding')) +{ + function mb_internal_encoding($enc) {return true; } +} + +/** + * mb_regex_encoding() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_regex_encoding')) +{ + function mb_regex_encoding($enc) {return true; } +} + +/** + * mb_strlen() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_strlen')) +{ + function mb_strlen($str) + { + return strlen($str); + } +} + +/** + * mb_strpos() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_strpos')) +{ + function mb_strpos($haystack, $needle, $offset=0) + { + return strpos($haystack, $needle, $offset); + } +} +/** + * mb_stripos() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_stripos')) +{ + function mb_stripos($haystack, $needle, $offset=0) + { + return stripos($haystack, $needle, $offset); + } +} + +/** + * mb_substr() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_substr')) +{ + function mb_substr($str, $start, $length=0) + { + return substr($str, $start, $length); + } +} + +/** + * mb_substr_count() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_substr_count')) +{ + function mb_substr_count($haystack, $needle) + { + return substr_count($haystack, $needle); + } +} + + /** * Static namespace for phpQuery functions. * diff --git a/phpQuery/phpQuery/Callback.php b/phpQuery/phpQuery/Callback.php deleted file mode 100644 index d9a90ea..0000000 --- a/phpQuery/phpQuery/Callback.php +++ /dev/null @@ -1,152 +0,0 @@ - - * - * @TODO??? return fake forwarding function created via create_function - * @TODO honor paramStructure - */ -class Callback - implements ICallbackNamed { - public $callback = null; - public $params = null; - protected $name; - public function __construct($callback, $param1 = null, $param2 = null, - $param3 = null) { - $params = func_get_args(); - $params = array_slice($params, 1); - if ($callback instanceof Callback) { - // TODO implement recurention - } else { - $this->callback = $callback; - $this->params = $params; - } - } - public function getName() { - return 'Callback: '.$this->name; - } - public function hasName() { - return isset($this->name) && $this->name; - } - public function setName($name) { - $this->name = $name; - return $this; - } - // TODO test me -// public function addParams() { -// $params = func_get_args(); -// return new Callback($this->callback, $this->params+$params); -// } -} -/** - * Shorthand for new Callback(create_function(...), ...); - * - * @author Tobiasz Cudnik - */ -class CallbackBody extends Callback { - public function __construct($paramList, $code, $param1 = null, $param2 = null, - $param3 = null) { - $params = func_get_args(); - $params = array_slice($params, 2); - $this->callback = create_function($paramList, $code); - $this->params = $params; - } -} -/** - * Callback type which on execution returns reference passed during creation. - * - * @author Tobiasz Cudnik - */ -class CallbackReturnReference extends Callback - implements ICallbackNamed { - protected $reference; - public function __construct(&$reference, $name = null){ - $this->reference =& $reference; - $this->callback = array($this, 'callback'); - } - public function callback() { - return $this->reference; - } - public function getName() { - return 'Callback: '.$this->name; - } - public function hasName() { - return isset($this->name) && $this->name; - } -} -/** - * Callback type which on execution returns value passed during creation. - * - * @author Tobiasz Cudnik - */ -class CallbackReturnValue extends Callback - implements ICallbackNamed { - protected $value; - protected $name; - public function __construct($value, $name = null){ - $this->value =& $value; - $this->name = $name; - $this->callback = array($this, 'callback'); - } - public function callback() { - return $this->value; - } - public function __toString() { - return $this->getName(); - } - public function getName() { - return 'Callback: '.$this->name; - } - public function hasName() { - return isset($this->name) && $this->name; - } -} -/** - * CallbackParameterToReference can be used when we don't really want a callback, - * only parameter passed to it. CallbackParameterToReference takes first - * parameter's value and passes it to reference. - * - * @author Tobiasz Cudnik - */ -class CallbackParameterToReference extends Callback { - /** - * @param $reference - * @TODO implement $paramIndex; - * param index choose which callback param will be passed to reference - */ - public function __construct(&$reference){ - $this->callback =& $reference; - } -} -//class CallbackReference extends Callback { -// /** -// * -// * @param $reference -// * @param $paramIndex -// * @todo implement $paramIndex; param index choose which callback param will be passed to reference -// */ -// public function __construct(&$reference, $name = null){ -// $this->callback =& $reference; -// } -//} -class CallbackParam {} \ No newline at end of file diff --git a/phpQuery/phpQuery/DOMDocumentWrapper.php b/phpQuery/phpQuery/DOMDocumentWrapper.php deleted file mode 100644 index 0b6a7fd..0000000 --- a/phpQuery/phpQuery/DOMDocumentWrapper.php +++ /dev/null @@ -1,677 +0,0 @@ - changes to
- * - * @todo check XML catalogs compatibility - * @author Tobiasz Cudnik - * @package phpQuery - */ -class DOMDocumentWrapper { - /** - * @var DOMDocument - */ - public $document; - public $id; - /** - * @todo Rewrite as method and quess if null. - * @var unknown_type - */ - public $contentType = ''; - public $xpath; - public $uuid = 0; - public $data = array(); - public $dataNodes = array(); - public $events = array(); - public $eventsNodes = array(); - public $eventsGlobal = array(); - /** - * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28 - * @var unknown_type - */ - public $frames = array(); - /** - * Document root, by default equals to document itself. - * Used by documentFragments. - * - * @var DOMNode - */ - public $root; - public $isDocumentFragment; - public $isXML = false; - public $isXHTML = false; - public $isHTML = false; - public $charset; - public function __construct($markup = null, $contentType = null, $newDocumentID = null) { - if (isset($markup)) - $this->load($markup, $contentType, $newDocumentID); - $this->id = $newDocumentID - ? $newDocumentID - : md5(microtime()); - } - public function load($markup, $contentType = null, $newDocumentID = null) { -// phpQuery::$documents[$id] = $this; - $this->contentType = strtolower($contentType); - if ($markup instanceof DOMDOCUMENT) { - $this->document = $markup; - $this->root = $this->document; - $this->charset = $this->document->encoding; - // TODO isDocumentFragment - } else { - $loaded = $this->loadMarkup($markup); - } - if ($loaded) { -// $this->document->formatOutput = true; - $this->document->preserveWhiteSpace = true; - $this->xpath = new DOMXPath($this->document); - $this->afterMarkupLoad(); - return true; - // remember last loaded document -// return phpQuery::selectDocument($id); - } - return false; - } - protected function afterMarkupLoad() { - if ($this->isXHTML) { - $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml"); - } - } - protected function loadMarkup($markup) { - $loaded = false; - if ($this->contentType) { - self::debug("Load markup for content type {$this->contentType}"); - // content determined by contentType - list($contentType, $charset) = $this->contentTypeToArray($this->contentType); - switch($contentType) { - case 'text/html': - phpQuery::debug("Loading HTML, content type '{$this->contentType}'"); - $loaded = $this->loadMarkupHTML($markup, $charset); - break; - case 'text/xml': - case 'application/xhtml+xml': - phpQuery::debug("Loading XML, content type '{$this->contentType}'"); - $loaded = $this->loadMarkupXML($markup, $charset); - break; - default: - // for feeds or anything that sometimes doesn't use text/xml - if (strpos('xml', $this->contentType) !== false) { - phpQuery::debug("Loading XML, content type '{$this->contentType}'"); - $loaded = $this->loadMarkupXML($markup, $charset); - } else - phpQuery::debug("Could not determine document type from content type '{$this->contentType}'"); - } - } else { - // content type autodetection - if ($this->isXML($markup)) { - phpQuery::debug("Loading XML, isXML() == true"); - $loaded = $this->loadMarkupXML($markup); - if (! $loaded && $this->isXHTML) { - phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true'); - $loaded = $this->loadMarkupHTML($markup); - } - } else { - phpQuery::debug("Loading HTML, isXML() == false"); - $loaded = $this->loadMarkupHTML($markup); - } - } - return $loaded; - } - protected function loadMarkupReset() { - $this->isXML = $this->isXHTML = $this->isHTML = false; - } - protected function documentCreate($charset, $version = '1.0') { - if (! $version) - $version = '1.0'; - $this->document = new DOMDocument($version, $charset); - $this->charset = $this->document->encoding; -// $this->document->encoding = $charset; - $this->document->formatOutput = true; - $this->document->preserveWhiteSpace = true; - } - protected function loadMarkupHTML($markup, $requestedCharset = null) { - if (phpQuery::$debug) - phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250)); - $this->loadMarkupReset(); - $this->isHTML = true; - if (!isset($this->isDocumentFragment)) - $this->isDocumentFragment = self::isDocumentFragmentHTML($markup); - $charset = null; - $documentCharset = $this->charsetFromHTML($markup); - $addDocumentCharset = false; - if ($documentCharset) { - $charset = $documentCharset; - $markup = $this->charsetFixHTML($markup); - } else if ($requestedCharset) { - $charset = $requestedCharset; - } - if (! $charset) - $charset = phpQuery::$defaultCharset; - // HTTP 1.1 says that the default charset is ISO-8859-1 - // @see http://www.w3.org/International/O-HTTP-charset - if (! $documentCharset) { - $documentCharset = 'ISO-8859-1'; - $addDocumentCharset = true; - } - // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' - // Worse, some pages can have mixed encodings... we'll try not to worry about that - $requestedCharset = strtoupper($requestedCharset); - $documentCharset = strtoupper($documentCharset); - phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset"); - if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) { - phpQuery::debug("CHARSET CONVERT"); - // Document Encoding Conversion - // http://code.google.com/p/phpquery/issues/detail?id=86 - if (function_exists('mb_detect_encoding')) { - $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO'); - $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets)); - if (! $docEncoding) - $docEncoding = $documentCharset; // ok trust the document - phpQuery::debug("DETECTED '$docEncoding'"); - // Detected does not match what document says... - if ($docEncoding !== $documentCharset) { - // Tricky.. - } - if ($docEncoding !== $requestedCharset) { - phpQuery::debug("CONVERT $docEncoding => $requestedCharset"); - $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding); - $markup = $this->charsetAppendToHTML($markup, $requestedCharset); - $charset = $requestedCharset; - } - } else { - phpQuery::debug("TODO: charset conversion without mbstring..."); - } - } - $return = false; - if ($this->isDocumentFragment) { - phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'"); - $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); - } else { - if ($addDocumentCharset) { - phpQuery::debug("Full markup load (HTML), appending charset: '$charset'"); - $markup = $this->charsetAppendToHTML($markup, $charset); - } - phpQuery::debug("Full markup load (HTML), documentCreate('$charset')"); - $this->documentCreate($charset); - $return = phpQuery::$debug === 2 - ? $this->document->loadHTML($markup) - : @$this->document->loadHTML($markup); - if ($return) - $this->root = $this->document; - } - if ($return && ! $this->contentType) - $this->contentType = 'text/html'; - return $return; - } - protected function loadMarkupXML($markup, $requestedCharset = null) { - if (phpQuery::$debug) - phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250)); - $this->loadMarkupReset(); - $this->isXML = true; - // check agains XHTML in contentType or markup - $isContentTypeXHTML = $this->isXHTML(); - $isMarkupXHTML = $this->isXHTML($markup); - if ($isContentTypeXHTML || $isMarkupXHTML) { - self::debug('Full markup load (XML), XHTML detected'); - $this->isXHTML = true; - } - // determine document fragment - if (! isset($this->isDocumentFragment)) - $this->isDocumentFragment = $this->isXHTML - ? self::isDocumentFragmentXHTML($markup) - : self::isDocumentFragmentXML($markup); - // this charset will be used - $charset = null; - // charset from XML declaration @var string - $documentCharset = $this->charsetFromXML($markup); - if (! $documentCharset) { - if ($this->isXHTML) { - // this is XHTML, try to get charset from content-type meta header - $documentCharset = $this->charsetFromHTML($markup); - if ($documentCharset) { - phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'"); - $this->charsetAppendToXML($markup, $documentCharset); - $charset = $documentCharset; - } - } - if (! $documentCharset) { - // if still no document charset... - $charset = $requestedCharset; - } - } else if ($requestedCharset) { - $charset = $requestedCharset; - } - if (! $charset) { - $charset = phpQuery::$defaultCharset; - } - if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) { - // TODO place for charset conversion -// $charset = $requestedCharset; - } - $return = false; - if ($this->isDocumentFragment) { - phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'"); - $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); - } else { - // FIXME ??? - if ($isContentTypeXHTML && ! $isMarkupXHTML) - if (! $documentCharset) { - phpQuery::debug("Full markup load (XML), appending charset '$charset'"); - $markup = $this->charsetAppendToXML($markup, $charset); - } - // see http://pl2.php.net/manual/en/book.dom.php#78929 - // LIBXML_DTDLOAD (>= PHP 5.1) - // does XML ctalogues works with LIBXML_NONET - // $this->document->resolveExternals = true; - // TODO test LIBXML_COMPACT for performance improvement - // create document - $this->documentCreate($charset); - if (phpversion() < 5.1) { - $this->document->resolveExternals = true; - $return = phpQuery::$debug === 2 - ? $this->document->loadXML($markup) - : @$this->document->loadXML($markup); - } else { - /** @link http://pl2.php.net/manual/en/libxml.constants.php */ - $libxmlStatic = phpQuery::$debug === 2 - ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET - : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR; - $return = $this->document->loadXML($markup, $libxmlStatic); -// if (! $return) -// $return = $this->document->loadHTML($markup); - } - if ($return) - $this->root = $this->document; - } - if ($return) { - if (! $this->contentType) { - if ($this->isXHTML) - $this->contentType = 'application/xhtml+xml'; - else - $this->contentType = 'text/xml'; - } - return $return; - } else { - throw new Exception("Error loading XML markup"); - } - } - protected function isXHTML($markup = null) { - if (! isset($markup)) { - return strpos($this->contentType, 'xhtml') !== false; - } - // XXX ok ? - return strpos($markup, "doctype) && is_object($dom->doctype) -// ? $dom->doctype->publicId -// : self::$defaultDoctype; - } - protected function isXML($markup) { -// return strpos($markup, ']+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', - $markup, $matches - ); - if (! isset($matches[0])) - return array(null, null); - // get attr 'content' - preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches); - if (! isset($matches[0])) - return array(null, null); - return $this->contentTypeToArray($matches[2]); - } - protected function charsetFromHTML($markup) { - $contentType = $this->contentTypeFromHTML($markup); - return $contentType[1]; - } - protected function charsetFromXML($markup) { - $matches; - // find declaration - preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i', - $markup, $matches - ); - return isset($matches[2]) - ? strtolower($matches[2]) - : null; - } - /** - * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug. - * - * @link http://code.google.com/p/phpquery/issues/detail?id=80 - * @param $html - */ - protected function charsetFixHTML($markup) { - $matches = array(); - // find meta tag - preg_match('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', - $markup, $matches, PREG_OFFSET_CAPTURE - ); - if (! isset($matches[0])) - return; - $metaContentType = $matches[0][0]; - $markup = substr($markup, 0, $matches[0][1]) - .substr($markup, $matches[0][1]+strlen($metaContentType)); - $headStart = stripos($markup, ''); - $markup = substr($markup, 0, $headStart+6).$metaContentType - .substr($markup, $headStart+6); - return $markup; - } - protected function charsetAppendToHTML($html, $charset, $xhtml = false) { - // remove existing meta[type=content-type] - $html = preg_replace('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html); - $meta = ''; - if (strpos($html, ')@s', - "{$meta}", - $html - ); - } - } else { - return preg_replace( - '@)@s', - ''.$meta, - $html - ); - } - } - protected function charsetAppendToXML($markup, $charset) { - $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>'; - return $declaration.$markup; - } - public static function isDocumentFragmentHTML($markup) { - return stripos($markup, 'documentFragmentCreate($node, $sourceCharset); -// if ($fake === false) -// throw new Exception("Error loading documentFragment markup"); -// else -// $return = array_merge($return, -// $this->import($fake->root->childNodes) -// ); -// } else { -// $return[] = $this->document->importNode($node, true); -// } -// } -// return $return; -// } else { -// // string markup -// $fake = $this->documentFragmentCreate($source, $sourceCharset); -// if ($fake === false) -// throw new Exception("Error loading documentFragment markup"); -// else -// return $this->import($fake->root->childNodes); -// } - if (is_array($source) || $source instanceof DOMNODELIST) { - // dom nodes - self::debug('Importing nodes to document'); - foreach($source as $node) - $return[] = $this->document->importNode($node, true); - } else { - // string markup - $fake = $this->documentFragmentCreate($source, $sourceCharset); - if ($fake === false) - throw new Exception("Error loading documentFragment markup"); - else - return $this->import($fake->root->childNodes); - } - return $return; - } - /** - * Creates new document fragment. - * - * @param $source - * @return DOMDocumentWrapper - */ - protected function documentFragmentCreate($source, $charset = null) { - $fake = new DOMDocumentWrapper(); - $fake->contentType = $this->contentType; - $fake->isXML = $this->isXML; - $fake->isHTML = $this->isHTML; - $fake->isXHTML = $this->isXHTML; - $fake->root = $fake->document; - if (! $charset) - $charset = $this->charset; -// $fake->documentCreate($this->charset); - if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) - $source = array($source); - if (is_array($source) || $source instanceof DOMNODELIST) { - // dom nodes - // load fake document - if (! $this->documentFragmentLoadMarkup($fake, $charset)) - return false; - $nodes = $fake->import($source); - foreach($nodes as $node) - $fake->root->appendChild($node); - } else { - // string markup - $this->documentFragmentLoadMarkup($fake, $charset, $source); - } - return $fake; - } - /** - * - * @param $document DOMDocumentWrapper - * @param $markup - * @return $document - */ - private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) { - // TODO error handling - // TODO copy doctype - // tempolary turn off - $fragment->isDocumentFragment = false; - if ($fragment->isXML) { - if ($fragment->isXHTML) { - // add FAKE element to set default namespace - $fragment->loadMarkupXML('' - .'' - .''.$markup.''); - $fragment->root = $fragment->document->firstChild->nextSibling; - } else { - $fragment->loadMarkupXML(''.$markup.''); - $fragment->root = $fragment->document->firstChild; - } - } else { - $markup2 = phpQuery::$defaultDoctype.''; - $noBody = strpos($markup, 'loadMarkupHTML($markup2); - // TODO resolv body tag merging issue - $fragment->root = $noBody - ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling - : $fragment->document->firstChild->nextSibling->firstChild->nextSibling; - } - if (! $fragment->root) - return false; - $fragment->isDocumentFragment = true; - return true; - } - protected function documentFragmentToMarkup($fragment) { - phpQuery::debug('documentFragmentToMarkup'); - $tmp = $fragment->isDocumentFragment; - $fragment->isDocumentFragment = false; - $markup = $fragment->markup(); - if ($fragment->isXML) { - $markup = substr($markup, 0, strrpos($markup, '')); - if ($fragment->isXHTML) { - $markup = substr($markup, strpos($markup, '')+6); - } - } else { - $markup = substr($markup, strpos($markup, '')+6); - $markup = substr($markup, 0, strrpos($markup, '')); - } - $fragment->isDocumentFragment = $tmp; - if (phpQuery::$debug) - phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150)); - return $markup; - } - /** - * Return document markup, starting with optional $nodes as root. - * - * @param $nodes DOMNode|DOMNodeList - * @return string - */ - public function markup($nodes = null, $innerMarkup = false) { - if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT) - $nodes = null; - if (isset($nodes)) { - $markup = ''; - if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) ) - $nodes = array($nodes); - if ($this->isDocumentFragment && ! $innerMarkup) - foreach($nodes as $i => $node) - if ($node->isSameNode($this->root)) { - // var_dump($node); - $nodes = array_slice($nodes, 0, $i) - + phpQuery::DOMNodeListToArray($node->childNodes) - + array_slice($nodes, $i+1); - } - if ($this->isXML && ! $innerMarkup) { - self::debug("Getting outerXML with charset '{$this->charset}'"); - // we need outerXML, so we can benefit from - // $node param support in saveXML() - foreach($nodes as $node) - $markup .= $this->document->saveXML($node); - } else { - $loop = array(); - if ($innerMarkup) - foreach($nodes as $node) { - if ($node->childNodes) - foreach($node->childNodes as $child) - $loop[] = $child; - else - $loop[] = $node; - } - else - $loop = $nodes; - self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment"); - $fake = $this->documentFragmentCreate($loop); - $markup = $this->documentFragmentToMarkup($fake); - } - if ($this->isXHTML) { - self::debug("Fixing XHTML"); - $markup = self::markupFixXHTML($markup); - } - self::debug("Markup: ".substr($markup, 0, 250)); - return $markup; - } else { - if ($this->isDocumentFragment) { - // documentFragment, html only... - self::debug("Getting markup, DocumentFragment detected"); -// return $this->markup( -//// $this->document->getElementsByTagName('body')->item(0) -// $this->document->root, true -// ); - $markup = $this->documentFragmentToMarkup($this); - // no need for markupFixXHTML, as it's done thought markup($nodes) method - return $markup; - } else { - self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'"); - $markup = $this->isXML - ? $this->document->saveXML() - : $this->document->saveHTML(); - if ($this->isXHTML) { - self::debug("Fixing XHTML"); - $markup = self::markupFixXHTML($markup); - } - self::debug("Markup: ".substr($markup, 0, 250)); - return $markup; - } - } - } - protected static function markupFixXHTML($markup) { - $markup = self::expandEmptyTag('script', $markup); - $markup = self::expandEmptyTag('select', $markup); - $markup = self::expandEmptyTag('textarea', $markup); - return $markup; - } - public static function debug($text) { - phpQuery::debug($text); - } - /** - * expandEmptyTag - * - * @param $tag - * @param $xml - * @return unknown_type - * @author mjaque at ilkebenson dot com - * @link http://php.net/manual/en/domdocument.savehtml.php#81256 - */ - public static function expandEmptyTag($tag, $xml){ - $indice = 0; - while ($indice< strlen($xml)){ - $pos = strpos($xml, "<$tag ", $indice); - if ($pos){ - $posCierre = strpos($xml, ">", $pos); - if ($xml[$posCierre-1] == "/"){ - $xml = substr_replace($xml, ">", $posCierre-1, 2); - } - $indice = $posCierre; - } - else break; - } - return $xml; - } -} \ No newline at end of file diff --git a/phpQuery/phpQuery/DOMEvent.php b/phpQuery/phpQuery/DOMEvent.php deleted file mode 100644 index 0cb0c46..0000000 --- a/phpQuery/phpQuery/DOMEvent.php +++ /dev/null @@ -1,107 +0,0 @@ - - * @package phpQuery - * @todo implement ArrayAccess ? - */ -class DOMEvent { - /** - * Returns a boolean indicating whether the event bubbles up through the DOM or not. - * - * @var unknown_type - */ - public $bubbles = true; - /** - * Returns a boolean indicating whether the event is cancelable. - * - * @var unknown_type - */ - public $cancelable = true; - /** - * Returns a reference to the currently registered target for the event. - * - * @var unknown_type - */ - public $currentTarget; - /** - * Returns detail about the event, depending on the type of event. - * - * @var unknown_type - * @link http://developer.mozilla.org/en/DOM/event.detail - */ - public $detail; // ??? - /** - * Used to indicate which phase of the event flow is currently being evaluated. - * - * NOT IMPLEMENTED - * - * @var unknown_type - * @link http://developer.mozilla.org/en/DOM/event.eventPhase - */ - public $eventPhase; // ??? - /** - * The explicit original target of the event (Mozilla-specific). - * - * NOT IMPLEMENTED - * - * @var unknown_type - */ - public $explicitOriginalTarget; // moz only - /** - * The original target of the event, before any retargetings (Mozilla-specific). - * - * NOT IMPLEMENTED - * - * @var unknown_type - */ - public $originalTarget; // moz only - /** - * Identifies a secondary target for the event. - * - * @var unknown_type - */ - public $relatedTarget; - /** - * Returns a reference to the target to which the event was originally dispatched. - * - * @var unknown_type - */ - public $target; - /** - * Returns the time that the event was created. - * - * @var unknown_type - */ - public $timeStamp; - /** - * Returns the name of the event (case-insensitive). - */ - public $type; - public $runDefault = true; - public $data = null; - public function __construct($data) { - foreach($data as $k => $v) { - $this->$k = $v; - } - if (! $this->timeStamp) - $this->timeStamp = time(); - } - /** - * Cancels the event (if it is cancelable). - * - */ - public function preventDefault() { - $this->runDefault = false; - } - /** - * Stops the propagation of events further along in the DOM. - * - */ - public function stopPropagation() { - $this->bubbles = false; - } -} diff --git a/phpQuery/phpQuery/Zend/Exception.php b/phpQuery/phpQuery/Zend/Exception.php deleted file mode 100644 index 599d8a0..0000000 --- a/phpQuery/phpQuery/Zend/Exception.php +++ /dev/null @@ -1,30 +0,0 @@ - 5, - 'strictredirects' => false, - 'useragent' => 'Zend_Http_Client', - 'timeout' => 10, - 'adapter' => 'Zend_Http_Client_Adapter_Socket', - 'httpversion' => self::HTTP_1, - 'keepalive' => false, - 'storeresponse' => true, - 'strict' => true - ); - - /** - * The adapter used to preform the actual connection to the server - * - * @var Zend_Http_Client_Adapter_Interface - */ - protected $adapter = null; - - /** - * Request URI - * - * @var Zend_Uri_Http - */ - protected $uri; - - /** - * Associative array of request headers - * - * @var array - */ - protected $headers = array(); - - /** - * HTTP request method - * - * @var string - */ - protected $method = self::GET; - - /** - * Associative array of GET parameters - * - * @var array - */ - protected $paramsGet = array(); - - /** - * Assiciative array of POST parameters - * - * @var array - */ - protected $paramsPost = array(); - - /** - * Request body content type (for POST requests) - * - * @var string - */ - protected $enctype = null; - - /** - * The raw post data to send. Could be set by setRawData($data, $enctype). - * - * @var string - */ - protected $raw_post_data = null; - - /** - * HTTP Authentication settings - * - * Expected to be an associative array with this structure: - * $this->auth = array('user' => 'username', 'password' => 'password', 'type' => 'basic') - * Where 'type' should be one of the supported authentication types (see the AUTH_* - * constants), for example 'basic' or 'digest'. - * - * If null, no authentication will be used. - * - * @var array|null - */ - protected $auth; - - /** - * File upload arrays (used in POST requests) - * - * An associative array, where each element is of the format: - * 'name' => array('filename.txt', 'text/plain', 'This is the actual file contents') - * - * @var array - */ - protected $files = array(); - - /** - * The client's cookie jar - * - * @var Zend_Http_CookieJar - */ - protected $cookiejar = null; - - /** - * The last HTTP request sent by the client, as string - * - * @var string - */ - protected $last_request = null; - - /** - * The last HTTP response received by the client - * - * @var Zend_Http_Response - */ - protected $last_response = null; - - /** - * Redirection counter - * - * @var int - */ - protected $redirectCounter = 0; - - /** - * Fileinfo magic database resource - * - * This varaiable is populated the first time _detectFileMimeType is called - * and is then reused on every call to this method - * - * @var resource - */ - static protected $_fileInfoDb = null; - - /** - * Contructor method. Will create a new HTTP client. Accepts the target - * URL and optionally configuration array. - * - * @param Zend_Uri_Http|string $uri - * @param array $config Configuration key-value pairs. - */ - public function __construct($uri = null, $config = null) - { - if ($uri !== null) $this->setUri($uri); - if ($config !== null) $this->setConfig($config); - } - - /** - * Set the URI for the next request - * - * @param Zend_Uri_Http|string $uri - * @return Zend_Http_Client - * @throws Zend_Http_Client_Exception - */ - public function setUri($uri) - { - if (is_string($uri)) { - $uri = Zend_Uri::factory($uri); - } - - if (!$uri instanceof Zend_Uri_Http) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception('Passed parameter is not a valid HTTP URI.'); - } - - // We have no ports, set the defaults - if (! $uri->getPort()) { - $uri->setPort(($uri->getScheme() == 'https' ? 443 : 80)); - } - - $this->uri = $uri; - - return $this; - } - - /** - * Get the URI for the next request - * - * @param boolean $as_string If true, will return the URI as a string - * @return Zend_Uri_Http|string - */ - public function getUri($as_string = false) - { - if ($as_string && $this->uri instanceof Zend_Uri_Http) { - return $this->uri->__toString(); - } else { - return $this->uri; - } - } - - /** - * Set configuration parameters for this HTTP client - * - * @param array $config - * @return Zend_Http_Client - * @throws Zend_Http_Client_Exception - */ - public function setConfig($config = array()) - { - if (! is_array($config)) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception('Expected array parameter, given ' . gettype($config)); - } - - foreach ($config as $k => $v) - $this->config[strtolower($k)] = $v; - - return $this; - } - - /** - * Set the next request's method - * - * Validated the passed method and sets it. If we have files set for - * POST requests, and the new method is not POST, the files are silently - * dropped. - * - * @param string $method - * @return Zend_Http_Client - * @throws Zend_Http_Client_Exception - */ - public function setMethod($method = self::GET) - { - $regex = '/^[^\x00-\x1f\x7f-\xff\(\)<>@,;:\\\\"\/\[\]\?={}\s]+$/'; - if (! preg_match($regex, $method)) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("'{$method}' is not a valid HTTP request method."); - } - - if ($method == self::POST && $this->enctype === null) - $this->setEncType(self::ENC_URLENCODED); - - $this->method = $method; - - return $this; - } - - /** - * Set one or more request headers - * - * This function can be used in several ways to set the client's request - * headers: - * 1. By providing two parameters: $name as the header to set (eg. 'Host') - * and $value as it's value (eg. 'www.example.com'). - * 2. By providing a single header string as the only parameter - * eg. 'Host: www.example.com' - * 3. By providing an array of headers as the first parameter - * eg. array('host' => 'www.example.com', 'x-foo: bar'). In This case - * the function will call itself recursively for each array item. - * - * @param string|array $name Header name, full header string ('Header: value') - * or an array of headers - * @param mixed $value Header value or null - * @return Zend_Http_Client - * @throws Zend_Http_Client_Exception - */ - public function setHeaders($name, $value = null) - { - // If we got an array, go recusive! - if (is_array($name)) { - foreach ($name as $k => $v) { - if (is_string($k)) { - $this->setHeaders($k, $v); - } else { - $this->setHeaders($v, null); - } - } - } else { - // Check if $name needs to be split - if ($value === null && (strpos($name, ':') > 0)) - list($name, $value) = explode(':', $name, 2); - - // Make sure the name is valid if we are in strict mode - if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("{$name} is not a valid HTTP header name"); - } - - $normalized_name = strtolower($name); - - // If $value is null or false, unset the header - if ($value === null || $value === false) { - unset($this->headers[$normalized_name]); - - // Else, set the header - } else { - // Header names are storred lowercase internally. - if (is_string($value)) $value = trim($value); - $this->headers[$normalized_name] = array($name, $value); - } - } - - return $this; - } - - /** - * Get the value of a specific header - * - * Note that if the header has more than one value, an array - * will be returned. - * - * @param string $key - * @return string|array|null The header value or null if it is not set - */ - public function getHeader($key) - { - $key = strtolower($key); - if (isset($this->headers[$key])) { - return $this->headers[$key][1]; - } else { - return null; - } - } - - /** - * Set a GET parameter for the request. Wrapper around _setParameter - * - * @param string|array $name - * @param string $value - * @return Zend_Http_Client - */ - public function setParameterGet($name, $value = null) - { - if (is_array($name)) { - foreach ($name as $k => $v) - $this->_setParameter('GET', $k, $v); - } else { - $this->_setParameter('GET', $name, $value); - } - - return $this; - } - - /** - * Set a POST parameter for the request. Wrapper around _setParameter - * - * @param string|array $name - * @param string $value - * @return Zend_Http_Client - */ - public function setParameterPost($name, $value = null) - { - if (is_array($name)) { - foreach ($name as $k => $v) - $this->_setParameter('POST', $k, $v); - } else { - $this->_setParameter('POST', $name, $value); - } - - return $this; - } - - /** - * Set a GET or POST parameter - used by SetParameterGet and SetParameterPost - * - * @param string $type GET or POST - * @param string $name - * @param string $value - * @return null - */ - protected function _setParameter($type, $name, $value) - { - $parray = array(); - $type = strtolower($type); - switch ($type) { - case 'get': - $parray = &$this->paramsGet; - break; - case 'post': - $parray = &$this->paramsPost; - break; - } - - if ($value === null) { - if (isset($parray[$name])) unset($parray[$name]); - } else { - $parray[$name] = $value; - } - } - - /** - * Get the number of redirections done on the last request - * - * @return int - */ - public function getRedirectionsCount() - { - return $this->redirectCounter; - } - - /** - * Set HTTP authentication parameters - * - * $type should be one of the supported types - see the self::AUTH_* - * constants. - * - * To enable authentication: - * - * $this->setAuth('shahar', 'secret', Zend_Http_Client::AUTH_BASIC); - * - * - * To disable authentication: - * - * $this->setAuth(false); - * - * - * @see http://www.faqs.org/rfcs/rfc2617.html - * @param string|false $user User name or false disable authentication - * @param string $password Password - * @param string $type Authentication type - * @return Zend_Http_Client - * @throws Zend_Http_Client_Exception - */ - public function setAuth($user, $password = '', $type = self::AUTH_BASIC) - { - // If we got false or null, disable authentication - if ($user === false || $user === null) { - $this->auth = null; - - // Else, set up authentication - } else { - // Check we got a proper authentication type - if (! defined('self::AUTH_' . strtoupper($type))) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("Invalid or not supported authentication type: '$type'"); - } - - $this->auth = array( - 'user' => (string) $user, - 'password' => (string) $password, - 'type' => $type - ); - } - - return $this; - } - - /** - * Set the HTTP client's cookie jar. - * - * A cookie jar is an object that holds and maintains cookies across HTTP requests - * and responses. - * - * @param Zend_Http_CookieJar|boolean $cookiejar Existing cookiejar object, true to create a new one, false to disable - * @return Zend_Http_Client - * @throws Zend_Http_Client_Exception - */ - public function setCookieJar($cookiejar = true) - { - if (! class_exists('Zend_Http_CookieJar')) - require_once 'Zend/Http/CookieJar.php'; - - if ($cookiejar instanceof Zend_Http_CookieJar) { - $this->cookiejar = $cookiejar; - } elseif ($cookiejar === true) { - $this->cookiejar = new Zend_Http_CookieJar(); - } elseif (! $cookiejar) { - $this->cookiejar = null; - } else { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception('Invalid parameter type passed as CookieJar'); - } - - return $this; - } - - /** - * Return the current cookie jar or null if none. - * - * @return Zend_Http_CookieJar|null - */ - public function getCookieJar() - { - return $this->cookiejar; - } - - /** - * Add a cookie to the request. If the client has no Cookie Jar, the cookies - * will be added directly to the headers array as "Cookie" headers. - * - * @param Zend_Http_Cookie|string $cookie - * @param string|null $value If "cookie" is a string, this is the cookie value. - * @return Zend_Http_Client - * @throws Zend_Http_Client_Exception - */ - public function setCookie($cookie, $value = null) - { - if (! class_exists('Zend_Http_Cookie')) - require_once 'Zend/Http/Cookie.php'; - - if (is_array($cookie)) { - foreach ($cookie as $c => $v) { - if (is_string($c)) { - $this->setCookie($c, $v); - } else { - $this->setCookie($v); - } - } - - return $this; - } - - if ($value !== null) $value = urlencode($value); - - if (isset($this->cookiejar)) { - if ($cookie instanceof Zend_Http_Cookie) { - $this->cookiejar->addCookie($cookie); - } elseif (is_string($cookie) && $value !== null) { - $cookie = Zend_Http_Cookie::fromString("{$cookie}={$value}", $this->uri); - $this->cookiejar->addCookie($cookie); - } - } else { - if ($cookie instanceof Zend_Http_Cookie) { - $name = $cookie->getName(); - $value = $cookie->getValue(); - $cookie = $name; - } - - if (preg_match("/[=,; \t\r\n\013\014]/", $cookie)) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("Cookie name cannot contain these characters: =,; \t\r\n\013\014 ({$cookie})"); - } - - $value = addslashes($value); - - if (! isset($this->headers['cookie'])) $this->headers['cookie'] = array('Cookie', ''); - $this->headers['cookie'][1] .= $cookie . '=' . $value . '; '; - } - - return $this; - } - - /** - * Set a file to upload (using a POST request) - * - * Can be used in two ways: - * - * 1. $data is null (default): $filename is treated as the name if a local file which - * will be read and sent. Will try to guess the content type using mime_content_type(). - * 2. $data is set - $filename is sent as the file name, but $data is sent as the file - * contents and no file is read from the file system. In this case, you need to - * manually set the content-type ($ctype) or it will default to - * application/octet-stream. - * - * @param string $filename Name of file to upload, or name to save as - * @param string $formname Name of form element to send as - * @param string $data Data to send (if null, $filename is read and sent) - * @param string $ctype Content type to use (if $data is set and $ctype is - * null, will be application/octet-stream) - * @return Zend_Http_Client - * @throws Zend_Http_Client_Exception - */ - public function setFileUpload($filename, $formname, $data = null, $ctype = null) - { - if ($data === null) { - if (($data = @file_get_contents($filename)) === false) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("Unable to read file '{$filename}' for upload"); - } - - if (! $ctype) $ctype = $this->_detectFileMimeType($filename); - } - - // Force enctype to multipart/form-data - $this->setEncType(self::ENC_FORMDATA); - - $this->files[$formname] = array(basename($filename), $ctype, $data); - - return $this; - } - - /** - * Set the encoding type for POST data - * - * @param string $enctype - * @return Zend_Http_Client - */ - public function setEncType($enctype = self::ENC_URLENCODED) - { - $this->enctype = $enctype; - - return $this; - } - - /** - * Set the raw (already encoded) POST data. - * - * This function is here for two reasons: - * 1. For advanced user who would like to set their own data, already encoded - * 2. For backwards compatibilty: If someone uses the old post($data) method. - * this method will be used to set the encoded data. - * - * @param string $data - * @param string $enctype - * @return Zend_Http_Client - */ - public function setRawData($data, $enctype = null) - { - $this->raw_post_data = $data; - $this->setEncType($enctype); - - return $this; - } - - /** - * Clear all GET and POST parameters - * - * Should be used to reset the request parameters if the client is - * used for several concurrent requests. - * - * @return Zend_Http_Client - */ - public function resetParameters() - { - // Reset parameter data - $this->paramsGet = array(); - $this->paramsPost = array(); - $this->files = array(); - $this->raw_post_data = null; - - // Clear outdated headers - if (isset($this->headers['content-type'])) unset($this->headers['content-type']); - if (isset($this->headers['content-length'])) unset($this->headers['content-length']); - - return $this; - } - - /** - * Get the last HTTP request as string - * - * @return string - */ - public function getLastRequest() - { - return $this->last_request; - } - - /** - * Get the last HTTP response received by this client - * - * If $config['storeresponse'] is set to false, or no response was - * stored yet, will return null - * - * @return Zend_Http_Response or null if none - */ - public function getLastResponse() - { - return $this->last_response; - } - - /** - * Load the connection adapter - * - * While this method is not called more than one for a client, it is - * seperated from ->request() to preserve logic and readability - * - * @param Zend_Http_Client_Adapter_Interface|string $adapter - * @return null - * @throws Zend_Http_Client_Exception - */ - public function setAdapter($adapter) - { - if (is_string($adapter)) { - try { - Zend_Loader::loadClass($adapter); - } catch (Zend_Exception $e) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("Unable to load adapter '$adapter': {$e->getMessage()}"); - } - - $adapter = new $adapter; - } - - if (! $adapter instanceof Zend_Http_Client_Adapter_Interface) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception('Passed adapter is not a HTTP connection adapter'); - } - - $this->adapter = $adapter; - $config = $this->config; - unset($config['adapter']); - $this->adapter->setConfig($config); - } - - /** - * Send the HTTP request and return an HTTP response object - * - * @param string $method - * @return Zend_Http_Response - * @throws Zend_Http_Client_Exception - */ - public function request($method = null) - { - if (! $this->uri instanceof Zend_Uri_Http) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception('No valid URI has been passed to the client'); - } - - if ($method) $this->setMethod($method); - $this->redirectCounter = 0; - $response = null; - - // Make sure the adapter is loaded - if ($this->adapter == null) $this->setAdapter($this->config['adapter']); - - // Send the first request. If redirected, continue. - do { - // Clone the URI and add the additional GET parameters to it - $uri = clone $this->uri; - if (! empty($this->paramsGet)) { - $query = $uri->getQuery(); - if (! empty($query)) $query .= '&'; - $query .= http_build_query($this->paramsGet, null, '&'); - - $uri->setQuery($query); - } - - $body = $this->_prepareBody(); - $headers = $this->_prepareHeaders(); - - // Open the connection, send the request and read the response - $this->adapter->connect($uri->getHost(), $uri->getPort(), - ($uri->getScheme() == 'https' ? true : false)); - - $this->last_request = $this->adapter->write($this->method, - $uri, $this->config['httpversion'], $headers, $body); - - $response = $this->adapter->read(); - if (! $response) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception('Unable to read response, or response is empty'); - } - - $response = Zend_Http_Response::fromString($response); - if ($this->config['storeresponse']) $this->last_response = $response; - - // Load cookies into cookie jar - if (isset($this->cookiejar)) $this->cookiejar->addCookiesFromResponse($response, $uri); - - // If we got redirected, look for the Location header - if ($response->isRedirect() && ($location = $response->getHeader('location'))) { - - // Check whether we send the exact same request again, or drop the parameters - // and send a GET request - if ($response->getStatus() == 303 || - ((! $this->config['strictredirects']) && ($response->getStatus() == 302 || - $response->getStatus() == 301))) { - - $this->resetParameters(); - $this->setMethod(self::GET); - } - - // If we got a well formed absolute URI - if (Zend_Uri_Http::check($location)) { - $this->setHeaders('host', null); - $this->setUri($location); - - } else { - - // Split into path and query and set the query - if (strpos($location, '?') !== false) { - list($location, $query) = explode('?', $location, 2); - } else { - $query = ''; - } - $this->uri->setQuery($query); - - // Else, if we got just an absolute path, set it - if(strpos($location, '/') === 0) { - $this->uri->setPath($location); - - // Else, assume we have a relative path - } else { - // Get the current path directory, removing any trailing slashes - $path = $this->uri->getPath(); - $path = rtrim(substr($path, 0, strrpos($path, '/')), "/"); - $this->uri->setPath($path . '/' . $location); - } - } - ++$this->redirectCounter; - - } else { - // If we didn't get any location, stop redirecting - break; - } - - } while ($this->redirectCounter < $this->config['maxredirects']); - - return $response; - } - - /** - * Prepare the request headers - * - * @return array - */ - protected function _prepareHeaders() - { - $headers = array(); - - // Set the host header - if (! isset($this->headers['host'])) { - $host = $this->uri->getHost(); - - // If the port is not default, add it - if (! (($this->uri->getScheme() == 'http' && $this->uri->getPort() == 80) || - ($this->uri->getScheme() == 'https' && $this->uri->getPort() == 443))) { - $host .= ':' . $this->uri->getPort(); - } - - $headers[] = "Host: {$host}"; - } - - // Set the connection header - if (! isset($this->headers['connection'])) { - if (! $this->config['keepalive']) $headers[] = "Connection: close"; - } - - // Set the Accept-encoding header if not set - depending on whether - // zlib is available or not. - if (! isset($this->headers['accept-encoding'])) { - if (function_exists('gzinflate')) { - $headers[] = 'Accept-encoding: gzip, deflate'; - } else { - $headers[] = 'Accept-encoding: identity'; - } - } - - // Set the content-type header - if ($this->method == self::POST && - (! isset($this->headers['content-type']) && isset($this->enctype))) { - - $headers[] = "Content-type: {$this->enctype}"; - } - - // Set the user agent header - if (! isset($this->headers['user-agent']) && isset($this->config['useragent'])) { - $headers[] = "User-agent: {$this->config['useragent']}"; - } - - // Set HTTP authentication if needed - if (is_array($this->auth)) { - $auth = self::encodeAuthHeader($this->auth['user'], $this->auth['password'], $this->auth['type']); - $headers[] = "Authorization: {$auth}"; - } - - // Load cookies from cookie jar - if (isset($this->cookiejar)) { - $cookstr = $this->cookiejar->getMatchingCookies($this->uri, - true, Zend_Http_CookieJar::COOKIE_STRING_CONCAT); - - if ($cookstr) $headers[] = "Cookie: {$cookstr}"; - } - - // Add all other user defined headers - foreach ($this->headers as $header) { - list($name, $value) = $header; - if (is_array($value)) - $value = implode(', ', $value); - - $headers[] = "$name: $value"; - } - - return $headers; - } - - /** - * Prepare the request body (for POST and PUT requests) - * - * @return string - * @throws Zend_Http_Client_Exception - */ - protected function _prepareBody() - { - // According to RFC2616, a TRACE request should not have a body. - if ($this->method == self::TRACE) { - return ''; - } - - // If we have raw_post_data set, just use it as the body. - if (isset($this->raw_post_data)) { - $this->setHeaders('Content-length', strlen($this->raw_post_data)); - return $this->raw_post_data; - } - - $body = ''; - - // If we have files to upload, force enctype to multipart/form-data - if (count ($this->files) > 0) $this->setEncType(self::ENC_FORMDATA); - - // If we have POST parameters or files, encode and add them to the body - if (count($this->paramsPost) > 0 || count($this->files) > 0) { - switch($this->enctype) { - case self::ENC_FORMDATA: - // Encode body as multipart/form-data - $boundary = '---ZENDHTTPCLIENT-' . md5(microtime()); - $this->setHeaders('Content-type', self::ENC_FORMDATA . "; boundary={$boundary}"); - - // Get POST parameters and encode them - $params = $this->_getParametersRecursive($this->paramsPost); - foreach ($params as $pp) { - $body .= self::encodeFormData($boundary, $pp[0], $pp[1]); - } - - // Encode files - foreach ($this->files as $name => $file) { - $fhead = array('Content-type' => $file[1]); - $body .= self::encodeFormData($boundary, $name, $file[2], $file[0], $fhead); - } - - $body .= "--{$boundary}--\r\n"; - break; - - case self::ENC_URLENCODED: - // Encode body as application/x-www-form-urlencoded - $this->setHeaders('Content-type', self::ENC_URLENCODED); - $body = http_build_query($this->paramsPost, '', '&'); - break; - - default: - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("Cannot handle content type '{$this->enctype}' automatically." . - " Please use Zend_Http_Client::setRawData to send this kind of content."); - break; - } - } - - // Set the content-length if we have a body or if request is POST/PUT - if ($body || $this->method == self::POST || $this->method == self::PUT) { - $this->setHeaders('Content-length', strlen($body)); - } - - return $body; - } - - /** - * Helper method that gets a possibly multi-level parameters array (get or - * post) and flattens it. - * - * The method returns an array of (key, value) pairs (because keys are not - * necessarily unique. If one of the parameters in as array, it will also - * add a [] suffix to the key. - * - * @param array $parray The parameters array - * @param bool $urlencode Whether to urlencode the name and value - * @return array - */ - protected function _getParametersRecursive($parray, $urlencode = false) - { - if (! is_array($parray)) return $parray; - $parameters = array(); - - foreach ($parray as $name => $value) { - if ($urlencode) $name = urlencode($name); - - // If $value is an array, iterate over it - if (is_array($value)) { - $name .= ($urlencode ? '%5B%5D' : '[]'); - foreach ($value as $subval) { - if ($urlencode) $subval = urlencode($subval); - $parameters[] = array($name, $subval); - } - } else { - if ($urlencode) $value = urlencode($value); - $parameters[] = array($name, $value); - } - } - - return $parameters; - } - - /** - * Attempt to detect the MIME type of a file using available extensions - * - * This method will try to detect the MIME type of a file. If the fileinfo - * extension is available, it will be used. If not, the mime_magic - * extension which is deprected but is still available in many PHP setups - * will be tried. - * - * If neither extension is available, the default application/octet-stream - * MIME type will be returned - * - * @param string $file File path - * @return string MIME type - */ - protected function _detectFileMimeType($file) - { - $type = null; - - // First try with fileinfo functions - if (function_exists('finfo_open')) { - if (self::$_fileInfoDb === null) { - self::$_fileInfoDb = @finfo_open(FILEINFO_MIME); - } - - if (self::$_fileInfoDb) { - $type = finfo_file(self::$_fileInfoDb, $file); - } - - } elseif (function_exists('mime_content_type')) { - $type = mime_content_type($file); - } - - // Fallback to the default application/octet-stream - if (! $type) { - $type = 'application/octet-stream'; - } - - return $type; - } - - /** - * Encode data to a multipart/form-data part suitable for a POST request. - * - * @param string $boundary - * @param string $name - * @param mixed $value - * @param string $filename - * @param array $headers Associative array of optional headers @example ("Content-transfer-encoding" => "binary") - * @return string - */ - public static function encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) { - $ret = "--{$boundary}\r\n" . - 'Content-disposition: form-data; name="' . $name .'"'; - - if ($filename) $ret .= '; filename="' . $filename . '"'; - $ret .= "\r\n"; - - foreach ($headers as $hname => $hvalue) { - $ret .= "{$hname}: {$hvalue}\r\n"; - } - $ret .= "\r\n"; - - $ret .= "{$value}\r\n"; - - return $ret; - } - - /** - * Create a HTTP authentication "Authorization:" header according to the - * specified user, password and authentication method. - * - * @see http://www.faqs.org/rfcs/rfc2617.html - * @param string $user - * @param string $password - * @param string $type - * @return string - * @throws Zend_Http_Client_Exception - */ - public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC) - { - $authHeader = null; - - switch ($type) { - case self::AUTH_BASIC: - // In basic authentication, the user name cannot contain ":" - if (strpos($user, ':') !== false) { - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("The user name cannot contain ':' in 'Basic' HTTP authentication"); - } - - $authHeader = 'Basic ' . base64_encode($user . ':' . $password); - break; - - //case self::AUTH_DIGEST: - /** - * @todo Implement digest authentication - */ - // break; - - default: - /** @see Zend_Http_Client_Exception */ - require_once 'Zend/Http/Client/Exception.php'; - throw new Zend_Http_Client_Exception("Not a supported HTTP authentication type: '$type'"); - } - - return $authHeader; - } -} diff --git a/phpQuery/phpQuery/Zend/Http/Client/Adapter/Exception.php b/phpQuery/phpQuery/Zend/Http/Client/Adapter/Exception.php deleted file mode 100644 index dfbe904..0000000 --- a/phpQuery/phpQuery/Zend/Http/Client/Adapter/Exception.php +++ /dev/null @@ -1,33 +0,0 @@ - 'ssl', - 'proxy_host' => '', - 'proxy_port' => 8080, - 'proxy_user' => '', - 'proxy_pass' => '', - 'proxy_auth' => Zend_Http_Client::AUTH_BASIC, - 'persistent' => false - ); - - /** - * Whether HTTPS CONNECT was already negotiated with the proxy or not - * - * @var boolean - */ - protected $negotiated = false; - - /** - * Connect to the remote server - * - * Will try to connect to the proxy server. If no proxy was set, will - * fall back to the target server (behave like regular Socket adapter) - * - * @param string $host - * @param int $port - * @param boolean $secure - * @param int $timeout - */ - public function connect($host, $port = 80, $secure = false) - { - // If no proxy is set, fall back to Socket adapter - if (! $this->config['proxy_host']) return parent::connect($host, $port, $secure); - - // Go through a proxy - the connection is actually to the proxy server - $host = $this->config['proxy_host']; - $port = $this->config['proxy_port']; - - // If we are connected to the wrong proxy, disconnect first - if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { - if (is_resource($this->socket)) $this->close(); - } - - // Now, if we are not connected, connect - if (! is_resource($this->socket) || ! $this->config['keepalive']) { - $this->socket = @fsockopen($host, $port, $errno, $errstr, (int) $this->config['timeout']); - if (! $this->socket) { - $this->close(); - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception( - 'Unable to Connect to proxy server ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr); - } - - // Set the stream timeout - if (!stream_set_timeout($this->socket, (int) $this->config['timeout'])) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout'); - } - - // Update connected_to - $this->connected_to = array($host, $port); - } - } - - /** - * Send request to the proxy server - * - * @param string $method - * @param Zend_Uri_Http $uri - * @param string $http_ver - * @param array $headers - * @param string $body - * @return string Request as string - */ - public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') - { - // If no proxy is set, fall back to default Socket adapter - if (! $this->config['proxy_host']) return parent::write($method, $uri, $http_ver, $headers, $body); - - // Make sure we're properly connected - if (! $this->socket) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected"); - } - - $host = $this->config['proxy_host']; - $port = $this->config['proxy_port']; - - if ($this->connected_to[0] != $host || $this->connected_to[1] != $port) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong proxy server"); - } - - // Add Proxy-Authorization header - if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) - $headers['proxy-authorization'] = Zend_Http_Client::encodeAuthHeader( - $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth'] - ); - - // if we are proxying HTTPS, preform CONNECT handshake with the proxy - if ($uri->getScheme() == 'https' && (! $this->negotiated)) { - $this->connectHandshake($uri->getHost(), $uri->getPort(), $http_ver, $headers); - $this->negotiated = true; - } - - // Save request method for later - $this->method = $method; - - // Build request headers - $request = "{$method} {$uri->__toString()} HTTP/{$http_ver}\r\n"; - - // Add all headers to the request string - foreach ($headers as $k => $v) { - if (is_string($k)) $v = "$k: $v"; - $request .= "$v\r\n"; - } - - // Add the request body - $request .= "\r\n" . $body; - - // Send the request - if (! @fwrite($this->socket, $request)) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server"); - } - - return $request; - } - - /** - * Preform handshaking with HTTPS proxy using CONNECT method - * - * @param string $host - * @param integer $port - * @param string $http_ver - * @param array $headers - */ - protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array()) - { - $request = "CONNECT $host:$port HTTP/$http_ver\r\n" . - "Host: " . $this->config['proxy_host'] . "\r\n"; - - // Add the user-agent header - if (isset($this->config['useragent'])) { - $request .= "User-agent: " . $this->config['useragent'] . "\r\n"; - } - - // If the proxy-authorization header is set, send it to proxy but remove - // it from headers sent to target host - if (isset($headers['proxy-authorization'])) { - $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n"; - unset($headers['proxy-authorization']); - } - - $request .= "\r\n"; - - // Send the request - if (! @fwrite($this->socket, $request)) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server"); - } - - // Read response headers only - $response = ''; - $gotStatus = false; - while ($line = @fgets($this->socket)) { - $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); - if ($gotStatus) { - $response .= $line; - if (!chop($line)) break; - } - } - - // Check that the response from the proxy is 200 - if (Zend_Http_Response::extractCode($response) != 200) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception("Unable to connect to HTTPS proxy. Server response: " . $response); - } - - // If all is good, switch socket to secure mode. We have to fall back - // through the different modes - $modes = array( - STREAM_CRYPTO_METHOD_TLS_CLIENT, - STREAM_CRYPTO_METHOD_SSLv3_CLIENT, - STREAM_CRYPTO_METHOD_SSLv23_CLIENT, - STREAM_CRYPTO_METHOD_SSLv2_CLIENT - ); - - $success = false; - foreach($modes as $mode) { - $success = stream_socket_enable_crypto($this->socket, true, $mode); - if ($success) break; - } - - if (! $success) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception("Unable to connect to" . - " HTTPS server through proxy: could not negotiate secure connection."); - } - } - - /** - * Close the connection to the server - * - */ - public function close() - { - parent::close(); - $this->negotiated = false; - } - - /** - * Destructor: make sure the socket is disconnected - * - */ - public function __destruct() - { - if ($this->socket) $this->close(); - } -} diff --git a/phpQuery/phpQuery/Zend/Http/Client/Adapter/Socket.php b/phpQuery/phpQuery/Zend/Http/Client/Adapter/Socket.php deleted file mode 100644 index 01b6ef3..0000000 --- a/phpQuery/phpQuery/Zend/Http/Client/Adapter/Socket.php +++ /dev/null @@ -1,332 +0,0 @@ - false, - 'ssltransport' => 'ssl', - 'sslcert' => null, - 'sslpassphrase' => null - ); - - /** - * Request method - will be set by write() and might be used by read() - * - * @var string - */ - protected $method = null; - - /** - * Adapter constructor, currently empty. Config is set using setConfig() - * - */ - public function __construct() - { - } - - /** - * Set the configuration array for the adapter - * - * @param array $config - */ - public function setConfig($config = array()) - { - if (! is_array($config)) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception( - '$config expects an array, ' . gettype($config) . ' recieved.'); - } - - foreach ($config as $k => $v) { - $this->config[strtolower($k)] = $v; - } - } - - /** - * Connect to the remote server - * - * @param string $host - * @param int $port - * @param boolean $secure - * @param int $timeout - */ - public function connect($host, $port = 80, $secure = false) - { - // If the URI should be accessed via SSL, prepend the Hostname with ssl:// - $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host; - - // If we are connected to the wrong host, disconnect first - if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) { - if (is_resource($this->socket)) $this->close(); - } - - // Now, if we are not connected, connect - if (! is_resource($this->socket) || ! $this->config['keepalive']) { - $context = stream_context_create(); - if ($secure) { - if ($this->config['sslcert'] !== null) { - if (! stream_context_set_option($context, 'ssl', 'local_cert', - $this->config['sslcert'])) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception('Unable to set sslcert option'); - } - } - if ($this->config['sslpassphrase'] !== null) { - if (! stream_context_set_option($context, 'ssl', 'passphrase', - $this->config['sslpassphrase'])) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception('Unable to set sslpassphrase option'); - } - } - } - - $flags = STREAM_CLIENT_CONNECT; - if ($this->config['persistent']) $flags |= STREAM_CLIENT_PERSISTENT; - - $this->socket = @stream_socket_client($host . ':' . $port, - $errno, - $errstr, - (int) $this->config['timeout'], - $flags, - $context); - if (! $this->socket) { - $this->close(); - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception( - 'Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr); - } - - // Set the stream timeout - if (! stream_set_timeout($this->socket, (int) $this->config['timeout'])) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout'); - } - - // Update connected_to - $this->connected_to = array($host, $port); - } - } - - /** - * Send request to the remote server - * - * @param string $method - * @param Zend_Uri_Http $uri - * @param string $http_ver - * @param array $headers - * @param string $body - * @return string Request as string - */ - public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') - { - // Make sure we're properly connected - if (! $this->socket) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are not connected'); - } - - $host = $uri->getHost(); - $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host; - if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong host'); - } - - // Save request method for later - $this->method = $method; - - // Build request headers - $path = $uri->getPath(); - if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); - $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; - foreach ($headers as $k => $v) { - if (is_string($k)) $v = ucfirst($k) . ": $v"; - $request .= "$v\r\n"; - } - - // Add the request body - $request .= "\r\n" . $body; - - // Send the request - if (! @fwrite($this->socket, $request)) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception('Error writing request to server'); - } - - return $request; - } - - /** - * Read response from server - * - * @return string - */ - public function read() - { - // First, read headers only - $response = ''; - $gotStatus = false; - while ($line = @fgets($this->socket)) { - $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false); - if ($gotStatus) { - $response .= $line; - if (!chop($line)) break; - } - } - - $statusCode = Zend_Http_Response::extractCode($response); - - // Handle 100 and 101 responses internally by restarting the read again - if ($statusCode == 100 || $statusCode == 101) return $this->read(); - - /** - * Responses to HEAD requests and 204 or 304 responses are not expected - * to have a body - stop reading here - */ - if ($statusCode == 304 || $statusCode == 204 || - $this->method == Zend_Http_Client::HEAD) return $response; - - // Check headers to see what kind of connection / transfer encoding we have - $headers = Zend_Http_Response::extractHeaders($response); - - // if the connection is set to close, just read until socket closes - if (isset($headers['connection']) && $headers['connection'] == 'close') { - while ($buff = @fread($this->socket, 8192)) { - $response .= $buff; - } - - $this->close(); - - // Else, if we got a transfer-encoding header (chunked body) - } elseif (isset($headers['transfer-encoding'])) { - if ($headers['transfer-encoding'] == 'chunked') { - do { - $chunk = ''; - $line = @fgets($this->socket); - $chunk .= $line; - - $hexchunksize = ltrim(chop($line), '0'); - $hexchunksize = strlen($hexchunksize) ? strtolower($hexchunksize) : 0; - - $chunksize = hexdec(chop($line)); - if (dechex($chunksize) != $hexchunksize) { - @fclose($this->socket); - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' . - $hexchunksize . '" unable to read chunked body'); - } - - $left_to_read = $chunksize; - while ($left_to_read > 0) { - $line = @fread($this->socket, $left_to_read); - $chunk .= $line; - $left_to_read -= strlen($line); - } - - $chunk .= @fgets($this->socket); - $response .= $chunk; - } while ($chunksize > 0); - } else { - throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' . - $headers['transfer-encoding'] . '" transfer encoding'); - } - - // Else, if we got the content-length header, read this number of bytes - } elseif (isset($headers['content-length'])) { - $left_to_read = $headers['content-length']; - $chunk = ''; - while ($left_to_read > 0) { - $chunk = @fread($this->socket, $left_to_read); - $left_to_read -= strlen($chunk); - $response .= $chunk; - } - - // Fallback: just read the response (should not happen) - } else { - while ($buff = @fread($this->socket, 8192)) { - $response .= $buff; - } - - $this->close(); - } - - return $response; - } - - /** - * Close the connection to the server - * - */ - public function close() - { - if (is_resource($this->socket)) @fclose($this->socket); - $this->socket = null; - $this->connected_to = array(null, null); - } - - /** - * Destructor: make sure the socket is disconnected - * - * If we are in persistent TCP mode, will not close the connection - * - */ - public function __destruct() - { - if (! $this->config['persistent']) { - if ($this->socket) $this->close(); - } - } -} diff --git a/phpQuery/phpQuery/Zend/Http/Client/Adapter/Test.php b/phpQuery/phpQuery/Zend/Http/Client/Adapter/Test.php deleted file mode 100644 index ce5c468..0000000 --- a/phpQuery/phpQuery/Zend/Http/Client/Adapter/Test.php +++ /dev/null @@ -1,193 +0,0 @@ - $v) { - $this->config[strtolower($k)] = $v; - } - } - - /** - * Connect to the remote server - * - * @param string $host - * @param int $port - * @param boolean $secure - * @param int $timeout - */ - public function connect($host, $port = 80, $secure = false) - { } - - /** - * Send request to the remote server - * - * @param string $method - * @param Zend_Uri_Http $uri - * @param string $http_ver - * @param array $headers - * @param string $body - * @return string Request as string - */ - public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') - { - $host = $uri->getHost(); - $host = (strtolower($uri->getScheme()) == 'https' ? 'sslv2://' . $host : $host); - - // Build request headers - $path = $uri->getPath(); - if ($uri->getQuery()) $path .= '?' . $uri->getQuery(); - $request = "{$method} {$path} HTTP/{$http_ver}\r\n"; - foreach ($headers as $k => $v) { - if (is_string($k)) $v = ucfirst($k) . ": $v"; - $request .= "$v\r\n"; - } - - // Add the request body - $request .= "\r\n" . $body; - - // Do nothing - just return the request as string - - return $request; - } - - /** - * Return the response set in $this->setResponse() - * - * @return string - */ - public function read() - { - if ($this->responseIndex >= count($this->responses)) { - $this->responseIndex = 0; - } - return $this->responses[$this->responseIndex++]; - } - - /** - * Close the connection (dummy) - * - */ - public function close() - { } - - /** - * Set the HTTP response(s) to be returned by this adapter - * - * @param Zend_Http_Response|array|string $response - */ - public function setResponse($response) - { - if ($response instanceof Zend_Http_Response) { - $response = $response->asString(); - } - - $this->responses = (array)$response; - $this->responseIndex = 0; - } - - /** - * Add another response to the response buffer. - * - * @param string $response - */ - public function addResponse($response) - { - $this->responses[] = $response; - } - - /** - * Sets the position of the response buffer. Selects which - * response will be returned on the next call to read(). - * - * @param integer $index - */ - public function setResponseIndex($index) - { - if ($index < 0 || $index >= count($this->responses)) { - require_once 'Zend/Http/Client/Adapter/Exception.php'; - throw new Zend_Http_Client_Adapter_Exception( - 'Index out of range of response buffer size'); - } - $this->responseIndex = $index; - } -} diff --git a/phpQuery/phpQuery/Zend/Http/Client/Exception.php b/phpQuery/phpQuery/Zend/Http/Client/Exception.php deleted file mode 100644 index 70c8e01..0000000 --- a/phpQuery/phpQuery/Zend/Http/Client/Exception.php +++ /dev/null @@ -1,33 +0,0 @@ -name = (string) $name) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('Cookies must have a name'); - } - - if (! $this->domain = (string) $domain) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('Cookies must have a domain'); - } - - $this->value = (string) $value; - $this->expires = ($expires === null ? null : (int) $expires); - $this->path = ($path ? $path : '/'); - $this->secure = $secure; - } - - /** - * Get Cookie name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * Get cookie value - * - * @return string - */ - public function getValue() - { - return $this->value; - } - - /** - * Get cookie domain - * - * @return string - */ - public function getDomain() - { - return $this->domain; - } - - /** - * Get the cookie path - * - * @return string - */ - public function getPath() - { - return $this->path; - } - - /** - * Get the expiry time of the cookie, or null if no expiry time is set - * - * @return int|null - */ - public function getExpiryTime() - { - return $this->expires; - } - - /** - * Check whether the cookie should only be sent over secure connections - * - * @return boolean - */ - public function isSecure() - { - return $this->secure; - } - - /** - * Check whether the cookie has expired - * - * Always returns false if the cookie is a session cookie (has no expiry time) - * - * @param int $now Timestamp to consider as "now" - * @return boolean - */ - public function isExpired($now = null) - { - if ($now === null) $now = time(); - if (is_int($this->expires) && $this->expires < $now) { - return true; - } else { - return false; - } - } - - /** - * Check whether the cookie is a session cookie (has no expiry time set) - * - * @return boolean - */ - public function isSessionCookie() - { - return ($this->expires === null); - } - - /** - * Checks whether the cookie should be sent or not in a specific scenario - * - * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path) - * @param boolean $matchSessionCookies Whether to send session cookies - * @param int $now Override the current time when checking for expiry time - * @return boolean - */ - public function match($uri, $matchSessionCookies = true, $now = null) - { - if (is_string ($uri)) { - $uri = Zend_Uri_Http::factory($uri); - } - - // Make sure we have a valid Zend_Uri_Http object - if (! ($uri->valid() && ($uri->getScheme() == 'http' || $uri->getScheme() =='https'))) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('Passed URI is not a valid HTTP or HTTPS URI'); - } - - // Check that the cookie is secure (if required) and not expired - if ($this->secure && $uri->getScheme() != 'https') return false; - if ($this->isExpired($now)) return false; - if ($this->isSessionCookie() && ! $matchSessionCookies) return false; - - // Validate domain and path - // Domain is validated using tail match, while path is validated using head match - $domain_preg = preg_quote($this->getDomain(), "/"); - if (! preg_match("/{$domain_preg}$/", $uri->getHost())) return false; - $path_preg = preg_quote($this->getPath(), "/"); - if (! preg_match("/^{$path_preg}/", $uri->getPath())) return false; - - // If we didn't die until now, return true. - return true; - } - - /** - * Get the cookie as a string, suitable for sending as a "Cookie" header in an - * HTTP request - * - * @return string - */ - public function __toString() - { - return $this->name . '=' . urlencode($this->value) . ';'; - } - - /** - * Generate a new Cookie object from a cookie string - * (for example the value of the Set-Cookie HTTP header) - * - * @param string $cookieStr - * @param Zend_Uri_Http|string $ref_uri Reference URI for default values (domain, path) - * @return Zend_Http_Cookie A new Zend_Http_Cookie object or false on failure. - */ - public static function fromString($cookieStr, $ref_uri = null) - { - // Set default values - if (is_string($ref_uri)) { - $ref_uri = Zend_Uri_Http::factory($ref_uri); - } - - $name = ''; - $value = ''; - $domain = ''; - $path = ''; - $expires = null; - $secure = false; - $parts = explode(';', $cookieStr); - - // If first part does not include '=', fail - if (strpos($parts[0], '=') === false) return false; - - // Get the name and value of the cookie - list($name, $value) = explode('=', trim(array_shift($parts)), 2); - $name = trim($name); - $value = urldecode(trim($value)); - - // Set default domain and path - if ($ref_uri instanceof Zend_Uri_Http) { - $domain = $ref_uri->getHost(); - $path = $ref_uri->getPath(); - $path = substr($path, 0, strrpos($path, '/')); - } - - // Set other cookie parameters - foreach ($parts as $part) { - $part = trim($part); - if (strtolower($part) == 'secure') { - $secure = true; - continue; - } - - $keyValue = explode('=', $part, 2); - if (count($keyValue) == 2) { - list($k, $v) = $keyValue; - switch (strtolower($k)) { - case 'expires': - $expires = strtotime($v); - break; - case 'path': - $path = $v; - break; - case 'domain': - $domain = $v; - break; - default: - break; - } - } - } - - if ($name !== '') { - return new Zend_Http_Cookie($name, $value, $domain, $expires, $path, $secure); - } else { - return false; - } - } -} diff --git a/phpQuery/phpQuery/Zend/Http/CookieJar.php b/phpQuery/phpQuery/Zend/Http/CookieJar.php deleted file mode 100644 index c11174e..0000000 --- a/phpQuery/phpQuery/Zend/Http/CookieJar.php +++ /dev/null @@ -1,350 +0,0 @@ -getDomain(); - $path = $cookie->getPath(); - if (! isset($this->cookies[$domain])) $this->cookies[$domain] = array(); - if (! isset($this->cookies[$domain][$path])) $this->cookies[$domain][$path] = array(); - $this->cookies[$domain][$path][$cookie->getName()] = $cookie; - } else { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('Supplient argument is not a valid cookie string or object'); - } - } - - /** - * Parse an HTTP response, adding all the cookies set in that response - * to the cookie jar. - * - * @param Zend_Http_Response $response - * @param Zend_Uri_Http|string $ref_uri Requested URI - */ - public function addCookiesFromResponse($response, $ref_uri) - { - if (! $response instanceof Zend_Http_Response) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('$response is expected to be a Response object, ' . - gettype($response) . ' was passed'); - } - - $cookie_hdrs = $response->getHeader('Set-Cookie'); - - if (is_array($cookie_hdrs)) { - foreach ($cookie_hdrs as $cookie) { - $this->addCookie($cookie, $ref_uri); - } - } elseif (is_string($cookie_hdrs)) { - $this->addCookie($cookie_hdrs, $ref_uri); - } - } - - /** - * Get all cookies in the cookie jar as an array - * - * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings - * @return array|string - */ - public function getAllCookies($ret_as = self::COOKIE_OBJECT) - { - $cookies = $this->_flattenCookiesArray($this->cookies, $ret_as); - return $cookies; - } - - /** - * Return an array of all cookies matching a specific request according to the request URI, - * whether session cookies should be sent or not, and the time to consider as "now" when - * checking cookie expiry time. - * - * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path) - * @param boolean $matchSessionCookies Whether to send session cookies - * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings - * @param int $now Override the current time when checking for expiry time - * @return array|string - */ - public function getMatchingCookies($uri, $matchSessionCookies = true, - $ret_as = self::COOKIE_OBJECT, $now = null) - { - if (is_string($uri)) $uri = Zend_Uri::factory($uri); - if (! $uri instanceof Zend_Uri_Http) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception("Invalid URI string or object passed"); - } - - // Set path - $path = $uri->getPath(); - $path = substr($path, 0, strrpos($path, '/')); - if (! $path) $path = '/'; - - // First, reduce the array of cookies to only those matching domain and path - $cookies = $this->_matchDomain($uri->getHost()); - $cookies = $this->_matchPath($cookies, $path); - $cookies = $this->_flattenCookiesArray($cookies, self::COOKIE_OBJECT); - - // Next, run Cookie->match on all cookies to check secure, time and session mathcing - $ret = array(); - foreach ($cookies as $cookie) - if ($cookie->match($uri, $matchSessionCookies, $now)) - $ret[] = $cookie; - - // Now, use self::_flattenCookiesArray again - only to convert to the return format ;) - $ret = $this->_flattenCookiesArray($ret, $ret_as); - - return $ret; - } - - /** - * Get a specific cookie according to a URI and name - * - * @param Zend_Uri_Http|string $uri The uri (domain and path) to match - * @param string $cookie_name The cookie's name - * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings - * @return Zend_Http_Cookie|string - */ - public function getCookie($uri, $cookie_name, $ret_as = self::COOKIE_OBJECT) - { - if (is_string($uri)) { - $uri = Zend_Uri::factory($uri); - } - - if (! $uri instanceof Zend_Uri_Http) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('Invalid URI specified'); - } - - // Get correct cookie path - $path = $uri->getPath(); - $path = substr($path, 0, strrpos($path, '/')); - if (! $path) $path = '/'; - - if (isset($this->cookies[$uri->getHost()][$path][$cookie_name])) { - $cookie = $this->cookies[$uri->getHost()][$path][$cookie_name]; - - switch ($ret_as) { - case self::COOKIE_OBJECT: - return $cookie; - break; - - case self::COOKIE_STRING_ARRAY: - case self::COOKIE_STRING_CONCAT: - return $cookie->__toString(); - break; - - default: - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception("Invalid value passed for \$ret_as: {$ret_as}"); - break; - } - } else { - return false; - } - } - - /** - * Helper function to recursivly flatten an array. Shoud be used when exporting the - * cookies array (or parts of it) - * - * @param Zend_Http_Cookie|array $ptr - * @param int $ret_as What value to return - * @return array|string - */ - protected function _flattenCookiesArray($ptr, $ret_as = self::COOKIE_OBJECT) { - if (is_array($ptr)) { - $ret = ($ret_as == self::COOKIE_STRING_CONCAT ? '' : array()); - foreach ($ptr as $item) { - if ($ret_as == self::COOKIE_STRING_CONCAT) { - $ret .= $this->_flattenCookiesArray($item, $ret_as); - } else { - $ret = array_merge($ret, $this->_flattenCookiesArray($item, $ret_as)); - } - } - return $ret; - } elseif ($ptr instanceof Zend_Http_Cookie) { - switch ($ret_as) { - case self::COOKIE_STRING_ARRAY: - return array($ptr->__toString()); - break; - - case self::COOKIE_STRING_CONCAT: - return $ptr->__toString(); - break; - - case self::COOKIE_OBJECT: - default: - return array($ptr); - break; - } - } - - return null; - } - - /** - * Return a subset of the cookies array matching a specific domain - * - * Returned array is actually an array of pointers to items in the $this->cookies array. - * - * @param string $domain - * @return array - */ - protected function _matchDomain($domain) { - $ret = array(); - - foreach (array_keys($this->cookies) as $cdom) { - $regex = "/" . preg_quote($cdom, "/") . "$/i"; - if (preg_match($regex, $domain)) $ret[$cdom] = &$this->cookies[$cdom]; - } - - return $ret; - } - - /** - * Return a subset of a domain-matching cookies that also match a specified path - * - * Returned array is actually an array of pointers to items in the $passed array. - * - * @param array $dom_array - * @param string $path - * @return array - */ - protected function _matchPath($domains, $path) { - $ret = array(); - if (substr($path, -1) != '/') $path .= '/'; - - foreach ($domains as $dom => $paths_array) { - foreach (array_keys($paths_array) as $cpath) { - $regex = "|^" . preg_quote($cpath, "|") . "|i"; - if (preg_match($regex, $path)) { - if (! isset($ret[$dom])) $ret[$dom] = array(); - $ret[$dom][$cpath] = &$paths_array[$cpath]; - } - } - } - - return $ret; - } - - /** - * Create a new CookieJar object and automatically load into it all the - * cookies set in an Http_Response object. If $uri is set, it will be - * considered as the requested URI for setting default domain and path - * of the cookie. - * - * @param Zend_Http_Response $response HTTP Response object - * @param Zend_Uri_Http|string $uri The requested URI - * @return Zend_Http_CookieJar - * @todo Add the $uri functionality. - */ - public static function fromResponse(Zend_Http_Response $response, $ref_uri) - { - $jar = new self(); - $jar->addCookiesFromResponse($response, $ref_uri); - return $jar; - } -} diff --git a/phpQuery/phpQuery/Zend/Http/Exception.php b/phpQuery/phpQuery/Zend/Http/Exception.php deleted file mode 100644 index 76e2a8d..0000000 --- a/phpQuery/phpQuery/Zend/Http/Exception.php +++ /dev/null @@ -1,33 +0,0 @@ - 'Continue', - 101 => 'Switching Protocols', - - // Success 2xx - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - - // Redirection 3xx - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', // 1.1 - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - // 306 is deprecated but reserved - 307 => 'Temporary Redirect', - - // Client Error 4xx - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - - // Server Error 5xx - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', - 509 => 'Bandwidth Limit Exceeded' - ); - - /** - * The HTTP version (1.0, 1.1) - * - * @var string - */ - protected $version; - - /** - * The HTTP response code - * - * @var int - */ - protected $code; - - /** - * The HTTP response code as string - * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500) - * - * @var string - */ - protected $message; - - /** - * The HTTP response headers array - * - * @var array - */ - protected $headers = array(); - - /** - * The HTTP response body - * - * @var string - */ - protected $body; - - /** - * HTTP response constructor - * - * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP - * response string and create a new Zend_Http_Response object. - * - * NOTE: The constructor no longer accepts nulls or empty values for the code and - * headers and will throw an exception if the passed values do not form a valid HTTP - * responses. - * - * If no message is passed, the message will be guessed according to the response code. - * - * @param int $code Response code (200, 404, ...) - * @param array $headers Headers array - * @param string $body Response body - * @param string $version HTTP version - * @param string $message Response code as text - * @throws Zend_Http_Exception - */ - public function __construct($code, $headers, $body = null, $version = '1.1', $message = null) - { - // Make sure the response code is valid and set it - if (self::responseCodeAsText($code) === null) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception("{$code} is not a valid HTTP response code"); - } - - $this->code = $code; - - // Make sure we got valid headers and set them - if (! is_array($headers)) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('No valid headers were passed'); - } - - foreach ($headers as $name => $value) { - if (is_int($name)) - list($name, $value) = explode(": ", $value, 1); - - $this->headers[ucwords(strtolower($name))] = $value; - } - - // Set the body - $this->body = $body; - - // Set the HTTP version - if (! preg_match('|^\d\.\d$|', $version)) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception("Invalid HTTP response version: $version"); - } - - $this->version = $version; - - // If we got the response message, set it. Else, set it according to - // the response code - if (is_string($message)) { - $this->message = $message; - } else { - $this->message = self::responseCodeAsText($code); - } - } - - /** - * Check whether the response is an error - * - * @return boolean - */ - public function isError() - { - $restype = floor($this->code / 100); - if ($restype == 4 || $restype == 5) { - return true; - } - - return false; - } - - /** - * Check whether the response in successful - * - * @return boolean - */ - public function isSuccessful() - { - $restype = floor($this->code / 100); - if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ??? - return true; - } - - return false; - } - - /** - * Check whether the response is a redirection - * - * @return boolean - */ - public function isRedirect() - { - $restype = floor($this->code / 100); - if ($restype == 3) { - return true; - } - - return false; - } - - /** - * Get the response body as string - * - * This method returns the body of the HTTP response (the content), as it - * should be in it's readable version - that is, after decoding it (if it - * was decoded), deflating it (if it was gzip compressed), etc. - * - * If you want to get the raw body (as transfered on wire) use - * $this->getRawBody() instead. - * - * @return string - */ - public function getBody() - { - $body = ''; - - // Decode the body if it was transfer-encoded - switch ($this->getHeader('transfer-encoding')) { - - // Handle chunked body - case 'chunked': - $body = self::decodeChunkedBody($this->body); - break; - - // No transfer encoding, or unknown encoding extension: - // return body as is - default: - $body = $this->body; - break; - } - - // Decode any content-encoding (gzip or deflate) if needed - switch (strtolower($this->getHeader('content-encoding'))) { - - // Handle gzip encoding - case 'gzip': - $body = self::decodeGzip($body); - break; - - // Handle deflate encoding - case 'deflate': - $body = self::decodeDeflate($body); - break; - - default: - break; - } - - return $body; - } - - /** - * Get the raw response body (as transfered "on wire") as string - * - * If the body is encoded (with Transfer-Encoding, not content-encoding - - * IE "chunked" body), gzip compressed, etc. it will not be decoded. - * - * @return string - */ - public function getRawBody() - { - return $this->body; - } - - /** - * Get the HTTP version of the response - * - * @return string - */ - public function getVersion() - { - return $this->version; - } - - /** - * Get the HTTP response status code - * - * @return int - */ - public function getStatus() - { - return $this->code; - } - - /** - * Return a message describing the HTTP response code - * (Eg. "OK", "Not Found", "Moved Permanently") - * - * @return string - */ - public function getMessage() - { - return $this->message; - } - - /** - * Get the response headers - * - * @return array - */ - public function getHeaders() - { - return $this->headers; - } - - /** - * Get a specific header as string, or null if it is not set - * - * @param string$header - * @return string|array|null - */ - public function getHeader($header) - { - $header = ucwords(strtolower($header)); - if (! is_string($header) || ! isset($this->headers[$header])) return null; - - return $this->headers[$header]; - } - - /** - * Get all headers as string - * - * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK") - * @param string $br Line breaks (eg. "\n", "\r\n", "
") - * @return string - */ - public function getHeadersAsString($status_line = true, $br = "\n") - { - $str = ''; - - if ($status_line) { - $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}"; - } - - // Iterate over the headers and stringify them - foreach ($this->headers as $name => $value) - { - if (is_string($value)) - $str .= "{$name}: {$value}{$br}"; - - elseif (is_array($value)) { - foreach ($value as $subval) { - $str .= "{$name}: {$subval}{$br}"; - } - } - } - - return $str; - } - - /** - * Get the entire response as string - * - * @param string $br Line breaks (eg. "\n", "\r\n", "
") - * @return string - */ - public function asString($br = "\n") - { - return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody(); - } - - /** - * A convenience function that returns a text representation of - * HTTP response codes. Returns 'Unknown' for unknown codes. - * Returns array of all codes, if $code is not specified. - * - * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown') - * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference - * - * @param int $code HTTP response code - * @param boolean $http11 Use HTTP version 1.1 - * @return string - */ - public static function responseCodeAsText($code = null, $http11 = true) - { - $messages = self::$messages; - if (! $http11) $messages[302] = 'Moved Temporarily'; - - if ($code === null) { - return $messages; - } elseif (isset($messages[$code])) { - return $messages[$code]; - } else { - return 'Unknown'; - } - } - - /** - * Extract the response code from a response string - * - * @param string $response_str - * @return int - */ - public static function extractCode($response_str) - { - preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m); - - if (isset($m[1])) { - return (int) $m[1]; - } else { - return false; - } - } - - /** - * Extract the HTTP message from a response - * - * @param string $response_str - * @return string - */ - public static function extractMessage($response_str) - { - preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m); - - if (isset($m[1])) { - return $m[1]; - } else { - return false; - } - } - - /** - * Extract the HTTP version from a response - * - * @param string $response_str - * @return string - */ - public static function extractVersion($response_str) - { - preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m); - - if (isset($m[1])) { - return $m[1]; - } else { - return false; - } - } - - /** - * Extract the headers from a response string - * - * @param string $response_str - * @return array - */ - public static function extractHeaders($response_str) - { - $headers = array(); - - // First, split body and headers - $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2); - if (! $parts[0]) return $headers; - - // Split headers part to lines - $lines = explode("\n", $parts[0]); - unset($parts); - $last_header = null; - - foreach($lines as $line) { - $line = trim($line, "\r\n"); - if ($line == "") break; - - if (preg_match("|^([\w-]+):\s+(.+)|", $line, $m)) { - unset($last_header); - $h_name = strtolower($m[1]); - $h_value = $m[2]; - - if (isset($headers[$h_name])) { - if (! is_array($headers[$h_name])) { - $headers[$h_name] = array($headers[$h_name]); - } - - $headers[$h_name][] = $h_value; - } else { - $headers[$h_name] = $h_value; - } - $last_header = $h_name; - } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) { - if (is_array($headers[$last_header])) { - end($headers[$last_header]); - $last_header_key = key($headers[$last_header]); - $headers[$last_header][$last_header_key] .= $m[1]; - } else { - $headers[$last_header] .= $m[1]; - } - } - } - - return $headers; - } - - /** - * Extract the body from a response string - * - * @param string $response_str - * @return string - */ - public static function extractBody($response_str) - { - $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2); - if (isset($parts[1])) { - return $parts[1]; - } else { - return ''; - } - } - - /** - * Decode a "chunked" transfer-encoded body and return the decoded text - * - * @param string $body - * @return string - */ - public static function decodeChunkedBody($body) - { - $decBody = ''; - - while (trim($body)) { - if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception("Error parsing body - doesn't seem to be a chunked message"); - } - - $length = hexdec(trim($m[1])); - $cut = strlen($m[0]); - - $decBody .= substr($body, $cut, $length); - $body = substr($body, $cut + $length + 2); - } - - return $decBody; - } - - /** - * Decode a gzip encoded message (when Content-encoding = gzip) - * - * Currently requires PHP with zlib support - * - * @param string $body - * @return string - */ - public static function decodeGzip($body) - { - if (! function_exists('gzinflate')) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('Unable to decode gzipped response ' . - 'body: perhaps the zlib extension is not loaded?'); - } - - return gzinflate(substr($body, 10)); - } - - /** - * Decode a zlib deflated message (when Content-encoding = deflate) - * - * Currently requires PHP with zlib support - * - * @param string $body - * @return string - */ - public static function decodeDeflate($body) - { - if (! function_exists('gzuncompress')) { - require_once 'Zend/Http/Exception.php'; - throw new Zend_Http_Exception('Unable to decode deflated response ' . - 'body: perhaps the zlib extension is not loaded?'); - } - - return gzuncompress($body); - } - - /** - * Create a new Zend_Http_Response object from a string - * - * @param string $response_str - * @return Zend_Http_Response - */ - public static function fromString($response_str) - { - $code = self::extractCode($response_str); - $headers = self::extractHeaders($response_str); - $body = self::extractBody($response_str); - $version = self::extractVersion($response_str); - $message = self::extractMessage($response_str); - - return new Zend_Http_Response($code, $headers, $body, $version, $message); - } -} diff --git a/phpQuery/phpQuery/Zend/Json/Decoder.php b/phpQuery/phpQuery/Zend/Json/Decoder.php deleted file mode 100644 index c5f1f25..0000000 --- a/phpQuery/phpQuery/Zend/Json/Decoder.php +++ /dev/null @@ -1,457 +0,0 @@ -_source = $source; - $this->_sourceLength = strlen($source); - $this->_token = self::EOF; - $this->_offset = 0; - - // Normalize and set $decodeType - if (!in_array($decodeType, array(Zend_Json::TYPE_ARRAY, Zend_Json::TYPE_OBJECT))) - { - $decodeType = Zend_Json::TYPE_ARRAY; - } - $this->_decodeType = $decodeType; - - // Set pointer at first token - $this->_getNextToken(); - } - - /** - * Decode a JSON source string - * - * Decodes a JSON encoded string. The value returned will be one of the - * following: - * - integer - * - float - * - boolean - * - null - * - StdClass - * - array - * - array of one or more of the above types - * - * By default, decoded objects will be returned as associative arrays; to - * return a StdClass object instead, pass {@link Zend_Json::TYPE_OBJECT} to - * the $objectDecodeType parameter. - * - * Throws a Zend_Json_Exception if the source string is null. - * - * @static - * @access public - * @param string $source String to be decoded - * @param int $objectDecodeType How objects should be decoded; should be - * either or {@link Zend_Json::TYPE_ARRAY} or - * {@link Zend_Json::TYPE_OBJECT}; defaults to TYPE_ARRAY - * @return mixed - * @throws Zend_Json_Exception - */ - public static function decode($source = null, $objectDecodeType = Zend_Json::TYPE_ARRAY) - { - if (null === $source) { - throw new Zend_Json_Exception('Must specify JSON encoded source for decoding'); - } elseif (!is_string($source)) { - throw new Zend_Json_Exception('Can only decode JSON encoded strings'); - } - - $decoder = new self($source, $objectDecodeType); - - return $decoder->_decodeValue(); - } - - - /** - * Recursive driving rountine for supported toplevel tops - * - * @return mixed - */ - protected function _decodeValue() - { - switch ($this->_token) { - case self::DATUM: - $result = $this->_tokenValue; - $this->_getNextToken(); - return($result); - break; - case self::LBRACE: - return($this->_decodeObject()); - break; - case self::LBRACKET: - return($this->_decodeArray()); - break; - default: - return null; - break; - } - } - - /** - * Decodes an object of the form: - * { "attribute: value, "attribute2" : value,...} - * - * If Zend_Json_Encoder was used to encode the original object then - * a special attribute called __className which specifies a class - * name that should wrap the data contained within the encoded source. - * - * Decodes to either an array or StdClass object, based on the value of - * {@link $_decodeType}. If invalid $_decodeType present, returns as an - * array. - * - * @return array|StdClass - */ - protected function _decodeObject() - { - $members = array(); - $tok = $this->_getNextToken(); - - while ($tok && $tok != self::RBRACE) { - if ($tok != self::DATUM || ! is_string($this->_tokenValue)) { - throw new Zend_Json_Exception('Missing key in object encoding: ' . $this->_source); - } - - $key = $this->_tokenValue; - $tok = $this->_getNextToken(); - - if ($tok != self::COLON) { - throw new Zend_Json_Exception('Missing ":" in object encoding: ' . $this->_source); - } - - $tok = $this->_getNextToken(); - $members[$key] = $this->_decodeValue(); - $tok = $this->_token; - - if ($tok == self::RBRACE) { - break; - } - - if ($tok != self::COMMA) { - throw new Zend_Json_Exception('Missing "," in object encoding: ' . $this->_source); - } - - $tok = $this->_getNextToken(); - } - - switch ($this->_decodeType) { - case Zend_Json::TYPE_OBJECT: - // Create new StdClass and populate with $members - $result = new StdClass(); - foreach ($members as $key => $value) { - $result->$key = $value; - } - break; - case Zend_Json::TYPE_ARRAY: - default: - $result = $members; - break; - } - - $this->_getNextToken(); - return $result; - } - - /** - * Decodes a JSON array format: - * [element, element2,...,elementN] - * - * @return array - */ - protected function _decodeArray() - { - $result = array(); - $starttok = $tok = $this->_getNextToken(); // Move past the '[' - $index = 0; - - while ($tok && $tok != self::RBRACKET) { - $result[$index++] = $this->_decodeValue(); - - $tok = $this->_token; - - if ($tok == self::RBRACKET || !$tok) { - break; - } - - if ($tok != self::COMMA) { - throw new Zend_Json_Exception('Missing "," in array encoding: ' . $this->_source); - } - - $tok = $this->_getNextToken(); - } - - $this->_getNextToken(); - return($result); - } - - - /** - * Removes whitepsace characters from the source input - */ - protected function _eatWhitespace() - { - if (preg_match( - '/([\t\b\f\n\r ])*/s', - $this->_source, - $matches, - PREG_OFFSET_CAPTURE, - $this->_offset) - && $matches[0][1] == $this->_offset) - { - $this->_offset += strlen($matches[0][0]); - } - } - - - /** - * Retrieves the next token from the source stream - * - * @return int Token constant value specified in class definition - */ - protected function _getNextToken() - { - $this->_token = self::EOF; - $this->_tokenValue = null; - $this->_eatWhitespace(); - - if ($this->_offset >= $this->_sourceLength) { - return(self::EOF); - } - - $str = $this->_source; - $str_length = $this->_sourceLength; - $i = $this->_offset; - $start = $i; - - switch ($str{$i}) { - case '{': - $this->_token = self::LBRACE; - break; - case '}': - $this->_token = self::RBRACE; - break; - case '[': - $this->_token = self::LBRACKET; - break; - case ']': - $this->_token = self::RBRACKET; - break; - case ',': - $this->_token = self::COMMA; - break; - case ':': - $this->_token = self::COLON; - break; - case '"': - $result = ''; - do { - $i++; - if ($i >= $str_length) { - break; - } - - $chr = $str{$i}; - if ($chr == '\\') { - $i++; - if ($i >= $str_length) { - break; - } - $chr = $str{$i}; - switch ($chr) { - case '"' : - $result .= '"'; - break; - case '\\': - $result .= '\\'; - break; - case '/' : - $result .= '/'; - break; - case 'b' : - $result .= chr(8); - break; - case 'f' : - $result .= chr(12); - break; - case 'n' : - $result .= chr(10); - break; - case 'r' : - $result .= chr(13); - break; - case 't' : - $result .= chr(9); - break; - case '\'' : - $result .= '\''; - break; - default: - throw new Zend_Json_Exception("Illegal escape " - . "sequence '" . $chr . "'"); - } - } elseif ($chr == '"') { - break; - } else { - $result .= $chr; - } - } while ($i < $str_length); - - $this->_token = self::DATUM; - //$this->_tokenValue = substr($str, $start + 1, $i - $start - 1); - $this->_tokenValue = $result; - break; - case 't': - if (($i+ 3) < $str_length && substr($str, $start, 4) == "true") { - $this->_token = self::DATUM; - } - $this->_tokenValue = true; - $i += 3; - break; - case 'f': - if (($i+ 4) < $str_length && substr($str, $start, 5) == "false") { - $this->_token = self::DATUM; - } - $this->_tokenValue = false; - $i += 4; - break; - case 'n': - if (($i+ 3) < $str_length && substr($str, $start, 4) == "null") { - $this->_token = self::DATUM; - } - $this->_tokenValue = NULL; - $i += 3; - break; - } - - if ($this->_token != self::EOF) { - $this->_offset = $i + 1; // Consume the last token character - return($this->_token); - } - - $chr = $str{$i}; - if ($chr == '-' || $chr == '.' || ($chr >= '0' && $chr <= '9')) { - if (preg_match('/-?([0-9])*(\.[0-9]*)?((e|E)((-|\+)?)[0-9]+)?/s', - $str, $matches, PREG_OFFSET_CAPTURE, $start) && $matches[0][1] == $start) { - - $datum = $matches[0][0]; - - if (is_numeric($datum)) { - if (preg_match('/^0\d+$/', $datum)) { - throw new Zend_Json_Exception("Octal notation not supported by JSON (value: $datum)"); - } else { - $val = intval($datum); - $fVal = floatval($datum); - $this->_tokenValue = ($val == $fVal ? $val : $fVal); - } - } else { - throw new Zend_Json_Exception("Illegal number format: $datum"); - } - - $this->_token = self::DATUM; - $this->_offset = $start + strlen($datum); - } - } else { - throw new Zend_Json_Exception('Illegal Token'); - } - - return($this->_token); - } -} - diff --git a/phpQuery/phpQuery/Zend/Json/Encoder.php b/phpQuery/phpQuery/Zend/Json/Encoder.php deleted file mode 100644 index ce2024a..0000000 --- a/phpQuery/phpQuery/Zend/Json/Encoder.php +++ /dev/null @@ -1,431 +0,0 @@ -_cycleCheck = $cycleCheck; - $this->_options = $options; - } - - /** - * Use the JSON encoding scheme for the value specified - * - * @param mixed $value The value to be encoded - * @param boolean $cycleCheck Whether or not to check for possible object recursion when encoding - * @param array $options Additional options used during encoding - * @return string The encoded value - */ - public static function encode($value, $cycleCheck = false, $options = array()) - { - $encoder = new self(($cycleCheck) ? true : false, $options); - - return $encoder->_encodeValue($value); - } - - /** - * Recursive driver which determines the type of value to be encoded - * and then dispatches to the appropriate method. $values are either - * - objects (returns from {@link _encodeObject()}) - * - arrays (returns from {@link _encodeArray()}) - * - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()}) - * - * @param $value mixed The value to be encoded - * @return string Encoded value - */ - protected function _encodeValue(&$value) - { - if (is_object($value)) { - return $this->_encodeObject($value); - } else if (is_array($value)) { - return $this->_encodeArray($value); - } - - return $this->_encodeDatum($value); - } - - - - /** - * Encode an object to JSON by encoding each of the public properties - * - * A special property is added to the JSON object called '__className' - * that contains the name of the class of $value. This is used to decode - * the object on the client into a specific class. - * - * @param $value object - * @return string - * @throws Zend_Json_Exception If recursive checks are enabled and the object has been serialized previously - */ - protected function _encodeObject(&$value) - { - if ($this->_cycleCheck) { - if ($this->_wasVisited($value)) { - - if (isset($this->_options['silenceCyclicalExceptions']) - && $this->_options['silenceCyclicalExceptions']===true) { - - return '"* RECURSION (' . get_class($value) . ') *"'; - - } else { - throw new Zend_Json_Exception( - 'Cycles not supported in JSON encoding, cycle introduced by ' - . 'class "' . get_class($value) . '"' - ); - } - } - - $this->_visited[] = $value; - } - - $props = ''; - foreach (get_object_vars($value) as $name => $propValue) { - if (isset($propValue)) { - $props .= ',' - . $this->_encodeValue($name) - . ':' - . $this->_encodeValue($propValue); - } - } - - return '{"__className":"' . get_class($value) . '"' - . $props . '}'; - } - - - /** - * Determine if an object has been serialized already - * - * @param mixed $value - * @return boolean - */ - protected function _wasVisited(&$value) - { - if (in_array($value, $this->_visited, true)) { - return true; - } - - return false; - } - - - /** - * JSON encode an array value - * - * Recursively encodes each value of an array and returns a JSON encoded - * array string. - * - * Arrays are defined as integer-indexed arrays starting at index 0, where - * the last index is (count($array) -1); any deviation from that is - * considered an associative array, and will be encoded as such. - * - * @param $array array - * @return string - */ - protected function _encodeArray(&$array) - { - $tmpArray = array(); - - // Check for associative array - if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) { - // Associative array - $result = '{'; - foreach ($array as $key => $value) { - $key = (string) $key; - $tmpArray[] = $this->_encodeString($key) - . ':' - . $this->_encodeValue($value); - } - $result .= implode(',', $tmpArray); - $result .= '}'; - } else { - // Indexed array - $result = '['; - $length = count($array); - for ($i = 0; $i < $length; $i++) { - $tmpArray[] = $this->_encodeValue($array[$i]); - } - $result .= implode(',', $tmpArray); - $result .= ']'; - } - - return $result; - } - - - /** - * JSON encode a basic data type (string, number, boolean, null) - * - * If value type is not a string, number, boolean, or null, the string - * 'null' is returned. - * - * @param $value mixed - * @return string - */ - protected function _encodeDatum(&$value) - { - $result = 'null'; - - if (is_int($value) || is_float($value)) { - $result = (string)$value; - } elseif (is_string($value)) { - $result = $this->_encodeString($value); - } elseif (is_bool($value)) { - $result = $value ? 'true' : 'false'; - } - - return $result; - } - - - /** - * JSON encode a string value by escaping characters as necessary - * - * @param $value string - * @return string - */ - protected function _encodeString(&$string) - { - // Escape these characters with a backslash: - // " \ / \n \r \t \b \f - $search = array('\\', "\n", "\t", "\r", "\b", "\f", '"'); - $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'); - $string = str_replace($search, $replace, $string); - - // Escape certain ASCII characters: - // 0x08 => \b - // 0x0c => \f - $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string); - - return '"' . $string . '"'; - } - - - /** - * Encode the constants associated with the ReflectionClass - * parameter. The encoding format is based on the class2 format - * - * @param $cls ReflectionClass - * @return string Encoded constant block in class2 format - */ - private static function _encodeConstants(ReflectionClass $cls) - { - $result = "constants : {"; - $constants = $cls->getConstants(); - - $tmpArray = array(); - if (!empty($constants)) { - foreach ($constants as $key => $value) { - $tmpArray[] = "$key: " . self::encode($value); - } - - $result .= implode(', ', $tmpArray); - } - - return $result . "}"; - } - - - /** - * Encode the public methods of the ReflectionClass in the - * class2 format - * - * @param $cls ReflectionClass - * @return string Encoded method fragment - * - */ - private static function _encodeMethods(ReflectionClass $cls) - { - $methods = $cls->getMethods(); - $result = 'methods:{'; - - $started = false; - foreach ($methods as $method) { - if (! $method->isPublic() || !$method->isUserDefined()) { - continue; - } - - if ($started) { - $result .= ','; - } - $started = true; - - $result .= '' . $method->getName(). ':function('; - - if ('__construct' != $method->getName()) { - $parameters = $method->getParameters(); - $paramCount = count($parameters); - $argsStarted = false; - - $argNames = "var argNames=["; - foreach ($parameters as $param) { - if ($argsStarted) { - $result .= ','; - } - - $result .= $param->getName(); - - if ($argsStarted) { - $argNames .= ','; - } - - $argNames .= '"' . $param->getName() . '"'; - - $argsStarted = true; - } - $argNames .= "];"; - - $result .= "){" - . $argNames - . 'var result = ZAjaxEngine.invokeRemoteMethod(' - . "this, '" . $method->getName() - . "',argNames,arguments);" - . 'return(result);}'; - } else { - $result .= "){}"; - } - } - - return $result . "}"; - } - - - /** - * Encode the public properties of the ReflectionClass in the class2 - * format. - * - * @param $cls ReflectionClass - * @return string Encode properties list - * - */ - private static function _encodeVariables(ReflectionClass $cls) - { - $properties = $cls->getProperties(); - $propValues = get_class_vars($cls->getName()); - $result = "variables:{"; - $cnt = 0; - - $tmpArray = array(); - foreach ($properties as $prop) { - if (! $prop->isPublic()) { - continue; - } - - $tmpArray[] = $prop->getName() - . ':' - . self::encode($propValues[$prop->getName()]); - } - $result .= implode(',', $tmpArray); - - return $result . "}"; - } - - /** - * Encodes the given $className into the class2 model of encoding PHP - * classes into JavaScript class2 classes. - * NOTE: Currently only public methods and variables are proxied onto - * the client machine - * - * @param $className string The name of the class, the class must be - * instantiable using a null constructor - * @param $package string Optional package name appended to JavaScript - * proxy class name - * @return string The class2 (JavaScript) encoding of the class - * @throws Zend_Json_Exception - */ - public static function encodeClass($className, $package = '') - { - $cls = new ReflectionClass($className); - if (! $cls->isInstantiable()) { - throw new Zend_Json_Exception("$className must be instantiable"); - } - - return "Class.create('$package$className',{" - . self::_encodeConstants($cls) ."," - . self::_encodeMethods($cls) ."," - . self::_encodeVariables($cls) .'});'; - } - - - /** - * Encode several classes at once - * - * Returns JSON encoded classes, using {@link encodeClass()}. - * - * @param array $classNames - * @param string $package - * @return string - */ - public static function encodeClasses(array $classNames, $package = '') - { - $result = ''; - foreach ($classNames as $className) { - $result .= self::encodeClass($className, $package); - } - - return $result; - } - -} - diff --git a/phpQuery/phpQuery/Zend/Json/Exception.php b/phpQuery/phpQuery/Zend/Json/Exception.php deleted file mode 100644 index 5b99095..0000000 --- a/phpQuery/phpQuery/Zend/Json/Exception.php +++ /dev/null @@ -1,36 +0,0 @@ - $dir) { - if ($dir == '.') { - $dirs[$key] = $dirPath; - } else { - $dir = rtrim($dir, '\\/'); - $dirs[$key] = $dir . DIRECTORY_SEPARATOR . $dirPath; - } - } - $file = basename($file); - self::loadFile($file, $dirs, true); - } else { - self::_securityCheck($file); - include_once $file; - } - - if (!class_exists($class, false) && !interface_exists($class, false)) { - require_once 'Zend/Exception.php'; - throw new Zend_Exception("File \"$file\" does not exist or class \"$class\" was not found in the file"); - } - } - - /** - * Loads a PHP file. This is a wrapper for PHP's include() function. - * - * $filename must be the complete filename, including any - * extension such as ".php". Note that a security check is performed that - * does not permit extended characters in the filename. This method is - * intended for loading Zend Framework files. - * - * If $dirs is a string or an array, it will search the directories - * in the order supplied, and attempt to load the first matching file. - * - * If the file was not found in the $dirs, or if no $dirs were specified, - * it will attempt to load it from PHP's include_path. - * - * If $once is TRUE, it will use include_once() instead of include(). - * - * @param string $filename - * @param string|array $dirs - OPTIONAL either a path or array of paths - * to search. - * @param boolean $once - * @return boolean - * @throws Zend_Exception - */ - public static function loadFile($filename, $dirs = null, $once = false) - { - self::_securityCheck($filename); - - /** - * Search in provided directories, as well as include_path - */ - $incPath = false; - if (!empty($dirs) && (is_array($dirs) || is_string($dirs))) { - if (is_array($dirs)) { - $dirs = implode(PATH_SEPARATOR, $dirs); - } - $incPath = get_include_path(); - set_include_path($dirs . PATH_SEPARATOR . $incPath); - } - - /** - * Try finding for the plain filename in the include_path. - */ - if ($once) { - include_once $filename; - } else { - include $filename; - } - - /** - * If searching in directories, reset include_path - */ - if ($incPath) { - set_include_path($incPath); - } - - return true; - } - - /** - * Returns TRUE if the $filename is readable, or FALSE otherwise. - * This function uses the PHP include_path, where PHP's is_readable() - * does not. - * - * @param string $filename - * @return boolean - */ - public static function isReadable($filename) - { - if (!$fh = @fopen($filename, 'r', true)) { - return false; - } - @fclose($fh); - return true; - } - - /** - * spl_autoload() suitable implementation for supporting class autoloading. - * - * Attach to spl_autoload() using the following: - * - * spl_autoload_register(array('Zend_Loader', 'autoload')); - * - * - * @param string $class - * @return string|false Class name on success; false on failure - */ - public static function autoload($class) - { - try { - self::loadClass($class); - return $class; - } catch (Exception $e) { - return false; - } - } - - /** - * Register {@link autoload()} with spl_autoload() - * - * @param string $class (optional) - * @param boolean $enabled (optional) - * @return void - * @throws Zend_Exception if spl_autoload() is not found - * or if the specified class does not have an autoload() method. - */ - public static function registerAutoload($class = 'Zend_Loader', $enabled = true) - { - if (!function_exists('spl_autoload_register')) { - require_once 'Zend/Exception.php'; - throw new Zend_Exception('spl_autoload does not exist in this PHP installation'); - } - - self::loadClass($class); - $methods = get_class_methods($class); - if (!in_array('autoload', (array) $methods)) { - require_once 'Zend/Exception.php'; - throw new Zend_Exception("The class \"$class\" does not have an autoload() method"); - } - - if ($enabled === true) { - spl_autoload_register(array($class, 'autoload')); - } else { - spl_autoload_unregister(array($class, 'autoload')); - } - } - - /** - * Ensure that filename does not contain exploits - * - * @param string $filename - * @return void - * @throws Zend_Exception - */ - protected static function _securityCheck($filename) - { - /** - * Security check - */ - if (preg_match('/[^a-z0-9\\/\\\\_.-]/i', $filename)) { - require_once 'Zend/Exception.php'; - throw new Zend_Exception('Security check: Illegal character in filename'); - } - } - - /** - * Attempt to include() the file. - * - * include() is not prefixed with the @ operator because if - * the file is loaded and contains a parse error, execution - * will halt silently and this is difficult to debug. - * - * Always set display_errors = Off on production servers! - * - * @param string $filespec - * @param boolean $once - * @return boolean - * @deprecated Since 1.5.0; use loadFile() instead - */ - protected static function _includeFile($filespec, $once = false) - { - if ($once) { - return include_once $filespec; - } else { - return include $filespec ; - } - } -} diff --git a/phpQuery/phpQuery/Zend/Registry.php b/phpQuery/phpQuery/Zend/Registry.php deleted file mode 100644 index 62d9ceb..0000000 --- a/phpQuery/phpQuery/Zend/Registry.php +++ /dev/null @@ -1,195 +0,0 @@ -offsetExists($index)) { - require_once 'Zend/Exception.php'; - throw new Zend_Exception("No entry is registered for key '$index'"); - } - - return $instance->offsetGet($index); - } - - /** - * setter method, basically same as offsetSet(). - * - * This method can be called from an object of type Zend_Registry, or it - * can be called statically. In the latter case, it uses the default - * static instance stored in the class. - * - * @param string $index The location in the ArrayObject in which to store - * the value. - * @param mixed $value The object to store in the ArrayObject. - * @return void - */ - public static function set($index, $value) - { - $instance = self::getInstance(); - $instance->offsetSet($index, $value); - } - - /** - * Returns TRUE if the $index is a named value in the registry, - * or FALSE if $index was not found in the registry. - * - * @param string $index - * @return boolean - */ - public static function isRegistered($index) - { - if (self::$_registry === null) { - return false; - } - return self::$_registry->offsetExists($index); - } - - /** - * @param string $index - * @returns mixed - * - * Workaround for http://bugs.php.net/bug.php?id=40442 (ZF-960). - */ - public function offsetExists($index) - { - return array_key_exists($index, $this); - } - -} diff --git a/phpQuery/phpQuery/Zend/Uri.php b/phpQuery/phpQuery/Zend/Uri.php deleted file mode 100644 index 4c5776b..0000000 --- a/phpQuery/phpQuery/Zend/Uri.php +++ /dev/null @@ -1,164 +0,0 @@ -getUri(); - } - - /** - * Convenience function, checks that a $uri string is well-formed - * by validating it but not returning an object. Returns TRUE if - * $uri is a well-formed URI, or FALSE otherwise. - * - * @param string $uri The URI to check - * @return boolean - */ - public static function check($uri) - { - try { - $uri = self::factory($uri); - } catch (Exception $e) { - return false; - } - - return $uri->valid(); - } - - /** - * Create a new Zend_Uri object for a URI. If building a new URI, then $uri should contain - * only the scheme (http, ftp, etc). Otherwise, supply $uri with the complete URI. - * - * @param string $uri The URI form which a Zend_Uri instance is created - * @throws Zend_Uri_Exception When an empty string was supplied for the scheme - * @throws Zend_Uri_Exception When an illegal scheme is supplied - * @throws Zend_Uri_Exception When the scheme is not supported - * @return Zend_Uri - * @link http://www.faqs.org/rfcs/rfc2396.html - */ - public static function factory($uri = 'http') - { - // Separate the scheme from the scheme-specific parts - $uri = explode(':', $uri, 2); - $scheme = strtolower($uri[0]); - $schemeSpecific = isset($uri[1]) === true ? $uri[1] : ''; - - if (strlen($scheme) === 0) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('An empty string was supplied for the scheme'); - } - - // Security check: $scheme is used to load a class file, so only alphanumerics are allowed. - if (ctype_alnum($scheme) === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Illegal scheme supplied, only alphanumeric characters are permitted'); - } - - /** - * Create a new Zend_Uri object for the $uri. If a subclass of Zend_Uri exists for the - * scheme, return an instance of that class. Otherwise, a Zend_Uri_Exception is thrown. - */ - switch ($scheme) { - case 'http': - // Break intentionally omitted - case 'https': - $className = 'Zend_Uri_Http'; - break; - - case 'mailto': - // TODO - default: - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception("Scheme \"$scheme\" is not supported"); - break; - } - - Zend_Loader::loadClass($className); - $schemeHandler = new $className($scheme, $schemeSpecific); - - return $schemeHandler; - } - - /** - * Get the URI's scheme - * - * @return string|false Scheme or false if no scheme is set. - */ - public function getScheme() - { - if (empty($this->_scheme) === false) { - return $this->_scheme; - } else { - return false; - } - } - - /** - * Zend_Uri and its subclasses cannot be instantiated directly. - * Use Zend_Uri::factory() to return a new Zend_Uri object. - * - * @param string $scheme The scheme of the URI - * @param string $schemeSpecific The scheme-specific part of the URI - */ - abstract protected function __construct($scheme, $schemeSpecific = ''); - - /** - * Return a string representation of this URI. - * - * @return string - */ - abstract public function getUri(); - - /** - * Returns TRUE if this URI is valid, or FALSE otherwise. - * - * @return boolean - */ - abstract public function valid(); -} diff --git a/phpQuery/phpQuery/Zend/Uri/Exception.php b/phpQuery/phpQuery/Zend/Uri/Exception.php deleted file mode 100644 index d327f3c..0000000 --- a/phpQuery/phpQuery/Zend/Uri/Exception.php +++ /dev/null @@ -1,37 +0,0 @@ -_scheme = $scheme; - - // Set up grammar rules for validation via regular expressions. These - // are to be used with slash-delimited regular expression strings. - $this->_regex['alphanum'] = '[^\W_]'; - $this->_regex['escaped'] = '(?:%[\da-fA-F]{2})'; - $this->_regex['mark'] = '[-_.!~*\'()\[\]]'; - $this->_regex['reserved'] = '[;\/?:@&=+$,]'; - $this->_regex['unreserved'] = '(?:' . $this->_regex['alphanum'] . '|' . $this->_regex['mark'] . ')'; - $this->_regex['segment'] = '(?:(?:' . $this->_regex['unreserved'] . '|' . $this->_regex['escaped'] - . '|[:@&=+$,;])*)'; - $this->_regex['path'] = '(?:\/' . $this->_regex['segment'] . '?)+'; - $this->_regex['uric'] = '(?:' . $this->_regex['reserved'] . '|' . $this->_regex['unreserved'] . '|' - . $this->_regex['escaped'] . ')'; - // If no scheme-specific part was supplied, the user intends to create - // a new URI with this object. No further parsing is required. - if (strlen($schemeSpecific) === 0) { - return; - } - - // Parse the scheme-specific URI parts into the instance variables. - $this->_parseUri($schemeSpecific); - - // Validate the URI - if ($this->valid() === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Invalid URI supplied'); - } - } - - /** - * Creates a Zend_Uri_Http from the given string - * - * @param string $uri String to create URI from, must start with - * 'http://' or 'https://' - * @throws InvalidArgumentException When the given $uri is not a string or - * does not start with http:// or https:// - * @throws Zend_Uri_Exception When the given $uri is invalid - * @return Zend_Uri_Http - */ - public static function fromString($uri) - { - if (is_string($uri) === false) { - throw new InvalidArgumentException('$uri is not a string'); - } - - $uri = explode(':', $uri, 2); - $scheme = strtolower($uri[0]); - $schemeSpecific = isset($uri[1]) === true ? $uri[1] : ''; - - if (in_array($scheme, array('http', 'https')) === false) { - throw new Zend_Uri_Exception("Invalid scheme: '$scheme'"); - } - - $schemeHandler = new Zend_Uri_Http($scheme, $schemeSpecific); - return $schemeHandler; - } - - /** - * Parse the scheme-specific portion of the URI and place its parts into instance variables. - * - * @param string $schemeSpecific The scheme-specific portion to parse - * @throws Zend_Uri_Exception When scheme-specific decoposition fails - * @throws Zend_Uri_Exception When authority decomposition fails - * @return void - */ - protected function _parseUri($schemeSpecific) - { - // High-level decomposition parser - $pattern = '~^((//)([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))?$~'; - $status = @preg_match($pattern, $schemeSpecific, $matches); - if ($status === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Internal error: scheme-specific decomposition failed'); - } - - // Failed decomposition; no further processing needed - if ($status === false) { - return; - } - - // Save URI components that need no further decomposition - $this->_path = isset($matches[4]) === true ? $matches[4] : ''; - $this->_query = isset($matches[6]) === true ? $matches[6] : ''; - $this->_fragment = isset($matches[8]) === true ? $matches[8] : ''; - - // Additional decomposition to get username, password, host, and port - $combo = isset($matches[3]) === true ? $matches[3] : ''; - $pattern = '~^(([^:@]*)(:([^@]*))?@)?([^:]+)(:(.*))?$~'; - $status = @preg_match($pattern, $combo, $matches); - if ($status === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Internal error: authority decomposition failed'); - } - - // Failed decomposition; no further processing needed - if ($status === false) { - return; - } - - // Save remaining URI components - $this->_username = isset($matches[2]) === true ? $matches[2] : ''; - $this->_password = isset($matches[4]) === true ? $matches[4] : ''; - $this->_host = isset($matches[5]) === true ? $matches[5] : ''; - $this->_port = isset($matches[7]) === true ? $matches[7] : ''; - - } - - /** - * Returns a URI based on current values of the instance variables. If any - * part of the URI does not pass validation, then an exception is thrown. - * - * @throws Zend_Uri_Exception When one or more parts of the URI are invalid - * @return string - */ - public function getUri() - { - if ($this->valid() === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('One or more parts of the URI are invalid'); - } - - $password = strlen($this->_password) > 0 ? ":$this->_password" : ''; - $auth = strlen($this->_username) > 0 ? "$this->_username$password@" : ''; - $port = strlen($this->_port) > 0 ? ":$this->_port" : ''; - $query = strlen($this->_query) > 0 ? "?$this->_query" : ''; - $fragment = strlen($this->_fragment) > 0 ? "#$this->_fragment" : ''; - - return $this->_scheme - . '://' - . $auth - . $this->_host - . $port - . $this->_path - . $query - . $fragment; - } - - /** - * Validate the current URI from the instance variables. Returns true if and only if all - * parts pass validation. - * - * @return boolean - */ - public function valid() - { - // Return true if and only if all parts of the URI have passed validation - return $this->validateUsername() - and $this->validatePassword() - and $this->validateHost() - and $this->validatePort() - and $this->validatePath() - and $this->validateQuery() - and $this->validateFragment(); - } - - /** - * Returns the username portion of the URL, or FALSE if none. - * - * @return string - */ - public function getUsername() - { - return strlen($this->_username) > 0 ? $this->_username : false; - } - - /** - * Returns true if and only if the username passes validation. If no username is passed, - * then the username contained in the instance variable is used. - * - * @param string $username The HTTP username - * @throws Zend_Uri_Exception When username validation fails - * @return boolean - * @link http://www.faqs.org/rfcs/rfc2396.html - */ - public function validateUsername($username = null) - { - if ($username === null) { - $username = $this->_username; - } - - // If the username is empty, then it is considered valid - if (strlen($username) === 0) { - return true; - } - - // Check the username against the allowed values - $status = @preg_match('/^(' . $this->_regex['alphanum'] . '|' . $this->_regex['mark'] . '|' - . $this->_regex['escaped'] . '|[;:&=+$,])+$/', $username); - if ($status === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Internal error: username validation failed'); - } - - return $status === 1; - } - - /** - * Sets the username for the current URI, and returns the old username - * - * @param string $username The HTTP username - * @throws Zend_Uri_Exception When $username is not a valid HTTP username - * @return string - */ - public function setUsername($username) - { - if ($this->validateUsername($username) === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception("Username \"$username\" is not a valid HTTP username"); - } - - $oldUsername = $this->_username; - $this->_username = $username; - - return $oldUsername; - } - - /** - * Returns the password portion of the URL, or FALSE if none. - * - * @return string - */ - public function getPassword() - { - return strlen($this->_password) > 0 ? $this->_password : false; - } - - /** - * Returns true if and only if the password passes validation. If no password is passed, - * then the password contained in the instance variable is used. - * - * @param string $password The HTTP password - * @throws Zend_Uri_Exception When password validation fails - * @return boolean - * @link http://www.faqs.org/rfcs/rfc2396.html - */ - public function validatePassword($password = null) - { - if ($password === null) { - $password = $this->_password; - } - - // If the password is empty, then it is considered valid - if (strlen($password) === 0) { - return true; - } - - // If the password is nonempty, but there is no username, then it is considered invalid - if (strlen($password) > 0 and strlen($this->_username) === 0) { - return false; - } - - // Check the password against the allowed values - $status = @preg_match('/^(' . $this->_regex['alphanum'] . '|' . $this->_regex['mark'] . '|' - . $this->_regex['escaped'] . '|[;:&=+$,])+$/', $password); - if ($status === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Internal error: password validation failed.'); - } - - return $status == 1; - } - - /** - * Sets the password for the current URI, and returns the old password - * - * @param string $password The HTTP password - * @throws Zend_Uri_Exception When $password is not a valid HTTP password - * @return string - */ - public function setPassword($password) - { - if ($this->validatePassword($password) === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception("Password \"$password\" is not a valid HTTP password."); - } - - $oldPassword = $this->_password; - $this->_password = $password; - - return $oldPassword; - } - - /** - * Returns the domain or host IP portion of the URL, or FALSE if none. - * - * @return string - */ - public function getHost() - { - return strlen($this->_host) > 0 ? $this->_host : false; - } - - /** - * Returns true if and only if the host string passes validation. If no host is passed, - * then the host contained in the instance variable is used. - * - * @param string $host The HTTP host - * @return boolean - * @uses Zend_Filter - */ - public function validateHost($host = null) - { - if ($host === null) { - $host = $this->_host; - } - - // If the host is empty, then it is considered invalid - if (strlen($host) === 0) { - return false; - } - - // Check the host against the allowed values; delegated to Zend_Filter. - $validate = new Zend_Validate_Hostname(Zend_Validate_Hostname::ALLOW_ALL); - - return $validate->isValid($host); - } - - /** - * Sets the host for the current URI, and returns the old host - * - * @param string $host The HTTP host - * @throws Zend_Uri_Exception When $host is nota valid HTTP host - * @return string - */ - public function setHost($host) - { - if ($this->validateHost($host) === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception("Host \"$host\" is not a valid HTTP host"); - } - - $oldHost = $this->_host; - $this->_host = $host; - - return $oldHost; - } - - /** - * Returns the TCP port, or FALSE if none. - * - * @return string - */ - public function getPort() - { - return strlen($this->_port) > 0 ? $this->_port : false; - } - - /** - * Returns true if and only if the TCP port string passes validation. If no port is passed, - * then the port contained in the instance variable is used. - * - * @param string $port The HTTP port - * @return boolean - */ - public function validatePort($port = null) - { - if ($port === null) { - $port = $this->_port; - } - - // If the port is empty, then it is considered valid - if (strlen($port) === 0) { - return true; - } - - // Check the port against the allowed values - return ctype_digit((string) $port) and 1 <= $port and $port <= 65535; - } - - /** - * Sets the port for the current URI, and returns the old port - * - * @param string $port The HTTP port - * @throws Zend_Uri_Exception When $port is not a valid HTTP port - * @return string - */ - public function setPort($port) - { - if ($this->validatePort($port) === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception("Port \"$port\" is not a valid HTTP port."); - } - - $oldPort = $this->_port; - $this->_port = $port; - - return $oldPort; - } - - /** - * Returns the path and filename portion of the URL, or FALSE if none. - * - * @return string - */ - public function getPath() - { - return strlen($this->_path) > 0 ? $this->_path : '/'; - } - - /** - * Returns true if and only if the path string passes validation. If no path is passed, - * then the path contained in the instance variable is used. - * - * @param string $path The HTTP path - * @throws Zend_Uri_Exception When path validation fails - * @return boolean - */ - public function validatePath($path = null) - { - if ($path === null) { - $path = $this->_path; - } - - // If the path is empty, then it is considered valid - if (strlen($path) === 0) { - return true; - } - - // Determine whether the path is well-formed - $pattern = '/^' . $this->_regex['path'] . '$/'; - $status = @preg_match($pattern, $path); - if ($status === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Internal error: path validation failed'); - } - - return (boolean) $status; - } - - /** - * Sets the path for the current URI, and returns the old path - * - * @param string $path The HTTP path - * @throws Zend_Uri_Exception When $path is not a valid HTTP path - * @return string - */ - public function setPath($path) - { - if ($this->validatePath($path) === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception("Path \"$path\" is not a valid HTTP path"); - } - - $oldPath = $this->_path; - $this->_path = $path; - - return $oldPath; - } - - /** - * Returns the query portion of the URL (after ?), or FALSE if none. - * - * @return string - */ - public function getQuery() - { - return strlen($this->_query) > 0 ? $this->_query : false; - } - - /** - * Returns true if and only if the query string passes validation. If no query is passed, - * then the query string contained in the instance variable is used. - * - * @param string $query The query to validate - * @throws Zend_Uri_Exception When query validation fails - * @return boolean - * @link http://www.faqs.org/rfcs/rfc2396.html - */ - public function validateQuery($query = null) - { - if ($query === null) { - $query = $this->_query; - } - - // If query is empty, it is considered to be valid - if (strlen($query) === 0) { - return true; - } - - // Determine whether the query is well-formed - $pattern = '/^' . $this->_regex['uric'] . '*$/'; - $status = @preg_match($pattern, $query); - if ($status === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Internal error: query validation failed'); - } - - return $status == 1; - } - - /** - * Set the query string for the current URI, and return the old query - * string This method accepts both strings and arrays. - * - * @param string|array $query The query string or array - * @throws Zend_Uri_Exception When $query is not a valid query string - * @return string Old query string - */ - public function setQuery($query) - { - $oldQuery = $this->_query; - - // If query is empty, set an empty string - if (empty($query) === true) { - $this->_query = ''; - return $oldQuery; - } - - // If query is an array, make a string out of it - if (is_array($query) === true) { - $query = http_build_query($query, '', '&'); - } else { - // If it is a string, make sure it is valid. If not parse and encode it - $query = (string) $query; - if ($this->validateQuery($query) === false) { - parse_str($query, $queryArray); - $query = http_build_query($queryArray, '', '&'); - } - } - - // Make sure the query is valid, and set it - if ($this->validateQuery($query) === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception("'$query' is not a valid query string"); - } - - $this->_query = $query; - - return $oldQuery; - } - - /** - * Returns the fragment portion of the URL (after #), or FALSE if none. - * - * @return string|false - */ - public function getFragment() - { - return strlen($this->_fragment) > 0 ? $this->_fragment : false; - } - - /** - * Returns true if and only if the fragment passes validation. If no fragment is passed, - * then the fragment contained in the instance variable is used. - * - * @param string $fragment Fragment of an URI - * @throws Zend_Uri_Exception When fragment validation fails - * @return boolean - * @link http://www.faqs.org/rfcs/rfc2396.html - */ - public function validateFragment($fragment = null) - { - if ($fragment === null) { - $fragment = $this->_fragment; - } - - // If fragment is empty, it is considered to be valid - if (strlen($fragment) === 0) { - return true; - } - - // Determine whether the fragment is well-formed - $pattern = '/^' . $this->_regex['uric'] . '*$/'; - $status = @preg_match($pattern, $fragment); - if ($status === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception('Internal error: fragment validation failed'); - } - - return (boolean) $status; - } - - /** - * Sets the fragment for the current URI, and returns the old fragment - * - * @param string $fragment Fragment of the current URI - * @throws Zend_Uri_Exception When $fragment is not a valid HTTP fragment - * @return string - */ - public function setFragment($fragment) - { - if ($this->validateFragment($fragment) === false) { - require_once 'Zend/Uri/Exception.php'; - throw new Zend_Uri_Exception("Fragment \"$fragment\" is not a valid HTTP fragment"); - } - - $oldFragment = $this->_fragment; - $this->_fragment = $fragment; - - return $oldFragment; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/Abstract.php b/phpQuery/phpQuery/Zend/Validate/Abstract.php deleted file mode 100644 index 1c11d54..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Abstract.php +++ /dev/null @@ -1,348 +0,0 @@ -_messages; - } - - /** - * Returns an array of the names of variables that are used in constructing validation failure messages - * - * @return array - */ - public function getMessageVariables() - { - return array_keys($this->_messageVariables); - } - - /** - * Sets the validation failure message template for a particular key - * - * @param string $messageString - * @param string $messageKey OPTIONAL - * @return Zend_Validate_Abstract Provides a fluent interface - * @throws Zend_Validate_Exception - */ - public function setMessage($messageString, $messageKey = null) - { - if ($messageKey === null) { - $keys = array_keys($this->_messageTemplates); - $messageKey = current($keys); - } - if (!isset($this->_messageTemplates[$messageKey])) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("No message template exists for key '$messageKey'"); - } - $this->_messageTemplates[$messageKey] = $messageString; - return $this; - } - - /** - * Sets validation failure message templates given as an array, where the array keys are the message keys, - * and the array values are the message template strings. - * - * @param array $messages - * @return Zend_Validate_Abstract - */ - public function setMessages(array $messages) - { - foreach ($messages as $key => $message) { - $this->setMessage($message, $key); - } - return $this; - } - - /** - * Magic function returns the value of the requested property, if and only if it is the value or a - * message variable. - * - * @param string $property - * @return mixed - * @throws Zend_Validate_Exception - */ - public function __get($property) - { - if ($property == 'value') { - return $this->_value; - } - if (array_key_exists($property, $this->_messageVariables)) { - return $this->{$this->_messageVariables[$property]}; - } - /** - * @see Zend_Validate_Exception - */ - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("No property exists by the name '$property'"); - } - - /** - * Constructs and returns a validation failure message with the given message key and value. - * - * Returns null if and only if $messageKey does not correspond to an existing template. - * - * If a translator is available and a translation exists for $messageKey, - * the translation will be used. - * - * @param string $messageKey - * @param string $value - * @return string - */ - protected function _createMessage($messageKey, $value) - { - if (!isset($this->_messageTemplates[$messageKey])) { - return null; - } - - $message = $this->_messageTemplates[$messageKey]; - - if (null !== ($translator = $this->getTranslator())) { - if ($translator->isTranslated($message)) { - $message = $translator->translate($message); - } elseif ($translator->isTranslated($messageKey)) { - $message = $translator->translate($messageKey); - } - } - - if ($this->getObscureValue()) { - $value = str_repeat('*', strlen($value)); - } - - $message = str_replace('%value%', (string) $value, $message); - foreach ($this->_messageVariables as $ident => $property) { - $message = str_replace("%$ident%", $this->$property, $message); - } - return $message; - } - - /** - * @param string $messageKey OPTIONAL - * @param string $value OPTIONAL - * @return void - */ - protected function _error($messageKey = null, $value = null) - { - if ($messageKey === null) { - $keys = array_keys($this->_messageTemplates); - $messageKey = current($keys); - } - if ($value === null) { - $value = $this->_value; - } - $this->_errors[] = $messageKey; - $this->_messages[$messageKey] = $this->_createMessage($messageKey, $value); - } - - /** - * Sets the value to be validated and clears the messages and errors arrays - * - * @param mixed $value - * @return void - */ - protected function _setValue($value) - { - $this->_value = $value; - $this->_messages = array(); - $this->_errors = array(); - } - - /** - * Returns array of validation failure message codes - * - * @return array - * @deprecated Since 1.5.0 - */ - public function getErrors() - { - return $this->_errors; - } - - /** - * Set flag indicating whether or not value should be obfuscated in messages - * - * @param bool $flag - * @return Zend_Validate_Abstract - */ - public function setObscureValue($flag) - { - $this->_obscureValue = (bool) $flag; - return $this; - } - - /** - * Retrieve flag indicating whether or not value should be obfuscated in - * messages - * - * @return bool - */ - public function getObscureValue() - { - return $this->_obscureValue; - } - - /** - * Set translation object - * - * @param Zend_Translate|Zend_Translate_Adapter|null $translator - * @return Zend_Validate_Abstract - */ - public function setTranslator($translator = null) - { - if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) { - $this->_translator = $translator; - } elseif ($translator instanceof Zend_Translate) { - $this->_translator = $translator->getAdapter(); - } else { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception('Invalid translator specified'); - } - return $this; - } - - /** - * Return translation object - * - * @return Zend_Translate_Adapter|null - */ - public function getTranslator() - { - if (null === $this->_translator) { - return self::getDefaultTranslator(); - } - - return $this->_translator; - } - - /** - * Set default translation object for all validate objects - * - * @param Zend_Translate|Zend_Translate_Adapter|null $translator - * @return void - */ - public static function setDefaultTranslator($translator = null) - { - if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) { - self::$_defaultTranslator = $translator; - } elseif ($translator instanceof Zend_Translate) { - self::$_defaultTranslator = $translator->getAdapter(); - } else { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception('Invalid translator specified'); - } - } - - /** - * Get default translation object for all validate objects - * - * @return Zend_Translate_Adapter|null - */ - public static function getDefaultTranslator() - { - if (null === self::$_defaultTranslator) { - require_once 'Zend/Registry.php'; - if (Zend_Registry::isRegistered('Zend_Translate')) { - $translator = Zend_Registry::get('Zend_Translate'); - if ($translator instanceof Zend_Translate_Adapter) { - return $translator; - } elseif ($translator instanceof Zend_Translate) { - return $translator->getAdapter(); - } - } - } - return self::$_defaultTranslator; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/Alnum.php b/phpQuery/phpQuery/Zend/Validate/Alnum.php deleted file mode 100644 index 36b0adc..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Alnum.php +++ /dev/null @@ -1,120 +0,0 @@ - "'%value%' has not only alphabetic and digit characters", - self::STRING_EMPTY => "'%value%' is an empty string" - ); - - /** - * Sets default option values for this instance - * - * @param boolean $allowWhiteSpace - * @return void - */ - public function __construct($allowWhiteSpace = false) - { - $this->allowWhiteSpace = (boolean) $allowWhiteSpace; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value contains only alphabetic and digit characters - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - if ('' === $valueString) { - $this->_error(self::STRING_EMPTY); - return false; - } - - if (null === self::$_filter) { - /** - * @see Zend_Filter_Alnum - */ - require_once 'Zend/Filter/Alnum.php'; - self::$_filter = new Zend_Filter_Alnum(); - } - - self::$_filter->allowWhiteSpace = $this->allowWhiteSpace; - - if ($valueString !== self::$_filter->filter($valueString)) { - $this->_error(self::NOT_ALNUM); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Alpha.php b/phpQuery/phpQuery/Zend/Validate/Alpha.php deleted file mode 100644 index 0f2298e..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Alpha.php +++ /dev/null @@ -1,120 +0,0 @@ - "'%value%' has not only alphabetic characters", - self::STRING_EMPTY => "'%value%' is an empty string" - ); - - /** - * Sets default option values for this instance - * - * @param boolean $allowWhiteSpace - * @return void - */ - public function __construct($allowWhiteSpace = false) - { - $this->allowWhiteSpace = (boolean) $allowWhiteSpace; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value contains only alphabetic characters - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - if ('' === $valueString) { - $this->_error(self::STRING_EMPTY); - return false; - } - - if (null === self::$_filter) { - /** - * @see Zend_Filter_Alpha - */ - require_once 'Zend/Filter/Alpha.php'; - self::$_filter = new Zend_Filter_Alpha(); - } - - self::$_filter->allowWhiteSpace = $this->allowWhiteSpace; - - if ($valueString !== self::$_filter->filter($valueString)) { - $this->_error(self::NOT_ALPHA); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Barcode.php b/phpQuery/phpQuery/Zend/Validate/Barcode.php deleted file mode 100644 index d51f11b..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Barcode.php +++ /dev/null @@ -1,99 +0,0 @@ -setType($barcodeType); - } - - /** - * Sets a new barcode validator - * - * @param string $barcodeType - Barcode validator to use - * @return void - * @throws Zend_Validate_Exception - */ - public function setType($barcodeType) - { - switch (strtolower($barcodeType)) { - case 'upc': - case 'upc-a': - $className = 'UpcA'; - break; - case 'ean13': - case 'ean-13': - $className = 'Ean13'; - break; - default: - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("Barcode type '$barcodeType' is not supported'"); - break; - } - - require_once 'Zend/Validate/Barcode/' . $className . '.php'; - - $class = 'Zend_Validate_Barcode_' . $className; - $this->_barcodeValidator = new $class; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value contains a valid barcode - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - return call_user_func(array($this->_barcodeValidator, 'isValid'), $value); - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/Barcode/Ean13.php b/phpQuery/phpQuery/Zend/Validate/Barcode/Ean13.php deleted file mode 100644 index 7be797d..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Barcode/Ean13.php +++ /dev/null @@ -1,100 +0,0 @@ - "'%value%' is an invalid EAN-13 barcode", - self::INVALID_LENGTH => "'%value%' should be 13 characters", - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value contains a valid barcode - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - $this->_setValue($valueString); - - if (strlen($valueString) !== 13) { - $this->_error(self::INVALID_LENGTH); - return false; - } - - $barcode = strrev(substr($valueString, 0, -1)); - $oddSum = 0; - $evenSum = 0; - - for ($i = 0; $i < 12; $i++) { - if ($i % 2 === 0) { - $oddSum += $barcode[$i] * 3; - } elseif ($i % 2 === 1) { - $evenSum += $barcode[$i]; - } - } - - $calculation = ($oddSum + $evenSum) % 10; - $checksum = ($calculation === 0) ? 0 : 10 - $calculation; - - if ($valueString[12] != $checksum) { - $this->_error(self::INVALID); - return false; - } - - return true; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/Barcode/UpcA.php b/phpQuery/phpQuery/Zend/Validate/Barcode/UpcA.php deleted file mode 100644 index c584e81..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Barcode/UpcA.php +++ /dev/null @@ -1,100 +0,0 @@ - "'%value%' is an invalid UPC-A barcode", - self::INVALID_LENGTH => "'%value%' should be 12 characters", - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value contains a valid barcode - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - $this->_setValue($valueString); - - if (strlen($valueString) !== 12) { - $this->_error(self::INVALID_LENGTH); - return false; - } - - $barcode = substr($valueString, 0, -1); - $oddSum = 0; - $evenSum = 0; - - for ($i = 0; $i < 11; $i++) { - if ($i % 2 === 0) { - $oddSum += $barcode[$i] * 3; - } elseif ($i % 2 === 1) { - $evenSum += $barcode[$i]; - } - } - - $calculation = ($oddSum + $evenSum) % 10; - $checksum = ($calculation === 0) ? 0 : 10 - $calculation; - - if ($valueString[11] != $checksum) { - $this->_error(self::INVALID); - return false; - } - - return true; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/Between.php b/phpQuery/phpQuery/Zend/Validate/Between.php deleted file mode 100644 index bb0b726..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Between.php +++ /dev/null @@ -1,200 +0,0 @@ - "'%value%' is not between '%min%' and '%max%', inclusively", - self::NOT_BETWEEN_STRICT => "'%value%' is not strictly between '%min%' and '%max%'" - ); - - /** - * Additional variables available for validation failure messages - * - * @var array - */ - protected $_messageVariables = array( - 'min' => '_min', - 'max' => '_max' - ); - - /** - * Minimum value - * - * @var mixed - */ - protected $_min; - - /** - * Maximum value - * - * @var mixed - */ - protected $_max; - - /** - * Whether to do inclusive comparisons, allowing equivalence to min and/or max - * - * If false, then strict comparisons are done, and the value may equal neither - * the min nor max options - * - * @var boolean - */ - protected $_inclusive; - - /** - * Sets validator options - * - * @param mixed $min - * @param mixed $max - * @param boolean $inclusive - * @return void - */ - public function __construct($min, $max, $inclusive = true) - { - $this->setMin($min) - ->setMax($max) - ->setInclusive($inclusive); - } - - /** - * Returns the min option - * - * @return mixed - */ - public function getMin() - { - return $this->_min; - } - - /** - * Sets the min option - * - * @param mixed $min - * @return Zend_Validate_Between Provides a fluent interface - */ - public function setMin($min) - { - $this->_min = $min; - return $this; - } - - /** - * Returns the max option - * - * @return mixed - */ - public function getMax() - { - return $this->_max; - } - - /** - * Sets the max option - * - * @param mixed $max - * @return Zend_Validate_Between Provides a fluent interface - */ - public function setMax($max) - { - $this->_max = $max; - return $this; - } - - /** - * Returns the inclusive option - * - * @return boolean - */ - public function getInclusive() - { - return $this->_inclusive; - } - - /** - * Sets the inclusive option - * - * @param boolean $inclusive - * @return Zend_Validate_Between Provides a fluent interface - */ - public function setInclusive($inclusive) - { - $this->_inclusive = $inclusive; - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is between min and max options, inclusively - * if inclusive option is true. - * - * @param mixed $value - * @return boolean - */ - public function isValid($value) - { - $this->_setValue($value); - - if ($this->_inclusive) { - if ($this->_min > $value || $value > $this->_max) { - $this->_error(self::NOT_BETWEEN); - return false; - } - } else { - if ($this->_min >= $value || $value >= $this->_max) { - $this->_error(self::NOT_BETWEEN_STRICT); - return false; - } - } - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Ccnum.php b/phpQuery/phpQuery/Zend/Validate/Ccnum.php deleted file mode 100644 index 227a4ec..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Ccnum.php +++ /dev/null @@ -1,111 +0,0 @@ - "'%value%' must contain between 13 and 19 digits", - self::CHECKSUM => "Luhn algorithm (mod-10 checksum) failed on '%value%'" - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value follows the Luhn algorithm (mod-10 checksum) - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $this->_setValue($value); - - if (null === self::$_filter) { - /** - * @see Zend_Filter_Digits - */ - require_once 'Zend/Filter/Digits.php'; - self::$_filter = new Zend_Filter_Digits(); - } - - $valueFiltered = self::$_filter->filter($value); - - $length = strlen($valueFiltered); - - if ($length < 13 || $length > 19) { - $this->_error(self::LENGTH); - return false; - } - - $sum = 0; - $weight = 2; - - for ($i = $length - 2; $i >= 0; $i--) { - $digit = $weight * $valueFiltered[$i]; - $sum += floor($digit / 10) + $digit % 10; - $weight = $weight % 2 + 1; - } - - if ((10 - $sum % 10) % 10 != $valueFiltered[$length - 1]) { - $this->_error(self::CHECKSUM, $valueFiltered); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Date.php b/phpQuery/phpQuery/Zend/Validate/Date.php deleted file mode 100644 index 220b0ee..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Date.php +++ /dev/null @@ -1,250 +0,0 @@ - "'%value%' is not of the format YYYY-MM-DD", - self::INVALID => "'%value%' does not appear to be a valid date", - self::FALSEFORMAT => "'%value%' does not fit given date format" - ); - - /** - * Optional format - * - * @var string|null - */ - protected $_format; - - /** - * Optional locale - * - * @var string|Zend_Locale|null - */ - protected $_locale; - - /** - * Sets validator options - * - * @param string $format OPTIONAL - * @param string|Zend_Locale $locale OPTIONAL - * @return void - */ - public function __construct($format = null, $locale = null) - { - $this->setFormat($format); - $this->setLocale($locale); - } - - /** - * Returns the locale option - * - * @return string|Zend_Locale|null - */ - public function getLocale() - { - return $this->_locale; - } - - /** - * Sets the locale option - * - * @param string|Zend_Locale $locale - * @return Zend_Validate_Date provides a fluent interface - */ - public function setLocale($locale = null) - { - if ($locale === null) { - $this->_locale = null; - return $this; - } - - require_once 'Zend/Locale.php'; - if (!Zend_Locale::isLocale($locale, true)) { - if (!Zend_Locale::isLocale($locale, false)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The locale '$locale' is no known locale"); - } - - $locale = new Zend_Locale($locale); - } - - $this->_locale = (string) $locale; - return $this; - } - - /** - * Returns the locale option - * - * @return string|null - */ - public function getFormat() - { - return $this->_format; - } - - /** - * Sets the format option - * - * @param string $format - * @return Zend_Validate_Date provides a fluent interface - */ - public function setFormat($format = null) - { - $this->_format = $format; - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if $value is a valid date of the format YYYY-MM-DD - * If optional $format or $locale is set the date format is checked - * according to Zend_Date, see Zend_Date::isDate() - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - if (($this->_format !== null) or ($this->_locale !== null)) { - require_once 'Zend/Date.php'; - if (!Zend_Date::isDate($value, $this->_format, $this->_locale)) { - if ($this->_checkFormat($value) === false) { - $this->_error(self::FALSEFORMAT); - } else { - $this->_error(self::INVALID); - } - return false; - } - } else { - if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $valueString)) { - $this->_error(self::NOT_YYYY_MM_DD); - return false; - } - - list($year, $month, $day) = sscanf($valueString, '%d-%d-%d'); - - if (!checkdate($month, $day, $year)) { - $this->_error(self::INVALID); - return false; - } - } - - return true; - } - - /** - * Check if the given date fits the given format - * - * @param string $value Date to check - * @return boolean False when date does not fit the format - */ - private function _checkFormat($value) - { - try { - require_once 'Zend/Locale/Format.php'; - $parsed = Zend_Locale_Format::getDate($value, array( - 'date_format' => $this->_format, 'format_type' => 'iso', - 'fix_date' => false)); - if (isset($parsed['year']) and ((strpos(strtoupper($this->_format), 'YY') !== false) and - (strpos(strtoupper($this->_format), 'YYYY') === false))) { - $parsed['year'] = Zend_Date::_century($parsed['year']); - } - } catch (Exception $e) { - // Date can not be parsed - return false; - } - - if (((strpos($this->_format, 'Y') !== false) or (strpos($this->_format, 'y') !== false)) and - (!isset($parsed['year']))) { - // Year expected but not found - return false; - } - - if ((strpos($this->_format, 'M') !== false) and (!isset($parsed['month']))) { - // Month expected but not found - return false; - } - - if ((strpos($this->_format, 'd') !== false) and (!isset($parsed['day']))) { - // Day expected but not found - return false; - } - - if (((strpos($this->_format, 'H') !== false) or (strpos($this->_format, 'h') !== false)) and - (!isset($parsed['hour']))) { - // Hour expected but not found - return false; - } - - if ((strpos($this->_format, 'm') !== false) and (!isset($parsed['minute']))) { - // Minute expected but not found - return false; - } - - if ((strpos($this->_format, 's') !== false) and (!isset($parsed['second']))) { - // Second expected but not found - return false; - } - - // Date fits the format - return true; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/Digits.php b/phpQuery/phpQuery/Zend/Validate/Digits.php deleted file mode 100644 index c42ec0a..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Digits.php +++ /dev/null @@ -1,100 +0,0 @@ - "'%value%' contains not only digit characters", - self::STRING_EMPTY => "'%value%' is an empty string" - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value only contains digit characters - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - if ('' === $valueString) { - $this->_error(self::STRING_EMPTY); - return false; - } - - if (null === self::$_filter) { - /** - * @see Zend_Filter_Digits - */ - require_once 'Zend/Filter/Digits.php'; - self::$_filter = new Zend_Filter_Digits(); - } - - if ($valueString !== self::$_filter->filter($valueString)) { - $this->_error(self::NOT_DIGITS); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/EmailAddress.php b/phpQuery/phpQuery/Zend/Validate/EmailAddress.php deleted file mode 100644 index d8e9595..0000000 --- a/phpQuery/phpQuery/Zend/Validate/EmailAddress.php +++ /dev/null @@ -1,245 +0,0 @@ - "'%value%' is not a valid email address in the basic format local-part@hostname", - self::INVALID_HOSTNAME => "'%hostname%' is not a valid hostname for email address '%value%'", - self::INVALID_MX_RECORD => "'%hostname%' does not appear to have a valid MX record for the email address '%value%'", - self::DOT_ATOM => "'%localPart%' not matched against dot-atom format", - self::QUOTED_STRING => "'%localPart%' not matched against quoted-string format", - self::INVALID_LOCAL_PART => "'%localPart%' is not a valid local part for email address '%value%'" - ); - - /** - * @var array - */ - protected $_messageVariables = array( - 'hostname' => '_hostname', - 'localPart' => '_localPart' - ); - - /** - * Local object for validating the hostname part of an email address - * - * @var Zend_Validate_Hostname - */ - public $hostnameValidator; - - /** - * Whether we check for a valid MX record via DNS - * - * @var boolean - */ - protected $_validateMx = false; - - /** - * @var string - */ - protected $_hostname; - - /** - * @var string - */ - protected $_localPart; - - /** - * Instantiates hostname validator for local use - * - * You can pass a bitfield to determine what types of hostnames are allowed. - * These bitfields are defined by the ALLOW_* constants in Zend_Validate_Hostname - * The default is to allow DNS hostnames only - * - * @param integer $allow OPTIONAL - * @param bool $validateMx OPTIONAL - * @param Zend_Validate_Hostname $hostnameValidator OPTIONAL - * @return void - */ - public function __construct($allow = Zend_Validate_Hostname::ALLOW_DNS, $validateMx = false, Zend_Validate_Hostname $hostnameValidator = null) - { - $this->setValidateMx($validateMx); - $this->setHostnameValidator($hostnameValidator, $allow); - } - - /** - * @param Zend_Validate_Hostname $hostnameValidator OPTIONAL - * @param int $allow OPTIONAL - * @return void - */ - public function setHostnameValidator(Zend_Validate_Hostname $hostnameValidator = null, $allow = Zend_Validate_Hostname::ALLOW_DNS) - { - if ($hostnameValidator === null) { - $hostnameValidator = new Zend_Validate_Hostname($allow); - } - $this->hostnameValidator = $hostnameValidator; - } - - /** - * Whether MX checking via dns_get_mx is supported or not - * - * This currently only works on UNIX systems - * - * @return boolean - */ - public function validateMxSupported() - { - return function_exists('dns_get_mx'); - } - - /** - * Set whether we check for a valid MX record via DNS - * - * This only applies when DNS hostnames are validated - * - * @param boolean $allowed Set allowed to true to validate for MX records, and false to not validate them - */ - public function setValidateMx($allowed) - { - $this->_validateMx = (bool) $allowed; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is a valid email address - * according to RFC2822 - * - * @link http://www.ietf.org/rfc/rfc2822.txt RFC2822 - * @link http://www.columbia.edu/kermit/ascii.html US-ASCII characters - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - // Split email address up - if (!preg_match('/^(.+)@([^@]+)$/', $valueString, $matches)) { - $this->_error(self::INVALID); - return false; - } - - $this->_localPart = $matches[1]; - $this->_hostname = $matches[2]; - - // Match hostname part - $hostnameResult = $this->hostnameValidator->setTranslator($this->getTranslator()) - ->isValid($this->_hostname); - if (!$hostnameResult) { - $this->_error(self::INVALID_HOSTNAME); - - // Get messages and errors from hostnameValidator - foreach ($this->hostnameValidator->getMessages() as $message) { - $this->_messages[] = $message; - } - foreach ($this->hostnameValidator->getErrors() as $error) { - $this->_errors[] = $error; - } - } - - // MX check on hostname via dns_get_record() - if ($this->_validateMx) { - if ($this->validateMxSupported()) { - $result = dns_get_mx($this->_hostname, $mxHosts); - if (count($mxHosts) < 1) { - $hostnameResult = false; - $this->_error(self::INVALID_MX_RECORD); - } - } else { - /** - * MX checks are not supported by this system - * @see Zend_Validate_Exception - */ - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception('Internal error: MX checking not available on this system'); - } - } - - // First try to match the local part on the common dot-atom format - $localResult = false; - - // Dot-atom characters are: 1*atext *("." 1*atext) - // atext: ALPHA / DIGIT / and "!", "#", "$", "%", "&", "'", "*", - // "-", "/", "=", "?", "^", "_", "`", "{", "|", "}", "~" - $atext = 'a-zA-Z0-9\x21\x23\x24\x25\x26\x27\x2a\x2b\x2d\x2f\x3d\x3f\x5e\x5f\x60\x7b\x7c\x7d'; - if (preg_match('/^[' . $atext . ']+(\x2e+[' . $atext . ']+)*$/', $this->_localPart)) { - $localResult = true; - } else { - // Try quoted string format - - // Quoted-string characters are: DQUOTE *([FWS] qtext/quoted-pair) [FWS] DQUOTE - // qtext: Non white space controls, and the rest of the US-ASCII characters not - // including "\" or the quote character - $noWsCtl = '\x01-\x08\x0b\x0c\x0e-\x1f\x7f'; - $qtext = $noWsCtl . '\x21\x23-\x5b\x5d-\x7e'; - $ws = '\x20\x09'; - if (preg_match('/^\x22([' . $ws . $qtext . '])*[$ws]?\x22$/', $this->_localPart)) { - $localResult = true; - } else { - $this->_error(self::DOT_ATOM); - $this->_error(self::QUOTED_STRING); - $this->_error(self::INVALID_LOCAL_PART); - } - } - - // If both parts valid, return true - if ($localResult && $hostnameResult) { - return true; - } else { - return false; - } - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Exception.php b/phpQuery/phpQuery/Zend/Validate/Exception.php deleted file mode 100644 index a38077e..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Exception.php +++ /dev/null @@ -1,37 +0,0 @@ - "Too much files, only '%value%' are allowed", - self::TOO_LESS => "Too less files, minimum '%value%' must be given" - ); - - /** - * @var array Error message template variables - */ - protected $_messageVariables = array( - 'min' => '_min', - 'max' => '_max' - ); - - /** - * Minimum file count - * - * If null, there is no minimum file count - * - * @var integer - */ - protected $_min; - - /** - * Maximum file count - * - * If null, there is no maximum file count - * - * @var integer|null - */ - protected $_max; - - /** - * Internal file array - * @var array - */ - protected $_files; - - /** - * Sets validator options - * - * Min limits the file count, when used with max=null it is the maximum file count - * It also accepts an array with the keys 'min' and 'max' - * - * @param integer|array $min Minimum file count - * @param integer $max Maximum file count - * @return void - */ - public function __construct($min, $max = null) - { - $this->_files = array(); - if (is_array($min) === true) { - if (isset($min['max']) === true) { - $max = $min['max']; - } - - if (isset($min['min']) === true) { - $min = $min['min']; - } - - if (isset($min[0]) === true) { - if (count($min) === 2) { - $max = $min[1]; - $min = $min[0]; - } else { - $max = $min[0]; - $min = null; - } - } - } - - if (empty($max) === true) { - $max = $min; - $min = null; - } - - $this->setMin($min); - $this->setMax($max); - } - - /** - * Returns the minimum file count - * - * @return integer - */ - public function getMin() - { - $min = $this->_min; - - return $min; - } - - /** - * Sets the minimum file count - * - * @param integer $min The minimum file count - * @return Zend_Validate_File_Size Provides a fluent interface - * @throws Zend_Validate_Exception When min is greater than max - */ - public function setMin($min) - { - if ($min === null) { - $this->_min = null; - } else if (($this->_max !== null) and ($min > $this->_max)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception('The minimum must be less than or equal to the maximum file count, but ' - . " {$min} > {$this->_max}"); - } else { - $this->_min = max(0, (integer) $min); - } - - return $this; - } - - /** - * Returns the maximum file count - * - * @return integer|null - */ - public function getMax() - { - return $this->_max; - } - - /** - * Sets the maximum file count - * - * @param integer|null $max The maximum file count - * @throws Zend_Validate_Exception When max is smaller than min - * @return Zend_Validate_StringLength Provides a fluent interface - */ - public function setMax($max) - { - if ($max === null) { - $this->_max = null; - } else if (($this->_min !== null) and ($max < $this->_min)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum file count, but " - . "{$max} < {$this->_min}"); - } else { - $this->_max = (integer) $max; - } - - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the file count of all checked files is at least min and - * not bigger than max (when max is not null). Attention: When checking with set min you - * must give all files with the first call, otherwise you will get an false. - * - * @param string|array $value Filenames to check for count - * @param array $file File data from Zend_File_Transfer - * @return boolean - */ - public function isValid($value, $file = null) - { - if (is_string($value)) { - $value = array($value); - } - - foreach ($value as $file) { - if (!isset($this->_files[$file])) { - $this->_files[$file] = $file; - } - } - - if (($this->_max !== null) && (count($this->_files) > $this->_max)) { - $this->_value = $this->_max; - $this->_error(self::TOO_MUCH); - return false; - } - - if (($this->_min !== null) && (count($this->_files) < $this->_min)) { - $this->_value = $this->_min; - $this->_error(self::TOO_LESS); - return false; - } - - return true; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/File/Exists.php b/phpQuery/phpQuery/Zend/Validate/File/Exists.php deleted file mode 100644 index 268090b..0000000 --- a/phpQuery/phpQuery/Zend/Validate/File/Exists.php +++ /dev/null @@ -1,192 +0,0 @@ - "The file '%value%' does not exist" - ); - - /** - * Internal list of directories - * @var string - */ - protected $_directory = ''; - - /** - * @var array Error message template variables - */ - protected $_messageVariables = array( - 'directory' => '_directory' - ); - - /** - * Sets validator options - * - * @param string|array $directory - * @return void - */ - public function __construct($directory = array()) - { - $this->setDirectory($directory); - } - - /** - * Returns the set file directories which are checked - * - * @param boolean $asArray Returns the values as array, when false an concated string is returned - * @return string - */ - public function getDirectory($asArray = false) - { - $asArray = (bool) $asArray; - $directory = (string) $this->_directory; - if ($asArray) { - $directory = explode(',', $directory); - } - - return $directory; - } - - /** - * Sets the file directory which will be checked - * - * @param string|array $directory The directories to validate - * @return Zend_Validate_File_Extension Provides a fluent interface - */ - public function setDirectory($directory) - { - $this->_directory = null; - $this->addDirectory($directory); - return $this; - } - - /** - * Adds the file directory which will be checked - * - * @param string|array $directory The directory to add for validation - * @return Zend_Validate_File_Extension Provides a fluent interface - */ - public function addDirectory($directory) - { - $directories = $this->getDirectory(true); - if (is_string($directory)) { - $directory = explode(',', $directory); - } - - foreach ($directory as $content) { - if (empty($content) || !is_string($content)) { - continue; - } - - $directories[] = trim($content); - } - $directories = array_unique($directories); - - // Sanity check to ensure no empty values - foreach ($directories as $key => $dir) { - if (empty($dir)) { - unset($directories[$key]); - } - } - - $this->_directory = implode(',', $directories); - - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the file already exists in the set directories - * - * @param string $value Real file to check for existance - * @param array $file File data from Zend_File_Transfer - * @return boolean - */ - public function isValid($value, $file = null) - { - $directories = $this->getDirectory(true); - if (($file !== null) and (!empty($file['destination']))) { - $directories[] = $file['destination']; - } else if (!isset($file['name'])) { - $file['name'] = $value; - } - - $check = false; - foreach ($directories as $directory) { - if (empty($directory)) { - continue; - } - - $check = true; - if (!file_exists($directory . DIRECTORY_SEPARATOR . $file['name'])) { - $this->_throw($file, self::DOES_NOT_EXIST); - return false; - } - } - - if (!$check) { - $this->_throw($file, self::DOES_NOT_EXIST); - return false; - } - - return true; - } - - /** - * Throws an error of the given type - * - * @param string $file - * @param string $errorType - * @return false - */ - protected function _throw($file, $errorType) - { - if ($file !== null) { - $this->_value = $file['name']; - } - - $this->_error($errorType); - return false; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/File/Extension.php b/phpQuery/phpQuery/Zend/Validate/File/Extension.php deleted file mode 100644 index 62577b3..0000000 --- a/phpQuery/phpQuery/Zend/Validate/File/Extension.php +++ /dev/null @@ -1,204 +0,0 @@ - "The file '%value%' has a false extension", - self::NOT_FOUND => "The file '%value%' was not found" - ); - - /** - * Internal list of extensions - * @var string - */ - protected $_extension = ''; - - /** - * Validate case sensitive - * - * @var boolean - */ - protected $_case = false; - - /** - * @var array Error message template variables - */ - protected $_messageVariables = array( - 'extension' => '_extension' - ); - - /** - * Sets validator options - * - * @param string|array $extension - * @param boolean $case If true validation is done case sensitive - * @return void - */ - public function __construct($extension, $case = false) - { - $this->_case = (boolean) $case; - $this->setExtension($extension); - } - - /** - * Returns the set file extension - * - * @param boolean $asArray Returns the values as array, when false an concated string is returned - * @return string - */ - public function getExtension($asArray = false) - { - $asArray = (bool) $asArray; - $extension = (string) $this->_extension; - if ($asArray) { - $extension = explode(',', $extension); - } - - return $extension; - } - - /** - * Sets the file extensions - * - * @param string|array $extension The extensions to validate - * @return Zend_Validate_File_Extension Provides a fluent interface - */ - public function setExtension($extension) - { - $this->_extension = null; - $this->addExtension($extension); - return $this; - } - - /** - * Adds the file extensions - * - * @param string|array $extension The extensions to add for validation - * @return Zend_Validate_File_Extension Provides a fluent interface - */ - public function addExtension($extension) - { - $extensions = $this->getExtension(true); - if (is_string($extension)) { - $extension = explode(',', $extension); - } - - foreach ($extension as $content) { - if (empty($content) || !is_string($content)) { - continue; - } - - $extensions[] = trim($content); - } - $extensions = array_unique($extensions); - - // Sanity check to ensure no empty values - foreach ($extensions as $key => $ext) { - if (empty($ext)) { - unset($extensions[$key]); - } - } - - $this->_extension = implode(',', $extensions); - - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the fileextension of $value is included in the - * set extension list - * - * @param string $value Real file to check for extension - * @param array $file File data from Zend_File_Transfer - * @return boolean - */ - public function isValid($value, $file = null) - { - // Is file readable ? - if (!@is_readable($value)) { - $this->_throw($file, self::NOT_FOUND); - return false; - } - - if ($file !== null) { - $info['extension'] = substr($file['name'], strpos($file['name'], '.') + 1); - } else { - $info = @pathinfo($value); - } - - $extensions = $this->getExtension(true); - - if ($this->_case and (in_array($info['extension'], $extensions))) { - return true; - } else if (!$this->_case) { - foreach ($extensions as $extension) { - if (strtolower($extension) == strtolower($info['extension'])) { - return true; - } - } - } - - $this->_throw($file, self::FALSE_EXTENSION); - return false; - } - - /** - * Throws an error of the given type - * - * @param string $file - * @param string $errorType - * @return false - */ - protected function _throw($file, $errorType) - { - if ($file !== null) { - $this->_value = $file['name']; - } - - $this->_error($errorType); - return false; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/File/FilesSize.php b/phpQuery/phpQuery/Zend/Validate/File/FilesSize.php deleted file mode 100644 index e8b060d..0000000 --- a/phpQuery/phpQuery/Zend/Validate/File/FilesSize.php +++ /dev/null @@ -1,156 +0,0 @@ - "The files in sum exceed the maximum allowed size", - self::TOO_SMALL => "All files are in sum smaller than required", - self::NOT_READABLE => "One or more files can not be read" - ); - - /** - * @var array Error message template variables - */ - protected $_messageVariables = array( - 'min' => '_min', - 'max' => '_max' - ); - - /** - * Minimum filesize - * - * @var integer - */ - protected $_min; - - /** - * Maximum filesize - * - * @var integer|null - */ - protected $_max; - - /** - * Internal file array - * - * @var array - */ - protected $_files; - - /** - * Internal file size counter - * - * @var integer - */ - protected $_size; - - /** - * Sets validator options - * - * Min limits the used diskspace for all files, when used with max=null it is the maximum filesize - * It also accepts an array with the keys 'min' and 'max' - * - * @param integer|array $min Minimum diskspace for all files - * @param integer $max Maximum diskspace for all files - * @return void - */ - public function __construct($min, $max = null) - { - $this->_files = array(); - $this->_size = 0; - parent::__construct($min, $max); - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the disk usage of all files is at least min and - * not bigger than max (when max is not null). - * - * @param string|array $value Real file to check for size - * @param array $file File data from Zend_File_Transfer - * @return boolean - */ - public function isValid($value, $file = null) - { - if (is_string($value)) { - $value = array($value); - } - - foreach ($value as $files) { - // Is file readable ? - if (!@is_readable($files)) { - $this->_throw($file, self::NOT_READABLE); - return false; - } - - if (!isset($this->_files[$files])) { - $this->_files[$files] = $files; - } else { - // file already counted... do not count twice - continue; - } - - // limited to 2GB files - $size = @filesize($files); - $this->_size += $size; - $this->_setValue($this->_size); - if (($this->_max !== null) && ($this->_max < $this->_size)) { - $this->_throw($file, self::TOO_BIG); - } - } - - // Check that aggregate files are >= minimum size - if (($this->_min !== null) && ($this->_size < $this->_min)) { - $this->_throw($file, self::TOO_SMALL); - } - - if (count($this->_messages) > 0) { - return false; - } else { - return true; - } - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/File/ImageSize.php b/phpQuery/phpQuery/Zend/Validate/File/ImageSize.php deleted file mode 100644 index 6c27ef6..0000000 --- a/phpQuery/phpQuery/Zend/Validate/File/ImageSize.php +++ /dev/null @@ -1,335 +0,0 @@ - "Width of the image '%value%' is bigger than allowed", - self::WIDTH_TOO_SMALL => "Width of the image '%value%' is smaller than allowed", - self::HEIGHT_TOO_BIG => "Height of the image '%value%' is bigger than allowed", - self::HEIGHT_TOO_SMALL => "Height of the image '%value%' is smaller than allowed", - self::NOT_DETECTED => "Size of the image '%value%' could not be detected", - self::NOT_READABLE => "The image '%value%' can not be read" - ); - - /** - * @var array Error message template variables - */ - protected $_messageVariables = array( - 'minwidth' => '_minwidth', - 'maxwidth' => '_maxwidth', - 'minheight' => '_minheight', - 'maxheight' => '_maxheight' - ); - - /** - * Minimum image width - * - * @var integer - */ - protected $_minwidth; - - /** - * Maximum image width - * - * @var integer - */ - protected $_maxwidth; - - /** - * Minimum image height - * - * @var integer - */ - protected $_minheight; - - /** - * Maximum image height - * - * @var integer - */ - protected $_maxheight; - - /** - * Sets validator options - * - * Min limits the filesize, when used with max=null if is the maximum filesize - * It also accepts an array with the keys 'min' and 'max' - * - * @param integer|array $max Maximum filesize - * @param integer $max Maximum filesize - * @return void - */ - public function __construct($minwidth = 0, $minheight = 0, $maxwidth = null, $maxheight = null) - { - if (is_array($minwidth) === true) { - if (isset($minwidth['maxheight']) === true) { - $maxheight = $minwidth['maxheight']; - } - - if (isset($minwidth['minheight']) === true) { - $minheight = $minwidth['minheight']; - } - - if (isset($minwidth['maxwidth']) === true) { - $maxwidth = $minwidth['maxwidth']; - } - - if (isset($minwidth['minwidth']) === true) { - $minwidth = $minwidth['minwidth']; - } - - if (isset($minwidth[0]) === true) { - $maxheight = $minwidth[3]; - $maxwidth = $minwidth[2]; - $minheight = $minwidth[1]; - $minwidth = $minwidth[0]; - } - } - - $this->setImageMin($minwidth, $minheight); - $this->setImageMax($maxwidth, $maxheight); - } - - /** - * Returns the set minimum image sizes - * - * @return array - */ - public function getImageMin() - { - return array($this->_minwidth, $this->_minheight); - } - - /** - * Returns the set maximum image sizes - * - * @return array - */ - public function getImageMax() - { - return array($this->_maxwidth, $this->_maxheight); - } - - /** - * Returns the set image width sizes - * - * @return array - */ - public function getImageWidth() - { - return array($this->_minwidth, $this->_maxwidth); - } - - /** - * Returns the set image height sizes - * - * @return array - */ - public function getImageHeight() - { - return array($this->_minheight, $this->_maxheight); - } - - /** - * Sets the minimum image size - * - * @param integer $minwidth The minimum image width - * @param integer $minheight The minimum image height - * @throws Zend_Validate_Exception When minwidth is greater than maxwidth - * @throws Zend_Validate_Exception When minheight is greater than maxheight - * @return Zend_Validate_File_ImageSize Provides a fluent interface - */ - public function setImageMin($minwidth, $minheight) - { - if (($this->_maxwidth !== null) and ($minwidth > $this->_maxwidth)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The minimum image width must be less than or equal to the " - . " maximum image width, but {$minwidth} > {$this->_maxwidth}"); - } - - if (($this->_maxheight !== null) and ($minheight > $this->_maxheight)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The minimum image height must be less than or equal to the " - . " maximum image height, but {$minheight} > {$this->_maxheight}"); - } - - $this->_minwidth = max(0, (integer) $minwidth); - $this->_minheight = max(0, (integer) $minheight); - return $this; - } - - /** - * Sets the maximum image size - * - * @param integer $maxwidth The maximum image width - * @param integer $maxheight The maximum image height - * @throws Zend_Validate_Exception When maxwidth is smaller than minwidth - * @throws Zend_Validate_Exception When maxheight is smaller than minheight - * @return Zend_Validate_StringLength Provides a fluent interface - */ - public function setImageMax($maxwidth, $maxheight) - { - if ($maxwidth === null) { - $tempwidth = null; - } else if (($this->_minwidth !== null) and ($maxwidth < $this->_minwidth)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The maximum image width must be greater than or equal to the " - . "minimum image width, but {$maxwidth} < {$this->_minwidth}"); - } else { - $tempwidth = (integer) $maxwidth; - } - - if ($maxheight === null) { - $tempheight = null; - } else if (($this->_minheight !== null) and ($maxheight < $this->_minheight)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The maximum image height must be greater than or equal to the " - . "minimum image height, but {$maxheight} < {$this->_minwidth}"); - } else { - $tempheight = (integer) $maxheight; - } - - $this->_maxwidth = $tempwidth; - $this->_maxheight = $tempheight; - return $this; - } - - /** - * Sets the mimimum and maximum image width - * - * @param integer $minwidth The minimum image width - * @param integer $maxwidth The maximum image width - * @return Zend_Validate_File_ImageSize Provides a fluent interface - */ - public function setImageWidth($minwidth, $maxwidth) - { - $this->setImageMin($minwidth, $this->_minheight); - $this->setImageMax($maxwidth, $this->_maxheight); - return $this; - } - - /** - * Sets the mimimum and maximum image height - * - * @param integer $minheight The minimum image height - * @param integer $maxheight The maximum image height - * @return Zend_Validate_File_ImageSize Provides a fluent interface - */ - public function setImageHeight($minheight, $maxheight) - { - $this->setImageMin($this->_minwidth, $minheight); - $this->setImageMax($this->_maxwidth, $maxheight); - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the imagesize of $value is at least min and - * not bigger than max - * - * @param string $value Real file to check for image size - * @param array $file File data from Zend_File_Transfer - * @return boolean - */ - public function isValid($value, $file = null) - { - // Is file readable ? - if (@is_readable($value) === false) { - $this->_throw($file, self::NOT_READABLE); - return false; - } - - $size = @getimagesize($value); - $this->_setValue($file); - - if (empty($size) or ($size[0] === 0) or ($size[1] === 0)) { - $this->_throw($file, self::NOT_DETECTED); - return false; - } - - if ($size[0] < $this->_minwidth) { - $this->_throw($file, self::WIDTH_TOO_SMALL); - } - - if ($size[1] < $this->_minheight) { - $this->_throw($file, self::HEIGHT_TOO_SMALL); - } - - if (($this->_maxwidth !== null) and ($this->_maxwidth < $size[0])) { - $this->_throw($file, self::WIDTH_TOO_BIG); - } - - if (($this->_maxheight !== null) and ($this->_maxheight < $size[1])) { - $this->_throw($file, self::HEIGHT_TOO_BIG); - } - - if (count($this->_messages) > 0) { - return false; - } else { - return true; - } - } - - /** - * Throws an error of the given type - * - * @param string $file - * @param string $errorType - * @return false - */ - protected function _throw($file, $errorType) - { - if ($file !== null) { - $this->_value = $file['name']; - } - - $this->_error($errorType); - return false; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/File/MimeType.php b/phpQuery/phpQuery/Zend/Validate/File/MimeType.php deleted file mode 100644 index 8f7e9cd..0000000 --- a/phpQuery/phpQuery/Zend/Validate/File/MimeType.php +++ /dev/null @@ -1,200 +0,0 @@ - "The file '%value%' has a false mimetype", - self::NOT_DETECTED => "The mimetype of file '%value%' has not been detected", - self::NOT_READABLE => "The file '%value%' can not be read" - ); - - /** - * @var array - */ - protected $_messageVariables = array( - 'mimetype' => '_mimetype' - ); - - /** - * Mimetypes - * - * If null, there is no mimetype - * - * @var string|null - */ - protected $_mimetype; - - /** - * Sets validator options - * - * Mimetype to accept - * - * @param string|array $mimetype MimeType - * @return void - */ - public function __construct($mimetype) - { - $this->setMimeType($mimetype); - } - - /** - * Returns the set mimetypes - * - * @param boolean $asArray Returns the values as array, when false an concated string is returned - * @return integer - */ - public function getMimeType($asArray = false) - { - $asArray = (bool) $asArray; - $mimetype = (string) $this->_mimetype; - if ($asArray) { - $mimetype = explode(',', $mimetype); - } - - return $mimetype; - } - - /** - * Sets the mimetypes - * - * @param string|array $mimetype The mimetypes to validate - * @return Zend_Validate_File_Extension Provides a fluent interface - */ - public function setMimeType($mimetype) - { - $this->_mimetype = null; - $this->addMimeType($mimetype); - return $this; - } - - /** - * Adds the mimetypes - * - * @param string|array $mimetype The mimetypes to add for validation - * @return Zend_Validate_File_Extension Provides a fluent interface - */ - public function addMimeType($mimetype) - { - $mimetypes = $this->getMimeType(true); - if (is_string($mimetype)) { - $mimetype = explode(',', $mimetype); - } - - foreach ($mimetype as $content) { - if (empty($content) || !is_string($content)) { - continue; - } - $mimetypes[] = trim($content); - } - $mimetypes = array_unique($mimetypes); - - // Sanity check to ensure no empty values - foreach ($mimetypes as $key => $mt) { - if (empty($mt)) { - unset($mimetypes[$key]); - } - } - - $this->_mimetype = implode(',', $mimetypes); - - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if the mimetype of the file matches the given ones. Also parts - * of mimetypes can be checked. If you give for example "image" all image - * mime types will be accepted like "image/gif", "image/jpeg" and so on. - * - * @param string $value Real file to check for mimetype - * @param array $file File data from Zend_File_Transfer - * @return boolean - */ - public function isValid($value, $file = null) - { - // Is file readable ? - if (!@is_readable($value)) { - $this->_throw($file, self::NOT_READABLE); - return false; - } - - if ($file !== null) { - $info['type'] = $file['type']; - } else { - $this->_throw($file, self::NOT_DETECTED); - return false; - } - - $mimetype = $this->getMimeType(true); - if (in_array($info['type'], $mimetype)) { - return true; - } - - foreach($mimetype as $mime) { - $types = explode('/', $info['type']); - if (in_array($mime, $types)) { - return true; - } - } - - $this->_throw($file, self::FALSE_TYPE); - return false; - } - - /** - * Throws an error of the given type - * - * @param string $file - * @param string $errorType - * @return false - */ - protected function _throw($file, $errorType) - { - if ($file !== null) { - $this->_value = $file['name']; - } - - $this->_error($errorType); - return false; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/File/NotExists.php b/phpQuery/phpQuery/Zend/Validate/File/NotExists.php deleted file mode 100644 index 2b812fc..0000000 --- a/phpQuery/phpQuery/Zend/Validate/File/NotExists.php +++ /dev/null @@ -1,86 +0,0 @@ - "The file '%value%' does exist" - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the file does not exist in the set destinations - * - * @param string $value Real file to check for - * @param array $file File data from Zend_File_Transfer - * @return boolean - */ - public function isValid($value, $file = null) - { - $directories = $this->getDirectory(true); - if (($file !== null) and (!empty($file['destination']))) { - $directories[] = $file['destination']; - } else if (!isset($file['name'])) { - $file['name'] = $value; - } - - foreach ($directories as $directory) { - if (empty($directory)) { - continue; - } - - $check = true; - if (file_exists($directory . DIRECTORY_SEPARATOR . $file['name'])) { - $this->_throw($file, self::DOES_EXIST); - return false; - } - } - - if (!isset($check)) { - $this->_throw($file, self::DOES_EXIST); - return false; - } - - return true; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/File/Size.php b/phpQuery/phpQuery/Zend/Validate/File/Size.php deleted file mode 100644 index 2adaeb3..0000000 --- a/phpQuery/phpQuery/Zend/Validate/File/Size.php +++ /dev/null @@ -1,308 +0,0 @@ - "The file '%value%' is bigger than allowed", - self::TOO_SMALL => "The file '%value%' is smaller than allowed", - self::NOT_FOUND => "The file '%value%' could not be found" - ); - - /** - * @var array Error message template variables - */ - protected $_messageVariables = array( - 'min' => '_min', - 'max' => '_max' - ); - - /** - * Minimum filesize - * @var integer - */ - protected $_min; - - /** - * Maximum filesize - * - * If null, there is no maximum filesize - * - * @var integer|null - */ - protected $_max; - - /** - * Sets validator options - * - * Min limits the filesize, when used with max=null it is the maximum filesize - * It also accepts an array with the keys 'min' and 'max' - * - * @param integer|array $min Minimum filesize - * @param integer $max Maximum filesize - * @return void - */ - public function __construct($min, $max = null) - { - if (is_array($min)) { - $count = count($min); - if (array_key_exists('min', $min)) { - if (array_key_exists('max', $min)) { - $max = $min['max']; - } - - $min = $min['min']; - } elseif ($count === 2) { - $minValue = array_shift($min); - $max = array_shift($min); - $min = $minValue; - } elseif($count === 1) { - $min = array_shift($min); - $max = null; - } else { - $min = 0; - $max = null; - } - } - - if (empty($max)) { - $max = $min; - $min = 0; - } - - $this->setMin($min); - $this->setMax($max); - } - - /** - * Returns the minimum filesize - * - * @param boolean $unit Return the value with unit, when false the plan bytes will be returned - * @return integer - */ - public function getMin($unit = true) - { - $unit = (bool) $unit; - $min = $this->_min; - if ($unit) { - $min = $this->_toByteString($min); - } - return $min; - } - - /** - * Sets the minimum filesize - * - * @param integer $min The minimum filesize - * @return Zend_Validate_File_Size Provides a fluent interface - * @throws Zend_Validate_Exception When min is greater than max - */ - public function setMin($min) - { - $min = (integer) $this->_fromByteString($min); - if (($this->_max !== null) && ($min > $this->_max)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The minimum must be less than or equal to the maximum filesize, but $min >" - . " {$this->_max}"); - } - - $this->_min = max(0, $min); - return $this; - } - - /** - * Returns the maximum filesize - * - * @param boolean $unit Return the value with unit, when false the plan bytes will be returned - * @return integer|null - */ - public function getMax($unit = true) - { - $unit = (bool) $unit; - $max = $this->_max; - if ($unit) { - $max = $this->_toByteString($max); - } - return $max; - } - - /** - * Sets the maximum filesize - * - * @param integer|null $max The maximum filesize - * @return Zend_Validate_StringLength Provides a fluent interface - * @throws Zend_Validate_Exception When max is smaller than min - */ - public function setMax($max) - { - $max = (integer) $this->_fromByteString($max); - if (($this->_min !== null) && ($max < $this->_min)) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum filesize, but " - . "$max < {$this->_min}"); - } else { - $this->_max = $max; - } - - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the filesize of $value is at least min and - * not bigger than max (when max is not null). - * - * @param string $value Real file to check for size - * @param array $file File data from Zend_File_Transfer - * @return boolean - */ - public function isValid($value, $file = null) - { - // Is file readable ? - if (!@is_readable($value)) { - $this->_throw($file, self::NOT_FOUND); - return false; - } - - // limited to 4GB files - $size = sprintf("%u",@filesize($value)); - $this->_setValue($size); - - // Check to see if it's smaller than min size - if (($this->_min !== null) && ($size < $this->_min)) { - $this->_throw($file, self::TOO_SMALL); - } - - // Check to see if it's larger than max size - if (($this->_max !== null) && ($this->_max < $size)) { - $this->_throw($file, self::TOO_BIG); - } - - if (count($this->_messages) > 0) { - return false; - } else { - return true; - } - } - - /** - * Returns the formatted size - * - * @param integer $size - * @return string - */ - protected function _toByteString($size) - { - $sizes = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); - for ($i=0; $size > 1024 && $i < 9; $i++) { - $size /= 1024; - } - return round($size, 2).$sizes[$i]; - } - - /** - * Returns the unformatted size - * - * @param string $size - * @return integer - */ - protected function _fromByteString($size) - { - if (is_numeric($size)) { - return (integer) $size; - } - - $type = trim(substr($size, -2)); - $value = substr($size, 0, -2); - switch (strtoupper($type)) { - case 'YB': - $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024); - break; - case 'ZB': - $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024); - break; - case 'EB': - $value *= (1024 * 1024 * 1024 * 1024 * 1024 * 1024); - break; - case 'PB': - $value *= (1024 * 1024 * 1024 * 1024 * 1024); - break; - case 'TB': - $value *= (1024 * 1024 * 1024 * 1024); - break; - case 'GB': - $value *= (1024 * 1024 * 1024); - break; - case 'MB': - $value *= (1024 * 1024); - break; - case 'KB': - $value *= 1024; - break; - default: - break; - } - - return $value; - } - - /** - * Throws an error of the given type - * - * @param string $file - * @param string $errorType - * @return false - */ - protected function _throw($file, $errorType) - { - if ($file !== null) { - $this->_value = $file['name']; - } - - $this->_error($errorType); - return false; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/File/Upload.php b/phpQuery/phpQuery/Zend/Validate/File/Upload.php deleted file mode 100644 index a56cf14..0000000 --- a/phpQuery/phpQuery/Zend/Validate/File/Upload.php +++ /dev/null @@ -1,216 +0,0 @@ - "The file '%value%' exceeds the defined ini size", - self::FORM_SIZE => "The file '%value%' exceeds the defined form size", - self::PARTIAL => "The file '%value%' was only partially uploaded", - self::NO_FILE => "The file '%value%' was not uploaded", - self::NO_TMP_DIR => "No temporary directory was found for the file '%value%'", - self::CANT_WRITE => "The file '%value%' can't be written", - self::EXTENSION => "The extension returned an error while uploading the file '%value%'", - self::ATTACK => "The file '%value%' was illegal uploaded, possible attack", - self::FILE_NOT_FOUND => "The file '%value%' was not found", - self::UNKNOWN => "Unknown error while uploading the file '%value%'" - ); - - /** - * Internal array of files - * @var array - */ - protected $_files = array(); - - /** - * Sets validator options - * - * The array $files must be given in syntax of Zend_File_Transfer to be checked - * If no files are given the $_FILES array will be used automatically. - * NOTE: This validator will only work with HTTP POST uploads! - * - * @param array $files Array of files in syntax of Zend_File_Transfer - * @return void - */ - public function __construct($files = array()) - { - $this->setFiles($files); - } - - /** - * Returns the array of set files - * - * @param string $files (Optional) The file to return in detail - * @return array - * @throws Zend_Validate_Exception If file is not found - */ - public function getFiles($file = null) - { - if ($file !== null) { - $return = array(); - foreach ($this->_files as $name => $content) { - if ($name === $file) { - $return[$file] = $this->_files[$name]; - } - - if ($content['name'] === $file) { - $return[$name] = $this->_files[$name]; - } - } - - if (count($return) === 0) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The file '$file' was not found"); - } - - return $return; - } - - return $this->_files; - } - - /** - * Sets the minimum filesize - * - * @param array $files The files to check in syntax of Zend_File_Transfer - * @return Zend_Validate_File_Upload Provides a fluent interface - */ - public function setFiles($files = array()) - { - if (count($files) === 0) { - $this->_files = $_FILES; - } else { - $this->_files = $files; - } - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the file was uploaded without errors - * - * @param string $value Single file to check for upload errors, when giving null the $_FILES array - * from initialization will be used - * @return boolean - */ - public function isValid($value) - { - if (array_key_exists($value, $this->_files)) { - $files[$value] = $this->_files[$value]; - } else { - foreach ($this->_files as $file => $content) { - if ($content['name'] === $value) { - $files[$file] = $this->_files[$file]; - } - - if ($content['tmp_name'] === $value) { - $files[$file] = $this->_files[$file]; - } - } - } - - if (empty($files)) { - $this->_error(self::FILE_NOT_FOUND); - return false; - } - - foreach ($files as $file => $content) { - $this->_value = $file; - switch($content['error']) { - case 0: - if (!is_uploaded_file($content['tmp_name'])) { - $this->_error(self::ATTACK); - } - break; - - case 1: - $this->_error(self::INI_SIZE); - break; - - case 2: - $this->_error(self::FORM_SIZE); - break; - - case 3: - $this->_error(self::PARTIAL); - break; - - case 4: - $this->_error(self::NO_FILE); - break; - - case 6: - $this->_error(self::NO_TMP_DIR); - break; - - case 7: - $this->_error(self::CANT_WRITE); - break; - - case 8: - $this->_error(self::EXTENSION); - break; - - default: - $this->_error(self::UNKNOWN); - break; - } - } - - if (count($this->_messages) > 0) { - return false; - } else { - return true; - } - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/Float.php b/phpQuery/phpQuery/Zend/Validate/Float.php deleted file mode 100644 index 0405161..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Float.php +++ /dev/null @@ -1,75 +0,0 @@ - "'%value%' does not appear to be a float" - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is a floating-point value - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - $locale = localeconv(); - - $valueFiltered = str_replace($locale['thousands_sep'], '', $valueString); - $valueFiltered = str_replace($locale['decimal_point'], '.', $valueFiltered); - - if (strval(floatval($valueFiltered)) != $valueFiltered) { - $this->_error(); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/GreaterThan.php b/phpQuery/phpQuery/Zend/Validate/GreaterThan.php deleted file mode 100644 index 35e658c..0000000 --- a/phpQuery/phpQuery/Zend/Validate/GreaterThan.php +++ /dev/null @@ -1,114 +0,0 @@ - "'%value%' is not greater than '%min%'" - ); - - /** - * @var array - */ - protected $_messageVariables = array( - 'min' => '_min' - ); - - /** - * Minimum value - * - * @var mixed - */ - protected $_min; - - /** - * Sets validator options - * - * @param mixed $min - * @return void - */ - public function __construct($min) - { - $this->setMin($min); - } - - /** - * Returns the min option - * - * @return mixed - */ - public function getMin() - { - return $this->_min; - } - - /** - * Sets the min option - * - * @param mixed $min - * @return Zend_Validate_GreaterThan Provides a fluent interface - */ - public function setMin($min) - { - $this->_min = $min; - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is greater than min option - * - * @param mixed $value - * @return boolean - */ - public function isValid($value) - { - $this->_setValue($value); - - if ($this->_min >= $value) { - $this->_error(); - return false; - } - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Hex.php b/phpQuery/phpQuery/Zend/Validate/Hex.php deleted file mode 100644 index 9512eda..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Hex.php +++ /dev/null @@ -1,74 +0,0 @@ - "'%value%' has not only hexadecimal digit characters" - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value contains only hexadecimal digit characters - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - if (!ctype_xdigit($valueString)) { - $this->_error(); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Hostname.php b/phpQuery/phpQuery/Zend/Validate/Hostname.php deleted file mode 100644 index ea79c24..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Hostname.php +++ /dev/null @@ -1,444 +0,0 @@ - "'%value%' appears to be an IP address, but IP addresses are not allowed", - self::UNKNOWN_TLD => "'%value%' appears to be a DNS hostname but cannot match TLD against known list", - self::INVALID_DASH => "'%value%' appears to be a DNS hostname but contains a dash (-) in an invalid position", - self::INVALID_HOSTNAME_SCHEMA => "'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'", - self::UNDECIPHERABLE_TLD => "'%value%' appears to be a DNS hostname but cannot extract TLD part", - self::INVALID_HOSTNAME => "'%value%' does not match the expected structure for a DNS hostname", - self::INVALID_LOCAL_NAME => "'%value%' does not appear to be a valid local network name", - self::LOCAL_NAME_NOT_ALLOWED => "'%value%' appears to be a local network name but local network names are not allowed" - ); - - /** - * @var array - */ - protected $_messageVariables = array( - 'tld' => '_tld' - ); - - /** - * Allows Internet domain names (e.g., example.com) - */ - const ALLOW_DNS = 1; - - /** - * Allows IP addresses - */ - const ALLOW_IP = 2; - - /** - * Allows local network names (e.g., localhost, www.localdomain) - */ - const ALLOW_LOCAL = 4; - - /** - * Allows all types of hostnames - */ - const ALLOW_ALL = 7; - - /** - * Whether IDN domains are validated - * - * @var boolean - */ - private $_validateIdn = true; - - /** - * Whether TLDs are validated against a known list - * - * @var boolean - */ - private $_validateTld = true; - - /** - * Bit field of ALLOW constants; determines which types of hostnames are allowed - * - * @var integer - */ - protected $_allow; - - /** - * Bit field of CHECK constants; determines what additional hostname checks to make - * - * @var unknown_type - */ - // protected $_check; - - /** - * Array of valid top-level-domains - * - * @var array - * @see ftp://data.iana.org/TLD/tlds-alpha-by-domain.txt List of all TLDs by domain - */ - protected $_validTlds = array( - 'ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', - 'aq', 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', - 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bm', 'bn', 'bo', - 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd', - 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', - 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', - 'dz', 'ec', 'edu', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', - 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', - 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', - 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', - 'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', - 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', - 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', - 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', - 'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', - 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng', - 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe', - 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', - 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', - 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', - 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj', - 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', - 'tz', 'ua', 'ug', 'uk', 'um', 'us', 'uy', 'uz', 'va', 'vc', 've', - 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm', - 'zw' - ); - - /** - * @var string - */ - protected $_tld; - - /** - * Sets validator options - * - * @param integer $allow OPTIONAL Set what types of hostname to allow (default ALLOW_DNS) - * @param boolean $validateIdn OPTIONAL Set whether IDN domains are validated (default true) - * @param boolean $validateTld OPTIONAL Set whether the TLD element of a hostname is validated (default true) - * @param Zend_Validate_Ip $ipValidator OPTIONAL - * @return void - * @see http://www.iana.org/cctld/specifications-policies-cctlds-01apr02.htm Technical Specifications for ccTLDs - */ - public function __construct($allow = self::ALLOW_DNS, $validateIdn = true, $validateTld = true, Zend_Validate_Ip $ipValidator = null) - { - // Set allow options - $this->setAllow($allow); - - // Set validation options - $this->_validateIdn = $validateIdn; - $this->_validateTld = $validateTld; - - $this->setIpValidator($ipValidator); - } - - /** - * @param Zend_Validate_Ip $ipValidator OPTIONAL - * @return void; - */ - public function setIpValidator(Zend_Validate_Ip $ipValidator = null) - { - if ($ipValidator === null) { - $ipValidator = new Zend_Validate_Ip(); - } - $this->_ipValidator = $ipValidator; - } - - /** - * Returns the allow option - * - * @return integer - */ - public function getAllow() - { - return $this->_allow; - } - - /** - * Sets the allow option - * - * @param integer $allow - * @return Zend_Validate_Hostname Provides a fluent interface - */ - public function setAllow($allow) - { - $this->_allow = $allow; - return $this; - } - - /** - * Set whether IDN domains are validated - * - * This only applies when DNS hostnames are validated - * - * @param boolean $allowed Set allowed to true to validate IDNs, and false to not validate them - */ - public function setValidateIdn ($allowed) - { - $this->_validateIdn = (bool) $allowed; - } - - /** - * Set whether the TLD element of a hostname is validated - * - * This only applies when DNS hostnames are validated - * - * @param boolean $allowed Set allowed to true to validate TLDs, and false to not validate them - */ - public function setValidateTld ($allowed) - { - $this->_validateTld = (bool) $allowed; - } - - /** - * Sets the check option - * - * @param integer $check - * @return Zend_Validate_Hostname Provides a fluent interface - */ - /* - public function setCheck($check) - { - $this->_check = $check; - return $this; - } - */ - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the $value is a valid hostname with respect to the current allow option - * - * @param string $value - * @throws Zend_Validate_Exception if a fatal error occurs for validation process - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - // Check input against IP address schema - if ($this->_ipValidator->setTranslator($this->getTranslator())->isValid($valueString)) { - if (!($this->_allow & self::ALLOW_IP)) { - $this->_error(self::IP_ADDRESS_NOT_ALLOWED); - return false; - } else{ - return true; - } - } - - // Check input against DNS hostname schema - $domainParts = explode('.', $valueString); - if ((count($domainParts) > 1) && (strlen($valueString) >= 4) && (strlen($valueString) <= 254)) { - $status = false; - - do { - // First check TLD - if (preg_match('/([a-z]{2,10})$/i', end($domainParts), $matches)) { - - reset($domainParts); - - // Hostname characters are: *(label dot)(label dot label); max 254 chars - // label: id-prefix [*ldh{61} id-prefix]; max 63 chars - // id-prefix: alpha / digit - // ldh: alpha / digit / dash - - // Match TLD against known list - $this->_tld = strtolower($matches[1]); - if ($this->_validateTld) { - if (!in_array($this->_tld, $this->_validTlds)) { - $this->_error(self::UNKNOWN_TLD); - $status = false; - break; - } - } - - /** - * Match against IDN hostnames - * @see Zend_Validate_Hostname_Interface - */ - $labelChars = 'a-z0-9'; - $utf8 = false; - $classFile = 'Zend/Validate/Hostname/' . ucfirst($this->_tld) . '.php'; - if ($this->_validateIdn) { - if (Zend_Loader::isReadable($classFile)) { - - // Load additional characters - $className = 'Zend_Validate_Hostname_' . ucfirst($this->_tld); - Zend_Loader::loadClass($className); - $labelChars .= call_user_func(array($className, 'getCharacters')); - $utf8 = true; - } - } - - // Keep label regex short to avoid issues with long patterns when matching IDN hostnames - $regexLabel = '/^[' . $labelChars . '\x2d]{1,63}$/i'; - if ($utf8) { - $regexLabel .= 'u'; - } - - // Check each hostname part - $valid = true; - foreach ($domainParts as $domainPart) { - - // Check dash (-) does not start, end or appear in 3rd and 4th positions - if (strpos($domainPart, '-') === 0 || - (strlen($domainPart) > 2 && strpos($domainPart, '-', 2) == 2 && strpos($domainPart, '-', 3) == 3) || - strrpos($domainPart, '-') === strlen($domainPart) - 1) { - - $this->_error(self::INVALID_DASH); - $status = false; - break 2; - } - - // Check each domain part - $status = @preg_match($regexLabel, $domainPart); - if ($status === false) { - /** - * Regex error - * @see Zend_Validate_Exception - */ - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception('Internal error: DNS validation failed'); - } elseif ($status === 0) { - $valid = false; - } - } - - // If all labels didn't match, the hostname is invalid - if (!$valid) { - $this->_error(self::INVALID_HOSTNAME_SCHEMA); - $status = false; - } - - } else { - // Hostname not long enough - $this->_error(self::UNDECIPHERABLE_TLD); - $status = false; - } - } while (false); - - // If the input passes as an Internet domain name, and domain names are allowed, then the hostname - // passes validation - if ($status && ($this->_allow & self::ALLOW_DNS)) { - return true; - } - } else { - $this->_error(self::INVALID_HOSTNAME); - } - - // Check input against local network name schema; last chance to pass validation - $regexLocal = '/^(([a-zA-Z0-9\x2d]{1,63}\x2e)*[a-zA-Z0-9\x2d]{1,63}){1,254}$/'; - $status = @preg_match($regexLocal, $valueString); - if (false === $status) { - /** - * Regex error - * @see Zend_Validate_Exception - */ - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception('Internal error: local network name validation failed'); - } - - // If the input passes as a local network name, and local network names are allowed, then the - // hostname passes validation - $allowLocal = $this->_allow & self::ALLOW_LOCAL; - if ($status && $allowLocal) { - return true; - } - - // If the input does not pass as a local network name, add a message - if (!$status) { - $this->_error(self::INVALID_LOCAL_NAME); - } - - // If local network names are not allowed, add a message - if ($status && !$allowLocal) { - $this->_error(self::LOCAL_NAME_NOT_ALLOWED); - } - - return false; - } - - /** - * Throws an exception if a regex for $type does not exist - * - * @param string $type - * @throws Zend_Validate_Exception - * @return Zend_Validate_Hostname Provides a fluent interface - */ - /* - protected function _checkRegexType($type) - { - if (!isset($this->_regex[$type])) { - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("'$type' must be one of ('" . implode(', ', array_keys($this->_regex)) - . "')"); - } - return $this; - } - */ - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Hostname/At.php b/phpQuery/phpQuery/Zend/Validate/Hostname/At.php deleted file mode 100644 index fff6bf2..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Hostname/At.php +++ /dev/null @@ -1,50 +0,0 @@ - 'Tokens do not match', - self::MISSING_TOKEN => 'No token was provided to match against', - ); - - /** - * Original token against which to validate - * @var string - */ - protected $_token; - - /** - * Sets validator options - * - * @param string $token - * @return void - */ - public function __construct($token = null) - { - if (null !== $token) { - $this->setToken($token); - } - } - - /** - * Set token against which to compare - * - * @param string $token - * @return Zend_Validate_Identical - */ - public function setToken($token) - { - $this->_token = (string) $token; - return $this; - } - - /** - * Retrieve token - * - * @return string - */ - public function getToken() - { - return $this->_token; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if a token has been set and the provided value - * matches that token. - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $this->_setValue($value); - $token = $this->getToken(); - - if (empty($token)) { - $this->_error(self::MISSING_TOKEN); - return false; - } - - if ($value !== $token) { - $this->_error(self::NOT_SAME); - return false; - } - - return true; - } -} diff --git a/phpQuery/phpQuery/Zend/Validate/InArray.php b/phpQuery/phpQuery/Zend/Validate/InArray.php deleted file mode 100644 index 1c7725a..0000000 --- a/phpQuery/phpQuery/Zend/Validate/InArray.php +++ /dev/null @@ -1,138 +0,0 @@ - "'%value%' was not found in the haystack" - ); - - /** - * Haystack of possible values - * - * @var array - */ - protected $_haystack; - - /** - * Whether a strict in_array() invocation is used - * - * @var boolean - */ - protected $_strict; - - /** - * Sets validator options - * - * @param array $haystack - * @param boolean $strict - * @return void - */ - public function __construct(array $haystack, $strict = false) - { - $this->setHaystack($haystack) - ->setStrict($strict); - } - - /** - * Returns the haystack option - * - * @return mixed - */ - public function getHaystack() - { - return $this->_haystack; - } - - /** - * Sets the haystack option - * - * @param mixed $haystack - * @return Zend_Validate_InArray Provides a fluent interface - */ - public function setHaystack(array $haystack) - { - $this->_haystack = $haystack; - return $this; - } - - /** - * Returns the strict option - * - * @return boolean - */ - public function getStrict() - { - return $this->_strict; - } - - /** - * Sets the strict option - * - * @param boolean $strict - * @return Zend_Validate_InArray Provides a fluent interface - */ - public function setStrict($strict) - { - $this->_strict = $strict; - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is contained in the haystack option. If the strict - * option is true, then the type of $value is also checked. - * - * @param mixed $value - * @return boolean - */ - public function isValid($value) - { - $this->_setValue($value); - if (!in_array($value, $this->_haystack, $this->_strict)) { - $this->_error(); - return false; - } - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Int.php b/phpQuery/phpQuery/Zend/Validate/Int.php deleted file mode 100644 index 0bde2cb..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Int.php +++ /dev/null @@ -1,75 +0,0 @@ - "'%value%' does not appear to be an integer" - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is a valid integer - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - $locale = localeconv(); - - $valueFiltered = str_replace($locale['decimal_point'], '.', $valueString); - $valueFiltered = str_replace($locale['thousands_sep'], '', $valueFiltered); - - if (strval(intval($valueFiltered)) != $valueFiltered) { - $this->_error(); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Interface.php b/phpQuery/phpQuery/Zend/Validate/Interface.php deleted file mode 100644 index 4fcd525..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Interface.php +++ /dev/null @@ -1,71 +0,0 @@ - "'%value%' does not appear to be a valid IP address" - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is a valid IP address - * - * @param mixed $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - if (ip2long($valueString) === false) { - $this->_error(); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/LessThan.php b/phpQuery/phpQuery/Zend/Validate/LessThan.php deleted file mode 100644 index 9f7b72c..0000000 --- a/phpQuery/phpQuery/Zend/Validate/LessThan.php +++ /dev/null @@ -1,113 +0,0 @@ - "'%value%' is not less than '%max%'" - ); - - /** - * @var array - */ - protected $_messageVariables = array( - 'max' => '_max' - ); - - /** - * Maximum value - * - * @var mixed - */ - protected $_max; - - /** - * Sets validator options - * - * @param mixed $max - * @return void - */ - public function __construct($max) - { - $this->setMax($max); - } - - /** - * Returns the max option - * - * @return mixed - */ - public function getMax() - { - return $this->_max; - } - - /** - * Sets the max option - * - * @param mixed $max - * @return Zend_Validate_LessThan Provides a fluent interface - */ - public function setMax($max) - { - $this->_max = $max; - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is less than max option - * - * @param mixed $value - * @return boolean - */ - public function isValid($value) - { - $this->_setValue($value); - if ($this->_max <= $value) { - $this->_error(); - return false; - } - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/NotEmpty.php b/phpQuery/phpQuery/Zend/Validate/NotEmpty.php deleted file mode 100644 index dcf3662..0000000 --- a/phpQuery/phpQuery/Zend/Validate/NotEmpty.php +++ /dev/null @@ -1,74 +0,0 @@ - "Value is empty, but a non-empty value is required" - ); - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value is not an empty value. - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $this->_setValue((string) $value); - - if (is_string($value) - && (('' === $value) - || preg_match('/^\s+$/s', $value)) - ) { - $this->_error(); - return false; - } elseif (!is_string($value) && empty($value)) { - $this->_error(); - return false; - } - - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/Regex.php b/phpQuery/phpQuery/Zend/Validate/Regex.php deleted file mode 100644 index 1566f07..0000000 --- a/phpQuery/phpQuery/Zend/Validate/Regex.php +++ /dev/null @@ -1,125 +0,0 @@ - "'%value%' does not match against pattern '%pattern%'" - ); - - /** - * @var array - */ - protected $_messageVariables = array( - 'pattern' => '_pattern' - ); - - /** - * Regular expression pattern - * - * @var string - */ - protected $_pattern; - - /** - * Sets validator options - * - * @param string $pattern - * @return void - */ - public function __construct($pattern) - { - $this->setPattern($pattern); - } - - /** - * Returns the pattern option - * - * @return string - */ - public function getPattern() - { - return $this->_pattern; - } - - /** - * Sets the pattern option - * - * @param string $pattern - * @return Zend_Validate_Regex Provides a fluent interface - */ - public function setPattern($pattern) - { - $this->_pattern = (string) $pattern; - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if $value matches against the pattern option - * - * @param string $value - * @throws Zend_Validate_Exception if there is a fatal error in pattern matching - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - - $this->_setValue($valueString); - - $status = @preg_match($this->_pattern, $valueString); - if (false === $status) { - /** - * @see Zend_Validate_Exception - */ - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("Internal error matching pattern '$this->_pattern' against value '$valueString'"); - } - if (!$status) { - $this->_error(); - return false; - } - return true; - } - -} diff --git a/phpQuery/phpQuery/Zend/Validate/StringLength.php b/phpQuery/phpQuery/Zend/Validate/StringLength.php deleted file mode 100644 index c43f2ca..0000000 --- a/phpQuery/phpQuery/Zend/Validate/StringLength.php +++ /dev/null @@ -1,180 +0,0 @@ - "'%value%' is less than %min% characters long", - self::TOO_LONG => "'%value%' is greater than %max% characters long" - ); - - /** - * @var array - */ - protected $_messageVariables = array( - 'min' => '_min', - 'max' => '_max' - ); - - /** - * Minimum length - * - * @var integer - */ - protected $_min; - - /** - * Maximum length - * - * If null, there is no maximum length - * - * @var integer|null - */ - protected $_max; - - /** - * Sets validator options - * - * @param integer $min - * @param integer $max - * @return void - */ - public function __construct($min = 0, $max = null) - { - $this->setMin($min); - $this->setMax($max); - } - - /** - * Returns the min option - * - * @return integer - */ - public function getMin() - { - return $this->_min; - } - - /** - * Sets the min option - * - * @param integer $min - * @throws Zend_Validate_Exception - * @return Zend_Validate_StringLength Provides a fluent interface - */ - public function setMin($min) - { - if (null !== $this->_max && $min > $this->_max) { - /** - * @see Zend_Validate_Exception - */ - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The minimum must be less than or equal to the maximum length, but $min >" - . " $this->_max"); - } - $this->_min = max(0, (integer) $min); - return $this; - } - - /** - * Returns the max option - * - * @return integer|null - */ - public function getMax() - { - return $this->_max; - } - - /** - * Sets the max option - * - * @param integer|null $max - * @throws Zend_Validate_Exception - * @return Zend_Validate_StringLength Provides a fluent interface - */ - public function setMax($max) - { - if (null === $max) { - $this->_max = null; - } else if ($max < $this->_min) { - /** - * @see Zend_Validate_Exception - */ - require_once 'Zend/Validate/Exception.php'; - throw new Zend_Validate_Exception("The maximum must be greater than or equal to the minimum length, but " - . "$max < $this->_min"); - } else { - $this->_max = (integer) $max; - } - - return $this; - } - - /** - * Defined by Zend_Validate_Interface - * - * Returns true if and only if the string length of $value is at least the min option and - * no greater than the max option (when the max option is not null). - * - * @param string $value - * @return boolean - */ - public function isValid($value) - { - $valueString = (string) $value; - $this->_setValue($valueString); - $length = iconv_strlen($valueString); - if ($length < $this->_min) { - $this->_error(self::TOO_SHORT); - } - if (null !== $this->_max && $this->_max < $length) { - $this->_error(self::TOO_LONG); - } - if (count($this->_messages)) { - return false; - } else { - return true; - } - } - -} diff --git a/phpQuery/phpQuery/bootstrap.example.php b/phpQuery/phpQuery/bootstrap.example.php deleted file mode 100644 index 4fafe1a..0000000 --- a/phpQuery/phpQuery/bootstrap.example.php +++ /dev/null @@ -1,14 +0,0 @@ - \ No newline at end of file diff --git a/phpQuery/phpQuery/compat/mbstring.php b/phpQuery/phpQuery/compat/mbstring.php deleted file mode 100644 index 409129e..0000000 --- a/phpQuery/phpQuery/compat/mbstring.php +++ /dev/null @@ -1,88 +0,0 @@ -document) - $pq->find('*')->add($pq->document) - ->trigger($type, $data); - } - } else { - if (isset($data[0]) && $data[0] instanceof DOMEvent) { - $event = $data[0]; - $event->relatedTarget = $event->target; - $event->target = $node; - $data = array_slice($data, 1); - } else { - $event = new DOMEvent(array( - 'type' => $type, - 'target' => $node, - 'timeStamp' => time(), - )); - } - $i = 0; - while($node) { - // TODO whois - phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on " - ."node \n");//.phpQueryObject::whois($node)."\n"); - $event->currentTarget = $node; - $eventNode = self::getNode($documentID, $node); - if (isset($eventNode->eventHandlers)) { - foreach($eventNode->eventHandlers as $eventType => $handlers) { - $eventNamespace = null; - if (strpos($type, '.') !== false) - list($eventName, $eventNamespace) = explode('.', $eventType); - else - $eventName = $eventType; - if ($name != $eventName) - continue; - if ($namespace && $eventNamespace && $namespace != $eventNamespace) - continue; - foreach($handlers as $handler) { - phpQuery::debug("Calling event handler\n"); - $event->data = $handler['data'] - ? $handler['data'] - : null; - $params = array_merge(array($event), $data); - $return = phpQuery::callbackRun($handler['callback'], $params); - if ($return === false) { - $event->bubbles = false; - } - } - } - } - // to bubble or not to bubble... - if (! $event->bubbles) - break; - $node = $node->parentNode; - $i++; - } - } - } - /** - * Binds a handler to one or more events (like click) for each matched element. - * Can also bind custom events. - * - * @param DOMNode|phpQueryObject|string $document - * @param unknown_type $type - * @param unknown_type $data Optional - * @param unknown_type $callback - * - * @TODO support '!' (exclusive) events - * @TODO support more than event in $type (space-separated) - * @TODO support binding to global events - */ - public static function add($document, $node, $type, $data, $callback = null) { - phpQuery::debug("Binding '$type' event"); - $documentID = phpQuery::getDocumentID($document); -// if (is_null($callback) && is_callable($data)) { -// $callback = $data; -// $data = null; -// } - $eventNode = self::getNode($documentID, $node); - if (! $eventNode) - $eventNode = self::setNode($documentID, $node); - if (!isset($eventNode->eventHandlers[$type])) - $eventNode->eventHandlers[$type] = array(); - $eventNode->eventHandlers[$type][] = array( - 'callback' => $callback, - 'data' => $data, - ); - } - /** - * Enter description here... - * - * @param DOMNode|phpQueryObject|string $document - * @param unknown_type $type - * @param unknown_type $callback - * - * @TODO namespace events - * @TODO support more than event in $type (space-separated) - */ - public static function remove($document, $node, $type = null, $callback = null) { - $documentID = phpQuery::getDocumentID($document); - $eventNode = self::getNode($documentID, $node); - if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) { - if ($callback) { - foreach($eventNode->eventHandlers[$type] as $k => $handler) - if ($handler['callback'] == $callback) - unset($eventNode->eventHandlers[$type][$k]); - } else { - unset($eventNode->eventHandlers[$type]); - } - } - } - protected static function getNode($documentID, $node) { - foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) { - if ($node->isSameNode($eventNode)) - return $eventNode; - } - } - protected static function setNode($documentID, $node) { - phpQuery::$documents[$documentID]->eventsNodes[] = $node; - return phpQuery::$documents[$documentID]->eventsNodes[ - count(phpQuery::$documents[$documentID]->eventsNodes)-1 - ]; - } - protected static function issetGlobal($documentID, $type) { - return isset(phpQuery::$documents[$documentID]) - ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal) - : false; - } -} diff --git a/phpQuery/phpQuery/phpQueryObject.php b/phpQuery/phpQuery/phpQueryObject.php deleted file mode 100644 index 146ddc8..0000000 --- a/phpQuery/phpQuery/phpQueryObject.php +++ /dev/null @@ -1,3180 +0,0 @@ - - * @package phpQuery - * @method phpQueryObject clone() clone() - * @method phpQueryObject empty() empty() - * @method phpQueryObject next() next($selector = null) - * @method phpQueryObject prev() prev($selector = null) - * @property Int $length - */ -class phpQueryObject - implements Iterator, Countable, ArrayAccess { - public $documentID = null; - /** - * DOMDocument class. - * - * @var DOMDocument - */ - public $document = null; - public $charset = null; - /** - * - * @var DOMDocumentWrapper - */ - public $documentWrapper = null; - /** - * XPath interface. - * - * @var DOMXPath - */ - public $xpath = null; - /** - * Stack of selected elements. - * @TODO refactor to ->nodes - * @var array - */ - public $elements = array(); - /** - * @access private - */ - protected $elementsBackup = array(); - /** - * @access private - */ - protected $previous = null; - /** - * @access private - * @TODO deprecate - */ - protected $root = array(); - /** - * Indicated if doument is just a fragment (no tag). - * - * Every document is realy a full document, so even documentFragments can - * be queried against , but getDocument(id)->htmlOuter() will return - * only contents of . - * - * @var bool - */ - public $documentFragment = true; - /** - * Iterator interface helper - * @access private - */ - protected $elementsInterator = array(); - /** - * Iterator interface helper - * @access private - */ - protected $valid = false; - /** - * Iterator interface helper - * @access private - */ - protected $current = null; - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function __construct($documentID) { -// if ($documentID instanceof self) -// var_dump($documentID->getDocumentID()); - $id = $documentID instanceof self - ? $documentID->getDocumentID() - : $documentID; -// var_dump($id); - if (! isset(phpQuery::$documents[$id] )) { -// var_dump(phpQuery::$documents); - throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first."); - } - $this->documentID = $id; - $this->documentWrapper =& phpQuery::$documents[$id]; - $this->document =& $this->documentWrapper->document; - $this->xpath =& $this->documentWrapper->xpath; - $this->charset =& $this->documentWrapper->charset; - $this->documentFragment =& $this->documentWrapper->isDocumentFragment; - // TODO check $this->DOM->documentElement; -// $this->root = $this->document->documentElement; - $this->root =& $this->documentWrapper->root; -// $this->toRoot(); - $this->elements = array($this->root); - } - /** - * - * @access private - * @param $attr - * @return unknown_type - */ - public function __get($attr) { - switch($attr) { - // FIXME doesnt work at all ? - case 'length': - return $this->size(); - break; - default: - return $this->$attr; - } - } - /** - * Saves actual object to $var by reference. - * Useful when need to break chain. - * @param phpQueryObject $var - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function toReference(&$var) { - return $var = $this; - } - public function documentFragment($state = null) { - if ($state) { - phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state; - return $this; - } - return $this->documentFragment; - } - /** - * @access private - * @TODO documentWrapper - */ - protected function isRoot( $node) { -// return $node instanceof DOMDOCUMENT || $node->tagName == 'html'; - return $node instanceof DOMDOCUMENT - || ($node instanceof DOMELEMENT && $node->tagName == 'html') - || $this->root->isSameNode($node); - } - /** - * @access private - */ - protected function stackIsRoot() { - return $this->size() == 1 && $this->isRoot($this->elements[0]); - } - /** - * Enter description here... - * NON JQUERY METHOD - * - * Watch out, it doesn't creates new instance, can be reverted with end(). - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function toRoot() { - $this->elements = array($this->root); - return $this; -// return $this->newInstance(array($this->root)); - } - /** - * Saves object's DocumentID to $var by reference. - * - * $myDocumentId; - * phpQuery::newDocument('
') - * ->getDocumentIDRef($myDocumentId) - * ->find('div')->... - * - * - * @param unknown_type $domId - * @see phpQuery::newDocument - * @see phpQuery::newDocumentFile - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function getDocumentIDRef(&$documentID) { - $documentID = $this->getDocumentID(); - return $this; - } - /** - * Returns object with stack set to document root. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function getDocument() { - return phpQuery::getDocument($this->getDocumentID()); - } - /** - * - * @return DOMDocument - */ - public function getDOMDocument() { - return $this->document; - } - /** - * Get object's Document ID. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function getDocumentID() { - return $this->documentID; - } - /** - * Unloads whole document from memory. - * CAUTION! None further operations will be possible on this document. - * All objects refering to it will be useless. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function unloadDocument() { - phpQuery::unloadDocuments($this->getDocumentID()); - } - public function isHTML() { - return $this->documentWrapper->isHTML; - } - public function isXHTML() { - return $this->documentWrapper->isXHTML; - } - public function isXML() { - return $this->documentWrapper->isXML; - } - /** - * Enter description here... - * - * @link http://docs.jquery.com/Ajax/serialize - * @return string - */ - public function serialize() { - return phpQuery::param($this->serializeArray()); - } - /** - * Enter description here... - * - * @link http://docs.jquery.com/Ajax/serializeArray - * @return array - */ - public function serializeArray($submit = null) { - $source = $this->filter('form, input, select, textarea') - ->find('input, select, textarea') - ->andSelf() - ->not('form'); - $return = array(); -// $source->dumpDie(); - foreach($source as $input) { - $input = phpQuery::pq($input); - if ($input->is('[disabled]')) - continue; - if (!$input->is('[name]')) - continue; - if ($input->is('[type=checkbox]') && !$input->is('[checked]')) - continue; - // jquery diff - if ($submit && $input->is('[type=submit]')) { - if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit)) - continue; - else if (is_string($submit) && $input->attr('name') != $submit) - continue; - } - $return[] = array( - 'name' => $input->attr('name'), - 'value' => $input->val(), - ); - } - return $return; - } - /** - * @access private - */ - protected function debug($in) { - if (! phpQuery::$debug ) - return; - print('
');
-		print_r($in);
-		// file debug
-//		file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
-		// quite handy debug trace
-//		if ( is_array($in))
-//			print_r(array_slice(debug_backtrace(), 3));
-		print("
\n"); - } - /** - * @access private - */ - protected function isRegexp($pattern) { - return in_array( - $pattern[ mb_strlen($pattern)-1 ], - array('^','*','$') - ); - } - /** - * Determines if $char is really a char. - * - * @param string $char - * @return bool - * @todo rewrite me to charcode range ! ;) - * @access private - */ - protected function isChar($char) { - return extension_loaded('mbstring') && phpQuery::$mbstringSupport - ? mb_eregi('\w', $char) - : preg_match('@\w@', $char); - } - /** - * @access private - */ - protected function parseSelector($query) { - // clean spaces - // TODO include this inside parsing ? - $query = trim( - preg_replace('@\s+@', ' ', - preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) - ) - ); - $queries = array(array()); - if (! $query) - return $queries; - $return =& $queries[0]; - $specialChars = array('>',' '); -// $specialCharsMapping = array('/' => '>'); - $specialCharsMapping = array(); - $strlen = mb_strlen($query); - $classChars = array('.', '-'); - $pseudoChars = array('-'); - $tagChars = array('*', '|', '-'); - // split multibyte string - // http://code.google.com/p/phpquery/issues/detail?id=76 - $_query = array(); - for ($i=0; $i<$strlen; $i++) - $_query[] = mb_substr($query, $i, 1); - $query = $_query; - // it works, but i dont like it... - $i = 0; - while( $i < $strlen) { - $c = $query[$i]; - $tmp = ''; - // TAG - if ($this->isChar($c) || in_array($c, $tagChars)) { - while(isset($query[$i]) - && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) { - $tmp .= $query[$i]; - $i++; - } - $return[] = $tmp; - // IDs - } else if ( $c == '#') { - $i++; - while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) { - $tmp .= $query[$i]; - $i++; - } - $return[] = '#'.$tmp; - // SPECIAL CHARS - } else if (in_array($c, $specialChars)) { - $return[] = $c; - $i++; - // MAPPED SPECIAL MULTICHARS -// } else if ( $c.$query[$i+1] == '//') { -// $return[] = ' '; -// $i = $i+2; - // MAPPED SPECIAL CHARS - } else if ( isset($specialCharsMapping[$c])) { - $return[] = $specialCharsMapping[$c]; - $i++; - // COMMA - } else if ( $c == ',') { - $queries[] = array(); - $return =& $queries[ count($queries)-1 ]; - $i++; - while( isset($query[$i]) && $query[$i] == ' ') - $i++; - // CLASSES - } else if ($c == '.') { - while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { - $tmp .= $query[$i]; - $i++; - } - $return[] = $tmp; - // ~ General Sibling Selector - } else if ($c == '~') { - $spaceAllowed = true; - $tmp .= $query[$i++]; - while( isset($query[$i]) - && ($this->isChar($query[$i]) - || in_array($query[$i], $classChars) - || $query[$i] == '*' - || ($query[$i] == ' ' && $spaceAllowed) - )) { - if ($query[$i] != ' ') - $spaceAllowed = false; - $tmp .= $query[$i]; - $i++; - } - $return[] = $tmp; - // + Adjacent sibling selectors - } else if ($c == '+') { - $spaceAllowed = true; - $tmp .= $query[$i++]; - while( isset($query[$i]) - && ($this->isChar($query[$i]) - || in_array($query[$i], $classChars) - || $query[$i] == '*' - || ($spaceAllowed && $query[$i] == ' ') - )) { - if ($query[$i] != ' ') - $spaceAllowed = false; - $tmp .= $query[$i]; - $i++; - } - $return[] = $tmp; - // ATTRS - } else if ($c == '[') { - $stack = 1; - $tmp .= $c; - while( isset($query[++$i])) { - $tmp .= $query[$i]; - if ( $query[$i] == '[') { - $stack++; - } else if ( $query[$i] == ']') { - $stack--; - if (! $stack ) - break; - } - } - $return[] = $tmp; - $i++; - // PSEUDO CLASSES - } else if ($c == ':') { - $stack = 1; - $tmp .= $query[$i++]; - while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { - $tmp .= $query[$i]; - $i++; - } - // with arguments ? - if ( isset($query[$i]) && $query[$i] == '(') { - $tmp .= $query[$i]; - $stack = 1; - while( isset($query[++$i])) { - $tmp .= $query[$i]; - if ( $query[$i] == '(') { - $stack++; - } else if ( $query[$i] == ')') { - $stack--; - if (! $stack ) - break; - } - } - $return[] = $tmp; - $i++; - } else { - $return[] = $tmp; - } - } else { - $i++; - } - } - foreach($queries as $k => $q) { - if (isset($q[0])) { - if (isset($q[0][0]) && $q[0][0] == ':') - array_unshift($queries[$k], '*'); - if ($q[0] != '>') - array_unshift($queries[$k], ' '); - } - } - return $queries; - } - /** - * Return matched DOM nodes. - * - * @param int $index - * @return array|DOMElement Single DOMElement or array of DOMElement. - */ - public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { - $return = isset($index) - ? (isset($this->elements[$index]) ? $this->elements[$index] : null) - : $this->elements; - // pass thou callbacks - $args = func_get_args(); - $args = array_slice($args, 1); - foreach($args as $callback) { - if (is_array($return)) - foreach($return as $k => $v) - $return[$k] = phpQuery::callbackRun($callback, array($v)); - else - $return = phpQuery::callbackRun($callback, array($return)); - } - return $return; - } - /** - * Return matched DOM nodes. - * jQuery difference. - * - * @param int $index - * @return array|string Returns string if $index != null - * @todo implement callbacks - * @todo return only arrays ? - * @todo maybe other name... - */ - public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { - if ($index) - $return = $this->eq($index)->text(); - else { - $return = array(); - for($i = 0; $i < $this->size(); $i++) { - $return[] = $this->eq($i)->text(); - } - } - // pass thou callbacks - $args = func_get_args(); - $args = array_slice($args, 1); - foreach($args as $callback) { - $return = phpQuery::callbackRun($callback, array($return)); - } - return $return; - } - /** - * Return matched DOM nodes. - * jQuery difference. - * - * @param int $index - * @return array|string Returns string if $index != null - * @todo implement callbacks - * @todo return only arrays ? - * @todo maybe other name... - */ - public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { - if ($index) - $return = $this->eq($index)->text(); - else { - $return = array(); - for($i = 0; $i < $this->size(); $i++) { - $return[] = $this->eq($i)->text(); - } - // pass thou callbacks - $args = func_get_args(); - $args = array_slice($args, 1); - } - foreach($args as $callback) { - if (is_array($return)) - foreach($return as $k => $v) - $return[$k] = phpQuery::callbackRun($callback, array($v)); - else - $return = phpQuery::callbackRun($callback, array($return)); - } - return $return; - } - /** - * Returns new instance of actual class. - * - * @param array $newStack Optional. Will replace old stack with new and move old one to history.c - */ - public function newInstance($newStack = null) { - $class = get_class($this); - // support inheritance by passing old object to overloaded constructor - $new = $class != 'phpQuery' - ? new $class($this, $this->getDocumentID()) - : new phpQueryObject($this->getDocumentID()); - $new->previous = $this; - if (is_null($newStack)) { - $new->elements = $this->elements; - if ($this->elementsBackup) - $this->elements = $this->elementsBackup; - } else if (is_string($newStack)) { - $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack(); - } else { - $new->elements = $newStack; - } - return $new; - } - /** - * Enter description here... - * - * In the future, when PHP will support XLS 2.0, then we would do that this way: - * contains(tokenize(@class, '\s'), "something") - * @param unknown_type $class - * @param unknown_type $node - * @return boolean - * @access private - */ - protected function matchClasses($class, $node) { - // multi-class - if ( mb_strpos($class, '.', 1)) { - $classes = explode('.', substr($class, 1)); - $classesCount = count( $classes ); - $nodeClasses = explode(' ', $node->getAttribute('class') ); - $nodeClassesCount = count( $nodeClasses ); - if ( $classesCount > $nodeClassesCount ) - return false; - $diff = count( - array_diff( - $classes, - $nodeClasses - ) - ); - if (! $diff ) - return true; - // single-class - } else { - return in_array( - // strip leading dot from class name - substr($class, 1), - // get classes for element as array - explode(' ', $node->getAttribute('class') ) - ); - } - } - /** - * @access private - */ - protected function runQuery($XQuery, $selector = null, $compare = null) { - if ($compare && ! method_exists($this, $compare)) - return false; - $stack = array(); - if (! $this->elements) - $this->debug('Stack empty, skipping...'); -// var_dump($this->elements[0]->nodeType); - // element, document - foreach($this->stack(array(1, 9, 13)) as $k => $stackNode) { - $detachAfter = false; - // to work on detached nodes we need temporary place them somewhere - // thats because context xpath queries sucks ;] - $testNode = $stackNode; - while ($testNode) { - if (! $testNode->parentNode && ! $this->isRoot($testNode)) { - $this->root->appendChild($testNode); - $detachAfter = $testNode; - break; - } - $testNode = isset($testNode->parentNode) - ? $testNode->parentNode - : null; - } - // XXX tmp ? - $xpath = $this->documentWrapper->isXHTML - ? $this->getNodeXpath($stackNode, 'html') - : $this->getNodeXpath($stackNode); - // FIXME pseudoclasses-only query, support XML - $query = $XQuery == '//' && $xpath == '/html[1]' - ? '//*' - : $xpath.$XQuery; - $this->debug("XPATH: {$query}"); - // run query, get elements - $nodes = $this->xpath->query($query); - $this->debug("QUERY FETCHED"); - if (! $nodes->length ) - $this->debug('Nothing found'); - $debug = array(); - foreach($nodes as $node) { - $matched = false; - if ( $compare) { - phpQuery::$debug ? - $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") - : null; - $phpQueryDebug = phpQuery::$debug; - phpQuery::$debug = false; - // TODO ??? use phpQuery::callbackRun() - if (call_user_func_array(array($this, $compare), array($selector, $node))) - $matched = true; - phpQuery::$debug = $phpQueryDebug; - } else { - $matched = true; - } - if ( $matched) { - if (phpQuery::$debug) - $debug[] = $this->whois( $node ); - $stack[] = $node; - } - } - if (phpQuery::$debug) { - $this->debug("Matched ".count($debug).": ".implode(', ', $debug)); - } - if ($detachAfter) - $this->root->removeChild($detachAfter); - } - $this->elements = $stack; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function find($selectors, $context = null, $noHistory = false) { - if (!$noHistory) - // backup last stack /for end()/ - $this->elementsBackup = $this->elements; - // allow to define context - // TODO combine code below with phpQuery::pq() context guessing code - // as generic function - if ($context) { - if (! is_array($context) && $context instanceof DOMELEMENT) - $this->elements = array($context); - else if (is_array($context)) { - $this->elements = array(); - foreach ($context as $c) - if ($c instanceof DOMELEMENT) - $this->elements[] = $c; - } else if ( $context instanceof self ) - $this->elements = $context->elements; - } - $queries = $this->parseSelector($selectors); - $this->debug(array('FIND', $selectors, $queries)); - $XQuery = ''; - // remember stack state because of multi-queries - $oldStack = $this->elements; - // here we will be keeping found elements - $stack = array(); - foreach($queries as $selector) { - $this->elements = $oldStack; - $delimiterBefore = false; - foreach($selector as $s) { - // TAG - $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport - ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*' - : preg_match('@^[\w|\||-]+$@', $s) || $s == '*'; - if ($isTag) { - if ($this->isXML()) { - // namespace support - if (mb_strpos($s, '|') !== false) { - $ns = $tag = null; - list($ns, $tag) = explode('|', $s); - $XQuery .= "$ns:$tag"; - } else if ($s == '*') { - $XQuery .= "*"; - } else { - $XQuery .= "*[local-name()='$s']"; - } - } else { - $XQuery .= $s; - } - // ID - } else if ($s[0] == '#') { - if ($delimiterBefore) - $XQuery .= '*'; - $XQuery .= "[@id='".substr($s, 1)."']"; - // ATTRIBUTES - } else if ($s[0] == '[') { - if ($delimiterBefore) - $XQuery .= '*'; - // strip side brackets - $attr = trim($s, ']['); - $execute = false; - // attr with specifed value - if (mb_strpos($s, '=')) { - $value = null; - list($attr, $value) = explode('=', $attr); - $value = trim($value, "'\""); - if ($this->isRegexp($attr)) { - // cut regexp character - $attr = substr($attr, 0, -1); - $execute = true; - $XQuery .= "[@{$attr}]"; - } else { - $XQuery .= "[@{$attr}='{$value}']"; - } - // attr without specified value - } else { - $XQuery .= "[@{$attr}]"; - } - if ($execute) { - $this->runQuery($XQuery, $s, 'is'); - $XQuery = ''; - if (! $this->length()) - break; - } - // CLASSES - } else if ($s[0] == '.') { - // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]"); - // thx wizDom ;) - if ($delimiterBefore) - $XQuery .= '*'; - $XQuery .= '[@class]'; - $this->runQuery($XQuery, $s, 'matchClasses'); - $XQuery = ''; - if (! $this->length() ) - break; - // ~ General Sibling Selector - } else if ($s[0] == '~') { - $this->runQuery($XQuery); - $XQuery = ''; - $this->elements = $this - ->siblings( - substr($s, 1) - )->elements; - if (! $this->length() ) - break; - // + Adjacent sibling selectors - } else if ($s[0] == '+') { - // TODO /following-sibling:: - $this->runQuery($XQuery); - $XQuery = ''; - $subSelector = substr($s, 1); - $subElements = $this->elements; - $this->elements = array(); - foreach($subElements as $node) { - // search first DOMElement sibling - $test = $node->nextSibling; - while($test && ! ($test instanceof DOMELEMENT)) - $test = $test->nextSibling; - if ($test && $this->is($subSelector, $test)) - $this->elements[] = $test; - } - if (! $this->length() ) - break; - // PSEUDO CLASSES - } else if ($s[0] == ':') { - // TODO optimization for :first :last - if ($XQuery) { - $this->runQuery($XQuery); - $XQuery = ''; - } - if (! $this->length()) - break; - $this->pseudoClasses($s); - if (! $this->length()) - break; - // DIRECT DESCENDANDS - } else if ($s == '>') { - $XQuery .= '/'; - $delimiterBefore = 2; - // ALL DESCENDANDS - } else if ($s == ' ') { - $XQuery .= '//'; - $delimiterBefore = 2; - // ERRORS - } else { - phpQuery::debug("Unrecognized token '$s'"); - } - $delimiterBefore = $delimiterBefore === 2; - } - // run query if any - if ($XQuery && $XQuery != '//') { - $this->runQuery($XQuery); - $XQuery = ''; - } - foreach($this->elements as $node) - if (! $this->elementsContainsNode($node, $stack)) - $stack[] = $node; - } - $this->elements = $stack; - return $this->newInstance(); - } - /** - * @todo create API for classes with pseudoselectors - * @access private - */ - protected function pseudoClasses($class) { - // TODO clean args parsing ? - $class = ltrim($class, ':'); - $haveArgs = mb_strpos($class, '('); - if ($haveArgs !== false) { - $args = substr($class, $haveArgs+1, -1); - $class = substr($class, 0, $haveArgs); - } - switch($class) { - case 'even': - case 'odd': - $stack = array(); - foreach($this->elements as $i => $node) { - if ($class == 'even' && ($i%2) == 0) - $stack[] = $node; - else if ( $class == 'odd' && $i % 2 ) - $stack[] = $node; - } - $this->elements = $stack; - break; - case 'eq': - $k = intval($args); - $this->elements = isset( $this->elements[$k] ) - ? array( $this->elements[$k] ) - : array(); - break; - case 'gt': - $this->elements = array_slice($this->elements, $args+1); - break; - case 'lt': - $this->elements = array_slice($this->elements, 0, $args+1); - break; - case 'first': - if (isset($this->elements[0])) - $this->elements = array($this->elements[0]); - break; - case 'last': - if ($this->elements) - $this->elements = array($this->elements[count($this->elements)-1]); - break; - /*case 'parent': - $stack = array(); - foreach($this->elements as $node) { - if ( $node->childNodes->length ) - $stack[] = $node; - } - $this->elements = $stack; - break;*/ - case 'contains': - $text = trim($args, "\"'"); - $stack = array(); - foreach($this->elements as $node) { - if (mb_stripos($node->textContent, $text) === false) - continue; - $stack[] = $node; - } - $this->elements = $stack; - break; - case 'not': - $selector = self::unQuote($args); - $this->elements = $this->not($selector)->stack(); - break; - case 'slice': - // TODO jQuery difference ? - $args = explode(',', - str_replace(', ', ',', trim($args, "\"'")) - ); - $start = $args[0]; - $end = isset($args[1]) - ? $args[1] - : null; - if ($end > 0) - $end = $end-$start; - $this->elements = array_slice($this->elements, $start, $end); - break; - case 'has': - $selector = trim($args, "\"'"); - $stack = array(); - foreach($this->stack(1) as $el) { - if ($this->find($selector, $el, true)->length) - $stack[] = $el; - } - $this->elements = $stack; - break; - case 'submit': - case 'reset': - $this->elements = phpQuery::merge( - $this->map(array($this, 'is'), - "input[type=$class]", new CallbackParam() - ), - $this->map(array($this, 'is'), - "button[type=$class]", new CallbackParam() - ) - ); - break; -// $stack = array(); -// foreach($this->elements as $node) -// if ($node->is('input[type=submit]') || $node->is('button[type=submit]')) -// $stack[] = $el; -// $this->elements = $stack; - case 'input': - $this->elements = $this->map( - array($this, 'is'), - 'input', new CallbackParam() - )->elements; - break; - case 'password': - case 'checkbox': - case 'radio': - case 'hidden': - case 'image': - case 'file': - $this->elements = $this->map( - array($this, 'is'), - "input[type=$class]", new CallbackParam() - )->elements; - break; - case 'parent': - $this->elements = $this->map( - create_function('$node', ' - return $node instanceof DOMELEMENT && $node->childNodes->length - ? $node : null;') - )->elements; - break; - case 'empty': - $this->elements = $this->map( - create_function('$node', ' - return $node instanceof DOMELEMENT && $node->childNodes->length - ? null : $node;') - )->elements; - break; - case 'disabled': - case 'selected': - case 'checked': - $this->elements = $this->map( - array($this, 'is'), - "[$class]", new CallbackParam() - )->elements; - break; - case 'enabled': - $this->elements = $this->map( - create_function('$node', ' - return pq($node)->not(":disabled") ? $node : null;') - )->elements; - break; - case 'header': - $this->elements = $this->map( - create_function('$node', - '$isHeader = isset($node->tagName) && in_array($node->tagName, array( - "h1", "h2", "h3", "h4", "h5", "h6", "h7" - )); - return $isHeader - ? $node - : null;') - )->elements; -// $this->elements = $this->map( -// create_function('$node', '$node = pq($node); -// return $node->is("h1") -// || $node->is("h2") -// || $node->is("h3") -// || $node->is("h4") -// || $node->is("h5") -// || $node->is("h6") -// || $node->is("h7") -// ? $node -// : null;') -// )->elements; - break; - case 'only-child': - $this->elements = $this->map( - create_function('$node', - 'return pq($node)->siblings()->size() == 0 ? $node : null;') - )->elements; - break; - case 'first-child': - $this->elements = $this->map( - create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;') - )->elements; - break; - case 'last-child': - $this->elements = $this->map( - create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;') - )->elements; - break; - case 'nth-child': - $param = trim($args, "\"'"); - if (! $param) - break; - // nth-child(n+b) to nth-child(1n+b) - if ($param{0} == 'n') - $param = '1'.$param; - // :nth-child(index/even/odd/equation) - if ($param == 'even' || $param == 'odd') - $mapped = $this->map( - create_function('$node, $param', - '$index = pq($node)->prevAll()->size()+1; - if ($param == "even" && ($index%2) == 0) - return $node; - else if ($param == "odd" && $index%2 == 1) - return $node; - else - return null;'), - new CallbackParam(), $param - ); - else if (mb_strlen($param) > 1 && $param{1} == 'n') - // an+b - $mapped = $this->map( - create_function('$node, $param', - '$prevs = pq($node)->prevAll()->size(); - $index = 1+$prevs; - $b = mb_strlen($param) > 3 - ? $param{3} - : 0; - $a = $param{0}; - if ($b && $param{2} == "-") - $b = -$b; - if ($a > 0) { - return ($index-$b)%$a == 0 - ? $node - : null; - phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs"); - return $a*floor($index/$a)+$b-1 == $prevs - ? $node - : null; - } else if ($a == 0) - return $index == $b - ? $node - : null; - else - // negative value - return $index <= $b - ? $node - : null; -// if (! $b) -// return $index%$a == 0 -// ? $node -// : null; -// else -// return ($index-$b)%$a == 0 -// ? $node -// : null; - '), - new CallbackParam(), $param - ); - else - // index - $mapped = $this->map( - create_function('$node, $index', - '$prevs = pq($node)->prevAll()->size(); - if ($prevs && $prevs == $index-1) - return $node; - else if (! $prevs && $index == 1) - return $node; - else - return null;'), - new CallbackParam(), $param - ); - $this->elements = $mapped->elements; - break; - default: - $this->debug("Unknown pseudoclass '{$class}', skipping..."); - } - } - /** - * @access private - */ - protected function __pseudoClassParam($paramsString) { - // TODO; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function is($selector, $nodes = null) { - phpQuery::debug(array("Is:", $selector)); - if (! $selector) - return false; - $oldStack = $this->elements; - $returnArray = false; - if ($nodes && is_array($nodes)) { - $this->elements = $nodes; - } else if ($nodes) - $this->elements = array($nodes); - $this->filter($selector, true); - $stack = $this->elements; - $this->elements = $oldStack; - if ($nodes) - return $stack ? $stack : null; - return (bool)count($stack); - } - /** - * Enter description here... - * jQuery difference. - * - * Callback: - * - $index int - * - $node DOMNode - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @link http://docs.jquery.com/Traversing/filter - */ - public function filterCallback($callback, $_skipHistory = false) { - if (! $_skipHistory) { - $this->elementsBackup = $this->elements; - $this->debug("Filtering by callback"); - } - $newStack = array(); - foreach($this->elements as $index => $node) { - $result = phpQuery::callbackRun($callback, array($index, $node)); - if (is_null($result) || (! is_null($result) && $result)) - $newStack[] = $node; - } - $this->elements = $newStack; - return $_skipHistory - ? $this - : $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @link http://docs.jquery.com/Traversing/filter - */ - public function filter($selectors, $_skipHistory = false) { - if ($selectors instanceof Callback OR $selectors instanceof Closure) - return $this->filterCallback($selectors, $_skipHistory); - if (! $_skipHistory) - $this->elementsBackup = $this->elements; - $notSimpleSelector = array(' ', '>', '~', '+', '/'); - if (! is_array($selectors)) - $selectors = $this->parseSelector($selectors); - if (! $_skipHistory) - $this->debug(array("Filtering:", $selectors)); - $finalStack = array(); - foreach($selectors as $selector) { - $stack = array(); - if (! $selector) - break; - // avoid first space or / - if (in_array($selector[0], $notSimpleSelector)) - $selector = array_slice($selector, 1); - // PER NODE selector chunks - foreach($this->stack() as $node) { - $break = false; - foreach($selector as $s) { - if (!($node instanceof DOMELEMENT)) { - // all besides DOMElement - if ( $s[0] == '[') { - $attr = trim($s, '[]'); - if ( mb_strpos($attr, '=')) { - list( $attr, $val ) = explode('=', $attr); - if ($attr == 'nodeType' && $node->nodeType != $val) - $break = true; - } - } else - $break = true; - } else { - // DOMElement only - // ID - if ( $s[0] == '#') { - if ( $node->getAttribute('id') != substr($s, 1) ) - $break = true; - // CLASSES - } else if ( $s[0] == '.') { - if (! $this->matchClasses( $s, $node ) ) - $break = true; - // ATTRS - } else if ( $s[0] == '[') { - // strip side brackets - $attr = trim($s, '[]'); - if (mb_strpos($attr, '=')) { - list($attr, $val) = explode('=', $attr); - $val = self::unQuote($val); - if ($attr == 'nodeType') { - if ($val != $node->nodeType) - $break = true; - } else if ($this->isRegexp($attr)) { - $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport - ? quotemeta(trim($val, '"\'')) - : preg_quote(trim($val, '"\''), '@'); - // switch last character - switch( substr($attr, -1)) { - // quotemeta used insted of preg_quote - // http://code.google.com/p/phpquery/issues/detail?id=76 - case '^': - $pattern = '^'.$val; - break; - case '*': - $pattern = '.*'.$val.'.*'; - break; - case '$': - $pattern = '.*'.$val.'$'; - break; - } - // cut last character - $attr = substr($attr, 0, -1); - $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport - ? mb_ereg_match($pattern, $node->getAttribute($attr)) - : preg_match("@{$pattern}@", $node->getAttribute($attr)); - if (! $isMatch) - $break = true; - } else if ($node->getAttribute($attr) != $val) - $break = true; - } else if (! $node->hasAttribute($attr)) - $break = true; - // PSEUDO CLASSES - } else if ( $s[0] == ':') { - // skip - // TAG - } else if (trim($s)) { - if ($s != '*') { - // TODO namespaces - if (isset($node->tagName)) { - if ($node->tagName != $s) - $break = true; - } else if ($s == 'html' && ! $this->isRoot($node)) - $break = true; - } - // AVOID NON-SIMPLE SELECTORS - } else if (in_array($s, $notSimpleSelector)) { - $break = true; - $this->debug(array('Skipping non simple selector', $selector)); - } - } - if ($break) - break; - } - // if element passed all chunks of selector - add it to new stack - if (! $break ) - $stack[] = $node; - } - $tmpStack = $this->elements; - $this->elements = $stack; - // PER ALL NODES selector chunks - foreach($selector as $s) - // PSEUDO CLASSES - if ($s[0] == ':') - $this->pseudoClasses($s); - foreach($this->elements as $node) - // XXX it should be merged without duplicates - // but jQuery doesnt do that - $finalStack[] = $node; - $this->elements = $tmpStack; - } - $this->elements = $finalStack; - if ($_skipHistory) { - return $this; - } else { - $this->debug("Stack length after filter(): ".count($finalStack)); - return $this->newInstance(); - } - } - /** - * - * @param $value - * @return unknown_type - * @TODO implement in all methods using passed parameters - */ - protected static function unQuote($value) { - return $value[0] == '\'' || $value[0] == '"' - ? substr($value, 1, -1) - : $value; - } - /** - * Enter description here... - * - * @link http://docs.jquery.com/Ajax/load - * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo Support $selector - */ - public function load($url, $data = null, $callback = null) { - if ($data && ! is_array($data)) { - $callback = $data; - $data = null; - } - if (mb_strpos($url, ' ') !== false) { - $matches = null; - if (extension_loaded('mbstring') && phpQuery::$mbstringSupport) - mb_ereg('^([^ ]+) (.*)$', $url, $matches); - else - preg_match('^([^ ]+) (.*)$', $url, $matches); - $url = $matches[1]; - $selector = $matches[2]; - // FIXME this sucks, pass as callback param - $this->_loadSelector = $selector; - } - $ajax = array( - 'url' => $url, - 'type' => $data ? 'POST' : 'GET', - 'data' => $data, - 'complete' => $callback, - 'success' => array($this, '__loadSuccess') - ); - phpQuery::ajax($ajax); - return $this; - } - /** - * @access private - * @param $html - * @return unknown_type - */ - public function __loadSuccess($html) { - if ($this->_loadSelector) { - $html = phpQuery::newDocument($html)->find($this->_loadSelector); - unset($this->_loadSelector); - } - foreach($this->stack(1) as $node) { - phpQuery::pq($node, $this->getDocumentID()) - ->markup($html); - } - } - /** - * Enter description here... - * - * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo - */ - public function css() { - // TODO - return $this; - } - /** - * @todo - * - */ - public function show(){ - // TODO - return $this; - } - /** - * @todo - * - */ - public function hide(){ - // TODO - return $this; - } - /** - * Trigger a type of event on every matched element. - * - * @param unknown_type $type - * @param unknown_type $data - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO support more than event in $type (space-separated) - */ - public function trigger($type, $data = array()) { - foreach($this->elements as $node) - phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node); - return $this; - } - /** - * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions. - * - * @param unknown_type $type - * @param unknown_type $data - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO - */ - public function triggerHandler($type, $data = array()) { - // TODO; - } - /** - * Binds a handler to one or more events (like click) for each matched element. - * Can also bind custom events. - * - * @param unknown_type $type - * @param unknown_type $data Optional - * @param unknown_type $callback - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO support '!' (exclusive) events - * @TODO support more than event in $type (space-separated) - */ - public function bind($type, $data, $callback = null) { - // TODO check if $data is callable, not using is_callable - if (! isset($callback)) { - $callback = $data; - $data = null; - } - foreach($this->elements as $node) - phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback); - return $this; - } - /** - * Enter description here... - * - * @param unknown_type $type - * @param unknown_type $callback - * @return unknown - * @TODO namespace events - * @TODO support more than event in $type (space-separated) - */ - public function unbind($type = null, $callback = null) { - foreach($this->elements as $node) - phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function change($callback = null) { - if ($callback) - return $this->bind('change', $callback); - return $this->trigger('change'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function submit($callback = null) { - if ($callback) - return $this->bind('submit', $callback); - return $this->trigger('submit'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function click($callback = null) { - if ($callback) - return $this->bind('click', $callback); - return $this->trigger('click'); - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapAllOld($wrapper) { - $wrapper = pq($wrapper)->_clone(); - if (! $wrapper->length() || ! $this->length() ) - return $this; - $wrapper->insertBefore($this->elements[0]); - $deepest = $wrapper->elements[0]; - while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) - $deepest = $deepest->firstChild; - pq($deepest)->append($this); - return $this; - } - /** - * Enter description here... - * - * TODO testme... - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapAll($wrapper) { - if (! $this->length()) - return $this; - return phpQuery::pq($wrapper, $this->getDocumentID()) - ->clone() - ->insertBefore($this->get(0)) - ->map(array($this, '___wrapAllCallback')) - ->append($this); - } - /** - * - * @param $node - * @return unknown_type - * @access private - */ - public function ___wrapAllCallback($node) { - $deepest = $node; - while($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) - $deepest = $deepest->firstChild; - return $deepest; - } - /** - * Enter description here... - * NON JQUERY METHOD - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapAllPHP($codeBefore, $codeAfter) { - return $this - ->slice(0, 1) - ->beforePHP($codeBefore) - ->end() - ->slice(-1) - ->afterPHP($codeAfter) - ->end(); - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrap($wrapper) { - foreach($this->stack() as $node) - phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper); - return $this; - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapPHP($codeBefore, $codeAfter) { - foreach($this->stack() as $node) - phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter); - return $this; - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapInner($wrapper) { - foreach($this->stack() as $node) - phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper); - return $this; - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function wrapInnerPHP($codeBefore, $codeAfter) { - foreach($this->stack(1) as $node) - phpQuery::pq($node, $this->getDocumentID())->contents() - ->wrapAllPHP($codeBefore, $codeAfter); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @testme Support for text nodes - */ - public function contents() { - $stack = array(); - foreach($this->stack(1) as $el) { - // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56 -// if (! isset($el->childNodes)) -// continue; - foreach($el->childNodes as $node) { - $stack[] = $node; - } - } - return $this->newInstance($stack); - } - /** - * Enter description here... - * - * jQuery difference. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function contentsUnwrap() { - foreach($this->stack(1) as $node) { - if (! $node->parentNode ) - continue; - $childNodes = array(); - // any modification in DOM tree breaks childNodes iteration, so cache them first - foreach($node->childNodes as $chNode ) - $childNodes[] = $chNode; - foreach($childNodes as $chNode ) -// $node->parentNode->appendChild($chNode); - $node->parentNode->insertBefore($chNode, $node); - $node->parentNode->removeChild($node); - } - return $this; - } - /** - * Enter description here... - * - * jQuery difference. - */ - public function switchWith($markup) { - $markup = pq($markup, $this->getDocumentID()); - $content = null; - foreach($this->stack(1) as $node) { - pq($node) - ->contents()->toReference($content)->end() - ->replaceWith($markup->clone()->append($content)); - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function eq($num) { - $oldStack = $this->elements; - $this->elementsBackup = $this->elements; - $this->elements = array(); - if ( isset($oldStack[$num]) ) - $this->elements[] = $oldStack[$num]; - return $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function size() { - return count($this->elements); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @deprecated Use length as attribute - */ - public function length() { - return $this->size(); - } - public function count() { - return $this->size(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo $level - */ - public function end($level = 1) { -// $this->elements = array_pop( $this->history ); -// return $this; -// $this->previous->DOM = $this->DOM; -// $this->previous->XPath = $this->XPath; - return $this->previous - ? $this->previous - : $this; - } - /** - * Enter description here... - * Normal use ->clone() . - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function _clone() { - $newStack = array(); - //pr(array('copy... ', $this->whois())); - //$this->dumpHistory('copy'); - $this->elementsBackup = $this->elements; - foreach($this->elements as $node) { - $newStack[] = $node->cloneNode(true); - } - $this->elements = $newStack; - return $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function replaceWithPHP($code) { - return $this->replaceWith(phpQuery::php($code)); - } - /** - * Enter description here... - * - * @param String|phpQuery $content - * @link http://docs.jquery.com/Manipulation/replaceWith#content - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function replaceWith($content) { - return $this->after($content)->remove(); - } - /** - * Enter description here... - * - * @param String $selector - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo this works ? - */ - public function replaceAll($selector) { - foreach(phpQuery::pq($selector, $this->getDocumentID()) as $node) - phpQuery::pq($node, $this->getDocumentID()) - ->after($this->_clone()) - ->remove(); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function remove($selector = null) { - $loop = $selector - ? $this->filter($selector)->elements - : $this->elements; - foreach($loop as $node) { - if (! $node->parentNode ) - continue; - if (isset($node->tagName)) - $this->debug("Removing '{$node->tagName}'"); - $node->parentNode->removeChild($node); - // Mutation event - $event = new DOMEvent(array( - 'target' => $node, - 'type' => 'DOMNodeRemoved' - )); - phpQueryEvents::trigger($this->getDocumentID(), - $event->type, array($event), $node - ); - } - return $this; - } - protected function markupEvents($newMarkup, $oldMarkup, $node) { - if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) { - $event = new DOMEvent(array( - 'target' => $node, - 'type' => 'change' - )); - phpQueryEvents::trigger($this->getDocumentID(), - $event->type, array($event), $node - ); - } - } - /** - * jQuey difference - * - * @param $markup - * @return unknown_type - * @TODO trigger change event for textarea - */ - public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) { - $args = func_get_args(); - if ($this->documentWrapper->isXML) - return call_user_func_array(array($this, 'xml'), $args); - else - return call_user_func_array(array($this, 'html'), $args); - } - /** - * jQuey difference - * - * @param $markup - * @return unknown_type - */ - public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) { - $args = func_get_args(); - if ($this->documentWrapper->isXML) - return call_user_func_array(array($this, 'xmlOuter'), $args); - else - return call_user_func_array(array($this, 'htmlOuter'), $args); - } - /** - * Enter description here... - * - * @param unknown_type $html - * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @TODO force html result - */ - public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) { - if (isset($html)) { - // INSERT - $nodes = $this->documentWrapper->import($html); - $this->empty(); - foreach($this->stack(1) as $alreadyAdded => $node) { - // for now, limit events for textarea - if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') - $oldHtml = pq($node, $this->getDocumentID())->markup(); - foreach($nodes as $newNode) { - $node->appendChild($alreadyAdded - ? $newNode->cloneNode(true) - : $newNode - ); - } - // for now, limit events for textarea - if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') - $this->markupEvents($html, $oldHtml, $node); - } - return $this; - } else { - // FETCH - $return = $this->documentWrapper->markup($this->elements, true); - $args = func_get_args(); - foreach(array_slice($args, 1) as $callback) { - $return = phpQuery::callbackRun($callback, array($return)); - } - return $return; - } - } - /** - * @TODO force xml result - */ - public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) { - $args = func_get_args(); - return call_user_func_array(array($this, 'html'), $args); - } - /** - * Enter description here... - * @TODO force html result - * - * @return String - */ - public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { - $markup = $this->documentWrapper->markup($this->elements); - // pass thou callbacks - $args = func_get_args(); - foreach($args as $callback) { - $markup = phpQuery::callbackRun($callback, array($markup)); - } - return $markup; - } - /** - * @TODO force xml result - */ - public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) { - $args = func_get_args(); - return call_user_func_array(array($this, 'htmlOuter'), $args); - } - public function __toString() { - return $this->markupOuter(); - } - /** - * Just like html(), but returns markup with VALID (dangerous) PHP tags. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo support returning markup with PHP tags when called without param - */ - public function php($code = null) { - return $this->markupPHP($code); - } - /** - * Enter description here... - * - * @param $code - * @return unknown_type - */ - public function markupPHP($code = null) { - return isset($code) - ? $this->markup(phpQuery::php($code)) - : phpQuery::markupToPHP($this->markup()); - } - /** - * Enter description here... - * - * @param $code - * @return unknown_type - */ - public function markupOuterPHP() { - return phpQuery::markupToPHP($this->markupOuter()); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function children($selector = null) { - $stack = array(); - foreach($this->stack(1) as $node) { -// foreach($node->getElementsByTagName('*') as $newNode) { - foreach($node->childNodes as $newNode) { - if ($newNode->nodeType != 1) - continue; - if ($selector && ! $this->is($selector, $newNode)) - continue; - if ($this->elementsContainsNode($newNode, $stack)) - continue; - $stack[] = $newNode; - } - } - $this->elementsBackup = $this->elements; - $this->elements = $stack; - return $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function ancestors($selector = null) { - return $this->children( $selector ); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function append( $content) { - return $this->insert($content, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function appendPHP( $content) { - return $this->insert("", 'append'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function appendTo( $seletor) { - return $this->insert($seletor, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function prepend( $content) { - return $this->insert($content, __FUNCTION__); - } - /** - * Enter description here... - * - * @todo accept many arguments, which are joined, arrays maybe also - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function prependPHP( $content) { - return $this->insert("", 'prepend'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function prependTo( $seletor) { - return $this->insert($seletor, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function before($content) { - return $this->insert($content, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function beforePHP( $content) { - return $this->insert("", 'before'); - } - /** - * Enter description here... - * - * @param String|phpQuery - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function insertBefore( $seletor) { - return $this->insert($seletor, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function after( $content) { - return $this->insert($content, __FUNCTION__); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function afterPHP( $content) { - return $this->insert("", 'after'); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function insertAfter( $seletor) { - return $this->insert($seletor, __FUNCTION__); - } - /** - * Internal insert method. Don't use it. - * - * @param unknown_type $target - * @param unknown_type $type - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function insert($target, $type) { - $this->debug("Inserting data with '{$type}'"); - $to = false; - switch( $type) { - case 'appendTo': - case 'prependTo': - case 'insertBefore': - case 'insertAfter': - $to = true; - } - switch(gettype($target)) { - case 'string': - $insertFrom = $insertTo = array(); - if ($to) { - // INSERT TO - $insertFrom = $this->elements; - if (phpQuery::isMarkup($target)) { - // $target is new markup, import it - $insertTo = $this->documentWrapper->import($target); - // insert into selected element - } else { - // $tagret is a selector - $thisStack = $this->elements; - $this->toRoot(); - $insertTo = $this->find($target)->elements; - $this->elements = $thisStack; - } - } else { - // INSERT FROM - $insertTo = $this->elements; - $insertFrom = $this->documentWrapper->import($target); - } - break; - case 'object': - $insertFrom = $insertTo = array(); - // phpQuery - if ($target instanceof self) { - if ($to) { - $insertTo = $target->elements; - if ($this->documentFragment && $this->stackIsRoot()) - // get all body children -// $loop = $this->find('body > *')->elements; - // TODO test it, test it hard... -// $loop = $this->newInstance($this->root)->find('> *')->elements; - $loop = $this->root->childNodes; - else - $loop = $this->elements; - // import nodes if needed - $insertFrom = $this->getDocumentID() == $target->getDocumentID() - ? $loop - : $target->documentWrapper->import($loop); - } else { - $insertTo = $this->elements; - if ( $target->documentFragment && $target->stackIsRoot() ) - // get all body children -// $loop = $target->find('body > *')->elements; - $loop = $target->root->childNodes; - else - $loop = $target->elements; - // import nodes if needed - $insertFrom = $this->getDocumentID() == $target->getDocumentID() - ? $loop - : $this->documentWrapper->import($loop); - } - // DOMNODE - } elseif ($target instanceof DOMNODE) { - // import node if needed -// if ( $target->ownerDocument != $this->DOM ) -// $target = $this->DOM->importNode($target, true); - if ( $to) { - $insertTo = array($target); - if ($this->documentFragment && $this->stackIsRoot()) - // get all body children - $loop = $this->root->childNodes; -// $loop = $this->find('body > *')->elements; - else - $loop = $this->elements; - foreach($loop as $fromNode) - // import nodes if needed - $insertFrom[] = ! $fromNode->ownerDocument->isSameNode($target->ownerDocument) - ? $target->ownerDocument->importNode($fromNode, true) - : $fromNode; - } else { - // import node if needed - if (! $target->ownerDocument->isSameNode($this->document)) - $target = $this->document->importNode($target, true); - $insertTo = $this->elements; - $insertFrom[] = $target; - } - } - break; - } - phpQuery::debug("From ".count($insertFrom)."; To ".count($insertTo)." nodes"); - foreach($insertTo as $insertNumber => $toNode) { - // we need static relative elements in some cases - switch( $type) { - case 'prependTo': - case 'prepend': - $firstChild = $toNode->firstChild; - break; - case 'insertAfter': - case 'after': - $nextSibling = $toNode->nextSibling; - break; - } - foreach($insertFrom as $fromNode) { - // clone if inserted already before - $insert = $insertNumber - ? $fromNode->cloneNode(true) - : $fromNode; - switch($type) { - case 'appendTo': - case 'append': -// $toNode->insertBefore( -// $fromNode, -// $toNode->lastChild->nextSibling -// ); - $toNode->appendChild($insert); - $eventTarget = $insert; - break; - case 'prependTo': - case 'prepend': - $toNode->insertBefore( - $insert, - $firstChild - ); - break; - case 'insertBefore': - case 'before': - if (! $toNode->parentNode) - throw new Exception("No parentNode, can't do {$type}()"); - else - $toNode->parentNode->insertBefore( - $insert, - $toNode - ); - break; - case 'insertAfter': - case 'after': - if (! $toNode->parentNode) - throw new Exception("No parentNode, can't do {$type}()"); - else - $toNode->parentNode->insertBefore( - $insert, - $nextSibling - ); - break; - } - // Mutation event - $event = new DOMEvent(array( - 'target' => $insert, - 'type' => 'DOMNodeInserted' - )); - phpQueryEvents::trigger($this->getDocumentID(), - $event->type, array($event), $insert - ); - } - } - return $this; - } - /** - * Enter description here... - * - * @return Int - */ - public function index($subject) { - $index = -1; - $subject = $subject instanceof phpQueryObject - ? $subject->elements[0] - : $subject; - foreach($this->newInstance() as $k => $node) { - if ($node->isSameNode($subject)) - $index = $k; - } - return $index; - } - /** - * Enter description here... - * - * @param unknown_type $start - * @param unknown_type $end - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @testme - */ - public function slice($start, $end = null) { -// $last = count($this->elements)-1; -// $end = $end -// ? min($end, $last) -// : $last; -// if ($start < 0) -// $start = $last+$start; -// if ($start > $last) -// return array(); - if ($end > 0) - $end = $end-$start; - return $this->newInstance( - array_slice($this->elements, $start, $end) - ); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function reverse() { - $this->elementsBackup = $this->elements; - $this->elements = array_reverse($this->elements); - return $this->newInstance(); - } - /** - * Return joined text content. - * @return String - */ - public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) { - if (isset($text)) - return $this->html(htmlspecialchars($text)); - $args = func_get_args(); - $args = array_slice($args, 1); - $return = ''; - foreach($this->elements as $node) { - $text = $node->textContent; - if (count($this->elements) > 1 && $text) - $text .= "\n"; - foreach($args as $callback) { - $text = phpQuery::callbackRun($callback, array($text)); - } - $return .= $text; - } - return $return; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function plugin($class, $file = null) { - phpQuery::plugin($class, $file); - return $this; - } - /** - * Deprecated, use $pq->plugin() instead. - * - * @deprecated - * @param $class - * @param $file - * @return unknown_type - */ - public static function extend($class, $file = null) { - return $this->plugin($class, $file); - } - /** - * - * @access private - * @param $method - * @param $args - * @return unknown_type - */ - public function __call($method, $args) { - $aliasMethods = array('clone', 'empty'); - if (isset(phpQuery::$extendMethods[$method])) { - array_unshift($args, $this); - return phpQuery::callbackRun( - phpQuery::$extendMethods[$method], $args - ); - } else if (isset(phpQuery::$pluginsMethods[$method])) { - array_unshift($args, $this); - $class = phpQuery::$pluginsMethods[$method]; - $realClass = "phpQueryObjectPlugin_$class"; - $return = call_user_func_array( - array($realClass, $method), - $args - ); - // XXX deprecate ? - return is_null($return) - ? $this - : $return; - } else if (in_array($method, $aliasMethods)) { - return call_user_func_array(array($this, '_'.$method), $args); - } else - throw new Exception("Method '{$method}' doesnt exist"); - } - /** - * Safe rename of next(). - * - * Use it ONLY when need to call next() on an iterated object (in same time). - * Normaly there is no need to do such thing ;) - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function _next($selector = null) { - return $this->newInstance( - $this->getElementSiblings('nextSibling', $selector, true) - ); - } - /** - * Use prev() and next(). - * - * @deprecated - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function _prev($selector = null) { - return $this->prev($selector); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function prev($selector = null) { - return $this->newInstance( - $this->getElementSiblings('previousSibling', $selector, true) - ); - } - /** - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo - */ - public function prevAll($selector = null) { - return $this->newInstance( - $this->getElementSiblings('previousSibling', $selector) - ); - } - /** - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo FIXME: returns source elements insted of next siblings - */ - public function nextAll($selector = null) { - return $this->newInstance( - $this->getElementSiblings('nextSibling', $selector) - ); - } - /** - * @access private - */ - protected function getElementSiblings($direction, $selector = null, $limitToOne = false) { - $stack = array(); - $count = 0; - foreach($this->stack() as $node) { - $test = $node; - while( isset($test->{$direction}) && $test->{$direction}) { - $test = $test->{$direction}; - if (! $test instanceof DOMELEMENT) - continue; - $stack[] = $test; - if ($limitToOne) - break; - } - } - if ($selector) { - $stackOld = $this->elements; - $this->elements = $stack; - $stack = $this->filter($selector, true)->stack(); - $this->elements = $stackOld; - } - return $stack; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function siblings($selector = null) { - $stack = array(); - $siblings = array_merge( - $this->getElementSiblings('previousSibling', $selector), - $this->getElementSiblings('nextSibling', $selector) - ); - foreach($siblings as $node) { - if (! $this->elementsContainsNode($node, $stack)) - $stack[] = $node; - } - return $this->newInstance($stack); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function not($selector = null) { - if (is_string($selector)) - phpQuery::debug(array('not', $selector)); - else - phpQuery::debug('not'); - $stack = array(); - if ($selector instanceof self || $selector instanceof DOMNODE) { - foreach($this->stack() as $node) { - if ($selector instanceof self) { - $matchFound = false; - foreach($selector->stack() as $notNode) { - if ($notNode->isSameNode($node)) - $matchFound = true; - } - if (! $matchFound) - $stack[] = $node; - } else if ($selector instanceof DOMNODE) { - if (! $selector->isSameNode($node)) - $stack[] = $node; - } else { - if (! $this->is($selector)) - $stack[] = $node; - } - } - } else { - $orgStack = $this->stack(); - $matched = $this->filter($selector, true)->stack(); -// $matched = array(); -// // simulate OR in filter() instead of AND 5y -// foreach($this->parseSelector($selector) as $s) { -// $matched = array_merge($matched, -// $this->filter(array($s))->stack() -// ); -// } - foreach($orgStack as $node) - if (! $this->elementsContainsNode($node, $matched)) - $stack[] = $node; - } - return $this->newInstance($stack); - } - /** - * Enter description here... - * - * @param string|phpQueryObject - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function add($selector = null) { - if (! $selector) - return $this; - $stack = array(); - $this->elementsBackup = $this->elements; - $found = phpQuery::pq($selector, $this->getDocumentID()); - $this->merge($found->elements); - return $this->newInstance(); - } - /** - * @access private - */ - protected function merge() { - foreach(func_get_args() as $nodes) - foreach($nodes as $newNode ) - if (! $this->elementsContainsNode($newNode) ) - $this->elements[] = $newNode; - } - /** - * @access private - * TODO refactor to stackContainsNode - */ - protected function elementsContainsNode($nodeToCheck, $elementsStack = null) { - $loop = ! is_null($elementsStack) - ? $elementsStack - : $this->elements; - foreach($loop as $node) { - if ( $node->isSameNode( $nodeToCheck ) ) - return true; - } - return false; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function parent($selector = null) { - $stack = array(); - foreach($this->elements as $node ) - if ( $node->parentNode && ! $this->elementsContainsNode($node->parentNode, $stack) ) - $stack[] = $node->parentNode; - $this->elementsBackup = $this->elements; - $this->elements = $stack; - if ( $selector ) - $this->filter($selector, true); - return $this->newInstance(); - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function parents($selector = null) { - $stack = array(); - if (! $this->elements ) - $this->debug('parents() - stack empty'); - foreach($this->elements as $node) { - $test = $node; - while( $test->parentNode) { - $test = $test->parentNode; - if ($this->isRoot($test)) - break; - if (! $this->elementsContainsNode($test, $stack)) { - $stack[] = $test; - continue; - } - } - } - $this->elementsBackup = $this->elements; - $this->elements = $stack; - if ( $selector ) - $this->filter($selector, true); - return $this->newInstance(); - } - /** - * Internal stack iterator. - * - * @access private - */ - public function stack($nodeTypes = null) { - if (!isset($nodeTypes)) - return $this->elements; - if (!is_array($nodeTypes)) - $nodeTypes = array($nodeTypes); - $return = array(); - foreach($this->elements as $node) { - if (in_array($node->nodeType, $nodeTypes)) - $return[] = $node; - } - return $return; - } - // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes - protected function attrEvents($attr, $oldAttr, $oldValue, $node) { - // skip events for XML documents - if (! $this->isXHTML() && ! $this->isHTML()) - return; - $event = null; - // identify - $isInputValue = $node->tagName == 'input' - && ( - in_array($node->getAttribute('type'), - array('text', 'password', 'hidden')) - || !$node->getAttribute('type') - ); - $isRadio = $node->tagName == 'input' - && $node->getAttribute('type') == 'radio'; - $isCheckbox = $node->tagName == 'input' - && $node->getAttribute('type') == 'checkbox'; - $isOption = $node->tagName == 'option'; - if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) { - $event = new DOMEvent(array( - 'target' => $node, - 'type' => 'change' - )); - } else if (($isRadio || $isCheckbox) && $attr == 'checked' && ( - // check - (! $oldAttr && $node->hasAttribute($attr)) - // un-check - || (! $node->hasAttribute($attr) && $oldAttr) - )) { - $event = new DOMEvent(array( - 'target' => $node, - 'type' => 'change' - )); - } else if ($isOption && $node->parentNode && $attr == 'selected' && ( - // select - (! $oldAttr && $node->hasAttribute($attr)) - // un-select - || (! $node->hasAttribute($attr) && $oldAttr) - )) { - $event = new DOMEvent(array( - 'target' => $node->parentNode, - 'type' => 'change' - )); - } - if ($event) { - phpQueryEvents::trigger($this->getDocumentID(), - $event->type, array($event), $node - ); - } - } - public function attr($attr = null, $value = null) { - foreach($this->stack(1) as $node) { - if (! is_null($value)) { - $loop = $attr == '*' - ? $this->getNodeAttrs($node) - : array($attr); - foreach($loop as $a) { - $oldValue = $node->getAttribute($a); - $oldAttr = $node->hasAttribute($a); - // TODO raises an error when charset other than UTF-8 - // while document's charset is also not UTF-8 - @$node->setAttribute($a, $value); - $this->attrEvents($a, $oldAttr, $oldValue, $node); - } - } else if ($attr == '*') { - // jQuery difference - $return = array(); - foreach($node->attributes as $n => $v) - $return[$n] = $v->value; - return $return; - } else - return $node->hasAttribute($attr) - ? $node->getAttribute($attr) - : null; - } - return is_null($value) - ? '' : $this; - } - /** - * @access private - */ - protected function getNodeAttrs($node) { - $return = array(); - foreach($node->attributes as $n => $o) - $return[] = $n; - return $return; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo check CDATA ??? - */ - public function attrPHP($attr, $code) { - if (! is_null($code)) { - $value = '<'.'?php '.$code.' ?'.'>'; - // TODO tempolary solution - // http://code.google.com/p/phpquery/issues/detail?id=17 -// if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII') -// $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES'); - } - foreach($this->stack(1) as $node) { - if (! is_null($code)) { -// $attrNode = $this->DOM->createAttribute($attr); - $node->setAttribute($attr, $value); -// $attrNode->value = $value; -// $node->appendChild($attrNode); - } else if ( $attr == '*') { - // jQuery diff - $return = array(); - foreach($node->attributes as $n => $v) - $return[$n] = $v->value; - return $return; - } else - return $node->getAttribute($attr); - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function removeAttr($attr) { - foreach($this->stack(1) as $node) { - $loop = $attr == '*' - ? $this->getNodeAttrs($node) - : array($attr); - foreach($loop as $a) { - $oldValue = $node->getAttribute($a); - $node->removeAttribute($a); - $this->attrEvents($a, $oldValue, null, $node); - } - } - return $this; - } - /** - * Return form element value. - * - * @return String Fields value. - */ - public function val($val = null) { - if (! isset($val)) { - if ($this->eq(0)->is('select')) { - $selected = $this->eq(0)->find('option[selected=selected]'); - if ($selected->is('[value]')) - return $selected->attr('value'); - else - return $selected->text(); - } else if ($this->eq(0)->is('textarea')) - return $this->eq(0)->markup(); - else - return $this->eq(0)->attr('value'); - } else { - $_val = null; - foreach($this->stack(1) as $node) { - $node = pq($node, $this->getDocumentID()); - if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) { - $isChecked = in_array($node->attr('value'), $val) - || in_array($node->attr('name'), $val); - if ($isChecked) - $node->attr('checked', 'checked'); - else - $node->removeAttr('checked'); - } else if ($node->get(0)->tagName == 'select') { - if (! isset($_val)) { - $_val = array(); - if (! is_array($val)) - $_val = array((string)$val); - else - foreach($val as $v) - $_val[] = $v; - } - foreach($node['option']->stack(1) as $option) { - $option = pq($option, $this->getDocumentID()); - $selected = false; - // XXX: workaround for string comparsion, see issue #96 - // http://code.google.com/p/phpquery/issues/detail?id=96 - $selected = is_null($option->attr('value')) - ? in_array($option->markup(), $_val) - : in_array($option->attr('value'), $_val); -// $optionValue = $option->attr('value'); -// $optionText = $option->text(); -// $optionTextLenght = mb_strlen($optionText); -// foreach($_val as $v) -// if ($optionValue == $v) -// $selected = true; -// else if ($optionText == $v && $optionTextLenght == mb_strlen($v)) -// $selected = true; - if ($selected) - $option->attr('selected', 'selected'); - else - $option->removeAttr('selected'); - } - } else if ($node->get(0)->tagName == 'textarea') - $node->markup($val); - else - $node->attr('value', $val); - } - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function andSelf() { - if ( $this->previous ) - $this->elements = array_merge($this->elements, $this->previous->elements); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function addClass( $className) { - if (! $className) - return $this; - foreach($this->stack(1) as $node) { - if (! $this->is(".$className", $node)) - $node->setAttribute( - 'class', - trim($node->getAttribute('class').' '.$className) - ); - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function addClassPHP( $className) { - foreach($this->stack(1) as $node) { - $classes = $node->getAttribute('class'); - $newValue = $classes - ? $classes.' <'.'?php '.$className.' ?'.'>' - : '<'.'?php '.$className.' ?'.'>'; - $node->setAttribute('class', $newValue); - } - return $this; - } - /** - * Enter description here... - * - * @param string $className - * @return bool - */ - public function hasClass($className) { - foreach($this->stack(1) as $node) { - if ( $this->is(".$className", $node)) - return true; - } - return false; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function removeClass($className) { - foreach($this->stack(1) as $node) { - $classes = explode( ' ', $node->getAttribute('class')); - if ( in_array($className, $classes)) { - $classes = array_diff($classes, array($className)); - if ( $classes ) - $node->setAttribute('class', implode(' ', $classes)); - else - $node->removeAttribute('class'); - } - } - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function toggleClass($className) { - foreach($this->stack(1) as $node) { - if ( $this->is( $node, '.'.$className )) - $this->removeClass($className); - else - $this->addClass($className); - } - return $this; - } - /** - * Proper name without underscore (just ->empty()) also works. - * - * Removes all child nodes from the set of matched elements. - * - * Example: - * pq("p")._empty() - * - * HTML: - *

Hello, Person and person

- * - * Result: - * [

] - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @access private - */ - public function _empty() { - foreach($this->stack(1) as $node) { - // thx to 'dave at dgx dot cz' - $node->nodeValue = ''; - } - return $this; - } - /** - * Enter description here... - * - * @param array|string $callback Expects $node as first param, $index as second - * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope) - * @param array $arg1 Will ba passed as third and futher args to callback. - * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on... - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function each($callback, $param1 = null, $param2 = null, $param3 = null) { - $paramStructure = null; - if (func_num_args() > 1) { - $paramStructure = func_get_args(); - $paramStructure = array_slice($paramStructure, 1); - } - foreach($this->elements as $v) - phpQuery::callbackRun($callback, array($v), $paramStructure); - return $this; - } - /** - * Run callback on actual object. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function callback($callback, $param1 = null, $param2 = null, $param3 = null) { - $params = func_get_args(); - $params[0] = $this; - phpQuery::callbackRun($callback, $params); - return $this; - } - /** - * Enter description here... - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * @todo add $scope and $args as in each() ??? - */ - public function map($callback, $param1 = null, $param2 = null, $param3 = null) { -// $stack = array(); -//// foreach($this->newInstance() as $node) { -// foreach($this->newInstance() as $node) { -// $result = call_user_func($callback, $node); -// if ($result) -// $stack[] = $result; -// } - $params = func_get_args(); - array_unshift($params, $this->elements); - return $this->newInstance( - call_user_func_array(array('phpQuery', 'map'), $params) -// phpQuery::map($this->elements, $callback) - ); - } - /** - * Enter description here... - * - * @param $key - * @param $value - */ - public function data($key, $value = null) { - if (! isset($value)) { - // TODO? implement specific jQuery behavior od returning parent values - // is child which we look up doesn't exist - return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID()); - } else { - foreach($this as $node) - phpQuery::data($node, $key, $value, $this->getDocumentID()); - return $this; - } - } - /** - * Enter description here... - * - * @param $key - */ - public function removeData($key) { - foreach($this as $node) - phpQuery::removeData($node, $key, $this->getDocumentID()); - return $this; - } - // INTERFACE IMPLEMENTATIONS - - // ITERATOR INTERFACE - /** - * @access private - */ - public function rewind(){ - $this->debug('iterating foreach'); -// phpQuery::selectDocument($this->getDocumentID()); - $this->elementsBackup = $this->elements; - $this->elementsInterator = $this->elements; - $this->valid = isset( $this->elements[0] ) - ? 1 : 0; -// $this->elements = $this->valid -// ? array($this->elements[0]) -// : array(); - $this->current = 0; - } - /** - * @access private - */ - public function current(){ - return $this->elementsInterator[ $this->current ]; - } - /** - * @access private - */ - public function key(){ - return $this->current; - } - /** - * Double-function method. - * - * First: main iterator interface method. - * Second: Returning next sibling, alias for _next(). - * - * Proper functionality is choosed automagicaly. - * - * @see phpQueryObject::_next() - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - */ - public function next($cssSelector = null){ -// if ($cssSelector || $this->valid) -// return $this->_next($cssSelector); - $this->valid = isset( $this->elementsInterator[ $this->current+1 ] ) - ? true - : false; - if (! $this->valid && $this->elementsInterator) { - $this->elementsInterator = null; - } else if ($this->valid) { - $this->current++; - } else { - return $this->_next($cssSelector); - } - } - /** - * @access private - */ - public function valid(){ - return $this->valid; - } - // ITERATOR INTERFACE END - // ARRAYACCESS INTERFACE - /** - * @access private - */ - public function offsetExists($offset) { - return $this->find($offset)->size() > 0; - } - /** - * @access private - */ - public function offsetGet($offset) { - return $this->find($offset); - } - /** - * @access private - */ - public function offsetSet($offset, $value) { -// $this->find($offset)->replaceWith($value); - $this->find($offset)->html($value); - } - /** - * @access private - */ - public function offsetUnset($offset) { - // empty - throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML."); - } - // ARRAYACCESS INTERFACE END - /** - * Returns node's XPath. - * - * @param unknown_type $oneNode - * @return string - * @TODO use native getNodePath is avaible - * @access private - */ - protected function getNodeXpath($oneNode = null, $namespace = null) { - $return = array(); - $loop = $oneNode - ? array($oneNode) - : $this->elements; -// if ($namespace) -// $namespace .= ':'; - foreach($loop as $node) { - if ($node instanceof DOMDOCUMENT) { - $return[] = ''; - continue; - } - $xpath = array(); - while(! ($node instanceof DOMDOCUMENT)) { - $i = 1; - $sibling = $node; - while($sibling->previousSibling) { - $sibling = $sibling->previousSibling; - $isElement = $sibling instanceof DOMELEMENT; - if ($isElement && $sibling->tagName == $node->tagName) - $i++; - } - $xpath[] = $this->isXML() - ? "*[local-name()='{$node->tagName}'][{$i}]" - : "{$node->tagName}[{$i}]"; - $node = $node->parentNode; - } - $xpath = join('/', array_reverse($xpath)); - $return[] = '/'.$xpath; - } - return $oneNode - ? $return[0] - : $return; - } - // HELPERS - public function whois($oneNode = null) { - $return = array(); - $loop = $oneNode - ? array( $oneNode ) - : $this->elements; - foreach($loop as $node) { - if (isset($node->tagName)) { - $tag = in_array($node->tagName, array('php', 'js')) - ? strtoupper($node->tagName) - : $node->tagName; - $return[] = $tag - .($node->getAttribute('id') - ? '#'.$node->getAttribute('id'):'') - .($node->getAttribute('class') - ? '.'.join('.', split(' ', $node->getAttribute('class'))):'') - .($node->getAttribute('name') - ? '[name="'.$node->getAttribute('name').'"]':'') - .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false - ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'') - .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false - ? '[value=PHP]':'') - .($node->getAttribute('selected') - ? '[selected]':'') - .($node->getAttribute('checked') - ? '[checked]':'') - ; - } else if ($node instanceof DOMTEXT) { - if (trim($node->textContent)) - $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15); - } else { - - } - } - return $oneNode && isset($return[0]) - ? $return[0] - : $return; - } - /** - * Dump htmlOuter and preserve chain. Usefull for debugging. - * - * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery - * - */ - public function dump() { - print 'DUMP #'.(phpQuery::$dumpCount++).' '; - $debug = phpQuery::$debug; - phpQuery::$debug = false; -// print __FILE__.':'.__LINE__."\n"; - var_dump($this->htmlOuter()); - return $this; - } - public function dumpWhois() { - print 'DUMP #'.(phpQuery::$dumpCount++).' '; - $debug = phpQuery::$debug; - phpQuery::$debug = false; -// print __FILE__.':'.__LINE__."\n"; - var_dump('whois', $this->whois()); - phpQuery::$debug = $debug; - return $this; - } - public function dumpLength() { - print 'DUMP #'.(phpQuery::$dumpCount++).' '; - $debug = phpQuery::$debug; - phpQuery::$debug = false; -// print __FILE__.':'.__LINE__."\n"; - var_dump('length', $this->length()); - phpQuery::$debug = $debug; - return $this; - } - public function dumpTree($html = true, $title = true) { - $output = $title - ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : ''; - $debug = phpQuery::$debug; - phpQuery::$debug = false; - foreach($this->stack() as $node) - $output .= $this->__dumpTree($node); - phpQuery::$debug = $debug; - print $html - ? nl2br(str_replace(' ', ' ', $output)) - : $output; - return $this; - } - private function __dumpTree($node, $intend = 0) { - $whois = $this->whois($node); - $return = ''; - if ($whois) - $return .= str_repeat(' - ', $intend).$whois."\n"; - if (isset($node->childNodes)) - foreach($node->childNodes as $chNode) - $return .= $this->__dumpTree($chNode, $intend+1); - return $return; - } - /** - * Dump htmlOuter and stop script execution. Usefull for debugging. - * - */ - public function dumpDie() { - print __FILE__.':'.__LINE__; - var_dump($this->htmlOuter()); - die(); - } -} diff --git a/phpQuery/phpQuery/plugins/Scripts.php b/phpQuery/phpQuery/plugins/Scripts.php deleted file mode 100644 index cc4dd6f..0000000 --- a/phpQuery/phpQuery/plugins/Scripts.php +++ /dev/null @@ -1,72 +0,0 @@ - \ No newline at end of file diff --git a/phpQuery/phpQuery/plugins/Scripts/__config.example.php b/phpQuery/phpQuery/plugins/Scripts/__config.example.php deleted file mode 100644 index db80652..0000000 --- a/phpQuery/phpQuery/plugins/Scripts/__config.example.php +++ /dev/null @@ -1,10 +0,0 @@ - array('login@mail', 'password'), -); -?> \ No newline at end of file diff --git a/phpQuery/phpQuery/plugins/Scripts/example.php b/phpQuery/phpQuery/plugins/Scripts/example.php deleted file mode 100644 index a8f45f4..0000000 --- a/phpQuery/phpQuery/plugins/Scripts/example.php +++ /dev/null @@ -1,14 +0,0 @@ -find($params[0]); -?> \ No newline at end of file diff --git a/phpQuery/phpQuery/plugins/Scripts/fix_webroot.php b/phpQuery/phpQuery/plugins/Scripts/fix_webroot.php deleted file mode 100644 index a7cac9e..0000000 --- a/phpQuery/phpQuery/plugins/Scripts/fix_webroot.php +++ /dev/null @@ -1,16 +0,0 @@ -filter($filter) as $el) { - $el = pq($el, $self->getDocumentID()); - // imgs and scripts - if ( $el->is('img') || $el->is('script') ) - $el->attr('src', $params[0].$el->attr('src')); - // css - if ( $el->is('link') ) - $el->attr('href', $params[0].$el->attr('href')); -} \ No newline at end of file diff --git a/phpQuery/phpQuery/plugins/Scripts/google_login.php b/phpQuery/phpQuery/plugins/Scripts/google_login.php deleted file mode 100644 index 0327045..0000000 --- a/phpQuery/phpQuery/plugins/Scripts/google_login.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ -phpQuery::ajaxAllowHost( - 'code.google.com', - 'google.com', 'www.google.com', - 'mail.google.com', - 'docs.google.com', - 'reader.google.com' -); -if (! function_exists('ndfasui8923')) { - function ndfasui8923($browser, $scope) { - extract($scope); - $browser - ->WebBrowser() - ->find('#Email') - ->val($config['google_login'][0])->end() - ->find('#Passwd') - ->val($config['google_login'][1]) - ->parents('form') - ->submit(); - } - $ndfasui8923 = new Callback('ndfasui8923', new CallbackParam, compact( - 'config', 'self', 'return', 'params' - )); -} -phpQuery::plugin('WebBrowser'); -$self->document->xhr = phpQuery::$plugins->browserGet( - 'https://www.google.com/accounts/Login', - $ndfasui8923 -); -//$self->document->xhr = phpQuery::$plugins->browserGet('https://www.google.com/accounts/Login', create_function('$browser', " -// \$browser -// ->WebBrowser() -// ->find('#Email') -// ->val('{$config['google_login'][0]}')->end() -// ->find('#Passwd') -// ->val('".str_replace("'", "\\'", $config['google_login'][1])."') -// ->parents('form') -// ->submit();" -//)); -?> \ No newline at end of file diff --git a/phpQuery/phpQuery/plugins/Scripts/print_source.php b/phpQuery/phpQuery/plugins/Scripts/print_source.php deleted file mode 100644 index 03d8061..0000000 --- a/phpQuery/phpQuery/plugins/Scripts/print_source.php +++ /dev/null @@ -1,9 +0,0 @@ - - */ -/** @var phpQueryObject */ -$self = $self; -$return = htmlspecialchars($self); \ No newline at end of file diff --git a/phpQuery/phpQuery/plugins/Scripts/print_websafe.php b/phpQuery/phpQuery/plugins/Scripts/print_websafe.php deleted file mode 100644 index 4f48bd7..0000000 --- a/phpQuery/phpQuery/plugins/Scripts/print_websafe.php +++ /dev/null @@ -1,13 +0,0 @@ - - */ -/** @var phpQueryObject */ -$self = $self; -$self - ->find('script') - ->add('meta[http-equiv=refresh]') - ->add('meta[http-equiv=Refresh]') - ->remove(); \ No newline at end of file diff --git a/phpQuery/phpQuery/plugins/WebBrowser.php b/phpQuery/phpQuery/plugins/WebBrowser.php deleted file mode 100644 index 6688d3f..0000000 --- a/phpQuery/phpQuery/plugins/WebBrowser.php +++ /dev/null @@ -1,405 +0,0 @@ -_clone()->toRoot(); - $location = $location - ? $location - // TODO use document.location - : $self->document->xhr->getUri(true); - // FIXME tmp - $self->document->WebBrowserCallback = $callback; - if (! $location) - throw new Exception('Location needed to activate WebBrowser plugin !'); - else { - $self->bind('click', array($location, $callback), array('phpQueryPlugin_WebBrowser', 'hadleClick')); - $self->bind('submit', array($location, $callback), array('phpQueryPlugin_WebBrowser', 'handleSubmit')); - } - } - public static function browser($self, $callback = null, $location = null) { - return $self->WebBrowser($callback, $location); - } - public static function downloadTo($self, $dir = null, $filename = null) { - $url = null; - if ($self->is('a[href]')) - $url = $self->attr('href'); - else if ($self->find('a')->length) - $url = $self->find('a')->attr('href'); - if ($url) { - $url = resolve_url($self->document->location, $url); - if (! $dir) - $dir = getcwd(); - // TODO resolv name from response headers - if (! $filename) { - $matches = null; - preg_match('@/([^/]+)$@', $url, $matches); - $filename = $matches[1]; - } - //print $url; - $path = rtrim($dir, '/').'/'.$filename; - phpQuery::debug("Requesting download of $url\n"); - // TODO use AJAX instead of file_get_contents - file_put_contents($path, file_get_contents($url)); - } - return $self; - } - /** - * Method changing browser location. - * Fires callback registered with WebBrowser(), if any. - * @param $self - * @param $url - * @return unknown_type - */ - public static function location($self, $url = null) { - // TODO if ! $url return actual location ??? - $xhr = isset($self->document->xhr) - ? $self->document->xhr - : null; - $xhr = phpQuery::ajax(array( - 'url' => $url, - ), $xhr); - $return = false; - if ($xhr->getLastResponse()->isSuccessful()) { - $return = phpQueryPlugin_WebBrowser::browserReceive($xhr); - if (isset($self->document->WebBrowserCallback)) - phpQuery::callbackRun( - $self->document->WebBrowserCallback, - array($return) - ); - } - return $return; - } -} -class phpQueryPlugin_WebBrowser { - /** - * - * @param $url - * @param $callback - * @param $param1 - * @param $param2 - * @param $param3 - * @return Zend_Http_Client - */ - public static function browserGet($url, $callback, - $param1 = null, $param2 = null, $param3 = null) { - phpQuery::debug("[WebBrowser] GET: $url"); - self::authorizeHost($url); - $xhr = phpQuery::ajax(array( - 'type' => 'GET', - 'url' => $url, - 'dataType' => 'html', - )); - $paramStructure = null; - if (func_num_args() > 2) { - $paramStructure = func_get_args(); - $paramStructure = array_slice($paramStructure, 2); - } - if ($xhr->getLastResponse()->isSuccessful()) { - phpQuery::callbackRun($callback, - array(self::browserReceive($xhr)->WebBrowser()), - $paramStructure - ); -// phpQuery::callbackRun($callback, array( -// self::browserReceive($xhr)//->WebBrowser($callback) -// )); - return $xhr; - } else { - throw new Exception("[WebBrowser] GET request failed; url: $url"); - return false; - } - } - /** - * - * @param $url - * @param $data - * @param $callback - * @param $param1 - * @param $param2 - * @param $param3 - * @return Zend_Http_Client - */ - public static function browserPost($url, $data, $callback, - $param1 = null, $param2 = null, $param3 = null) { - self::authorizeHost($url); - $xhr = phpQuery::ajax(array( - 'type' => 'POST', - 'url' => $url, - 'dataType' => 'html', - 'data' => $data, - )); - $paramStructure = null; - if (func_num_args() > 3) { - $paramStructure = func_get_args(); - $paramStructure = array_slice($paramStructure, 3); - } - if ($xhr->getLastResponse()->isSuccessful()) { - phpQuery::callbackRun($callback, - array(self::browserReceive($xhr)->WebBrowser()), - $paramStructure - ); -// phpQuery::callbackRun($callback, array( -// self::browserReceive($xhr)//->WebBrowser($callback) -// )); - return $xhr; - } else - return false; - } - /** - * - * @param $ajaxSettings - * @param $callback - * @param $param1 - * @param $param2 - * @param $param3 - * @return Zend_Http_Client - */ - public static function browser($ajaxSettings, $callback, - $param1 = null, $param2 = null, $param3 = null) { - self::authorizeHost($ajaxSettings['url']); - $xhr = phpQuery::ajax( - self::ajaxSettingsPrepare($ajaxSettings) - ); - $paramStructure = null; - if (func_num_args() > 2) { - $paramStructure = func_get_args(); - $paramStructure = array_slice($paramStructure, 2); - } - if ($xhr->getLastResponse()->isSuccessful()) { - phpQuery::callbackRun($callback, - array(self::browserReceive($xhr)->WebBrowser()), - $paramStructure - ); -// phpQuery::callbackRun($callback, array( -// self::browserReceive($xhr)//->WebBrowser($callback) -// )); - return $xhr; - } else - return false; - } - protected static function authorizeHost($url) { - $host = parse_url($url, PHP_URL_HOST); - if ($host) - phpQuery::ajaxAllowHost($host); - } - protected static function ajaxSettingsPrepare($settings) { - unset($settings['success']); - unset($settings['error']); - return $settings; - } - /** - * @param Zend_Http_Client $xhr - */ - public static function browserReceive($xhr) { - phpQuery::debug("[WebBrowser] Received from ".$xhr->getUri(true)); - // TODO handle meta redirects - $body = $xhr->getLastResponse()->getBody(); - - // XXX error ??? - if (strpos($body, '') !== false) { - $body = '' - .str_replace('', '', $body) - .''; - } - $pq = phpQuery::newDocument($body); - $pq->document->xhr = $xhr; - $pq->document->location = $xhr->getUri(true); - $refresh = $pq->find('meta[http-equiv=refresh]') - ->add('meta[http-equiv=Refresh]'); - if ($refresh->size()) { -// print htmlspecialchars(var_export($xhr->getCookieJar()->getAllCookies(), true)); -// print htmlspecialchars(var_export($xhr->getLastResponse()->getHeader('Set-Cookie'), true)); - phpQuery::debug("Meta redirect... '{$refresh->attr('content')}'\n"); - // there is a refresh, so get the new url - $content = $refresh->attr('content'); - $urlRefresh = substr($content, strpos($content, '=')+1); - $urlRefresh = trim($urlRefresh, '\'"'); - // XXX not secure ?! - phpQuery::ajaxAllowURL($urlRefresh); -// $urlRefresh = urldecode($urlRefresh); - // make ajax call, passing last $xhr object to preserve important stuff - $xhr = phpQuery::ajax(array( - 'type' => 'GET', - 'url' => $urlRefresh, - 'dataType' => 'html', - ), $xhr); - if ($xhr->getLastResponse()->isSuccessful()) { - // if all is ok, repeat this method... - return call_user_func_array( - array('phpQueryPlugin_WebBrowser', 'browserReceive'), array($xhr) - ); - } - } else - return $pq; - } - /** - * - * @param $e - * @param $callback - * @return unknown_type - */ - public static function hadleClick($e, $callback = null) { - $node = phpQuery::pq($e->target); - $type = null; - if ($node->is('a[href]')) { - // TODO document.location - $xhr = isset($node->document->xhr) - ? $node->document->xhr - : null; - $xhr = phpQuery::ajax(array( - 'url' => resolve_url($e->data[0], $node->attr('href')), - 'referer' => $node->document->location, - ), $xhr); - if ((! $callback || !($callback instanceof Callback)) && $e->data[1]) - $callback = $e->data[1]; - if ($xhr->getLastResponse()->isSuccessful() && $callback) - phpQuery::callbackRun($callback, array( - self::browserReceive($xhr) - )); - } else if ($node->is(':submit') && $node->parents('form')->size()) - $node->parents('form')->trigger('submit', array($e)); - } - /** - * Enter description here... - * - * @param unknown_type $e - * @TODO trigger submit for form after form's submit button has a click event - */ - public static function handleSubmit($e, $callback = null) { - $node = phpQuery::pq($e->target); - if (!$node->is('form') || !$node->is('[action]')) - return; - // TODO document.location - $xhr = isset($node->document->xhr) - ? $node->document->xhr - : null; - $submit = pq($e->relatedTarget)->is(':submit') - ? $e->relatedTarget - // will this work ? -// : $node->find(':submit:first')->get(0); - : $node->find('*:submit:first')->get(0); - $data = array(); - foreach($node->serializeArray($submit) as $r) - // XXXt.c maybe $node->not(':submit')->add($sumit) would be better ? -// foreach($node->serializeArray($submit) as $r) - $data[ $r['name'] ] = $r['value']; - $options = array( - 'type' => $node->attr('method') - ? $node->attr('method') - : 'GET', - 'url' => resolve_url($e->data[0], $node->attr('action')), - 'data' => $data, - 'referer' => $node->document->location, -// 'success' => $e->data[1], - ); - if ($node->attr('enctype')) - $options['contentType'] = $node->attr('enctype'); - $xhr = phpQuery::ajax($options, $xhr); - if ((! $callback || !($callback instanceof Callback)) && $e->data[1]) - $callback = $e->data[1]; - if ($xhr->getLastResponse()->isSuccessful() && $callback) - phpQuery::callbackRun($callback, array( - self::browserReceive($xhr) - )); - } -} -/** - * - * @param unknown_type $parsed - * @return unknown - * @link http://www.php.net/manual/en/function.parse-url.php - * @author stevenlewis at hotmail dot com - */ -function glue_url($parsed) - { - if (! is_array($parsed)) return false; - $uri = isset($parsed['scheme']) ? $parsed['scheme'].':'.((strtolower($parsed['scheme']) == 'mailto') ? '':'//'): ''; - $uri .= isset($parsed['user']) ? $parsed['user'].($parsed['pass']? ':'.$parsed['pass']:'').'@':''; - $uri .= isset($parsed['host']) ? $parsed['host'] : ''; - $uri .= isset($parsed['port']) ? ':'.$parsed['port'] : ''; - if(isset($parsed['path'])) - { - $uri .= (substr($parsed['path'],0,1) == '/')?$parsed['path']:'/'.$parsed['path']; - } - $uri .= isset($parsed['query']) ? '?'.$parsed['query'] : ''; - $uri .= isset($parsed['fragment']) ? '#'.$parsed['fragment'] : ''; - return $uri; - } -/** - * Enter description here... - * - * @param unknown_type $base - * @param unknown_type $url - * @return unknown - * @author adrian-php at sixfingeredman dot net - */ -function resolve_url($base, $url) { - if (!strlen($base)) return $url; - // Step 2 - if (!strlen($url)) return $base; - // Step 3 - if (preg_match('!^[a-z]+:!i', $url)) return $url; - $base = parse_url($base); - if ($url{0} == "#") { - // Step 2 (fragment) - $base['fragment'] = substr($url, 1); - return unparse_url($base); - } - unset($base['fragment']); - unset($base['query']); - if (substr($url, 0, 2) == "//") { - // Step 4 - return unparse_url(array( - 'scheme'=>$base['scheme'], - 'path'=>substr($url,2), - )); - } else if ($url{0} == "/") { - // Step 5 - $base['path'] = $url; - } else { - // Step 6 - $path = explode('/', $base['path']); - $url_path = explode('/', $url); - // Step 6a: drop file from base - array_pop($path); - // Step 6b, 6c, 6e: append url while removing "." and ".." from - // the directory portion - $end = array_pop($url_path); - foreach ($url_path as $segment) { - if ($segment == '.') { - // skip - } else if ($segment == '..' && $path && $path[sizeof($path)-1] != '..') { - array_pop($path); - } else { - $path[] = $segment; - } - } - // Step 6d, 6f: remove "." and ".." from file portion - if ($end == '.') { - $path[] = ''; - } else if ($end == '..' && $path && $path[sizeof($path)-1] != '..') { - $path[sizeof($path)-1] = ''; - } else { - $path[] = $end; - } - // Step 6h - $base['path'] = join('/', $path); - - } - // Step 7 - return glue_url($base); -} diff --git a/phpQuery/phpQuery/plugins/example.php b/phpQuery/phpQuery/plugins/example.php deleted file mode 100644 index 732f05c..0000000 --- a/phpQuery/phpQuery/plugins/example.php +++ /dev/null @@ -1,75 +0,0 @@ -plugin('example') - * pq('ul')->plugin('example', 'example.php') - * - * Plugin classes are never intialized, just method calls are forwarded - * in static way from phpQuery. - * - * Have fun writing plugins :) - */ - -/** - * phpQuery plugin class extending phpQuery object. - * Methods from this class are callable on every phpQuery object. - * - * Class name prefix 'phpQueryObjectPlugin_' must be preserved. - */ -abstract class phpQueryObjectPlugin_example { - /** - * Limit binded methods. - * - * null means all public. - * array means only specified ones. - * - * @var array|null - */ - public static $phpQueryMethods = null; - /** - * Enter description here... - * - * @param phpQueryObject $self - */ - public static function example($self, $arg1) { - // this method can be called on any phpQuery object, like this: - // pq('div')->example('$arg1 Value') - - // do something - $self->append('Im just an example !'); - // change stack of result object - return $self->find('div'); - } - protected static function helperFunction() { - // this method WONT be avaible as phpQuery method, - // because it isn't publicly callable - } -} - -/** - * phpQuery plugin class extending phpQuery static namespace. - * Methods from this class are callable as follows: - * phpQuery::$plugins->staticMethod() - * - * Class name prefix 'phpQueryPlugin_' must be preserved. - */ -abstract class phpQueryPlugin_example { - /** - * Limit binded methods. - * - * null means all public. - * array means only specified ones. - * - * @var array|null - */ - public static $phpQueryMethods = null; - public static function staticMethod() { - // this method can be called within phpQuery class namespace, like this: - // phpQuery::$plugins->staticMethod() - } -} -?> \ No newline at end of file