PluginProbe
WooCommerce / 11.0.0
WooCommerce v11.0.0
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 All 648 releases
woocommerce / lib / packages / Sabberworm / CSS / RuleSet / RuleSet.php
RuleSet.php
358 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Automattic\WooCommerce\Vendor\Sabberworm\CSS\RuleSet;
4
5 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Comment\Comment;
6 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Comment\Commentable;
7 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\CSSElement;
8 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\OutputFormat;
9 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Parsing\ParserState;
10 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Parsing\UnexpectedEOFException;
11 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Parsing\UnexpectedTokenException;
12 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Position\Position;
13 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Position\Positionable;
14 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Renderable;
15 use Automattic\WooCommerce\Vendor\Sabberworm\CSS\Rule\Rule;
16
17 /**
18 * This class is a container for individual 'Rule's.
19 *
20 * The most common form of a rule set is one constrained by a selector, i.e., a `DeclarationBlock`.
21 * However, unknown `AtRule`s (like `@font-face`) are rule sets as well.
22 *
23 * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
24 * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
25 */
26 abstract class RuleSet implements CSSElement, Commentable, Positionable
27 {
28 use Position;
29
30 /**
31 * the rules in this rule set, using the property name as the key,
32 * with potentially multiple rules per property name.
33 *
34 * @var array<string, array<int<0, max>, Rule>>
35 */
36 private $aRules;
37
38 /**
39 * @var array<array-key, Comment>
40 *
41 * @internal since 8.8.0
42 */
43 protected $aComments;
44
45 /**
46 * @param int $iLineNo
47 */
48 public function __construct($iLineNo = 0)
49 {
50 $this->aRules = [];
51 $this->setPosition($iLineNo);
52 $this->aComments = [];
53 }
54
55 /**
56 * @return void
57 *
58 * @throws UnexpectedTokenException
59 * @throws UnexpectedEOFException
60 *
61 * @internal since V8.8.0
62 */
63 public static function parseRuleSet(ParserState $oParserState, RuleSet $oRuleSet)
64 {
65 while ($oParserState->comes(';')) {
66 $oParserState->consume(';');
67 }
68 while (true) {
69 $commentsBeforeRule = $oParserState->consumeWhiteSpace();
70 if ($oParserState->comes('}')) {
71 break;
72 }
73 $oRule = null;
74 if ($oParserState->getSettings()->bLenientParsing) {
75 try {
76 $oRule = Rule::parse($oParserState, $commentsBeforeRule);
77 } catch (UnexpectedTokenException $e) {
78 try {
79 $sConsume = $oParserState->consumeUntil(["\n", ";", '}'], true);
80 // We need to “unfind” the matches to the end of the ruleSet as this will be matched later
81 if ($oParserState->streql(substr($sConsume, -1), '}')) {
82 $oParserState->backtrack(1);
83 } else {
84 while ($oParserState->comes(';')) {
85 $oParserState->consume(';');
86 }
87 }
88 } catch (UnexpectedTokenException $e) {
89 // We’ve reached the end of the document. Just close the RuleSet.
90 return;
91 }
92 }
93 } else {
94 $oRule = Rule::parse($oParserState, $commentsBeforeRule);
95 }
96 if ($oRule) {
97 $oRuleSet->addRule($oRule);
98 }
99 }
100 $oParserState->consume('}');
101 }
102
103 /**
104 * @param Rule|null $oSibling
105 *
106 * @return void
107 */
108 public function addRule(Rule $oRule, $oSibling = null)
109 {
110 $sRule = $oRule->getRule();
111 if (!isset($this->aRules[$sRule])) {
112 $this->aRules[$sRule] = [];
113 }
114
115 $iPosition = count($this->aRules[$sRule]);
116
117 if ($oSibling !== null) {
118 $iSiblingPos = array_search($oSibling, $this->aRules[$sRule], true);
119 if ($iSiblingPos !== false) {
120 $iPosition = $iSiblingPos;
121 $oRule->setPosition($oSibling->getLineNo(), $oSibling->getColNo() - 1);
122 }
123 }
124 if ($oRule->getLineNumber() === null) {
125 //this node is added manually, give it the next best line
126 $columnNumber = $oRule->getColNo();
127 $rules = $this->getRules();
128 $pos = count($rules);
129 if ($pos > 0) {
130 $last = $rules[$pos - 1];
131 $oRule->setPosition($last->getLineNo() + 1, $columnNumber);
132 } else {
133 $oRule->setPosition(1, $columnNumber);
134 }
135 } elseif ($oRule->getColumnNumber() === null) {
136 $oRule->setPosition($oRule->getLineNumber(), 0);
137 }
138
139 array_splice($this->aRules[$sRule], $iPosition, 0, [$oRule]);
140 }
141
142 /**
143 * Returns all rules matching the given rule name
144 *
145 * @example $oRuleSet->getRules('font') // returns array(0 => $oRule, …) or array().
146 *
147 * @example $oRuleSet->getRules('font-')
148 * //returns an array of all rules either beginning with font- or matching font.
149 *
150 * @param Rule|string|null $mRule
151 * Pattern to search for. If null, returns all rules.
152 * If the pattern ends with a dash, all rules starting with the pattern are returned
153 * as well as one matching the pattern with the dash excluded.
154 * Passing a `Rule` for this parameter is deprecated in version 8.9.0, and will not work from v9.0.
155 * Call `getRules($rule->getRule())` instead.
156 *
157 * @return array<int, Rule>
158 */
159 public function getRules($mRule = null)
160 {
161 if ($mRule instanceof Rule) {
162 $mRule = $mRule->getRule();
163 }
164 /** @var array<int, Rule> $aResult */
165 $aResult = [];
166 foreach ($this->aRules as $sName => $aRules) {
167 // Either no search rule is given or the search rule matches the found rule exactly
168 // or the search rule ends in “-” and the found rule starts with the search rule.
169 if (
170 !$mRule || $sName === $mRule
171 || (
172 strrpos($mRule, '-') === strlen($mRule) - strlen('-')
173 && (strpos($sName, $mRule) === 0 || $sName === substr($mRule, 0, -1))
174 )
175 ) {
176 $aResult = array_merge($aResult, $aRules);
177 }
178 }
179 usort($aResult, function (Rule $first, Rule $second) {
180 if ($first->getLineNo() === $second->getLineNo()) {
181 return $first->getColNo() - $second->getColNo();
182 }
183 return $first->getLineNo() - $second->getLineNo();
184 });
185 return $aResult;
186 }
187
188 /**
189 * Overrides all the rules of this set.
190 *
191 * @param array<array-key, Rule> $aRules The rules to override with.
192 *
193 * @return void
194 */
195 public function setRules(array $aRules)
196 {
197 $this->aRules = [];
198 foreach ($aRules as $rule) {
199 $this->addRule($rule);
200 }
201 }
202
203 /**
204 * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name
205 * as keys. This method exists mainly for backwards-compatibility and is really only partially useful.
206 *
207 * Note: This method loses some information: Calling this (with an argument of `background-`) on a declaration block
208 * like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array
209 * containing the rgba-valued rule while `getRules()` would yield an indexed array containing both.
210 *
211 * @param Rule|string|null $mRule $mRule
212 * Pattern to search for. If null, returns all rules. If the pattern ends with a dash,
213 * all rules starting with the pattern are returned as well as one matching the pattern with the dash
214 * excluded.
215 * Passing a `Rule` for this parameter is deprecated in version 8.9.0, and will not work from v9.0.
216 * Call `getRulesAssoc($rule->getRule())` instead.
217 *
218 * @return array<string, Rule>
219 */
220 public function getRulesAssoc($mRule = null)
221 {
222 /** @var array<string, Rule> $aResult */
223 $aResult = [];
224 foreach ($this->getRules($mRule) as $oRule) {
225 $aResult[$oRule->getRule()] = $oRule;
226 }
227 return $aResult;
228 }
229
230 /**
231 * Removes a `Rule` from this `RuleSet` by identity.
232 *
233 * @param Rule|string|null $mRule
234 * `Rule` to remove.
235 * Passing a `string` or `null` is deprecated in version 8.9.0, and will no longer work from v9.0.
236 * Use `removeMatchingRules()` or `removeAllRules()` instead.
237 */
238 public function removeRule($mRule)
239 {
240 if ($mRule instanceof Rule) {
241 $sRule = $mRule->getRule();
242 if (!isset($this->aRules[$sRule])) {
243 return;
244 }
245 foreach ($this->aRules[$sRule] as $iKey => $oRule) {
246 if ($oRule === $mRule) {
247 unset($this->aRules[$sRule][$iKey]);
248 }
249 }
250 } elseif ($mRule !== null) {
251 $this->removeMatchingRules($mRule);
252 } else {
253 $this->removeAllRules();
254 }
255 }
256
257 /**
258 * Removes rules by property name or search pattern.
259 *
260 * @param string $searchPattern
261 * pattern to remove.
262 * If the pattern ends in a dash,
263 * all rules starting with the pattern are removed as well as one matching the pattern with the dash
264 * excluded.
265 */
266 public function removeMatchingRules($searchPattern)
267 {
268 foreach ($this->aRules as $propertyName => $rules) {
269 // Either the search rule matches the found rule exactly
270 // or the search rule ends in “-” and the found rule starts with the search rule or equals it
271 // (without the trailing dash).
272 if (
273 $propertyName === $searchPattern
274 || (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-')
275 && (\strpos($propertyName, $searchPattern) === 0
276 || $propertyName === \substr($searchPattern, 0, -1)))
277 ) {
278 unset($this->aRules[$propertyName]);
279 }
280 }
281 }
282
283 public function removeAllRules()
284 {
285 $this->aRules = [];
286 }
287
288 /**
289 * @return string
290 *
291 * @deprecated in V8.8.0, will be removed in V9.0.0. Use `render` instead.
292 */
293 public function __toString()
294 {
295 return $this->render(new OutputFormat());
296 }
297
298 /**
299 * @return string
300 */
301 protected function renderRules(OutputFormat $oOutputFormat)
302 {
303 $sResult = '';
304 $bIsFirst = true;
305 $oNextLevel = $oOutputFormat->nextLevel();
306 foreach ($this->getRules() as $oRule) {
307 $sRendered = $oNextLevel->safely(function () use ($oRule, $oNextLevel) {
308 return $oRule->render($oNextLevel);
309 });
310 if ($sRendered === null) {
311 continue;
312 }
313 if ($bIsFirst) {
314 $bIsFirst = false;
315 $sResult .= $oNextLevel->spaceBeforeRules();
316 } else {
317 $sResult .= $oNextLevel->spaceBetweenRules();
318 }
319 $sResult .= $sRendered;
320 }
321
322 if (!$bIsFirst) {
323 // Had some output
324 $sResult .= $oOutputFormat->spaceAfterRules();
325 }
326
327 return $oOutputFormat->removeLastSemicolon($sResult);
328 }
329
330 /**
331 * @param array<string, Comment> $aComments
332 *
333 * @return void
334 */
335 public function addComments(array $aComments)
336 {
337 $this->aComments = array_merge($this->aComments, $aComments);
338 }
339
340 /**
341 * @return array<string, Comment>
342 */
343 public function getComments()
344 {
345 return $this->aComments;
346 }
347
348 /**
349 * @param array<string, Comment> $aComments
350 *
351 * @return void
352 */
353 public function setComments(array $aComments)
354 {
355 $this->aComments = $aComments;
356 }
357 }
358