These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace voku\CssToInlineStyles; |
||
4 | |||
5 | use Symfony\Component\CssSelector\CssSelectorConverter; |
||
6 | use Symfony\Component\CssSelector\Exception\ExceptionInterface; |
||
7 | use voku\helper\HtmlDomParser; |
||
8 | |||
9 | /** |
||
10 | * CSS to Inline Styles class |
||
11 | * |
||
12 | * @author Tijs Verkoyen <[email protected]> |
||
13 | */ |
||
14 | class CssToInlineStyles |
||
15 | { |
||
16 | |||
17 | /** |
||
18 | * regular expression: css media queries |
||
19 | * |
||
20 | * @var string |
||
21 | */ |
||
22 | private static $cssMediaQueriesRegEx = '#@media\\s+(?:only\\s)?(?:[\\s{\\(]|screen|all)\\s?[^{]+{.*}\\s*}\\s*#misU'; |
||
23 | |||
24 | /** |
||
25 | * regular expression: css charset |
||
26 | * |
||
27 | * @var string |
||
28 | */ |
||
29 | private static $cssCharsetRegEx = '/@charset [\'"][^\'"]+[\'"];/i'; |
||
30 | |||
31 | /** |
||
32 | * regular expression: conditional inline style tags |
||
33 | * |
||
34 | * @var string |
||
35 | */ |
||
36 | private static $excludeConditionalInlineStylesBlockRegEx = '/<!--.*<style.*-->/isU'; |
||
37 | |||
38 | /** |
||
39 | * regular expression: inline style tags |
||
40 | * |
||
41 | * @var string |
||
42 | */ |
||
43 | private static $styleTagRegEx = '|<style(.*)>(.*)</style>|isU'; |
||
44 | |||
45 | /** |
||
46 | * regular expression: css-comments |
||
47 | * |
||
48 | * @var string |
||
49 | */ |
||
50 | private static $styleCommentRegEx = '/\\/\\*.*\\*\\//sU'; |
||
51 | |||
52 | /** |
||
53 | * The CSS to use |
||
54 | * |
||
55 | * @var string |
||
56 | */ |
||
57 | private $css; |
||
58 | |||
59 | /** |
||
60 | * Should the generated HTML be cleaned |
||
61 | * |
||
62 | * @var bool |
||
63 | */ |
||
64 | private $cleanup = false; |
||
65 | |||
66 | /** |
||
67 | * The encoding to use. |
||
68 | * |
||
69 | * @var string |
||
70 | */ |
||
71 | private $encoding = 'UTF-8'; |
||
72 | |||
73 | /** |
||
74 | * The HTML to process |
||
75 | * |
||
76 | * @var string |
||
77 | */ |
||
78 | private $html; |
||
79 | |||
80 | /** |
||
81 | * Use inline-styles block as CSS |
||
82 | * |
||
83 | * @var bool |
||
84 | */ |
||
85 | private $useInlineStylesBlock = false; |
||
86 | |||
87 | /** |
||
88 | * Use link block reference as CSS |
||
89 | * |
||
90 | * @var bool |
||
91 | */ |
||
92 | private $loadCSSFromHTML = false; |
||
93 | |||
94 | /** |
||
95 | * Strip original style tags |
||
96 | * |
||
97 | * @var bool |
||
98 | */ |
||
99 | private $stripOriginalStyleTags = false; |
||
100 | |||
101 | /** |
||
102 | * Exclude conditional inline-style blocks |
||
103 | * |
||
104 | * @var bool |
||
105 | */ |
||
106 | private $excludeConditionalInlineStylesBlock = true; |
||
107 | |||
108 | /** |
||
109 | * Exclude media queries from "$this->css" and keep media queries for inline-styles blocks |
||
110 | * |
||
111 | * @var bool |
||
112 | */ |
||
113 | private $excludeMediaQueries = true; |
||
114 | |||
115 | /** |
||
116 | * Exclude media queries from "$this->css" and keep media queries for inline-styles blocks |
||
117 | * |
||
118 | * @var bool |
||
119 | */ |
||
120 | private $excludeCssCharset = true; |
||
121 | |||
122 | /** |
||
123 | * Creates an instance, you could set the HTML and CSS here, or load it |
||
124 | * later. |
||
125 | * |
||
126 | * @param null|string $html The HTML to process. |
||
127 | * @param null|string $css The CSS to use. |
||
128 | */ |
||
129 | 48 | public function __construct($html = null, $css = null) |
|
130 | { |
||
131 | 48 | if (null !== $html) { |
|
132 | 2 | $this->setHTML($html); |
|
133 | } |
||
134 | |||
135 | 48 | if (null !== $css) { |
|
136 | 2 | $this->setCSS($css); |
|
137 | } |
||
138 | 48 | } |
|
139 | |||
140 | /** |
||
141 | * Set HTML to process |
||
142 | * |
||
143 | * @param string $html The HTML to process. |
||
144 | */ |
||
145 | 46 | public function setHTML($html) |
|
146 | { |
||
147 | // strip style definitions, if we use css-class "cleanup" on a style-element |
||
148 | 46 | $this->html = (string)preg_replace('/<style[^>]+class="cleanup"[^>]*>.*<\/style>/Usi', ' ', $html); |
|
149 | 46 | } |
|
150 | |||
151 | /** |
||
152 | * Set CSS to use |
||
153 | * |
||
154 | * @param string $css The CSS to use. |
||
155 | */ |
||
156 | 44 | public function setCSS($css) |
|
157 | { |
||
158 | 44 | $this->css = (string)$css; |
|
159 | 44 | } |
|
160 | |||
161 | /** |
||
162 | * Sort an array on the specificity element |
||
163 | * |
||
164 | * @return int |
||
165 | * |
||
166 | * @param Specificity[] $e1 The first element. |
||
167 | * @param Specificity[] $e2 The second element. |
||
168 | */ |
||
169 | 17 | private static function sortOnSpecificity($e1, $e2) |
|
170 | { |
||
171 | // Compare the specificity |
||
172 | 17 | $value = $e1['specificity']->compareTo($e2['specificity']); |
|
173 | |||
174 | // if the specificity is the same, use the order in which the element appeared |
||
175 | 17 | if (0 === $value) { |
|
176 | 12 | $value = $e1['order'] - $e2['order']; |
|
177 | } |
||
178 | |||
179 | 17 | return $value; |
|
180 | } |
||
181 | |||
182 | /** |
||
183 | * Converts the loaded HTML into an HTML-string with inline styles based on the loaded CSS |
||
184 | * |
||
185 | * @param bool $outputXHTML [optional] Should we output valid XHTML? |
||
186 | * @param int $libXMLOptions [optional] $libXMLOptions Since PHP 5.4.0 and Libxml 2.6.0, |
||
187 | * you may also use the options parameter to specify additional |
||
188 | * Libxml parameters. Recommend these options: |
||
189 | * LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD |
||
190 | * @param bool $path [optional] Set the path to your external css-files. |
||
191 | * |
||
192 | * @return string |
||
193 | * |
||
194 | * @throws Exception |
||
195 | */ |
||
196 | 46 | public function convert($outputXHTML = false, $libXMLOptions = 0, $path = false) |
|
0 ignored issues
–
show
|
|||
197 | { |
||
198 | // redefine |
||
199 | 46 | $outputXHTML = (bool)$outputXHTML; |
|
200 | |||
201 | // validate |
||
202 | 46 | if (!$this->html) { |
|
203 | 1 | throw new Exception('No HTML provided.'); |
|
204 | } |
||
205 | |||
206 | // use local variables |
||
207 | 45 | $css = $this->css; |
|
208 | |||
209 | // create new HtmlDomParser |
||
210 | 45 | $dom = HtmlDomParser::str_get_html($this->html); |
|
0 ignored issues
–
show
The call to
HtmlDomParser::str_get_html() has too many arguments starting with $this->html .
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. In this case you can add the
Loading history...
|
|||
211 | |||
212 | // check if there is some link css reference |
||
213 | 45 | if ($this->loadCSSFromHTML) { |
|
214 | 1 | foreach ($dom->find('link') as $node) { |
|
215 | |||
216 | /** @noinspection PhpUndefinedMethodInspection */ |
||
217 | 1 | $file = ($path ?: __DIR__) . '/' . $node->getAttribute('href'); |
|
218 | |||
219 | 1 | if (file_exists($file)) { |
|
220 | 1 | $css .= file_get_contents($file); |
|
221 | |||
222 | // converting to inline css because we don't need/want to load css files, so remove the link |
||
223 | 1 | $node->outertext = ''; |
|
224 | } |
||
225 | } |
||
226 | } |
||
227 | |||
228 | // should we use inline style-block |
||
229 | 45 | if ($this->useInlineStylesBlock) { |
|
230 | |||
231 | 27 | if (true === $this->excludeConditionalInlineStylesBlock) { |
|
232 | 23 | $this->html = preg_replace(self::$excludeConditionalInlineStylesBlockRegEx, '', $this->html); |
|
233 | } |
||
234 | |||
235 | 27 | $css .= $this->getCssFromInlineHtmlStyleBlock($this->html); |
|
236 | } |
||
237 | |||
238 | // process css |
||
239 | 45 | $cssRules = $this->processCSS($css); |
|
240 | |||
241 | // create new XPath |
||
242 | 45 | $xPath = $this->createXPath($dom->getDocument(), $cssRules); |
|
243 | |||
244 | // strip original style tags if we need to |
||
245 | 45 | if ($this->stripOriginalStyleTags === true) { |
|
246 | 13 | $this->stripOriginalStyleTags($xPath); |
|
247 | } |
||
248 | |||
249 | // cleanup the HTML if we need to |
||
250 | 45 | if (true === $this->cleanup) { |
|
251 | 3 | $this->cleanupHTML($xPath); |
|
252 | } |
||
253 | |||
254 | // should we output XHTML? |
||
255 | 45 | if (true === $outputXHTML) { |
|
256 | 6 | return $dom->xml(); |
|
257 | } |
||
258 | |||
259 | // just regular HTML 4.01 as it should be used in newsletters |
||
260 | 40 | return $dom->html(); |
|
261 | } |
||
262 | |||
263 | /** |
||
264 | * get css from inline-html style-block |
||
265 | * |
||
266 | * @param string $html |
||
267 | * |
||
268 | * @return string |
||
269 | */ |
||
270 | 29 | public function getCssFromInlineHtmlStyleBlock($html) |
|
271 | { |
||
272 | // init var |
||
273 | 29 | $css = ''; |
|
274 | 29 | $matches = array(); |
|
275 | |||
276 | // match the style blocks |
||
277 | 29 | preg_match_all(self::$styleTagRegEx, $html, $matches); |
|
278 | |||
279 | // any style-blocks found? |
||
280 | 29 | if (!empty($matches[2])) { |
|
281 | // add |
||
282 | 28 | foreach ($matches[2] as $match) { |
|
283 | 28 | $css .= trim($match) . "\n"; |
|
284 | } |
||
285 | } |
||
286 | |||
287 | 29 | return $css; |
|
288 | } |
||
289 | |||
290 | /** |
||
291 | * Process the loaded CSS |
||
292 | * |
||
293 | * @param $css |
||
294 | * |
||
295 | * @return array |
||
296 | */ |
||
297 | 45 | private function processCSS($css) |
|
298 | { |
||
299 | //reset current set of rules |
||
300 | 45 | $cssRules = array(); |
|
301 | |||
302 | // init vars |
||
303 | 45 | $css = (string)$css; |
|
304 | |||
305 | 45 | $css = $this->doCleanup($css); |
|
306 | |||
307 | // rules are splitted by } |
||
308 | 45 | $rules = (array)explode('}', $css); |
|
309 | |||
310 | // init var |
||
311 | 45 | $i = 1; |
|
312 | |||
313 | // loop rules |
||
314 | 45 | foreach ($rules as $rule) { |
|
315 | // split into chunks |
||
316 | 45 | $chunks = explode('{', $rule); |
|
317 | |||
318 | // invalid rule? |
||
319 | 45 | if (!isset($chunks[1])) { |
|
320 | 45 | continue; |
|
321 | } |
||
322 | |||
323 | // set the selectors |
||
324 | 34 | $selectors = trim($chunks[0]); |
|
325 | |||
326 | // get cssProperties |
||
327 | 34 | $cssProperties = trim($chunks[1]); |
|
328 | |||
329 | // split multiple selectors |
||
330 | 34 | $selectors = (array)explode(',', $selectors); |
|
331 | |||
332 | // loop selectors |
||
333 | 34 | foreach ($selectors as $selector) { |
|
334 | // cleanup |
||
335 | 34 | $selector = trim($selector); |
|
336 | |||
337 | // build an array for each selector |
||
338 | 34 | $ruleSet = array(); |
|
339 | |||
340 | // store selector |
||
341 | 34 | $ruleSet['selector'] = $selector; |
|
342 | |||
343 | // process the properties |
||
344 | 34 | $ruleSet['properties'] = $this->processCSSProperties($cssProperties); |
|
345 | |||
346 | |||
347 | // calculate specificity |
||
348 | 34 | $ruleSet['specificity'] = Specificity::fromSelector($selector); |
|
349 | |||
350 | // remember the order in which the rules appear |
||
351 | 34 | $ruleSet['order'] = $i; |
|
352 | |||
353 | // add into rules |
||
354 | 34 | $cssRules[] = $ruleSet; |
|
355 | |||
356 | // increment |
||
357 | 34 | $i++; |
|
358 | } |
||
359 | } |
||
360 | |||
361 | // sort based on specificity |
||
362 | 45 | if (0 !== count($cssRules)) { |
|
363 | 34 | usort($cssRules, array(__CLASS__, 'sortOnSpecificity')); |
|
364 | } |
||
365 | |||
366 | 45 | return $cssRules; |
|
367 | } |
||
368 | |||
369 | /** |
||
370 | * @param string $css |
||
371 | * |
||
372 | * @return string |
||
373 | */ |
||
374 | 45 | private function doCleanup($css) |
|
375 | { |
||
376 | // remove newlines & replace double quotes by single quotes |
||
377 | 45 | $css = str_replace( |
|
378 | 45 | array("\r", "\n", '"'), |
|
379 | 45 | array('', '', '\''), |
|
380 | $css |
||
381 | ); |
||
382 | |||
383 | // remove comments |
||
384 | 45 | $css = preg_replace(self::$styleCommentRegEx, '', $css); |
|
385 | |||
386 | // remove spaces |
||
387 | 45 | $css = preg_replace('/\s\s+/', ' ', $css); |
|
388 | |||
389 | // remove css charset |
||
390 | 45 | if (true === $this->excludeCssCharset) { |
|
391 | 45 | $css = $this->stripeCharsetInCss($css); |
|
392 | } |
||
393 | |||
394 | // remove css media queries |
||
395 | 45 | if (true === $this->excludeMediaQueries) { |
|
396 | 44 | $css = $this->stripeMediaQueries($css); |
|
397 | } |
||
398 | |||
399 | 45 | return (string)$css; |
|
400 | } |
||
401 | |||
402 | /** |
||
403 | * remove css media queries from the string |
||
404 | * |
||
405 | * @param string $css |
||
406 | * |
||
407 | * @return string |
||
408 | */ |
||
409 | 44 | private function stripeMediaQueries($css) |
|
410 | { |
||
411 | // remove comments previously to matching media queries |
||
412 | 44 | $css = preg_replace(self::$styleCommentRegEx, '', $css); |
|
413 | |||
414 | 44 | return (string)preg_replace(self::$cssMediaQueriesRegEx, '', $css); |
|
415 | } |
||
416 | |||
417 | /** |
||
418 | * remove charset from the string |
||
419 | * |
||
420 | * @param $css |
||
421 | * |
||
422 | * @return string |
||
423 | */ |
||
424 | 45 | private function stripeCharsetInCss($css) |
|
425 | { |
||
426 | 45 | return (string)preg_replace(self::$cssCharsetRegEx, '', $css); |
|
427 | } |
||
428 | |||
429 | /** |
||
430 | * Process the CSS-properties |
||
431 | * |
||
432 | * @return array |
||
433 | * |
||
434 | * @param string $propertyString The CSS-properties. |
||
435 | */ |
||
436 | 34 | private function processCSSProperties($propertyString) |
|
437 | { |
||
438 | // split into chunks |
||
439 | 34 | $properties = $this->splitIntoProperties($propertyString); |
|
440 | |||
441 | // init var |
||
442 | 34 | $pairs = array(); |
|
443 | |||
444 | // loop properties |
||
445 | 34 | foreach ($properties as $property) { |
|
446 | // split into chunks |
||
447 | 34 | $chunks = (array)explode(':', $property, 2); |
|
448 | |||
449 | // validate |
||
450 | 34 | if (!isset($chunks[1])) { |
|
451 | 28 | continue; |
|
452 | } |
||
453 | |||
454 | // cleanup |
||
455 | 33 | $chunks[0] = trim($chunks[0]); |
|
456 | 33 | $chunks[1] = trim($chunks[1]); |
|
457 | |||
458 | // add to pairs array |
||
459 | if ( |
||
460 | 33 | !isset($pairs[$chunks[0]]) |
|
461 | || |
||
462 | 33 | !in_array($chunks[1], $pairs[$chunks[0]], true) |
|
463 | ) { |
||
464 | 33 | $pairs[$chunks[0]][] = $chunks[1]; |
|
465 | } |
||
466 | } |
||
467 | |||
468 | // sort the pairs |
||
469 | 34 | ksort($pairs); |
|
470 | |||
471 | // return |
||
472 | 34 | return $pairs; |
|
473 | } |
||
474 | |||
475 | /** |
||
476 | * Split a style string into an array of properties. |
||
477 | * The returned array can contain empty strings. |
||
478 | * |
||
479 | * @param string $styles ex: 'color:blue;font-size:12px;' |
||
480 | * |
||
481 | * @return array an array of strings containing css property ex: array('color:blue','font-size:12px') |
||
482 | */ |
||
483 | 34 | private function splitIntoProperties($styles) |
|
484 | { |
||
485 | 34 | $properties = (array)explode(';', $styles); |
|
486 | 34 | $propertiesCount = count($properties); |
|
487 | |||
488 | /** @noinspection ForeachInvariantsInspection */ |
||
489 | 34 | for ($i = 0; $i < $propertiesCount; $i++) { |
|
490 | // If next property begins with base64, |
||
491 | // Then the ';' was part of this property (and we should not have split on it). |
||
492 | if ( |
||
493 | 34 | isset($properties[$i + 1]) |
|
494 | && |
||
495 | 34 | strpos($properties[$i + 1], 'base64,') !== false |
|
496 | ) { |
||
497 | 1 | $properties[$i] .= ';' . $properties[$i + 1]; |
|
498 | 1 | $properties[$i + 1] = ''; |
|
499 | 1 | ++$i; |
|
500 | } |
||
501 | } |
||
502 | |||
503 | 34 | return $properties; |
|
504 | } |
||
505 | |||
506 | /** |
||
507 | * create XPath |
||
508 | * |
||
509 | * @param \DOMDocument $document |
||
510 | * @param array $cssRules |
||
511 | * |
||
512 | * @return \DOMXPath |
||
513 | */ |
||
514 | 45 | private function createXPath(\DOMDocument $document, array $cssRules) |
|
515 | { |
||
516 | 45 | $xPath = new \DOMXPath($document); |
|
517 | |||
518 | // any rules? |
||
519 | 45 | if (0 !== count($cssRules)) { |
|
520 | // loop rules |
||
521 | 34 | foreach ($cssRules as $rule) { |
|
522 | |||
523 | 34 | $ruleSelector = $rule['selector']; |
|
524 | 34 | $ruleProperties = $rule['properties']; |
|
525 | |||
526 | 34 | if (!$ruleSelector || !$ruleProperties) { |
|
527 | 3 | continue; |
|
528 | } |
||
529 | |||
530 | try { |
||
531 | 33 | $converter = new CssSelectorConverter(); |
|
532 | 33 | $query = $converter->toXPath($ruleSelector); |
|
533 | 3 | } catch (ExceptionInterface $e) { |
|
534 | 3 | $query = null; |
|
535 | } |
||
536 | 33 | $converter = null; |
|
537 | |||
538 | // validate query |
||
539 | 33 | if (null === $query) { |
|
540 | 3 | continue; |
|
541 | } |
||
542 | |||
543 | // search elements |
||
544 | 32 | $elements = $xPath->query($query); |
|
545 | |||
546 | // validate elements |
||
547 | 32 | if (false === $elements) { |
|
548 | continue; |
||
549 | } |
||
550 | |||
551 | // loop found elements |
||
552 | 32 | foreach ($elements as $element) { |
|
553 | |||
554 | /** |
||
555 | * @var $element \DOMElement |
||
556 | */ |
||
557 | |||
558 | if ( |
||
559 | 32 | $ruleSelector == '*' |
|
560 | && |
||
561 | ( |
||
562 | 1 | $element->tagName == 'html' |
|
563 | 1 | || $element->tagName === 'title' |
|
564 | 1 | || $element->tagName == 'meta' |
|
565 | 1 | || $element->tagName == 'head' |
|
566 | 1 | || $element->tagName == 'style' |
|
567 | 1 | || $element->tagName == 'script' |
|
568 | 32 | || $element->tagName == 'link' |
|
569 | ) |
||
570 | ) { |
||
571 | 1 | continue; |
|
572 | } |
||
573 | |||
574 | // no styles stored? |
||
575 | 32 | if (null === $element->attributes->getNamedItem('data-css-to-inline-styles-original-styles')) { |
|
576 | |||
577 | // init var |
||
578 | 32 | $originalStyle = ''; |
|
579 | |||
580 | 32 | if (null !== $element->attributes->getNamedItem('style')) { |
|
581 | /** @noinspection PhpUndefinedFieldInspection */ |
||
582 | 9 | $originalStyle = $element->attributes->getNamedItem('style')->value; |
|
583 | } |
||
584 | |||
585 | // store original styles |
||
586 | 32 | $element->setAttribute('data-css-to-inline-styles-original-styles', $originalStyle); |
|
587 | |||
588 | // clear the styles |
||
589 | 32 | $element->setAttribute('style', ''); |
|
590 | } |
||
591 | |||
592 | 32 | $propertiesString = $this->createPropertyChunks($element, $ruleProperties); |
|
593 | |||
594 | // set attribute |
||
595 | 32 | if ('' != $propertiesString) { |
|
596 | 32 | $element->setAttribute('style', $propertiesString); |
|
597 | } |
||
598 | } |
||
599 | } |
||
600 | |||
601 | // reapply original styles |
||
602 | // search elements |
||
603 | 34 | $elements = $xPath->query('//*[@data-css-to-inline-styles-original-styles]'); |
|
604 | |||
605 | // loop found elements |
||
606 | 34 | foreach ($elements as $element) { |
|
607 | // get the original styles |
||
608 | /** @noinspection PhpUndefinedFieldInspection */ |
||
609 | 32 | $originalStyle = $element->attributes->getNamedItem('data-css-to-inline-styles-original-styles')->value; |
|
610 | |||
611 | 32 | if ('' != $originalStyle) { |
|
612 | 9 | $originalStyles = $this->splitIntoProperties($originalStyle); |
|
613 | |||
614 | 9 | $originalProperties = $this->splitStyleIntoChunks($originalStyles); |
|
615 | |||
616 | 9 | $propertiesString = $this->createPropertyChunks($element, $originalProperties); |
|
617 | |||
618 | // set attribute |
||
619 | 9 | if ('' != $propertiesString) { |
|
620 | 9 | $element->setAttribute('style', $propertiesString); |
|
621 | } |
||
622 | } |
||
623 | |||
624 | // remove placeholder |
||
625 | 32 | $element->removeAttribute('data-css-to-inline-styles-original-styles'); |
|
626 | } |
||
627 | } |
||
628 | |||
629 | 45 | return $xPath; |
|
630 | } |
||
631 | |||
632 | /** |
||
633 | * @param \DOMElement $element |
||
634 | * @param array $ruleProperties |
||
635 | * |
||
636 | * @return array |
||
637 | */ |
||
638 | 32 | private function createPropertyChunks(\DOMElement $element, array $ruleProperties) |
|
639 | { |
||
640 | // init var |
||
641 | 32 | $properties = array(); |
|
642 | |||
643 | // get current styles |
||
644 | 32 | $stylesAttribute = $element->attributes->getNamedItem('style'); |
|
645 | |||
646 | // any styles defined before? |
||
647 | 32 | if (null !== $stylesAttribute) { |
|
648 | // get value for the styles attribute |
||
649 | /** @noinspection PhpUndefinedFieldInspection */ |
||
650 | 32 | $definedStyles = (string)$stylesAttribute->value; |
|
651 | |||
652 | // split into properties |
||
653 | 32 | $definedProperties = $this->splitIntoProperties($definedStyles); |
|
654 | |||
655 | 32 | $properties = $this->splitStyleIntoChunks($definedProperties); |
|
656 | } |
||
657 | |||
658 | // add new properties into the list |
||
659 | 32 | foreach ($ruleProperties as $key => $value) { |
|
660 | // If one of the rules is already set and is !important, don't apply it, |
||
661 | // except if the new rule is also important. |
||
662 | if ( |
||
663 | 32 | !isset($properties[$key]) |
|
664 | || |
||
665 | 10 | false === stripos($properties[$key], '!important') |
|
666 | || |
||
667 | 32 | false !== stripos(implode('', (array)$value), '!important') |
|
668 | ) { |
||
669 | 32 | $properties[$key] = $value; |
|
670 | } |
||
671 | } |
||
672 | |||
673 | // build string |
||
674 | 32 | $propertyChunks = array(); |
|
675 | |||
676 | // build chunks |
||
677 | 32 | foreach ($properties as $key => $values) { |
|
678 | 32 | foreach ((array)$values as $value) { |
|
679 | 32 | $propertyChunks[] = $key . ': ' . $value . ';'; |
|
680 | } |
||
681 | } |
||
682 | |||
683 | 32 | return implode(' ', $propertyChunks); |
|
684 | } |
||
685 | |||
686 | /** |
||
687 | * @param array $definedProperties |
||
688 | * |
||
689 | * @return array |
||
690 | */ |
||
691 | 32 | private function splitStyleIntoChunks(array $definedProperties) |
|
692 | { |
||
693 | // init var |
||
694 | 32 | $properties = array(); |
|
695 | |||
696 | // loop properties |
||
697 | 32 | foreach ($definedProperties as $property) { |
|
698 | // validate property |
||
699 | if ( |
||
700 | 32 | !$property |
|
701 | || |
||
702 | 32 | strpos($property, ':') === false |
|
703 | ) { |
||
704 | 32 | continue; |
|
705 | } |
||
706 | |||
707 | // split into chunks |
||
708 | 17 | $chunks = (array)explode(':', trim($property), 2); |
|
709 | |||
710 | // validate |
||
711 | 17 | if (!isset($chunks[1])) { |
|
712 | continue; |
||
713 | } |
||
714 | |||
715 | // loop chunks |
||
716 | 17 | $properties[$chunks[0]] = trim($chunks[1]); |
|
717 | } |
||
718 | |||
719 | 32 | return $properties; |
|
720 | } |
||
721 | |||
722 | /** |
||
723 | * Strip style tags into the generated HTML |
||
724 | * |
||
725 | * @param \DOMXPath $xPath The DOMXPath for the entire document. |
||
726 | * |
||
727 | * @return string |
||
728 | */ |
||
729 | 13 | private function stripOriginalStyleTags(\DOMXPath $xPath) |
|
730 | { |
||
731 | // get all style tags |
||
732 | 13 | $nodes = $xPath->query('descendant-or-self::style'); |
|
733 | 13 | foreach ($nodes as $node) { |
|
734 | 12 | if ($this->excludeMediaQueries === true) { |
|
735 | |||
736 | // remove comments previously to matching media queries |
||
737 | 11 | $node->nodeValue = preg_replace(self::$styleCommentRegEx, '', $node->nodeValue); |
|
738 | |||
739 | // search for Media Queries |
||
740 | 11 | preg_match_all(self::$cssMediaQueriesRegEx, $node->nodeValue, $mqs); |
|
741 | |||
742 | // replace the nodeValue with just the Media Queries |
||
743 | 11 | $node->nodeValue = implode("\n", $mqs[0]); |
|
744 | |||
745 | } else { |
||
746 | // remove the entire style tag |
||
747 | 12 | $node->parentNode->removeChild($node); |
|
748 | } |
||
749 | } |
||
750 | 13 | } |
|
751 | |||
752 | /** |
||
753 | * Remove id and class attributes. |
||
754 | * |
||
755 | * @param \DOMXPath $xPath The DOMXPath for the entire document. |
||
756 | * |
||
757 | * @return string |
||
758 | */ |
||
759 | 3 | private function cleanupHTML(\DOMXPath $xPath) |
|
760 | { |
||
761 | 3 | $nodes = $xPath->query('//@class | //@id'); |
|
762 | 3 | foreach ($nodes as $node) { |
|
763 | 3 | $node->ownerElement->removeAttributeNode($node); |
|
764 | } |
||
765 | 3 | } |
|
766 | |||
767 | /** |
||
768 | * Should the IDs and classes be removed? |
||
769 | * |
||
770 | * @param bool $on Should we enable cleanup? |
||
771 | */ |
||
772 | 3 | public function setCleanup($on = true) |
|
773 | { |
||
774 | 3 | $this->cleanup = (bool)$on; |
|
775 | 3 | } |
|
776 | |||
777 | /** |
||
778 | * Set the encoding to use with the DOMDocument |
||
779 | * |
||
780 | * @param string $encoding The encoding to use. |
||
781 | * |
||
782 | * @deprecated Doesn't have any effect |
||
783 | */ |
||
784 | public function setEncoding($encoding) |
||
785 | { |
||
786 | $this->encoding = (string)$encoding; |
||
787 | } |
||
788 | |||
789 | /** |
||
790 | * Set use of inline styles block |
||
791 | * If this is enabled the class will use the style-block in the HTML. |
||
792 | * |
||
793 | * @param bool $on Should we process inline styles? |
||
794 | */ |
||
795 | 27 | public function setUseInlineStylesBlock($on = true) |
|
796 | { |
||
797 | 27 | $this->useInlineStylesBlock = (bool)$on; |
|
798 | 27 | } |
|
799 | |||
800 | /** |
||
801 | * Set use of inline link block |
||
802 | * If this is enabled the class will use the links reference in the HTML. |
||
803 | * |
||
804 | * @return void |
||
805 | * |
||
806 | * @param bool [optional] $on Should we process link styles? |
||
807 | */ |
||
808 | 2 | public function setLoadCSSFromHTML($on = true) |
|
809 | { |
||
810 | 2 | $this->loadCSSFromHTML = (bool)$on; |
|
811 | 2 | } |
|
812 | |||
813 | /** |
||
814 | * Set strip original style tags |
||
815 | * If this is enabled the class will remove all style tags in the HTML. |
||
816 | * |
||
817 | * @param bool $on Should we process inline styles? |
||
818 | */ |
||
819 | 17 | public function setStripOriginalStyleTags($on = true) |
|
820 | { |
||
821 | 17 | $this->stripOriginalStyleTags = (bool)$on; |
|
822 | 17 | } |
|
823 | |||
824 | /** |
||
825 | * Set exclude media queries |
||
826 | * |
||
827 | * If this is enabled the media queries will be removed before inlining the rules. |
||
828 | * |
||
829 | * WARNING: If you use inline styles block "<style>" the this option will keep the media queries. |
||
830 | * |
||
831 | * @param bool $on |
||
832 | */ |
||
833 | 14 | public function setExcludeMediaQueries($on = true) |
|
834 | { |
||
835 | 14 | $this->excludeMediaQueries = (bool)$on; |
|
836 | 14 | } |
|
837 | |||
838 | /** |
||
839 | * Set exclude charset |
||
840 | * |
||
841 | * @param bool $on |
||
842 | */ |
||
843 | 1 | public function setExcludeCssCharset($on = true) |
|
844 | { |
||
845 | 1 | $this->excludeCssCharset = (bool)$on; |
|
846 | 1 | } |
|
847 | |||
848 | /** |
||
849 | * Set exclude conditional inline-style blocks e.g.: <!--[if gte mso 9]><style>.foo { bar } </style><![endif]--> |
||
850 | * |
||
851 | * @param bool $on |
||
852 | */ |
||
853 | 6 | public function setExcludeConditionalInlineStylesBlock($on = true) |
|
854 | { |
||
855 | 6 | $this->excludeConditionalInlineStylesBlock = (bool)$on; |
|
856 | 6 | } |
|
857 | } |
||
858 |
This check looks from parameters that have been defined for a function or method, but which are not used in the method body.