diff --git a/.user.ini b/.user.ini new file mode 100644 index 0000000..b49569e --- /dev/null +++ b/.user.ini @@ -0,0 +1 @@ +open_basedir=/www/wwwroot/php/:/tmp/:/proc/ \ No newline at end of file diff --git a/91.php b/91.php new file mode 100644 index 0000000..fdbc78d --- /dev/null +++ b/91.php @@ -0,0 +1,182 @@ + 'sqlite', + 'database_file' => 'db/91.db' +]); + +function getList($domain="http://www.91porn.com",$page = 1){ + + $jinghua = $_COOKIE["jinghua"]; + + $url = $domain."/video.php?". ($jinghua == 2 ? "" : "category=rf") ."&page=".$page; + + //echo $url; + + $html = readHtml($url); + + //echo $html; + + $html = preg_replace('/(.*)/', '', $html); + + $rules = array( + //采集id为one这个元素里面的纯文本内容 + 'pic' => array('.imagechannelhd>a>img,.imagechannel>a>img','src'), + 'title' => array('.imagechannelhd>a>img,.imagechannel>a>img','title'), + 'link' => array('.imagechannelhd>a,.imagechannel>a','href'), + ); + $data = QueryList::Query($html,$rules)->data; + //print_r($data); + return $data; +} + + +#获取URL +$domain="http://www.91porn.com"; +if($_REQUEST["domain"]){ + $domain = urldecode($_REQUEST["domain"]); +} +$page=1; +if($_REQUEST["page"]){ + $page = $_REQUEST["page"]; +} +$list = getList($domain,$page); +?> + + + + + + + + 视频列表-91视频预览 + + + + + + + +
+

视频列表

+
+ +
+
+
+

+ + +
+
+
+
+

+ 只看精华 +

+ +
+
+
+ +
+
    + $value) { ?> +
  • &url="> +
    +
    + ')"> +
    +
    + select("videos","link",["url" => $viewkey]); + ?> +

    + 已缓存'; + } + echo $value["title"] + ?>

    +
    +
    +
  • + + +
+
+
+
+
+ + 1){ ?> +
上一页
+ + 下一页 + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/91v.php b/91v.php new file mode 100644 index 0000000..3d6b5af --- /dev/null +++ b/91v.php @@ -0,0 +1,172 @@ + 'sqlite', + 'database_file' => 'db/91.db' +]); + +//echo $_REQUEST["proxy"] ? 'tcp://'.$_REQUEST["proxy"] : ''; + +//根据地址,获取视频地址 +function getList($url){ + + #获取URL + /*$url = $_REQUEST["url"]; + + $video=$db->select("videos","Video",["URL" => $url]); + + if($data){ + return $video[0]["Video"]; + }*/ + + #$html = readHtml($url,urldecode($_REQUEST["proxyip"])); + $html = getHtml($url); + + $rules = array( + //采集id为one这个元素里面的纯文本内容 + 'video' => array('source','src') + ); + $data = QueryList::Query($html,$rules)->data; + //print_r($data); + + $link = $data[0]["video"]; + + //print_r($db->id()); + if($link){ + global $db,$viewkey; + + $db->insert("videos",[ + "url" => $viewkey, + "link" => $link + ]); + } + + + return $link; +} + +//$video = getList(); + + + +function getHtml($url){ + + $ip = randIp(); + $snoopy = new Snoopy; + + #$snoopy->proxy_host = "165.227.104.78"; + $snoopy->proxy_port = "3128"; + #$snoopy->_isproxy = true; + + $snoopy->cookies["PHPSESSID"] = 'fsef'; //伪装sessionid + #$snoopy->agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"; //伪装浏览器 + //$snoopy->referer = 'http://www.4wei.cn'; //伪装来源页地址 http_referer + $snoopy->rawheaders["Pragma"] = "no-cache"; //cache 的http头信息 + $snoopy->rawheaders["Accept-language"] = "zh-cn"; //cache 的http头信息 + $snoopy->rawheaders["Content-Type"] = "text/html; charset=utf-8"; //cache 的http头信息 + $snoopy->rawheaders["CLIENT-IP"] = $ip; //伪装ip + $snoopy->rawheaders["HTTP_X_FORWARDED_FOR"] = $ip; //伪装ip + #$snoopy->fetch("http://www.checkip.net"); + $snoopy->fetch($url); + + return $snoopy->results; +} + + +#获取URL +$url = urldecode($_REQUEST["url"]); + +$urlarr=parse_url($url); +parse_str($urlarr['query'],$parr); +$viewkey = $parr["viewkey"]; + +$dbResult=$db->select("videos","link",["url" => $viewkey]); + + //print_r($dbResult); +//$video = ''; + +//数据在缓存中,直接取 +if($dbResult){ + //global $video; + $video = $dbResult[0]; + $catch=true; + //echo $video; +}else{ + global $video; + $video = getList($url); + //echo "src"; + //$video = getVideo($url); +} + +?> + + + + + + + + 视频详情-91视频预览 + + + + +
+     列表页

视频详情

+
+ +
+ +
+ +
+ + + +
+
+ 获取失败,请刷新重试 +
+
+ + + +

+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/core/.DS_Store b/core/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/core/.DS_Store differ diff --git a/core/db.php b/core/db.php new file mode 100644 index 0000000..b0f73ee --- /dev/null +++ b/core/db.php @@ -0,0 +1,20 @@ + 'sqlite', + 'database_file' => dirname(__FILE__).'/../db/91.db' +]); + +/* $id = $db->insert("videos",[ + "url"=>'aa', + "link"=>'bb', + ]); +print_r($id); +*/ +//$db->query("INSERT INTO videos ('url', 'link') VALUES ('aa', 'bb')"); + +print_r($db->select("videos","*")); diff --git a/core/myfunction.php b/core/myfunction.php new file mode 100644 index 0000000..286c233 --- /dev/null +++ b/core/myfunction.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/core/readHtml.php b/core/readHtml.php new file mode 100644 index 0000000..1eab4ce --- /dev/null +++ b/core/readHtml.php @@ -0,0 +1,26 @@ +"; + + #读取HTML + $opts = array( + 'http'=>array( + 'method'=>"GET", + 'proxy'=> $proxyip=="" ? "" : 'tcp://'.$proxyip, + 'header'=>"Content-Type: text/xml\r\n"."charset=utf-8\r\n"."Accept-language: zh-cn\r\n"."Cookie: foo=bar\r\n", + 'float'=>4, + 'timeout'=>4 + ) + ); + + $context = stream_context_create($opts); + + // Open the file using the HTTP headers set above + $html = file_get_contents($url, false, $context); + #$file = iconv("utf-8", "utf-8",file_get_contents($url, false, $context)); + + return $html; +} \ No newline at end of file diff --git a/db/91.db b/db/91.db new file mode 100755 index 0000000..2215897 Binary files /dev/null and b/db/91.db differ diff --git a/lib/.DS_Store b/lib/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/lib/.DS_Store differ diff --git a/lib/Medoo.php b/lib/Medoo.php new file mode 100644 index 0000000..097d998 --- /dev/null +++ b/lib/Medoo.php @@ -0,0 +1,1509 @@ +database_type = strtolower($options[ 'database_type' ]); + } + } + else + { + return false; + } + + if (isset($options[ 'prefix' ])) + { + $this->prefix = $options[ 'prefix' ]; + } + + if (isset($options[ 'option' ])) + { + $this->option = $options[ 'option' ]; + } + + if (isset($options[ 'logging' ]) && is_bool($options[ 'logging' ])) + { + $this->logging = $options[ 'logging' ]; + } + + if (isset($options[ 'command' ]) && is_array($options[ 'command' ])) + { + $commands = $options[ 'command' ]; + } + else + { + $commands = []; + } + + if (isset($options[ 'dsn' ])) + { + if (isset($options[ 'dsn' ][ 'driver' ])) + { + $attr = $options[ 'dsn' ]; + } + else + { + return false; + } + } + else + { + if ( + isset($options[ 'port' ]) && + is_int($options[ 'port' ] * 1) + ) + { + $port = $options[ 'port' ]; + } + + $is_port = isset($port); + + switch ($this->database_type) + { + case 'mariadb': + case 'mysql': + $attr = [ + 'driver' => 'mysql', + 'dbname' => $options[ 'database_name' ] + ]; + + if (isset($options[ 'socket' ])) + { + $attr[ 'unix_socket' ] = $options[ 'socket' ]; + } + else + { + $attr[ 'host' ] = $options[ 'server' ]; + + if ($is_port) + { + $attr[ 'port' ] = $port; + } + } + + // Make MySQL using standard quoted identifier + $commands[] = 'SET SQL_MODE=ANSI_QUOTES'; + break; + + case 'pgsql': + $attr = [ + 'driver' => 'pgsql', + 'host' => $options[ 'server' ], + 'dbname' => $options[ 'database_name' ] + ]; + + if ($is_port) + { + $attr[ 'port' ] = $port; + } + + break; + + case 'sybase': + $attr = [ + 'driver' => 'dblib', + 'host' => $options[ 'server' ], + 'dbname' => $options[ 'database_name' ] + ]; + + if ($is_port) + { + $attr[ 'port' ] = $port; + } + + break; + + case 'oracle': + $attr = [ + 'driver' => 'oci', + 'dbname' => $options[ 'server' ] ? + '//' . $options[ 'server' ] . ($is_port ? ':' . $port : ':1521') . '/' . $options[ 'database_name' ] : + $options[ 'database_name' ] + ]; + + if (isset($options[ 'charset' ])) + { + $attr[ 'charset' ] = $options[ 'charset' ]; + } + + break; + + case 'mssql': + if (strstr(PHP_OS, 'WIN')) + { + $attr = [ + 'driver' => 'sqlsrv', + 'Server' => $options[ 'server' ] . ($is_port ? ',' . $port : ''), + 'Database' => $options[ 'database_name' ] + ]; + } + else + { + $attr = [ + 'driver' => 'dblib', + 'host' => $options[ 'server' ] . ($is_port ? ':' . $port : ''), + 'dbname' => $options[ 'database_name' ] + ]; + } + + // Keep MSSQL QUOTED_IDENTIFIER is ON for standard quoting + $commands[] = 'SET QUOTED_IDENTIFIER ON'; + + // Make ANSI_NULLS is ON for NULL value + $commands[] = 'SET ANSI_NULLS ON'; + break; + + case 'sqlite': + $this->pdo = new PDO('sqlite:' . $options[ 'database_file' ], null, null, $this->option); + + return; + } + } + + $driver = $attr[ 'driver' ]; + + unset($attr[ 'driver' ]); + + $stack = []; + + foreach ($attr as $key => $value) + { + if (is_int($key)) + { + $stack[] = $value; + } + else + { + $stack[] = $key . '=' . $value; + } + } + + $dsn = $driver . ':' . implode($stack, ';'); + + if ( + in_array($this->database_type, ['mariadb', 'mysql', 'pgsql', 'sybase', 'mssql']) && + isset($options[ 'charset' ]) + ) + { + $commands[] = "SET NAMES '" . $options[ 'charset' ] . "'"; + } + + $this->pdo = new PDO( + $dsn, + $options[ 'username' ], + $options[ 'password' ], + $this->option + ); + + foreach ($commands as $value) + { + $this->pdo->exec($value); + } + } + catch (PDOException $e) { + throw new Exception($e->getMessage()); + } + } + + public function query($query, $map = []) + { + if (!empty($map)) + { + foreach ($map as $key => $value) + { + switch (gettype($value)) + { + case 'NULL': + $map[ $key ] = [null, PDO::PARAM_NULL]; + break; + + case 'resource': + $map[ $key ] = [$value, PDO::PARAM_LOB]; + break; + + case 'boolean': + $map[ $key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL]; + break; + + case 'integer': + case 'double': + $map[ $key ] = [$value, PDO::PARAM_INT]; + break; + + case 'string': + $map[ $key ] = [$value, PDO::PARAM_STR]; + break; + } + } + } + + return $this->exec($query, $map); + } + + public function exec($query, $map = []) + { + if ($this->debug_mode) + { + echo $this->generate($query, $map); + + $this->debug_mode = false; + + return false; + } + + if ($this->logging) + { + $this->logs[] = [$query, $map]; + } + else + { + $this->logs = [[$query, $map]]; + } + + $statement = $this->pdo->prepare($query); + + if ($statement) + { + foreach ($map as $key => $value) + { + $statement->bindValue($key, $value[ 0 ], $value[ 1 ]); + } + + $statement->execute(); + + $this->statement = $statement; + + return $statement; + } + else + { + return false; + } + } + + protected function generate($query, $map) + { + foreach ($map as $key => $value) + { + if ($value[ 1 ] === PDO::PARAM_STR) + { + $query = str_replace($key, $this->quote($value[ 0 ]), $query); + } + elseif ($value[ 1 ] === PDO::PARAM_NULL) + { + $query = str_replace($key, 'NULL', $query); + } + else + { + $query = str_replace($key, $value[ 0 ], $query); + } + } + + return $query; + } + + public function quote($string) + { + return $this->pdo->quote($string); + } + + protected function tableQuote($table) + { + return '"' . $this->prefix . $table . '"'; + } + + protected function mapKey() + { + return ':MeDoO_' . $this->guid++ . '_mEdOo'; + } + + protected function columnQuote($string) + { + preg_match('/(^#)?([a-zA-Z0-9_]*)\.([a-zA-Z0-9_]*)(\s*\[JSON\]$)?/', $string, $column_match); + + if (isset($column_match[ 2 ], $column_match[ 3 ])) + { + return '"' . $this->prefix . $column_match[ 2 ] . '"."' . $column_match[ 3 ] . '"'; + } + + return '"' . $string . '"'; + } + + protected function columnPush(&$columns) + { + if ($columns === '*') + { + return $columns; + } + + $stack = []; + + if (is_string($columns)) + { + $columns = [$columns]; + } + + foreach ($columns as $key => $value) + { + if (is_array($value)) + { + $stack[] = $this->columnPush($value); + } + else + { + preg_match('/(?[a-zA-Z0-9_\.]+)(?:\s*\((?[a-zA-Z0-9_]+)\)|\s*\[(?(String|Bool|Int|Number|Object|JSON))\])?/i', $value, $match); + + if (!empty($match[ 'alias' ])) + { + $stack[] = $this->columnQuote( $match[ 'column' ] ) . ' AS ' . $this->columnQuote( $match[ 'alias' ] ); + + $columns[ $key ] = $match[ 'alias' ]; + } + else + { + $stack[] = $this->columnQuote( $match[ 'column' ] ); + } + } + } + + return implode($stack, ','); + } + + protected function arrayQuote($array) + { + $stack = []; + + foreach ($array as $value) + { + $stack[] = is_int($value) ? $value : $this->pdo->quote($value); + } + + return implode($stack, ','); + } + + protected function innerConjunct($data, $map, $conjunctor, $outer_conjunctor) + { + $stack = []; + + foreach ($data as $value) + { + $stack[] = '(' . $this->dataImplode($value, $map, $conjunctor) . ')'; + } + + return implode($outer_conjunctor . ' ', $stack); + } + + protected function fnQuote($column, $string) + { + return (strpos($column, '#') === 0 && preg_match('/^[A-Z0-9\_]*\([^)]*\)$/', $string)) ? + + $string : + + $this->quote($string); + } + + protected function dataImplode($data, &$map, $conjunctor) + { + $wheres = []; + + foreach ($data as $key => $value) + { + $map_key = $this->mapKey(); + + $type = gettype($value); + + if ( + preg_match("/^(AND|OR)(\s+#.*)?$/i", $key, $relation_match) && + $type === 'array' + ) + { + $wheres[] = 0 !== count(array_diff_key($value, array_keys(array_keys($value)))) ? + '(' . $this->dataImplode($value, $map, ' ' . $relation_match[ 1 ]) . ')' : + '(' . $this->innerConjunct($value, $map, ' ' . $relation_match[ 1 ], $conjunctor) . ')'; + } + else + { + if ( + is_int($key) && + preg_match('/([a-zA-Z0-9_\.]+)\[(?\>|\>\=|\<|\<\=|\!|\=)\]([a-zA-Z0-9_\.]+)/i', $value, $match) + ) + { + $wheres[] = $this->columnQuote($match[ 1 ]) . ' ' . $match[ 'operator' ] . ' ' . $this->columnQuote($match[ 3 ]); + } + else + { + preg_match('/(#?)([a-zA-Z0-9_\.]+)(\[(?\>|\>\=|\<|\<\=|\!|\<\>|\>\<|\!?~)\])?/i', $key, $match); + $column = $this->columnQuote($match[ 2 ]); + + if (!empty($match[ 1 ])) + { + $wheres[] = $column . + (isset($match[ 'operator' ]) ? ' ' . $match[ 'operator' ] . ' ' : ' = ') . + $this->fnQuote($key, $value); + + continue; + } + + if (isset($match[ 'operator' ])) + { + $operator = $match[ 'operator' ]; + + if ($operator === '!') + { + switch ($type) + { + case 'NULL': + $wheres[] = $column . ' IS NOT NULL'; + break; + + case 'array': + $wheres[] = $column . ' NOT IN (' . $this->arrayQuote($value) . ')'; + break; + + case 'integer': + case 'double': + $wheres[] = $column . ' != ' . $map_key; + $map[ $map_key ] = [$value, PDO::PARAM_INT]; + break; + + case 'boolean': + $wheres[] = $column . ' != ' . $map_key; + $map[ $map_key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL]; + break; + + case 'string': + $wheres[] = $column . ' != ' . $map_key; + $map[ $map_key ] = [$value, PDO::PARAM_STR]; + break; + } + } + + if ($operator === '<>' || $operator === '><') + { + if ($type === 'array') + { + if ($operator === '><') + { + $column .= ' NOT'; + } + + $wheres[] = '(' . $column . ' BETWEEN ' . $map_key . 'a AND ' . $map_key . 'b)'; + + $data_type = (is_numeric($value[ 0 ]) && is_numeric($value[ 1 ])) ? PDO::PARAM_INT : PDO::PARAM_STR; + + $map[ $map_key . 'a' ] = [$value[ 0 ], $data_type]; + $map[ $map_key . 'b' ] = [$value[ 1 ], $data_type]; + } + } + + if ($operator === '~' || $operator === '!~') + { + if ($type !== 'array') + { + $value = [ $value ]; + } + + $connector = ' OR '; + $stack = array_values($value); + + if (is_array($stack[ 0 ])) + { + if (isset($value[ 'AND' ]) || isset($value[ 'OR' ])) + { + $connector = ' ' . array_keys($value)[ 0 ] . ' '; + $value = $stack[ 0 ]; + } + } + + $like_clauses = []; + + foreach ($value as $index => $item) + { + $item = strval($item); + + if (!preg_match('/(\[.+\]|_|%.+|.+%)/', $item)) + { + $item = '%' . $item . '%'; + } + + $like_clauses[] = $column . ($operator === '!~' ? ' NOT' : '') . ' LIKE ' . $map_key . 'L' . $index; + $map[ $map_key . 'L' . $index ] = [$item, PDO::PARAM_STR]; + } + + $wheres[] = '(' . implode($connector, $like_clauses) . ')'; + } + + if (in_array($operator, ['>', '>=', '<', '<='])) + { + $condition = $column . ' ' . $operator . ' '; + + if (is_numeric($value)) + { + $condition .= $map_key; + $map[ $map_key ] = [$value, PDO::PARAM_INT]; + } + else + { + $condition .= $map_key; + $map[ $map_key ] = [$value, PDO::PARAM_STR]; + } + + $wheres[] = $condition; + } + } + else + { + switch ($type) + { + case 'NULL': + $wheres[] = $column . ' IS NULL'; + break; + + case 'array': + $wheres[] = $column . ' IN (' . $this->arrayQuote($value) . ')'; + break; + + case 'integer': + case 'double': + $wheres[] = $column . ' = ' . $map_key; + $map[ $map_key ] = [$value, PDO::PARAM_INT]; + break; + + case 'boolean': + $wheres[] = $column . ' = ' . $map_key; + $map[ $map_key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL]; + break; + + case 'string': + $wheres[] = $column . ' = ' . $map_key; + $map[ $map_key ] = [$value, PDO::PARAM_STR]; + break; + } + } + } + } + } + + return implode($conjunctor . ' ', $wheres); + } + + protected function whereClause($where, &$map) + { + $where_clause = ''; + + if (is_array($where)) + { + $where_keys = array_keys($where); + $where_AND = preg_grep("/^AND\s*#?$/i", $where_keys); + $where_OR = preg_grep("/^OR\s*#?$/i", $where_keys); + + $single_condition = array_diff_key($where, array_flip( + ['AND', 'OR', 'GROUP', 'ORDER', 'HAVING', 'LIMIT', 'LIKE', 'MATCH'] + )); + + if (!empty($single_condition)) + { + $condition = $this->dataImplode($single_condition, $map, ' AND'); + + if ($condition !== '') + { + $where_clause = ' WHERE ' . $condition; + } + } + + if (!empty($where_AND)) + { + $value = array_values($where_AND); + $where_clause = ' WHERE ' . $this->dataImplode($where[ $value[ 0 ] ], $map, ' AND'); + } + + if (!empty($where_OR)) + { + $value = array_values($where_OR); + $where_clause = ' WHERE ' . $this->dataImplode($where[ $value[ 0 ] ], $map, ' OR'); + } + + if (isset($where[ 'MATCH' ])) + { + $MATCH = $where[ 'MATCH' ]; + + if (is_array($MATCH) && isset($MATCH[ 'columns' ], $MATCH[ 'keyword' ])) + { + $mode = ''; + + $mode_array = [ + 'natural' => 'IN NATURAL LANGUAGE MODE', + 'natural+query' => 'IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION', + 'boolean' => 'IN BOOLEAN MODE', + 'query' => 'WITH QUERY EXPANSION' + ]; + + if (isset($MATCH[ 'mode' ], $mode_array[ $MATCH[ 'mode' ] ])) + { + $mode = ' ' . $mode_array[ $MATCH[ 'mode' ] ]; + } + + $columns = implode(array_map([$this, 'columnQuote'], $MATCH[ 'columns' ]), ', '); + $map_key = $this->mapKey(); + $map[ $map_key ] = [$MATCH[ 'keyword' ], PDO::PARAM_STR]; + + $where_clause .= ($where_clause !== '' ? ' AND ' : ' WHERE') . ' MATCH (' . $columns . ') AGAINST (' . $map_key . $mode . ')'; + } + } + + if (isset($where[ 'GROUP' ])) + { + $GROUP = $where[ 'GROUP' ]; + + if (is_array($GROUP)) + { + $stack = []; + + foreach ($GROUP as $column => $value) + { + $stack[] = $this->columnQuote($value); + } + + $where_clause .= ' GROUP BY ' . implode($stack, ','); + } + else + { + $where_clause .= ' GROUP BY ' . $this->columnQuote($where[ 'GROUP' ]); + } + + if (isset($where[ 'HAVING' ])) + { + $where_clause .= ' HAVING ' . $this->dataImplode($where[ 'HAVING' ], $map, ' AND'); + } + } + + if (isset($where[ 'ORDER' ])) + { + $ORDER = $where[ 'ORDER' ]; + + if (is_array($ORDER)) + { + $stack = []; + + foreach ($ORDER as $column => $value) + { + if (is_array($value)) + { + $stack[] = 'FIELD(' . $this->columnQuote($column) . ', ' . $this->arrayQuote($value) . ')'; + } + else if ($value === 'ASC' || $value === 'DESC') + { + $stack[] = $this->columnQuote($column) . ' ' . $value; + } + else if (is_int($column)) + { + $stack[] = $this->columnQuote($value); + } + } + + $where_clause .= ' ORDER BY ' . implode($stack, ','); + } + else + { + $where_clause .= ' ORDER BY ' . $this->columnQuote($ORDER); + } + + if ( + isset($where[ 'LIMIT' ]) && + in_array($this->database_type, ['oracle', 'mssql']) + ) + { + $LIMIT = $where[ 'LIMIT' ]; + + if (is_numeric($LIMIT)) + { + $where_clause .= ' FETCH FIRST ' . $LIMIT . ' ROWS ONLY'; + } + + if ( + is_array($LIMIT) && + is_numeric($LIMIT[ 0 ]) && + is_numeric($LIMIT[ 1 ]) + ) + { + $where_clause .= ' OFFSET ' . $LIMIT[ 0 ] . ' ROWS FETCH NEXT ' . $LIMIT[ 1 ] . ' ROWS ONLY'; + } + } + } + + if (isset($where[ 'LIMIT' ]) && !in_array($this->database_type, ['oracle', 'mssql'])) + { + $LIMIT = $where[ 'LIMIT' ]; + + if (is_numeric($LIMIT)) + { + $where_clause .= ' LIMIT ' . $LIMIT; + } + + if ( + is_array($LIMIT) && + is_numeric($LIMIT[ 0 ]) && + is_numeric($LIMIT[ 1 ]) + ) + { + $where_clause .= ' LIMIT ' . $LIMIT[ 1 ] . ' OFFSET ' . $LIMIT[ 0 ]; + } + } + } + else + { + if ($where !== null) + { + $where_clause .= ' ' . $where; + } + } + + return $where_clause; + } + + protected function selectContext($table, &$map, $join, &$columns = null, $where = null, $column_fn = null) + { + preg_match('/(?[a-zA-Z0-9_]+)\s*\((?[a-zA-Z0-9_]+)\)/i', $table, $table_match); + + if (isset($table_match[ 'table' ], $table_match[ 'alias' ])) + { + $table = $this->tableQuote($table_match[ 'table' ]); + + $table_query = $table . ' AS ' . $this->tableQuote($table_match[ 'alias' ]); + } + else + { + $table = $this->tableQuote($table); + + $table_query = $table; + } + + $join_key = is_array($join) ? array_keys($join) : null; + + if ( + isset($join_key[ 0 ]) && + strpos($join_key[ 0 ], '[') === 0 + ) + { + $table_join = []; + + $join_array = [ + '>' => 'LEFT', + '<' => 'RIGHT', + '<>' => 'FULL', + '><' => 'INNER' + ]; + + foreach($join as $sub_table => $relation) + { + preg_match('/(\[(?\<|\>|\>\<|\<\>)\])?(?
[a-zA-Z0-9_]+)\s?(\((?[a-zA-Z0-9_]+)\))?/', $sub_table, $match); + + if ($match[ 'join' ] !== '' && $match[ 'table' ] !== '') + { + if (is_string($relation)) + { + $relation = 'USING ("' . $relation . '")'; + } + + if (is_array($relation)) + { + // For ['column1', 'column2'] + if (isset($relation[ 0 ])) + { + $relation = 'USING ("' . implode($relation, '", "') . '")'; + } + else + { + $joins = []; + + foreach ($relation as $key => $value) + { + $joins[] = ( + strpos($key, '.') > 0 ? + // For ['tableB.column' => 'column'] + $this->columnQuote($key) : + + // For ['column1' => 'column2'] + $table . '."' . $key . '"' + ) . + ' = ' . + $this->tableQuote(isset($match[ 'alias' ]) ? $match[ 'alias' ] : $match[ 'table' ]) . '."' . $value . '"'; + } + + $relation = 'ON ' . implode($joins, ' AND '); + } + } + + $table_name = $this->tableQuote($match[ 'table' ]) . ' '; + + if (isset($match[ 'alias' ])) + { + $table_name .= 'AS ' . $this->tableQuote($match[ 'alias' ]) . ' '; + } + + $table_join[] = $join_array[ $match[ 'join' ] ] . ' JOIN ' . $table_name . $relation; + } + } + + $table_query .= ' ' . implode($table_join, ' '); + } + else + { + if (is_null($columns)) + { + if (is_null($where)) + { + if ( + is_array($join) && + isset($column_fn) + ) + { + $where = $join; + $columns = null; + } + else + { + $where = null; + $columns = $join; + } + } + else + { + $where = $join; + $columns = null; + } + } + else + { + $where = $columns; + $columns = $join; + } + } + + if (isset($column_fn)) + { + if ($column_fn === 1) + { + $column = '1'; + + if (is_null($where)) + { + $where = $columns; + } + } + else + { + if (empty($columns)) + { + $columns = '*'; + $where = $join; + } + + $column = $column_fn . '(' . $this->columnPush($columns) . ')'; + } + } + else + { + $column = $this->columnPush($columns); + } + + return 'SELECT ' . $column . ' FROM ' . $table_query . $this->whereClause($where, $map); + } + + protected function columnMap($columns, &$stack) + { + if ($columns === '*') + { + return $stack; + } + + foreach ($columns as $key => $value) + { + if (is_int($key)) + { + preg_match('/(?[a-zA-Z0-9_\.]*)(?:\s*\((?[a-zA-Z0-9_]+)\)|\s*\[(?(String|Bool|Int|Number|Object|JSON))\])?/i', $value, $key_match); + + $column_key = !empty($key_match[ 'alias' ]) ? + $key_match[ 'alias' ] : + preg_replace('/^[\w]*\./i', '', $key_match[ 'column' ]); + + if (isset($key_match[ 'type' ])) + { + $stack[ $value ] = [$column_key, $key_match[ 'type' ]]; + } + else + { + $stack[ $value ] = [$column_key, 'String']; + } + } + else + { + $this->columnMap($value, $stack); + } + } + + return $stack; + } + + protected function dataMap($data, $columns, $column_map, &$stack) + { + foreach ($columns as $key => $value) + { + if (is_int($key)) + { + $map = $column_map[ $value ]; + $column_key = $map[ 0 ]; + + if (isset($map[ 1 ])) + { + switch ($map[ 1 ]) + { + case 'Number': + case 'Int': + $stack[ $column_key ] = (int) $data[ $column_key ]; + break; + + case 'Bool': + $stack[ $column_key ] = (bool) $data[ $column_key ]; + break; + + case 'Object': + $stack[ $column_key ] = unserialize($data[ $column_key ]); + break; + + case 'JSON': + $stack[ $column_key ] = json_decode($data[ $column_key ], true); + break; + + case 'String': + $stack[ $column_key ] = $data[ $column_key ]; + break; + } + } + else + { + $stack[ $column_key ] = $data[ $column_key ]; + } + } + else + { + $current_stack = []; + + $this->dataMap($data, $value, $column_map, $current_stack); + + $stack[ $key ] = $current_stack; + } + } + } + + public function select($table, $join, $columns = null, $where = null) + { + $map = []; + $stack = []; + $column_map = []; + + $index = 0; + + $column = $where === null ? $join : $columns; + + $is_single_column = (is_string($column) && $column !== '*'); + + $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where), $map); + + $this->columnMap($columns, $column_map); + + if (!$query) + { + return false; + } + + if ($columns === '*') + { + return $query->fetchAll(PDO::FETCH_ASSOC); + } + + if ($is_single_column) + { + return $query->fetchAll(PDO::FETCH_COLUMN); + } + + while ($data = $query->fetch(PDO::FETCH_ASSOC)) + { + $current_stack = []; + + $this->dataMap($data, $columns, $column_map, $current_stack); + + $stack[ $index ] = $current_stack; + + $index++; + } + + return $stack; + } + + public function insert($table, $datas) + { + $stack = []; + $columns = []; + $fields = []; + $map = []; + + if (!isset($datas[ 0 ])) + { + $datas = [$datas]; + } + + foreach ($datas as $data) + { + foreach ($data as $key => $value) + { + $columns[] = $key; + } + } + + $columns = array_unique($columns); + + foreach ($datas as $data) + { + $values = []; + + foreach ($columns as $key) + { + if (strpos($key, '#') === 0) + { + $values[] = $this->fnQuote($key, $data[ $key ]); + continue; + } + + $map_key =$this->mapKey(); + + $values[] = $map_key; + + if (!isset($data[ $key ])) + { + $map[ $map_key ] = [null, PDO::PARAM_NULL]; + } + else + { + $value = $data[ $key ]; + + switch (gettype($value)) + { + case 'NULL': + $map[ $map_key ] = [null, PDO::PARAM_NULL]; + break; + + case 'array': + $map[ $map_key ] = [ + strpos($key, '[JSON]') === strlen($key) - 6 ? + json_encode($value) : + serialize($value), + PDO::PARAM_STR + ]; + break; + + case 'object': + $map[ $map_key ] = [serialize($value), PDO::PARAM_STR]; + break; + + case 'resource': + $map[ $map_key ] = [$value, PDO::PARAM_LOB]; + break; + + case 'boolean': + $map[ $map_key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL]; + break; + + case 'integer': + case 'double': + $map[ $map_key ] = [$value, PDO::PARAM_INT]; + break; + + case 'string': + $map[ $map_key ] = [$value, PDO::PARAM_STR]; + break; + } + } + } + + $stack[] = '(' . implode($values, ', ') . ')'; + } + + foreach ($columns as $key) + { + $fields[] = $this->columnQuote(preg_replace("/(^#|\s*\[JSON\]$)/i", '', $key)); + } + + return $this->exec('INSERT INTO ' . $this->tableQuote($table) . ' (' . implode(', ', $fields) . ') VALUES ' . implode(', ', $stack), $map); + } + + public function update($table, $data, $where = null) + { + $fields = []; + $map = []; + + foreach ($data as $key => $value) + { + $column = $this->columnQuote(preg_replace("/(^#|\s*\[(JSON|\+|\-|\*|\/)\]$)/i", '', $key)); + + if (strpos($key, '#') === 0) + { + $fields[] = $column . ' = ' . $value; + continue; + } + + $map_key = $this->mapKey(); + + preg_match('/(?[a-zA-Z0-9_]+)(\[(?\+|\-|\*|\/)\])?/i', $key, $match); + + if (isset($match[ 'operator' ])) + { + if (is_numeric($value)) + { + $fields[] = $column . ' = ' . $column . ' ' . $match[ 'operator' ] . ' ' . $value; + } + } + else + { + $fields[] = $column . ' = ' . $map_key; + + switch (gettype($value)) + { + case 'NULL': + $map[ $map_key ] = [null, PDO::PARAM_NULL]; + break; + + case 'array': + $map[ $map_key ] = [ + strpos($key, '[JSON]') === strlen($key) - 6 ? + json_encode($value) : + serialize($value), + PDO::PARAM_STR + ]; + break; + + case 'object': + $map[ $map_key ] = [serialize($value), PDO::PARAM_STR]; + break; + + case 'resource': + $map[ $map_key ] = [$value, PDO::PARAM_LOB]; + break; + + case 'boolean': + $map[ $map_key ] = [($value ? '1' : '0'), PDO::PARAM_BOOL]; + break; + + case 'integer': + case 'double': + $map[ $map_key ] = [$value, PDO::PARAM_INT]; + break; + + case 'string': + $map[ $map_key ] = [$value, PDO::PARAM_STR]; + break; + } + } + } + + return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . implode(', ', $fields) . $this->whereClause($where, $map), $map); + } + + public function delete($table, $where) + { + $map = []; + + return $this->exec('DELETE FROM ' . $this->tableQuote($table) . $this->whereClause($where, $map), $map); + } + + public function replace($table, $columns, $where = null) + { + $map = []; + + if (is_array($columns)) + { + $replace_query = []; + + foreach ($columns as $column => $replacements) + { + if (is_array($replacements[ 0 ])) + { + foreach ($replacements as $replacement) + { + $map_key = $this->mapKey(); + + $replace_query[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)'; + + $map[ $map_key . 'a' ] = [$replacement[ 0 ], PDO::PARAM_STR]; + $map[ $map_key . 'b' ] = [$replacement[ 1 ], PDO::PARAM_STR]; + } + } + else + { + $map_key = $this->mapKey(); + + $replace_query[] = $this->columnQuote($column) . ' = REPLACE(' . $this->columnQuote($column) . ', ' . $map_key . 'a, ' . $map_key . 'b)'; + + $map[ $map_key . 'a' ] = [$replacements[ 0 ], PDO::PARAM_STR]; + $map[ $map_key . 'b' ] = [$replacements[ 1 ], PDO::PARAM_STR]; + } + } + + $replace_query = implode(', ', $replace_query); + } + + return $this->exec('UPDATE ' . $this->tableQuote($table) . ' SET ' . $replace_query . $this->whereClause($where, $map), $map); + } + + public function get($table, $join = null, $columns = null, $where = null) + { + $map = []; + $stack = []; + $column_map = []; + + $column = $where === null ? $join : $columns; + + $is_single_column = (is_string($column) && $column !== '*'); + + $query = $this->exec($this->selectContext($table, $map, $join, $columns, $where) . ' LIMIT 1', $map); + + if ($query) + { + $data = $query->fetchAll(PDO::FETCH_ASSOC); + + if (isset($data[ 0 ])) + { + if ($column === '*') + { + return $data[ 0 ]; + } + + $this->columnMap($columns, $column_map); + + $this->dataMap($data[ 0 ], $columns, $column_map, $stack); + + if ($is_single_column) + { + return $stack[ $column_map[ $column ][ 0 ] ]; + } + + return $stack; + } + else + { + return false; + } + } + else + { + return false; + } + } + + public function has($table, $join, $where = null) + { + $map = []; + $column = null; + + $query = $this->exec('SELECT EXISTS(' . $this->selectContext($table, $map, $join, $column, $where, 1) . ')', $map); + + if ($query) + { + return $query->fetchColumn() === '1'; + } + else + { + return false; + } + } + + public function count($table, $join = null, $column = null, $where = null) + { + $map = []; + + $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'COUNT'), $map); + + return $query ? 0 + $query->fetchColumn() : false; + } + + public function max($table, $join, $column = null, $where = null) + { + $map = []; + + $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'MAX'), $map); + + if ($query) + { + $max = $query->fetchColumn(); + + return is_numeric($max) ? $max + 0 : $max; + } + else + { + return false; + } + } + + public function min($table, $join, $column = null, $where = null) + { + $map = []; + + $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'MIN'), $map); + + if ($query) + { + $min = $query->fetchColumn(); + + return is_numeric($min) ? $min + 0 : $min; + } + else + { + return false; + } + } + + public function avg($table, $join, $column = null, $where = null) + { + $map = []; + + $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'AVG'), $map); + + return $query ? 0 + $query->fetchColumn() : false; + } + + public function sum($table, $join, $column = null, $where = null) + { + $map = []; + + $query = $this->exec($this->selectContext($table, $map, $join, $column, $where, 'SUM'), $map); + + return $query ? 0 + $query->fetchColumn() : false; + } + + public function action($actions) + { + if (is_callable($actions)) + { + $this->pdo->beginTransaction(); + + $result = $actions($this); + + if ($result === false) + { + $this->pdo->rollBack(); + } + else + { + $this->pdo->commit(); + } + } + else + { + return false; + } + } + + public function id() + { + $type = $this->database_type; + + if ($type === 'oracle') + { + return 0; + } + elseif ($type === 'mssql') + { + return $this->pdo->query('SELECT SCOPE_IDENTITY()')->fetchColumn(); + } + elseif ($type === 'pgsql') + { + return $this->pdo->query('SELECT LASTVAL()')->fetchColumn(); + } + + return $this->pdo->lastInsertId(); + } + + public function debug() + { + $this->debug_mode = true; + + return $this; + } + + public function error() + { + return $this->statement ? $this->statement->errorInfo() : null; + } + + public function last() + { + $log = end($this->logs); + + return $this->generate($log[ 0 ], $log[ 1 ]); + } + + public function log() + { + return array_map(function ($log) + { + return $this->generate($log[ 0 ], $log[ 1 ]); + }, + $this->logs + ); + } + + public function info() + { + $output = [ + 'server' => 'SERVER_INFO', + 'driver' => 'DRIVER_NAME', + 'client' => 'CLIENT_VERSION', + 'version' => 'SERVER_VERSION', + 'connection' => 'CONNECTION_STATUS' + ]; + + foreach ($output as $key => $value) + { + $output[ $key ] = @$this->pdo->getAttribute(constant('PDO::ATTR_' . $value)); + } + + return $output; + } +} +?> \ No newline at end of file diff --git a/lib/QueryList.php b/lib/QueryList.php new file mode 100644 index 0000000..e62d829 --- /dev/null +++ b/lib/QueryList.php @@ -0,0 +1,472 @@ +array('.unit h1','text'))); +print_r($hj->data); + +//回调函数1 +function callfun1($content,$key) +{ + return '回调函数1:'.$key.'-'.$content; +} +class HJ{ + //回调函数2 + static public function callfun2($content,$key) + { + return '回调函数2:'.$key.'-'.$content; + } +} +//获取CSDN文章页下面的文章标题和内容 +$url = 'http://www.csdn.net/article/2014-06-05/2820091-build-or-buy-a-mobile-game-backend'; +$rules = array( + 'title'=>array('h1','text','','callfun1'), //获取纯文本格式的标题,并调用回调函数1 + 'summary'=>array('.summary','text','-input strong'), //获取纯文本的文章摘要,但保strong标签并去除input标签 + 'content'=>array('.news_content','html','div a -.copyright'), //获取html格式的文章内容,但过滤掉div和a标签,去除类名为copyright的元素 + 'callback'=>array('HJ','callfun2') //调用回调函数2作为全局回调函数 + ); +$rang = '.left'; +$hj = QueryList::Query($url,$rules,$rang); +print_r($hj->data); + +//继续获取右边相关热门文章列表的标题以及链接地址 +$hj->setQuery(array('title'=>array('','text'),'url'=>array('a','href')),'#con_two_2 li'); +//输出数据 +echo $hj->getData(); + + */ + +class QueryList +{ + public $data; + public $html; + private $page; + private $pqHtml; + private $outputEncoding = false; + private $inputEncoding = false; + private $htmlEncoding; + public static $logger = null; + public static $instances; + + public function __construct() { + } + /** + * 静态方法,访问入口 + * @param string $page 要抓取的网页URL地址(支持https);或者是html源代码 + * @param array $rules 【选择器数组】说明:格式array("名称"=>array("选择器","类型"[,"标签过滤列表"][,"回调函数"]),.......[,"callback"=>"全局回调函数"]); + * 【选择器】说明:可以为任意的jQuery选择器语法 + * 【类型】说明:值 "text" ,"html" ,"HTML标签属性" , + * 【标签过滤列表】:可选,要过滤的选择器名,多个用空格隔开,当标签名前面添加减号(-)时(此时标签可以为任意的元素选择器),表示移除该标签以及标签内容,否则当【类型】值为text时表示需要保留的HTML标签,为html时表示要过滤掉的HTML标签 + * 【回调函数】/【全局回调函数】:可选,字符串(函数名) 或 数组(array("类名","类的静态方法")),回调函数应有俩个参数,第一个参数是选择到的内容,第二个参数是选择器数组下标,回调函数会覆盖全局回调函数 + * + * @param string $range 【块选择器】:指 先按照规则 选出 几个大块 ,然后再分别再在块里面 进行相关的选择 + * @param string $outputEncoding【输出编码格式】指要以什么编码输出(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则不改变原字符串编码 + * @param string $inputEncoding 【输入编码格式】明确指定输入的页面编码格式(UTF-8,GB2312,.....),防止出现乱码,如果设置为 假值 则自动识别 + * @param bool|false $removeHead 【是否移除页面头部区域】 乱码终极解决方案 + * @return mixed + */ + public static function Query($page,array $rules, $range = '', $outputEncoding = null, $inputEncoding = null,$removeHead = false) + { + return self::getInstance()->_query($page, $rules, $range, $outputEncoding, $inputEncoding,$removeHead); + } + + /** + * 运行QueryList扩展 + * @param $class + * @param array $args + * @return mixed + * @throws Exception + */ + public static function run($class,$args = array()) + { + $extension = self::getInstance("QL\\Ext\\{$class}"); + return $extension->run($args); + } + + /** + * 日志设置 + * @param $handler + */ + public static function setLog($handler) + { + if(class_exists('Monolog\Logger')) + { + if(is_string($handler)) + { + $handler = new StreamHandler($handler,Logger::INFO); + } + self::$logger = new Logger('QueryList'); + self::$logger->pushHandler($handler); + }else{ + throw new Exception("You need to install the package [monolog/monolog]"); + + } + + } + + /** + * 获取任意实例 + * @return mixed + * @throws Exception + */ + public static function getInstance() + { + $args = func_get_args(); + count($args) || $args = array('QL\QueryList'); + $key = md5(serialize($args)); + $className = array_shift($args); + if(!class_exists($className)) { + throw new Exception("no class {$className}"); + } + if(!isset(self::$instances[$key])) { + $rc = new ReflectionClass($className); + self::$instances[$key] = $rc->newInstanceArgs($args); + } + return self::$instances[$key]; + } + + /** + * 获取目标页面源码(主要用于调试) + * @param bool|true $rel + * @return string + */ + public function getHtml($rel = true) + { + return $rel?$this->qpHtml:$this->html; + } + + /** + * 获取采集结果数据 + * @param callback $callback + * @return array + */ + public function getData($callback = null) + { + if(is_callable($callback)){ + return array_map($callback,$this->data); + } + return $this->data; + } + + /** + * 重新设置选择器 + * @param $rules + * @param string $range + * @param string $outputEncoding + * @param string $inputEncoding + * @param bool|false $removeHead + * @return QueryList + */ + public function setQuery(array $rules, $range = '',$outputEncoding = null, $inputEncoding = null,$removeHead = false) + { + return $this->_query($this->html,$rules, $range, $outputEncoding, $inputEncoding,$removeHead); + } + + private function _query($page,array $rules, $range, $outputEncoding, $inputEncoding,$removeHead) + { + $this->data = array(); + $this->page = $page; + $this->html = $this->_isURL($this->page)?$this->_request($this->page):$this->page; + $outputEncoding && $this->outputEncoding = $outputEncoding; + $inputEncoding && $this->inputEncoding = $inputEncoding; + $removeHead && $this->html = $this->_removeHead($this->html); + $this->pqHtml = ''; + if(empty($this->html)){ + $this->_log('The received content is empty!','error'); + trigger_error('The received content is empty!',E_USER_NOTICE); + } + //获取编码格式 + $this->htmlEncoding = $this->inputEncoding?$this->inputEncoding:$this->_getEncode($this->html); + // $this->html = $this->_removeTags($this->html,array('script','style')); + $this->regArr = $rules; + $this->regRange = $range; + $this->_getList(); + $this->_log(); + return $this; + } + + private function _getList() + { + $this->inputEncoding && phpQuery::$defaultCharset = $this->inputEncoding; + $document = phpQuery::newDocumentHTML($this->html); + $this->qpHtml = $document->htmlOuter(); + if (!empty($this->regRange)) { + $robj = pq($document)->find($this->regRange); + $i = 0; + foreach ($robj as $item) { + while (list($key, $reg_value) = each($this->regArr)) { + if($key=='callback')continue; + $tags = isset($reg_value[2])?$reg_value[2]:''; + $iobj = pq($item)->find($reg_value[0]); + + switch ($reg_value[1]) { + case 'text': + $this->data[$i][$key] = $this->_allowTags(pq($iobj)->html(),$tags); + break; + case 'html': + $this->data[$i][$key] = $this->_stripTags(pq($iobj)->html(),$tags); + break; + default: + $this->data[$i][$key] = pq($iobj)->attr($reg_value[1]); + break; + } + + if(isset($reg_value[3])){ + $this->data[$i][$key] = call_user_func($reg_value[3],$this->data[$i][$key],$key); + }else if(isset($this->regArr['callback'])){ + $this->data[$i][$key] = call_user_func($this->regArr['callback'],$this->data[$i][$key],$key); + } + } + //重置数组指针 + reset($this->regArr); + $i++; + } + } else { + while (list($key, $reg_value) = each($this->regArr)) { + if($key=='callback')continue; + $document = phpQuery::newDocumentHTML($this->html); + $tags = isset($reg_value[2])?$reg_value[2]:''; + $lobj = pq($document)->find($reg_value[0]); + $i = 0; + foreach ($lobj as $item) { + switch ($reg_value[1]) { + case 'text': + $this->data[$i][$key] = $this->_allowTags(pq($item)->html(),$tags); + break; + case 'html': + $this->data[$i][$key] = $this->_stripTags(pq($item)->html(),$tags); + break; + default: + $this->data[$i][$key] = pq($item)->attr($reg_value[1]); + break; + } + + if(isset($reg_value[3])){ + $this->data[$i][$key] = call_user_func($reg_value[3],$this->data[$i][$key],$key); + }else if(isset($this->regArr['callback'])){ + $this->data[$i][$key] = call_user_func($this->regArr['callback'],$this->data[$i][$key],$key); + } + + $i++; + } + } + } + if ($this->outputEncoding) { + //编码转换 + $this->data = $this->_arrayConvertEncoding($this->data, $this->outputEncoding, $this->htmlEncoding); + } + phpQuery::$documents = array(); + } + + /** + * URL请求 + * @param $url + * @return string + */ + private function _request($url) + { + if(function_exists('curl_init')){ + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_AUTOREFERER, true); + curl_setopt($ch, CURLOPT_REFERER, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36'); + $result = curl_exec($ch); + curl_close($ch); + }elseif(version_compare(PHP_VERSION, '5.0.0')>=0){ + $opts = array( + 'http' => array( + 'header' => "Referer:{$url}" + ) + ); + $result = file_get_contents($url,false,stream_context_create($opts)); + }else{ + $result = file_get_contents($url); + } + return $result; + } + + /** + * 移除页面head区域代码 + * @param $html + * @return mixed + */ + private function _removeHead($html) + { + return preg_replace('/.+<\/head>/is','',$html); + } + + /** + * 获取文件编码 + * @param $string + * @return string + */ + private function _getEncode($string) + { + return mb_detect_encoding($string, array('ASCII', 'GB2312', 'GBK', 'UTF-8')); + } + + /** + * 转换数组值的编码格式 + * @param array $arr + * @param string $toEncoding + * @param string $fromEncoding + * @return array + */ + private function _arrayConvertEncoding($arr, $toEncoding, $fromEncoding) + { + eval('$arr = '.iconv($fromEncoding, $toEncoding.'//IGNORE', var_export($arr,TRUE)).';'); + return $arr; + } + + /** + * 简单的判断一下参数是否为一个URL链接 + * @param string $str + * @return boolean + */ + private function _isURL($str) + { + if (preg_match('/^http(s)?:\\/\\/.+/', $str)) { + return true; + } + return false; + } + + /** + * 去除特定的html标签 + * @param string $html + * @param string $tags_str 多个标签名之间用空格隔开 + * @return string + */ + private function _stripTags($html,$tags_str) + { + $tagsArr = $this->_tag($tags_str); + $html = $this->_removeTags($html,$tagsArr[1]); + $p = array(); + foreach ($tagsArr[0] as $tag) { + $p[]="/(<(?:\/".$tag."|".$tag.")[^>]*>)/i"; + } + $html = preg_replace($p,"",trim($html)); + return $html; + } + + /** + * 保留特定的html标签 + * @param string $html + * @param string $tags_str 多个标签名之间用空格隔开 + * @return string + */ + private function _allowTags($html,$tags_str) + { + $tagsArr = $this->_tag($tags_str); + $html = $this->_removeTags($html,$tagsArr[1]); + $allow = ''; + foreach ($tagsArr[0] as $tag) { + $allow .= "<$tag> "; + } + return strip_tags(trim($html),$allow); + } + + private function _tag($tags_str) + { + $tagArr = preg_split("/\s+/",$tags_str,-1,PREG_SPLIT_NO_EMPTY); + $tags = array(array(),array()); + foreach($tagArr as $tag) + { + if(preg_match('/-(.+)/', $tag,$arr)) + { + array_push($tags[1], $arr[1]); + }else{ + array_push($tags[0], $tag); + } + } + return $tags; + } + + /** + * 移除特定的html标签 + * @param string $html + * @param array $tags 标签数组 + * @return string + */ + private function _removeTags($html,$tags) + { + $tag_str = ''; + if(count($tags)) + { + foreach ($tags as $tag) { + $tag_str .= $tag_str?','.$tag:$tag; + } + phpQuery::$defaultCharset = $this->inputEncoding?$this->inputEncoding:$this->htmlEncoding; + $doc = phpQuery::newDocumentHTML($html); + pq($doc)->find($tag_str)->remove(); + $html = pq($doc)->htmlOuter(); + $doc->unloadDocument(); + } + return $html; + } + + /** + * 打印日志 + * @param string $message + * @param string $level + */ + private function _log($message = '',$level = 'info') + { + if(!is_null(self::$logger)) + { + $url = $this->_isURL($this->page)?$this->page:'[html]'; + $count = count($this->data); + $level = empty($level)?($count?'info':'warning'):$level; + $message = empty($message)?($count?'Get data successfully':'Get data failed'):$message; + self::$logger->$level($message,array( + 'page' => $url, + 'count' => $count + )); + } + } +} + +/* +class Autoload +{ + public static function load($className) + { + $files = array( + sprintf('%s/extensions/%s.php',__DIR__,$className), + sprintf('%s/extensions/vendors/%s.php',__DIR__,$className) + ); + foreach ($files as $file) { + if(is_file($file)){ + require $file; + return true; + } + } + return false; + } +} + +spl_autoload_register(array('Autoload','load')); + +*/ diff --git a/lib/Snoopy.class.php b/lib/Snoopy.class.php new file mode 100755 index 0000000..5311610 --- /dev/null +++ b/lib/Snoopy.class.php @@ -0,0 +1,1250 @@ + +Copyright (c): 1999-2008 New Digital Group, all rights reserved +Version: 1.2.4 + + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You may contact the author of Snoopy by e-mail at: +monte@ohrt.com + +The latest version of Snoopy can be obtained from: +http://snoopy.sourceforge.net/ + +*************************************************/ + +class Snoopy +{ + /**** Public variables ****/ + + /* user definable vars */ + + var $host = "www.php.net"; // host name we are connecting to + var $port = 80; // port we are connecting to + var $proxy_host = ""; // proxy host to use + var $proxy_port = ""; // proxy port to use + var $proxy_user = ""; // proxy user to use + var $proxy_pass = ""; // proxy password to use + + var $agent = "Snoopy v1.2.4"; // agent we masquerade as + var $referer = ""; // referer info to pass + var $cookies = array(); // array of cookies to pass + // $cookies["username"]="joe"; + var $rawheaders = array(); // array of raw headers to send + // $rawheaders["Content-type"]="text/html"; + + var $maxredirs = 5; // http redirection depth maximum. 0 = disallow + var $lastredirectaddr = ""; // contains address of last redirected address + var $offsiteok = true; // allows redirection off-site + var $maxframes = 0; // frame content depth maximum. 0 = disallow + var $expandlinks = true; // expand links to fully qualified URLs. + // this only applies to fetchlinks() + // submitlinks(), and submittext() + var $passcookies = true; // pass set cookies back through redirects + // NOTE: this currently does not respect + // dates, domains or paths. + + var $user = ""; // user for http authentication + var $pass = ""; // password for http authentication + + // http accept types + var $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; + + var $results = ""; // where the content is put + + var $error = ""; // error messages sent here + var $response_code = ""; // response code returned from server + var $headers = array(); // headers returned from server sent here + var $maxlength = 500000; // max return data length (body) + var $read_timeout = 0; // timeout on read operations, in seconds + // supported only since PHP 4 Beta 4 + // set to 0 to disallow timeouts + var $timed_out = false; // if a read operation timed out + var $status = 0; // http request status + + var $temp_dir = "/tmp"; // temporary directory that the webserver + // has permission to write to. + // under Windows, this should be C:\temp + + var $curl_path = "/usr/local/bin/curl"; + // Snoopy will use cURL for fetching + // SSL content if a full system path to + // the cURL binary is supplied here. + // set to false if you do not have + // cURL installed. See http://curl.haxx.se + // for details on installing cURL. + // Snoopy does *not* use the cURL + // library functions built into php, + // as these functions are not stable + // as of this Snoopy release. + + /**** Private variables ****/ + + var $_maxlinelen = 4096; // max line length (headers) + + var $_httpmethod = "GET"; // default http request method + var $_httpversion = "HTTP/1.0"; // default http request version + var $_submit_method = "POST"; // default submit method + var $_submit_type = "application/x-www-form-urlencoded"; // default submit type + var $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type + var $_redirectaddr = false; // will be set if page fetched is a redirect + var $_redirectdepth = 0; // increments on an http redirect + var $_frameurls = array(); // frame src urls + var $_framedepth = 0; // increments on frame depth + + var $_isproxy = false; // set if using a proxy server + var $_fp_timeout = 30; // timeout for socket connection + +/*======================================================================*\ + Function: fetch + Purpose: fetch the contents of a web page + (and possibly other protocols in the + future like ftp, nntp, gopher, etc.) + Input: $URI the location of the page to fetch + Output: $this->results the output text from the fetch +\*======================================================================*/ + + function fetch($URI) + { + + //preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS); + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) + $this->user = $URI_PARTS["user"]; + if (!empty($URI_PARTS["pass"])) + $this->pass = $URI_PARTS["pass"]; + if (empty($URI_PARTS["query"])) + $URI_PARTS["query"] = ''; + if (empty($URI_PARTS["path"])) + $URI_PARTS["path"] = ''; + + switch(strtolower($URI_PARTS["scheme"])) + { + case "http": + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_connect($fp)) + { + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httprequest($URI,$fp,$URI,$this->_httpmethod); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_httpmethod); + } + + $this->_disconnect($fp); + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + } + else + { + return false; + } + return true; + break; + case "https": + if(!$this->curl_path) + return false; + if(function_exists("is_executable")) + if (!is_executable($this->curl_path)) + return false; + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httpsrequest($URI,$URI,$this->_httpmethod); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httpsrequest($path, $URI, $this->_httpmethod); + } + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + return true; + break; + default: + // not a valid protocol + $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; + return false; + break; + } + return true; + } + +/*======================================================================*\ + Function: submit + Purpose: submit an http form + Input: $URI the location to post the data + $formvars the formvars to use. + format: $formvars["var"] = "val"; + $formfiles an array of files to submit + format: $formfiles["var"] = "/dir/filename.ext"; + Output: $this->results the text output from the post +\*======================================================================*/ + + function submit($URI, $formvars="", $formfiles="") + { + unset($postdata); + + $postdata = $this->_prepare_post_body($formvars, $formfiles); + + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) + $this->user = $URI_PARTS["user"]; + if (!empty($URI_PARTS["pass"])) + $this->pass = $URI_PARTS["pass"]; + if (empty($URI_PARTS["query"])) + $URI_PARTS["query"] = ''; + if (empty($URI_PARTS["path"])) + $URI_PARTS["path"] = ''; + + switch(strtolower($URI_PARTS["scheme"])) + { + case "http": + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_connect($fp)) + { + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httprequest($URI,$fp,$URI,$this->_submit_method,$this->_submit_type,$postdata); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + $this->_disconnect($fp); + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr)) + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]); + + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + if( strpos( $this->_redirectaddr, "?" ) > 0 ) + $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get + else + $this->submit($this->_redirectaddr,$formvars, $formfiles); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + + } + else + { + return false; + } + return true; + break; + case "https": + if(!$this->curl_path) + return false; + if(function_exists("is_executable")) + if (!is_executable($this->curl_path)) + return false; + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httpsrequest($URI, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httpsrequest($path, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr)) + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]); + + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + if( strpos( $this->_redirectaddr, "?" ) > 0 ) + $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get + else + $this->submit($this->_redirectaddr,$formvars, $formfiles); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + return true; + break; + + default: + // not a valid protocol + $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; + return false; + break; + } + return true; + } + +/*======================================================================*\ + Function: fetchlinks + Purpose: fetch the links from a web page + Input: $URI where you are fetching from + Output: $this->results an array of the URLs +\*======================================================================*/ + + function fetchlinks($URI) + { + if ($this->fetch($URI)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_striplinks($this->results[$x]); + } + else + $this->results = $this->_striplinks($this->results); + + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results, $URI); + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: fetchform + Purpose: fetch the form elements from a web page + Input: $URI where you are fetching from + Output: $this->results the resulting html form +\*======================================================================*/ + + function fetchform($URI) + { + + if ($this->fetch($URI)) + { + + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_stripform($this->results[$x]); + } + else + $this->results = $this->_stripform($this->results); + + return true; + } + else + return false; + } + + +/*======================================================================*\ + Function: fetchtext + Purpose: fetch the text from a web page, stripping the links + Input: $URI where you are fetching from + Output: $this->results the text from the web page +\*======================================================================*/ + + function fetchtext($URI) + { + if($this->fetch($URI)) + { + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + $this->results[$x] = $this->_striptext($this->results[$x]); + } + else + $this->results = $this->_striptext($this->results); + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: submitlinks + Purpose: grab links from a form submission + Input: $URI where you are submitting from + Output: $this->results an array of the links from the post +\*======================================================================*/ + + function submitlinks($URI, $formvars="", $formfiles="") + { + if($this->submit($URI,$formvars, $formfiles)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + { + $this->results[$x] = $this->_striplinks($this->results[$x]); + if($this->expandlinks) + $this->results[$x] = $this->_expandlinks($this->results[$x],$URI); + } + } + else + { + $this->results = $this->_striplinks($this->results); + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results,$URI); + } + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: submittext + Purpose: grab text from a form submission + Input: $URI where you are submitting from + Output: $this->results the text from the web page +\*======================================================================*/ + + function submittext($URI, $formvars = "", $formfiles = "") + { + if($this->submit($URI,$formvars, $formfiles)) + { + if($this->lastredirectaddr) + $URI = $this->lastredirectaddr; + if(is_array($this->results)) + { + for($x=0;$xresults);$x++) + { + $this->results[$x] = $this->_striptext($this->results[$x]); + if($this->expandlinks) + $this->results[$x] = $this->_expandlinks($this->results[$x],$URI); + } + } + else + { + $this->results = $this->_striptext($this->results); + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results,$URI); + } + return true; + } + else + return false; + } + + + +/*======================================================================*\ + Function: set_submit_multipart + Purpose: Set the form submission content type to + multipart/form-data +\*======================================================================*/ + function set_submit_multipart() + { + $this->_submit_type = "multipart/form-data"; + } + + +/*======================================================================*\ + Function: set_submit_normal + Purpose: Set the form submission content type to + application/x-www-form-urlencoded +\*======================================================================*/ + function set_submit_normal() + { + $this->_submit_type = "application/x-www-form-urlencoded"; + } + + + + +/*======================================================================*\ + Private functions +\*======================================================================*/ + + +/*======================================================================*\ + Function: _striplinks + Purpose: strip the hyperlinks from an html document + Input: $document document to strip. + Output: $match an array of the links +\*======================================================================*/ + + function _striplinks($document) + { + preg_match_all("'<\s*a\s.*?href\s*=\s* # find ]+)) # if quote found, match up to next matching + # quote, otherwise match up to next space + 'isx",$document,$links); + + + // catenate the non-empty matches from the conditional subpattern + + while(list($key,$val) = each($links[2])) + { + if(!empty($val)) + $match[] = $val; + } + + while(list($key,$val) = each($links[3])) + { + if(!empty($val)) + $match[] = $val; + } + + // return the links + return $match; + } + +/*======================================================================*\ + Function: _stripform + Purpose: strip the form elements from an html document + Input: $document document to strip. + Output: $match an array of the links +\*======================================================================*/ + + function _stripform($document) + { + preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi",$document,$elements); + + // catenate the matches + $match = implode("\r\n",$elements[0]); + + // return the links + return $match; + } + + + +/*======================================================================*\ + Function: _striptext + Purpose: strip the text from an html document + Input: $document document to strip. + Output: $text the resulting text +\*======================================================================*/ + + function _striptext($document) + { + + // I didn't use preg eval (//e) since that is only available in PHP 4.0. + // so, list your entities one by one here. I included some of the + // more common ones. + + $search = array("']*?>.*?'si", // strip out javascript + "'<[\/\!]*?[^<>]*?>'si", // strip out html tags + "'([\r\n])[\s]+'", // strip out white space + "'&(quot|#34|#034|#x22);'i", // replace html entities + "'&(amp|#38|#038|#x26);'i", // added hexadecimal values + "'&(lt|#60|#060|#x3c);'i", + "'&(gt|#62|#062|#x3e);'i", + "'&(nbsp|#160|#xa0);'i", + "'&(iexcl|#161);'i", + "'&(cent|#162);'i", + "'&(pound|#163);'i", + "'&(copy|#169);'i", + "'&(reg|#174);'i", + "'&(deg|#176);'i", + "'&(#39|#039|#x27);'", + "'&(euro|#8364);'i", // europe + "'&a(uml|UML);'", // german + "'&o(uml|UML);'", + "'&u(uml|UML);'", + "'&A(uml|UML);'", + "'&O(uml|UML);'", + "'&U(uml|UML);'", + "'ß'i", + ); + $replace = array( "", + "", + "\\1", + "\"", + "&", + "<", + ">", + " ", + chr(161), + chr(162), + chr(163), + chr(169), + chr(174), + chr(176), + chr(39), + chr(128), + "", + "", + "", + "", + "", + "", + "", + ); + + $text = preg_replace($search,$replace,$document); + + return $text; + } + +/*======================================================================*\ + Function: _expandlinks + Purpose: expand each link into a fully qualified URL + Input: $links the links to qualify + $URI the full URI to get the base from + Output: $expandedLinks the expanded links +\*======================================================================*/ + + function _expandlinks($links,$URI) + { + + preg_match("/^[^\?]+/",$URI,$match); + + $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|","",$match[0]); + $match = preg_replace("|/$|","",$match); + $match_part = parse_url($match); + $match_root = + $match_part["scheme"]."://".$match_part["host"]; + + $search = array( "|^http://".preg_quote($this->host)."|i", + "|^(\/)|i", + "|^(?!http://)(?!mailto:)|i", + "|/\./|", + "|/[^\/]+/\.\./|" + ); + + $replace = array( "", + $match_root."/", + $match."/", + "/", + "/" + ); + + $expandedLinks = preg_replace($search,$replace,$links); + + return $expandedLinks; + } + +/*======================================================================*\ + Function: _httprequest + Purpose: go get the http data from the server + Input: $url the url to fetch + $fp the current open file pointer + $URI the full URI + $body body contents to send if any (POST) + Output: +\*======================================================================*/ + + function _httprequest($url,$fp,$URI,$http_method,$content_type="",$body="") + { + $cookie_headers = ''; + if($this->passcookies && $this->_redirectaddr) + $this->setcookies(); + + $URI_PARTS = parse_url($URI); + if(empty($url)) + $url = "/"; + $headers = $http_method." ".$url." ".$this->_httpversion."\r\n"; + if(!empty($this->agent)) + $headers .= "User-Agent: ".$this->agent."\r\n"; + if(!empty($this->host) && !isset($this->rawheaders['Host'])) { + $headers .= "Host: ".$this->host; + if(!empty($this->port)) + $headers .= ":".$this->port; + $headers .= "\r\n"; + } + if(!empty($this->accept)) + $headers .= "Accept: ".$this->accept."\r\n"; + if(!empty($this->referer)) + $headers .= "Referer: ".$this->referer."\r\n"; + if(!empty($this->cookies)) + { + if(!is_array($this->cookies)) + $this->cookies = (array)$this->cookies; + + reset($this->cookies); + if ( count($this->cookies) > 0 ) { + $cookie_headers .= 'Cookie: '; + foreach ( $this->cookies as $cookieKey => $cookieVal ) { + $cookie_headers .= $cookieKey."=".urlencode($cookieVal)."; "; + } + $headers .= substr($cookie_headers,0,-2) . "\r\n"; + } + } + if(!empty($this->rawheaders)) + { + if(!is_array($this->rawheaders)) + $this->rawheaders = (array)$this->rawheaders; + while(list($headerKey,$headerVal) = each($this->rawheaders)) + $headers .= $headerKey.": ".$headerVal."\r\n"; + } + if(!empty($content_type)) { + $headers .= "Content-type: $content_type"; + if ($content_type == "multipart/form-data") + $headers .= "; boundary=".$this->_mime_boundary; + $headers .= "\r\n"; + } + if(!empty($body)) + $headers .= "Content-length: ".strlen($body)."\r\n"; + if(!empty($this->user) || !empty($this->pass)) + $headers .= "Authorization: Basic ".base64_encode($this->user.":".$this->pass)."\r\n"; + + //add proxy auth headers + if(!empty($this->proxy_user)) + $headers .= 'Proxy-Authorization: ' . 'Basic ' . base64_encode($this->proxy_user . ':' . $this->proxy_pass)."\r\n"; + + + $headers .= "\r\n"; + + // set the read timeout if needed + if ($this->read_timeout > 0) + socket_set_timeout($fp, $this->read_timeout); + $this->timed_out = false; + + fwrite($fp,$headers.$body,strlen($headers.$body)); + + $this->_redirectaddr = false; + unset($this->headers); + + while($currentHeader = fgets($fp,$this->_maxlinelen)) + { + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) + { + $this->status=-100; + return false; + } + + if($currentHeader == "\r\n") + break; + + // if a header begins with Location: or URI:, set the redirect + if(preg_match("/^(Location:|URI:)/i",$currentHeader)) + { + // get URL portion of the redirect + preg_match("/^(Location:|URI:)[ ]+(.*)/i",chop($currentHeader),$matches); + // look for :// in the Location header to see if hostname is included + if(!preg_match("|\:\/\/|",$matches[2])) + { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; + // eliminate double slash + if(!preg_match("|^/|",$matches[2])) + $this->_redirectaddr .= "/".$matches[2]; + else + $this->_redirectaddr .= $matches[2]; + } + else + $this->_redirectaddr = $matches[2]; + } + + if(preg_match("|^HTTP/|",$currentHeader)) + { + if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status)) + { + $this->status= $status[1]; + } + $this->response_code = $currentHeader; + } + + $this->headers[] = $currentHeader; + } + + $results = ''; + do { + $_data = fread($fp, $this->maxlength); + if (strlen($_data) == 0) { + break; + } + $results .= $_data; + } while(true); + + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) + { + $this->status=-100; + return false; + } + + // check if there is a a redirect meta tag + + if(preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) + + { + $this->_redirectaddr = $this->_expandlinks($match[1],$URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if(($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i",$results,$match)) + { + $this->results[] = $results; + for($x=0; $x_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); + } + // have we already fetched framed content? + elseif(is_array($this->results)) + $this->results[] = $results; + // no framed content + else + $this->results = $results; + + return true; + } + +/*======================================================================*\ + Function: _httpsrequest + Purpose: go get the https data from the server using curl + Input: $url the url to fetch + $URI the full URI + $body body contents to send if any (POST) + Output: +\*======================================================================*/ + + function _httpsrequest($url,$URI,$http_method,$content_type="",$body="") + { + if($this->passcookies && $this->_redirectaddr) + $this->setcookies(); + + $headers = array(); + + $URI_PARTS = parse_url($URI); + if(empty($url)) + $url = "/"; + // GET ... header not needed for curl + //$headers[] = $http_method." ".$url." ".$this->_httpversion; + if(!empty($this->agent)) + $headers[] = "User-Agent: ".$this->agent; + if(!empty($this->host)) + if(!empty($this->port)) + $headers[] = "Host: ".$this->host.":".$this->port; + else + $headers[] = "Host: ".$this->host; + if(!empty($this->accept)) + $headers[] = "Accept: ".$this->accept; + if(!empty($this->referer)) + $headers[] = "Referer: ".$this->referer; + if(!empty($this->cookies)) + { + if(!is_array($this->cookies)) + $this->cookies = (array)$this->cookies; + + reset($this->cookies); + if ( count($this->cookies) > 0 ) { + $cookie_str = 'Cookie: '; + foreach ( $this->cookies as $cookieKey => $cookieVal ) { + $cookie_str .= $cookieKey."=".urlencode($cookieVal)."; "; + } + $headers[] = substr($cookie_str,0,-2); + } + } + if(!empty($this->rawheaders)) + { + if(!is_array($this->rawheaders)) + $this->rawheaders = (array)$this->rawheaders; + while(list($headerKey,$headerVal) = each($this->rawheaders)) + $headers[] = $headerKey.": ".$headerVal; + } + if(!empty($content_type)) { + if ($content_type == "multipart/form-data") + $headers[] = "Content-type: $content_type; boundary=".$this->_mime_boundary; + else + $headers[] = "Content-type: $content_type"; + } + if(!empty($body)) + $headers[] = "Content-length: ".strlen($body); + if(!empty($this->user) || !empty($this->pass)) + $headers[] = "Authorization: BASIC ".base64_encode($this->user.":".$this->pass); + + for($curr_header = 0; $curr_header < count($headers); $curr_header++) { + $safer_header = strtr( $headers[$curr_header], "\"", " " ); + $cmdline_params .= " -H \"".$safer_header."\""; + } + + if(!empty($body)) + $cmdline_params .= " -d \"$body\""; + + if($this->read_timeout > 0) + $cmdline_params .= " -m ".$this->read_timeout; + + $headerfile = tempnam($temp_dir, "sno"); + + exec($this->curl_path." -k -D \"$headerfile\"".$cmdline_params." \"".escapeshellcmd($URI)."\"",$results,$return); + + if($return) + { + $this->error = "Error: cURL could not retrieve the document, error $return."; + return false; + } + + + $results = implode("\r\n",$results); + + $result_headers = file("$headerfile"); + + $this->_redirectaddr = false; + unset($this->headers); + + for($currentHeader = 0; $currentHeader < count($result_headers); $currentHeader++) + { + + // if a header begins with Location: or URI:, set the redirect + if(preg_match("/^(Location: |URI: )/i",$result_headers[$currentHeader])) + { + // get URL portion of the redirect + preg_match("/^(Location: |URI:)\s+(.*)/",chop($result_headers[$currentHeader]),$matches); + // look for :// in the Location header to see if hostname is included + if(!preg_match("|\:\/\/|",$matches[2])) + { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; + // eliminate double slash + if(!preg_match("|^/|",$matches[2])) + $this->_redirectaddr .= "/".$matches[2]; + else + $this->_redirectaddr .= $matches[2]; + } + else + $this->_redirectaddr = $matches[2]; + } + + if(preg_match("|^HTTP/|",$result_headers[$currentHeader])) + $this->response_code = $result_headers[$currentHeader]; + + $this->headers[] = $result_headers[$currentHeader]; + } + + // check if there is a a redirect meta tag + + if(preg_match("']*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) + { + $this->_redirectaddr = $this->_expandlinks($match[1],$URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if(($this->_framedepth < $this->maxframes) && preg_match_all("']+)'i",$results,$match)) + { + $this->results[] = $results; + for($x=0; $x_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); + } + // have we already fetched framed content? + elseif(is_array($this->results)) + $this->results[] = $results; + // no framed content + else + $this->results = $results; + + unlink("$headerfile"); + + return true; + } + +/*======================================================================*\ + Function: setcookies() + Purpose: set cookies for a redirection +\*======================================================================*/ + + function setcookies() + { + for($x=0; $xheaders); $x++) + { + if(preg_match('/^set-cookie:[\s]+([^=]+)=([^;]+)/i', $this->headers[$x],$match)) + $this->cookies[$match[1]] = urldecode($match[2]); + } + } + + +/*======================================================================*\ + Function: _check_timeout + Purpose: checks whether timeout has occurred + Input: $fp file pointer +\*======================================================================*/ + + function _check_timeout($fp) + { + if ($this->read_timeout > 0) { + $fp_status = socket_get_status($fp); + if ($fp_status["timed_out"]) { + $this->timed_out = true; + return true; + } + } + return false; + } + +/*======================================================================*\ + Function: _connect + Purpose: make a socket connection + Input: $fp file pointer +\*======================================================================*/ + + function _connect(&$fp) + { + if(!empty($this->proxy_host) && !empty($this->proxy_port)) + { + $this->_isproxy = true; + + $host = $this->proxy_host; + $port = $this->proxy_port; + } + else + { + $host = $this->host; + $port = $this->port; + } + + $this->status = 0; + + if($fp = fsockopen( + $host, + $port, + $errno, + $errstr, + $this->_fp_timeout + )) + { + // socket connection succeeded + + return true; + } + else + { + // socket connection failed + $this->status = $errno; + switch($errno) + { + case -3: + $this->error="socket creation failed (-3)"; + case -4: + $this->error="dns lookup failure (-4)"; + case -5: + $this->error="connection refused or timed out (-5)"; + default: + $this->error="connection failed (".$errno.")"; + } + return false; + } + } +/*======================================================================*\ + Function: _disconnect + Purpose: disconnect a socket connection + Input: $fp file pointer +\*======================================================================*/ + + function _disconnect($fp) + { + return(fclose($fp)); + } + + +/*======================================================================*\ + Function: _prepare_post_body + Purpose: Prepare post body according to encoding type + Input: $formvars - form variables + $formfiles - form upload files + Output: post body +\*======================================================================*/ + + function _prepare_post_body($formvars, $formfiles) + { + settype($formvars, "array"); + settype($formfiles, "array"); + $postdata = ''; + + if (count($formvars) == 0 && count($formfiles) == 0) + return; + + switch ($this->_submit_type) { + case "application/x-www-form-urlencoded": + reset($formvars); + while(list($key,$val) = each($formvars)) { + if (is_array($val) || is_object($val)) { + while (list($cur_key, $cur_val) = each($val)) { + $postdata .= urlencode($key)."[]=".urlencode($cur_val)."&"; + } + } else + $postdata .= urlencode($key)."=".urlencode($val)."&"; + } + break; + + case "multipart/form-data": + $this->_mime_boundary = "Snoopy".md5(uniqid(microtime())); + + reset($formvars); + while(list($key,$val) = each($formvars)) { + if (is_array($val) || is_object($val)) { + while (list($cur_key, $cur_val) = each($val)) { + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n"; + $postdata .= "$cur_val\r\n"; + } + } else { + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n"; + $postdata .= "$val\r\n"; + } + } + + reset($formfiles); + while (list($field_name, $file_names) = each($formfiles)) { + settype($file_names, "array"); + while (list(, $file_name) = each($file_names)) { + if (!is_readable($file_name)) continue; + + $fp = fopen($file_name, "r"); + $file_content = fread($fp, filesize($file_name)); + fclose($fp); + $base_name = basename($file_name); + + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n"; + $postdata .= "$file_content\r\n"; + } + } + $postdata .= "--".$this->_mime_boundary."--\r\n"; + break; + } + + return $postdata; + } +} + +?> diff --git a/lib/phpQuery.php b/lib/phpQuery.php new file mode 100644 index 0000000..8b8f78a --- /dev/null +++ b/lib/phpQuery.php @@ -0,0 +1,5742 @@ + + * @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'); + +/** + * DOMEvent class. + * + * Based on + * @link http://developer.mozilla.org/En/DOM:event + * @author Tobiasz Cudnik + * @package phpQuery + * @todo implement ArrayAccess ? + */ +class DOMEvent { + /** + * Returns a boolean indicating whether the event bubbles up through the DOM or not. + * + * @var unknown_type + */ + public $bubbles = true; + /** + * Returns a boolean indicating whether the event is cancelable. + * + * @var unknown_type + */ + public $cancelable = true; + /** + * Returns a reference to the currently registered target for the event. + * + * @var unknown_type + */ + public $currentTarget; + /** + * Returns detail about the event, depending on the type of event. + * + * @var unknown_type + * @link http://developer.mozilla.org/en/DOM/event.detail + */ + public $detail; // ??? + /** + * Used to indicate which phase of the event flow is currently being evaluated. + * + * NOT IMPLEMENTED + * + * @var unknown_type + * @link http://developer.mozilla.org/en/DOM/event.eventPhase + */ + public $eventPhase; // ??? + /** + * The explicit original target of the event (Mozilla-specific). + * + * NOT IMPLEMENTED + * + * @var unknown_type + */ + public $explicitOriginalTarget; // moz only + /** + * The original target of the event, before any retargetings (Mozilla-specific). + * + * NOT IMPLEMENTED + * + * @var unknown_type + */ + public $originalTarget; // moz only + /** + * Identifies a secondary target for the event. + * + * @var unknown_type + */ + public $relatedTarget; + /** + * Returns a reference to the target to which the event was originally dispatched. + * + * @var unknown_type + */ + public $target; + /** + * Returns the time that the event was created. + * + * @var unknown_type + */ + public $timeStamp; + /** + * Returns the name of the event (case-insensitive). + */ + public $type; + public $runDefault = true; + public $data = null; + public function __construct($data) { + foreach($data as $k => $v) { + $this->$k = $v; + } + if (! $this->timeStamp) + $this->timeStamp = time(); + } + /** + * Cancels the event (if it is cancelable). + * + */ + public function preventDefault() { + $this->runDefault = false; + } + /** + * Stops the propagation of events further along in the DOM. + * + */ + public function stopPropagation() { + $this->bubbles = false; + } +} + + +/** + * DOMDocumentWrapper class simplifies work with DOMDocument. + * + * Know bug: + * - in XHTML fragments,
changes to
+ * + * @todo check XML catalogs compatibility + * @author Tobiasz Cudnik + * @package phpQuery + */ +class DOMDocumentWrapper { + /** + * @var DOMDocument + */ + public $document; + public $id; + /** + * @todo Rewrite as method and quess if null. + * @var unknown_type + */ + public $contentType = ''; + public $xpath; + public $uuid = 0; + public $data = array(); + public $dataNodes = array(); + public $events = array(); + public $eventsNodes = array(); + public $eventsGlobal = array(); + /** + * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28 + * @var unknown_type + */ + public $frames = array(); + /** + * Document root, by default equals to document itself. + * Used by documentFragments. + * + * @var DOMNode + */ + public $root; + public $isDocumentFragment; + public $isXML = false; + public $isXHTML = false; + public $isHTML = false; + public $charset; + public function __construct($markup = null, $contentType = null, $newDocumentID = null) { + if (isset($markup)) + $this->load($markup, $contentType, $newDocumentID); + $this->id = $newDocumentID + ? $newDocumentID + : md5(microtime()); + } + public function load($markup, $contentType = null, $newDocumentID = null) { +// phpQuery::$documents[$id] = $this; + $this->contentType = strtolower($contentType); + if ($markup instanceof DOMDOCUMENT) { + $this->document = $markup; + $this->root = $this->document; + $this->charset = $this->document->encoding; + // TODO isDocumentFragment + $loaded = true; + } else { + $loaded = $this->loadMarkup($markup); + } + if ($loaded) { +// $this->document->formatOutput = true; + $this->document->preserveWhiteSpace = true; + $this->xpath = new DOMXPath($this->document); + $this->afterMarkupLoad(); + return true; + // remember last loaded document +// return phpQuery::selectDocument($id); + } + return false; + } + protected function afterMarkupLoad() { + if ($this->isXHTML) { + $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml"); + } + } + protected function loadMarkup($markup) { + $loaded = false; + if ($this->contentType) { + self::debug("Load markup for content type {$this->contentType}"); + // content determined by contentType + list($contentType, $charset) = $this->contentTypeToArray($this->contentType); + switch($contentType) { + case 'text/html': + phpQuery::debug("Loading HTML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupHTML($markup, $charset); + break; + case 'text/xml': + case 'application/xhtml+xml': + phpQuery::debug("Loading XML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupXML($markup, $charset); + break; + default: + // for feeds or anything that sometimes doesn't use text/xml + if (strpos('xml', $this->contentType) !== false) { + phpQuery::debug("Loading XML, content type '{$this->contentType}'"); + $loaded = $this->loadMarkupXML($markup, $charset); + } else + phpQuery::debug("Could not determine document type from content type '{$this->contentType}'"); + } + } else { + // content type autodetection + if ($this->isXML($markup)) { + phpQuery::debug("Loading XML, isXML() == true"); + $loaded = $this->loadMarkupXML($markup); + if (! $loaded && $this->isXHTML) { + phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true'); + $loaded = $this->loadMarkupHTML($markup); + } + } else { + phpQuery::debug("Loading HTML, isXML() == false"); + $loaded = $this->loadMarkupHTML($markup); + } + } + return $loaded; + } + protected function loadMarkupReset() { + $this->isXML = $this->isXHTML = $this->isHTML = false; + } + protected function documentCreate($charset, $version = '1.0') { + if (! $version) + $version = '1.0'; + $this->document = new DOMDocument($version, $charset); + $this->charset = $this->document->encoding; +// $this->document->encoding = $charset; + $this->document->formatOutput = true; + $this->document->preserveWhiteSpace = true; + } + protected function loadMarkupHTML($markup, $requestedCharset = null) { + if (phpQuery::$debug) + phpQuery::debug('Full markup load (HTML): '.substr($markup, 0, 250)); + $this->loadMarkupReset(); + $this->isHTML = true; + if (!isset($this->isDocumentFragment)) + $this->isDocumentFragment = self::isDocumentFragmentHTML($markup); + $charset = null; + $documentCharset = $this->charsetFromHTML($markup); + $addDocumentCharset = false; + if ($documentCharset) { + $charset = $documentCharset; + $markup = $this->charsetFixHTML($markup); + } else if ($requestedCharset) { + $charset = $requestedCharset; + } + if (! $charset) + $charset = phpQuery::$defaultCharset; + // HTTP 1.1 says that the default charset is ISO-8859-1 + // @see http://www.w3.org/International/O-HTTP-charset + if (! $documentCharset) { + $documentCharset = 'ISO-8859-1'; + $addDocumentCharset = true; + } + // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' + // Worse, some pages can have mixed encodings... we'll try not to worry about that + $requestedCharset = strtoupper($requestedCharset); + $documentCharset = strtoupper($documentCharset); + phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset"); + if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) { + phpQuery::debug("CHARSET CONVERT"); + // Document Encoding Conversion + // http://code.google.com/p/phpquery/issues/detail?id=86 + if (function_exists('mb_detect_encoding')) { + $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO'); + $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets)); + if (! $docEncoding) + $docEncoding = $documentCharset; // ok trust the document + phpQuery::debug("DETECTED '$docEncoding'"); + // Detected does not match what document says... + if ($docEncoding !== $documentCharset) { + // Tricky.. + } + if ($docEncoding !== $requestedCharset) { + phpQuery::debug("CONVERT $docEncoding => $requestedCharset"); + $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding); + $markup = $this->charsetAppendToHTML($markup, $requestedCharset); + $charset = $requestedCharset; + } + } else { + phpQuery::debug("TODO: charset conversion without mbstring..."); + } + } + $return = false; + if ($this->isDocumentFragment) { + phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'"); + $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); + } else { + if ($addDocumentCharset) { + phpQuery::debug("Full markup load (HTML), appending charset: '$charset'"); + $markup = $this->charsetAppendToHTML($markup, $charset); + } + phpQuery::debug("Full markup load (HTML), documentCreate('$charset')"); + $this->documentCreate($charset); + $return = phpQuery::$debug === 2 + ? $this->document->loadHTML($markup) + : @$this->document->loadHTML($markup); + if ($return) + $this->root = $this->document; + } + if ($return && ! $this->contentType) + $this->contentType = 'text/html'; + return $return; + } + protected function loadMarkupXML($markup, $requestedCharset = null) { + if (phpQuery::$debug) + phpQuery::debug('Full markup load (XML): '.substr($markup, 0, 250)); + $this->loadMarkupReset(); + $this->isXML = true; + // check agains XHTML in contentType or markup + $isContentTypeXHTML = $this->isXHTML(); + $isMarkupXHTML = $this->isXHTML($markup); + if ($isContentTypeXHTML || $isMarkupXHTML) { + self::debug('Full markup load (XML), XHTML detected'); + $this->isXHTML = true; + } + // determine document fragment + if (! isset($this->isDocumentFragment)) + $this->isDocumentFragment = $this->isXHTML + ? self::isDocumentFragmentXHTML($markup) + : self::isDocumentFragmentXML($markup); + // this charset will be used + $charset = null; + // charset from XML declaration @var string + $documentCharset = $this->charsetFromXML($markup); + if (! $documentCharset) { + if ($this->isXHTML) { + // this is XHTML, try to get charset from content-type meta header + $documentCharset = $this->charsetFromHTML($markup); + if ($documentCharset) { + phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'"); + $this->charsetAppendToXML($markup, $documentCharset); + $charset = $documentCharset; + } + } + if (! $documentCharset) { + // if still no document charset... + $charset = $requestedCharset; + } + } else if ($requestedCharset) { + $charset = $requestedCharset; + } + if (! $charset) { + $charset = phpQuery::$defaultCharset; + } + if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) { + // TODO place for charset conversion +// $charset = $requestedCharset; + } + $return = false; + if ($this->isDocumentFragment) { + phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'"); + $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); + } else { + // FIXME ??? + if ($isContentTypeXHTML && ! $isMarkupXHTML) + if (! $documentCharset) { + phpQuery::debug("Full markup load (XML), appending charset '$charset'"); + $markup = $this->charsetAppendToXML($markup, $charset); + } + // see http://pl2.php.net/manual/en/book.dom.php#78929 + // LIBXML_DTDLOAD (>= PHP 5.1) + // does XML ctalogues works with LIBXML_NONET + // $this->document->resolveExternals = true; + // TODO test LIBXML_COMPACT for performance improvement + // create document + $this->documentCreate($charset); + if (phpversion() < 5.1) { + $this->document->resolveExternals = true; + $return = phpQuery::$debug === 2 + ? $this->document->loadXML($markup) + : @$this->document->loadXML($markup); + } else { + /** @link http://pl2.php.net/manual/en/libxml.constants.php */ + $libxmlStatic = phpQuery::$debug === 2 + ? LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET + : LIBXML_DTDLOAD|LIBXML_DTDATTR|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOERROR; + $return = $this->document->loadXML($markup, $libxmlStatic); +// if (! $return) +// $return = $this->document->loadHTML($markup); + } + if ($return) + $this->root = $this->document; + } + if ($return) { + if (! $this->contentType) { + if ($this->isXHTML) + $this->contentType = 'application/xhtml+xml'; + else + $this->contentType = 'text/xml'; + } + return $return; + } else { + throw new Exception("Error loading XML markup"); + } + } + protected function isXHTML($markup = null) { + if (! isset($markup)) { + return strpos($this->contentType, 'xhtml') !== false; + } + // XXX ok ? + return strpos($markup, "doctype) && is_object($dom->doctype) +// ? $dom->doctype->publicId +// : self::$defaultDoctype; + } + protected function isXML($markup) { +// return strpos($markup, ']+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', + $markup, $matches + ); + if (! isset($matches[0])) + return array(null, null); + // get attr 'content' + preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches); + if (! isset($matches[0])) + return array(null, null); + return $this->contentTypeToArray($matches[2]); + } + protected function charsetFromHTML($markup) { + $contentType = $this->contentTypeFromHTML($markup); + return $contentType[1]; + } + protected function charsetFromXML($markup) { + $matches; + // find declaration + preg_match('@<'.'?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i', + $markup, $matches + ); + return isset($matches[2]) + ? strtolower($matches[2]) + : null; + } + /** + * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug. + * + * @link http://code.google.com/p/phpquery/issues/detail?id=80 + * @param $html + */ + protected function charsetFixHTML($markup) { + $matches = array(); + // find meta tag + preg_match('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', + $markup, $matches, PREG_OFFSET_CAPTURE + ); + if (! isset($matches[0])) + return; + $metaContentType = $matches[0][0]; + $markup = substr($markup, 0, $matches[0][1]) + .substr($markup, $matches[0][1]+strlen($metaContentType)); + $headStart = stripos($markup, ''); + $markup = substr($markup, 0, $headStart+6).$metaContentType + .substr($markup, $headStart+6); + return $markup; + } + protected function charsetAppendToHTML($html, $charset, $xhtml = false) { + // remove existing meta[type=content-type] + $html = preg_replace('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html); + $meta = ''; + if (strpos($html, ')@s', + "{$meta}", + $html + ); + } + } else { + return preg_replace( + '@)@s', + ''.$meta, + $html + ); + } + } + protected function charsetAppendToXML($markup, $charset) { + $declaration = '<'.'?xml version="1.0" encoding="'.$charset.'"?'.'>'; + return $declaration.$markup; + } + public static function isDocumentFragmentHTML($markup) { + return stripos($markup, 'documentFragmentCreate($node, $sourceCharset); +// if ($fake === false) +// throw new Exception("Error loading documentFragment markup"); +// else +// $return = array_merge($return, +// $this->import($fake->root->childNodes) +// ); +// } else { +// $return[] = $this->document->importNode($node, true); +// } +// } +// return $return; +// } else { +// // string markup +// $fake = $this->documentFragmentCreate($source, $sourceCharset); +// if ($fake === false) +// throw new Exception("Error loading documentFragment markup"); +// else +// return $this->import($fake->root->childNodes); +// } + if (is_array($source) || $source instanceof DOMNODELIST) { + // dom nodes + self::debug('Importing nodes to document'); + foreach($source as $node) + $return[] = $this->document->importNode($node, true); + } else { + // string markup + $fake = $this->documentFragmentCreate($source, $sourceCharset); + if ($fake === false) + throw new Exception("Error loading documentFragment markup"); + else + return $this->import($fake->root->childNodes); + } + return $return; + } + /** + * Creates new document fragment. + * + * @param $source + * @return DOMDocumentWrapper + */ + protected function documentFragmentCreate($source, $charset = null) { + $fake = new DOMDocumentWrapper(); + $fake->contentType = $this->contentType; + $fake->isXML = $this->isXML; + $fake->isHTML = $this->isHTML; + $fake->isXHTML = $this->isXHTML; + $fake->root = $fake->document; + if (! $charset) + $charset = $this->charset; +// $fake->documentCreate($this->charset); + if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) + $source = array($source); + if (is_array($source) || $source instanceof DOMNODELIST) { + // dom nodes + // load fake document + if (! $this->documentFragmentLoadMarkup($fake, $charset)) + return false; + $nodes = $fake->import($source); + foreach($nodes as $node) + $fake->root->appendChild($node); + } else { + // string markup + $this->documentFragmentLoadMarkup($fake, $charset, $source); + } + return $fake; + } + /** + * + * @param $document DOMDocumentWrapper + * @param $markup + * @return $document + */ + private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) { + // TODO error handling + // TODO copy doctype + // tempolary turn off + $fragment->isDocumentFragment = false; + if ($fragment->isXML) { + if ($fragment->isXHTML) { + // add FAKE element to set default namespace + $fragment->loadMarkupXML('' + .'' + .''.$markup.''); + $fragment->root = $fragment->document->firstChild->nextSibling; + } else { + $fragment->loadMarkupXML(''.$markup.''); + $fragment->root = $fragment->document->firstChild; + } + } else { + $markup2 = phpQuery::$defaultDoctype.''; + $noBody = strpos($markup, 'loadMarkupHTML($markup2); + // TODO resolv body tag merging issue + $fragment->root = $noBody + ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling + : $fragment->document->firstChild->nextSibling->firstChild->nextSibling; + } + if (! $fragment->root) + return false; + $fragment->isDocumentFragment = true; + return true; + } + protected function documentFragmentToMarkup($fragment) { + phpQuery::debug('documentFragmentToMarkup'); + $tmp = $fragment->isDocumentFragment; + $fragment->isDocumentFragment = false; + $markup = $fragment->markup(); + if ($fragment->isXML) { + $markup = substr($markup, 0, strrpos($markup, '')); + if ($fragment->isXHTML) { + $markup = substr($markup, strpos($markup, '')+6); + } + } else { + $markup = substr($markup, strpos($markup, '')+6); + $markup = substr($markup, 0, strrpos($markup, '')); + } + $fragment->isDocumentFragment = $tmp; + if (phpQuery::$debug) + phpQuery::debug('documentFragmentToMarkup: '.substr($markup, 0, 150)); + return $markup; + } + /** + * Return document markup, starting with optional $nodes as root. + * + * @param $nodes DOMNode|DOMNodeList + * @return string + */ + public function markup($nodes = null, $innerMarkup = false) { + if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT) + $nodes = null; + if (isset($nodes)) { + $markup = ''; + if (!is_array($nodes) && !($nodes instanceof DOMNODELIST) ) + $nodes = array($nodes); + if ($this->isDocumentFragment && ! $innerMarkup) + foreach($nodes as $i => $node) + if ($node->isSameNode($this->root)) { + // var_dump($node); + $nodes = array_slice($nodes, 0, $i) + + phpQuery::DOMNodeListToArray($node->childNodes) + + array_slice($nodes, $i+1); + } + if ($this->isXML && ! $innerMarkup) { + self::debug("Getting outerXML with charset '{$this->charset}'"); + // we need outerXML, so we can benefit from + // $node param support in saveXML() + foreach($nodes as $node) + $markup .= $this->document->saveXML($node); + } else { + $loop = array(); + if ($innerMarkup) + foreach($nodes as $node) { + if ($node->childNodes) + foreach($node->childNodes as $child) + $loop[] = $child; + else + $loop[] = $node; + } + else + $loop = $nodes; + self::debug("Getting markup, moving selected nodes (".count($loop).") to new DocumentFragment"); + $fake = $this->documentFragmentCreate($loop); + $markup = $this->documentFragmentToMarkup($fake); + } + if ($this->isXHTML) { + self::debug("Fixing XHTML"); + $markup = self::markupFixXHTML($markup); + } + self::debug("Markup: ".substr($markup, 0, 250)); + return $markup; + } else { + if ($this->isDocumentFragment) { + // documentFragment, html only... + self::debug("Getting markup, DocumentFragment detected"); +// return $this->markup( +//// $this->document->getElementsByTagName('body')->item(0) +// $this->document->root, true +// ); + $markup = $this->documentFragmentToMarkup($this); + // no need for markupFixXHTML, as it's done thought markup($nodes) method + return $markup; + } else { + self::debug("Getting markup (".($this->isXML?'XML':'HTML')."), final with charset '{$this->charset}'"); + $markup = $this->isXML + ? $this->document->saveXML() + : $this->document->saveHTML(); + if ($this->isXHTML) { + self::debug("Fixing XHTML"); + $markup = self::markupFixXHTML($markup); + } + self::debug("Markup: ".substr($markup, 0, 250)); + return $markup; + } + } + } + protected static function markupFixXHTML($markup) { + $markup = self::expandEmptyTag('script', $markup); + $markup = self::expandEmptyTag('select', $markup); + $markup = self::expandEmptyTag('textarea', $markup); + return $markup; + } + public static function debug($text) { + phpQuery::debug($text); + } + /** + * expandEmptyTag + * + * @param $tag + * @param $xml + * @return unknown_type + * @author mjaque at ilkebenson dot com + * @link http://php.net/manual/en/domdocument.savehtml.php#81256 + */ + public static function expandEmptyTag($tag, $xml){ + $indice = 0; + while ($indice< strlen($xml)){ + $pos = strpos($xml, "<$tag ", $indice); + if ($pos){ + $posCierre = strpos($xml, ">", $pos); + if ($xml[$posCierre-1] == "/"){ + $xml = substr_replace($xml, ">", $posCierre-1, 2); + } + $indice = $posCierre; + } + else break; + } + return $xml; + } +} + +/** + * Event handling class. + * + * @author Tobiasz Cudnik + * @package phpQuery + * @static + */ +abstract class phpQueryEvents { + /** + * Trigger a type of event on every matched element. + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $data + * + * @TODO exclusive events (with !) + * @TODO global events (test) + * @TODO support more than event in $type (space-separated) + */ + public static function trigger($document, $type, $data = array(), $node = null) { + // trigger: function(type, data, elem, donative, extra) { + $documentID = phpQuery::getDocumentID($document); + $namespace = null; + if (strpos($type, '.') !== false) + list($name, $namespace) = explode('.', $type); + else + $name = $type; + if (! $node) { + if (self::issetGlobal($documentID, $type)) { + $pq = phpQuery::getDocument($documentID); + // TODO check add($pq->document) + $pq->find('*')->add($pq->document) + ->trigger($type, $data); + } + } else { + if (isset($data[0]) && $data[0] instanceof DOMEvent) { + $event = $data[0]; + $event->relatedTarget = $event->target; + $event->target = $node; + $data = array_slice($data, 1); + } else { + $event = new DOMEvent(array( + 'type' => $type, + 'target' => $node, + 'timeStamp' => time(), + )); + } + $i = 0; + while($node) { + // TODO whois + phpQuery::debug("Triggering ".($i?"bubbled ":'')."event '{$type}' on " + ."node \n");//.phpQueryObject::whois($node)."\n"); + $event->currentTarget = $node; + $eventNode = self::getNode($documentID, $node); + if (isset($eventNode->eventHandlers)) { + foreach($eventNode->eventHandlers as $eventType => $handlers) { + $eventNamespace = null; + if (strpos($type, '.') !== false) + list($eventName, $eventNamespace) = explode('.', $eventType); + else + $eventName = $eventType; + if ($name != $eventName) + continue; + if ($namespace && $eventNamespace && $namespace != $eventNamespace) + continue; + foreach($handlers as $handler) { + phpQuery::debug("Calling event handler\n"); + $event->data = $handler['data'] + ? $handler['data'] + : null; + $params = array_merge(array($event), $data); + $return = phpQuery::callbackRun($handler['callback'], $params); + if ($return === false) { + $event->bubbles = false; + } + } + } + } + // to bubble or not to bubble... + if (! $event->bubbles) + break; + $node = $node->parentNode; + $i++; + } + } + } + /** + * Binds a handler to one or more events (like click) for each matched element. + * Can also bind custom events. + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $data Optional + * @param unknown_type $callback + * + * @TODO support '!' (exclusive) events + * @TODO support more than event in $type (space-separated) + * @TODO support binding to global events + */ + public static function add($document, $node, $type, $data, $callback = null) { + phpQuery::debug("Binding '$type' event"); + $documentID = phpQuery::getDocumentID($document); +// if (is_null($callback) && is_callable($data)) { +// $callback = $data; +// $data = null; +// } + $eventNode = self::getNode($documentID, $node); + if (! $eventNode) + $eventNode = self::setNode($documentID, $node); + if (!isset($eventNode->eventHandlers[$type])) + $eventNode->eventHandlers[$type] = array(); + $eventNode->eventHandlers[$type][] = array( + 'callback' => $callback, + 'data' => $data, + ); + } + /** + * Enter description here... + * + * @param DOMNode|phpQueryObject|string $document + * @param unknown_type $type + * @param unknown_type $callback + * + * @TODO namespace events + * @TODO support more than event in $type (space-separated) + */ + public static function remove($document, $node, $type = null, $callback = null) { + $documentID = phpQuery::getDocumentID($document); + $eventNode = self::getNode($documentID, $node); + if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) { + if ($callback) { + foreach($eventNode->eventHandlers[$type] as $k => $handler) + if ($handler['callback'] == $callback) + unset($eventNode->eventHandlers[$type][$k]); + } else { + unset($eventNode->eventHandlers[$type]); + } + } + } + protected static function getNode($documentID, $node) { + foreach(phpQuery::$documents[$documentID]->eventsNodes as $eventNode) { + if ($node->isSameNode($eventNode)) + return $eventNode; + } + } + protected static function setNode($documentID, $node) { + phpQuery::$documents[$documentID]->eventsNodes[] = $node; + return phpQuery::$documents[$documentID]->eventsNodes[ + count(phpQuery::$documents[$documentID]->eventsNodes)-1 + ]; + } + protected static function issetGlobal($documentID, $type) { + return isset(phpQuery::$documents[$documentID]) + ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal) + : false; + } +} + + +interface ICallbackNamed { + function hasName(); + function getName(); +} +/** + * Callback class introduces currying-like pattern. + * + * Example: + * function foo($param1, $param2, $param3) { + * var_dump($param1, $param2, $param3); + * } + * $fooCurried = new Callback('foo', + * 'param1 is now statically set', + * new CallbackParam, new CallbackParam + * ); + * phpQuery::callbackRun($fooCurried, + * array('param2 value', 'param3 value' + * ); + * + * Callback class is supported in all phpQuery methods which accepts callbacks. + * + * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures + * @author Tobiasz Cudnik + * + * @TODO??? return fake forwarding function created via create_function + * @TODO honor paramStructure + */ +class Callback + implements ICallbackNamed { + public $callback = null; + public $params = null; + protected $name; + public function __construct($callback, $param1 = null, $param2 = null, + $param3 = null) { + $params = func_get_args(); + $params = array_slice($params, 1); + if ($callback instanceof Callback) { + // TODO implement recurention + } else { + $this->callback = $callback; + $this->params = $params; + } + } + public function getName() { + return 'Callback: '.$this->name; + } + public function hasName() { + return isset($this->name) && $this->name; + } + public function setName($name) { + $this->name = $name; + return $this; + } + // TODO test me +// public function addParams() { +// $params = func_get_args(); +// return new Callback($this->callback, $this->params+$params); +// } +} +/** + * Shorthand for new Callback(create_function(...), ...); + * + * @author Tobiasz Cudnik + */ +class CallbackBody extends Callback { + public function __construct($paramList, $code, $param1 = null, $param2 = null, + $param3 = null) { + $params = func_get_args(); + $params = array_slice($params, 2); + $this->callback = create_function($paramList, $code); + $this->params = $params; + } +} +/** + * Callback type which on execution returns reference passed during creation. + * + * @author Tobiasz Cudnik + */ +class CallbackReturnReference extends Callback + implements ICallbackNamed { + protected $reference; + public function __construct(&$reference, $name = null){ + $this->reference =& $reference; + $this->callback = array($this, 'callback'); + } + public function callback() { + return $this->reference; + } + public function getName() { + return 'Callback: '.$this->name; + } + public function hasName() { + return isset($this->name) && $this->name; + } +} +/** + * Callback type which on execution returns value passed during creation. + * + * @author Tobiasz Cudnik + */ +class CallbackReturnValue extends Callback + implements ICallbackNamed { + protected $value; + protected $name; + public function __construct($value, $name = null){ + $this->value =& $value; + $this->name = $name; + $this->callback = array($this, 'callback'); + } + public function callback() { + return $this->value; + } + public function __toString() { + return $this->getName(); + } + public function getName() { + return 'Callback: '.$this->name; + } + public function hasName() { + return isset($this->name) && $this->name; + } +} +/** + * CallbackParameterToReference can be used when we don't really want a callback, + * only parameter passed to it. CallbackParameterToReference takes first + * parameter's value and passes it to reference. + * + * @author Tobiasz Cudnik + */ +class CallbackParameterToReference extends Callback { + /** + * @param $reference + * @TODO implement $paramIndex; + * param index choose which callback param will be passed to reference + */ + public function __construct(&$reference){ + $this->callback =& $reference; + } +} +//class CallbackReference extends Callback { +// /** +// * +// * @param $reference +// * @param $paramIndex +// * @todo implement $paramIndex; param index choose which callback param will be passed to reference +// */ +// public function __construct(&$reference, $name = null){ +// $this->callback =& $reference; +// } +//} +class CallbackParam {} + +/** + * Class representing phpQuery objects. + * + * @author Tobiasz Cudnik + * @package phpQuery + * @method phpQueryObject clone() clone() + * @method phpQueryObject empty() empty() + * @method phpQueryObject next() next($selector = null) + * @method phpQueryObject prev() prev($selector = null) + * @property Int $length + */ +class phpQueryObject + implements Iterator, Countable, ArrayAccess { + public $documentID = null; + /** + * DOMDocument class. + * + * @var DOMDocument + */ + public $document = null; + public $charset = null; + /** + * + * @var DOMDocumentWrapper + */ + public $documentWrapper = null; + /** + * XPath interface. + * + * @var DOMXPath + */ + public $xpath = null; + /** + * Stack of selected elements. + * @TODO refactor to ->nodes + * @var array + */ + public $elements = array(); + /** + * @access private + */ + protected $elementsBackup = array(); + /** + * @access private + */ + protected $previous = null; + /** + * @access private + * @TODO deprecate + */ + protected $root = array(); + /** + * Indicated if doument is just a fragment (no tag). + * + * Every document is realy a full document, so even documentFragments can + * be queried against , but getDocument(id)->htmlOuter() will return + * only contents of . + * + * @var bool + */ + public $documentFragment = true; + /** + * Iterator interface helper + * @access private + */ + protected $elementsInterator = array(); + /** + * Iterator interface helper + * @access private + */ + protected $valid = false; + /** + * Iterator interface helper + * @access private + */ + protected $current = null; + /** + * Enter description here... + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function __construct($documentID) { +// if ($documentID instanceof self) +// var_dump($documentID->getDocumentID()); + $id = $documentID instanceof self + ? $documentID->getDocumentID() + : $documentID; +// var_dump($id); + if (! isset(phpQuery::$documents[$id] )) { +// var_dump(phpQuery::$documents); + throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first."); + } + $this->documentID = $id; + $this->documentWrapper =& phpQuery::$documents[$id]; + $this->document =& $this->documentWrapper->document; + $this->xpath =& $this->documentWrapper->xpath; + $this->charset =& $this->documentWrapper->charset; + $this->documentFragment =& $this->documentWrapper->isDocumentFragment; + // TODO check $this->DOM->documentElement; +// $this->root = $this->document->documentElement; + $this->root =& $this->documentWrapper->root; +// $this->toRoot(); + $this->elements = array($this->root); + } + /** + * + * @access private + * @param $attr + * @return unknown_type + */ + public function __get($attr) { + switch($attr) { + // FIXME doesnt work at all ? + case 'length': + return $this->size(); + break; + default: + return $this->$attr; + } + } + /** + * Saves actual object to $var by reference. + * Useful when need to break chain. + * @param phpQueryObject $var + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function toReference(&$var) { + return $var = $this; + } + public function documentFragment($state = null) { + if ($state) { + phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state; + return $this; + } + return $this->documentFragment; + } + /** + * @access private + * @TODO documentWrapper + */ + protected function isRoot( $node) { +// return $node instanceof DOMDOCUMENT || $node->tagName == 'html'; + return $node instanceof DOMDOCUMENT + || ($node instanceof DOMELEMENT && $node->tagName == 'html') + || $this->root->isSameNode($node); + } + /** + * @access private + */ + protected function stackIsRoot() { + return $this->size() == 1 && $this->isRoot($this->elements[0]); + } + /** + * Enter description here... + * NON JQUERY METHOD + * + * Watch out, it doesn't creates new instance, can be reverted with end(). + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function toRoot() { + $this->elements = array($this->root); + return $this; +// return $this->newInstance(array($this->root)); + } + /** + * Saves object's DocumentID to $var by reference. + * + * $myDocumentId; + * phpQuery::newDocument('
') + * ->getDocumentIDRef($myDocumentId) + * ->find('div')->... + * + * + * @param unknown_type $domId + * @see phpQuery::newDocument + * @see phpQuery::newDocumentFile + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocumentIDRef(&$documentID) { + $documentID = $this->getDocumentID(); + return $this; + } + /** + * Returns object with stack set to document root. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocument() { + return phpQuery::getDocument($this->getDocumentID()); + } + /** + * + * @return DOMDocument + */ + public function getDOMDocument() { + return $this->document; + } + /** + * Get object's Document ID. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function getDocumentID() { + return $this->documentID; + } + /** + * Unloads whole document from memory. + * CAUTION! None further operations will be possible on this document. + * All objects refering to it will be useless. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + */ + public function unloadDocument() { + phpQuery::unloadDocuments($this->getDocumentID()); + } + public function isHTML() { + return $this->documentWrapper->isHTML; + } + public function isXHTML() { + return $this->documentWrapper->isXHTML; + } + public function isXML() { + return $this->documentWrapper->isXML; + } + /** + * Enter description here... + * + * @link http://docs.jquery.com/Ajax/serialize + * @return string + */ + public function serialize() { + return phpQuery::param($this->serializeArray()); + } + /** + * Enter description here... + * + * @link http://docs.jquery.com/Ajax/serializeArray + * @return array + */ + public function serializeArray($submit = null) { + $source = $this->filter('form, input, select, textarea') + ->find('input, select, textarea') + ->andSelf() + ->not('form'); + $return = array(); +// $source->dumpDie(); + foreach($source as $input) { + $input = phpQuery::pq($input); + if ($input->is('[disabled]')) + continue; + if (!$input->is('[name]')) + continue; + if ($input->is('[type=checkbox]') && !$input->is('[checked]')) + continue; + // jquery diff + if ($submit && $input->is('[type=submit]')) { + if ($submit instanceof DOMELEMENT && ! $input->elements[0]->isSameNode($submit)) + continue; + else if (is_string($submit) && $input->attr('name') != $submit) + continue; + } + $return[] = array( + 'name' => $input->attr('name'), + 'value' => $input->val(), + ); + } + return $return; + } + /** + * @access private + */ + protected function debug($in) { + if (! phpQuery::$debug ) + return; + print('
');
+		print_r($in);
+		// file debug
+//		file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
+		// quite handy debug trace
+//		if ( is_array($in))
+//			print_r(array_slice(debug_backtrace(), 3));
+		print("
\n"); + } + /** + * @access private + */ + protected function isRegexp($pattern) { + return in_array( + $pattern[ mb_strlen($pattern)-1 ], + array('^','*','$') + ); + } + /** + * Determines if $char is really a char. + * + * @param string $char + * @return bool + * @todo rewrite me to charcode range ! ;) + * @access private + */ + protected function isChar($char) { + return extension_loaded('mbstring') && phpQuery::$mbstringSupport + ? mb_eregi('\w', $char) + : preg_match('@\w@', $char); + } + /** + * @access private + */ + protected function parseSelector($query) { + // clean spaces + // TODO include this inside parsing ? + $query = trim( + preg_replace('@\s+@', ' ', + preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) + ) + ); + $queries = array(array()); + if (! $query) + return $queries; + $return =& $queries[0]; + $specialChars = array('>',' '); +// $specialCharsMapping = array('/' => '>'); + $specialCharsMapping = array(); + $strlen = mb_strlen($query); + $classChars = array('.', '-'); + $pseudoChars = array('-'); + $tagChars = array('*', '|', '-'); + // split multibyte string + // http://code.google.com/p/phpquery/issues/detail?id=76 + $_query = array(); + for ($i=0; $i<$strlen; $i++) + $_query[] = mb_substr($query, $i, 1); + $query = $_query; + // it works, but i dont like it... + $i = 0; + while( $i < $strlen) { + $c = $query[$i]; + $tmp = ''; + // TAG + if ($this->isChar($c) || in_array($c, $tagChars)) { + while(isset($query[$i]) + && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))) { + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // IDs + } else if ( $c == '#') { + $i++; + while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) { + $tmp .= $query[$i]; + $i++; + } + $return[] = '#'.$tmp; + // SPECIAL CHARS + } else if (in_array($c, $specialChars)) { + $return[] = $c; + $i++; + // MAPPED SPECIAL MULTICHARS +// } else if ( $c.$query[$i+1] == '//') { +// $return[] = ' '; +// $i = $i+2; + // MAPPED SPECIAL CHARS + } else if ( isset($specialCharsMapping[$c])) { + $return[] = $specialCharsMapping[$c]; + $i++; + // COMMA + } else if ( $c == ',') { + $queries[] = array(); + $return =& $queries[ count($queries)-1 ]; + $i++; + while( isset($query[$i]) && $query[$i] == ' ') + $i++; + // CLASSES + } else if ($c == '.') { + while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // ~ General Sibling Selector + } else if ($c == '~') { + $spaceAllowed = true; + $tmp .= $query[$i++]; + while( isset($query[$i]) + && ($this->isChar($query[$i]) + || in_array($query[$i], $classChars) + || $query[$i] == '*' + || ($query[$i] == ' ' && $spaceAllowed) + )) { + if ($query[$i] != ' ') + $spaceAllowed = false; + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // + Adjacent sibling selectors + } else if ($c == '+') { + $spaceAllowed = true; + $tmp .= $query[$i++]; + while( isset($query[$i]) + && ($this->isChar($query[$i]) + || in_array($query[$i], $classChars) + || $query[$i] == '*' + || ($spaceAllowed && $query[$i] == ' ') + )) { + if ($query[$i] != ' ') + $spaceAllowed = false; + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // ATTRS + } else if ($c == '[') { + $stack = 1; + $tmp .= $c; + while( isset($query[++$i])) { + $tmp .= $query[$i]; + if ( $query[$i] == '[') { + $stack++; + } else if ( $query[$i] == ']') { + $stack--; + if (! $stack ) + break; + } + } + $return[] = $tmp; + $i++; + // PSEUDO CLASSES + } else if ($c == ':') { + $stack = 1; + $tmp .= $query[$i++]; + while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { + $tmp .= $query[$i]; + $i++; + } + // with arguments ? + if ( isset($query[$i]) && $query[$i] == '(') { + $tmp .= $query[$i]; + $stack = 1; + while( isset($query[++$i])) { + $tmp .= $query[$i]; + if ( $query[$i] == '(') { + $stack++; + } else if ( $query[$i] == ')') { + $stack--; + if (! $stack ) + break; + } + } + $return[] = $tmp; + $i++; + } else { + $return[] = $tmp; + } + } else { + $i++; + } + } + foreach($queries as $k => $q) { + if (isset($q[0])) { + if (isset($q[0][0]) && $q[0][0] == ':') + array_unshift($queries[$k], '*'); + if ($q[0] != '>') + array_unshift($queries[$k], ' '); + } + } + return $queries; + } + /** + * Return matched DOM nodes. + * + * @param int $index + * @return array|DOMElement Single DOMElement or array of DOMElement. + */ + public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { + $return = isset($index) + ? (isset($this->elements[$index]) ? $this->elements[$index] : null) + : $this->elements; + // pass thou callbacks + $args = func_get_args(); + $args = array_slice($args, 1); + foreach($args as $callback) { + if (is_array($return)) + foreach($return as $k => $v) + $return[$k] = phpQuery::callbackRun($callback, array($v)); + else + $return = phpQuery::callbackRun($callback, array($return)); + } + return $return; + } + /** + * Return matched DOM nodes. + * jQuery difference. + * + * @param int $index + * @return array|string Returns string if $index != null + * @todo implement callbacks + * @todo return only arrays ? + * @todo maybe other name... + */ + public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) { + if (!is_null($index) && is_int($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 (!is_null($index) && is_int($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( + function ($node) { + return $node instanceof DOMELEMENT && $node->childNodes->length + ? $node : null; + } + )->elements; + break; + case 'empty': + $this->elements = $this->map( + 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( + function ($node) { + return pq($node)->not(":disabled") ? $node : null; + } + )->elements; + break; + case 'header': + $this->elements = $this->map( + 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( + function ($node) { + return pq($node)->siblings()->size() == 0 ? $node : null; + } + )->elements; + break; + case 'first-child': + $this->elements = $this->map( + function ($node) { + return pq($node)->prevAll()->size() == 0 ? $node : null; + } + )->elements; + break; + case 'last-child': + $this->elements = $this->map( + 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( + 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 && preg_match('/^(\d*)n([-+]?)(\d*)/', $param) === 1) + // an+b + $mapped = $this->map( + function ($node, $param) { + $prevs = pq($node)->prevAll()->size(); + $index = 1+$prevs; + + preg_match("/^(\d*)n([-+]?)(\d*)/", $param, $matches); + $a = intval($matches[1]); + $b = intval($matches[3]); + if( $matches[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( + 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; + } + /** + * @return The text content of each matching element, like + * text() but returns an array with one entry per matched element. + * Read only. + */ + public function texts($attr = null) { + $results = array(); + foreach($this->elements as $node) { + $results[] = $node->textContent; + } + return $results; + } + /** + * 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; + } + /** + * @return The same attribute of each matching element, like + * attr() but returns an array with one entry per matched element. + * Read only. + */ + public function attrs($attr = null) { + $results = array(); + foreach($this->stack(1) as $node) { + $results[] = $node->hasAttribute($attr) + ? $node->getAttribute($attr) + : null; + } + return $results; + } + /** + * @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 = implode('/', 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') + ? '.'.implode('.', explode(' ', $node->getAttribute('class'))):'') + .($node->getAttribute('name') + ? '[name="'.$node->getAttribute('name').'"]':'') + .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') === false + ? '[value="'.substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15).'"]':'') + .($node->getAttribute('value') && strpos($node->getAttribute('value'), '<'.'?php') !== false + ? '[value=PHP]':'') + .($node->getAttribute('selected') + ? '[selected]':'') + .($node->getAttribute('checked') + ? '[checked]':'') + ; + } else if ($node instanceof DOMTEXT) { + if (trim($node->textContent)) + $return[] = 'Text:'.substr(str_replace("\n", ' ', $node->textContent), 0, 15); + } else { + + } + } + return $oneNode && isset($return[0]) + ? $return[0] + : $return; + } + /** + * Dump htmlOuter and preserve chain. Usefull for debugging. + * + * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery + * + */ + public function dump() { + print 'DUMP #'.(phpQuery::$dumpCount++).' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; +// print __FILE__.':'.__LINE__."\n"; + var_dump($this->htmlOuter()); + return $this; + } + public function dumpWhois() { + print 'DUMP #'.(phpQuery::$dumpCount++).' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; +// print __FILE__.':'.__LINE__."\n"; + var_dump('whois', $this->whois()); + phpQuery::$debug = $debug; + return $this; + } + public function dumpLength() { + print 'DUMP #'.(phpQuery::$dumpCount++).' '; + $debug = phpQuery::$debug; + phpQuery::$debug = false; +// print __FILE__.':'.__LINE__."\n"; + var_dump('length', $this->length()); + phpQuery::$debug = $debug; + return $this; + } + public function dumpTree($html = true, $title = true) { + $output = $title + ? 'DUMP #'.(phpQuery::$dumpCount++)." \n" : ''; + $debug = phpQuery::$debug; + phpQuery::$debug = false; + foreach($this->stack() as $node) + $output .= $this->__dumpTree($node); + phpQuery::$debug = $debug; + print $html + ? nl2br(str_replace(' ', ' ', $output)) + : $output; + return $this; + } + private function __dumpTree($node, $intend = 0) { + $whois = $this->whois($node); + $return = ''; + if ($whois) + $return .= str_repeat(' - ', $intend).$whois."\n"; + if (isset($node->childNodes)) + foreach($node->childNodes as $chNode) + $return .= $this->__dumpTree($chNode, $intend+1); + return $return; + } + /** + * Dump htmlOuter and stop script execution. Usefull for debugging. + * + */ + public function dumpDie() { + print __FILE__.':'.__LINE__; + var_dump($this->htmlOuter()); + die(); + } +} + + +// -- Multibyte Compatibility functions --------------------------------------- +// http://svn.iphonewebdev.com/lace/lib/mb_compat.php + +/** + * mb_internal_encoding() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_internal_encoding')) +{ + function mb_internal_encoding($enc) {return true; } +} + +/** + * mb_regex_encoding() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_regex_encoding')) +{ + function mb_regex_encoding($enc) {return true; } +} + +/** + * mb_strlen() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_strlen')) +{ + function mb_strlen($str) + { + return strlen($str); + } +} + +/** + * mb_strpos() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_strpos')) +{ + function mb_strpos($haystack, $needle, $offset=0) + { + return strpos($haystack, $needle, $offset); + } +} +/** + * mb_stripos() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_stripos')) +{ + function mb_stripos($haystack, $needle, $offset=0) + { + return stripos($haystack, $needle, $offset); + } +} + +/** + * mb_substr() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_substr')) +{ + function mb_substr($str, $start, $length=0) + { + return substr($str, $start, $length); + } +} + +/** + * mb_substr_count() + * + * Included for mbstring pseudo-compatability. + */ +if (!function_exists('mb_substr_count')) +{ + function mb_substr_count($haystack, $needle) + { + return substr_count($haystack, $needle); + } +} + + +/** + * Static namespace for phpQuery functions. + * + * @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, + 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';