├── README.md ├── composer.json └── phpQuery.php /README.md: -------------------------------------------------------------------------------- 1 | # phpQuery-single 2 | phpQuery onefile composer.Continuous maintenance,Welcome PR. 3 | 4 | `QueryList` base on phpQuery: https://github.com/jae-jae/QueryList 5 | 6 | phpQuery单文件版本,持续维护,欢迎PR. 7 | > phpQuery项目主页:http://code.google.com/p/phpquery/ 8 | 9 | `QueryList`是基于phpQuery的采集工具: https://github.com/jae-jae/QueryList 10 | 11 | ## Composer Installation 12 | Packagist: https://packagist.org/packages/jaeger/phpquery-single 13 | ``` 14 | composer require jaeger/phpquery-single 15 | ``` 16 | 17 | ## Usage 18 | ```php 19 | $html = << 21 |
22 | QueryList官网 23 | 这是图片 24 | 这是图片2 25 |
26 | 其它的一些文本 27 | 28 | STR; 29 | 30 | $doc = phpQuery::newDocumentHTML($html); 31 | 32 | $src = $doc->find('.two img:eq(0)')->attr('src'); 33 | 34 | echo $src; 35 | // http://querylist.cc/1.jpg 36 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jaeger/phpquery-single", 3 | "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/", 4 | "homepage": "http://code.google.com/p/phpquery/", 5 | "license": "MIT", 6 | "require": { 7 | "PHP":">=5.3.0" 8 | }, 9 | "authors": [ 10 | { 11 | "name": "Tobiasz Cudnik" 12 | ,"email": "tobiasz.cudnik@gmail.com" 13 | ,"homepage": "https://github.com/TobiaszCudnik" 14 | ,"role": "Developer" 15 | } 16 | ,{ 17 | "name": "Jaeger", 18 | "role": "Packager" 19 | } 20 | ], 21 | "autoload":{ 22 | "classmap":["phpQuery.php"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /phpQuery.php: -------------------------------------------------------------------------------- 1 | 12 | * @license http://www.opensource.org/licenses/mit-license.php MIT License 13 | * @package phpQuery 14 | */ 15 | 16 | // class names for instanceof 17 | // TODO move them as class constants into phpQuery 18 | define('DOMDOCUMENT', 'DOMDocument'); 19 | define('DOMELEMENT', 'DOMElement'); 20 | define('DOMNODELIST', 'DOMNodeList'); 21 | define('DOMNODE', 'DOMNode'); 22 | 23 | /** 24 | * DOMEvent class. 25 | * 26 | * Based on 27 | * @link http://developer.mozilla.org/En/DOM:event 28 | * @author Tobiasz Cudnik 29 | * @package phpQuery 30 | * @todo implement ArrayAccess ? 31 | */ 32 | class DOMEvent 33 | { 34 | /** 35 | * Returns a boolean indicating whether the event bubbles up through the DOM or not. 36 | * 37 | * @var unknown_type 38 | */ 39 | public $bubbles = true; 40 | /** 41 | * Returns a boolean indicating whether the event is cancelable. 42 | * 43 | * @var unknown_type 44 | */ 45 | public $cancelable = true; 46 | /** 47 | * Returns a reference to the currently registered target for the event. 48 | * 49 | * @var unknown_type 50 | */ 51 | public $currentTarget; 52 | /** 53 | * Returns detail about the event, depending on the type of event. 54 | * 55 | * @var unknown_type 56 | * @link http://developer.mozilla.org/en/DOM/event.detail 57 | */ 58 | public $detail; // ??? 59 | /** 60 | * Used to indicate which phase of the event flow is currently being evaluated. 61 | * 62 | * NOT IMPLEMENTED 63 | * 64 | * @var unknown_type 65 | * @link http://developer.mozilla.org/en/DOM/event.eventPhase 66 | */ 67 | public $eventPhase; // ??? 68 | /** 69 | * The explicit original target of the event (Mozilla-specific). 70 | * 71 | * NOT IMPLEMENTED 72 | * 73 | * @var unknown_type 74 | */ 75 | public $explicitOriginalTarget; // moz only 76 | /** 77 | * The original target of the event, before any retargetings (Mozilla-specific). 78 | * 79 | * NOT IMPLEMENTED 80 | * 81 | * @var unknown_type 82 | */ 83 | public $originalTarget; // moz only 84 | /** 85 | * Identifies a secondary target for the event. 86 | * 87 | * @var unknown_type 88 | */ 89 | public $relatedTarget; 90 | /** 91 | * Returns a reference to the target to which the event was originally dispatched. 92 | * 93 | * @var unknown_type 94 | */ 95 | public $target; 96 | /** 97 | * Returns the time that the event was created. 98 | * 99 | * @var unknown_type 100 | */ 101 | public $timeStamp; 102 | /** 103 | * Returns the name of the event (case-insensitive). 104 | */ 105 | public $type; 106 | public $runDefault = true; 107 | public $data = null; 108 | public function __construct($data) 109 | { 110 | foreach ($data as $k => $v) { 111 | $this->$k = $v; 112 | } 113 | if (!$this->timeStamp) 114 | $this->timeStamp = time(); 115 | } 116 | /** 117 | * Cancels the event (if it is cancelable). 118 | * 119 | */ 120 | public function preventDefault() 121 | { 122 | $this->runDefault = false; 123 | } 124 | /** 125 | * Stops the propagation of events further along in the DOM. 126 | * 127 | */ 128 | public function stopPropagation() 129 | { 130 | $this->bubbles = false; 131 | } 132 | } 133 | 134 | 135 | /** 136 | * DOMDocumentWrapper class simplifies work with DOMDocument. 137 | * 138 | * Know bug: 139 | * - in XHTML fragments,
changes to
140 | * 141 | * @todo check XML catalogs compatibility 142 | * @author Tobiasz Cudnik 143 | * @package phpQuery 144 | */ 145 | class DOMDocumentWrapper 146 | { 147 | /** 148 | * @var DOMDocument 149 | */ 150 | public $document; 151 | public $id; 152 | /** 153 | * @todo Rewrite as method and quess if null. 154 | * @var unknown_type 155 | */ 156 | public $contentType = ''; 157 | public $xpath; 158 | public $uuid = 0; 159 | public $data = array(); 160 | public $dataNodes = array(); 161 | public $events = array(); 162 | public $eventsNodes = array(); 163 | public $eventsGlobal = array(); 164 | /** 165 | * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28 166 | * @var unknown_type 167 | */ 168 | public $frames = array(); 169 | /** 170 | * Document root, by default equals to document itself. 171 | * Used by documentFragments. 172 | * 173 | * @var DOMNode 174 | */ 175 | public $root; 176 | public $isDocumentFragment; 177 | public $isXML = false; 178 | public $isXHTML = false; 179 | public $isHTML = false; 180 | public $charset; 181 | public function __construct($markup = null, $contentType = null, $newDocumentID = null) 182 | { 183 | if (isset($markup)) 184 | $this->load($markup, $contentType, $newDocumentID); 185 | $this->id = $newDocumentID 186 | ? $newDocumentID 187 | : md5(microtime()); 188 | } 189 | public function load($markup, $contentType = null, $newDocumentID = null) 190 | { 191 | // phpQuery::$documents[$id] = $this; 192 | $this->contentType = strtolower($contentType); 193 | if ($markup instanceof DOMDOCUMENT) { 194 | $this->document = $markup; 195 | $this->root = $this->document; 196 | $this->charset = $this->document->encoding; 197 | // TODO isDocumentFragment 198 | $loaded = true; 199 | } else { 200 | $loaded = $this->loadMarkup($markup); 201 | } 202 | if ($loaded) { 203 | // $this->document->formatOutput = true; 204 | $this->document->preserveWhiteSpace = true; 205 | $this->xpath = new DOMXPath($this->document); 206 | $this->afterMarkupLoad(); 207 | return true; 208 | // remember last loaded document 209 | // return phpQuery::selectDocument($id); 210 | } 211 | return false; 212 | } 213 | protected function afterMarkupLoad() 214 | { 215 | if ($this->isXHTML) { 216 | $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml"); 217 | } 218 | } 219 | protected function loadMarkup($markup) 220 | { 221 | $loaded = false; 222 | if ($this->contentType) { 223 | self::debug("Load markup for content type {$this->contentType}"); 224 | // content determined by contentType 225 | list($contentType, $charset) = $this->contentTypeToArray($this->contentType); 226 | switch ($contentType) { 227 | case 'text/html': 228 | phpQuery::debug("Loading HTML, content type '{$this->contentType}'"); 229 | $loaded = $this->loadMarkupHTML($markup, $charset); 230 | break; 231 | case 'text/xml': 232 | case 'application/xhtml+xml': 233 | phpQuery::debug("Loading XML, content type '{$this->contentType}'"); 234 | $loaded = $this->loadMarkupXML($markup, $charset); 235 | break; 236 | default: 237 | // for feeds or anything that sometimes doesn't use text/xml 238 | if (strpos('xml', $this->contentType) !== false) { 239 | phpQuery::debug("Loading XML, content type '{$this->contentType}'"); 240 | $loaded = $this->loadMarkupXML($markup, $charset); 241 | } else 242 | phpQuery::debug("Could not determine document type from content type '{$this->contentType}'"); 243 | } 244 | } else { 245 | // content type autodetection 246 | if ($this->isXML($markup)) { 247 | phpQuery::debug("Loading XML, isXML() == true"); 248 | $loaded = $this->loadMarkupXML($markup); 249 | if (!$loaded && $this->isXHTML) { 250 | phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true'); 251 | $loaded = $this->loadMarkupHTML($markup); 252 | } 253 | } else { 254 | phpQuery::debug("Loading HTML, isXML() == false"); 255 | $loaded = $this->loadMarkupHTML($markup); 256 | } 257 | } 258 | return $loaded; 259 | } 260 | protected function loadMarkupReset() 261 | { 262 | $this->isXML = $this->isXHTML = $this->isHTML = false; 263 | } 264 | protected function documentCreate($charset, $version = '1.0') 265 | { 266 | if (!$version) 267 | $version = '1.0'; 268 | $this->document = new DOMDocument($version, $charset); 269 | $this->charset = $this->document->encoding; 270 | // $this->document->encoding = $charset; 271 | $this->document->formatOutput = true; 272 | $this->document->preserveWhiteSpace = true; 273 | } 274 | protected function loadMarkupHTML($markup, $requestedCharset = null) 275 | { 276 | if (phpQuery::$debug) 277 | phpQuery::debug('Full markup load (HTML): ' . substr($markup, 0, 250)); 278 | $this->loadMarkupReset(); 279 | $this->isHTML = true; 280 | if (!isset($this->isDocumentFragment)) 281 | $this->isDocumentFragment = self::isDocumentFragmentHTML($markup); 282 | $charset = null; 283 | $documentCharset = $this->charsetFromHTML($markup); 284 | $addDocumentCharset = false; 285 | if ($documentCharset) { 286 | $charset = $documentCharset; 287 | $markup = $this->charsetFixHTML($markup); 288 | } else if ($requestedCharset) { 289 | $charset = $requestedCharset; 290 | } 291 | if (!$charset) 292 | $charset = phpQuery::$defaultCharset; 293 | // HTTP 1.1 says that the default charset is ISO-8859-1 294 | // @see http://www.w3.org/International/O-HTTP-charset 295 | if (!$documentCharset) { 296 | $documentCharset = 'ISO-8859-1'; 297 | $addDocumentCharset = true; 298 | } 299 | // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' 300 | // Worse, some pages can have mixed encodings... we'll try not to worry about that 301 | $requestedCharset = $requestedCharset ? strtoupper($requestedCharset) : ""; 302 | $documentCharset = strtoupper($documentCharset); 303 | phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset"); 304 | if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) { 305 | phpQuery::debug("CHARSET CONVERT"); 306 | // Document Encoding Conversion 307 | // http://code.google.com/p/phpquery/issues/detail?id=86 308 | if (function_exists('mb_detect_encoding')) { 309 | $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO'); 310 | $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets)); 311 | if (!$docEncoding) 312 | $docEncoding = $documentCharset; // ok trust the document 313 | phpQuery::debug("DETECTED '$docEncoding'"); 314 | // Detected does not match what document says... 315 | if ($docEncoding !== $documentCharset) { 316 | // Tricky.. 317 | } 318 | if ($docEncoding !== $requestedCharset) { 319 | phpQuery::debug("CONVERT $docEncoding => $requestedCharset"); 320 | $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding); 321 | $markup = $this->charsetAppendToHTML($markup, $requestedCharset); 322 | $charset = $requestedCharset; 323 | } 324 | } else { 325 | phpQuery::debug("TODO: charset conversion without mbstring..."); 326 | } 327 | } 328 | $return = false; 329 | if ($this->isDocumentFragment) { 330 | phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'"); 331 | $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); 332 | } else { 333 | if ($addDocumentCharset) { 334 | phpQuery::debug("Full markup load (HTML), appending charset: '$charset'"); 335 | $markup = $this->charsetAppendToHTML($markup, $charset); 336 | } 337 | phpQuery::debug("Full markup load (HTML), documentCreate('$charset')"); 338 | $this->documentCreate($charset); 339 | $return = phpQuery::$debug === 2 340 | ? $this->document->loadHTML($markup) 341 | : @$this->document->loadHTML($markup); 342 | if ($return) 343 | $this->root = $this->document; 344 | } 345 | if ($return && !$this->contentType) 346 | $this->contentType = 'text/html'; 347 | return $return; 348 | } 349 | protected function loadMarkupXML($markup, $requestedCharset = null) 350 | { 351 | if (phpQuery::$debug) 352 | phpQuery::debug('Full markup load (XML): ' . substr($markup, 0, 250)); 353 | $this->loadMarkupReset(); 354 | $this->isXML = true; 355 | // check agains XHTML in contentType or markup 356 | $isContentTypeXHTML = $this->isXHTML(); 357 | $isMarkupXHTML = $this->isXHTML($markup); 358 | if ($isContentTypeXHTML || $isMarkupXHTML) { 359 | self::debug('Full markup load (XML), XHTML detected'); 360 | $this->isXHTML = true; 361 | } 362 | // determine document fragment 363 | if (!isset($this->isDocumentFragment)) 364 | $this->isDocumentFragment = $this->isXHTML 365 | ? self::isDocumentFragmentXHTML($markup) 366 | : self::isDocumentFragmentXML($markup); 367 | // this charset will be used 368 | $charset = null; 369 | // charset from XML declaration @var string 370 | $documentCharset = $this->charsetFromXML($markup); 371 | if (!$documentCharset) { 372 | if ($this->isXHTML) { 373 | // this is XHTML, try to get charset from content-type meta header 374 | $documentCharset = $this->charsetFromHTML($markup); 375 | if ($documentCharset) { 376 | phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'"); 377 | $this->charsetAppendToXML($markup, $documentCharset); 378 | $charset = $documentCharset; 379 | } 380 | } 381 | if (!$documentCharset) { 382 | // if still no document charset... 383 | $charset = $requestedCharset; 384 | } 385 | } else if ($requestedCharset) { 386 | $charset = $requestedCharset; 387 | } 388 | if (!$charset) { 389 | $charset = phpQuery::$defaultCharset; 390 | } 391 | if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) { 392 | // TODO place for charset conversion 393 | // $charset = $requestedCharset; 394 | } 395 | $return = false; 396 | if ($this->isDocumentFragment) { 397 | phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'"); 398 | $return = $this->documentFragmentLoadMarkup($this, $charset, $markup); 399 | } else { 400 | // FIXME ??? 401 | if ($isContentTypeXHTML && !$isMarkupXHTML) 402 | if (!$documentCharset) { 403 | phpQuery::debug("Full markup load (XML), appending charset '$charset'"); 404 | $markup = $this->charsetAppendToXML($markup, $charset); 405 | } 406 | // see http://pl2.php.net/manual/en/book.dom.php#78929 407 | // LIBXML_DTDLOAD (>= PHP 5.1) 408 | // does XML ctalogues works with LIBXML_NONET 409 | // $this->document->resolveExternals = true; 410 | // TODO test LIBXML_COMPACT for performance improvement 411 | // create document 412 | $this->documentCreate($charset); 413 | if (phpversion() < 5.1) { 414 | $this->document->resolveExternals = true; 415 | $return = phpQuery::$debug === 2 416 | ? $this->document->loadXML($markup) 417 | : @$this->document->loadXML($markup); 418 | } else { 419 | /** @link http://pl2.php.net/manual/en/libxml.constants.php */ 420 | $libxmlStatic = phpQuery::$debug === 2 421 | ? LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET 422 | : LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR; 423 | $return = $this->document->loadXML($markup, $libxmlStatic); 424 | // if (! $return) 425 | // $return = $this->document->loadHTML($markup); 426 | } 427 | if ($return) 428 | $this->root = $this->document; 429 | } 430 | if ($return) { 431 | if (!$this->contentType) { 432 | if ($this->isXHTML) 433 | $this->contentType = 'application/xhtml+xml'; 434 | else 435 | $this->contentType = 'text/xml'; 436 | } 437 | return $return; 438 | } else { 439 | throw new Exception("Error loading XML markup"); 440 | } 441 | } 442 | protected function isXHTML($markup = null) 443 | { 444 | if (!isset($markup)) { 445 | return strpos($this->contentType, 'xhtml') !== false; 446 | } 447 | // XXX ok ? 448 | return strpos($markup, "doctype) && is_object($dom->doctype) 451 | // ? $dom->doctype->publicId 452 | // : self::$defaultDoctype; 453 | } 454 | protected function isXML($markup) 455 | { 456 | // return strpos($markup, ']+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', 483 | $markup, 484 | $matches 485 | ); 486 | if (!isset($matches[0])) 487 | return array(null, null); 488 | // get attr 'content' 489 | preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches); 490 | if (!isset($matches[0])) 491 | return array(null, null); 492 | return $this->contentTypeToArray($matches[2]); 493 | } 494 | protected function charsetFromHTML($markup) 495 | { 496 | $contentType = $this->contentTypeFromHTML($markup); 497 | return $contentType[1]; 498 | } 499 | protected function charsetFromXML($markup) 500 | { 501 | $matches; 502 | // find declaration 503 | preg_match( 504 | '@<' . '?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i', 505 | $markup, 506 | $matches 507 | ); 508 | return isset($matches[2]) 509 | ? strtolower($matches[2]) 510 | : null; 511 | } 512 | /** 513 | * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug. 514 | * 515 | * @link http://code.google.com/p/phpquery/issues/detail?id=80 516 | * @param $html 517 | */ 518 | protected function charsetFixHTML($markup) 519 | { 520 | $matches = array(); 521 | // find meta tag 522 | preg_match( 523 | '@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', 524 | $markup, 525 | $matches, 526 | PREG_OFFSET_CAPTURE 527 | ); 528 | if (!isset($matches[0])) 529 | return; 530 | $metaContentType = $matches[0][0]; 531 | $markup = substr($markup, 0, $matches[0][1]) 532 | . substr($markup, $matches[0][1] + strlen($metaContentType)); 533 | $headStart = stripos($markup, ''); 534 | $markup = substr($markup, 0, $headStart + 6) . $metaContentType 535 | . substr($markup, $headStart + 6); 536 | return $markup; 537 | } 538 | protected function charsetAppendToHTML($html, $charset, $xhtml = false) 539 | { 540 | // remove existing meta[type=content-type] 541 | $html = preg_replace('@\s*]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html); 542 | $meta = ''; 546 | if (strpos($html, ')@s', 552 | "{$meta}", 553 | $html 554 | ); 555 | } 556 | } else { 557 | return preg_replace( 558 | '@)@s', 559 | '' . $meta, 560 | $html 561 | ); 562 | } 563 | } 564 | protected function charsetAppendToXML($markup, $charset) 565 | { 566 | $declaration = '<' . '?xml version="1.0" encoding="' . $charset . '"?' . '>'; 567 | return $declaration . $markup; 568 | } 569 | public static function isDocumentFragmentHTML($markup) 570 | { 571 | return stripos($markup, 'documentFragmentCreate($node, $sourceCharset); 603 | // if ($fake === false) 604 | // throw new Exception("Error loading documentFragment markup"); 605 | // else 606 | // $return = array_merge($return, 607 | // $this->import($fake->root->childNodes) 608 | // ); 609 | // } else { 610 | // $return[] = $this->document->importNode($node, true); 611 | // } 612 | // } 613 | // return $return; 614 | // } else { 615 | // // string markup 616 | // $fake = $this->documentFragmentCreate($source, $sourceCharset); 617 | // if ($fake === false) 618 | // throw new Exception("Error loading documentFragment markup"); 619 | // else 620 | // return $this->import($fake->root->childNodes); 621 | // } 622 | if (is_array($source) || $source instanceof DOMNODELIST) { 623 | // dom nodes 624 | self::debug('Importing nodes to document'); 625 | foreach ($source as $node) 626 | $return[] = $this->document->importNode($node, true); 627 | } else { 628 | // string markup 629 | $fake = $this->documentFragmentCreate($source, $sourceCharset); 630 | if ($fake === false) 631 | throw new Exception("Error loading documentFragment markup"); 632 | else 633 | return $this->import($fake->root->childNodes); 634 | } 635 | return $return; 636 | } 637 | /** 638 | * Creates new document fragment. 639 | * 640 | * @param $source 641 | * @return DOMDocumentWrapper 642 | */ 643 | protected function documentFragmentCreate($source, $charset = null) 644 | { 645 | $fake = new DOMDocumentWrapper(); 646 | $fake->contentType = $this->contentType; 647 | $fake->isXML = $this->isXML; 648 | $fake->isHTML = $this->isHTML; 649 | $fake->isXHTML = $this->isXHTML; 650 | $fake->root = $fake->document; 651 | if (!$charset) 652 | $charset = $this->charset; 653 | // $fake->documentCreate($this->charset); 654 | if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST)) 655 | $source = array($source); 656 | if (is_array($source) || $source instanceof DOMNODELIST) { 657 | // dom nodes 658 | // load fake document 659 | if (!$this->documentFragmentLoadMarkup($fake, $charset)) 660 | return false; 661 | $nodes = $fake->import($source); 662 | foreach ($nodes as $node) 663 | $fake->root->appendChild($node); 664 | } else { 665 | // string markup 666 | $this->documentFragmentLoadMarkup($fake, $charset, $source); 667 | } 668 | return $fake; 669 | } 670 | /** 671 | * 672 | * @param $document DOMDocumentWrapper 673 | * @param $markup 674 | * @return $document 675 | */ 676 | private function documentFragmentLoadMarkup($fragment, $charset, $markup = null) 677 | { 678 | // TODO error handling 679 | // TODO copy doctype 680 | // tempolary turn off 681 | $fragment->isDocumentFragment = false; 682 | if ($fragment->isXML) { 683 | if ($fragment->isXHTML) { 684 | // add FAKE element to set default namespace 685 | $fragment->loadMarkupXML('' 686 | . '' 688 | . '' . $markup . ''); 689 | $fragment->root = $fragment->document->firstChild->nextSibling; 690 | } else { 691 | $fragment->loadMarkupXML('' . $markup . ''); 692 | $fragment->root = $fragment->document->firstChild; 693 | } 694 | } else { 695 | $markup2 = phpQuery::$defaultDoctype . ''; 697 | if ($markup == null) { 698 | $markup = ""; 699 | } 700 | $noBody = strpos($markup, 'loadMarkupHTML($markup2); 708 | // TODO resolv body tag merging issue 709 | $fragment->root = $noBody 710 | ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling 711 | : $fragment->document->firstChild->nextSibling->firstChild->nextSibling; 712 | } 713 | if (!$fragment->root) 714 | return false; 715 | $fragment->isDocumentFragment = true; 716 | return true; 717 | } 718 | protected function documentFragmentToMarkup($fragment) 719 | { 720 | phpQuery::debug('documentFragmentToMarkup'); 721 | $tmp = $fragment->isDocumentFragment; 722 | $fragment->isDocumentFragment = false; 723 | $markup = $fragment->markup(); 724 | if ($fragment->isXML) { 725 | $markup = substr($markup, 0, strrpos($markup, '')); 726 | if ($fragment->isXHTML) { 727 | $markup = substr($markup, strpos($markup, '') + 6); 730 | } 731 | } else { 732 | $markup = substr($markup, strpos($markup, '') + 6); 733 | $markup = substr($markup, 0, strrpos($markup, '')); 734 | } 735 | $fragment->isDocumentFragment = $tmp; 736 | if (phpQuery::$debug) 737 | phpQuery::debug('documentFragmentToMarkup: ' . substr($markup, 0, 150)); 738 | return $markup; 739 | } 740 | /** 741 | * Return document markup, starting with optional $nodes as root. 742 | * 743 | * @param $nodes DOMNode|DOMNodeList 744 | * @return string 745 | */ 746 | public function markup($nodes = null, $innerMarkup = false) 747 | { 748 | if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT) 749 | $nodes = null; 750 | if (isset($nodes)) { 751 | $markup = ''; 752 | if (!is_array($nodes) && !($nodes instanceof DOMNODELIST)) 753 | $nodes = array($nodes); 754 | if ($this->isDocumentFragment && !$innerMarkup) 755 | foreach ($nodes as $i => $node) 756 | if ($node->isSameNode($this->root)) { 757 | // var_dump($node); 758 | $nodes = array_slice($nodes, 0, $i) 759 | + phpQuery::DOMNodeListToArray($node->childNodes) 760 | + array_slice($nodes, $i + 1); 761 | } 762 | if ($this->isXML && !$innerMarkup) { 763 | self::debug("Getting outerXML with charset '{$this->charset}'"); 764 | // we need outerXML, so we can benefit from 765 | // $node param support in saveXML() 766 | foreach ($nodes as $node) 767 | $markup .= $this->document->saveXML($node); 768 | } else { 769 | $loop = array(); 770 | if ($innerMarkup) 771 | foreach ($nodes as $node) { 772 | if ($node->childNodes) 773 | foreach ($node->childNodes as $child) 774 | $loop[] = $child; 775 | else 776 | $loop[] = $node; 777 | } 778 | else 779 | $loop = $nodes; 780 | self::debug("Getting markup, moving selected nodes (" . count($loop) . ") to new DocumentFragment"); 781 | $fake = $this->documentFragmentCreate($loop); 782 | $markup = $this->documentFragmentToMarkup($fake); 783 | } 784 | if ($this->isXHTML) { 785 | self::debug("Fixing XHTML"); 786 | $markup = self::markupFixXHTML($markup); 787 | } 788 | self::debug("Markup: " . substr($markup, 0, 250)); 789 | return $markup; 790 | } else { 791 | if ($this->isDocumentFragment) { 792 | // documentFragment, html only... 793 | self::debug("Getting markup, DocumentFragment detected"); 794 | // return $this->markup( 795 | //// $this->document->getElementsByTagName('body')->item(0) 796 | // $this->document->root, true 797 | // ); 798 | $markup = $this->documentFragmentToMarkup($this); 799 | // no need for markupFixXHTML, as it's done thought markup($nodes) method 800 | return $markup; 801 | } else { 802 | self::debug("Getting markup (" . ($this->isXML ? 'XML' : 'HTML') . "), final with charset '{$this->charset}'"); 803 | $markup = $this->isXML 804 | ? $this->document->saveXML() 805 | : $this->document->saveHTML(); 806 | if ($this->isXHTML) { 807 | self::debug("Fixing XHTML"); 808 | $markup = self::markupFixXHTML($markup); 809 | } 810 | self::debug("Markup: " . substr($markup, 0, 250)); 811 | return $markup; 812 | } 813 | } 814 | } 815 | protected static function markupFixXHTML($markup) 816 | { 817 | $markup = self::expandEmptyTag('script', $markup); 818 | $markup = self::expandEmptyTag('select', $markup); 819 | $markup = self::expandEmptyTag('textarea', $markup); 820 | return $markup; 821 | } 822 | public static function debug($text) 823 | { 824 | phpQuery::debug($text); 825 | } 826 | /** 827 | * expandEmptyTag 828 | * 829 | * @param $tag 830 | * @param $xml 831 | * @return unknown_type 832 | * @author mjaque at ilkebenson dot com 833 | * @link http://php.net/manual/en/domdocument.savehtml.php#81256 834 | */ 835 | public static function expandEmptyTag($tag, $xml) 836 | { 837 | $indice = 0; 838 | while ($indice < strlen($xml)) { 839 | $pos = strpos($xml, "<$tag ", $indice); 840 | if ($pos) { 841 | $posCierre = strpos($xml, ">", $pos); 842 | if ($xml[$posCierre - 1] == "/") { 843 | $xml = substr_replace($xml, ">", $posCierre - 1, 2); 844 | } 845 | $indice = $posCierre; 846 | } else break; 847 | } 848 | return $xml; 849 | } 850 | } 851 | 852 | /** 853 | * Event handling class. 854 | * 855 | * @author Tobiasz Cudnik 856 | * @package phpQuery 857 | * @static 858 | */ 859 | abstract class phpQueryEvents 860 | { 861 | /** 862 | * Trigger a type of event on every matched element. 863 | * 864 | * @param DOMNode|phpQueryObject|string $document 865 | * @param unknown_type $type 866 | * @param unknown_type $data 867 | * 868 | * @TODO exclusive events (with !) 869 | * @TODO global events (test) 870 | * @TODO support more than event in $type (space-separated) 871 | */ 872 | public static function trigger($document, $type, $data = array(), $node = null) 873 | { 874 | // trigger: function(type, data, elem, donative, extra) { 875 | $documentID = phpQuery::getDocumentID($document); 876 | $namespace = null; 877 | if (strpos($type, '.') !== false) 878 | list($name, $namespace) = explode('.', $type); 879 | else 880 | $name = $type; 881 | if (!$node) { 882 | if (self::issetGlobal($documentID, $type)) { 883 | $pq = phpQuery::getDocument($documentID); 884 | // TODO check add($pq->document) 885 | $pq->find('*')->add($pq->document) 886 | ->trigger($type, $data); 887 | } 888 | } else { 889 | if (isset($data[0]) && $data[0] instanceof DOMEvent) { 890 | $event = $data[0]; 891 | $event->relatedTarget = $event->target; 892 | $event->target = $node; 893 | $data = array_slice($data, 1); 894 | } else { 895 | $event = new DOMEvent(array( 896 | 'type' => $type, 897 | 'target' => $node, 898 | 'timeStamp' => time(), 899 | )); 900 | } 901 | $i = 0; 902 | while ($node) { 903 | // TODO whois 904 | phpQuery::debug("Triggering " . ($i ? "bubbled " : '') . "event '{$type}' on " 905 | . "node \n"); //.phpQueryObject::whois($node)."\n"); 906 | $event->currentTarget = $node; 907 | $eventNode = self::getNode($documentID, $node); 908 | if (isset($eventNode->eventHandlers)) { 909 | foreach ($eventNode->eventHandlers as $eventType => $handlers) { 910 | $eventNamespace = null; 911 | if (strpos($type, '.') !== false) 912 | list($eventName, $eventNamespace) = explode('.', $eventType); 913 | else 914 | $eventName = $eventType; 915 | if ($name != $eventName) 916 | continue; 917 | if ($namespace && $eventNamespace && $namespace != $eventNamespace) 918 | continue; 919 | foreach ($handlers as $handler) { 920 | phpQuery::debug("Calling event handler\n"); 921 | $event->data = $handler['data'] 922 | ? $handler['data'] 923 | : null; 924 | $params = array_merge(array($event), $data); 925 | $return = phpQuery::callbackRun($handler['callback'], $params); 926 | if ($return === false) { 927 | $event->bubbles = false; 928 | } 929 | } 930 | } 931 | } 932 | // to bubble or not to bubble... 933 | if (!$event->bubbles) 934 | break; 935 | $node = $node->parentNode; 936 | $i++; 937 | } 938 | } 939 | } 940 | /** 941 | * Binds a handler to one or more events (like click) for each matched element. 942 | * Can also bind custom events. 943 | * 944 | * @param DOMNode|phpQueryObject|string $document 945 | * @param unknown_type $type 946 | * @param unknown_type $data Optional 947 | * @param unknown_type $callback 948 | * 949 | * @TODO support '!' (exclusive) events 950 | * @TODO support more than event in $type (space-separated) 951 | * @TODO support binding to global events 952 | */ 953 | public static function add($document, $node, $type, $data, $callback = null) 954 | { 955 | phpQuery::debug("Binding '$type' event"); 956 | $documentID = phpQuery::getDocumentID($document); 957 | // if (is_null($callback) && is_callable($data)) { 958 | // $callback = $data; 959 | // $data = null; 960 | // } 961 | $eventNode = self::getNode($documentID, $node); 962 | if (!$eventNode) 963 | $eventNode = self::setNode($documentID, $node); 964 | if (!isset($eventNode->eventHandlers[$type])) 965 | $eventNode->eventHandlers[$type] = array(); 966 | $eventNode->eventHandlers[$type][] = array( 967 | 'callback' => $callback, 968 | 'data' => $data, 969 | ); 970 | } 971 | /** 972 | * Enter description here... 973 | * 974 | * @param DOMNode|phpQueryObject|string $document 975 | * @param unknown_type $type 976 | * @param unknown_type $callback 977 | * 978 | * @TODO namespace events 979 | * @TODO support more than event in $type (space-separated) 980 | */ 981 | public static function remove($document, $node, $type = null, $callback = null) 982 | { 983 | $documentID = phpQuery::getDocumentID($document); 984 | $eventNode = self::getNode($documentID, $node); 985 | if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) { 986 | if ($callback) { 987 | foreach ($eventNode->eventHandlers[$type] as $k => $handler) 988 | if ($handler['callback'] == $callback) 989 | unset($eventNode->eventHandlers[$type][$k]); 990 | } else { 991 | unset($eventNode->eventHandlers[$type]); 992 | } 993 | } 994 | } 995 | protected static function getNode($documentID, $node) 996 | { 997 | foreach (phpQuery::$documents[$documentID]->eventsNodes as $eventNode) { 998 | if ($node->isSameNode($eventNode)) 999 | return $eventNode; 1000 | } 1001 | } 1002 | protected static function setNode($documentID, $node) 1003 | { 1004 | phpQuery::$documents[$documentID]->eventsNodes[] = $node; 1005 | return phpQuery::$documents[$documentID]->eventsNodes[count(phpQuery::$documents[$documentID]->eventsNodes) - 1]; 1006 | } 1007 | protected static function issetGlobal($documentID, $type) 1008 | { 1009 | return isset(phpQuery::$documents[$documentID]) 1010 | ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal) 1011 | : false; 1012 | } 1013 | } 1014 | 1015 | 1016 | interface ICallbackNamed 1017 | { 1018 | function hasName(); 1019 | function getName(); 1020 | } 1021 | /** 1022 | * Callback class introduces currying-like pattern. 1023 | * 1024 | * Example: 1025 | * function foo($param1, $param2, $param3) { 1026 | * var_dump($param1, $param2, $param3); 1027 | * } 1028 | * $fooCurried = new Callback('foo', 1029 | * 'param1 is now statically set', 1030 | * new CallbackParam, new CallbackParam 1031 | * ); 1032 | * phpQuery::callbackRun($fooCurried, 1033 | * array('param2 value', 'param3 value' 1034 | * ); 1035 | * 1036 | * Callback class is supported in all phpQuery methods which accepts callbacks. 1037 | * 1038 | * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures 1039 | * @author Tobiasz Cudnik 1040 | * 1041 | * @TODO??? return fake forwarding function created via create_function 1042 | * @TODO honor paramStructure 1043 | */ 1044 | class Callback 1045 | implements ICallbackNamed 1046 | { 1047 | public $callback = null; 1048 | public $params = null; 1049 | protected $name; 1050 | public function __construct( 1051 | $callback, 1052 | $param1 = null, 1053 | $param2 = null, 1054 | $param3 = null 1055 | ) { 1056 | $params = func_get_args(); 1057 | $params = array_slice($params, 1); 1058 | if ($callback instanceof Callback) { 1059 | // TODO implement recurention 1060 | } else { 1061 | $this->callback = $callback; 1062 | $this->params = $params; 1063 | } 1064 | } 1065 | public function getName() 1066 | { 1067 | return 'Callback: ' . $this->name; 1068 | } 1069 | public function hasName() 1070 | { 1071 | return isset($this->name) && $this->name; 1072 | } 1073 | public function setName($name) 1074 | { 1075 | $this->name = $name; 1076 | return $this; 1077 | } 1078 | // TODO test me 1079 | // public function addParams() { 1080 | // $params = func_get_args(); 1081 | // return new Callback($this->callback, $this->params+$params); 1082 | // } 1083 | } 1084 | /** 1085 | * Shorthand for new Callback(create_function(...), ...); 1086 | * 1087 | * @author Tobiasz Cudnik 1088 | */ 1089 | class CallbackBody extends Callback 1090 | { 1091 | public function __construct( 1092 | $paramList, 1093 | $code, 1094 | $param1 = null, 1095 | $param2 = null, 1096 | $param3 = null 1097 | ) { 1098 | $params = func_get_args(); 1099 | $params = array_slice($params, 2); 1100 | $this->callback = create_function($paramList, $code); 1101 | $this->params = $params; 1102 | } 1103 | } 1104 | /** 1105 | * Callback type which on execution returns reference passed during creation. 1106 | * 1107 | * @author Tobiasz Cudnik 1108 | */ 1109 | class CallbackReturnReference extends Callback 1110 | implements ICallbackNamed 1111 | { 1112 | protected $reference; 1113 | public function __construct(&$reference, $name = null) 1114 | { 1115 | $this->reference = &$reference; 1116 | $this->callback = array($this, 'callback'); 1117 | } 1118 | public function callback() 1119 | { 1120 | return $this->reference; 1121 | } 1122 | public function getName() 1123 | { 1124 | return 'Callback: ' . $this->name; 1125 | } 1126 | public function hasName() 1127 | { 1128 | return isset($this->name) && $this->name; 1129 | } 1130 | } 1131 | /** 1132 | * Callback type which on execution returns value passed during creation. 1133 | * 1134 | * @author Tobiasz Cudnik 1135 | */ 1136 | class CallbackReturnValue extends Callback 1137 | implements ICallbackNamed 1138 | { 1139 | protected $value; 1140 | protected $name; 1141 | public function __construct($value, $name = null) 1142 | { 1143 | $this->value = &$value; 1144 | $this->name = $name; 1145 | $this->callback = array($this, 'callback'); 1146 | } 1147 | public function callback() 1148 | { 1149 | return $this->value; 1150 | } 1151 | public function __toString() 1152 | { 1153 | return $this->getName(); 1154 | } 1155 | public function getName() 1156 | { 1157 | return 'Callback: ' . $this->name; 1158 | } 1159 | public function hasName() 1160 | { 1161 | return isset($this->name) && $this->name; 1162 | } 1163 | } 1164 | /** 1165 | * CallbackParameterToReference can be used when we don't really want a callback, 1166 | * only parameter passed to it. CallbackParameterToReference takes first 1167 | * parameter's value and passes it to reference. 1168 | * 1169 | * @author Tobiasz Cudnik 1170 | */ 1171 | class CallbackParameterToReference extends Callback 1172 | { 1173 | /** 1174 | * @param $reference 1175 | * @TODO implement $paramIndex; 1176 | * param index choose which callback param will be passed to reference 1177 | */ 1178 | public function __construct(&$reference) 1179 | { 1180 | $this->callback = &$reference; 1181 | } 1182 | } 1183 | //class CallbackReference extends Callback { 1184 | // /** 1185 | // * 1186 | // * @param $reference 1187 | // * @param $paramIndex 1188 | // * @todo implement $paramIndex; param index choose which callback param will be passed to reference 1189 | // */ 1190 | // public function __construct(&$reference, $name = null){ 1191 | // $this->callback =& $reference; 1192 | // } 1193 | //} 1194 | class CallbackParam 1195 | { 1196 | } 1197 | 1198 | /** 1199 | * Class representing phpQuery objects. 1200 | * 1201 | * @author Tobiasz Cudnik 1202 | * @package phpQuery 1203 | * @method phpQueryObject clone() clone() 1204 | * @method phpQueryObject empty() empty() 1205 | * @method phpQueryObject next() next($selector = null) 1206 | * @method phpQueryObject prev() prev($selector = null) 1207 | * @property Int $length 1208 | */ 1209 | class phpQueryObject 1210 | implements Iterator, Countable, ArrayAccess 1211 | { 1212 | public $documentID = null; 1213 | /** 1214 | * DOMDocument class. 1215 | * 1216 | * @var DOMDocument 1217 | */ 1218 | public $document = null; 1219 | public $charset = null; 1220 | /** 1221 | * 1222 | * @var DOMDocumentWrapper 1223 | */ 1224 | public $documentWrapper = null; 1225 | /** 1226 | * XPath interface. 1227 | * 1228 | * @var DOMXPath 1229 | */ 1230 | public $xpath = null; 1231 | /** 1232 | * Stack of selected elements. 1233 | * @TODO refactor to ->nodes 1234 | * @var array 1235 | */ 1236 | public $elements = array(); 1237 | /** 1238 | * @access private 1239 | */ 1240 | protected $elementsBackup = array(); 1241 | /** 1242 | * @access private 1243 | */ 1244 | protected $previous = null; 1245 | /** 1246 | * @access private 1247 | * @TODO deprecate 1248 | */ 1249 | protected $root = array(); 1250 | /** 1251 | * Indicated if doument is just a fragment (no tag). 1252 | * 1253 | * Every document is realy a full document, so even documentFragments can 1254 | * be queried against , but getDocument(id)->htmlOuter() will return 1255 | * only contents of . 1256 | * 1257 | * @var bool 1258 | */ 1259 | public $documentFragment = true; 1260 | /** 1261 | * Iterator interface helper 1262 | * @access private 1263 | */ 1264 | protected $elementsInterator = array(); 1265 | /** 1266 | * Iterator interface helper 1267 | * @access private 1268 | */ 1269 | protected $valid = false; 1270 | /** 1271 | * Iterator interface helper 1272 | * @access private 1273 | */ 1274 | protected $current = null; 1275 | /** 1276 | * Enter description here... 1277 | * 1278 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1279 | */ 1280 | public function __construct($documentID) 1281 | { 1282 | // if ($documentID instanceof self) 1283 | // var_dump($documentID->getDocumentID()); 1284 | $id = $documentID instanceof self 1285 | ? $documentID->getDocumentID() 1286 | : $documentID; 1287 | // var_dump($id); 1288 | if (!isset(phpQuery::$documents[$id])) { 1289 | // var_dump(phpQuery::$documents); 1290 | throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first."); 1291 | } 1292 | $this->documentID = $id; 1293 | $this->documentWrapper = &phpQuery::$documents[$id]; 1294 | $this->document = &$this->documentWrapper->document; 1295 | $this->xpath = &$this->documentWrapper->xpath; 1296 | $this->charset = &$this->documentWrapper->charset; 1297 | $this->documentFragment = &$this->documentWrapper->isDocumentFragment; 1298 | // TODO check $this->DOM->documentElement; 1299 | // $this->root = $this->document->documentElement; 1300 | $this->root = &$this->documentWrapper->root; 1301 | // $this->toRoot(); 1302 | $this->elements = array($this->root); 1303 | } 1304 | /** 1305 | * 1306 | * @access private 1307 | * @param $attr 1308 | * @return unknown_type 1309 | */ 1310 | public function __get($attr) 1311 | { 1312 | switch ($attr) { 1313 | // FIXME doesnt work at all ? 1314 | case 'length': 1315 | return $this->size(); 1316 | break; 1317 | default: 1318 | return $this->$attr; 1319 | } 1320 | } 1321 | /** 1322 | * Saves actual object to $var by reference. 1323 | * Useful when need to break chain. 1324 | * @param phpQueryObject $var 1325 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1326 | */ 1327 | public function toReference(&$var) 1328 | { 1329 | return $var = $this; 1330 | } 1331 | public function documentFragment($state = null) 1332 | { 1333 | if ($state) { 1334 | phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state; 1335 | return $this; 1336 | } 1337 | return $this->documentFragment; 1338 | } 1339 | /** 1340 | * @access private 1341 | * @TODO documentWrapper 1342 | */ 1343 | protected function isRoot($node) 1344 | { 1345 | // return $node instanceof DOMDOCUMENT || $node->tagName == 'html'; 1346 | return $node instanceof DOMDOCUMENT 1347 | || ($node instanceof DOMELEMENT && $node->tagName == 'html') 1348 | || $this->root->isSameNode($node); 1349 | } 1350 | /** 1351 | * @access private 1352 | */ 1353 | protected function stackIsRoot() 1354 | { 1355 | return $this->size() == 1 && $this->isRoot($this->elements[0]); 1356 | } 1357 | /** 1358 | * Enter description here... 1359 | * NON JQUERY METHOD 1360 | * 1361 | * Watch out, it doesn't creates new instance, can be reverted with end(). 1362 | * 1363 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1364 | */ 1365 | public function toRoot() 1366 | { 1367 | $this->elements = array($this->root); 1368 | return $this; 1369 | // return $this->newInstance(array($this->root)); 1370 | } 1371 | /** 1372 | * Saves object's DocumentID to $var by reference. 1373 | * 1374 | * $myDocumentId; 1375 | * phpQuery::newDocument('
') 1376 | * ->getDocumentIDRef($myDocumentId) 1377 | * ->find('div')->... 1378 | * 1379 | * 1380 | * @param unknown_type $domId 1381 | * @see phpQuery::newDocument 1382 | * @see phpQuery::newDocumentFile 1383 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1384 | */ 1385 | public function getDocumentIDRef(&$documentID) 1386 | { 1387 | $documentID = $this->getDocumentID(); 1388 | return $this; 1389 | } 1390 | /** 1391 | * Returns object with stack set to document root. 1392 | * 1393 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1394 | */ 1395 | public function getDocument() 1396 | { 1397 | return phpQuery::getDocument($this->getDocumentID()); 1398 | } 1399 | /** 1400 | * 1401 | * @return DOMDocument 1402 | */ 1403 | public function getDOMDocument() 1404 | { 1405 | return $this->document; 1406 | } 1407 | /** 1408 | * Get object's Document ID. 1409 | * 1410 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1411 | */ 1412 | public function getDocumentID() 1413 | { 1414 | return $this->documentID; 1415 | } 1416 | /** 1417 | * Unloads whole document from memory. 1418 | * CAUTION! None further operations will be possible on this document. 1419 | * All objects refering to it will be useless. 1420 | * 1421 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1422 | */ 1423 | public function unloadDocument() 1424 | { 1425 | phpQuery::unloadDocuments($this->getDocumentID()); 1426 | } 1427 | public function isHTML() 1428 | { 1429 | return $this->documentWrapper->isHTML; 1430 | } 1431 | public function isXHTML() 1432 | { 1433 | return $this->documentWrapper->isXHTML; 1434 | } 1435 | public function isXML() 1436 | { 1437 | return $this->documentWrapper->isXML; 1438 | } 1439 | /** 1440 | * Enter description here... 1441 | * 1442 | * @link http://docs.jquery.com/Ajax/serialize 1443 | * @return string 1444 | */ 1445 | public function serialize() 1446 | { 1447 | return phpQuery::param($this->serializeArray()); 1448 | } 1449 | /** 1450 | * Enter description here... 1451 | * 1452 | * @link http://docs.jquery.com/Ajax/serializeArray 1453 | * @return array 1454 | */ 1455 | public function serializeArray($submit = null) 1456 | { 1457 | $source = $this->filter('form, input, select, textarea') 1458 | ->find('input, select, textarea') 1459 | ->andSelf() 1460 | ->not('form'); 1461 | $return = array(); 1462 | // $source->dumpDie(); 1463 | foreach ($source as $input) { 1464 | $input = phpQuery::pq($input); 1465 | if ($input->is('[disabled]')) 1466 | continue; 1467 | if (!$input->is('[name]')) 1468 | continue; 1469 | if ($input->is('[type=checkbox]') && !$input->is('[checked]')) 1470 | continue; 1471 | // jquery diff 1472 | if ($submit && $input->is('[type=submit]')) { 1473 | if ($submit instanceof DOMELEMENT && !$input->elements[0]->isSameNode($submit)) 1474 | continue; 1475 | else if (is_string($submit) && $input->attr('name') != $submit) 1476 | continue; 1477 | } 1478 | $return[] = array( 1479 | 'name' => $input->attr('name'), 1480 | 'value' => $input->val(), 1481 | ); 1482 | } 1483 | return $return; 1484 | } 1485 | /** 1486 | * @access private 1487 | */ 1488 | protected function debug($in) 1489 | { 1490 | if (!phpQuery::$debug) 1491 | return; 1492 | print('
');
1493 | 		print_r($in);
1494 | 		// file debug
1495 | 		//		file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
1496 | 		// quite handy debug trace
1497 | 		//		if ( is_array($in))
1498 | 		//			print_r(array_slice(debug_backtrace(), 3));
1499 | 		print("
\n"); 1500 | } 1501 | /** 1502 | * @access private 1503 | */ 1504 | protected function isRegexp($pattern) 1505 | { 1506 | return in_array( 1507 | $pattern[mb_strlen($pattern) - 1], 1508 | array('^', '*', '$') 1509 | ); 1510 | } 1511 | /** 1512 | * Determines if $char is really a char. 1513 | * 1514 | * @param string $char 1515 | * @return bool 1516 | * @todo rewrite me to charcode range ! ;) 1517 | * @access private 1518 | */ 1519 | protected function isChar($char) 1520 | { 1521 | return extension_loaded('mbstring') && phpQuery::$mbstringSupport 1522 | ? mb_eregi('\w', $char) 1523 | : preg_match('@\w@', $char); 1524 | } 1525 | /** 1526 | * @access private 1527 | */ 1528 | protected function parseSelector($query) 1529 | { 1530 | // clean spaces 1531 | // TODO include this inside parsing ? 1532 | $query = trim( 1533 | preg_replace( 1534 | '@\s+@', 1535 | ' ', 1536 | preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) 1537 | ) 1538 | ); 1539 | $queries = array(array()); 1540 | if (!$query) 1541 | return $queries; 1542 | $return = &$queries[0]; 1543 | $specialChars = array('>', ' '); 1544 | // $specialCharsMapping = array('/' => '>'); 1545 | $specialCharsMapping = array(); 1546 | $strlen = mb_strlen($query); 1547 | $classChars = array('.', '-'); 1548 | $pseudoChars = array('-'); 1549 | $tagChars = array('*', '|', '-'); 1550 | // split multibyte string 1551 | // http://code.google.com/p/phpquery/issues/detail?id=76 1552 | $_query = array(); 1553 | for ($i = 0; $i < $strlen; $i++) 1554 | $_query[] = mb_substr($query, $i, 1); 1555 | $query = $_query; 1556 | // it works, but i dont like it... 1557 | $i = 0; 1558 | while ($i < $strlen) { 1559 | $c = $query[$i]; 1560 | $tmp = ''; 1561 | // TAG 1562 | if ($this->isChar($c) || in_array($c, $tagChars)) { 1563 | while ( 1564 | isset($query[$i]) 1565 | && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars)) 1566 | ) { 1567 | $tmp .= $query[$i]; 1568 | $i++; 1569 | } 1570 | $return[] = $tmp; 1571 | // IDs 1572 | } else if ($c == '#') { 1573 | $i++; 1574 | while (isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) { 1575 | $tmp .= $query[$i]; 1576 | $i++; 1577 | } 1578 | $return[] = '#' . $tmp; 1579 | // SPECIAL CHARS 1580 | } else if (in_array($c, $specialChars)) { 1581 | $return[] = $c; 1582 | $i++; 1583 | // MAPPED SPECIAL MULTICHARS 1584 | // } else if ( $c.$query[$i+1] == '//') { 1585 | // $return[] = ' '; 1586 | // $i = $i+2; 1587 | // MAPPED SPECIAL CHARS 1588 | } else if (isset($specialCharsMapping[$c])) { 1589 | $return[] = $specialCharsMapping[$c]; 1590 | $i++; 1591 | // COMMA 1592 | } else if ($c == ',') { 1593 | $queries[] = array(); 1594 | $return = &$queries[count($queries) - 1]; 1595 | $i++; 1596 | while (isset($query[$i]) && $query[$i] == ' ') 1597 | $i++; 1598 | // CLASSES 1599 | } else if ($c == '.') { 1600 | while (isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { 1601 | $tmp .= $query[$i]; 1602 | $i++; 1603 | } 1604 | $return[] = $tmp; 1605 | // ~ General Sibling Selector 1606 | } else if ($c == '~') { 1607 | $spaceAllowed = true; 1608 | $tmp .= $query[$i++]; 1609 | while ( 1610 | isset($query[$i]) 1611 | && ($this->isChar($query[$i]) 1612 | || in_array($query[$i], $classChars) 1613 | || $query[$i] == '*' 1614 | || ($query[$i] == ' ' && $spaceAllowed) 1615 | ) 1616 | ) { 1617 | if ($query[$i] != ' ') 1618 | $spaceAllowed = false; 1619 | $tmp .= $query[$i]; 1620 | $i++; 1621 | } 1622 | $return[] = $tmp; 1623 | // + Adjacent sibling selectors 1624 | } else if ($c == '+') { 1625 | $spaceAllowed = true; 1626 | $tmp .= $query[$i++]; 1627 | while ( 1628 | isset($query[$i]) 1629 | && ($this->isChar($query[$i]) 1630 | || in_array($query[$i], $classChars) 1631 | || $query[$i] == '*' 1632 | || ($spaceAllowed && $query[$i] == ' ') 1633 | ) 1634 | ) { 1635 | if ($query[$i] != ' ') 1636 | $spaceAllowed = false; 1637 | $tmp .= $query[$i]; 1638 | $i++; 1639 | } 1640 | $return[] = $tmp; 1641 | // ATTRS 1642 | } else if ($c == '[') { 1643 | $stack = 1; 1644 | $tmp .= $c; 1645 | while (isset($query[++$i])) { 1646 | $tmp .= $query[$i]; 1647 | if ($query[$i] == '[') { 1648 | $stack++; 1649 | } else if ($query[$i] == ']') { 1650 | $stack--; 1651 | if (!$stack) 1652 | break; 1653 | } 1654 | } 1655 | $return[] = $tmp; 1656 | $i++; 1657 | // PSEUDO CLASSES 1658 | } else if ($c == ':') { 1659 | $stack = 1; 1660 | $tmp .= $query[$i++]; 1661 | while (isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { 1662 | $tmp .= $query[$i]; 1663 | $i++; 1664 | } 1665 | // with arguments ? 1666 | if (isset($query[$i]) && $query[$i] == '(') { 1667 | $tmp .= $query[$i]; 1668 | $stack = 1; 1669 | while (isset($query[++$i])) { 1670 | $tmp .= $query[$i]; 1671 | if ($query[$i] == '(') { 1672 | $stack++; 1673 | } else if ($query[$i] == ')') { 1674 | $stack--; 1675 | if (!$stack) 1676 | break; 1677 | } 1678 | } 1679 | $return[] = $tmp; 1680 | $i++; 1681 | } else { 1682 | $return[] = $tmp; 1683 | } 1684 | } else { 1685 | $i++; 1686 | } 1687 | } 1688 | foreach ($queries as $k => $q) { 1689 | if (isset($q[0])) { 1690 | if (isset($q[0][0]) && $q[0][0] == ':') 1691 | array_unshift($queries[$k], '*'); 1692 | if ($q[0] != '>') 1693 | array_unshift($queries[$k], ' '); 1694 | } 1695 | } 1696 | return $queries; 1697 | } 1698 | /** 1699 | * Return matched DOM nodes. 1700 | * 1701 | * @param int $index 1702 | * @return array|DOMElement Single DOMElement or array of DOMElement. 1703 | */ 1704 | public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null) 1705 | { 1706 | $return = isset($index) 1707 | ? (isset($this->elements[$index]) ? $this->elements[$index] : null) 1708 | : $this->elements; 1709 | // pass thou callbacks 1710 | $args = func_get_args(); 1711 | $args = array_slice($args, 1); 1712 | foreach ($args as $callback) { 1713 | if (is_array($return)) 1714 | foreach ($return as $k => $v) 1715 | $return[$k] = phpQuery::callbackRun($callback, array($v)); 1716 | else 1717 | $return = phpQuery::callbackRun($callback, array($return)); 1718 | } 1719 | return $return; 1720 | } 1721 | /** 1722 | * Return matched DOM nodes. 1723 | * jQuery difference. 1724 | * 1725 | * @param int $index 1726 | * @return array|string Returns string if $index != null 1727 | * @todo implement callbacks 1728 | * @todo return only arrays ? 1729 | * @todo maybe other name... 1730 | */ 1731 | public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null) 1732 | { 1733 | if (!is_null($index) && is_int($index)) 1734 | $return = $this->eq($index)->text(); 1735 | else { 1736 | $return = array(); 1737 | for ($i = 0; $i < $this->size(); $i++) { 1738 | $return[] = $this->eq($i)->text(); 1739 | } 1740 | } 1741 | // pass thou callbacks 1742 | $args = func_get_args(); 1743 | $args = array_slice($args, 1); 1744 | foreach ($args as $callback) { 1745 | $return = phpQuery::callbackRun($callback, array($return)); 1746 | } 1747 | return $return; 1748 | } 1749 | /** 1750 | * Return matched DOM nodes. 1751 | * jQuery difference. 1752 | * 1753 | * @param int $index 1754 | * @return array|string Returns string if $index != null 1755 | * @todo implement callbacks 1756 | * @todo return only arrays ? 1757 | * @todo maybe other name... 1758 | */ 1759 | public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null) 1760 | { 1761 | if (!is_null($index) && is_int($index)) 1762 | $return = $this->eq($index)->text(); 1763 | else { 1764 | $return = array(); 1765 | for ($i = 0; $i < $this->size(); $i++) { 1766 | $return[] = $this->eq($i)->text(); 1767 | } 1768 | // pass thou callbacks 1769 | $args = func_get_args(); 1770 | $args = array_slice($args, 1); 1771 | } 1772 | foreach ($args as $callback) { 1773 | if (is_array($return)) 1774 | foreach ($return as $k => $v) 1775 | $return[$k] = phpQuery::callbackRun($callback, array($v)); 1776 | else 1777 | $return = phpQuery::callbackRun($callback, array($return)); 1778 | } 1779 | return $return; 1780 | } 1781 | /** 1782 | * Returns new instance of actual class. 1783 | * 1784 | * @param array $newStack Optional. Will replace old stack with new and move old one to history.c 1785 | */ 1786 | public function newInstance($newStack = null) 1787 | { 1788 | $class = get_class($this); 1789 | // support inheritance by passing old object to overloaded constructor 1790 | $new = $class != 'phpQuery' 1791 | ? new $class($this, $this->getDocumentID()) 1792 | : new phpQueryObject($this->getDocumentID()); 1793 | $new->previous = $this; 1794 | if (is_null($newStack)) { 1795 | $new->elements = $this->elements; 1796 | if ($this->elementsBackup) 1797 | $this->elements = $this->elementsBackup; 1798 | } else if (is_string($newStack)) { 1799 | $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack(); 1800 | } else { 1801 | $new->elements = $newStack; 1802 | } 1803 | return $new; 1804 | } 1805 | /** 1806 | * Enter description here... 1807 | * 1808 | * In the future, when PHP will support XLS 2.0, then we would do that this way: 1809 | * contains(tokenize(@class, '\s'), "something") 1810 | * @param unknown_type $class 1811 | * @param unknown_type $node 1812 | * @return boolean 1813 | * @access private 1814 | */ 1815 | protected function matchClasses($class, $node) 1816 | { 1817 | // multi-class 1818 | if (mb_strpos($class, '.', 1)) { 1819 | $classes = explode('.', substr($class, 1)); 1820 | $classesCount = count($classes); 1821 | $nodeClasses = explode(' ', $node->getAttribute('class')); 1822 | $nodeClassesCount = count($nodeClasses); 1823 | if ($classesCount > $nodeClassesCount) 1824 | return false; 1825 | $diff = count( 1826 | array_diff( 1827 | $classes, 1828 | $nodeClasses 1829 | ) 1830 | ); 1831 | if (!$diff) 1832 | return true; 1833 | // single-class 1834 | } else { 1835 | return in_array( 1836 | // strip leading dot from class name 1837 | substr($class, 1), 1838 | // get classes for element as array 1839 | explode(' ', $node->getAttribute('class')) 1840 | ); 1841 | } 1842 | } 1843 | /** 1844 | * @access private 1845 | */ 1846 | protected function runQuery($XQuery, $selector = null, $compare = null) 1847 | { 1848 | if ($compare && !method_exists($this, $compare)) 1849 | return false; 1850 | $stack = array(); 1851 | if (!$this->elements) 1852 | $this->debug('Stack empty, skipping...'); 1853 | // var_dump($this->elements[0]->nodeType); 1854 | // element, document 1855 | foreach ($this->stack(array(1, 9, 13)) as $k => $stackNode) { 1856 | $detachAfter = false; 1857 | // to work on detached nodes we need temporary place them somewhere 1858 | // thats because context xpath queries sucks ;] 1859 | $testNode = $stackNode; 1860 | while ($testNode) { 1861 | if (!$testNode->parentNode && !$this->isRoot($testNode)) { 1862 | $this->root->appendChild($testNode); 1863 | $detachAfter = $testNode; 1864 | break; 1865 | } 1866 | $testNode = isset($testNode->parentNode) 1867 | ? $testNode->parentNode 1868 | : null; 1869 | } 1870 | // XXX tmp ? 1871 | $xpath = $this->documentWrapper->isXHTML 1872 | ? $this->getNodeXpath($stackNode, 'html') 1873 | : $this->getNodeXpath($stackNode); 1874 | // FIXME pseudoclasses-only query, support XML 1875 | $query = $XQuery == '//' && $xpath == '/html[1]' 1876 | ? '//*' 1877 | : $xpath . $XQuery; 1878 | $this->debug("XPATH: {$query}"); 1879 | // run query, get elements 1880 | $nodes = $this->xpath->query($query); 1881 | $this->debug("QUERY FETCHED"); 1882 | if (!$nodes->length) 1883 | $this->debug('Nothing found'); 1884 | $debug = array(); 1885 | foreach ($nodes as $node) { 1886 | $matched = false; 1887 | if ($compare) { 1888 | phpQuery::$debug ? 1889 | $this->debug("Found: " . $this->whois($node) . ", comparing with {$compare}()") 1890 | : null; 1891 | $phpQueryDebug = phpQuery::$debug; 1892 | phpQuery::$debug = false; 1893 | // TODO ??? use phpQuery::callbackRun() 1894 | if (call_user_func_array(array($this, $compare), array($selector, $node))) 1895 | $matched = true; 1896 | phpQuery::$debug = $phpQueryDebug; 1897 | } else { 1898 | $matched = true; 1899 | } 1900 | if ($matched) { 1901 | if (phpQuery::$debug) 1902 | $debug[] = $this->whois($node); 1903 | $stack[] = $node; 1904 | } 1905 | } 1906 | if (phpQuery::$debug) { 1907 | $this->debug("Matched " . count($debug) . ": " . implode(', ', $debug)); 1908 | } 1909 | if ($detachAfter) 1910 | $this->root->removeChild($detachAfter); 1911 | } 1912 | $this->elements = $stack; 1913 | } 1914 | /** 1915 | * Enter description here... 1916 | * 1917 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 1918 | */ 1919 | public function find($selectors, $context = null, $noHistory = false) 1920 | { 1921 | if (!$noHistory) 1922 | // backup last stack /for end()/ 1923 | $this->elementsBackup = $this->elements; 1924 | // allow to define context 1925 | // TODO combine code below with phpQuery::pq() context guessing code 1926 | // as generic function 1927 | if ($context) { 1928 | if (!is_array($context) && $context instanceof DOMELEMENT) 1929 | $this->elements = array($context); 1930 | else if (is_array($context)) { 1931 | $this->elements = array(); 1932 | foreach ($context as $c) 1933 | if ($c instanceof DOMELEMENT) 1934 | $this->elements[] = $c; 1935 | } else if ($context instanceof self) 1936 | $this->elements = $context->elements; 1937 | } 1938 | $queries = $this->parseSelector($selectors); 1939 | $this->debug(array('FIND', $selectors, $queries)); 1940 | $XQuery = ''; 1941 | // remember stack state because of multi-queries 1942 | $oldStack = $this->elements; 1943 | // here we will be keeping found elements 1944 | $stack = array(); 1945 | foreach ($queries as $selector) { 1946 | $this->elements = $oldStack; 1947 | $delimiterBefore = false; 1948 | foreach ($selector as $s) { 1949 | // TAG 1950 | $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport 1951 | ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*' 1952 | : preg_match('@^[\w|\||-]+$@', $s) || $s == '*'; 1953 | if ($isTag) { 1954 | if ($this->isXML()) { 1955 | // namespace support 1956 | if (mb_strpos($s, '|') !== false) { 1957 | $ns = $tag = null; 1958 | list($ns, $tag) = explode('|', $s); 1959 | $XQuery .= "$ns:$tag"; 1960 | } else if ($s == '*') { 1961 | $XQuery .= "*"; 1962 | } else { 1963 | $XQuery .= "*[local-name()='$s']"; 1964 | } 1965 | } else { 1966 | $XQuery .= $s; 1967 | } 1968 | // ID 1969 | } else if ($s[0] == '#') { 1970 | if ($delimiterBefore) 1971 | $XQuery .= '*'; 1972 | $XQuery .= "[@id='" . substr($s, 1) . "']"; 1973 | // ATTRIBUTES 1974 | } else if ($s[0] == '[') { 1975 | if ($delimiterBefore) 1976 | $XQuery .= '*'; 1977 | // strip side brackets 1978 | $attr = trim($s, ']['); 1979 | $execute = false; 1980 | // attr with specifed value 1981 | if (mb_strpos($s, '=')) { 1982 | $value = null; 1983 | list($attr, $value) = explode('=', $attr); 1984 | $value = trim($value, "'\""); 1985 | if ($this->isRegexp($attr)) { 1986 | // cut regexp character 1987 | $attr = substr($attr, 0, -1); 1988 | $execute = true; 1989 | $XQuery .= "[@{$attr}]"; 1990 | } else { 1991 | $XQuery .= "[@{$attr}='{$value}']"; 1992 | } 1993 | // attr without specified value 1994 | } else { 1995 | $XQuery .= "[@{$attr}]"; 1996 | } 1997 | if ($execute) { 1998 | $this->runQuery($XQuery, $s, 'is'); 1999 | $XQuery = ''; 2000 | if (!$this->length()) 2001 | break; 2002 | } 2003 | // CLASSES 2004 | } else if ($s[0] == '.') { 2005 | // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]"); 2006 | // thx wizDom ;) 2007 | if ($delimiterBefore) 2008 | $XQuery .= '*'; 2009 | $XQuery .= '[@class]'; 2010 | $this->runQuery($XQuery, $s, 'matchClasses'); 2011 | $XQuery = ''; 2012 | if (!$this->length()) 2013 | break; 2014 | // ~ General Sibling Selector 2015 | } else if ($s[0] == '~') { 2016 | $this->runQuery($XQuery); 2017 | $XQuery = ''; 2018 | $this->elements = $this 2019 | ->siblings( 2020 | substr($s, 1) 2021 | )->elements; 2022 | if (!$this->length()) 2023 | break; 2024 | // + Adjacent sibling selectors 2025 | } else if ($s[0] == '+') { 2026 | // TODO /following-sibling:: 2027 | $this->runQuery($XQuery); 2028 | $XQuery = ''; 2029 | $subSelector = substr($s, 1); 2030 | $subElements = $this->elements; 2031 | $this->elements = array(); 2032 | foreach ($subElements as $node) { 2033 | // search first DOMElement sibling 2034 | $test = $node->nextSibling; 2035 | while ($test && !($test instanceof DOMELEMENT)) 2036 | $test = $test->nextSibling; 2037 | if ($test && $this->is($subSelector, $test)) 2038 | $this->elements[] = $test; 2039 | } 2040 | if (!$this->length()) 2041 | break; 2042 | // PSEUDO CLASSES 2043 | } else if ($s[0] == ':') { 2044 | // TODO optimization for :first :last 2045 | if ($XQuery) { 2046 | $this->runQuery($XQuery); 2047 | $XQuery = ''; 2048 | } 2049 | if (!$this->length()) 2050 | break; 2051 | $this->pseudoClasses($s); 2052 | if (!$this->length()) 2053 | break; 2054 | // DIRECT DESCENDANDS 2055 | } else if ($s == '>') { 2056 | $XQuery .= '/'; 2057 | $delimiterBefore = 2; 2058 | // ALL DESCENDANDS 2059 | } else if ($s == ' ') { 2060 | $XQuery .= '//'; 2061 | $delimiterBefore = 2; 2062 | // ERRORS 2063 | } else { 2064 | phpQuery::debug("Unrecognized token '$s'"); 2065 | } 2066 | $delimiterBefore = $delimiterBefore === 2; 2067 | } 2068 | // run query if any 2069 | if ($XQuery && $XQuery != '//') { 2070 | $this->runQuery($XQuery); 2071 | $XQuery = ''; 2072 | } 2073 | foreach ($this->elements as $node) 2074 | if (!$this->elementsContainsNode($node, $stack)) 2075 | $stack[] = $node; 2076 | } 2077 | $this->elements = $stack; 2078 | return $this->newInstance(); 2079 | } 2080 | /** 2081 | * @todo create API for classes with pseudoselectors 2082 | * @access private 2083 | */ 2084 | protected function pseudoClasses($class) 2085 | { 2086 | // TODO clean args parsing ? 2087 | $class = ltrim($class, ':'); 2088 | $haveArgs = mb_strpos($class, '('); 2089 | if ($haveArgs !== false) { 2090 | $args = substr($class, $haveArgs + 1, -1); 2091 | $class = substr($class, 0, $haveArgs); 2092 | } 2093 | switch ($class) { 2094 | case 'even': 2095 | case 'odd': 2096 | $stack = array(); 2097 | foreach ($this->elements as $i => $node) { 2098 | if ($class == 'even' && ($i % 2) == 0) 2099 | $stack[] = $node; 2100 | else if ($class == 'odd' && $i % 2) 2101 | $stack[] = $node; 2102 | } 2103 | $this->elements = $stack; 2104 | break; 2105 | case 'eq': 2106 | $k = intval($args); 2107 | if ($k < 0) { 2108 | $this->elements = array($this->elements[count($this->elements) + $k]); 2109 | } else { 2110 | $this->elements = isset($this->elements[$k]) 2111 | ? array($this->elements[$k]) 2112 | : array(); 2113 | } 2114 | break; 2115 | case 'gt': 2116 | $this->elements = array_slice($this->elements, $args + 1); 2117 | break; 2118 | case 'lt': 2119 | $this->elements = array_slice($this->elements, 0, $args + 1); 2120 | break; 2121 | case 'first': 2122 | if (isset($this->elements[0])) 2123 | $this->elements = array($this->elements[0]); 2124 | break; 2125 | case 'last': 2126 | if ($this->elements) 2127 | $this->elements = array($this->elements[count($this->elements) - 1]); 2128 | break; 2129 | /*case 'parent': 2130 | $stack = array(); 2131 | foreach($this->elements as $node) { 2132 | if ( $node->childNodes->length ) 2133 | $stack[] = $node; 2134 | } 2135 | $this->elements = $stack; 2136 | break;*/ 2137 | case 'contains': 2138 | $text = trim($args, "\"'"); 2139 | $stack = array(); 2140 | foreach ($this->elements as $node) { 2141 | if (mb_stripos($node->textContent, $text) === false) 2142 | continue; 2143 | $stack[] = $node; 2144 | } 2145 | $this->elements = $stack; 2146 | break; 2147 | case 'not': 2148 | $selector = self::unQuote($args); 2149 | $this->elements = $this->not($selector)->stack(); 2150 | break; 2151 | case 'slice': 2152 | // TODO jQuery difference ? 2153 | $args = explode( 2154 | ',', 2155 | str_replace(', ', ',', trim($args, "\"'")) 2156 | ); 2157 | $start = $args[0]; 2158 | $end = isset($args[1]) 2159 | ? $args[1] 2160 | : null; 2161 | if ($end > 0) 2162 | $end = $end - $start; 2163 | $this->elements = array_slice($this->elements, $start, $end); 2164 | break; 2165 | case 'has': 2166 | $selector = trim($args, "\"'"); 2167 | $stack = array(); 2168 | foreach ($this->stack(1) as $el) { 2169 | if ($this->find($selector, $el, true)->length) 2170 | $stack[] = $el; 2171 | } 2172 | $this->elements = $stack; 2173 | break; 2174 | case 'submit': 2175 | case 'reset': 2176 | $this->elements = phpQuery::merge( 2177 | $this->map( 2178 | array($this, 'is'), 2179 | "input[type=$class]", 2180 | new CallbackParam() 2181 | ), 2182 | $this->map( 2183 | array($this, 'is'), 2184 | "button[type=$class]", 2185 | new CallbackParam() 2186 | ) 2187 | ); 2188 | break; 2189 | // $stack = array(); 2190 | // foreach($this->elements as $node) 2191 | // if ($node->is('input[type=submit]') || $node->is('button[type=submit]')) 2192 | // $stack[] = $el; 2193 | // $this->elements = $stack; 2194 | case 'input': 2195 | $this->elements = $this->map( 2196 | array($this, 'is'), 2197 | 'input', 2198 | new CallbackParam() 2199 | )->elements; 2200 | break; 2201 | case 'password': 2202 | case 'checkbox': 2203 | case 'radio': 2204 | case 'hidden': 2205 | case 'image': 2206 | case 'file': 2207 | $this->elements = $this->map( 2208 | array($this, 'is'), 2209 | "input[type=$class]", 2210 | new CallbackParam() 2211 | )->elements; 2212 | break; 2213 | case 'parent': 2214 | $this->elements = $this->map( 2215 | function ($node) { 2216 | return $node instanceof DOMELEMENT && $node->childNodes->length 2217 | ? $node : null; 2218 | } 2219 | )->elements; 2220 | break; 2221 | case 'empty': 2222 | $this->elements = $this->map( 2223 | function ($node) { 2224 | return $node instanceof DOMELEMENT && $node->childNodes->length 2225 | ? null : $node; 2226 | } 2227 | )->elements; 2228 | break; 2229 | case 'disabled': 2230 | case 'selected': 2231 | case 'checked': 2232 | $this->elements = $this->map( 2233 | array($this, 'is'), 2234 | "[$class]", 2235 | new CallbackParam() 2236 | )->elements; 2237 | break; 2238 | case 'enabled': 2239 | $this->elements = $this->map( 2240 | function ($node) { 2241 | return pq($node)->not(":disabled") ? $node : null; 2242 | } 2243 | )->elements; 2244 | break; 2245 | case 'header': 2246 | $this->elements = $this->map( 2247 | function ($node) { 2248 | $isHeader = isset($node->tagName) && in_array($node->tagName, array( 2249 | "h1", "h2", "h3", "h4", "h5", "h6", "h7" 2250 | )); 2251 | return $isHeader 2252 | ? $node 2253 | : null; 2254 | } 2255 | )->elements; 2256 | // $this->elements = $this->map( 2257 | // create_function('$node', '$node = pq($node); 2258 | // return $node->is("h1") 2259 | // || $node->is("h2") 2260 | // || $node->is("h3") 2261 | // || $node->is("h4") 2262 | // || $node->is("h5") 2263 | // || $node->is("h6") 2264 | // || $node->is("h7") 2265 | // ? $node 2266 | // : null;') 2267 | // )->elements; 2268 | break; 2269 | case 'only-child': 2270 | $this->elements = $this->map( 2271 | function ($node) { 2272 | return pq($node)->siblings()->size() == 0 ? $node : null; 2273 | } 2274 | )->elements; 2275 | break; 2276 | case 'first-child': 2277 | $this->elements = $this->map( 2278 | function ($node) { 2279 | return pq($node)->prevAll()->size() == 0 ? $node : null; 2280 | } 2281 | )->elements; 2282 | break; 2283 | case 'last-child': 2284 | $this->elements = $this->map( 2285 | function ($node) { 2286 | return pq($node)->nextAll()->size() == 0 ? $node : null; 2287 | } 2288 | )->elements; 2289 | break; 2290 | case 'nth-child': 2291 | $param = trim($args, "\"'"); 2292 | if (!$param) 2293 | break; 2294 | // nth-child(n+b) to nth-child(1n+b) 2295 | if ($param[0] == 'n') 2296 | $param = '1' . $param; 2297 | // :nth-child(index/even/odd/equation) 2298 | if ($param == 'even' || $param == 'odd') 2299 | $mapped = $this->map( 2300 | function ($node, $param) { 2301 | $index = pq($node)->prevAll()->size() + 1; 2302 | if ($param == "even" && ($index % 2) == 0) 2303 | return $node; 2304 | else if ($param == "odd" && $index % 2 == 1) 2305 | return $node; 2306 | else 2307 | return null; 2308 | }, 2309 | new CallbackParam(), 2310 | $param 2311 | ); 2312 | else if (mb_strlen($param) > 1 && preg_match('/^(\d*)n([-+]?)(\d*)/', $param) === 1) 2313 | // an+b 2314 | $mapped = $this->map( 2315 | function ($node, $param) { 2316 | $prevs = pq($node)->prevAll()->size(); 2317 | $index = 1 + $prevs; 2318 | 2319 | preg_match("/^(\d*)n([-+]?)(\d*)/", $param, $matches); 2320 | $a = intval($matches[1]); 2321 | $b = intval($matches[3]); 2322 | if ($matches[2] === "-") { 2323 | $b = -$b; 2324 | } 2325 | 2326 | if ($a > 0) { 2327 | return ($index - $b) % $a == 0 2328 | ? $node 2329 | : null; 2330 | phpQuery::debug($a . "*" . floor($index / $a) . "+$b-1 == " . ($a * floor($index / $a) + $b - 1) . " ?= $prevs"); 2331 | return $a * floor($index / $a) + $b - 1 == $prevs 2332 | ? $node 2333 | : null; 2334 | } else if ($a == 0) 2335 | return $index == $b 2336 | ? $node 2337 | : null; 2338 | else 2339 | // negative value 2340 | return $index <= $b 2341 | ? $node 2342 | : null; 2343 | // if (! $b) 2344 | // return $index%$a == 0 2345 | // ? $node 2346 | // : null; 2347 | // else 2348 | // return ($index-$b)%$a == 0 2349 | // ? $node 2350 | // : null; 2351 | }, 2352 | new CallbackParam(), 2353 | $param 2354 | ); 2355 | else 2356 | // index 2357 | $mapped = $this->map( 2358 | function ($node, $index) { 2359 | $prevs = pq($node)->prevAll()->size(); 2360 | if ($prevs && $prevs == $index - 1) 2361 | return $node; 2362 | else if (!$prevs && $index == 1) 2363 | return $node; 2364 | else 2365 | return null; 2366 | }, 2367 | new CallbackParam(), 2368 | $param 2369 | ); 2370 | $this->elements = $mapped->elements; 2371 | break; 2372 | default: 2373 | $this->debug("Unknown pseudoclass '{$class}', skipping..."); 2374 | } 2375 | } 2376 | /** 2377 | * @access private 2378 | */ 2379 | protected function __pseudoClassParam($paramsString) 2380 | { 2381 | // TODO; 2382 | } 2383 | /** 2384 | * Enter description here... 2385 | * 2386 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2387 | */ 2388 | public function is($selector, $nodes = null) 2389 | { 2390 | phpQuery::debug(array("Is:", $selector)); 2391 | if (!$selector) 2392 | return false; 2393 | $oldStack = $this->elements; 2394 | $returnArray = false; 2395 | if ($nodes && is_array($nodes)) { 2396 | $this->elements = $nodes; 2397 | } else if ($nodes) 2398 | $this->elements = array($nodes); 2399 | $this->filter($selector, true); 2400 | $stack = $this->elements; 2401 | $this->elements = $oldStack; 2402 | if ($nodes) 2403 | return $stack ? $stack : null; 2404 | return (bool)count($stack); 2405 | } 2406 | /** 2407 | * Enter description here... 2408 | * jQuery difference. 2409 | * 2410 | * Callback: 2411 | * - $index int 2412 | * - $node DOMNode 2413 | * 2414 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2415 | * @link http://docs.jquery.com/Traversing/filter 2416 | */ 2417 | public function filterCallback($callback, $_skipHistory = false) 2418 | { 2419 | if (!$_skipHistory) { 2420 | $this->elementsBackup = $this->elements; 2421 | $this->debug("Filtering by callback"); 2422 | } 2423 | $newStack = array(); 2424 | foreach ($this->elements as $index => $node) { 2425 | $result = phpQuery::callbackRun($callback, array($index, $node)); 2426 | if (is_null($result) || (!is_null($result) && $result)) 2427 | $newStack[] = $node; 2428 | } 2429 | $this->elements = $newStack; 2430 | return $_skipHistory 2431 | ? $this 2432 | : $this->newInstance(); 2433 | } 2434 | /** 2435 | * Enter description here... 2436 | * 2437 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2438 | * @link http://docs.jquery.com/Traversing/filter 2439 | */ 2440 | public function filter($selectors, $_skipHistory = false) 2441 | { 2442 | if ($selectors instanceof Callback or $selectors instanceof Closure) 2443 | return $this->filterCallback($selectors, $_skipHistory); 2444 | if (!$_skipHistory) 2445 | $this->elementsBackup = $this->elements; 2446 | $notSimpleSelector = array(' ', '>', '~', '+', '/'); 2447 | if (!is_array($selectors)) 2448 | $selectors = $this->parseSelector($selectors); 2449 | if (!$_skipHistory) 2450 | $this->debug(array("Filtering:", $selectors)); 2451 | $finalStack = array(); 2452 | foreach ($selectors as $selector) { 2453 | $stack = array(); 2454 | if (!$selector) 2455 | break; 2456 | // avoid first space or / 2457 | if (in_array($selector[0], $notSimpleSelector)) 2458 | $selector = array_slice($selector, 1); 2459 | // PER NODE selector chunks 2460 | foreach ($this->stack() as $node) { 2461 | $break = false; 2462 | foreach ($selector as $s) { 2463 | if (!($node instanceof DOMELEMENT)) { 2464 | // all besides DOMElement 2465 | if ($s[0] == '[') { 2466 | $attr = trim($s, '[]'); 2467 | if (mb_strpos($attr, '=')) { 2468 | list($attr, $val) = explode('=', $attr); 2469 | if ($attr == 'nodeType' && $node->nodeType != $val) 2470 | $break = true; 2471 | } 2472 | } else 2473 | $break = true; 2474 | } else { 2475 | // DOMElement only 2476 | // ID 2477 | if ($s[0] == '#') { 2478 | if ($node->getAttribute('id') != substr($s, 1)) 2479 | $break = true; 2480 | // CLASSES 2481 | } else if ($s[0] == '.') { 2482 | if (!$this->matchClasses($s, $node)) 2483 | $break = true; 2484 | // ATTRS 2485 | } else if ($s[0] == '[') { 2486 | // strip side brackets 2487 | $attr = trim($s, '[]'); 2488 | if (mb_strpos($attr, '=')) { 2489 | list($attr, $val) = explode('=', $attr); 2490 | $val = self::unQuote($val); 2491 | if ($attr == 'nodeType') { 2492 | if ($val != $node->nodeType) 2493 | $break = true; 2494 | } else if ($this->isRegexp($attr)) { 2495 | $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport 2496 | ? quotemeta(trim($val, '"\'')) 2497 | : preg_quote(trim($val, '"\''), '@'); 2498 | // switch last character 2499 | switch (substr($attr, -1)) { 2500 | // quotemeta used insted of preg_quote 2501 | // http://code.google.com/p/phpquery/issues/detail?id=76 2502 | case '^': 2503 | $pattern = '^' . $val; 2504 | break; 2505 | case '*': 2506 | $pattern = '.*' . $val . '.*'; 2507 | break; 2508 | case '$': 2509 | $pattern = '.*' . $val . '$'; 2510 | break; 2511 | } 2512 | // cut last character 2513 | $attr = substr($attr, 0, -1); 2514 | $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport 2515 | ? mb_ereg_match($pattern, $node->getAttribute($attr)) 2516 | : preg_match("@{$pattern}@", $node->getAttribute($attr)); 2517 | if (!$isMatch) 2518 | $break = true; 2519 | } else if ($node->getAttribute($attr) != $val) 2520 | $break = true; 2521 | } else if (!$node->hasAttribute($attr)) 2522 | $break = true; 2523 | // PSEUDO CLASSES 2524 | } else if ($s[0] == ':') { 2525 | // skip 2526 | // TAG 2527 | } else if (trim($s)) { 2528 | if ($s != '*') { 2529 | // TODO namespaces 2530 | if (isset($node->tagName)) { 2531 | if ($node->tagName != $s) 2532 | $break = true; 2533 | } else if ($s == 'html' && !$this->isRoot($node)) 2534 | $break = true; 2535 | } 2536 | // AVOID NON-SIMPLE SELECTORS 2537 | } else if (in_array($s, $notSimpleSelector)) { 2538 | $break = true; 2539 | $this->debug(array('Skipping non simple selector', $selector)); 2540 | } 2541 | } 2542 | if ($break) 2543 | break; 2544 | } 2545 | // if element passed all chunks of selector - add it to new stack 2546 | if (!$break) 2547 | $stack[] = $node; 2548 | } 2549 | $tmpStack = $this->elements; 2550 | $this->elements = $stack; 2551 | // PER ALL NODES selector chunks 2552 | foreach ($selector as $s) 2553 | // PSEUDO CLASSES 2554 | if ($s[0] == ':') 2555 | $this->pseudoClasses($s); 2556 | foreach ($this->elements as $node) 2557 | // XXX it should be merged without duplicates 2558 | // but jQuery doesnt do that 2559 | $finalStack[] = $node; 2560 | $this->elements = $tmpStack; 2561 | } 2562 | $this->elements = $finalStack; 2563 | if ($_skipHistory) { 2564 | return $this; 2565 | } else { 2566 | $this->debug("Stack length after filter(): " . count($finalStack)); 2567 | return $this->newInstance(); 2568 | } 2569 | } 2570 | /** 2571 | * 2572 | * @param $value 2573 | * @return unknown_type 2574 | * @TODO implement in all methods using passed parameters 2575 | */ 2576 | protected static function unQuote($value) 2577 | { 2578 | return $value[0] == '\'' || $value[0] == '"' 2579 | ? substr($value, 1, -1) 2580 | : $value; 2581 | } 2582 | /** 2583 | * Enter description here... 2584 | * 2585 | * @link http://docs.jquery.com/Ajax/load 2586 | * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2587 | * @todo Support $selector 2588 | */ 2589 | public function load($url, $data = null, $callback = null) 2590 | { 2591 | if ($data && !is_array($data)) { 2592 | $callback = $data; 2593 | $data = null; 2594 | } 2595 | if (mb_strpos($url, ' ') !== false) { 2596 | $matches = null; 2597 | if (extension_loaded('mbstring') && phpQuery::$mbstringSupport) 2598 | mb_ereg('^([^ ]+) (.*)$', $url, $matches); 2599 | else 2600 | preg_match('^([^ ]+) (.*)$', $url, $matches); 2601 | $url = $matches[1]; 2602 | $selector = $matches[2]; 2603 | // FIXME this sucks, pass as callback param 2604 | $this->_loadSelector = $selector; 2605 | } 2606 | $ajax = array( 2607 | 'url' => $url, 2608 | 'type' => $data ? 'POST' : 'GET', 2609 | 'data' => $data, 2610 | 'complete' => $callback, 2611 | 'success' => array($this, '__loadSuccess') 2612 | ); 2613 | phpQuery::ajax($ajax); 2614 | return $this; 2615 | } 2616 | /** 2617 | * @access private 2618 | * @param $html 2619 | * @return unknown_type 2620 | */ 2621 | public function __loadSuccess($html) 2622 | { 2623 | if ($this->_loadSelector) { 2624 | $html = phpQuery::newDocument($html)->find($this->_loadSelector); 2625 | unset($this->_loadSelector); 2626 | } 2627 | foreach ($this->stack(1) as $node) { 2628 | phpQuery::pq($node, $this->getDocumentID()) 2629 | ->markup($html); 2630 | } 2631 | } 2632 | /** 2633 | * Enter description here... 2634 | * 2635 | * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2636 | * @todo 2637 | */ 2638 | public function css() 2639 | { 2640 | // TODO 2641 | return $this; 2642 | } 2643 | /** 2644 | * @todo 2645 | * 2646 | */ 2647 | public function show() 2648 | { 2649 | // TODO 2650 | return $this; 2651 | } 2652 | /** 2653 | * @todo 2654 | * 2655 | */ 2656 | public function hide() 2657 | { 2658 | // TODO 2659 | return $this; 2660 | } 2661 | /** 2662 | * Trigger a type of event on every matched element. 2663 | * 2664 | * @param unknown_type $type 2665 | * @param unknown_type $data 2666 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2667 | * @TODO support more than event in $type (space-separated) 2668 | */ 2669 | public function trigger($type, $data = array()) 2670 | { 2671 | foreach ($this->elements as $node) 2672 | phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node); 2673 | return $this; 2674 | } 2675 | /** 2676 | * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions. 2677 | * 2678 | * @param unknown_type $type 2679 | * @param unknown_type $data 2680 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2681 | * @TODO 2682 | */ 2683 | public function triggerHandler($type, $data = array()) 2684 | { 2685 | // TODO; 2686 | } 2687 | /** 2688 | * Binds a handler to one or more events (like click) for each matched element. 2689 | * Can also bind custom events. 2690 | * 2691 | * @param unknown_type $type 2692 | * @param unknown_type $data Optional 2693 | * @param unknown_type $callback 2694 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2695 | * @TODO support '!' (exclusive) events 2696 | * @TODO support more than event in $type (space-separated) 2697 | */ 2698 | public function bind($type, $data, $callback = null) 2699 | { 2700 | // TODO check if $data is callable, not using is_callable 2701 | if (!isset($callback)) { 2702 | $callback = $data; 2703 | $data = null; 2704 | } 2705 | foreach ($this->elements as $node) 2706 | phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback); 2707 | return $this; 2708 | } 2709 | /** 2710 | * Enter description here... 2711 | * 2712 | * @param unknown_type $type 2713 | * @param unknown_type $callback 2714 | * @return unknown 2715 | * @TODO namespace events 2716 | * @TODO support more than event in $type (space-separated) 2717 | */ 2718 | public function unbind($type = null, $callback = null) 2719 | { 2720 | foreach ($this->elements as $node) 2721 | phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback); 2722 | return $this; 2723 | } 2724 | /** 2725 | * Enter description here... 2726 | * 2727 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2728 | */ 2729 | public function change($callback = null) 2730 | { 2731 | if ($callback) 2732 | return $this->bind('change', $callback); 2733 | return $this->trigger('change'); 2734 | } 2735 | /** 2736 | * Enter description here... 2737 | * 2738 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2739 | */ 2740 | public function submit($callback = null) 2741 | { 2742 | if ($callback) 2743 | return $this->bind('submit', $callback); 2744 | return $this->trigger('submit'); 2745 | } 2746 | /** 2747 | * Enter description here... 2748 | * 2749 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2750 | */ 2751 | public function click($callback = null) 2752 | { 2753 | if ($callback) 2754 | return $this->bind('click', $callback); 2755 | return $this->trigger('click'); 2756 | } 2757 | /** 2758 | * Enter description here... 2759 | * 2760 | * @param String|phpQuery 2761 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2762 | */ 2763 | public function wrapAllOld($wrapper) 2764 | { 2765 | $wrapper = pq($wrapper)->_clone(); 2766 | if (!$wrapper->length() || !$this->length()) 2767 | return $this; 2768 | $wrapper->insertBefore($this->elements[0]); 2769 | $deepest = $wrapper->elements[0]; 2770 | while ($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) 2771 | $deepest = $deepest->firstChild; 2772 | pq($deepest)->append($this); 2773 | return $this; 2774 | } 2775 | /** 2776 | * Enter description here... 2777 | * 2778 | * TODO testme... 2779 | * @param String|phpQuery 2780 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2781 | */ 2782 | public function wrapAll($wrapper) 2783 | { 2784 | if (!$this->length()) 2785 | return $this; 2786 | return phpQuery::pq($wrapper, $this->getDocumentID()) 2787 | ->clone() 2788 | ->insertBefore($this->get(0)) 2789 | ->map(array($this, '___wrapAllCallback')) 2790 | ->append($this); 2791 | } 2792 | /** 2793 | * 2794 | * @param $node 2795 | * @return unknown_type 2796 | * @access private 2797 | */ 2798 | public function ___wrapAllCallback($node) 2799 | { 2800 | $deepest = $node; 2801 | while ($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT) 2802 | $deepest = $deepest->firstChild; 2803 | return $deepest; 2804 | } 2805 | /** 2806 | * Enter description here... 2807 | * NON JQUERY METHOD 2808 | * 2809 | * @param String|phpQuery 2810 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2811 | */ 2812 | public function wrapAllPHP($codeBefore, $codeAfter) 2813 | { 2814 | return $this 2815 | ->slice(0, 1) 2816 | ->beforePHP($codeBefore) 2817 | ->end() 2818 | ->slice(-1) 2819 | ->afterPHP($codeAfter) 2820 | ->end(); 2821 | } 2822 | /** 2823 | * Enter description here... 2824 | * 2825 | * @param String|phpQuery 2826 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2827 | */ 2828 | public function wrap($wrapper) 2829 | { 2830 | foreach ($this->stack() as $node) 2831 | phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper); 2832 | return $this; 2833 | } 2834 | /** 2835 | * Enter description here... 2836 | * 2837 | * @param String|phpQuery 2838 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2839 | */ 2840 | public function wrapPHP($codeBefore, $codeAfter) 2841 | { 2842 | foreach ($this->stack() as $node) 2843 | phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter); 2844 | return $this; 2845 | } 2846 | /** 2847 | * Enter description here... 2848 | * 2849 | * @param String|phpQuery 2850 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2851 | */ 2852 | public function wrapInner($wrapper) 2853 | { 2854 | foreach ($this->stack() as $node) 2855 | phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper); 2856 | return $this; 2857 | } 2858 | /** 2859 | * Enter description here... 2860 | * 2861 | * @param String|phpQuery 2862 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2863 | */ 2864 | public function wrapInnerPHP($codeBefore, $codeAfter) 2865 | { 2866 | foreach ($this->stack(1) as $node) 2867 | phpQuery::pq($node, $this->getDocumentID())->contents() 2868 | ->wrapAllPHP($codeBefore, $codeAfter); 2869 | return $this; 2870 | } 2871 | /** 2872 | * Enter description here... 2873 | * 2874 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2875 | * @testme Support for text nodes 2876 | */ 2877 | public function contents() 2878 | { 2879 | $stack = array(); 2880 | foreach ($this->stack(1) as $el) { 2881 | // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56 2882 | // if (! isset($el->childNodes)) 2883 | // continue; 2884 | foreach ($el->childNodes as $node) { 2885 | $stack[] = $node; 2886 | } 2887 | } 2888 | return $this->newInstance($stack); 2889 | } 2890 | /** 2891 | * Enter description here... 2892 | * 2893 | * jQuery difference. 2894 | * 2895 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2896 | */ 2897 | public function contentsUnwrap() 2898 | { 2899 | foreach ($this->stack(1) as $node) { 2900 | if (!$node->parentNode) 2901 | continue; 2902 | $childNodes = array(); 2903 | // any modification in DOM tree breaks childNodes iteration, so cache them first 2904 | foreach ($node->childNodes as $chNode) 2905 | $childNodes[] = $chNode; 2906 | foreach ($childNodes as $chNode) 2907 | // $node->parentNode->appendChild($chNode); 2908 | $node->parentNode->insertBefore($chNode, $node); 2909 | $node->parentNode->removeChild($node); 2910 | } 2911 | return $this; 2912 | } 2913 | /** 2914 | * Enter description here... 2915 | * 2916 | * jQuery difference. 2917 | */ 2918 | public function switchWith($markup) 2919 | { 2920 | $markup = pq($markup, $this->getDocumentID()); 2921 | $content = null; 2922 | foreach ($this->stack(1) as $node) { 2923 | pq($node) 2924 | ->contents()->toReference($content)->end() 2925 | ->replaceWith($markup->clone()->append($content)); 2926 | } 2927 | return $this; 2928 | } 2929 | /** 2930 | * Enter description here... 2931 | * 2932 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2933 | */ 2934 | public function eq($num) 2935 | { 2936 | $oldStack = $this->elements; 2937 | $this->elementsBackup = $this->elements; 2938 | $this->elements = array(); 2939 | if (isset($oldStack[$num])) 2940 | $this->elements[] = $oldStack[$num]; 2941 | return $this->newInstance(); 2942 | } 2943 | /** 2944 | * Enter description here... 2945 | * 2946 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2947 | */ 2948 | public function size() 2949 | { 2950 | return count($this->elements); 2951 | } 2952 | /** 2953 | * Enter description here... 2954 | * 2955 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2956 | * @deprecated Use length as attribute 2957 | */ 2958 | public function length() 2959 | { 2960 | return $this->size(); 2961 | } 2962 | 2963 | #[\ReturnTypeWillChange] 2964 | public function count() 2965 | { 2966 | return $this->size(); 2967 | } 2968 | /** 2969 | * Enter description here... 2970 | * 2971 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2972 | * @todo $level 2973 | */ 2974 | public function end($level = 1) 2975 | { 2976 | // $this->elements = array_pop( $this->history ); 2977 | // return $this; 2978 | // $this->previous->DOM = $this->DOM; 2979 | // $this->previous->XPath = $this->XPath; 2980 | return $this->previous 2981 | ? $this->previous 2982 | : $this; 2983 | } 2984 | /** 2985 | * Enter description here... 2986 | * Normal use ->clone() . 2987 | * 2988 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 2989 | * @access private 2990 | */ 2991 | public function _clone() 2992 | { 2993 | $newStack = array(); 2994 | //pr(array('copy... ', $this->whois())); 2995 | //$this->dumpHistory('copy'); 2996 | $this->elementsBackup = $this->elements; 2997 | foreach ($this->elements as $node) { 2998 | $newStack[] = $node->cloneNode(true); 2999 | } 3000 | $this->elements = $newStack; 3001 | return $this->newInstance(); 3002 | } 3003 | /** 3004 | * Enter description here... 3005 | * 3006 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3007 | */ 3008 | public function replaceWithPHP($code) 3009 | { 3010 | return $this->replaceWith(phpQuery::php($code)); 3011 | } 3012 | /** 3013 | * Enter description here... 3014 | * 3015 | * @param String|phpQuery $content 3016 | * @link http://docs.jquery.com/Manipulation/replaceWith#content 3017 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3018 | */ 3019 | public function replaceWith($content) 3020 | { 3021 | return $this->after($content)->remove(); 3022 | } 3023 | /** 3024 | * Enter description here... 3025 | * 3026 | * @param String $selector 3027 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3028 | * @todo this works ? 3029 | */ 3030 | public function replaceAll($selector) 3031 | { 3032 | foreach (phpQuery::pq($selector, $this->getDocumentID()) as $node) 3033 | phpQuery::pq($node, $this->getDocumentID()) 3034 | ->after($this->_clone()) 3035 | ->remove(); 3036 | return $this; 3037 | } 3038 | /** 3039 | * Enter description here... 3040 | * 3041 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3042 | */ 3043 | public function remove($selector = null) 3044 | { 3045 | $loop = $selector 3046 | ? $this->filter($selector)->elements 3047 | : $this->elements; 3048 | foreach ($loop as $node) { 3049 | if (!$node->parentNode) 3050 | continue; 3051 | if (isset($node->tagName)) 3052 | $this->debug("Removing '{$node->tagName}'"); 3053 | $node->parentNode->removeChild($node); 3054 | // Mutation event 3055 | $event = new DOMEvent(array( 3056 | 'target' => $node, 3057 | 'type' => 'DOMNodeRemoved' 3058 | )); 3059 | phpQueryEvents::trigger( 3060 | $this->getDocumentID(), 3061 | $event->type, 3062 | array($event), 3063 | $node 3064 | ); 3065 | } 3066 | return $this; 3067 | } 3068 | protected function markupEvents($newMarkup, $oldMarkup, $node) 3069 | { 3070 | if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) { 3071 | $event = new DOMEvent(array( 3072 | 'target' => $node, 3073 | 'type' => 'change' 3074 | )); 3075 | phpQueryEvents::trigger( 3076 | $this->getDocumentID(), 3077 | $event->type, 3078 | array($event), 3079 | $node 3080 | ); 3081 | } 3082 | } 3083 | /** 3084 | * jQuey difference 3085 | * 3086 | * @param $markup 3087 | * @return unknown_type 3088 | * @TODO trigger change event for textarea 3089 | */ 3090 | public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null) 3091 | { 3092 | $args = func_get_args(); 3093 | if ($this->documentWrapper->isXML) 3094 | return call_user_func_array(array($this, 'xml'), $args); 3095 | else 3096 | return call_user_func_array(array($this, 'html'), $args); 3097 | } 3098 | /** 3099 | * jQuey difference 3100 | * 3101 | * @param $markup 3102 | * @return unknown_type 3103 | */ 3104 | public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null) 3105 | { 3106 | $args = func_get_args(); 3107 | if ($this->documentWrapper->isXML) 3108 | return call_user_func_array(array($this, 'xmlOuter'), $args); 3109 | else 3110 | return call_user_func_array(array($this, 'htmlOuter'), $args); 3111 | } 3112 | /** 3113 | * Enter description here... 3114 | * 3115 | * @param unknown_type $html 3116 | * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3117 | * @TODO force html result 3118 | */ 3119 | public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null) 3120 | { 3121 | if (isset($html)) { 3122 | // INSERT 3123 | $nodes = $this->documentWrapper->import($html); 3124 | $this->empty(); 3125 | foreach ($this->stack(1) as $alreadyAdded => $node) { 3126 | // for now, limit events for textarea 3127 | if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') 3128 | $oldHtml = pq($node, $this->getDocumentID())->markup(); 3129 | foreach ($nodes as $newNode) { 3130 | $node->appendChild( 3131 | $alreadyAdded 3132 | ? $newNode->cloneNode(true) 3133 | : $newNode 3134 | ); 3135 | } 3136 | // for now, limit events for textarea 3137 | if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea') 3138 | $this->markupEvents($html, $oldHtml, $node); 3139 | } 3140 | return $this; 3141 | } else { 3142 | // FETCH 3143 | $return = $this->documentWrapper->markup($this->elements, true); 3144 | $args = func_get_args(); 3145 | foreach (array_slice($args, 1) as $callback) { 3146 | $return = phpQuery::callbackRun($callback, array($return)); 3147 | } 3148 | return $return; 3149 | } 3150 | } 3151 | /** 3152 | * @TODO force xml result 3153 | */ 3154 | public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null) 3155 | { 3156 | $args = func_get_args(); 3157 | return call_user_func_array(array($this, 'html'), $args); 3158 | } 3159 | /** 3160 | * Enter description here... 3161 | * @TODO force html result 3162 | * 3163 | * @return String 3164 | */ 3165 | public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null) 3166 | { 3167 | $markup = $this->documentWrapper->markup($this->elements); 3168 | // pass thou callbacks 3169 | $args = func_get_args(); 3170 | foreach ($args as $callback) { 3171 | $markup = phpQuery::callbackRun($callback, array($markup)); 3172 | } 3173 | return $markup; 3174 | } 3175 | /** 3176 | * @TODO force xml result 3177 | */ 3178 | public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null) 3179 | { 3180 | $args = func_get_args(); 3181 | return call_user_func_array(array($this, 'htmlOuter'), $args); 3182 | } 3183 | public function __toString() 3184 | { 3185 | return $this->markupOuter(); 3186 | } 3187 | /** 3188 | * Just like html(), but returns markup with VALID (dangerous) PHP tags. 3189 | * 3190 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3191 | * @todo support returning markup with PHP tags when called without param 3192 | */ 3193 | public function php($code = null) 3194 | { 3195 | return $this->markupPHP($code); 3196 | } 3197 | /** 3198 | * Enter description here... 3199 | * 3200 | * @param $code 3201 | * @return unknown_type 3202 | */ 3203 | public function markupPHP($code = null) 3204 | { 3205 | return isset($code) 3206 | ? $this->markup(phpQuery::php($code)) 3207 | : phpQuery::markupToPHP($this->markup()); 3208 | } 3209 | /** 3210 | * Enter description here... 3211 | * 3212 | * @param $code 3213 | * @return unknown_type 3214 | */ 3215 | public function markupOuterPHP() 3216 | { 3217 | return phpQuery::markupToPHP($this->markupOuter()); 3218 | } 3219 | /** 3220 | * Enter description here... 3221 | * 3222 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3223 | */ 3224 | public function children($selector = null) 3225 | { 3226 | $stack = array(); 3227 | foreach ($this->stack(1) as $node) { 3228 | // foreach($node->getElementsByTagName('*') as $newNode) { 3229 | foreach ($node->childNodes as $newNode) { 3230 | if ($newNode->nodeType != 1) 3231 | continue; 3232 | if ($selector && !$this->is($selector, $newNode)) 3233 | continue; 3234 | if ($this->elementsContainsNode($newNode, $stack)) 3235 | continue; 3236 | $stack[] = $newNode; 3237 | } 3238 | } 3239 | $this->elementsBackup = $this->elements; 3240 | $this->elements = $stack; 3241 | return $this->newInstance(); 3242 | } 3243 | /** 3244 | * Enter description here... 3245 | * 3246 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3247 | */ 3248 | public function ancestors($selector = null) 3249 | { 3250 | return $this->children($selector); 3251 | } 3252 | /** 3253 | * Enter description here... 3254 | * 3255 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3256 | */ 3257 | public function append($content) 3258 | { 3259 | return $this->insert($content, __FUNCTION__); 3260 | } 3261 | /** 3262 | * Enter description here... 3263 | * 3264 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3265 | */ 3266 | public function appendPHP($content) 3267 | { 3268 | return $this->insert("", 'append'); 3269 | } 3270 | /** 3271 | * Enter description here... 3272 | * 3273 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3274 | */ 3275 | public function appendTo($seletor) 3276 | { 3277 | return $this->insert($seletor, __FUNCTION__); 3278 | } 3279 | /** 3280 | * Enter description here... 3281 | * 3282 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3283 | */ 3284 | public function prepend($content) 3285 | { 3286 | return $this->insert($content, __FUNCTION__); 3287 | } 3288 | /** 3289 | * Enter description here... 3290 | * 3291 | * @todo accept many arguments, which are joined, arrays maybe also 3292 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3293 | */ 3294 | public function prependPHP($content) 3295 | { 3296 | return $this->insert("", 'prepend'); 3297 | } 3298 | /** 3299 | * Enter description here... 3300 | * 3301 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3302 | */ 3303 | public function prependTo($seletor) 3304 | { 3305 | return $this->insert($seletor, __FUNCTION__); 3306 | } 3307 | /** 3308 | * Enter description here... 3309 | * 3310 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3311 | */ 3312 | public function before($content) 3313 | { 3314 | return $this->insert($content, __FUNCTION__); 3315 | } 3316 | /** 3317 | * Enter description here... 3318 | * 3319 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3320 | */ 3321 | public function beforePHP($content) 3322 | { 3323 | return $this->insert("", 'before'); 3324 | } 3325 | /** 3326 | * Enter description here... 3327 | * 3328 | * @param String|phpQuery 3329 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3330 | */ 3331 | public function insertBefore($seletor) 3332 | { 3333 | return $this->insert($seletor, __FUNCTION__); 3334 | } 3335 | /** 3336 | * Enter description here... 3337 | * 3338 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3339 | */ 3340 | public function after($content) 3341 | { 3342 | return $this->insert($content, __FUNCTION__); 3343 | } 3344 | /** 3345 | * Enter description here... 3346 | * 3347 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3348 | */ 3349 | public function afterPHP($content) 3350 | { 3351 | return $this->insert("", 'after'); 3352 | } 3353 | /** 3354 | * Enter description here... 3355 | * 3356 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3357 | */ 3358 | public function insertAfter($seletor) 3359 | { 3360 | return $this->insert($seletor, __FUNCTION__); 3361 | } 3362 | /** 3363 | * Internal insert method. Don't use it. 3364 | * 3365 | * @param unknown_type $target 3366 | * @param unknown_type $type 3367 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3368 | * @access private 3369 | */ 3370 | public function insert($target, $type) 3371 | { 3372 | $this->debug("Inserting data with '{$type}'"); 3373 | $to = false; 3374 | switch ($type) { 3375 | case 'appendTo': 3376 | case 'prependTo': 3377 | case 'insertBefore': 3378 | case 'insertAfter': 3379 | $to = true; 3380 | } 3381 | switch (gettype($target)) { 3382 | case 'string': 3383 | $insertFrom = $insertTo = array(); 3384 | if ($to) { 3385 | // INSERT TO 3386 | $insertFrom = $this->elements; 3387 | if (phpQuery::isMarkup($target)) { 3388 | // $target is new markup, import it 3389 | $insertTo = $this->documentWrapper->import($target); 3390 | // insert into selected element 3391 | } else { 3392 | // $tagret is a selector 3393 | $thisStack = $this->elements; 3394 | $this->toRoot(); 3395 | $insertTo = $this->find($target)->elements; 3396 | $this->elements = $thisStack; 3397 | } 3398 | } else { 3399 | // INSERT FROM 3400 | $insertTo = $this->elements; 3401 | $insertFrom = $this->documentWrapper->import($target); 3402 | } 3403 | break; 3404 | case 'object': 3405 | $insertFrom = $insertTo = array(); 3406 | // phpQuery 3407 | if ($target instanceof self) { 3408 | if ($to) { 3409 | $insertTo = $target->elements; 3410 | if ($this->documentFragment && $this->stackIsRoot()) 3411 | // get all body children 3412 | // $loop = $this->find('body > *')->elements; 3413 | // TODO test it, test it hard... 3414 | // $loop = $this->newInstance($this->root)->find('> *')->elements; 3415 | $loop = $this->root->childNodes; 3416 | else 3417 | $loop = $this->elements; 3418 | // import nodes if needed 3419 | $insertFrom = $this->getDocumentID() == $target->getDocumentID() 3420 | ? $loop 3421 | : $target->documentWrapper->import($loop); 3422 | } else { 3423 | $insertTo = $this->elements; 3424 | if ($target->documentFragment && $target->stackIsRoot()) 3425 | // get all body children 3426 | // $loop = $target->find('body > *')->elements; 3427 | $loop = $target->root->childNodes; 3428 | else 3429 | $loop = $target->elements; 3430 | // import nodes if needed 3431 | $insertFrom = $this->getDocumentID() == $target->getDocumentID() 3432 | ? $loop 3433 | : $this->documentWrapper->import($loop); 3434 | } 3435 | // DOMNODE 3436 | } elseif ($target instanceof DOMNODE) { 3437 | // import node if needed 3438 | // if ( $target->ownerDocument != $this->DOM ) 3439 | // $target = $this->DOM->importNode($target, true); 3440 | if ($to) { 3441 | $insertTo = array($target); 3442 | if ($this->documentFragment && $this->stackIsRoot()) 3443 | // get all body children 3444 | $loop = $this->root->childNodes; 3445 | // $loop = $this->find('body > *')->elements; 3446 | else 3447 | $loop = $this->elements; 3448 | foreach ($loop as $fromNode) 3449 | // import nodes if needed 3450 | $insertFrom[] = !$fromNode->ownerDocument->isSameNode($target->ownerDocument) 3451 | ? $target->ownerDocument->importNode($fromNode, true) 3452 | : $fromNode; 3453 | } else { 3454 | // import node if needed 3455 | if (!$target->ownerDocument->isSameNode($this->document)) 3456 | $target = $this->document->importNode($target, true); 3457 | $insertTo = $this->elements; 3458 | $insertFrom[] = $target; 3459 | } 3460 | } 3461 | break; 3462 | } 3463 | phpQuery::debug("From " . count($insertFrom) . "; To " . count($insertTo) . " nodes"); 3464 | foreach ($insertTo as $insertNumber => $toNode) { 3465 | // we need static relative elements in some cases 3466 | switch ($type) { 3467 | case 'prependTo': 3468 | case 'prepend': 3469 | $firstChild = $toNode->firstChild; 3470 | break; 3471 | case 'insertAfter': 3472 | case 'after': 3473 | $nextSibling = $toNode->nextSibling; 3474 | break; 3475 | } 3476 | foreach ($insertFrom as $fromNode) { 3477 | // clone if inserted already before 3478 | $insert = $insertNumber 3479 | ? $fromNode->cloneNode(true) 3480 | : $fromNode; 3481 | switch ($type) { 3482 | case 'appendTo': 3483 | case 'append': 3484 | // $toNode->insertBefore( 3485 | // $fromNode, 3486 | // $toNode->lastChild->nextSibling 3487 | // ); 3488 | $toNode->appendChild($insert); 3489 | $eventTarget = $insert; 3490 | break; 3491 | case 'prependTo': 3492 | case 'prepend': 3493 | $toNode->insertBefore( 3494 | $insert, 3495 | $firstChild 3496 | ); 3497 | break; 3498 | case 'insertBefore': 3499 | case 'before': 3500 | if (!$toNode->parentNode) 3501 | throw new Exception("No parentNode, can't do {$type}()"); 3502 | else 3503 | $toNode->parentNode->insertBefore( 3504 | $insert, 3505 | $toNode 3506 | ); 3507 | break; 3508 | case 'insertAfter': 3509 | case 'after': 3510 | if (!$toNode->parentNode) 3511 | throw new Exception("No parentNode, can't do {$type}()"); 3512 | else 3513 | $toNode->parentNode->insertBefore( 3514 | $insert, 3515 | $nextSibling 3516 | ); 3517 | break; 3518 | } 3519 | // Mutation event 3520 | $event = new DOMEvent(array( 3521 | 'target' => $insert, 3522 | 'type' => 'DOMNodeInserted' 3523 | )); 3524 | phpQueryEvents::trigger( 3525 | $this->getDocumentID(), 3526 | $event->type, 3527 | array($event), 3528 | $insert 3529 | ); 3530 | } 3531 | } 3532 | return $this; 3533 | } 3534 | /** 3535 | * Enter description here... 3536 | * 3537 | * @return Int 3538 | */ 3539 | public function index($subject) 3540 | { 3541 | $index = -1; 3542 | $subject = $subject instanceof phpQueryObject 3543 | ? $subject->elements[0] 3544 | : $subject; 3545 | foreach ($this->newInstance() as $k => $node) { 3546 | if ($node->isSameNode($subject)) 3547 | $index = $k; 3548 | } 3549 | return $index; 3550 | } 3551 | /** 3552 | * Enter description here... 3553 | * 3554 | * @param unknown_type $start 3555 | * @param unknown_type $end 3556 | * 3557 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3558 | * @testme 3559 | */ 3560 | public function slice($start, $end = null) 3561 | { 3562 | // $last = count($this->elements)-1; 3563 | // $end = $end 3564 | // ? min($end, $last) 3565 | // : $last; 3566 | // if ($start < 0) 3567 | // $start = $last+$start; 3568 | // if ($start > $last) 3569 | // return array(); 3570 | if ($end > 0) 3571 | $end = $end - $start; 3572 | return $this->newInstance( 3573 | array_slice($this->elements, $start, $end) 3574 | ); 3575 | } 3576 | /** 3577 | * Enter description here... 3578 | * 3579 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3580 | */ 3581 | public function reverse() 3582 | { 3583 | $this->elementsBackup = $this->elements; 3584 | $this->elements = array_reverse($this->elements); 3585 | return $this->newInstance(); 3586 | } 3587 | /** 3588 | * Return joined text content. 3589 | * @return String 3590 | */ 3591 | public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null) 3592 | { 3593 | if (isset($text)) 3594 | return $this->html(htmlspecialchars($text)); 3595 | $args = func_get_args(); 3596 | $args = array_slice($args, 1); 3597 | $return = ''; 3598 | foreach ($this->elements as $node) { 3599 | $text = $node->textContent; 3600 | if (count($this->elements) > 1 && $text) 3601 | $text .= "\n"; 3602 | foreach ($args as $callback) { 3603 | $text = phpQuery::callbackRun($callback, array($text)); 3604 | } 3605 | $return .= $text; 3606 | } 3607 | return $return; 3608 | } 3609 | /** 3610 | * @return The text content of each matching element, like 3611 | * text() but returns an array with one entry per matched element. 3612 | * Read only. 3613 | */ 3614 | public function texts($attr = null) 3615 | { 3616 | $results = array(); 3617 | foreach ($this->elements as $node) { 3618 | $results[] = $node->textContent; 3619 | } 3620 | return $results; 3621 | } 3622 | /** 3623 | * Enter description here... 3624 | * 3625 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3626 | */ 3627 | public function plugin($class, $file = null) 3628 | { 3629 | phpQuery::plugin($class, $file); 3630 | return $this; 3631 | } 3632 | /** 3633 | * Deprecated, use $pq->plugin() instead. 3634 | * 3635 | * @deprecated 3636 | * @param $class 3637 | * @param $file 3638 | * @return unknown_type 3639 | */ 3640 | public static function extend($class, $file = null) 3641 | { 3642 | return $this->plugin($class, $file); 3643 | } 3644 | /** 3645 | * 3646 | * @access private 3647 | * @param $method 3648 | * @param $args 3649 | * @return unknown_type 3650 | */ 3651 | public function __call($method, $args) 3652 | { 3653 | $aliasMethods = array('clone', 'empty'); 3654 | if (isset(phpQuery::$extendMethods[$method])) { 3655 | array_unshift($args, $this); 3656 | return phpQuery::callbackRun( 3657 | phpQuery::$extendMethods[$method], 3658 | $args 3659 | ); 3660 | } else if (isset(phpQuery::$pluginsMethods[$method])) { 3661 | array_unshift($args, $this); 3662 | $class = phpQuery::$pluginsMethods[$method]; 3663 | $realClass = "phpQueryObjectPlugin_$class"; 3664 | $return = call_user_func_array( 3665 | array($realClass, $method), 3666 | $args 3667 | ); 3668 | // XXX deprecate ? 3669 | return is_null($return) 3670 | ? $this 3671 | : $return; 3672 | } else if (in_array($method, $aliasMethods)) { 3673 | return call_user_func_array(array($this, '_' . $method), $args); 3674 | } else 3675 | throw new Exception("Method '{$method}' doesnt exist"); 3676 | } 3677 | /** 3678 | * Safe rename of next(). 3679 | * 3680 | * Use it ONLY when need to call next() on an iterated object (in same time). 3681 | * Normaly there is no need to do such thing ;) 3682 | * 3683 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3684 | * @access private 3685 | */ 3686 | public function _next($selector = null) 3687 | { 3688 | return $this->newInstance( 3689 | $this->getElementSiblings('nextSibling', $selector, true) 3690 | ); 3691 | } 3692 | /** 3693 | * Use prev() and next(). 3694 | * 3695 | * @deprecated 3696 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3697 | * @access private 3698 | */ 3699 | public function _prev($selector = null) 3700 | { 3701 | return $this->prev($selector); 3702 | } 3703 | /** 3704 | * Enter description here... 3705 | * 3706 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3707 | */ 3708 | public function prev($selector = null) 3709 | { 3710 | return $this->newInstance( 3711 | $this->getElementSiblings('previousSibling', $selector, true) 3712 | ); 3713 | } 3714 | /** 3715 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3716 | * @todo 3717 | */ 3718 | public function prevAll($selector = null) 3719 | { 3720 | return $this->newInstance( 3721 | $this->getElementSiblings('previousSibling', $selector) 3722 | ); 3723 | } 3724 | /** 3725 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3726 | * @todo FIXME: returns source elements insted of next siblings 3727 | */ 3728 | public function nextAll($selector = null) 3729 | { 3730 | return $this->newInstance( 3731 | $this->getElementSiblings('nextSibling', $selector) 3732 | ); 3733 | } 3734 | /** 3735 | * @access private 3736 | */ 3737 | protected function getElementSiblings($direction, $selector = null, $limitToOne = false) 3738 | { 3739 | $stack = array(); 3740 | $count = 0; 3741 | foreach ($this->stack() as $node) { 3742 | $test = $node; 3743 | while (isset($test->{$direction}) && $test->{$direction}) { 3744 | $test = $test->{$direction}; 3745 | if (!$test instanceof DOMELEMENT) 3746 | continue; 3747 | $stack[] = $test; 3748 | if ($limitToOne) 3749 | break; 3750 | } 3751 | } 3752 | if ($selector) { 3753 | $stackOld = $this->elements; 3754 | $this->elements = $stack; 3755 | $stack = $this->filter($selector, true)->stack(); 3756 | $this->elements = $stackOld; 3757 | } 3758 | return $stack; 3759 | } 3760 | /** 3761 | * Enter description here... 3762 | * 3763 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3764 | */ 3765 | public function siblings($selector = null) 3766 | { 3767 | $stack = array(); 3768 | $siblings = array_merge( 3769 | $this->getElementSiblings('previousSibling', $selector), 3770 | $this->getElementSiblings('nextSibling', $selector) 3771 | ); 3772 | foreach ($siblings as $node) { 3773 | if (!$this->elementsContainsNode($node, $stack)) 3774 | $stack[] = $node; 3775 | } 3776 | return $this->newInstance($stack); 3777 | } 3778 | /** 3779 | * Enter description here... 3780 | * 3781 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3782 | */ 3783 | public function not($selector = null) 3784 | { 3785 | if (is_string($selector)) 3786 | phpQuery::debug(array('not', $selector)); 3787 | else 3788 | phpQuery::debug('not'); 3789 | $stack = array(); 3790 | if ($selector instanceof self || $selector instanceof DOMNODE) { 3791 | foreach ($this->stack() as $node) { 3792 | if ($selector instanceof self) { 3793 | $matchFound = false; 3794 | foreach ($selector->stack() as $notNode) { 3795 | if ($notNode->isSameNode($node)) 3796 | $matchFound = true; 3797 | } 3798 | if (!$matchFound) 3799 | $stack[] = $node; 3800 | } else if ($selector instanceof DOMNODE) { 3801 | if (!$selector->isSameNode($node)) 3802 | $stack[] = $node; 3803 | } else { 3804 | if (!$this->is($selector)) 3805 | $stack[] = $node; 3806 | } 3807 | } 3808 | } else { 3809 | $orgStack = $this->stack(); 3810 | $matched = $this->filter($selector, true)->stack(); 3811 | // $matched = array(); 3812 | // // simulate OR in filter() instead of AND 5y 3813 | // foreach($this->parseSelector($selector) as $s) { 3814 | // $matched = array_merge($matched, 3815 | // $this->filter(array($s))->stack() 3816 | // ); 3817 | // } 3818 | foreach ($orgStack as $node) 3819 | if (!$this->elementsContainsNode($node, $matched)) 3820 | $stack[] = $node; 3821 | } 3822 | return $this->newInstance($stack); 3823 | } 3824 | /** 3825 | * Enter description here... 3826 | * 3827 | * @param string|phpQueryObject 3828 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3829 | */ 3830 | public function add($selector = null) 3831 | { 3832 | if (!$selector) 3833 | return $this; 3834 | $stack = array(); 3835 | $this->elementsBackup = $this->elements; 3836 | $found = phpQuery::pq($selector, $this->getDocumentID()); 3837 | $this->merge($found->elements); 3838 | return $this->newInstance(); 3839 | } 3840 | /** 3841 | * @access private 3842 | */ 3843 | protected function merge() 3844 | { 3845 | foreach (func_get_args() as $nodes) 3846 | foreach ($nodes as $newNode) 3847 | if (!$this->elementsContainsNode($newNode)) 3848 | $this->elements[] = $newNode; 3849 | } 3850 | /** 3851 | * @access private 3852 | * TODO refactor to stackContainsNode 3853 | */ 3854 | protected function elementsContainsNode($nodeToCheck, $elementsStack = null) 3855 | { 3856 | $loop = !is_null($elementsStack) 3857 | ? $elementsStack 3858 | : $this->elements; 3859 | foreach ($loop as $node) { 3860 | if ($node->isSameNode($nodeToCheck)) 3861 | return true; 3862 | } 3863 | return false; 3864 | } 3865 | /** 3866 | * Enter description here... 3867 | * 3868 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3869 | */ 3870 | public function parent($selector = null) 3871 | { 3872 | $stack = array(); 3873 | foreach ($this->elements as $node) 3874 | if ($node->parentNode && !$this->elementsContainsNode($node->parentNode, $stack)) 3875 | $stack[] = $node->parentNode; 3876 | $this->elementsBackup = $this->elements; 3877 | $this->elements = $stack; 3878 | if ($selector) 3879 | $this->filter($selector, true); 3880 | return $this->newInstance(); 3881 | } 3882 | /** 3883 | * Enter description here... 3884 | * 3885 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 3886 | */ 3887 | public function parents($selector = null) 3888 | { 3889 | $stack = array(); 3890 | if (!$this->elements) 3891 | $this->debug('parents() - stack empty'); 3892 | foreach ($this->elements as $node) { 3893 | $test = $node; 3894 | while ($test->parentNode) { 3895 | $test = $test->parentNode; 3896 | if ($this->isRoot($test)) 3897 | break; 3898 | if (!$this->elementsContainsNode($test, $stack)) { 3899 | $stack[] = $test; 3900 | continue; 3901 | } 3902 | } 3903 | } 3904 | $this->elementsBackup = $this->elements; 3905 | $this->elements = $stack; 3906 | if ($selector) 3907 | $this->filter($selector, true); 3908 | return $this->newInstance(); 3909 | } 3910 | /** 3911 | * Internal stack iterator. 3912 | * 3913 | * @access private 3914 | */ 3915 | public function stack($nodeTypes = null) 3916 | { 3917 | if (!isset($nodeTypes)) 3918 | return $this->elements; 3919 | if (!is_array($nodeTypes)) 3920 | $nodeTypes = array($nodeTypes); 3921 | $return = array(); 3922 | foreach ($this->elements as $node) { 3923 | if (in_array($node->nodeType, $nodeTypes)) 3924 | $return[] = $node; 3925 | } 3926 | return $return; 3927 | } 3928 | // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes 3929 | protected function attrEvents($attr, $oldAttr, $oldValue, $node) 3930 | { 3931 | // skip events for XML documents 3932 | if (!$this->isXHTML() && !$this->isHTML()) 3933 | return; 3934 | $event = null; 3935 | // identify 3936 | $isInputValue = $node->tagName == 'input' 3937 | && (in_array( 3938 | $node->getAttribute('type'), 3939 | array('text', 'password', 'hidden') 3940 | ) 3941 | || !$node->getAttribute('type') 3942 | ); 3943 | $isRadio = $node->tagName == 'input' 3944 | && $node->getAttribute('type') == 'radio'; 3945 | $isCheckbox = $node->tagName == 'input' 3946 | && $node->getAttribute('type') == 'checkbox'; 3947 | $isOption = $node->tagName == 'option'; 3948 | if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) { 3949 | $event = new DOMEvent(array( 3950 | 'target' => $node, 3951 | 'type' => 'change' 3952 | )); 3953 | } else if (($isRadio || $isCheckbox) && $attr == 'checked' && ( 3954 | // check 3955 | (!$oldAttr && $node->hasAttribute($attr)) 3956 | // un-check 3957 | || (!$node->hasAttribute($attr) && $oldAttr) 3958 | )) { 3959 | $event = new DOMEvent(array( 3960 | 'target' => $node, 3961 | 'type' => 'change' 3962 | )); 3963 | } else if ($isOption && $node->parentNode && $attr == 'selected' && ( 3964 | // select 3965 | (!$oldAttr && $node->hasAttribute($attr)) 3966 | // un-select 3967 | || (!$node->hasAttribute($attr) && $oldAttr) 3968 | )) { 3969 | $event = new DOMEvent(array( 3970 | 'target' => $node->parentNode, 3971 | 'type' => 'change' 3972 | )); 3973 | } 3974 | if ($event) { 3975 | phpQueryEvents::trigger( 3976 | $this->getDocumentID(), 3977 | $event->type, 3978 | array($event), 3979 | $node 3980 | ); 3981 | } 3982 | } 3983 | public function attr($attr = null, $value = null) 3984 | { 3985 | foreach ($this->stack(1) as $node) { 3986 | if (!is_null($value)) { 3987 | $loop = $attr == '*' 3988 | ? $this->getNodeAttrs($node) 3989 | : array($attr); 3990 | foreach ($loop as $a) { 3991 | $oldValue = $node->getAttribute($a); 3992 | $oldAttr = $node->hasAttribute($a); 3993 | // TODO raises an error when charset other than UTF-8 3994 | // while document's charset is also not UTF-8 3995 | @$node->setAttribute($a, $value); 3996 | $this->attrEvents($a, $oldAttr, $oldValue, $node); 3997 | } 3998 | } else if ($attr == '*') { 3999 | // jQuery difference 4000 | $return = array(); 4001 | foreach ($node->attributes as $n => $v) 4002 | $return[$n] = $v->value; 4003 | return $return; 4004 | } else 4005 | return $node->hasAttribute($attr) 4006 | ? $node->getAttribute($attr) 4007 | : null; 4008 | } 4009 | return is_null($value) 4010 | ? '' : $this; 4011 | } 4012 | /** 4013 | * @return The same attribute of each matching element, like 4014 | * attr() but returns an array with one entry per matched element. 4015 | * Read only. 4016 | */ 4017 | public function attrs($attr = null) 4018 | { 4019 | $results = array(); 4020 | foreach ($this->stack(1) as $node) { 4021 | $results[] = $node->hasAttribute($attr) 4022 | ? $node->getAttribute($attr) 4023 | : null; 4024 | } 4025 | return $results; 4026 | } 4027 | /** 4028 | * @access private 4029 | */ 4030 | protected function getNodeAttrs($node) 4031 | { 4032 | $return = array(); 4033 | foreach ($node->attributes as $n => $o) 4034 | $return[] = $n; 4035 | return $return; 4036 | } 4037 | /** 4038 | * Enter description here... 4039 | * 4040 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4041 | * @todo check CDATA ??? 4042 | */ 4043 | public function attrPHP($attr, $code) 4044 | { 4045 | if (!is_null($code)) { 4046 | $value = '<' . '?php ' . $code . ' ?' . '>'; 4047 | // TODO tempolary solution 4048 | // http://code.google.com/p/phpquery/issues/detail?id=17 4049 | // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII') 4050 | // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES'); 4051 | } 4052 | foreach ($this->stack(1) as $node) { 4053 | if (!is_null($code)) { 4054 | // $attrNode = $this->DOM->createAttribute($attr); 4055 | $node->setAttribute($attr, $value); 4056 | // $attrNode->value = $value; 4057 | // $node->appendChild($attrNode); 4058 | } else if ($attr == '*') { 4059 | // jQuery diff 4060 | $return = array(); 4061 | foreach ($node->attributes as $n => $v) 4062 | $return[$n] = $v->value; 4063 | return $return; 4064 | } else 4065 | return $node->getAttribute($attr); 4066 | } 4067 | return $this; 4068 | } 4069 | /** 4070 | * Enter description here... 4071 | * 4072 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4073 | */ 4074 | public function removeAttr($attr) 4075 | { 4076 | foreach ($this->stack(1) as $node) { 4077 | $loop = $attr == '*' 4078 | ? $this->getNodeAttrs($node) 4079 | : array($attr); 4080 | foreach ($loop as $a) { 4081 | $oldValue = $node->getAttribute($a); 4082 | $node->removeAttribute($a); 4083 | $this->attrEvents($a, $oldValue, null, $node); 4084 | } 4085 | } 4086 | return $this; 4087 | } 4088 | /** 4089 | * Return form element value. 4090 | * 4091 | * @return String Fields value. 4092 | */ 4093 | public function val($val = null) 4094 | { 4095 | if (!isset($val)) { 4096 | if ($this->eq(0)->is('select')) { 4097 | $selected = $this->eq(0)->find('option[selected=selected]'); 4098 | if ($selected->is('[value]')) 4099 | return $selected->attr('value'); 4100 | else 4101 | return $selected->text(); 4102 | } else if ($this->eq(0)->is('textarea')) 4103 | return $this->eq(0)->markup(); 4104 | else 4105 | return $this->eq(0)->attr('value'); 4106 | } else { 4107 | $_val = null; 4108 | foreach ($this->stack(1) as $node) { 4109 | $node = pq($node, $this->getDocumentID()); 4110 | if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) { 4111 | $isChecked = in_array($node->attr('value'), $val) 4112 | || in_array($node->attr('name'), $val); 4113 | if ($isChecked) 4114 | $node->attr('checked', 'checked'); 4115 | else 4116 | $node->removeAttr('checked'); 4117 | } else if ($node->get(0)->tagName == 'select') { 4118 | if (!isset($_val)) { 4119 | $_val = array(); 4120 | if (!is_array($val)) 4121 | $_val = array((string)$val); 4122 | else 4123 | foreach ($val as $v) 4124 | $_val[] = $v; 4125 | } 4126 | foreach ($node['option']->stack(1) as $option) { 4127 | $option = pq($option, $this->getDocumentID()); 4128 | $selected = false; 4129 | // XXX: workaround for string comparsion, see issue #96 4130 | // http://code.google.com/p/phpquery/issues/detail?id=96 4131 | $selected = is_null($option->attr('value')) 4132 | ? in_array($option->markup(), $_val) 4133 | : in_array($option->attr('value'), $_val); 4134 | // $optionValue = $option->attr('value'); 4135 | // $optionText = $option->text(); 4136 | // $optionTextLenght = mb_strlen($optionText); 4137 | // foreach($_val as $v) 4138 | // if ($optionValue == $v) 4139 | // $selected = true; 4140 | // else if ($optionText == $v && $optionTextLenght == mb_strlen($v)) 4141 | // $selected = true; 4142 | if ($selected) 4143 | $option->attr('selected', 'selected'); 4144 | else 4145 | $option->removeAttr('selected'); 4146 | } 4147 | } else if ($node->get(0)->tagName == 'textarea') 4148 | $node->markup($val); 4149 | else 4150 | $node->attr('value', $val); 4151 | } 4152 | } 4153 | return $this; 4154 | } 4155 | /** 4156 | * Enter description here... 4157 | * 4158 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4159 | */ 4160 | public function andSelf() 4161 | { 4162 | if ($this->previous) 4163 | $this->elements = array_merge($this->elements, $this->previous->elements); 4164 | return $this; 4165 | } 4166 | /** 4167 | * Enter description here... 4168 | * 4169 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4170 | */ 4171 | public function addClass($className) 4172 | { 4173 | if (!$className) 4174 | return $this; 4175 | foreach ($this->stack(1) as $node) { 4176 | if (!$this->is(".$className", $node)) 4177 | $node->setAttribute( 4178 | 'class', 4179 | trim($node->getAttribute('class') . ' ' . $className) 4180 | ); 4181 | } 4182 | return $this; 4183 | } 4184 | /** 4185 | * Enter description here... 4186 | * 4187 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4188 | */ 4189 | public function addClassPHP($className) 4190 | { 4191 | foreach ($this->stack(1) as $node) { 4192 | $classes = $node->getAttribute('class'); 4193 | $newValue = $classes 4194 | ? $classes . ' <' . '?php ' . $className . ' ?' . '>' 4195 | : '<' . '?php ' . $className . ' ?' . '>'; 4196 | $node->setAttribute('class', $newValue); 4197 | } 4198 | return $this; 4199 | } 4200 | /** 4201 | * Enter description here... 4202 | * 4203 | * @param string $className 4204 | * @return bool 4205 | */ 4206 | public function hasClass($className) 4207 | { 4208 | foreach ($this->stack(1) as $node) { 4209 | if ($this->is(".$className", $node)) 4210 | return true; 4211 | } 4212 | return false; 4213 | } 4214 | /** 4215 | * Enter description here... 4216 | * 4217 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4218 | */ 4219 | public function removeClass($className) 4220 | { 4221 | foreach ($this->stack(1) as $node) { 4222 | $classes = explode(' ', $node->getAttribute('class')); 4223 | if (in_array($className, $classes)) { 4224 | $classes = array_diff($classes, array($className)); 4225 | if ($classes) 4226 | $node->setAttribute('class', implode(' ', $classes)); 4227 | else 4228 | $node->removeAttribute('class'); 4229 | } 4230 | } 4231 | return $this; 4232 | } 4233 | /** 4234 | * Enter description here... 4235 | * 4236 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4237 | */ 4238 | public function toggleClass($className) 4239 | { 4240 | foreach ($this->stack(1) as $node) { 4241 | if ($this->is($node, '.' . $className)) 4242 | $this->removeClass($className); 4243 | else 4244 | $this->addClass($className); 4245 | } 4246 | return $this; 4247 | } 4248 | /** 4249 | * Proper name without underscore (just ->empty()) also works. 4250 | * 4251 | * Removes all child nodes from the set of matched elements. 4252 | * 4253 | * Example: 4254 | * pq("p")._empty() 4255 | * 4256 | * HTML: 4257 | *

Hello, Person and person

4258 | * 4259 | * Result: 4260 | * [

] 4261 | * 4262 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4263 | * @access private 4264 | */ 4265 | public function _empty() 4266 | { 4267 | foreach ($this->stack(1) as $node) { 4268 | // thx to 'dave at dgx dot cz' 4269 | $node->nodeValue = ''; 4270 | } 4271 | return $this; 4272 | } 4273 | /** 4274 | * Enter description here... 4275 | * 4276 | * @param array|string $callback Expects $node as first param, $index as second 4277 | * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope) 4278 | * @param array $arg1 Will ba passed as third and futher args to callback. 4279 | * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on... 4280 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4281 | */ 4282 | public function each($callback, $param1 = null, $param2 = null, $param3 = null) 4283 | { 4284 | $paramStructure = null; 4285 | if (func_num_args() > 1) { 4286 | $paramStructure = func_get_args(); 4287 | $paramStructure = array_slice($paramStructure, 1); 4288 | } 4289 | foreach ($this->elements as $v) 4290 | phpQuery::callbackRun($callback, array($v), $paramStructure); 4291 | return $this; 4292 | } 4293 | /** 4294 | * Run callback on actual object. 4295 | * 4296 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4297 | */ 4298 | public function callback($callback, $param1 = null, $param2 = null, $param3 = null) 4299 | { 4300 | $params = func_get_args(); 4301 | $params[0] = $this; 4302 | phpQuery::callbackRun($callback, $params); 4303 | return $this; 4304 | } 4305 | /** 4306 | * Enter description here... 4307 | * 4308 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4309 | * @todo add $scope and $args as in each() ??? 4310 | */ 4311 | public function map($callback, $param1 = null, $param2 = null, $param3 = null) 4312 | { 4313 | // $stack = array(); 4314 | //// foreach($this->newInstance() as $node) { 4315 | // foreach($this->newInstance() as $node) { 4316 | // $result = call_user_func($callback, $node); 4317 | // if ($result) 4318 | // $stack[] = $result; 4319 | // } 4320 | $params = func_get_args(); 4321 | array_unshift($params, $this->elements); 4322 | return $this->newInstance( 4323 | call_user_func_array(array('phpQuery', 'map'), $params) 4324 | // phpQuery::map($this->elements, $callback) 4325 | ); 4326 | } 4327 | /** 4328 | * Enter description here... 4329 | * 4330 | * @param $key 4331 | * @param $value 4332 | */ 4333 | public function data($key, $value = null) 4334 | { 4335 | if (!isset($value)) { 4336 | // TODO? implement specific jQuery behavior od returning parent values 4337 | // is child which we look up doesn't exist 4338 | return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID()); 4339 | } else { 4340 | foreach ($this as $node) 4341 | phpQuery::data($node, $key, $value, $this->getDocumentID()); 4342 | return $this; 4343 | } 4344 | } 4345 | /** 4346 | * Enter description here... 4347 | * 4348 | * @param $key 4349 | */ 4350 | public function removeData($key) 4351 | { 4352 | foreach ($this as $node) 4353 | phpQuery::removeData($node, $key, $this->getDocumentID()); 4354 | return $this; 4355 | } 4356 | // INTERFACE IMPLEMENTATIONS 4357 | 4358 | // ITERATOR INTERFACE 4359 | /** 4360 | * @access private 4361 | */ 4362 | #[\ReturnTypeWillChange] 4363 | public function rewind() 4364 | { 4365 | $this->debug('iterating foreach'); 4366 | // phpQuery::selectDocument($this->getDocumentID()); 4367 | $this->elementsBackup = $this->elements; 4368 | $this->elementsInterator = $this->elements; 4369 | $this->valid = isset($this->elements[0]) 4370 | ? 1 : 0; 4371 | // $this->elements = $this->valid 4372 | // ? array($this->elements[0]) 4373 | // : array(); 4374 | $this->current = 0; 4375 | } 4376 | /** 4377 | * @access private 4378 | */ 4379 | #[\ReturnTypeWillChange] 4380 | public function current() 4381 | { 4382 | return $this->elementsInterator[$this->current]; 4383 | } 4384 | /** 4385 | * @access private 4386 | */ 4387 | #[\ReturnTypeWillChange] 4388 | public function key() 4389 | { 4390 | return $this->current; 4391 | } 4392 | /** 4393 | * Double-function method. 4394 | * 4395 | * First: main iterator interface method. 4396 | * Second: Returning next sibling, alias for _next(). 4397 | * 4398 | * Proper functionality is choosed automagicaly. 4399 | * 4400 | * @see phpQueryObject::_next() 4401 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4402 | */ 4403 | #[\ReturnTypeWillChange] 4404 | public function next($cssSelector = null) 4405 | { 4406 | // if ($cssSelector || $this->valid) 4407 | // return $this->_next($cssSelector); 4408 | $this->valid = isset($this->elementsInterator[$this->current + 1]) 4409 | ? true 4410 | : false; 4411 | if (!$this->valid && $this->elementsInterator) { 4412 | $this->elementsInterator = null; 4413 | } else if ($this->valid) { 4414 | $this->current++; 4415 | } else { 4416 | return $this->_next($cssSelector); 4417 | } 4418 | } 4419 | /** 4420 | * @access private 4421 | */ 4422 | #[\ReturnTypeWillChange] 4423 | public function valid() 4424 | { 4425 | return $this->valid; 4426 | } 4427 | // ITERATOR INTERFACE END 4428 | // ARRAYACCESS INTERFACE 4429 | /** 4430 | * @access private 4431 | */ 4432 | #[\ReturnTypeWillChange] 4433 | public function offsetExists($offset) 4434 | { 4435 | return $this->find($offset)->size() > 0; 4436 | } 4437 | /** 4438 | * @access private 4439 | */ 4440 | #[\ReturnTypeWillChange] 4441 | public function offsetGet($offset) 4442 | { 4443 | return $this->find($offset); 4444 | } 4445 | /** 4446 | * @access private 4447 | */ 4448 | #[\ReturnTypeWillChange] 4449 | public function offsetSet($offset, $value) 4450 | { 4451 | // $this->find($offset)->replaceWith($value); 4452 | $this->find($offset)->html($value); 4453 | } 4454 | /** 4455 | * @access private 4456 | */ 4457 | #[\ReturnTypeWillChange] 4458 | public function offsetUnset($offset) 4459 | { 4460 | // empty 4461 | throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML."); 4462 | } 4463 | // ARRAYACCESS INTERFACE END 4464 | /** 4465 | * Returns node's XPath. 4466 | * 4467 | * @param unknown_type $oneNode 4468 | * @return string 4469 | * @TODO use native getNodePath is avaible 4470 | * @access private 4471 | */ 4472 | protected function getNodeXpath($oneNode = null, $namespace = null) 4473 | { 4474 | $return = array(); 4475 | $loop = $oneNode 4476 | ? array($oneNode) 4477 | : $this->elements; 4478 | // if ($namespace) 4479 | // $namespace .= ':'; 4480 | foreach ($loop as $node) { 4481 | if ($node instanceof DOMDOCUMENT) { 4482 | $return[] = ''; 4483 | continue; 4484 | } 4485 | $xpath = array(); 4486 | while (!($node instanceof DOMDOCUMENT)) { 4487 | $i = 1; 4488 | $sibling = $node; 4489 | while ($sibling->previousSibling) { 4490 | $sibling = $sibling->previousSibling; 4491 | $isElement = $sibling instanceof DOMELEMENT; 4492 | if ($isElement && $sibling->tagName == $node->tagName) 4493 | $i++; 4494 | } 4495 | $xpath[] = $this->isXML() 4496 | ? "*[local-name()='{$node->tagName}'][{$i}]" 4497 | : "{$node->tagName}[{$i}]"; 4498 | $node = $node->parentNode; 4499 | } 4500 | $xpath = implode('/', array_reverse($xpath)); 4501 | $return[] = '/' . $xpath; 4502 | } 4503 | return $oneNode 4504 | ? $return[0] 4505 | : $return; 4506 | } 4507 | // HELPERS 4508 | public function whois($oneNode = null) 4509 | { 4510 | $return = array(); 4511 | $loop = $oneNode 4512 | ? array($oneNode) 4513 | : $this->elements; 4514 | foreach ($loop as $node) { 4515 | if (isset($node->tagName)) { 4516 | $tag = in_array($node->tagName, array('php', 'js')) 4517 | ? strtoupper($node->tagName) 4518 | : $node->tagName; 4519 | $return[] = $tag 4520 | . ($node->getAttribute('id') 4521 | ? '#' . $node->getAttribute('id') : '') 4522 | . ($node->getAttribute('class') 4523 | ? '.' . implode('.', explode(' ', $node->getAttribute('class'))) : '') 4524 | . ($node->getAttribute('name') 4525 | ? '[name="' . $node->getAttribute('name') . '"]' : '') 4526 | . ($node->getAttribute('value') && strpos($node->getAttribute('value'), '<' . '?php') === false 4527 | ? '[value="' . substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15) . '"]' : '') 4528 | . ($node->getAttribute('value') && strpos($node->getAttribute('value'), '<' . '?php') !== false 4529 | ? '[value=PHP]' : '') 4530 | . ($node->getAttribute('selected') 4531 | ? '[selected]' : '') 4532 | . ($node->getAttribute('checked') 4533 | ? '[checked]' : ''); 4534 | } else if ($node instanceof DOMTEXT) { 4535 | if (trim($node->textContent)) 4536 | $return[] = 'Text:' . substr(str_replace("\n", ' ', $node->textContent), 0, 15); 4537 | } else { 4538 | } 4539 | } 4540 | return $oneNode && isset($return[0]) 4541 | ? $return[0] 4542 | : $return; 4543 | } 4544 | /** 4545 | * Dump htmlOuter and preserve chain. Usefull for debugging. 4546 | * 4547 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4548 | * 4549 | */ 4550 | public function dump() 4551 | { 4552 | print 'DUMP #' . (phpQuery::$dumpCount++) . ' '; 4553 | $debug = phpQuery::$debug; 4554 | phpQuery::$debug = false; 4555 | // print __FILE__.':'.__LINE__."\n"; 4556 | var_dump($this->htmlOuter()); 4557 | return $this; 4558 | } 4559 | public function dumpWhois() 4560 | { 4561 | print 'DUMP #' . (phpQuery::$dumpCount++) . ' '; 4562 | $debug = phpQuery::$debug; 4563 | phpQuery::$debug = false; 4564 | // print __FILE__.':'.__LINE__."\n"; 4565 | var_dump('whois', $this->whois()); 4566 | phpQuery::$debug = $debug; 4567 | return $this; 4568 | } 4569 | public function dumpLength() 4570 | { 4571 | print 'DUMP #' . (phpQuery::$dumpCount++) . ' '; 4572 | $debug = phpQuery::$debug; 4573 | phpQuery::$debug = false; 4574 | // print __FILE__.':'.__LINE__."\n"; 4575 | var_dump('length', $this->length()); 4576 | phpQuery::$debug = $debug; 4577 | return $this; 4578 | } 4579 | public function dumpTree($html = true, $title = true) 4580 | { 4581 | $output = $title 4582 | ? 'DUMP #' . (phpQuery::$dumpCount++) . " \n" : ''; 4583 | $debug = phpQuery::$debug; 4584 | phpQuery::$debug = false; 4585 | foreach ($this->stack() as $node) 4586 | $output .= $this->__dumpTree($node); 4587 | phpQuery::$debug = $debug; 4588 | print $html 4589 | ? nl2br(str_replace(' ', ' ', $output)) 4590 | : $output; 4591 | return $this; 4592 | } 4593 | private function __dumpTree($node, $intend = 0) 4594 | { 4595 | $whois = $this->whois($node); 4596 | $return = ''; 4597 | if ($whois) 4598 | $return .= str_repeat(' - ', $intend) . $whois . "\n"; 4599 | if (isset($node->childNodes)) 4600 | foreach ($node->childNodes as $chNode) 4601 | $return .= $this->__dumpTree($chNode, $intend + 1); 4602 | return $return; 4603 | } 4604 | /** 4605 | * Dump htmlOuter and stop script execution. Usefull for debugging. 4606 | * 4607 | */ 4608 | public function dumpDie() 4609 | { 4610 | print __FILE__ . ':' . __LINE__; 4611 | var_dump($this->htmlOuter()); 4612 | die(); 4613 | } 4614 | } 4615 | 4616 | 4617 | // -- Multibyte Compatibility functions --------------------------------------- 4618 | // http://svn.iphonewebdev.com/lace/lib/mb_compat.php 4619 | 4620 | /** 4621 | * mb_internal_encoding() 4622 | * 4623 | * Included for mbstring pseudo-compatability. 4624 | */ 4625 | if (!function_exists('mb_internal_encoding')) { 4626 | function mb_internal_encoding($enc) 4627 | { 4628 | return true; 4629 | } 4630 | } 4631 | 4632 | /** 4633 | * mb_regex_encoding() 4634 | * 4635 | * Included for mbstring pseudo-compatability. 4636 | */ 4637 | if (!function_exists('mb_regex_encoding')) { 4638 | function mb_regex_encoding($enc) 4639 | { 4640 | return true; 4641 | } 4642 | } 4643 | 4644 | /** 4645 | * mb_strlen() 4646 | * 4647 | * Included for mbstring pseudo-compatability. 4648 | */ 4649 | if (!function_exists('mb_strlen')) { 4650 | function mb_strlen($str) 4651 | { 4652 | return strlen($str); 4653 | } 4654 | } 4655 | 4656 | /** 4657 | * mb_strpos() 4658 | * 4659 | * Included for mbstring pseudo-compatability. 4660 | */ 4661 | if (!function_exists('mb_strpos')) { 4662 | function mb_strpos($haystack, $needle, $offset = 0) 4663 | { 4664 | return strpos($haystack, $needle, $offset); 4665 | } 4666 | } 4667 | /** 4668 | * mb_stripos() 4669 | * 4670 | * Included for mbstring pseudo-compatability. 4671 | */ 4672 | if (!function_exists('mb_stripos')) { 4673 | function mb_stripos($haystack, $needle, $offset = 0) 4674 | { 4675 | return stripos($haystack, $needle, $offset); 4676 | } 4677 | } 4678 | 4679 | /** 4680 | * mb_substr() 4681 | * 4682 | * Included for mbstring pseudo-compatability. 4683 | */ 4684 | if (!function_exists('mb_substr')) { 4685 | function mb_substr($str, $start, $length = 0) 4686 | { 4687 | return substr($str, $start, $length); 4688 | } 4689 | } 4690 | 4691 | /** 4692 | * mb_substr_count() 4693 | * 4694 | * Included for mbstring pseudo-compatability. 4695 | */ 4696 | if (!function_exists('mb_substr_count')) { 4697 | function mb_substr_count($haystack, $needle) 4698 | { 4699 | return substr_count($haystack, $needle); 4700 | } 4701 | } 4702 | 4703 | 4704 | /** 4705 | * Static namespace for phpQuery functions. 4706 | * 4707 | * @author Tobiasz Cudnik 4708 | * @package phpQuery 4709 | */ 4710 | abstract class phpQuery 4711 | { 4712 | /** 4713 | * XXX: Workaround for mbstring problems 4714 | * 4715 | * @var bool 4716 | */ 4717 | public static $mbstringSupport = true; 4718 | public static $debug = false; 4719 | public static $documents = array(); 4720 | public static $defaultDocumentID = null; 4721 | // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'; 4722 | /** 4723 | * Applies only to HTML. 4724 | * 4725 | * @var unknown_type 4726 | */ 4727 | public static $defaultDoctype = ''; 4729 | public static $defaultCharset = 'UTF-8'; 4730 | /** 4731 | * Static namespace for plugins. 4732 | * 4733 | * @var object 4734 | */ 4735 | public static $plugins = array(); 4736 | /** 4737 | * List of loaded plugins. 4738 | * 4739 | * @var unknown_type 4740 | */ 4741 | public static $pluginsLoaded = array(); 4742 | public static $pluginsMethods = array(); 4743 | public static $pluginsStaticMethods = array(); 4744 | public static $extendMethods = array(); 4745 | /** 4746 | * @TODO implement 4747 | */ 4748 | public static $extendStaticMethods = array(); 4749 | /** 4750 | * Hosts allowed for AJAX connections. 4751 | * Dot '.' means $_SERVER['HTTP_HOST'] (if any). 4752 | * 4753 | * @var array 4754 | */ 4755 | public static $ajaxAllowedHosts = array( 4756 | '.' 4757 | ); 4758 | /** 4759 | * AJAX settings. 4760 | * 4761 | * @var array 4762 | * XXX should it be static or not ? 4763 | */ 4764 | public static $ajaxSettings = array( 4765 | 'url' => '', //TODO 4766 | 'global' => true, 4767 | 'type' => "GET", 4768 | 'timeout' => null, 4769 | 'contentType' => "application/x-www-form-urlencoded", 4770 | 'processData' => true, 4771 | // 'async' => true, 4772 | 'data' => null, 4773 | 'username' => null, 4774 | 'password' => null, 4775 | 'accepts' => array( 4776 | 'xml' => "application/xml, text/xml", 4777 | 'html' => "text/html", 4778 | 'script' => "text/javascript, application/javascript", 4779 | 'json' => "application/json, text/javascript", 4780 | 'text' => "text/plain", 4781 | '_default' => "*/*" 4782 | ) 4783 | ); 4784 | public static $lastModified = null; 4785 | public static $active = 0; 4786 | public static $dumpCount = 0; 4787 | /** 4788 | * Multi-purpose function. 4789 | * Use pq() as shortcut. 4790 | * 4791 | * In below examples, $pq is any result of pq(); function. 4792 | * 4793 | * 1. Import markup into existing document (without any attaching): 4794 | * - Import into selected document: 4795 | * pq('
') // DOESNT accept text nodes at beginning of input string ! 4796 | * - Import into document with ID from $pq->getDocumentID(): 4797 | * pq('
', $pq->getDocumentID()) 4798 | * - Import into same document as DOMNode belongs to: 4799 | * pq('
', DOMNode) 4800 | * - Import into document from phpQuery object: 4801 | * pq('
', $pq) 4802 | * 4803 | * 2. Run query: 4804 | * - Run query on last selected document: 4805 | * pq('div.myClass') 4806 | * - Run query on document with ID from $pq->getDocumentID(): 4807 | * pq('div.myClass', $pq->getDocumentID()) 4808 | * - Run query on same document as DOMNode belongs to and use node(s)as root for query: 4809 | * pq('div.myClass', DOMNode) 4810 | * - Run query on document from phpQuery object 4811 | * and use object's stack as root node(s) for query: 4812 | * pq('div.myClass', $pq) 4813 | * 4814 | * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes 4815 | * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root) 4816 | * 4817 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false 4818 | * phpQuery object or false in case of error. 4819 | */ 4820 | public static function pq($arg1, $context = null) 4821 | { 4822 | if ($arg1 instanceof DOMNODE && !isset($context)) { 4823 | foreach (phpQuery::$documents as $documentWrapper) { 4824 | $compare = $arg1 instanceof DOMDocument 4825 | ? $arg1 : $arg1->ownerDocument; 4826 | if ($documentWrapper->document->isSameNode($compare)) 4827 | $context = $documentWrapper->id; 4828 | } 4829 | } 4830 | if (!$context) { 4831 | $domId = self::$defaultDocumentID; 4832 | if (!$domId) 4833 | throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first."); 4834 | // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) 4835 | } else if (is_object($context) && $context instanceof phpQueryObject) 4836 | $domId = $context->getDocumentID(); 4837 | else if ($context instanceof DOMDOCUMENT) { 4838 | $domId = self::getDocumentID($context); 4839 | if (!$domId) { 4840 | //throw new Exception('Orphaned DOMDocument'); 4841 | $domId = self::newDocument($context)->getDocumentID(); 4842 | } 4843 | } else if ($context instanceof DOMNODE) { 4844 | $domId = self::getDocumentID($context); 4845 | if (!$domId) { 4846 | throw new Exception('Orphaned DOMNode'); 4847 | // $domId = self::newDocument($context->ownerDocument); 4848 | } 4849 | } else 4850 | $domId = $context; 4851 | if ($arg1 instanceof phpQueryObject) { 4852 | // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) { 4853 | /** 4854 | * Return $arg1 or import $arg1 stack if document differs: 4855 | * pq(pq('
')) 4856 | */ 4857 | if ($arg1->getDocumentID() == $domId) 4858 | return $arg1; 4859 | $class = get_class($arg1); 4860 | // support inheritance by passing old object to overloaded constructor 4861 | $phpQuery = $class != 'phpQuery' 4862 | ? new $class($arg1, $domId) 4863 | : new phpQueryObject($domId); 4864 | $phpQuery->elements = array(); 4865 | foreach ($arg1->elements as $node) 4866 | $phpQuery->elements[] = $phpQuery->document->importNode($node, true); 4867 | return $phpQuery; 4868 | } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) { 4869 | /* 4870 | * Wrap DOM nodes with phpQuery object, import into document when needed: 4871 | * pq(array($domNode1, $domNode2)) 4872 | */ 4873 | $phpQuery = new phpQueryObject($domId); 4874 | if (!($arg1 instanceof DOMNODELIST) && !is_array($arg1)) 4875 | $arg1 = array($arg1); 4876 | $phpQuery->elements = array(); 4877 | foreach ($arg1 as $node) { 4878 | $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT 4879 | && !$node->ownerDocument->isSameNode($phpQuery->document); 4880 | $phpQuery->elements[] = $sameDocument 4881 | ? $phpQuery->document->importNode($node, true) 4882 | : $node; 4883 | } 4884 | return $phpQuery; 4885 | } else if (self::isMarkup($arg1)) { 4886 | /** 4887 | * Import HTML: 4888 | * pq('
') 4889 | */ 4890 | $phpQuery = new phpQueryObject($domId); 4891 | return $phpQuery->newInstance( 4892 | $phpQuery->documentWrapper->import($arg1) 4893 | ); 4894 | } else { 4895 | /** 4896 | * Run CSS query: 4897 | * pq('div.myClass') 4898 | */ 4899 | $phpQuery = new phpQueryObject($domId); 4900 | // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject'))) 4901 | if ($context && $context instanceof phpQueryObject) 4902 | $phpQuery->elements = $context->elements; 4903 | else if ($context && $context instanceof DOMNODELIST) { 4904 | $phpQuery->elements = array(); 4905 | foreach ($context as $node) 4906 | $phpQuery->elements[] = $node; 4907 | } else if ($context && $context instanceof DOMNODE) 4908 | $phpQuery->elements = array($context); 4909 | return $phpQuery->find($arg1); 4910 | } 4911 | } 4912 | /** 4913 | * Sets default document to $id. Document has to be loaded prior 4914 | * to using this method. 4915 | * $id can be retrived via getDocumentID() or getDocumentIDRef(). 4916 | * 4917 | * @param unknown_type $id 4918 | */ 4919 | public static function selectDocument($id) 4920 | { 4921 | $id = self::getDocumentID($id); 4922 | self::debug("Selecting document '$id' as default one"); 4923 | self::$defaultDocumentID = self::getDocumentID($id); 4924 | } 4925 | /** 4926 | * Returns document with id $id or last used as phpQueryObject. 4927 | * $id can be retrived via getDocumentID() or getDocumentIDRef(). 4928 | * Chainable. 4929 | * 4930 | * @see phpQuery::selectDocument() 4931 | * @param unknown_type $id 4932 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4933 | */ 4934 | public static function getDocument($id = null) 4935 | { 4936 | if ($id) 4937 | phpQuery::selectDocument($id); 4938 | else 4939 | $id = phpQuery::$defaultDocumentID; 4940 | return new phpQueryObject($id); 4941 | } 4942 | /** 4943 | * Creates new document from markup. 4944 | * Chainable. 4945 | * 4946 | * @param unknown_type $markup 4947 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4948 | */ 4949 | public static function newDocument($markup = null, $contentType = null) 4950 | { 4951 | if (!$markup) 4952 | $markup = ''; 4953 | $documentID = phpQuery::createDocumentWrapper($markup, $contentType); 4954 | return new phpQueryObject($documentID); 4955 | } 4956 | /** 4957 | * Creates new document from markup. 4958 | * Chainable. 4959 | * 4960 | * @param unknown_type $markup 4961 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4962 | */ 4963 | public static function newDocumentHTML($markup = null, $charset = null) 4964 | { 4965 | $contentType = $charset 4966 | ? ";charset=$charset" 4967 | : ''; 4968 | return self::newDocument($markup, "text/html{$contentType}"); 4969 | } 4970 | /** 4971 | * Creates new document from markup. 4972 | * Chainable. 4973 | * 4974 | * @param unknown_type $markup 4975 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4976 | */ 4977 | public static function newDocumentXML($markup = null, $charset = null) 4978 | { 4979 | $contentType = $charset 4980 | ? ";charset=$charset" 4981 | : ''; 4982 | return self::newDocument($markup, "text/xml{$contentType}"); 4983 | } 4984 | /** 4985 | * Creates new document from markup. 4986 | * Chainable. 4987 | * 4988 | * @param unknown_type $markup 4989 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 4990 | */ 4991 | public static function newDocumentXHTML($markup = null, $charset = null) 4992 | { 4993 | $contentType = $charset 4994 | ? ";charset=$charset" 4995 | : ''; 4996 | return self::newDocument($markup, "application/xhtml+xml{$contentType}"); 4997 | } 4998 | /** 4999 | * Creates new document from markup. 5000 | * Chainable. 5001 | * 5002 | * @param unknown_type $markup 5003 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5004 | */ 5005 | public static function newDocumentPHP($markup = null, $contentType = "text/html") 5006 | { 5007 | // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function) 5008 | $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset); 5009 | return self::newDocument($markup, $contentType); 5010 | } 5011 | public static function phpToMarkup($php, $charset = 'utf-8') 5012 | { 5013 | $regexes = array( 5014 | '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<' . '?php?(.*?)(?:\\?>)([^\']*)\'@s', 5015 | '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<' . '?php?(.*?)(?:\\?>)([^"]*)"@s', 5016 | ); 5017 | foreach ($regexes as $regex) 5018 | while (preg_match($regex, $php, $matches)) { 5019 | $php = preg_replace_callback( 5020 | $regex, 5021 | // create_function('$m, $charset = "'.$charset.'"', 5022 | // 'return $m[1].$m[2] 5023 | // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset) 5024 | // .$m[5].$m[2];' 5025 | // ), 5026 | array('phpQuery', '_phpToMarkupCallback'), 5027 | $php 5028 | ); 5029 | } 5030 | $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s'; 5031 | //preg_match_all($regex, $php, $matches); 5032 | //var_dump($matches); 5033 | $php = preg_replace($regex, '\\1', $php); 5034 | return $php; 5035 | } 5036 | public static function _phpToMarkupCallback($php, $charset = 'utf-8') 5037 | { 5038 | return $m[1] . $m[2] 5039 | . htmlspecialchars("<" . "?php" . $m[4] . "?" . ">", ENT_QUOTES | ENT_NOQUOTES, $charset) 5040 | . $m[5] . $m[2]; 5041 | } 5042 | public static function _markupToPHPCallback($m) 5043 | { 5044 | return "<" . "?php " . htmlspecialchars_decode($m[1]) . " ?" . ">"; 5045 | } 5046 | /** 5047 | * Converts document markup containing PHP code generated by phpQuery::php() 5048 | * into valid (executable) PHP code syntax. 5049 | * 5050 | * @param string|phpQueryObject $content 5051 | * @return string PHP code. 5052 | */ 5053 | public static function markupToPHP($content) 5054 | { 5055 | if ($content instanceof phpQueryObject) 5056 | $content = $content->markupOuter(); 5057 | /* ... to */ 5058 | $content = preg_replace_callback( 5059 | '@\s*\s*@s', 5060 | // create_function('$m', 5061 | // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";' 5062 | // ), 5063 | array('phpQuery', '_markupToPHPCallback'), 5064 | $content 5065 | ); 5066 | /* extra space added to save highlighters */ 5067 | $regexes = array( 5068 | '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^\']*)\'@s', 5069 | '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^"]*)"@s', 5070 | ); 5071 | foreach ($regexes as $regex) 5072 | while (preg_match($regex, $content)) 5073 | $content = preg_replace_callback( 5074 | $regex, 5075 | function ($m) { 5076 | return $m[1] . $m[2] . $m[3] . "", " ", "\n", " ", "{", "$", "}", '"', "[", "]"), 5080 | htmlspecialchars_decode($m[4]) 5081 | ) 5082 | . " ?>" . $m[5] . $m[2]; 5083 | }, 5084 | $content 5085 | ); 5086 | return $content; 5087 | } 5088 | /** 5089 | * Creates new document from file $file. 5090 | * Chainable. 5091 | * 5092 | * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources. 5093 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5094 | */ 5095 | public static function newDocumentFile($file, $contentType = null) 5096 | { 5097 | $documentID = self::createDocumentWrapper( 5098 | file_get_contents($file), 5099 | $contentType 5100 | ); 5101 | return new phpQueryObject($documentID); 5102 | } 5103 | /** 5104 | * Creates new document from markup. 5105 | * Chainable. 5106 | * 5107 | * @param unknown_type $markup 5108 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5109 | */ 5110 | public static function newDocumentFileHTML($file, $charset = null) 5111 | { 5112 | $contentType = $charset 5113 | ? ";charset=$charset" 5114 | : ''; 5115 | return self::newDocumentFile($file, "text/html{$contentType}"); 5116 | } 5117 | /** 5118 | * Creates new document from markup. 5119 | * Chainable. 5120 | * 5121 | * @param unknown_type $markup 5122 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5123 | */ 5124 | public static function newDocumentFileXML($file, $charset = null) 5125 | { 5126 | $contentType = $charset 5127 | ? ";charset=$charset" 5128 | : ''; 5129 | return self::newDocumentFile($file, "text/xml{$contentType}"); 5130 | } 5131 | /** 5132 | * Creates new document from markup. 5133 | * Chainable. 5134 | * 5135 | * @param unknown_type $markup 5136 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5137 | */ 5138 | public static function newDocumentFileXHTML($file, $charset = null) 5139 | { 5140 | $contentType = $charset 5141 | ? ";charset=$charset" 5142 | : ''; 5143 | return self::newDocumentFile($file, "application/xhtml+xml{$contentType}"); 5144 | } 5145 | /** 5146 | * Creates new document from markup. 5147 | * Chainable. 5148 | * 5149 | * @param unknown_type $markup 5150 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5151 | */ 5152 | public static function newDocumentFilePHP($file, $contentType = null) 5153 | { 5154 | return self::newDocumentPHP(file_get_contents($file), $contentType); 5155 | } 5156 | /** 5157 | * Reuses existing DOMDocument object. 5158 | * Chainable. 5159 | * 5160 | * @param $document DOMDocument 5161 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 5162 | * @TODO support DOMDocument 5163 | */ 5164 | public static function loadDocument($document) 5165 | { 5166 | // TODO 5167 | die('TODO loadDocument'); 5168 | } 5169 | /** 5170 | * Enter description here... 5171 | * 5172 | * @param unknown_type $html 5173 | * @param unknown_type $domId 5174 | * @return unknown New DOM ID 5175 | * @todo support PHP tags in input 5176 | * @todo support passing DOMDocument object from self::loadDocument 5177 | */ 5178 | protected static function createDocumentWrapper($html, $contentType = null, $documentID = null) 5179 | { 5180 | if (function_exists('domxml_open_mem')) 5181 | throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled."); 5182 | // $id = $documentID 5183 | // ? $documentID 5184 | // : md5(microtime()); 5185 | $document = null; 5186 | if ($html instanceof DOMDOCUMENT) { 5187 | if (self::getDocumentID($html)) { 5188 | // document already exists in phpQuery::$documents, make a copy 5189 | $document = clone $html; 5190 | } else { 5191 | // new document, add it to phpQuery::$documents 5192 | $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); 5193 | } 5194 | } else { 5195 | $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID); 5196 | } 5197 | // $wrapper->id = $id; 5198 | // bind document 5199 | phpQuery::$documents[$wrapper->id] = $wrapper; 5200 | // remember last loaded document 5201 | phpQuery::selectDocument($wrapper->id); 5202 | return $wrapper->id; 5203 | } 5204 | /** 5205 | * Extend class namespace. 5206 | * 5207 | * @param string|array $target 5208 | * @param array $source 5209 | * @TODO support string $source 5210 | * @return unknown_type 5211 | */ 5212 | public static function extend($target, $source) 5213 | { 5214 | switch ($target) { 5215 | case 'phpQueryObject': 5216 | $targetRef = &self::$extendMethods; 5217 | $targetRef2 = &self::$pluginsMethods; 5218 | break; 5219 | case 'phpQuery': 5220 | $targetRef = &self::$extendStaticMethods; 5221 | $targetRef2 = &self::$pluginsStaticMethods; 5222 | break; 5223 | default: 5224 | throw new Exception("Unsupported \$target type"); 5225 | } 5226 | if (is_string($source)) 5227 | $source = array($source => $source); 5228 | foreach ($source as $method => $callback) { 5229 | if (isset($targetRef[$method])) { 5230 | // throw new Exception 5231 | self::debug("Duplicate method '{$method}', can\'t extend '{$target}'"); 5232 | continue; 5233 | } 5234 | if (isset($targetRef2[$method])) { 5235 | // throw new Exception 5236 | self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}'," 5237 | . " can\'t extend '{$target}'"); 5238 | continue; 5239 | } 5240 | $targetRef[$method] = $callback; 5241 | } 5242 | return true; 5243 | } 5244 | /** 5245 | * Extend phpQuery with $class from $file. 5246 | * 5247 | * @param string $class Extending class name. Real class name can be prepended phpQuery_. 5248 | * @param string $file Filename to include. Defaults to "{$class}.php". 5249 | */ 5250 | public static function plugin($class, $file = null) 5251 | { 5252 | // TODO $class checked agains phpQuery_$class 5253 | // if (strpos($class, 'phpQuery') === 0) 5254 | // $class = substr($class, 8); 5255 | if (in_array($class, self::$pluginsLoaded)) 5256 | return true; 5257 | if (!$file) 5258 | $file = $class . '.php'; 5259 | $objectClassExists = class_exists('phpQueryObjectPlugin_' . $class); 5260 | $staticClassExists = class_exists('phpQueryPlugin_' . $class); 5261 | if (!$objectClassExists && !$staticClassExists) 5262 | require_once($file); 5263 | self::$pluginsLoaded[] = $class; 5264 | // static methods 5265 | if (class_exists('phpQueryPlugin_' . $class)) { 5266 | $realClass = 'phpQueryPlugin_' . $class; 5267 | $vars = get_class_vars($realClass); 5268 | $loop = isset($vars['phpQueryMethods']) 5269 | && !is_null($vars['phpQueryMethods']) 5270 | ? $vars['phpQueryMethods'] 5271 | : get_class_methods($realClass); 5272 | foreach ($loop as $method) { 5273 | if ($method == '__initialize') 5274 | continue; 5275 | if (!is_callable(array($realClass, $method))) 5276 | continue; 5277 | if (isset(self::$pluginsStaticMethods[$method])) { 5278 | throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '" . self::$pluginsStaticMethods[$method] . "'"); 5279 | return; 5280 | } 5281 | self::$pluginsStaticMethods[$method] = $class; 5282 | } 5283 | if (method_exists($realClass, '__initialize')) 5284 | call_user_func_array(array($realClass, '__initialize'), array()); 5285 | } 5286 | // object methods 5287 | if (class_exists('phpQueryObjectPlugin_' . $class)) { 5288 | $realClass = 'phpQueryObjectPlugin_' . $class; 5289 | $vars = get_class_vars($realClass); 5290 | $loop = isset($vars['phpQueryMethods']) 5291 | && !is_null($vars['phpQueryMethods']) 5292 | ? $vars['phpQueryMethods'] 5293 | : get_class_methods($realClass); 5294 | foreach ($loop as $method) { 5295 | if (!is_callable(array($realClass, $method))) 5296 | continue; 5297 | if (isset(self::$pluginsMethods[$method])) { 5298 | throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '" . self::$pluginsMethods[$method] . "'"); 5299 | continue; 5300 | } 5301 | self::$pluginsMethods[$method] = $class; 5302 | } 5303 | } 5304 | return true; 5305 | } 5306 | /** 5307 | * Unloades all or specified document from memory. 5308 | * 5309 | * @param mixed $documentID @see phpQuery::getDocumentID() for supported types. 5310 | */ 5311 | public static function unloadDocuments($id = null) 5312 | { 5313 | if (isset($id)) { 5314 | if ($id = self::getDocumentID($id)) 5315 | unset(phpQuery::$documents[$id]); 5316 | } else { 5317 | foreach (phpQuery::$documents as $k => $v) { 5318 | unset(phpQuery::$documents[$k]); 5319 | } 5320 | } 5321 | } 5322 | /** 5323 | * Parses phpQuery object or HTML result against PHP tags and makes them active. 5324 | * 5325 | * @param phpQuery|string $content 5326 | * @deprecated 5327 | * @return string 5328 | */ 5329 | public static function unsafePHPTags($content) 5330 | { 5331 | return self::markupToPHP($content); 5332 | } 5333 | public static function DOMNodeListToArray($DOMNodeList) 5334 | { 5335 | $array = array(); 5336 | if (!$DOMNodeList) 5337 | return $array; 5338 | foreach ($DOMNodeList as $node) 5339 | $array[] = $node; 5340 | return $array; 5341 | } 5342 | /** 5343 | * Checks if $input is HTML string, which has to start with '<'. 5344 | * 5345 | * @deprecated 5346 | * @param String $input 5347 | * @return Bool 5348 | * @todo still used ? 5349 | */ 5350 | public static function isMarkup($input) 5351 | { 5352 | return !is_array($input) && substr(trim($input), 0, 1) == '<'; 5353 | } 5354 | public static function debug($text) 5355 | { 5356 | if (self::$debug) 5357 | print var_dump($text); 5358 | } 5359 | /** 5360 | * Make an AJAX request. 5361 | * 5362 | * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions 5363 | * Additional options are: 5364 | * 'document' - document for global events, @see phpQuery::getDocumentID() 5365 | * 'referer' - implemented 5366 | * 'requested_with' - TODO; not implemented (X-Requested-With) 5367 | * @return Zend_Http_Client 5368 | * @link http://docs.jquery.com/Ajax/jQuery.ajax 5369 | * 5370 | * @TODO $options['cache'] 5371 | * @TODO $options['processData'] 5372 | * @TODO $options['xhr'] 5373 | * @TODO $options['data'] as string 5374 | * @TODO XHR interface 5375 | */ 5376 | public static function ajax($options = array(), $xhr = null) 5377 | { 5378 | $options = array_merge( 5379 | self::$ajaxSettings, 5380 | $options 5381 | ); 5382 | $documentID = isset($options['document']) 5383 | ? self::getDocumentID($options['document']) 5384 | : null; 5385 | if ($xhr) { 5386 | // reuse existing XHR object, but clean it up 5387 | $client = $xhr; 5388 | // $client->setParameterPost(null); 5389 | // $client->setParameterGet(null); 5390 | $client->setAuth(false); 5391 | $client->setHeaders("If-Modified-Since", null); 5392 | $client->setHeaders("Referer", null); 5393 | $client->resetParameters(); 5394 | } else { 5395 | // create new XHR object 5396 | require_once('Zend/Http/Client.php'); 5397 | $client = new Zend_Http_Client(); 5398 | $client->setCookieJar(); 5399 | } 5400 | if (isset($options['timeout'])) 5401 | $client->setConfig(array( 5402 | 'timeout' => $options['timeout'], 5403 | )); 5404 | // 'maxredirects' => 0, 5405 | foreach (self::$ajaxAllowedHosts as $k => $host) 5406 | if ($host == '.' && isset($_SERVER['HTTP_HOST'])) 5407 | self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST']; 5408 | $host = parse_url($options['url'], PHP_URL_HOST); 5409 | if (!in_array($host, self::$ajaxAllowedHosts)) { 5410 | throw new Exception("Request not permitted, host '$host' not present in " 5411 | . "phpQuery::\$ajaxAllowedHosts"); 5412 | } 5413 | // JSONP 5414 | $jsre = "/=\\?(&|$)/"; 5415 | if (isset($options['dataType']) && $options['dataType'] == 'jsonp') { 5416 | $jsonpCallbackParam = $options['jsonp'] 5417 | ? $options['jsonp'] : 'callback'; 5418 | if (strtolower($options['type']) == 'get') { 5419 | if (!preg_match($jsre, $options['url'])) { 5420 | $sep = strpos($options['url'], '?') 5421 | ? '&' : '?'; 5422 | $options['url'] .= "$sep$jsonpCallbackParam=?"; 5423 | } 5424 | } else if ($options['data']) { 5425 | $jsonp = false; 5426 | foreach ($options['data'] as $n => $v) { 5427 | if ($v == '?') 5428 | $jsonp = true; 5429 | } 5430 | if (!$jsonp) { 5431 | $options['data'][$jsonpCallbackParam] = '?'; 5432 | } 5433 | } 5434 | $options['dataType'] = 'json'; 5435 | } 5436 | if (isset($options['dataType']) && $options['dataType'] == 'json') { 5437 | $jsonpCallback = 'json_' . md5(microtime()); 5438 | $jsonpData = $jsonpUrl = false; 5439 | if ($options['data']) { 5440 | foreach ($options['data'] as $n => $v) { 5441 | if ($v == '?') 5442 | $jsonpData = $n; 5443 | } 5444 | } 5445 | if (preg_match($jsre, $options['url'])) 5446 | $jsonpUrl = true; 5447 | if ($jsonpData !== false || $jsonpUrl) { 5448 | // remember callback name for httpData() 5449 | $options['_jsonp'] = $jsonpCallback; 5450 | if ($jsonpData !== false) 5451 | $options['data'][$jsonpData] = $jsonpCallback; 5452 | if ($jsonpUrl) 5453 | $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']); 5454 | } 5455 | } 5456 | $client->setUri($options['url']); 5457 | $client->setMethod(strtoupper($options['type'])); 5458 | if (isset($options['referer']) && $options['referer']) 5459 | $client->setHeaders('Referer', $options['referer']); 5460 | $client->setHeaders(array( 5461 | // 'content-type' => $options['contentType'], 5462 | 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko' 5463 | . '/2008122010 Firefox/3.0.5', 5464 | // TODO custom charset 5465 | 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 5466 | // 'Connection' => 'keep-alive', 5467 | // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 5468 | 'Accept-Language' => 'en-us,en;q=0.5', 5469 | )); 5470 | if ($options['username']) 5471 | $client->setAuth($options['username'], $options['password']); 5472 | if (isset($options['ifModified']) && $options['ifModified']) 5473 | $client->setHeaders( 5474 | "If-Modified-Since", 5475 | self::$lastModified 5476 | ? self::$lastModified 5477 | : "Thu, 01 Jan 1970 00:00:00 GMT" 5478 | ); 5479 | $client->setHeaders( 5480 | "Accept", 5481 | isset($options['dataType']) 5482 | && isset(self::$ajaxSettings['accepts'][$options['dataType']]) 5483 | ? self::$ajaxSettings['accepts'][$options['dataType']] . ", */*" 5484 | : self::$ajaxSettings['accepts']['_default'] 5485 | ); 5486 | // TODO $options['processData'] 5487 | if ($options['data'] instanceof phpQueryObject) { 5488 | $serialized = $options['data']->serializeArray($options['data']); 5489 | $options['data'] = array(); 5490 | foreach ($serialized as $r) 5491 | $options['data'][$r['name']] = $r['value']; 5492 | } 5493 | if (strtolower($options['type']) == 'get') { 5494 | $client->setParameterGet($options['data']); 5495 | } else if (strtolower($options['type']) == 'post') { 5496 | $client->setEncType($options['contentType']); 5497 | $client->setParameterPost($options['data']); 5498 | } 5499 | if (self::$active == 0 && $options['global']) 5500 | phpQueryEvents::trigger($documentID, 'ajaxStart'); 5501 | self::$active++; 5502 | // beforeSend callback 5503 | if (isset($options['beforeSend']) && $options['beforeSend']) 5504 | phpQuery::callbackRun($options['beforeSend'], array($client)); 5505 | // ajaxSend event 5506 | if ($options['global']) 5507 | phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options)); 5508 | if (phpQuery::$debug) { 5509 | self::debug("{$options['type']}: {$options['url']}\n"); 5510 | self::debug("Options:
" . var_export($options, true) . "
\n"); 5511 | // if ($client->getCookieJar()) 5512 | // self::debug("Cookies:
".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."
\n"); 5513 | } 5514 | // request 5515 | $response = $client->request(); 5516 | if (phpQuery::$debug) { 5517 | self::debug('Status: ' . $response->getStatus() . ' / ' . $response->getMessage()); 5518 | self::debug($client->getLastRequest()); 5519 | self::debug($response->getHeaders()); 5520 | } 5521 | if ($response->isSuccessful()) { 5522 | // XXX tempolary 5523 | self::$lastModified = $response->getHeader('Last-Modified'); 5524 | $data = self::httpData($response->getBody(), $options['dataType'], $options); 5525 | if (isset($options['success']) && $options['success']) 5526 | phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options)); 5527 | if ($options['global']) 5528 | phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options)); 5529 | } else { 5530 | if (isset($options['error']) && $options['error']) 5531 | phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage())); 5532 | if ($options['global']) 5533 | phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/ $response->getMessage(), $options)); 5534 | } 5535 | if (isset($options['complete']) && $options['complete']) 5536 | phpQuery::callbackRun($options['complete'], array($client, $response->getStatus())); 5537 | if ($options['global']) 5538 | phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options)); 5539 | if ($options['global'] && !--self::$active) 5540 | phpQueryEvents::trigger($documentID, 'ajaxStop'); 5541 | return $client; 5542 | // if (is_null($domId)) 5543 | // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false; 5544 | // return new phpQueryAjaxResponse($response, $domId); 5545 | } 5546 | protected static function httpData($data, $type, $options) 5547 | { 5548 | if (isset($options['dataFilter']) && $options['dataFilter']) 5549 | $data = self::callbackRun($options['dataFilter'], array($data, $type)); 5550 | if (is_string($data)) { 5551 | if ($type == "json") { 5552 | if (isset($options['_jsonp']) && $options['_jsonp']) { 5553 | $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data); 5554 | } 5555 | $data = self::parseJSON($data); 5556 | } 5557 | } 5558 | return $data; 5559 | } 5560 | /** 5561 | * Enter description here... 5562 | * 5563 | * @param array|phpQuery $data 5564 | * 5565 | */ 5566 | public static function param($data) 5567 | { 5568 | return http_build_query($data, null, '&'); 5569 | } 5570 | public static function get($url, $data = null, $callback = null, $type = null) 5571 | { 5572 | if (!is_array($data)) { 5573 | $callback = $data; 5574 | $data = null; 5575 | } 5576 | // TODO some array_values on this shit 5577 | return phpQuery::ajax(array( 5578 | 'type' => 'GET', 5579 | 'url' => $url, 5580 | 'data' => $data, 5581 | 'success' => $callback, 5582 | 'dataType' => $type, 5583 | )); 5584 | } 5585 | public static function post($url, $data = null, $callback = null, $type = null) 5586 | { 5587 | if (!is_array($data)) { 5588 | $callback = $data; 5589 | $data = null; 5590 | } 5591 | return phpQuery::ajax(array( 5592 | 'type' => 'POST', 5593 | 'url' => $url, 5594 | 'data' => $data, 5595 | 'success' => $callback, 5596 | 'dataType' => $type, 5597 | )); 5598 | } 5599 | public static function getJSON($url, $data = null, $callback = null) 5600 | { 5601 | if (!is_array($data)) { 5602 | $callback = $data; 5603 | $data = null; 5604 | } 5605 | // TODO some array_values on this shit 5606 | return phpQuery::ajax(array( 5607 | 'type' => 'GET', 5608 | 'url' => $url, 5609 | 'data' => $data, 5610 | 'success' => $callback, 5611 | 'dataType' => 'json', 5612 | )); 5613 | } 5614 | public static function ajaxSetup($options) 5615 | { 5616 | self::$ajaxSettings = array_merge( 5617 | self::$ajaxSettings, 5618 | $options 5619 | ); 5620 | } 5621 | public static function ajaxAllowHost($host1, $host2 = null, $host3 = null) 5622 | { 5623 | $loop = is_array($host1) 5624 | ? $host1 5625 | : func_get_args(); 5626 | foreach ($loop as $host) { 5627 | if ($host && !in_array($host, phpQuery::$ajaxAllowedHosts)) { 5628 | phpQuery::$ajaxAllowedHosts[] = $host; 5629 | } 5630 | } 5631 | } 5632 | public static function ajaxAllowURL($url1, $url2 = null, $url3 = null) 5633 | { 5634 | $loop = is_array($url1) 5635 | ? $url1 5636 | : func_get_args(); 5637 | foreach ($loop as $url) 5638 | phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST)); 5639 | } 5640 | /** 5641 | * Returns JSON representation of $data. 5642 | * 5643 | * @static 5644 | * @param mixed $data 5645 | * @return string 5646 | */ 5647 | public static function toJSON($data) 5648 | { 5649 | if (function_exists('json_encode')) 5650 | return json_encode($data); 5651 | require_once('Zend/Json/Encoder.php'); 5652 | return Zend_Json_Encoder::encode($data); 5653 | } 5654 | /** 5655 | * Parses JSON into proper PHP type. 5656 | * 5657 | * @static 5658 | * @param string $json 5659 | * @return mixed 5660 | */ 5661 | public static function parseJSON($json) 5662 | { 5663 | if (function_exists('json_decode')) { 5664 | $return = json_decode(trim($json), true); 5665 | // json_decode and UTF8 issues 5666 | if (isset($return)) 5667 | return $return; 5668 | } 5669 | require_once('Zend/Json/Decoder.php'); 5670 | return Zend_Json_Decoder::decode($json); 5671 | } 5672 | /** 5673 | * Returns source's document ID. 5674 | * 5675 | * @param $source DOMNode|phpQueryObject 5676 | * @return string 5677 | */ 5678 | public static function getDocumentID($source) 5679 | { 5680 | if ($source instanceof DOMDOCUMENT) { 5681 | foreach (phpQuery::$documents as $id => $document) { 5682 | if ($source->isSameNode($document->document)) 5683 | return $id; 5684 | } 5685 | } else if ($source instanceof DOMNODE) { 5686 | foreach (phpQuery::$documents as $id => $document) { 5687 | if ($source->ownerDocument->isSameNode($document->document)) 5688 | return $id; 5689 | } 5690 | } else if ($source instanceof phpQueryObject) 5691 | return $source->getDocumentID(); 5692 | else if (is_string($source) && isset(phpQuery::$documents[$source])) 5693 | return $source; 5694 | } 5695 | /** 5696 | * Get DOMDocument object related to $source. 5697 | * Returns null if such document doesn't exist. 5698 | * 5699 | * @param $source DOMNode|phpQueryObject|string 5700 | * @return string 5701 | */ 5702 | public static function getDOMDocument($source) 5703 | { 5704 | if ($source instanceof DOMDOCUMENT) 5705 | return $source; 5706 | $source = self::getDocumentID($source); 5707 | return $source 5708 | ? self::$documents[$id]['document'] 5709 | : null; 5710 | } 5711 | 5712 | // UTILITIES 5713 | // http://docs.jquery.com/Utilities 5714 | 5715 | /** 5716 | * 5717 | * @return unknown_type 5718 | * @link http://docs.jquery.com/Utilities/jQuery.makeArray 5719 | */ 5720 | public static function makeArray($obj) 5721 | { 5722 | $array = array(); 5723 | if (is_object($object) && $object instanceof DOMNODELIST) { 5724 | foreach ($object as $value) 5725 | $array[] = $value; 5726 | } else if (is_object($object) && !($object instanceof Iterator)) { 5727 | foreach (get_object_vars($object) as $name => $value) 5728 | $array[0][$name] = $value; 5729 | } else { 5730 | foreach ($object as $name => $value) 5731 | $array[0][$name] = $value; 5732 | } 5733 | return $array; 5734 | } 5735 | public static function inArray($value, $array) 5736 | { 5737 | return in_array($value, $array); 5738 | } 5739 | /** 5740 | * 5741 | * @param $object 5742 | * @param $callback 5743 | * @return unknown_type 5744 | * @link http://docs.jquery.com/Utilities/jQuery.each 5745 | */ 5746 | public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) 5747 | { 5748 | $paramStructure = null; 5749 | if (func_num_args() > 2) { 5750 | $paramStructure = func_get_args(); 5751 | $paramStructure = array_slice($paramStructure, 2); 5752 | } 5753 | if (is_object($object) && !($object instanceof Iterator)) { 5754 | foreach (get_object_vars($object) as $name => $value) 5755 | phpQuery::callbackRun($callback, array($name, $value), $paramStructure); 5756 | } else { 5757 | foreach ($object as $name => $value) 5758 | phpQuery::callbackRun($callback, array($name, $value), $paramStructure); 5759 | } 5760 | } 5761 | /** 5762 | * 5763 | * @link http://docs.jquery.com/Utilities/jQuery.map 5764 | */ 5765 | public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) 5766 | { 5767 | $result = array(); 5768 | $paramStructure = null; 5769 | if (func_num_args() > 2) { 5770 | $paramStructure = func_get_args(); 5771 | $paramStructure = array_slice($paramStructure, 2); 5772 | } 5773 | foreach ($array as $v) { 5774 | $vv = phpQuery::callbackRun($callback, array($v), $paramStructure); 5775 | // $callbackArgs = $args; 5776 | // foreach($args as $i => $arg) { 5777 | // $callbackArgs[$i] = $arg instanceof CallbackParam 5778 | // ? $v 5779 | // : $arg; 5780 | // } 5781 | // $vv = call_user_func_array($callback, $callbackArgs); 5782 | if (is_array($vv)) { 5783 | foreach ($vv as $vvv) 5784 | $result[] = $vvv; 5785 | } else if ($vv !== null) { 5786 | $result[] = $vv; 5787 | } 5788 | } 5789 | return $result; 5790 | } 5791 | /** 5792 | * 5793 | * @param $callback Callback 5794 | * @param $params 5795 | * @param $paramStructure 5796 | * @return unknown_type 5797 | */ 5798 | public static function callbackRun($callback, $params = array(), $paramStructure = null) 5799 | { 5800 | if (!$callback) 5801 | return; 5802 | if ($callback instanceof CallbackParameterToReference) { 5803 | // TODO support ParamStructure to select which $param push to reference 5804 | if (isset($params[0])) 5805 | $callback->callback = $params[0]; 5806 | return true; 5807 | } 5808 | if ($callback instanceof Callback) { 5809 | $paramStructure = $callback->params; 5810 | $callback = $callback->callback; 5811 | } 5812 | if (!$paramStructure) 5813 | return call_user_func_array($callback, $params); 5814 | $p = 0; 5815 | foreach ($paramStructure as $i => $v) { 5816 | $paramStructure[$i] = $v instanceof CallbackParam 5817 | ? $params[$p++] 5818 | : $v; 5819 | } 5820 | return call_user_func_array($callback, $paramStructure); 5821 | } 5822 | /** 5823 | * Merge 2 phpQuery objects. 5824 | * @param array $one 5825 | * @param array $two 5826 | * @protected 5827 | * @todo node lists, phpQueryObject 5828 | */ 5829 | public static function merge($one, $two) 5830 | { 5831 | $elements = $one->elements; 5832 | foreach ($two->elements as $node) { 5833 | $exists = false; 5834 | foreach ($elements as $node2) { 5835 | if ($node2->isSameNode($node)) 5836 | $exists = true; 5837 | } 5838 | if (!$exists) 5839 | $elements[] = $node; 5840 | } 5841 | return $elements; 5842 | // $one = $one->newInstance(); 5843 | // $one->elements = $elements; 5844 | // return $one; 5845 | } 5846 | /** 5847 | * 5848 | * @param $array 5849 | * @param $callback 5850 | * @param $invert 5851 | * @return unknown_type 5852 | * @link http://docs.jquery.com/Utilities/jQuery.grep 5853 | */ 5854 | public static function grep($array, $callback, $invert = false) 5855 | { 5856 | $result = array(); 5857 | foreach ($array as $k => $v) { 5858 | $r = call_user_func_array($callback, array($v, $k)); 5859 | if ($r === !(bool)$invert) 5860 | $result[] = $v; 5861 | } 5862 | return $result; 5863 | } 5864 | public static function unique($array) 5865 | { 5866 | return array_unique($array); 5867 | } 5868 | /** 5869 | * 5870 | * @param $function 5871 | * @return unknown_type 5872 | * @TODO there are problems with non-static methods, second parameter pass it 5873 | * but doesnt verify is method is really callable 5874 | */ 5875 | public static function isFunction($function) 5876 | { 5877 | return is_callable($function); 5878 | } 5879 | public static function trim($str) 5880 | { 5881 | return trim($str); 5882 | } 5883 | /* PLUGINS NAMESPACE */ 5884 | /** 5885 | * 5886 | * @param $url 5887 | * @param $callback 5888 | * @param $param1 5889 | * @param $param2 5890 | * @param $param3 5891 | * @return phpQueryObject 5892 | */ 5893 | public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null) 5894 | { 5895 | if (self::plugin('WebBrowser')) { 5896 | $params = func_get_args(); 5897 | return self::callbackRun(array(self::$plugins, 'browserGet'), $params); 5898 | } else { 5899 | self::debug('WebBrowser plugin not available...'); 5900 | } 5901 | } 5902 | /** 5903 | * 5904 | * @param $url 5905 | * @param $data 5906 | * @param $callback 5907 | * @param $param1 5908 | * @param $param2 5909 | * @param $param3 5910 | * @return phpQueryObject 5911 | */ 5912 | public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null) 5913 | { 5914 | if (self::plugin('WebBrowser')) { 5915 | $params = func_get_args(); 5916 | return self::callbackRun(array(self::$plugins, 'browserPost'), $params); 5917 | } else { 5918 | self::debug('WebBrowser plugin not available...'); 5919 | } 5920 | } 5921 | /** 5922 | * 5923 | * @param $ajaxSettings 5924 | * @param $callback 5925 | * @param $param1 5926 | * @param $param2 5927 | * @param $param3 5928 | * @return phpQueryObject 5929 | */ 5930 | public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null) 5931 | { 5932 | if (self::plugin('WebBrowser')) { 5933 | $params = func_get_args(); 5934 | return self::callbackRun(array(self::$plugins, 'browser'), $params); 5935 | } else { 5936 | self::debug('WebBrowser plugin not available...'); 5937 | } 5938 | } 5939 | /** 5940 | * 5941 | * @param $code 5942 | * @return string 5943 | */ 5944 | public static function php($code) 5945 | { 5946 | return self::code('php', $code); 5947 | } 5948 | /** 5949 | * 5950 | * @param $type 5951 | * @param $code 5952 | * @return string 5953 | */ 5954 | public static function code($type, $code) 5955 | { 5956 | return "<$type>"; 5957 | } 5958 | 5959 | public static function __callStatic($method, $params) 5960 | { 5961 | return call_user_func_array( 5962 | array(phpQuery::$plugins, $method), 5963 | $params 5964 | ); 5965 | } 5966 | protected static function dataSetupNode($node, $documentID) 5967 | { 5968 | // search are return if alredy exists 5969 | foreach (phpQuery::$documents[$documentID]->dataNodes as $dataNode) { 5970 | if ($node->isSameNode($dataNode)) 5971 | return $dataNode; 5972 | } 5973 | // if doesn't, add it 5974 | phpQuery::$documents[$documentID]->dataNodes[] = $node; 5975 | return $node; 5976 | } 5977 | protected static function dataRemoveNode($node, $documentID) 5978 | { 5979 | // search are return if alredy exists 5980 | foreach (phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) { 5981 | if ($node->isSameNode($dataNode)) { 5982 | unset(self::$documents[$documentID]->dataNodes[$k]); 5983 | unset(self::$documents[$documentID]->data[$dataNode->dataID]); 5984 | } 5985 | } 5986 | } 5987 | public static function data($node, $name, $data, $documentID = null) 5988 | { 5989 | if (!$documentID) 5990 | // TODO check if this works 5991 | $documentID = self::getDocumentID($node); 5992 | $document = phpQuery::$documents[$documentID]; 5993 | $node = self::dataSetupNode($node, $documentID); 5994 | if (!isset($node->dataID)) 5995 | $node->dataID = ++phpQuery::$documents[$documentID]->uuid; 5996 | $id = $node->dataID; 5997 | if (!isset($document->data[$id])) 5998 | $document->data[$id] = array(); 5999 | if (!is_null($data)) 6000 | $document->data[$id][$name] = $data; 6001 | if ($name) { 6002 | if (isset($document->data[$id][$name])) 6003 | return $document->data[$id][$name]; 6004 | } else 6005 | return $id; 6006 | } 6007 | public static function removeData($node, $name, $documentID) 6008 | { 6009 | if (!$documentID) 6010 | // TODO check if this works 6011 | $documentID = self::getDocumentID($node); 6012 | $document = phpQuery::$documents[$documentID]; 6013 | $node = self::dataSetupNode($node, $documentID); 6014 | $id = $node->dataID; 6015 | if ($name) { 6016 | if (isset($document->data[$id][$name])) 6017 | unset($document->data[$id][$name]); 6018 | $name = null; 6019 | foreach ($document->data[$id] as $name) 6020 | break; 6021 | if (!$name) 6022 | self::removeData($node, $name, $documentID); 6023 | } else { 6024 | self::dataRemoveNode($node, $documentID); 6025 | } 6026 | } 6027 | } 6028 | /** 6029 | * Plugins static namespace class. 6030 | * 6031 | * @author Tobiasz Cudnik 6032 | * @package phpQuery 6033 | * @todo move plugin methods here (as statics) 6034 | */ 6035 | class phpQueryPlugins 6036 | { 6037 | public function __call($method, $args) 6038 | { 6039 | if (isset(phpQuery::$extendStaticMethods[$method])) { 6040 | $return = call_user_func_array( 6041 | phpQuery::$extendStaticMethods[$method], 6042 | $args 6043 | ); 6044 | } else if (isset(phpQuery::$pluginsStaticMethods[$method])) { 6045 | $class = phpQuery::$pluginsStaticMethods[$method]; 6046 | $realClass = "phpQueryPlugin_$class"; 6047 | $return = call_user_func_array( 6048 | array($realClass, $method), 6049 | $args 6050 | ); 6051 | return isset($return) 6052 | ? $return 6053 | : $this; 6054 | } else 6055 | throw new Exception("Method '{$method}' doesnt exist"); 6056 | } 6057 | } 6058 | /** 6059 | * Shortcut to phpQuery::pq($arg1, $context) 6060 | * Chainable. 6061 | * 6062 | * @see phpQuery::pq() 6063 | * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery 6064 | * @author Tobiasz Cudnik 6065 | * @package phpQuery 6066 | */ 6067 | function pq($arg1, $context = null) 6068 | { 6069 | $args = func_get_args(); 6070 | return call_user_func_array( 6071 | array('phpQuery', 'pq'), 6072 | $args 6073 | ); 6074 | } 6075 | // add plugins dir and Zend framework to include path 6076 | set_include_path( 6077 | get_include_path() 6078 | . PATH_SEPARATOR . dirname(__FILE__) . '/phpQuery/' 6079 | . PATH_SEPARATOR . dirname(__FILE__) . '/phpQuery/plugins/' 6080 | ); 6081 | // why ? no __call nor __get for statics in php... 6082 | // XXX __callStatic will be available in PHP 5.3 6083 | phpQuery::$plugins = new phpQueryPlugins(); 6084 | // include bootstrap file (personal library config) 6085 | if (file_exists(dirname(__FILE__) . '/phpQuery/bootstrap.php')) 6086 | require_once dirname(__FILE__) . '/phpQuery/bootstrap.php'; 6087 | --------------------------------------------------------------------------------