Issues (1066)

Security Analysis    7 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection (3)
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection (3)
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/PhpQueryObject.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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