PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.10
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.10
4.4.10 4.4.9.1 4.4.9 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 All 142 releases
learnpress / vendor / tijsverkoyen / css-to-inline-styles / src / CssToInlineStyles.php

CssToInlineStyles.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.10, at vendor/tijsverkoyen/css-to-inline-styles/src/CssToInlineStyles.php

254 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TijsVerkoyen\CssToInlineStyles;
4
5 use Symfony\Component\CssSelector\CssSelectorConverter;
6 use Symfony\Component\CssSelector\Exception\ExceptionInterface;
7 use TijsVerkoyen\CssToInlineStyles\Css\Processor;
8 use TijsVerkoyen\CssToInlineStyles\Css\Property\Processor as PropertyProcessor;
9 use TijsVerkoyen\CssToInlineStyles\Css\Property\Property;
10 use TijsVerkoyen\CssToInlineStyles\Css\Rule\Processor as RuleProcessor;
11
12 class CssToInlineStyles
13 {
14 /**
15 * @var CssSelectorConverter
16 */
17 private $cssConverter;
18
19 public function __construct()
20 {
21 $this->cssConverter = new CssSelectorConverter();
22 }
23
24 /**
25 * Will inline the $css into the given $html
26 *
27 * Remark: if the html contains <style>-tags those will be used, the rules
28 * in $css will be appended.
29 *
30 * @param string $html
31 * @param string $css
32 *
33 * @return string
34 */
35 public function convert($html, $css = null)
36 {
37 $document = $this->createDomDocumentFromHtml($html);
38 $processor = new Processor();
39
40 // get all styles from the style-tags
41 $rules = $processor->getRules(
42 $processor->getCssFromStyleTags($html)
43 );
44
45 if ($css !== null) {
46 $rules = $processor->getRules($css, $rules);
47 }
48
49 $document = $this->inline($document, $rules);
50
51 return $this->getHtmlFromDocument($document);
52 }
53
54 /**
55 * Inline the given properties on a given DOMElement
56 *
57 * @param \DOMElement $element
58 * @param Property[] $properties
59 *
60 * @return \DOMElement
61 */
62 public function inlineCssOnElement(\DOMElement $element, array $properties)
63 {
64 if (empty($properties)) {
65 return $element;
66 }
67
68 $cssProperties = array();
69 $inlineProperties = array();
70
71 foreach ($this->getInlineStyles($element) as $property) {
72 $inlineProperties[$property->getName()] = $property;
73 }
74
75 foreach ($properties as $property) {
76 if (!isset($inlineProperties[$property->getName()])) {
77 $cssProperties[$property->getName()] = $property;
78 }
79 }
80
81 $rules = array();
82 foreach (array_merge($cssProperties, $inlineProperties) as $property) {
83 $rules[] = $property->toString();
84 }
85 $element->setAttribute('style', implode(' ', $rules));
86
87 return $element;
88 }
89
90 /**
91 * Get the current inline styles for a given DOMElement
92 *
93 * @param \DOMElement $element
94 *
95 * @return Property[]
96 */
97 public function getInlineStyles(\DOMElement $element)
98 {
99 $processor = new PropertyProcessor();
100
101 return $processor->convertArrayToObjects(
102 $processor->splitIntoSeparateProperties(
103 $element->getAttribute('style')
104 )
105 );
106 }
107
108 /**
109 * @param string $html
110 *
111 * @return \DOMDocument
112 */
113 protected function createDomDocumentFromHtml($html)
114 {
115 $document = new \DOMDocument('1.0', 'UTF-8');
116 $internalErrors = libxml_use_internal_errors(true);
117 $document->loadHTML(mb_encode_numericentity($html, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8'));
118 libxml_use_internal_errors($internalErrors);
119 $document->formatOutput = true;
120
121 return $document;
122 }
123
124 /**
125 * @param \DOMDocument $document
126 *
127 * @return string
128 */
129 protected function getHtmlFromDocument(\DOMDocument $document)
130 {
131 // retrieve the document element
132 // we do it this way to preserve the utf-8 encoding
133 $htmlElement = $document->documentElement;
134
135 if ($htmlElement === null) {
136 throw new \RuntimeException('Failed to get HTML from empty document.');
137 }
138
139 $html = $document->saveHTML($htmlElement);
140
141 if ($html === false) {
142 throw new \RuntimeException('Failed to get HTML from document.');
143 }
144
145 $html = trim($html);
146
147 // retrieve the doctype
148 $document->removeChild($htmlElement);
149 $doctype = $document->saveHTML();
150 if ($doctype === false) {
151 $doctype = '';
152 }
153 $doctype = trim($doctype);
154
155 // if it is the html5 doctype convert it to lowercase
156 if ($doctype === '<!DOCTYPE html>') {
157 $doctype = strtolower($doctype);
158 }
159
160 return $doctype."\n".$html;
161 }
162
163 /**
164 * @param \DOMDocument $document
165 * @param Css\Rule\Rule[] $rules
166 *
167 * @return \DOMDocument
168 */
169 protected function inline(\DOMDocument $document, array $rules)
170 {
171 if (empty($rules)) {
172 return $document;
173 }
174
175 /** @var \SplObjectStorage<\DOMElement, array<string, Property>> $propertyStorage */
176 $propertyStorage = new \SplObjectStorage();
177
178 $xPath = new \DOMXPath($document);
179
180 usort($rules, array(RuleProcessor::class, 'sortOnSpecificity'));
181
182 foreach ($rules as $rule) {
183 try {
184 $expression = $this->cssConverter->toXPath($rule->getSelector());
185 } catch (ExceptionInterface $e) {
186 continue;
187 }
188
189 $elements = $xPath->query($expression);
190
191 if ($elements === false) {
192 continue;
193 }
194
195 foreach ($elements as $element) {
196 \assert($element instanceof \DOMElement);
197 $propertyStorage[$element] = $this->calculatePropertiesToBeApplied(
198 $rule->getProperties(),
199 $propertyStorage->offsetExists($element) ? $propertyStorage[$element] : array()
200 );
201 }
202 }
203
204 foreach ($propertyStorage as $element) {
205 $this->inlineCssOnElement($element, $propertyStorage[$element]);
206 }
207
208 return $document;
209 }
210
211 /**
212 * Merge the CSS rules to determine the applied properties.
213 *
214 * @param Property[] $properties
215 * @param array<string, Property> $cssProperties existing applied properties indexed by name
216 *
217 * @return array<string, Property> updated properties, indexed by name
218 */
219 private function calculatePropertiesToBeApplied(array $properties, array $cssProperties): array
220 {
221 if (empty($properties)) {
222 return $cssProperties;
223 }
224
225 foreach ($properties as $property) {
226 if (isset($cssProperties[$property->getName()])) {
227 $existingProperty = $cssProperties[$property->getName()];
228
229 //skip check to overrule if existing property is important and current is not
230 if ($existingProperty->isImportant() && !$property->isImportant()) {
231 continue;
232 }
233
234 //overrule if current property is important and existing is not, else check specificity
235 $overrule = !$existingProperty->isImportant() && $property->isImportant();
236 if (!$overrule) {
237 \assert($existingProperty->getOriginalSpecificity() !== null, 'Properties created for parsed CSS always have their associated specificity.');
238 \assert($property->getOriginalSpecificity() !== null, 'Properties created for parsed CSS always have their associated specificity.');
239 $overrule = $existingProperty->getOriginalSpecificity()->compareTo($property->getOriginalSpecificity()) <= 0;
240 }
241
242 if ($overrule) {
243 unset($cssProperties[$property->getName()]);
244 $cssProperties[$property->getName()] = $property;
245 }
246 } else {
247 $cssProperties[$property->getName()] = $property;
248 }
249 }
250
251 return $cssProperties;
252 }
253 }
254