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 |