Passed
Push — master ( f21bed...142c4d )
by Arthur
02:05
created

QueryChecks   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 175
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 41
dl 0
loc 175
rs 10
c 0
b 0
f 0
wmc 24

5 Methods

Rating   Name   Duplication   Size   Complexity  
A hasClass() 0 12 4
A hasAttr() 0 9 3
A removeAttr() 0 7 2
A has() 0 33 6
B is() 0 28 9
1
<?php
2
3
namespace QueryPath\Helpers;
4
5
use QueryPath\CSS\ParseException;
6
use QueryPath\DOMQuery;
7
use QueryPath\Exception;
8
use QueryPath\Query;
9
10
/**
11
 * Trait QueryChecks
12
 *
13
 * @package QueryPath\Helpers
14
 *
15
 * @property array matches
16
 */
17
trait QueryChecks
18
{
19
20
    /**
21
     * Given a selector, this checks to see if the current set has one or more matches.
22
     *
23
     * Unlike jQuery's version, this supports full selectors (not just simple ones).
24
     *
25
     * @param string|\DOMNode $selector
26
     *   The selector to search for. As of QueryPath 2.1.1, this also supports passing a
27
     *   DOMNode object.
28
     * @return boolean
29
     *   TRUE if one or more elements match. FALSE if no match is found.
30
     * @see get()
31
     * @see eq()
32
     * @throws Exception
33
     * @throws Exception
34
     */
35
    public function is($selector): bool
36
    {
37
        if (is_object($selector)) {
38
            if ($selector instanceof \DOMNode) {
0 ignored issues
show
introduced by
$selector is always a sub-type of DOMNode.
Loading history...
39
                return count($this->matches) === 1 && $selector->isSameNode($this->get(0));
0 ignored issues
show
Bug introduced by
It seems like get() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

39
                return count($this->matches) === 1 && $selector->isSameNode($this->/** @scrutinizer ignore-call */ get(0));
Loading history...
40
            }
41
42
            if ($selector instanceof \Traversable) {
43
                if (count($selector) !== count($this->matches)) {
44
                    return false;
45
                }
46
                // Without $seen, there is an edge case here if $selector contains the same object
47
                // more than once, but the counts are equal. For example, [a, a, a, a] will
48
                // pass an is() on [a, b, c, d]. We use the $seen SPLOS to prevent this.
49
                $seen = new \SplObjectStorage();
50
                foreach ($selector as $item) {
51
                    if (!$this->matches->contains($item) || $seen->contains($item)) {
52
                        return false;
53
                    }
54
                    $seen->attach($item);
55
                }
56
57
                return true;
58
            }
59
            throw new Exception('Cannot compare an object to a DOMQuery.');
60
        }
61
62
        return $this->branch($selector)->count() > 0;
0 ignored issues
show
Bug introduced by
It seems like branch() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

62
        return $this->/** @scrutinizer ignore-call */ branch($selector)->count() > 0;
Loading history...
63
    }
64
65
    /**
66
     * Reduce the elements matched by DOMQuery to only those which contain the given item.
67
     *
68
     * There are two ways in which this is different from jQuery's implementation:
69
     * - We allow ANY DOMNode, not just DOMElements. That means this will work on
70
     *   processor instructions, text nodes, comments, etc.
71
     * - Unlike jQuery, this implementation of has() follows QueryPath standard behavior
72
     *   and modifies the existing object. It does not create a brand new object.
73
     *
74
     * @param mixed $contained
75
     *     - If $contained is a CSS selector (e.g. '#foo'), this will test to see
76
     *     if the current DOMQuery has any elements that contain items that match
77
     *     the selector.
78
     *     - If $contained is a DOMNode, then this will test to see if THE EXACT DOMNode
79
     *     exists in the currently matched elements. (Note that you cannot match across DOM trees, even if it is the
80
     *     same document.)
81
     * @since  2.1
82
     * @author eabrand
83
     * @todo   It would be trivially easy to add support for iterating over an array or Iterable of DOMNodes.
84
     * @return DOMQuery
85
     * @throws ParseException
86
     */
87
    public function has($contained): Query
88
    {
89
        /*
90
    if (count($this->matches) == 0) {
91
      return false;
92
    }
93
     */
94
        $found = new \SplObjectStorage();
95
96
        // If it's a selector, we just get all of the DOMNodes that match the selector.
97
        $nodes = [];
98
        if (is_string($contained)) {
99
            // Get the list of nodes.
100
            $nodes = $this->branch($contained)->get();
101
        } elseif ($contained instanceof \DOMNode) {
102
            // Make a list with one node.
103
            $nodes = [$contained];
104
        }
105
106
        // Now we go through each of the nodes that we are testing. We want to find
107
        // ALL PARENTS that are in our existing DOMQuery matches. Those are the
108
        // ones we add to our new matches.
109
        foreach ($nodes as $original_node) {
110
            $node = $original_node;
111
            while (!empty($node)/* && $node != $node->ownerDocument*/) {
112
                if ($this->matches->contains($node)) {
113
                    $found->attach($node);
114
                }
115
                $node = $node->parentNode;
116
            }
117
        }
118
119
        return $this->inst($found, NULL);
0 ignored issues
show
Bug introduced by
It seems like inst() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

119
        return $this->/** @scrutinizer ignore-call */ inst($found, NULL);
Loading history...
120
    }
121
122
    /**
123
     * Returns TRUE if any of the elements in the DOMQuery have the specified class.
124
     *
125
     * @param string $class
126
     *  The name of the class.
127
     * @return boolean
128
     *  TRUE if the class exists in one or more of the elements, FALSE otherwise.
129
     * @see addClass()
130
     * @see removeClass()
131
     */
132
    public function hasClass($class): bool
133
    {
134
        foreach ($this->matches as $m) {
135
            if ($m->hasAttribute('class')) {
136
                $vals = explode(' ', $m->getAttribute('class'));
137
                if (in_array($class, $vals)) {
138
                    return true;
139
                }
140
            }
141
        }
142
143
        return false;
144
    }
145
146
    /**
147
     * Check to see if the given attribute is present.
148
     *
149
     * This returns TRUE if <em>all</em> selected items have the attribute, or
150
     * FALSE if at least one item does not have the attribute.
151
     *
152
     * @param string $attrName
153
     *  The attribute name.
154
     * @return boolean
155
     *  TRUE if all matches have the attribute, FALSE otherwise.
156
     * @since 2.0
157
     * @see   attr()
158
     * @see   hasClass()
159
     */
160
    public function hasAttr($attrName): bool
161
    {
162
        foreach ($this->matches as $match) {
163
            if (!$match->hasAttribute($attrName)) {
164
                return false;
165
            }
166
        }
167
168
        return true;
169
    }
170
171
    /**
172
     * Remove the named attribute from all elements in the current DOMQuery.
173
     *
174
     * This will remove any attribute with the given name. It will do this on each
175
     * item currently wrapped by DOMQuery.
176
     *
177
     * As is the case in jQuery, this operation is not considered destructive.
178
     *
179
     * @param string $name
180
     *  Name of the parameter to remove.
181
     * @return \QueryPath\DOMQuery
182
     *  The DOMQuery object with the same elements.
183
     * @see attr()
184
     */
185
    public function removeAttr($name): Query
186
    {
187
        foreach ($this->matches as $m) {
188
            $m->removeAttribute($name);
189
        }
190
191
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type QueryPath\Helpers\QueryChecks which is incompatible with the type-hinted return QueryPath\Query.
Loading history...
192
    }
193
}