PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.1
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Libraries/Sabberworm/CSS/src/RuleSet/RuleSet.php +0 -375 11.0.2 → 11.1.1 View file →
@@ -1,375 +1,0 @@
1 -<?php
2 -
3 -declare(strict_types=1);
4 -
5 -namespace AcyMailing\Libraries\Sabberworm\CSS\RuleSet;
6 -
7 -use AcyMailing\Libraries\Sabberworm\CSS\Comment\CommentContainer;
8 -use AcyMailing\Libraries\Sabberworm\CSS\CSSElement;
9 -use AcyMailing\Libraries\Sabberworm\CSS\CSSList\CSSListItem;
10 -use AcyMailing\Libraries\Sabberworm\CSS\OutputFormat;
11 -use AcyMailing\Libraries\Sabberworm\CSS\Parsing\ParserState;
12 -use AcyMailing\Libraries\Sabberworm\CSS\Parsing\UnexpectedEOFException;
13 -use AcyMailing\Libraries\Sabberworm\CSS\Parsing\UnexpectedTokenException;
14 -use AcyMailing\Libraries\Sabberworm\CSS\Position\Position;
15 -use AcyMailing\Libraries\Sabberworm\CSS\Position\Positionable;
16 -use AcyMailing\Libraries\Sabberworm\CSS\Rule\Rule;
17 -
18 -/**
19 - * This class is a container for individual 'Rule's.
20 - *
21 - * The most common form of a rule set is one constrained by a selector, i.e., a `DeclarationBlock`.
22 - * However, unknown `AtRule`s (like `@font-face`) are rule sets as well.
23 - *
24 - * If you want to manipulate a `RuleSet`, use the methods `addRule(Rule $rule)`, `getRules()` and `removeRule($rule)`
25 - * (which accepts either a `Rule` or a rule name; optionally suffixed by a dash to remove all related rules).
26 - *
27 - * Note that `CSSListItem` extends both `Commentable` and `Renderable`, so those interfaces must also be implemented.
28 - */
29 -class RuleSet implements CSSElement, CSSListItem, Positionable, RuleContainer
30 -{
31 - use CommentContainer;
32 - use Position;
33 -
34 - /**
35 - * the rules in this rule set, using the property name as the key,
36 - * with potentially multiple rules per property name.
37 - *
38 - * @var array<string, array<int<0, max>, Rule>>
39 - */
40 - private $rules = [];
41 -
42 - /**
43 - * @param int<1, max>|null $lineNumber
44 - */
45 - public function __construct(?int $lineNumber = null)
46 - {
47 - $this->setPosition($lineNumber);
48 - }
49 -
50 - /**
51 - * @throws UnexpectedTokenException
52 - * @throws UnexpectedEOFException
53 - *
54 - * @internal since V8.8.0
55 - */
56 - public static function parseRuleSet(ParserState $parserState, RuleSet $ruleSet): void
57 - {
58 - while ($parserState->comes(';')) {
59 - $parserState->consume(';');
60 - }
61 - while (true) {
62 - $commentsBeforeRule = $parserState->consumeWhiteSpace();
63 - if ($parserState->comes('}')) {
64 - break;
65 - }
66 - $rule = null;
67 - if ($parserState->getSettings()->usesLenientParsing()) {
68 - try {
69 - $rule = Rule::parse($parserState, $commentsBeforeRule);
70 - } catch (UnexpectedTokenException $e) {
71 - try {
72 - $consumedText = $parserState->consumeUntil(["\n", ';', '}'], true);
73 - // We need to “unfind” the matches to the end of the ruleSet as this will be matched later
74 - if ($parserState->streql(\substr($consumedText, -1), '}')) {
75 - $parserState->backtrack(1);
76 - } else {
77 - while ($parserState->comes(';')) {
78 - $parserState->consume(';');
79 - }
80 - }
81 - } catch (UnexpectedTokenException $e) {
82 - // We’ve reached the end of the document. Just close the RuleSet.
83 - return;
84 - }
85 - }
86 - } else {
87 - $rule = Rule::parse($parserState, $commentsBeforeRule);
88 - }
89 - if ($rule instanceof Rule) {
90 - $ruleSet->addRule($rule);
91 - }
92 - }
93 - $parserState->consume('}');
94 - }
95 -
96 - /**
97 - * @throws \UnexpectedValueException
98 - * if the last `Rule` is needed as a basis for setting position, but does not have a valid position,
99 - * which should never happen
100 - */
101 - public function addRule(Rule $ruleToAdd, ?Rule $sibling = null): void
102 - {
103 - $propertyName = $ruleToAdd->getRule();
104 - if (!isset($this->rules[$propertyName])) {
105 - $this->rules[$propertyName] = [];
106 - }
107 -
108 - $position = \count($this->rules[$propertyName]);
109 -
110 - if ($sibling !== null) {
111 - $siblingIsInSet = false;
112 - $siblingPosition = \array_search($sibling, $this->rules[$propertyName], true);
113 - if ($siblingPosition !== false) {
114 - $siblingIsInSet = true;
115 - $position = $siblingPosition;
116 - } else {
117 - $siblingIsInSet = $this->hasRule($sibling);
118 - if ($siblingIsInSet) {
119 - // Maintain ordering within `$this->rules[$propertyName]`
120 - // by inserting before first `Rule` with a same-or-later position than the sibling.
121 - foreach ($this->rules[$propertyName] as $index => $rule) {
122 - if (self::comparePositionable($rule, $sibling) >= 0) {
123 - $position = $index;
124 - break;
125 - }
126 - }
127 - }
128 - }
129 - if ($siblingIsInSet) {
130 - // Increment column number of all existing rules on same line, starting at sibling
131 - $siblingLineNumber = $sibling->getLineNumber();
132 - $siblingColumnNumber = $sibling->getColumnNumber();
133 - foreach ($this->rules as $rulesForAProperty) {
134 - foreach ($rulesForAProperty as $rule) {
135 - if (
136 - $rule->getLineNumber() === $siblingLineNumber &&
137 - $rule->getColumnNumber() >= $siblingColumnNumber
138 - ) {
139 - $rule->setPosition($siblingLineNumber, $rule->getColumnNumber() + 1);
140 - }
141 - }
142 - }
143 - $ruleToAdd->setPosition($siblingLineNumber, $siblingColumnNumber);
144 - }
145 - }
146 -
147 - if ($ruleToAdd->getLineNumber() === null) {
148 - //this node is added manually, give it the next best line
149 - $columnNumber = $ruleToAdd->getColumnNumber() ?? 0;
150 - $rules = $this->getRules();
151 - $rulesCount = \count($rules);
152 - if ($rulesCount > 0) {
153 - $last = $rules[$rulesCount - 1];
154 - $lastsLineNumber = $last->getLineNumber();
155 - if (!\is_int($lastsLineNumber)) {
156 - throw new \UnexpectedValueException(
157 - 'A Rule without a line number was found during addRule',
158 - 1750718399
159 - );
160 - }
161 - $ruleToAdd->setPosition($lastsLineNumber + 1, $columnNumber);
162 - } else {
163 - $ruleToAdd->setPosition(1, $columnNumber);
164 - }
165 - } elseif ($ruleToAdd->getColumnNumber() === null) {
166 - $ruleToAdd->setPosition($ruleToAdd->getLineNumber(), 0);
167 - }
168 -
169 - \array_splice($this->rules[$propertyName], $position, 0, [$ruleToAdd]);
170 - }
171 -
172 - /**
173 - * Returns all rules matching the given rule name
174 - *
175 - * @example $ruleSet->getRules('font') // returns array(0 => $rule, …) or array().
176 - *
177 - * @example $ruleSet->getRules('font-')
178 - * //returns an array of all rules either beginning with font- or matching font.
179 - *
180 - * @param string|null $searchPattern
181 - * Pattern to search for. If null, returns all rules.
182 - * If the pattern ends with a dash, all rules starting with the pattern are returned
183 - * as well as one matching the pattern with the dash excluded.
184 - *
185 - * @return array<int<0, max>, Rule>
186 - */
187 - public function getRules(?string $searchPattern = null): array
188 - {
189 - $result = [];
190 - foreach ($this->rules as $propertyName => $rules) {
191 - // Either no search rule is given or the search rule matches the found rule exactly
192 - // or the search rule ends in “-” and the found rule starts with the search rule.
193 - if (
194 - $searchPattern === null || $propertyName === $searchPattern
195 - || (
196 - \strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-')
197 - && (\strpos($propertyName, $searchPattern) === 0
198 - || $propertyName === \substr($searchPattern, 0, -1))
199 - )
200 - ) {
201 - $result = \array_merge($result, $rules);
202 - }
203 - }
204 - \usort($result, [self::class, 'comparePositionable']);
205 -
206 - return $result;
207 - }
208 -
209 - /**
210 - * Overrides all the rules of this set.
211 - *
212 - * @param array<Rule> $rules The rules to override with.
213 - */
214 - public function setRules(array $rules): void
215 - {
216 - $this->rules = [];
217 - foreach ($rules as $rule) {
218 - $this->addRule($rule);
219 - }
220 - }
221 -
222 - /**
223 - * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name
224 - * as keys. This method exists mainly for backwards-compatibility and is really only partially useful.
225 - *
226 - * Note: This method loses some information: Calling this (with an argument of `background-`) on a declaration block
227 - * like `{ background-color: green; background-color; rgba(0, 127, 0, 0.7); }` will only yield an associative array
228 - * containing the rgba-valued rule while `getRules()` would yield an indexed array containing both.
229 - *
230 - * @param string|null $searchPattern
231 - * Pattern to search for. If null, returns all rules. If the pattern ends with a dash,
232 - * all rules starting with the pattern are returned as well as one matching the pattern with the dash
233 - * excluded.
234 - *
235 - * @return array<string, Rule>
236 - */
237 - public function getRulesAssoc(?string $searchPattern = null): array
238 - {
239 - /** @var array<string, Rule> $result */
240 - $result = [];
241 - foreach ($this->getRules($searchPattern) as $rule) {
242 - $result[$rule->getRule()] = $rule;
243 - }
244 -
245 - return $result;
246 - }
247 -
248 - /**
249 - * Removes a `Rule` from this `RuleSet` by identity.
250 - */
251 - public function removeRule(Rule $ruleToRemove): void
252 - {
253 - $nameOfPropertyToRemove = $ruleToRemove->getRule();
254 - if (!isset($this->rules[$nameOfPropertyToRemove])) {
255 - return;
256 - }
257 - foreach ($this->rules[$nameOfPropertyToRemove] as $key => $rule) {
258 - if ($rule === $ruleToRemove) {
259 - unset($this->rules[$nameOfPropertyToRemove][$key]);
260 - }
261 - }
262 - }
263 -
264 - /**
265 - * Removes rules by property name or search pattern.
266 - *
267 - * @param string $searchPattern
268 - * pattern to remove.
269 - * If the pattern ends in a dash,
270 - * all rules starting with the pattern are removed as well as one matching the pattern with the dash
271 - * excluded.
272 - */
273 - public function removeMatchingRules(string $searchPattern): void
274 - {
275 - foreach ($this->rules as $propertyName => $rules) {
276 - // Either the search rule matches the found rule exactly
277 - // or the search rule ends in “-” and the found rule starts with the search rule or equals it
278 - // (without the trailing dash).
279 - if (
280 - $propertyName === $searchPattern
281 - || (\strrpos($searchPattern, '-') === \strlen($searchPattern) - \strlen('-')
282 - && (\strpos($propertyName, $searchPattern) === 0
283 - || $propertyName === \substr($searchPattern, 0, -1)))
284 - ) {
285 - unset($this->rules[$propertyName]);
286 - }
287 - }
288 - }
289 -
290 - public function removeAllRules(): void
291 - {
292 - $this->rules = [];
293 - }
294 -
295 - /**
296 - * @internal
297 - */
298 - public function render(OutputFormat $outputFormat): string
299 - {
300 - return $this->renderRules($outputFormat);
301 - }
302 -
303 - protected function renderRules(OutputFormat $outputFormat): string
304 - {
305 - $result = '';
306 - $isFirst = true;
307 - $nextLevelFormat = $outputFormat->nextLevel();
308 - foreach ($this->getRules() as $rule) {
309 - $nextLevelFormatter = $nextLevelFormat->getFormatter();
310 - $renderedRule = $nextLevelFormatter->safely(static function () use ($rule, $nextLevelFormat): string {
311 - return $rule->render($nextLevelFormat);
312 - });
313 - if ($renderedRule === null) {
314 - continue;
315 - }
316 - if ($isFirst) {
317 - $isFirst = false;
318 - $result .= $nextLevelFormatter->spaceBeforeRules();
319 - } else {
320 - $result .= $nextLevelFormatter->spaceBetweenRules();
321 - }
322 - $result .= $renderedRule;
323 - }
324 -
325 - $formatter = $outputFormat->getFormatter();
326 - if (!$isFirst) {
327 - // Had some output
328 - $result .= $formatter->spaceAfterRules();
329 - }
330 -
331 - return $formatter->removeLastSemicolon($result);
332 - }
333 -
334 - /**
335 - * @return int negative if `$first` is before `$second`; zero if they have the same position; positive otherwise
336 - *
337 - * @throws \UnexpectedValueException if either argument does not have a valid position, which should never happen
338 - */
339 - private static function comparePositionable(Positionable $first, Positionable $second): int
340 - {
341 - $firstsLineNumber = $first->getLineNumber();
342 - $secondsLineNumber = $second->getLineNumber();
343 - if (!\is_int($firstsLineNumber) || !\is_int($secondsLineNumber)) {
344 - throw new \UnexpectedValueException(
345 - 'A Rule without a line number was passed to comparePositionable',
346 - 1750637683
347 - );
348 - }
349 -
350 - if ($firstsLineNumber === $secondsLineNumber) {
351 - $firstsColumnNumber = $first->getColumnNumber();
352 - $secondsColumnNumber = $second->getColumnNumber();
353 - if (!\is_int($firstsColumnNumber) || !\is_int($secondsColumnNumber)) {
354 - throw new \UnexpectedValueException(
355 - 'A Rule without a column number was passed to comparePositionable',
356 - 1750637761
357 - );
358 - }
359 - return $firstsColumnNumber - $secondsColumnNumber;
360 - }
361 -
362 - return $firstsLineNumber - $secondsLineNumber;
363 - }
364 -
365 - private function hasRule(Rule $rule): bool
366 - {
367 - foreach ($this->rules as $rulesForAProperty) {
368 - if (\in_array($rule, $rulesForAProperty, true)) {
369 - return true;
370 - }
371 - }
372 -
373 - return false;
374 - }
375 -}