commit a832da15d12d40118d1808ab82e4633a641859f4 Author: JAE Date: Thu Apr 3 11:03:54 2014 +0800 Jaeger diff --git a/QueryList.class.php b/QueryList.class.php new file mode 100644 index 0000000..7ea4840 --- /dev/null +++ b/QueryList.class.php @@ -0,0 +1,209 @@ +array("选择器","类型"),.......),【类型】说明:值 "text" ,"html" ,"属性" + * @param string $regRange 【块选择器】:指 先按照规则 选出 几个大块 ,然后再分别再在块里面 进行相关的选择 + * @param string $getHtmlWay 【源码获取方式】指是通过curl抓取源码,还是通过file_get_contents抓取源码 + * @param string $output_encoding【输出编码格式】指要以什么编码输出,防止出现乱码,如果设置为 假值 则不改变原字符串编码 + */ + function QueryList($page,$regArr=array(),$regRange='',$getHtmlWay="curl",$output_encoding="UTF-8") + { + + $this->output_encoding = $output_encoding; + if($this->isURL($page)) + { + $this->pageURL = $page; + if($getHtmlWay=="curl") + { + //为了能获取https:// + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL,$this->pageURL); + 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($this->pageURL); + } + }else{ + $this->html = $page; + } + + //获取编码格式 + $this->html_encoding = $this->get_encode($this->html); + + + if(!empty($regArr)) + { + + $this->regArr = $regArr; + $this->regRange = $regRange; + $this->getList(); + } + + } + function setQuery($regArr,$regRange='') + { + $this->jsonArr=array(); + $this->regArr = $regArr; + $this->regRange = $regRange; + $this->getList(); + } + private function getList() + { + + $hobj = phpQuery::newDocumentHTML($this->html); + + if(!empty($this->regRange)) + { + $robj = pq($hobj)->find($this->regRange); + + $i=0; + foreach($robj as $item) + { + + while(list($key,$reg_value)=each($this->regArr)) + { + $iobj = pq($item)->find($reg_value[0]); + + switch($reg_value[1]) + { + case 'text': + $this->jsonArr[$i][$key] = trim(pq($iobj)->text()); + break; + case 'html': + $this->jsonArr[$i][$key] = trim(pq($iobj)->html()); + break; + default: + $this->jsonArr[$i][$key] = pq($iobj)->attr($reg_value[1]); + break; + + } + } + //重置数组指针 + reset($this->regArr); + $i++; + } + } + else + { + while(list($key,$reg_value)=each($this->regArr)) + { + $lobj = pq($hobj)->find($reg_value[0]); + + + $i=0; + foreach($lobj as $item) + { + switch($reg_value[1]) + { + case 'text': + $this->jsonArr[$i++][$key] = trim(pq($item)->text()); + break; + case 'html': + $this->jsonArr[$i++][$key] = trim(pq($item)->html()); + break; + default: + $this->jsonArr[$i++][$key] = pq($item)->attr($reg_value[1]); + break; + + } + + + } + + + } + } + if($this->output_encoding) + { + //编码转换 + $this->jsonArr = $this->array_convert_encoding($this->jsonArr,$this->output_encoding,$this->html_encoding); + } + } + function getJSON() + { + return json_encode($this->jsonArr); + } + /** + * 获取文件编码 + * @param $string + * @return string + */ + private function get_encode($string){ + return mb_detect_encoding($string, array('ASCII','GB2312','GBK','UTF-8')); + } + /** + * 递归转换数组值得编码格式 + * @param array $arr + * @param string $to_encoding + * @param string $from_encoding + * @return array + */ + private function array_convert_encoding($arr,$to_encoding,$from_encoding) + { + if(!is_array($arr))return $arr; + foreach ($arr as $key => $value) { + if (is_array($value)) { + $arr[$key] = $this->array_convert_encoding($value,$to_encoding,$from_encoding); + }else{ + $arr[$key] = mb_convert_encoding($value, $to_encoding,$from_encoding); + } + } + return $arr; + } + /** + * 简单的判断一下参数是否为一个URL链接 + * @param string $str + * @return boolean + */ + private function isURL($str) + { + if(preg_match('/^http(s)?:\/\/.+/', $str)) + { + return true; + } + return false; + } + +} + // $hj = new QueryList("http://www.baidu.com/s?rn=20&ie=utf-8&bs=love+me&f=8&rsv_bp=1&wd=%E4%B8%83%E9%87%8C%E9%A6%99&rsv_sug3=2&rsv_sug=0&rsv_sug1=2&rsv_sug4=111&inputT=2224",array("title"=>array("h3.t a","text"),"url"=>array("h3.t a","href"),"con"=>array("div.c-abstract","html")),"table.result"); + //$hj=new QueryList("https://www.google.com.hk/search?filter=0&lr=&newwindow=1&safe=images&hl=en&as_qdr=all&q=QQ2014",array("hcon"=>array(".st","html"))); + // print_r($hj->getJSON()); + // print_r($hj->jsonArr); +// $hj = new QueryList('https://www.google.com.hk/search?filter=0&lr=&newwindow=1&safe=images&hl=en&as_qdr=all&q=QQ2014',array("url"=>array("h3.r a","href"))); +// print_r($hj->jsonArr); +// $hj->setQuery(array("hcon"=>array("span.st","text"))); +// print_r($hj->jsonArr); +/* +$ct = new QueryList("http://www.mianbao.com/cartoon/",array("title"=>array("a:eq(0)","text")),"ul.txt-list li","get"); +print_r($ct->jsonArr);*/ + +/*$hj = new QueryList("http://t.sohu.com/jingxuan",array("con"=>array(".ugc","html")),"[id$=_con]","get","UTF-8"); +print_r($hj->jsonArr);*/ + +/*$html = file_get_contents('http://www.baidu.com/s?rn=20&ie=utf-8&bs=love+me&f=8&rsv_bp=1&wd=%E4%B8%83%E9%87%8C%E9%A6%99&rsv_sug3=2&rsv_sug=0&rsv_sug1=2&rsv_sug4=111&inputT=2224'); +$hj = new QueryList($html,array("title"=>array("h3.t a","text"),"url"=>array("h3.t a","href"),"con"=>array("div.c-abstract","html")),"table.result"); +print_r($hj->jsonArr);*/ +?> \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..35c3607 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ + #QueryList交流QQ群:123266961 ╰☆邪恶 魔方☆ #QueryList简介 *** QueryList是一个基于phpQuery的通用列表采集类,是一个简单、 灵活、强大的采集工具,采集任何复杂的页面 基本上就一句话就能搞定了。 #QueryList 使用 ```php //实例化一个采集对象 $hj = new QueryList('http://www.baidu.com/s?wd=jaekj',array('title'=>array('h3','text'))); //输出结果:二维关联数组 print_r($hj->jsonArr); //输出结果:JSON数据 echo $hj->getJSON(); ``` 上面的代码实现的功能是采集百度搜索结果页面的所有搜索结果的标题,然后分别以数组和JSON格式输出。 ###QueryList 构造函数原型: >***QueryList***($page,$regArr,$regRange='',$getHtmlWay="curl",$output_encoding=false) 一共有五个参数,后面三个参数是可选的 * *$page* 要抓取的网页URL地址(默认支持https);或者是html源代码 * *$regArr* 【选择器数组】说明:格式array("名称"=>array("选择器","类型"),.......),【类型】说明:值 "text" ,"html" ,"属性" * *$regRange* 【块选择器】:指 先按照规则 选出 几个大块 ,然后再分别再在块里面 进行相关的选择 * *$getHtmlWay* 【源码获取方式】指是通过curl抓取源码,还是通过file\_get_contents抓取源码,当$page参数为URL时此参数才有效 * *$output_encoding*【输出编码格式】指要以什么编码输出(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则不改变原字符串编码 ###QueryList 属性 * **得到多维数组格式的采集结果** >***jsonArr*** ###QueryList 方法 * **重新设置选择器** >void ***setQuery***($regArr,$regRange='') 一共两个参数,第二个参数是可选的,参数意义同构造函数。 * **得到JSON格式的采集结果** > string ***getJSON***() 无参,返回JSON字符串。 ##QueryList 依赖库 ``` phpQuery ``` phpQuery项目主页:[https://code.google.com/p/phpquery/](https://code.google.com/p/phpquery/) ##其它说明 ``` Author : Jaeger Email : hj.q@qq.com 交流QQ群:123266961 ``` \ No newline at end of file diff --git a/demo/Searcher.class.php b/demo/Searcher.class.php new file mode 100644 index 0000000..442a102 --- /dev/null +++ b/demo/Searcher.class.php @@ -0,0 +1,113 @@ +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")); + } + $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'; + } + $searcherObj = new QueryList($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); + + + } + 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 = new Searcher('baidu','site:pan.baidu.com torrent',20,2); + print_r( $hj->jsonArr); diff --git a/demo/demo.php b/demo/demo.php new file mode 100644 index 0000000..7b6a119 --- /dev/null +++ b/demo/demo.php @@ -0,0 +1,28 @@ +array(".code_title a:eq(0)","text"),"url"=>array(".code_title a:eq(0)","href"),"author"=>array("img","title")); +$rang = ".code_list li"; +//使用curl抓取源码并以GB2312编码格式输出 +$hj = new QueryList($url,$reg,$rang,'curl','GB2312'); +$arr = $hj->jsonArr; +echo "
";
+print_r($arr);
+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 = new QueryList($url,$reg); +$arr = $hj->jsonArr; +echo "
";
+print_r($arr);
+echo "

"; \ No newline at end of file diff --git a/phpQuery/phpQuery.php b/phpQuery/phpQuery.php new file mode 100644 index 0000000..5d214ad --- /dev/null +++ b/phpQuery/phpQuery.php @@ -0,0 +1,1342 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package phpQuery + */ + +// class names for instanceof +// TODO move them as class constants into phpQuery +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'); +/** + * Static namespace for phpQuery functions. + * + * @author Tobiasz Cudnik + * @package phpQuery + */ +abstract class phpQuery { + /** + * XXX: Workaround for mbstring problems + * + * @var bool + */ + public static $mbstringSupport = true; + public static $debug = false; + public static $documents = array(); + public static $defaultDocumentID = null; +// public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'; + /** + * Applies only to HTML. + * + * @var unknown_type + */ + public static $defaultDoctype = ''; + public static $defaultCharset = 'UTF-8'; + /** + * Static namespace for plugins. + * + * @var object + */ + public static $plugins = array(); + /** + * List of loaded plugins. + * + * @var unknown_type + */ + public static $pluginsLoaded = array(); + public static $pluginsMethods = array(); + public static $pluginsStaticMethods = array(); + public static $extendMethods = array(); + /** + * @TODO implement + */ + public static $extendStaticMethods = array(); + /** + * Hosts allowed for AJAX connections. + * Dot '.' means $_SERVER['HTTP_HOST'] (if any). + * + * @var array + */ + public static $ajaxAllowedHosts = array( + '.' + ); + /** + * AJAX settings. + * + * @var array + * XXX should it be static or not ? + */ + public static $ajaxSettings = array( + 'url' => '',//TODO + 'global' => true, + 'type' => "GET", + 'timeout' => null, + 'contentType' => "application/x-www-form-urlencoded", + 'processData' => true, +// 'async' => true, + 'data' => null, + 'username' => null, + 'password' => null, + 'accepts' => array( + 'xml' => "application/xml, text/xml", + 'html' => "text/html", + 'script' => "text/javascript, application/javascript", + 'json' => "application/json, text/javascript", + 'text' => "text/plain", + '_default' => "*/*" + ) + ); + public static $lastModified = null; + public static $active = 0; + public static $dumpCount = 0; + /** + * Multi-purpose function. + * Use pq() as shortcut. + * + * In below examples, $pq is any result of pq(); function. + * + * 1. Import markup into existing document (without any attaching): + * - Import into selected document: + * pq('
') // DOESNT accept text nodes at beginning of input string ! + * - Import into document with ID from $pq->getDocumentID(): + * pq('
', $pq->getDocumentID()) + * - Import into same document as DOMNode belongs to: + * pq('
', DOMNode) + * - Import into document from phpQuery object: + * pq('
', $pq) + * + * 2. Run query: + * - Run query on last selected document: + * pq('div.myClass') + * - Run query on document with ID from $pq->getDocumentID(): + * pq('div.myClass', $pq->getDocumentID()) + * - Run query on same document as DOMNode belongs to and use node(s)as root for query: + * pq('div.myClass', DOMNode) + * - Run query on document from phpQuery object + * and use object's stack as root node(s) for query: + * pq('div.myClass', $pq) + * + * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes + * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root) + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false + * phpQuery object or false in case of error. + */ + public static function pq($arg1, $context = null) { + if ($arg1 instanceof DOMNODE && ! isset($context)) { + foreach(phpQuery::$documents as $documentWrapper) { + $compare = $arg1 instanceof DOMDocument + ? $arg1 : $arg1->ownerDocument; + if ($documentWrapper->document->isSameNode($compare)) + $context = $documentWrapper->id; + } + } + if (! $context) { + $domId = self::$defaultDocumentID; + if (! $domId) + throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first."); +// } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) + } else if (is_object($context) && $context instanceof phpQueryObject) + $domId = $context->getDocumentID(); + else if ($context instanceof DOMDOCUMENT) { + $domId = self::getDocumentID($context); + if (! $domId) { + //throw new Exception('Orphaned DOMDocument'); + $domId = self::newDocument($context)->getDocumentID(); + } + } else if ($context instanceof DOMNODE) { + $domId = self::getDocumentID($context); + if (! $domId) { + throw new Exception('Orphaned DOMNode'); +// $domId = self::newDocument($context->ownerDocument); + } + } else + $domId = $context; + if ($arg1 instanceof phpQueryObject) { +// if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) { + /** + * Return $arg1 or import $arg1 stack if document differs: + * pq(pq('
')) + */ + if ($arg1->getDocumentID() == $domId) + return $arg1; + $class = get_class($arg1); + // support inheritance by passing old object to overloaded constructor + $phpQuery = $class != 'phpQuery' + ? new $class($arg1, $domId) + : new phpQueryObject($domId); + $phpQuery->elements = array(); + foreach($arg1->elements as $node) + $phpQuery->elements[] = $phpQuery->document->importNode($node, true); + return $phpQuery; + } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) { + /* + * Wrap DOM nodes with phpQuery object, import into document when needed: + * pq(array($domNode1, $domNode2)) + */ + $phpQuery = new phpQueryObject($domId); + if (!($arg1 instanceof DOMNODELIST) && ! is_array($arg1)) + $arg1 = array($arg1); + $phpQuery->elements = array(); + foreach($arg1 as $node) { + $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT + && ! $node->ownerDocument->isSameNode($phpQuery->document); + $phpQuery->elements[] = $sameDocument + ? $phpQuery->document->importNode($node, true) + : $node; + } + return $phpQuery; + } else if (self::isMarkup($arg1)) { + /** + * Import HTML: + * pq('
') + */ + $phpQuery = new phpQueryObject($domId); + return $phpQuery->newInstance( + $phpQuery->documentWrapper->import($arg1) + ); + } else { + /** + * Run CSS query: + * pq('div.myClass') + */ + $phpQuery = new phpQueryObject($domId); +// if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) + if ($context && $context instanceof phpQueryObject) + $phpQuery->elements = $context->elements; + else if ($context && $context instanceof DOMNODELIST) { + $phpQuery->elements = array(); + foreach($context as $node) + $phpQuery->elements[] = $node; + } else if ($context && $context instanceof DOMNODE) + $phpQuery->elements = array($context); + return $phpQuery->find($arg1); + } + } + /** + * Sets default document to $id. Document has to be loaded prior + * to using this method. + * $id can be retrived via getDocumentID() or getDocumentIDRef(). + * + * @param unknown_type $id + */ + public static function selectDocument($id) { + $id = self::getDocumentID($id); + self::debug("Selecting document '$id' as default one"); + self::$defaultDocumentID = self::getDocumentID($id); + } + /** + * Returns document with id $id or last used as phpQueryObject. + * $id can be retrived via getDocumentID() or getDocumentIDRef(). + * Chainable. + * + * @see phpQuery::selectDocument() + * @param unknown_type $id + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function getDocument($id = null) { + if ($id) + phpQuery::selectDocument($id); + else + $id = phpQuery::$defaultDocumentID; + return new phpQueryObject($id); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocument($markup = null, $contentType = null) { + if (! $markup) + $markup = ''; + $documentID = phpQuery::createDocumentWrapper($markup, $contentType); + return new phpQueryObject($documentID); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentHTML($markup = null, $charset = null) { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocument($markup, "text/html{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentXML($markup = null, $charset = null) { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocument($markup, "text/xml{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentXHTML($markup = null, $charset = null) { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocument($markup, "application/xhtml+xml{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentPHP($markup = null, $contentType = "text/html") { + // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function) + $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset); + return self::newDocument($markup, $contentType); + } + public static function phpToMarkup($php, $charset = 'utf-8') { + $regexes = array( + '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<'.'?php?(.*?)(?:\\?>)([^\']*)\'@s', + '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<'.'?php?(.*?)(?:\\?>)([^"]*)"@s', + ); + foreach($regexes as $regex) + while (preg_match($regex, $php, $matches)) { + $php = preg_replace_callback( + $regex, +// create_function('$m, $charset = "'.$charset.'"', +// 'return $m[1].$m[2] +// .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) +// .$m[5].$m[2];' +// ), + array('phpQuery', '_phpToMarkupCallback'), + $php + ); + } + $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s'; +//preg_match_all($regex, $php, $matches); +//var_dump($matches); + $php = preg_replace($regex, '\\1', $php); + return $php; + } + public static function _phpToMarkupCallback($php, $charset = 'utf-8') { + return $m[1].$m[2] + .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) + .$m[5].$m[2]; + } + public static function _markupToPHPCallback($m) { + return "<"."?php ".htmlspecialchars_decode($m[1])." ?".">"; + } + /** + * Converts document markup containing PHP code generated by phpQuery::php() + * into valid (executable) PHP code syntax. + * + * @param string|phpQueryObject $content + * @return string PHP code. + */ + public static function markupToPHP($content) { + if ($content instanceof phpQueryObject) + $content = $content->markupOuter(); + /* ... to */ + $content = preg_replace_callback( + '@\s*\s*@s', +// create_function('$m', +// 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";' +// ), + array('phpQuery', '_markupToPHPCallback'), + $content + ); + /* extra space added to save highlighters */ + $regexes = array( + '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^\']*)\'@s', + '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^"]*)"@s', + ); + foreach($regexes as $regex) + while (preg_match($regex, $content)) + $content = preg_replace_callback( + $regex, + create_function('$m', + 'return $m[1].$m[2].$m[3]."", " ", "\n", " ", "{", "$", "}", \'"\', "[", "]"), + htmlspecialchars_decode($m[4]) + ) + ." ?>".$m[5].$m[2];' + ), + $content + ); + return $content; + } + /** + * Creates new document from file $file. + * Chainable. + * + * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources. + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFile($file, $contentType = null) { + $documentID = self::createDocumentWrapper( + file_get_contents($file), $contentType + ); + return new phpQueryObject($documentID); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFileHTML($file, $charset = null) { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocumentFile($file, "text/html{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFileXML($file, $charset = null) { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocumentFile($file, "text/xml{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFileXHTML($file, $charset = null) { + $contentType = $charset + ? ";charset=$charset" + : ''; + return self::newDocumentFile($file, "application/xhtml+xml{$contentType}"); + } + /** + * Creates new document from markup. + * Chainable. + * + * @param unknown_type $markup + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public static function newDocumentFilePHP($file, $contentType = null) { + return self::newDocumentPHP(file_get_contents($file), $contentType); + } + /** + * Reuses existing DOMDocument object. + * Chainable. + * + * @param $document DOMDocument + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @TODO support DOMDocument + */ + public static function loadDocument($document) { + // TODO + die('TODO loadDocument'); + } + /** + * Enter description here... + * + * @param unknown_type $html + * @param unknown_type $domId + * @return unknown New DOM ID + * @todo support PHP tags in input + * @todo support passing DOMDocument object from self::loadDocument + */ + protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) { + if (function_exists('domxml_open_mem')) + throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled."); +// $id = $documentID +// ? $documentID +// : md5(microtime()); + $document = null; + if ($html instanceof DOMDOCUMENT) { + if (self::getDocumentID($html)) { + // document already exists in phpQuery::$documents, make a copy + $document = clone $html; + } else { + // new document, add it to phpQuery::$documents + $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); + } + } else { + $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); + } +// $wrapper->id = $id; + // bind document + phpQuery::$documents[$wrapper->id] = $wrapper; + // remember last loaded document + phpQuery::selectDocument($wrapper->id); + return $wrapper->id; + } + /** + * Extend class namespace. + * + * @param string|array $target + * @param array $source + * @TODO support string $source + * @return unknown_type + */ + public static function extend($target, $source) { + switch($target) { + case 'phpQueryObject': + $targetRef = &self::$extendMethods; + $targetRef2 = &self::$pluginsMethods; + break; + case 'phpQuery': + $targetRef = &self::$extendStaticMethods; + $targetRef2 = &self::$pluginsStaticMethods; + break; + default: + throw new Exception("Unsupported \$target type"); + } + if (is_string($source)) + $source = array($source => $source); + foreach($source as $method => $callback) { + if (isset($targetRef[$method])) { +// throw new Exception + self::debug("Duplicate method '{$method}', can\'t extend '{$target}'"); + continue; + } + if (isset($targetRef2[$method])) { +// throw new Exception + self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}'," + ." can\'t extend '{$target}'"); + continue; + } + $targetRef[$method] = $callback; + } + return true; + } + /** + * Extend phpQuery with $class from $file. + * + * @param string $class Extending class name. Real class name can be prepended phpQuery_. + * @param string $file Filename to include. Defaults to "{$class}.php". + */ + public static function plugin($class, $file = null) { + // TODO $class checked agains phpQuery_$class +// if (strpos($class, 'phpQuery') === 0) +// $class = substr($class, 8); + if (in_array($class, self::$pluginsLoaded)) + return true; + if (! $file) + $file = $class.'.php'; + $objectClassExists = class_exists('phpQueryObjectPlugin_'.$class); + $staticClassExists = class_exists('phpQueryPlugin_'.$class); + if (! $objectClassExists && ! $staticClassExists) + require_once($file); + self::$pluginsLoaded[] = $class; + // static methods + if (class_exists('phpQueryPlugin_'.$class)) { + $realClass = 'phpQueryPlugin_'.$class; + $vars = get_class_vars($realClass); + $loop = isset($vars['phpQueryMethods']) + && ! is_null($vars['phpQueryMethods']) + ? $vars['phpQueryMethods'] + : get_class_methods($realClass); + foreach($loop as $method) { + if ($method == '__initialize') + continue; + if (! is_callable(array($realClass, $method))) + continue; + if (isset(self::$pluginsStaticMethods[$method])) { + throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsStaticMethods[$method]."'"); + return; + } + self::$pluginsStaticMethods[$method] = $class; + } + if (method_exists($realClass, '__initialize')) + call_user_func_array(array($realClass, '__initialize'), array()); + } + // object methods + if (class_exists('phpQueryObjectPlugin_'.$class)) { + $realClass = 'phpQueryObjectPlugin_'.$class; + $vars = get_class_vars($realClass); + $loop = isset($vars['phpQueryMethods']) + && ! is_null($vars['phpQueryMethods']) + ? $vars['phpQueryMethods'] + : get_class_methods($realClass); + foreach($loop as $method) { + if (! is_callable(array($realClass, $method))) + continue; + if (isset(self::$pluginsMethods[$method])) { + throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '".self::$pluginsMethods[$method]."'"); + continue; + } + self::$pluginsMethods[$method] = $class; + } + } + return true; + } + /** + * Unloades all or specified document from memory. + * + * @param mixed $documentID @see phpQuery::getDocumentID() for supported types. + */ + public static function unloadDocuments($id = null) { + if (isset($id)) { + if ($id = self::getDocumentID($id)) + unset(phpQuery::$documents[$id]); + } else { + foreach(phpQuery::$documents as $k => $v) { + unset(phpQuery::$documents[$k]); + } + } + } + /** + * Parses phpQuery object or HTML result against PHP tags and makes them active. + * + * @param phpQuery|string $content + * @deprecated + * @return string + */ + public static function unsafePHPTags($content) { + return self::markupToPHP($content); + } + public static function DOMNodeListToArray($DOMNodeList) { + $array = array(); + if (! $DOMNodeList) + return $array; + foreach($DOMNodeList as $node) + $array[] = $node; + return $array; + } + /** + * Checks if $input is HTML string, which has to start with '<'. + * + * @deprecated + * @param String $input + * @return Bool + * @todo still used ? + */ + public static function isMarkup($input) { + return ! is_array($input) && substr(trim($input), 0, 1) == '<'; + } + public static function debug($text) { + if (self::$debug) + print var_dump($text); + } + /** + * Make an AJAX request. + * + * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions + * Additional options are: + * 'document' - document for global events, @see phpQuery::getDocumentID() + * 'referer' - implemented + * 'requested_with' - TODO; not implemented (X-Requested-With) + * @return Zend_Http_Client + * @link http://docs.jquery.com/Ajax/jQuery.ajax + * + * @TODO $options['cache'] + * @TODO $options['processData'] + * @TODO $options['xhr'] + * @TODO $options['data'] as string + * @TODO XHR interface + */ + public static function ajax($options = array(), $xhr = null) { + $options = array_merge( + self::$ajaxSettings, $options + ); + $documentID = isset($options['document']) + ? self::getDocumentID($options['document']) + : null; + if ($xhr) { + // reuse existing XHR object, but clean it up + $client = $xhr; +// $client->setParameterPost(null); +// $client->setParameterGet(null); + $client->setAuth(false); + $client->setHeaders("If-Modified-Since", null); + $client->setHeaders("Referer", null); + $client->resetParameters(); + } else { + // create new XHR object + require_once('Zend/Http/Client.php'); + $client = new Zend_Http_Client(); + $client->setCookieJar(); + } + if (isset($options['timeout'])) + $client->setConfig(array( + 'timeout' => $options['timeout'], + )); +// 'maxredirects' => 0, + foreach(self::$ajaxAllowedHosts as $k => $host) + if ($host == '.' && isset($_SERVER['HTTP_HOST'])) + self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST']; + $host = parse_url($options['url'], PHP_URL_HOST); + if (! in_array($host, self::$ajaxAllowedHosts)) { + throw new Exception("Request not permitted, host '$host' not present in " + ."phpQuery::\$ajaxAllowedHosts"); + } + // JSONP + $jsre = "/=\\?(&|$)/"; + if (isset($options['dataType']) && $options['dataType'] == 'jsonp') { + $jsonpCallbackParam = $options['jsonp'] + ? $options['jsonp'] : 'callback'; + if (strtolower($options['type']) == 'get') { + if (! preg_match($jsre, $options['url'])) { + $sep = strpos($options['url'], '?') + ? '&' : '?'; + $options['url'] .= "$sep$jsonpCallbackParam=?"; + } + } else if ($options['data']) { + $jsonp = false; + foreach($options['data'] as $n => $v) { + if ($v == '?') + $jsonp = true; + } + if (! $jsonp) { + $options['data'][$jsonpCallbackParam] = '?'; + } + } + $options['dataType'] = 'json'; + } + if (isset($options['dataType']) && $options['dataType'] == 'json') { + $jsonpCallback = 'json_'.md5(microtime()); + $jsonpData = $jsonpUrl = false; + if ($options['data']) { + foreach($options['data'] as $n => $v) { + if ($v == '?') + $jsonpData = $n; + } + } + if (preg_match($jsre, $options['url'])) + $jsonpUrl = true; + if ($jsonpData !== false || $jsonpUrl) { + // remember callback name for httpData() + $options['_jsonp'] = $jsonpCallback; + if ($jsonpData !== false) + $options['data'][$jsonpData] = $jsonpCallback; + if ($jsonpUrl) + $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']); + } + } + $client->setUri($options['url']); + $client->setMethod(strtoupper($options['type'])); + if (isset($options['referer']) && $options['referer']) + $client->setHeaders('Referer', $options['referer']); + $client->setHeaders(array( +// 'content-type' => $options['contentType'], + 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko' + .'/2008122010 Firefox/3.0.5', + // TODO custom charset + 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', +// 'Connection' => 'keep-alive', +// 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language' => 'en-us,en;q=0.5', + )); + if ($options['username']) + $client->setAuth($options['username'], $options['password']); + if (isset($options['ifModified']) && $options['ifModified']) + $client->setHeaders("If-Modified-Since", + self::$lastModified + ? self::$lastModified + : "Thu, 01 Jan 1970 00:00:00 GMT" + ); + $client->setHeaders("Accept", + isset($options['dataType']) + && isset(self::$ajaxSettings['accepts'][ $options['dataType'] ]) + ? self::$ajaxSettings['accepts'][ $options['dataType'] ].", */*" + : self::$ajaxSettings['accepts']['_default'] + ); + // TODO $options['processData'] + if ($options['data'] instanceof phpQueryObject) { + $serialized = $options['data']->serializeArray($options['data']); + $options['data'] = array(); + foreach($serialized as $r) + $options['data'][ $r['name'] ] = $r['value']; + } + if (strtolower($options['type']) == 'get') { + $client->setParameterGet($options['data']); + } else if (strtolower($options['type']) == 'post') { + $client->setEncType($options['contentType']); + $client->setParameterPost($options['data']); + } + if (self::$active == 0 && $options['global']) + phpQueryEvents::trigger($documentID, 'ajaxStart'); + self::$active++; + // beforeSend callback + if (isset($options['beforeSend']) && $options['beforeSend']) + phpQuery::callbackRun($options['beforeSend'], array($client)); + // ajaxSend event + if ($options['global']) + phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options)); + if (phpQuery::$debug) { + self::debug("{$options['type']}: {$options['url']}\n"); + self::debug("Options:
".var_export($options, true)."
\n"); +// if ($client->getCookieJar()) +// self::debug("Cookies:
".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."
\n"); + } + // request + $response = $client->request(); + if (phpQuery::$debug) { + self::debug('Status: '.$response->getStatus().' / '.$response->getMessage()); + self::debug($client->getLastRequest()); + self::debug($response->getHeaders()); + } + if ($response->isSuccessful()) { + // XXX tempolary + self::$lastModified = $response->getHeader('Last-Modified'); + $data = self::httpData($response->getBody(), $options['dataType'], $options); + if (isset($options['success']) && $options['success']) + phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options)); + if ($options['global']) + phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options)); + } else { + if (isset($options['error']) && $options['error']) + phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage())); + if ($options['global']) + phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/$response->getMessage(), $options)); + } + if (isset($options['complete']) && $options['complete']) + phpQuery::callbackRun($options['complete'], array($client, $response->getStatus())); + if ($options['global']) + phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options)); + if ($options['global'] && ! --self::$active) + phpQueryEvents::trigger($documentID, 'ajaxStop'); + return $client; +// if (is_null($domId)) +// $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false; +// return new phpQueryAjaxResponse($response, $domId); + } + protected static function httpData($data, $type, $options) { + if (isset($options['dataFilter']) && $options['dataFilter']) + $data = self::callbackRun($options['dataFilter'], array($data, $type)); + if (is_string($data)) { + if ($type == "json") { + if (isset($options['_jsonp']) && $options['_jsonp']) { + $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data); + } + $data = self::parseJSON($data); + } + } + return $data; + } + /** + * Enter description here... + * + * @param array|phpQuery $data + * + */ + public static function param($data) { + return http_build_query($data, null, '&'); + } + public static function get($url, $data = null, $callback = null, $type = null) { + if (!is_array($data)) { + $callback = $data; + $data = null; + } + // TODO some array_values on this shit + return phpQuery::ajax(array( + 'type' => 'GET', + 'url' => $url, + 'data' => $data, + 'success' => $callback, + 'dataType' => $type, + )); + } + public static function post($url, $data = null, $callback = null, $type = null) { + if (!is_array($data)) { + $callback = $data; + $data = null; + } + return phpQuery::ajax(array( + 'type' => 'POST', + 'url' => $url, + 'data' => $data, + 'success' => $callback, + 'dataType' => $type, + )); + } + public static function getJSON($url, $data = null, $callback = null) { + if (!is_array($data)) { + $callback = $data; + $data = null; + } + // TODO some array_values on this shit + return phpQuery::ajax(array( + 'type' => 'GET', + 'url' => $url, + 'data' => $data, + 'success' => $callback, + 'dataType' => 'json', + )); + } + public static function ajaxSetup($options) { + self::$ajaxSettings = array_merge( + self::$ajaxSettings, + $options + ); + } + public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) { + $loop = is_array($host1) + ? $host1 + : func_get_args(); + foreach($loop as $host) { + if ($host && ! in_array($host, phpQuery::$ajaxAllowedHosts)) { + phpQuery::$ajaxAllowedHosts[] = $host; + } + } + } + public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) { + $loop = is_array($url1) + ? $url1 + : func_get_args(); + foreach($loop as $url) + phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST)); + } + /** + * Returns JSON representation of $data. + * + * @static + * @param mixed $data + * @return string + */ + public static function toJSON($data) { + if (function_exists('json_encode')) + return json_encode($data); + require_once('Zend/Json/Encoder.php'); + return Zend_Json_Encoder::encode($data); + } + /** + * Parses JSON into proper PHP type. + * + * @static + * @param string $json + * @return mixed + */ + public static function parseJSON($json) { + if (function_exists('json_decode')) { + $return = json_decode(trim($json), true); + // json_decode and UTF8 issues + if (isset($return)) + return $return; + } + require_once('Zend/Json/Decoder.php'); + return Zend_Json_Decoder::decode($json); + } + /** + * Returns source's document ID. + * + * @param $source DOMNode|phpQueryObject + * @return string + */ + public static function getDocumentID($source) { + if ($source instanceof DOMDOCUMENT) { + foreach(phpQuery::$documents as $id => $document) { + if ($source->isSameNode($document->document)) + return $id; + } + } else if ($source instanceof DOMNODE) { + foreach(phpQuery::$documents as $id => $document) { + if ($source->ownerDocument->isSameNode($document->document)) + return $id; + } + } else if ($source instanceof phpQueryObject) + return $source->getDocumentID(); + else if (is_string($source) && isset(phpQuery::$documents[$source])) + return $source; + } + /** + * Get DOMDocument object related to $source. + * Returns null if such document doesn't exist. + * + * @param $source DOMNode|phpQueryObject|string + * @return string + */ + public static function getDOMDocument($source) { + if ($source instanceof DOMDOCUMENT) + return $source; + $source = self::getDocumentID($source); + return $source + ? self::$documents[$id]['document'] + : null; + } + + // UTILITIES + // http://docs.jquery.com/Utilities + + /** + * + * @return unknown_type + * @link http://docs.jquery.com/Utilities/jQuery.makeArray + */ + public static function makeArray($obj) { + $array = array(); + if (is_object($object) && $object instanceof DOMNODELIST) { + foreach($object as $value) + $array[] = $value; + } else if (is_object($object) && ! ($object instanceof Iterator)) { + foreach(get_object_vars($object) as $name => $value) + $array[0][$name] = $value; + } else { + foreach($object as $name => $value) + $array[0][$name] = $value; + } + return $array; + } + public static function inArray($value, $array) { + return in_array($value, $array); + } + /** + * + * @param $object + * @param $callback + * @return unknown_type + * @link http://docs.jquery.com/Utilities/jQuery.each + */ + public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) { + $paramStructure = null; + if (func_num_args() > 2) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 2); + } + if (is_object($object) && ! ($object instanceof Iterator)) { + foreach(get_object_vars($object) as $name => $value) + phpQuery::callbackRun($callback, array($name, $value), $paramStructure); + } else { + foreach($object as $name => $value) + phpQuery::callbackRun($callback, array($name, $value), $paramStructure); + } + } + /** + * + * @link http://docs.jquery.com/Utilities/jQuery.map + */ + public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) { + $result = array(); + $paramStructure = null; + if (func_num_args() > 2) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 2); + } + foreach($array as $v) { + $vv = phpQuery::callbackRun($callback, array($v), $paramStructure); +// $callbackArgs = $args; +// foreach($args as $i => $arg) { +// $callbackArgs[$i] = $arg instanceof CallbackParam +// ? $v +// : $arg; +// } +// $vv = call_user_func_array($callback, $callbackArgs); + if (is_array($vv)) { + foreach($vv as $vvv) + $result[] = $vvv; + } else if ($vv !== null) { + $result[] = $vv; + } + } + return $result; + } + /** + * + * @param $callback Callback + * @param $params + * @param $paramStructure + * @return unknown_type + */ + public static function callbackRun($callback, $params = array(), $paramStructure = null) { + if (! $callback) + return; + if ($callback instanceof CallbackParameterToReference) { + // TODO support ParamStructure to select which $param push to reference + if (isset($params[0])) + $callback->callback = $params[0]; + return true; + } + if ($callback instanceof Callback) { + $paramStructure = $callback->params; + $callback = $callback->callback; + } + if (! $paramStructure) + return call_user_func_array($callback, $params); + $p = 0; + foreach($paramStructure as $i => $v) { + $paramStructure[$i] = $v instanceof CallbackParam + ? $params[$p++] + : $v; + } + return call_user_func_array($callback, $paramStructure); + } + /** + * Merge 2 phpQuery objects. + * @param array $one + * @param array $two + * @protected + * @todo node lists, phpQueryObject + */ + public static function merge($one, $two) { + $elements = $one->elements; + foreach($two->elements as $node) { + $exists = false; + foreach($elements as $node2) { + if ($node2->isSameNode($node)) + $exists = true; + } + if (! $exists) + $elements[] = $node; + } + return $elements; +// $one = $one->newInstance(); +// $one->elements = $elements; +// return $one; + } + /** + * + * @param $array + * @param $callback + * @param $invert + * @return unknown_type + * @link http://docs.jquery.com/Utilities/jQuery.grep + */ + public static function grep($array, $callback, $invert = false) { + $result = array(); + foreach($array as $k => $v) { + $r = call_user_func_array($callback, array($v, $k)); + if ($r === !(bool)$invert) + $result[] = $v; + } + return $result; + } + public static function unique($array) { + return array_unique($array); + } + /** + * + * @param $function + * @return unknown_type + * @TODO there are problems with non-static methods, second parameter pass it + * but doesnt verify is method is really callable + */ + public static function isFunction($function) { + return is_callable($function); + } + public static function trim($str) { + return trim($str); + } + /* PLUGINS NAMESPACE */ + /** + * + * @param $url + * @param $callback + * @param $param1 + * @param $param2 + * @param $param3 + * @return phpQueryObject + */ + public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) { + if (self::plugin('WebBrowser')) { + $params = func_get_args(); + return self::callbackRun(array(self::$plugins, 'browserGet'), $params); + } else { + self::debug('WebBrowser plugin not available...'); + } + } + /** + * + * @param $url + * @param $data + * @param $callback + * @param $param1 + * @param $param2 + * @param $param3 + * @return phpQueryObject + */ + public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) { + if (self::plugin('WebBrowser')) { + $params = func_get_args(); + return self::callbackRun(array(self::$plugins, 'browserPost'), $params); + } else { + self::debug('WebBrowser plugin not available...'); + } + } + /** + * + * @param $ajaxSettings + * @param $callback + * @param $param1 + * @param $param2 + * @param $param3 + * @return phpQueryObject + */ + public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) { + if (self::plugin('WebBrowser')) { + $params = func_get_args(); + return self::callbackRun(array(self::$plugins, 'browser'), $params); + } else { + self::debug('WebBrowser plugin not available...'); + } + } + /** + * + * @param $code + * @return string + */ + public static function php($code) { + return self::code('php', $code); + } + /** + * + * @param $type + * @param $code + * @return string + */ + public static function code($type, $code) { + return "<$type>"; + } + + public static function __callStatic($method, $params) { + return call_user_func_array( + array(phpQuery::$plugins, $method), + $params + ); + } + protected static function dataSetupNode($node, $documentID) { + // search are return if alredy exists + foreach(phpQuery::$documents[$documentID]->dataNodes as $dataNode) { + if ($node->isSameNode($dataNode)) + return $dataNode; + } + // if doesn't, add it + phpQuery::$documents[$documentID]->dataNodes[] = $node; + return $node; + } + protected static function dataRemoveNode($node, $documentID) { + // search are return if alredy exists + foreach(phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) { + if ($node->isSameNode($dataNode)) { + unset(self::$documents[$documentID]->dataNodes[$k]); + unset(self::$documents[$documentID]->data[ $dataNode->dataID ]); + } + } + } + public static function data($node, $name, $data, $documentID = null) { + if (! $documentID) + // TODO check if this works + $documentID = self::getDocumentID($node); + $document = phpQuery::$documents[$documentID]; + $node = self::dataSetupNode($node, $documentID); + if (! isset($node->dataID)) + $node->dataID = ++phpQuery::$documents[$documentID]->uuid; + $id = $node->dataID; + if (! isset($document->data[$id])) + $document->data[$id] = array(); + if (! is_null($data)) + $document->data[$id][$name] = $data; + if ($name) { + if (isset($document->data[$id][$name])) + return $document->data[$id][$name]; + } else + return $id; + } + public static function removeData($node, $name, $documentID) { + if (! $documentID) + // TODO check if this works + $documentID = self::getDocumentID($node); + $document = phpQuery::$documents[$documentID]; + $node = self::dataSetupNode($node, $documentID); + $id = $node->dataID; + if ($name) { + if (isset($document->data[$id][$name])) + unset($document->data[$id][$name]); + $name = null; + foreach($document->data[$id] as $name) + break; + if (! $name) + self::removeData($node, $name, $documentID); + } else { + self::dataRemoveNode($node, $documentID); + } + } +} +/** + * Plugins static namespace class. + * + * @author Tobiasz Cudnik + * @package phpQuery + * @todo move plugin methods here (as statics) + */ +class phpQueryPlugins { + public function __call($method, $args) { + if (isset(phpQuery::$extendStaticMethods[$method])) { + $return = call_user_func_array( + phpQuery::$extendStaticMethods[$method], + $args + ); + } else if (isset(phpQuery::$pluginsStaticMethods[$method])) { + $class = phpQuery::$pluginsStaticMethods[$method]; + $realClass = "phpQueryPlugin_$class"; + $return = call_user_func_array( + array($realClass, $method), + $args + ); + return isset($return) + ? $return + : $this; + } else + throw new Exception("Method '{$method}' doesnt exist"); + } +} +/** + * Shortcut to phpQuery::pq($arg1, $context) + * Chainable. + * + * @see phpQuery::pq() + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * @author Tobiasz Cudnik + * @package phpQuery + */ +function pq($arg1, $context = null) { + $args = func_get_args(); + return call_user_func_array( + array('phpQuery', 'pq'), + $args + ); +} +// add plugins dir and Zend framework to include path +set_include_path( + get_include_path() + .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/' + .PATH_SEPARATOR.dirname(__FILE__).'/phpQuery/plugins/' +); +// why ? no __call nor __get for statics in php... +// XXX __callStatic will be available in PHP 5.3 +phpQuery::$plugins = new phpQueryPlugins(); +// include bootstrap file (personal library config) +if (file_exists(dirname(__FILE__).'/phpQuery/bootstrap.php')) + require_once dirname(__FILE__).'/phpQuery/bootstrap.php'; \ No newline at end of file diff --git a/phpQuery/phpQuery/Callback.php b/phpQuery/phpQuery/Callback.php new file mode 100644 index 0000000..d9a90ea --- /dev/null +++ b/phpQuery/phpQuery/Callback.php @@ -0,0 +1,152 @@ + + * + * @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 new file mode 100644 index 0000000..0b6a7fd --- /dev/null +++ b/phpQuery/phpQuery/DOMDocumentWrapper.php @@ -0,0 +1,677 @@ + 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 new file mode 100644 index 0000000..0cb0c46 --- /dev/null +++ b/phpQuery/phpQuery/DOMEvent.php @@ -0,0 +1,107 @@ + + * @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 new file mode 100644 index 0000000..599d8a0 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Exception.php @@ -0,0 +1,30 @@ + 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 new file mode 100644 index 0000000..dfbe904 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Http/Client/Adapter/Exception.php @@ -0,0 +1,33 @@ + '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 new file mode 100644 index 0000000..01b6ef3 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Http/Client/Adapter/Socket.php @@ -0,0 +1,332 @@ + 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 new file mode 100644 index 0000000..ce5c468 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Http/Client/Adapter/Test.php @@ -0,0 +1,193 @@ + $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 new file mode 100644 index 0000000..70c8e01 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Http/Client/Exception.php @@ -0,0 +1,33 @@ +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 new file mode 100644 index 0000000..c11174e --- /dev/null +++ b/phpQuery/phpQuery/Zend/Http/CookieJar.php @@ -0,0 +1,350 @@ +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 new file mode 100644 index 0000000..76e2a8d --- /dev/null +++ b/phpQuery/phpQuery/Zend/Http/Exception.php @@ -0,0 +1,33 @@ + '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 new file mode 100644 index 0000000..c5f1f25 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Json/Decoder.php @@ -0,0 +1,457 @@ +_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 new file mode 100644 index 0000000..ce2024a --- /dev/null +++ b/phpQuery/phpQuery/Zend/Json/Encoder.php @@ -0,0 +1,431 @@ +_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 new file mode 100644 index 0000000..5b99095 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Json/Exception.php @@ -0,0 +1,36 @@ + $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 new file mode 100644 index 0000000..62d9ceb --- /dev/null +++ b/phpQuery/phpQuery/Zend/Registry.php @@ -0,0 +1,195 @@ +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 new file mode 100644 index 0000000..4c5776b --- /dev/null +++ b/phpQuery/phpQuery/Zend/Uri.php @@ -0,0 +1,164 @@ +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 new file mode 100644 index 0000000..d327f3c --- /dev/null +++ b/phpQuery/phpQuery/Zend/Uri/Exception.php @@ -0,0 +1,37 @@ +_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 new file mode 100644 index 0000000..1c11d54 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Abstract.php @@ -0,0 +1,348 @@ +_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 new file mode 100644 index 0000000..36b0adc --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Alnum.php @@ -0,0 +1,120 @@ + "'%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 new file mode 100644 index 0000000..0f2298e --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Alpha.php @@ -0,0 +1,120 @@ + "'%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 new file mode 100644 index 0000000..d51f11b --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Barcode.php @@ -0,0 +1,99 @@ +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 new file mode 100644 index 0000000..7be797d --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Barcode/Ean13.php @@ -0,0 +1,100 @@ + "'%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 new file mode 100644 index 0000000..c584e81 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Barcode/UpcA.php @@ -0,0 +1,100 @@ + "'%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 new file mode 100644 index 0000000..bb0b726 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Between.php @@ -0,0 +1,200 @@ + "'%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 new file mode 100644 index 0000000..227a4ec --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Ccnum.php @@ -0,0 +1,111 @@ + "'%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 new file mode 100644 index 0000000..220b0ee --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Date.php @@ -0,0 +1,250 @@ + "'%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 new file mode 100644 index 0000000..c42ec0a --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Digits.php @@ -0,0 +1,100 @@ + "'%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 new file mode 100644 index 0000000..d8e9595 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/EmailAddress.php @@ -0,0 +1,245 @@ + "'%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 new file mode 100644 index 0000000..a38077e --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Exception.php @@ -0,0 +1,37 @@ + "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 new file mode 100644 index 0000000..268090b --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/File/Exists.php @@ -0,0 +1,192 @@ + "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 new file mode 100644 index 0000000..62577b3 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/File/Extension.php @@ -0,0 +1,204 @@ + "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 new file mode 100644 index 0000000..e8b060d --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/File/FilesSize.php @@ -0,0 +1,156 @@ + "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 new file mode 100644 index 0000000..6c27ef6 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/File/ImageSize.php @@ -0,0 +1,335 @@ + "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 new file mode 100644 index 0000000..8f7e9cd --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/File/MimeType.php @@ -0,0 +1,200 @@ + "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 new file mode 100644 index 0000000..2b812fc --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/File/NotExists.php @@ -0,0 +1,86 @@ + "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 new file mode 100644 index 0000000..2adaeb3 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/File/Size.php @@ -0,0 +1,308 @@ + "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 new file mode 100644 index 0000000..a56cf14 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/File/Upload.php @@ -0,0 +1,216 @@ + "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 new file mode 100644 index 0000000..0405161 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Float.php @@ -0,0 +1,75 @@ + "'%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 new file mode 100644 index 0000000..35e658c --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/GreaterThan.php @@ -0,0 +1,114 @@ + "'%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 new file mode 100644 index 0000000..9512eda --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Hex.php @@ -0,0 +1,74 @@ + "'%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 new file mode 100644 index 0000000..ea79c24 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Hostname.php @@ -0,0 +1,444 @@ + "'%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 new file mode 100644 index 0000000..fff6bf2 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Hostname/At.php @@ -0,0 +1,50 @@ + '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 new file mode 100644 index 0000000..1c7725a --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/InArray.php @@ -0,0 +1,138 @@ + "'%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 new file mode 100644 index 0000000..0bde2cb --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Int.php @@ -0,0 +1,75 @@ + "'%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 new file mode 100644 index 0000000..4fcd525 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Interface.php @@ -0,0 +1,71 @@ + "'%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 new file mode 100644 index 0000000..9f7b72c --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/LessThan.php @@ -0,0 +1,113 @@ + "'%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 new file mode 100644 index 0000000..dcf3662 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/NotEmpty.php @@ -0,0 +1,74 @@ + "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 new file mode 100644 index 0000000..1566f07 --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/Regex.php @@ -0,0 +1,125 @@ + "'%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 new file mode 100644 index 0000000..c43f2ca --- /dev/null +++ b/phpQuery/phpQuery/Zend/Validate/StringLength.php @@ -0,0 +1,180 @@ + "'%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 new file mode 100644 index 0000000..4fafe1a --- /dev/null +++ b/phpQuery/phpQuery/bootstrap.example.php @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/phpQuery/phpQuery/compat/mbstring.php b/phpQuery/phpQuery/compat/mbstring.php new file mode 100644 index 0000000..409129e --- /dev/null +++ b/phpQuery/phpQuery/compat/mbstring.php @@ -0,0 +1,88 @@ +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 new file mode 100644 index 0000000..146ddc8 --- /dev/null +++ b/phpQuery/phpQuery/phpQueryObject.php @@ -0,0 +1,3180 @@ + + * @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 new file mode 100644 index 0000000..cc4dd6f --- /dev/null +++ b/phpQuery/phpQuery/plugins/Scripts.php @@ -0,0 +1,72 @@ + \ No newline at end of file diff --git a/phpQuery/phpQuery/plugins/Scripts/__config.example.php b/phpQuery/phpQuery/plugins/Scripts/__config.example.php new file mode 100644 index 0000000..db80652 --- /dev/null +++ b/phpQuery/phpQuery/plugins/Scripts/__config.example.php @@ -0,0 +1,10 @@ + 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 new file mode 100644 index 0000000..a8f45f4 --- /dev/null +++ b/phpQuery/phpQuery/plugins/Scripts/example.php @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..a7cac9e --- /dev/null +++ b/phpQuery/phpQuery/plugins/Scripts/fix_webroot.php @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..0327045 --- /dev/null +++ b/phpQuery/phpQuery/plugins/Scripts/google_login.php @@ -0,0 +1,47 @@ + + */ +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 new file mode 100644 index 0000000..03d8061 --- /dev/null +++ b/phpQuery/phpQuery/plugins/Scripts/print_source.php @@ -0,0 +1,9 @@ + + */ +/** @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 new file mode 100644 index 0000000..4f48bd7 --- /dev/null +++ b/phpQuery/phpQuery/plugins/Scripts/print_websafe.php @@ -0,0 +1,13 @@ + + */ +/** @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 new file mode 100644 index 0000000..6688d3f --- /dev/null +++ b/phpQuery/phpQuery/plugins/WebBrowser.php @@ -0,0 +1,405 @@ +_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 new file mode 100644 index 0000000..732f05c --- /dev/null +++ b/phpQuery/phpQuery/plugins/example.php @@ -0,0 +1,75 @@ +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