PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib-3rd-party / CSS.php
mailpoet / lib-3rd-party Last commit date
pquery 5 months ago CSS.php 2 years ago XLSXWriter.php 1 year ago index.php 3 years ago
CSS.php
296 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoetVendor;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
9 use MailPoet\Util\pQuery\DomNode;
10 use MailPoet\Util\pQuery\pQuery;
11
12 /*
13 Copyright 2013-2014, François-Marie de Jouvencel
14
15 This program is free software: you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation, either version 3 of the License, or
18 (at your option) any later version.
19
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 */
28
29 /*
30 * A class to inline CSS.
31 *
32 * It honours !important attributes and doesn't choke on complex styles.
33 *
34 *
35 */
36
37 class CSS {
38 /**
39 * @param string $contents
40 * @return DomNode
41 */
42 function inlineCSS($contents) {
43 $html = pQuery::parseStr($contents);
44
45 if (!$html instanceof DomNode) {
46 throw new \InvalidArgumentException('Error parsing contents.');
47 }
48
49 $css_blocks = '';
50
51 // Find all <style> blocks and cut styles from them (leaving media queries)
52 foreach ($html->query('style') as $style) {
53 list($_css_to_parse, $_css_to_keep) = $this->splitMediaQueries($style->getInnerText());
54 $css_blocks .= $_css_to_parse;
55 if (!empty($_css_to_keep)) {
56 $style->setInnerText($_css_to_keep);
57 } else {
58 $style->setOuterText('');
59 }
60 }
61
62 $raw_css = '';
63 if (!empty($css_blocks)) {
64 $raw_css .= $css_blocks;
65 }
66
67 // Get the CSS rules by decreasing specificity (the most specific rule first).
68 // This is an array with, amongst other things, the keys 'properties', which hold the CSS properties
69 // and the 'selector', which holds the CSS selector
70 $rules = $this->parseCSS($raw_css);
71 $nodes_map = [];
72
73 // We loop over each rule by increasing order of specificity, find the nodes matching the selector
74 // and apply the CSS properties
75 foreach ($rules as $rule) {
76 if (!isset($nodes_map[$rule['selector']])) {
77 $nodes_map[$rule['selector']] = $html->query($rule['selector']);
78 }
79 foreach ($nodes_map[$rule['selector']] as $node) {
80 // I'm leaving this for debug purposes, it has proved useful.
81 /*
82 if ($node->already_styled === 'yes')
83 {
84 echo "<PRE>";
85 echo "Rule:\n";
86 print_r($rule);
87 echo "\n\nOld style:\n";
88 echo $node->style."\n";
89 print_r($this->styleToArray($node->style));
90 echo "\n\nNew style:\n";
91 print_r(array_merge($this->styleToArray($node->style), $rule['properties']));
92 echo "</PRE>";
93 die();
94 }//*/
95
96 // Unserialize the style array, merge the rule's CSS into it...
97 $nodeStyles = $this->styleToArray($node->style);
98 $style = array_merge($rule['properties'], $nodeStyles);
99
100 // And put the CSS back as a string!
101 $node->style = $this->arrayToStyle($style);
102
103 // I'm leaving this for debug purposes, it has proved useful.
104 /*
105 if ($rule['selector'] === 'table.table-recap td')
106 {
107 $node->already_styled = 'yes';
108 }//*/
109 }
110 }
111
112 // Now a tricky part: do a second pass with only stuff marked !important
113 // because !important properties do not care about specificity, except when fighting
114 // against another !important property
115 // We need to start with a rule with lowest specificity
116 $rules = array_reverse($rules);
117 foreach ($rules as $rule) {
118 foreach ($rule['properties'] as $key => $value) {
119 if (strpos($value, '!important') === false) {
120 continue;
121 }
122 foreach ($nodes_map[$rule['selector']] as $node) {
123 $style = $this->styleToArray($node->style);
124 $style[$key] = $value;
125 $node->style = $this->arrayToStyle($style);
126 // remove all !important tags (inlined styles take precedent over others anyway)
127 $node->style = str_replace("!important", "", $node->style);
128 }
129 }
130 }
131
132 return $html;
133 }
134
135 function parseCSS($text) {
136 $css = new csstidy();
137 $css->settings['compress_colors'] = false;
138 // Disable shorthand optimisation--this breaks `padding: calc() calc()` style rules.
139 $css->settings['optimise_shorthands'] = 0;
140 $css->parse($text);
141
142 $rules = [];
143 $position = 0;
144
145 foreach ($css->css as $declarations) {
146 foreach ($declarations as $selectors => $properties) {
147 foreach (explode(",", $selectors) as $selector) {
148 $rules[] = [
149 'position' => $position,
150 'specificity' => $this->calculateCSSSpecifity($selector),
151 'selector' => $selector,
152 'properties' => $properties,
153 ];
154 }
155
156 $position += 1;
157 }
158 }
159
160 usort($rules, function($a, $b) {
161 if ($a['specificity'] > $b['specificity']) {
162 return -1;
163 } else if ($a['specificity'] < $b['specificity']) {
164 return 1;
165 } else {
166 if ($a['position'] > $b['position']) {
167 return -1;
168 } else {
169 return 1;
170 }
171 }
172 });
173
174 return $rules;
175 }
176
177 /*
178 * Merges two CSS inline styles strings into one.
179 * If both styles defines same property the property from second styles will be used.
180 */
181 function mergeInlineStyles($styles_1, $styles_2) {
182 $merged_styles = array_merge($this->styleToArray($styles_1), $this->styleToArray($styles_2));
183 return $this->arrayToStyle($merged_styles);
184 }
185
186 private function splitMediaQueries($css) {
187 $start = 0;
188 $queries = '';
189
190 while (($start = strpos($css, "@media", $start)) !== false) {
191 // stack to manage brackets
192 $s = [];
193
194 // get the first opening bracket
195 $i = strpos($css, "{", $start);
196
197 // if $i is false, then there is probably a css syntax error
198 if ($i !== false) {
199 // push bracket onto stack
200 array_push($s, $css[$i]);
201
202 // move past first bracket
203 $i++;
204
205 while (!empty($s)) {
206 // if the character is an opening bracket, push it onto the stack, otherwise pop the stack
207 if ($css[$i] == "{") {
208 array_push($s, "{");
209 } else if ($css[$i] == "}") {
210 array_pop($s);
211 }
212
213 $i++;
214 }
215
216 $queries .= substr($css, $start - 1, $i + 1 - $start) . "\n";
217 $css = substr($css, 0, $start - 1) . substr($css, $i);
218 $i = $start;
219 }
220 }
221
222 return [$css, $queries];
223 }
224
225 /**
226 * The following function fomes from CssToInlineStyles.php - here is the original licence FOR THIS FUNCTION
227 *
228 * CSS to Inline Styles class
229 *
230 * @author Tijs Verkoyen <php-css-to-inline-styles@verkoyen.eu>
231 * @version 1.2.1
232 * @copyright Copyright (c), Tijs Verkoyen. All rights reserved.
233 * @license BSD License
234 */
235
236 private function calculateCSSSpecifity($selector) {
237 // cleanup selector
238 $selector = str_replace(['>', '+'], [' > ', ' + '], $selector);
239
240 // init var
241 $specifity = 0;
242
243 // split the selector into chunks based on spaces
244 $chunks = explode(' ', $selector);
245
246 // loop chunks
247 foreach ($chunks as $chunk) {
248 // an ID is important, so give it a high specifity
249 if (strstr($chunk, '#') !== false) $specifity += 100;
250
251 // classes are more important than a tag, but less important then an ID
252 elseif (strstr($chunk, '.')) $specifity += 10;
253
254 // anything else isn't that important
255 else $specifity += 1;
256 }
257
258 // return
259 return $specifity;
260 }
261
262 /*
263 * Turns a CSS style string (like: "border: 1px solid black; color:red")
264 * into an array of properties (like: array("border" => "1px solid black", "color" => "red"))
265 */
266 private function styleToArray($str) {
267 $str = EHelper::unescapeHtmlStyleAttr($str);
268 $array = [];
269
270 if (trim($str) === '') return $array;
271
272 foreach (explode(';', $str) as $kv) {
273 $line = trim($kv);
274 if ($line === '') {
275 continue;
276 }
277 list($selector, $rule) = explode(':', $line, 2);
278 $array[trim($selector)] = trim($rule);
279 }
280
281 return $array;
282 }
283
284 /*
285 * Reverses what styleToArray does, see above.
286 * array("border" => "1px solid black", "color" => "red") yields "border: 1px solid black; color:red"
287 */
288 private function arrayToStyle($array) {
289 $parts = [];
290 foreach ($array as $k => $v) {
291 $parts[] = "$k:$v";
292 }
293 return EHelper::escapeHtmlStyleAttr(implode(';', $parts));
294 }
295 }
296