PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 3.47.7
MailPoet – Newsletters, Email Marketing, and Automation v3.47.7
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
Idiorm 6 years ago Paris 6 years ago Sudzy 6 years ago ArrayColumn.php 6 years ago CSS.php 6 years ago XLSXWriter.php 6 years ago index.php 6 years ago
CSS.php
292 lines
1 <?php
2 namespace MailPoetVendor;
3
4 if (!defined('ABSPATH')) exit;
5
6
7 use MailPoet\Util\pQuery\DomNode;
8 use MailPoet\Util\pQuery\pQuery;
9 use MailPoet\Newsletter\Renderer\EscapeHelper as EHelper;
10
11 /*
12 Copyright 2013-2014, François-Marie de Jouvencel
13
14 This program is free software: you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation, either version 3 of the License, or
17 (at your option) any later version.
18
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28 /*
29 * A class to inline CSS.
30 *
31 * It honours !important attributes and doesn't choke on complex styles.
32 *
33 *
34 */
35
36 class CSS {
37 /**
38 * @param string $contents
39 * @return DomNode
40 */
41 function inlineCSS($contents) {
42 $html = pQuery::parseStr($contents);
43
44 if (!$html instanceof DomNode) {
45 throw new \InvalidArgumentException('Error parsing contents.');
46 }
47
48 $css_blocks = '';
49
50 // Find all <style> blocks and cut styles from them (leaving media queries)
51 foreach ($html->query('style') as $style) {
52 list($_css_to_parse, $_css_to_keep) = $this->splitMediaQueries($style->getInnerText());
53 $css_blocks .= $_css_to_parse;
54 if (!empty($_css_to_keep)) {
55 $style->setInnerText($_css_to_keep);
56 } else {
57 $style->setOuterText('');
58 }
59 }
60
61 $raw_css = '';
62 if (!empty($css_blocks)) {
63 $raw_css .= $css_blocks;
64 }
65
66 // Get the CSS rules by decreasing specificity (the most specific rule first).
67 // This is an array with, amongst other things, the keys 'properties', which hold the CSS properties
68 // and the 'selector', which holds the CSS selector
69 $rules = $this->parseCSS($raw_css);
70 $nodes_map = [];
71
72 // We loop over each rule by increasing order of specificity, find the nodes matching the selector
73 // and apply the CSS properties
74 foreach ($rules as $rule) {
75 if (!isset($nodes_map[$rule['selector']])) {
76 $nodes_map[$rule['selector']] = $html->query($rule['selector']);
77 }
78 foreach ($nodes_map[$rule['selector']] as $node) {
79 // I'm leaving this for debug purposes, it has proved useful.
80 /*
81 if ($node->already_styled === 'yes')
82 {
83 echo "<PRE>";
84 echo "Rule:\n";
85 print_r($rule);
86 echo "\n\nOld style:\n";
87 echo $node->style."\n";
88 print_r($this->styleToArray($node->style));
89 echo "\n\nNew style:\n";
90 print_r(array_merge($this->styleToArray($node->style), $rule['properties']));
91 echo "</PRE>";
92 die();
93 }//*/
94
95 // Unserialize the style array, merge the rule's CSS into it...
96 $nodeStyles = $this->styleToArray($node->style);
97 $style = array_merge($rule['properties'], $nodeStyles);
98
99 // And put the CSS back as a string!
100 $node->style = $this->arrayToStyle($style);
101
102 // I'm leaving this for debug purposes, it has proved useful.
103 /*
104 if ($rule['selector'] === 'table.table-recap td')
105 {
106 $node->already_styled = 'yes';
107 }//*/
108 }
109 }
110
111 // Now a tricky part: do a second pass with only stuff marked !important
112 // because !important properties do not care about specificity, except when fighting
113 // against another !important property
114 // We need to start with a rule with lowest specificity
115 $rules = array_reverse($rules);
116 foreach ($rules as $rule) {
117 foreach ($rule['properties'] as $key => $value) {
118 if (strpos($value, '!important') === false) {
119 continue;
120 }
121 foreach ($nodes_map[$rule['selector']] as $node) {
122 $style = $this->styleToArray($node->style);
123 $style[$key] = $value;
124 $node->style = $this->arrayToStyle($style);
125 // remove all !important tags (inlined styles take precedent over others anyway)
126 $node->style = str_replace("!important", "", $node->style);
127 }
128 }
129 }
130
131 return $html;
132 }
133
134 function parseCSS($text) {
135 $css = new csstidy();
136 $css->settings['compress_colors'] = false;
137 $css->parse($text);
138
139 $rules = [];
140 $position = 0;
141
142 foreach ($css->css as $declarations) {
143 foreach ($declarations as $selectors => $properties) {
144 foreach (explode(",", $selectors) as $selector) {
145 $rules[] = [
146 'position' => $position,
147 'specificity' => $this->calculateCSSSpecifity($selector),
148 'selector' => $selector,
149 'properties' => $properties,
150 ];
151 }
152
153 $position += 1;
154 }
155 }
156
157 usort($rules, function($a, $b) {
158 if ($a['specificity'] > $b['specificity']) {
159 return -1;
160 } else if ($a['specificity'] < $b['specificity']) {
161 return 1;
162 } else {
163 if ($a['position'] > $b['position']) {
164 return -1;
165 } else {
166 return 1;
167 }
168 }
169 });
170
171 return $rules;
172 }
173
174 /*
175 * Merges two CSS inline styles strings into one.
176 * If both styles defines same property the property from second styles will be used.
177 */
178 function mergeInlineStyles($styles_1, $styles_2) {
179 $merged_styles = array_merge($this->styleToArray($styles_1), $this->styleToArray($styles_2));
180 return $this->arrayToStyle($merged_styles);
181 }
182
183 private function splitMediaQueries($css) {
184 $start = 0;
185 $queries = '';
186
187 while (($start = strpos($css, "@media", $start)) !== false) {
188 // stack to manage brackets
189 $s = [];
190
191 // get the first opening bracket
192 $i = strpos($css, "{", $start);
193
194 // if $i is false, then there is probably a css syntax error
195 if ($i !== false) {
196 // push bracket onto stack
197 array_push($s, $css[$i]);
198
199 // move past first bracket
200 $i++;
201
202 while (!empty($s)) {
203 // if the character is an opening bracket, push it onto the stack, otherwise pop the stack
204 if ($css[$i] == "{") {
205 array_push($s, "{");
206 } else if ($css[$i] == "}") {
207 array_pop($s);
208 }
209
210 $i++;
211 }
212
213 $queries .= substr($css, $start - 1, $i + 1 - $start) . "\n";
214 $css = substr($css, 0, $start - 1) . substr($css, $i);
215 $i = $start;
216 }
217 }
218
219 return [$css, $queries];
220 }
221
222 /**
223 * The following function fomes from CssToInlineStyles.php - here is the original licence FOR THIS FUNCTION
224 *
225 * CSS to Inline Styles class
226 *
227 * @author Tijs Verkoyen <php-css-to-inline-styles@verkoyen.eu>
228 * @version 1.2.1
229 * @copyright Copyright (c), Tijs Verkoyen. All rights reserved.
230 * @license BSD License
231 */
232
233 private function calculateCSSSpecifity($selector) {
234 // cleanup selector
235 $selector = str_replace(['>', '+'], [' > ', ' + '], $selector);
236
237 // init var
238 $specifity = 0;
239
240 // split the selector into chunks based on spaces
241 $chunks = explode(' ', $selector);
242
243 // loop chunks
244 foreach ($chunks as $chunk) {
245 // an ID is important, so give it a high specifity
246 if (strstr($chunk, '#') !== false) $specifity += 100;
247
248 // classes are more important than a tag, but less important then an ID
249 elseif (strstr($chunk, '.')) $specifity += 10;
250
251 // anything else isn't that important
252 else $specifity += 1;
253 }
254
255 // return
256 return $specifity;
257 }
258
259 /*
260 * Turns a CSS style string (like: "border: 1px solid black; color:red")
261 * into an array of properties (like: array("border" => "1px solid black", "color" => "red"))
262 */
263 private function styleToArray($str) {
264 $str = EHelper::unescapeHtmlStyleAttr($str);
265 $array = [];
266
267 if (trim($str) === '') return $array;
268
269 foreach (explode(';', $str) as $kv) {
270 if ($kv === '') {
271 continue;
272 }
273 list($selector, $rule) = explode(':', $kv, 2);
274 $array[trim($selector)] = trim($rule);
275 }
276
277 return $array;
278 }
279
280 /*
281 * Reverses what styleToArray does, see above.
282 * array("border" => "1px solid black", "color" => "red") yields "border: 1px solid black; color:red"
283 */
284 private function arrayToStyle($array) {
285 $parts = [];
286 foreach ($array as $k => $v) {
287 $parts[] = "$k:$v";
288 }
289 return EHelper::escapeHtmlStyleAttr(implode(';', $parts));
290 }
291 }
292