PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
← All changes | vendor/symfony/css-selector/Parser/Parser.php +133 -15 4.3.74.4.8 View file →
@@ -10,8 +10,9 @@
10 10 */
11 11
12 12 namespace Symfony\Component\CssSelector\Parser;
13 13
14 +use Symfony\Component\CssSelector\Exception\InternalErrorException;
14 15 use Symfony\Component\CssSelector\Exception\SyntaxErrorException;
15 16 use Symfony\Component\CssSelector\Node;
16 17 use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer;
17 18
@@ -26,9 +27,14 @@
26 27 * @internal
27 28 */
28 29 class Parser implements ParserInterface
29 30 {
31 + private const HAS_NESTING_LIMIT = 16;
32 + private const NESTING_LIMIT = 16;
33 +
30 34 private Tokenizer $tokenizer;
35 + private int $hasNestingDepth = 0;
36 + private int $nestingDepth = 0;
31 37
32 38 public function __construct(?Tokenizer $tokenizer = null)
33 39 {
34 40 $this->tokenizer = $tokenizer ?? new Tokenizer();
@@ -56,11 +62,11 @@
56 62 throw SyntaxErrorException::stringAsFunctionArgument();
57 63 }
58 64 }
59 65
60 - $joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens)));
66 + $joined = trim(implode('', array_map(static fn (Token $token) => $token->getValue(), $tokens)));
61 67
62 - $int = function ($string) {
68 + $int = static function ($string) {
63 69 if (!is_numeric($string)) {
64 70 throw SyntaxErrorException::stringAsFunctionArgument();
65 71 }
66 72
@@ -86,16 +92,20 @@
86 92 isset($split[1]) && $split[1] ? $int($split[1]) : 0,
87 93 ];
88 94 }
89 95
90 - private function parseSelectorList(TokenStream $stream): array
96 + private function parseSelectorList(TokenStream $stream, bool $isArgument = false): array
91 97 {
92 98 $stream->skipWhitespace();
93 99 $selectors = [];
94 100
95 101 while (true) {
96 - $selectors[] = $this->parserSelectorNode($stream);
102 + if ($isArgument && $stream->getPeek()->isDelimiter([')'])) {
103 + break;
104 + }
97 105
106 + $selectors[] = $this->parserSelectorNode($stream, $isArgument);
107 +
98 108 if ($stream->getPeek()->isDelimiter([','])) {
99 109 $stream->getNext();
100 110 $stream->skipWhitespace();
101 111 } else {
@@ -105,17 +115,21 @@
105 115
106 116 return $selectors;
107 117 }
108 118
109 - private function parserSelectorNode(TokenStream $stream): Node\SelectorNode
119 + private function parserSelectorNode(TokenStream $stream, bool $isArgument = false, bool $insideRelativeSelector = false): Node\SelectorNode
110 120 {
111 - [$result, $pseudoElement] = $this->parseSimpleSelector($stream);
121 + [$result, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument, $insideRelativeSelector);
112 122
113 123 while (true) {
114 124 $stream->skipWhitespace();
115 125 $peek = $stream->getPeek();
116 126
117 - if ($peek->isFileEnd() || $peek->isDelimiter([','])) {
127 + if (
128 + $peek->isFileEnd()
129 + || $peek->isDelimiter([','])
130 + || ($isArgument && $peek->isDelimiter([')']))
131 + ) {
118 132 break;
119 133 }
120 134
121 135 if (null !== $pseudoElement) {
@@ -128,9 +142,9 @@
128 142 } else {
129 143 $combinator = ' ';
130 144 }
131 145
132 - [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream);
146 + [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument, $insideRelativeSelector);
133 147 $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
134 148 }
135 149
136 150 return new Node\SelectorNode($result, $pseudoElement);
@@ -136,13 +150,73 @@
136 150 return new Node\SelectorNode($result, $pseudoElement);
137 151 }
138 152
139 153 /**
154 + * @return list<array{0: string, 1: Node\SelectorNode}>
155 + *
156 + * @throws SyntaxErrorException
157 + * @throws InternalErrorException
158 + */
159 + private function parseRelativeSelector(TokenStream $stream): array
160 + {
161 + if ($this->hasNestingDepth >= self::HAS_NESTING_LIMIT) {
162 + throw SyntaxErrorException::nestedHas();
163 + }
164 +
165 + ++$this->hasNestingDepth;
166 +
167 + try {
168 + $arguments = [];
169 + while (true) {
170 + $stream->skipWhitespace();
171 + $peek = $stream->getPeek();
172 +
173 + if ($peek->isDelimiter(['+', '>', '~'])) {
174 + $combinator = $stream->getNext()->getValue();
175 + $stream->skipWhitespace();
176 + $peek = $stream->getPeek();
177 + } else {
178 + $combinator = ' ';
179 + }
180 +
181 + if ($peek->isString() || $peek->isNumber()) {
182 + throw SyntaxErrorException::unexpectedToken('an argument', $stream->getNext());
183 + }
184 +
185 + $selector = $this->parserSelectorNode($stream, true, true);
186 +
187 + if (null !== $pseudoElement = $selector->getPseudoElement()) {
188 + throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'inside :has()');
189 + }
190 +
191 + $arguments[] = [$combinator, $selector];
192 +
193 + if ($stream->getPeek()->isDelimiter([','])) {
194 + $stream->getNext();
195 + continue;
196 + }
197 +
198 + break;
199 + }
200 +
201 + $next = $stream->getNext();
202 + if (!$next->isDelimiter([')'])) {
203 + throw SyntaxErrorException::unexpectedToken('")"', $next);
204 + }
205 +
206 + return $arguments;
207 + } finally {
208 + --$this->hasNestingDepth;
209 + }
210 + }
211 +
212 + /**
140 213 * Parses next simple node (hash, class, pseudo, negation).
141 214 *
142 215 * @throws SyntaxErrorException
216 + * @throws InternalErrorException
143 217 */
144 - private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false): array
218 + private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false, bool $isArgument = false, bool $insideRelativeSelector = false): array
145 219 {
146 220 $stream->skipWhitespace();
147 221
148 222 $selectorStart = \count($stream->getUsed());
@@ -153,9 +227,9 @@
153 227 $peek = $stream->getPeek();
154 228 if ($peek->isWhitespace()
155 229 || $peek->isFileEnd()
156 230 || $peek->isDelimiter([',', '+', '>', '~'])
157 - || ($insideNegation && $peek->isDelimiter([')']))
231 + || ($isArgument && $peek->isDelimiter([')']))
158 232 ) {
159 233 break;
160 234 }
161 235
@@ -181,9 +255,9 @@
181 255 continue;
182 256 }
183 257
184 258 $identifier = $stream->getNextIdentifier();
185 - if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'])) {
259 + if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'], true)) {
186 260 // Special case: CSS 2.1 pseudo-elements can have a single ':'.
187 261 // Any new pseudo-element must have two.
188 262 $pseudoElement = $identifier;
189 263
@@ -193,14 +267,18 @@
193 267 if (!$stream->getPeek()->isDelimiter(['('])) {
194 268 $result = new Node\PseudoNode($result, $identifier);
195 269 if ('Pseudo[Element[*]:scope]' === $result->__toString()) {
196 270 $used = \count($stream->getUsed());
271 + $prevSeparators = [','];
272 + if ($insideRelativeSelector) {
273 + $prevSeparators = [',', '(', '>', '+', '~'];
274 + }
197 275 if (!(2 === $used
198 276 || 3 === $used && $stream->getUsed()[0]->isWhiteSpace()
199 - || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([','])
277 + || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter($prevSeparators)
200 278 || $used >= 4
201 279 && $stream->getUsed()[$used - 3]->isWhiteSpace()
202 - && $stream->getUsed()[$used - 4]->isDelimiter([','])
280 + && $stream->getUsed()[$used - 4]->isDelimiter($prevSeparators)
203 281 )) {
204 282 throw SyntaxErrorException::notAtTheStartOfASelector('scope');
205 283 }
206 284 }
@@ -214,13 +292,13 @@
214 292 if ($insideNegation) {
215 293 throw SyntaxErrorException::nestedNot();
216 294 }
217 295
218 - [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true);
296 + [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true, true);
219 297 $next = $stream->getNext();
220 298
221 299 if (null !== $argumentPseudoElement) {
222 - throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()');
300 + throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside :not()');
223 301 }
224 302
225 303 if (!$next->isDelimiter([')'])) {
226 304 throw SyntaxErrorException::unexpectedToken('")"', $next);
@@ -226,8 +304,28 @@
226 304 throw SyntaxErrorException::unexpectedToken('")"', $next);
227 305 }
228 306
229 307 $result = new Node\NegationNode($result, $argument);
308 + } elseif ('is' === strtolower($identifier)) {
309 + $selectors = $this->parseNestedSelectorList($stream, 'is');
310 +
311 + $next = $stream->getNext();
312 + if (!$next->isDelimiter([')'])) {
313 + throw SyntaxErrorException::unexpectedToken('")"', $next);
314 + }
315 +
316 + $result = new Node\MatchingNode($result, $selectors);
317 + } elseif ('where' === strtolower($identifier)) {
318 + $selectors = $this->parseNestedSelectorList($stream, 'where');
319 +
320 + $next = $stream->getNext();
321 + if (!$next->isDelimiter([')'])) {
322 + throw SyntaxErrorException::unexpectedToken('")"', $next);
323 + }
324 +
325 + $result = new Node\SpecificityAdjustmentNode($result, $selectors);
326 + } elseif ('has' === strtolower($identifier)) {
327 + $result = new Node\RelationNode($result, $this->parseRelativeSelector($stream));
230 328 } else {
231 329 $arguments = [];
232 330 $next = null;
233 331
@@ -263,8 +361,28 @@
263 361 throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek());
264 362 }
265 363
266 364 return [$result, $pseudoElement];
365 + }
366 +
367 + /**
368 + * @return Node\SelectorNode[]
369 + *
370 + * @throws SyntaxErrorException
371 + */
372 + private function parseNestedSelectorList(TokenStream $stream, string $identifier): array
373 + {
374 + if ($this->nestingDepth >= self::NESTING_LIMIT) {
375 + throw new SyntaxErrorException(\sprintf('Got too deeply nested :%s().', $identifier));
376 + }
377 +
378 + ++$this->nestingDepth;
379 +
380 + try {
381 + return $this->parseSelectorList($stream, true);
382 + } finally {
383 + --$this->nestingDepth;
384 + }
267 385 }
268 386
269 387 private function parseElementNode(TokenStream $stream): Node\ElementNode
270 388 {