futtta /
autoptimize
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /*! |
||
| 4 | * cssmin.php |
||
| 5 | * Author: Tubal Martin - http://tubalmartin.me/ |
||
| 6 | * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port |
||
| 7 | * |
||
| 8 | * This is a PHP port of the CSS minification tool distributed with YUICompressor, |
||
| 9 | * itself a port of the cssmin utility by Isaac Schlueter - http://foohack.com/ |
||
| 10 | * Permission is hereby granted to use the PHP version under the same |
||
| 11 | * conditions as the YUICompressor. |
||
| 12 | */ |
||
| 13 | |||
| 14 | /*! |
||
| 15 | * YUI Compressor |
||
| 16 | * http://developer.yahoo.com/yui/compressor/ |
||
| 17 | * Author: Julien Lecomte - http://www.julienlecomte.net/ |
||
| 18 | * Copyright (c) 2013 Yahoo! Inc. All rights reserved. |
||
| 19 | * The copyrights embodied in the content of this file are licensed |
||
| 20 | * by Yahoo! Inc. under the BSD (revised) open source license. |
||
| 21 | */ |
||
| 22 | |||
| 23 | class CSSmin |
||
| 24 | { |
||
| 25 | const NL = '___YUICSSMIN_PRESERVED_NL___'; |
||
| 26 | const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___'; |
||
| 27 | const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___'; |
||
| 28 | |||
| 29 | const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_'; |
||
| 30 | const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_'; |
||
| 31 | const AT_RULE_BLOCK = '___YUICSSMIN_PRESERVE_AT_RULE_BLOCK_'; |
||
| 32 | |||
| 33 | private $comments; |
||
| 34 | private $atRuleBlocks; |
||
| 35 | private $preservedTokens; |
||
| 36 | private $chunkLength = 5000; |
||
| 37 | private $minChunkLength = 100; |
||
| 38 | private $memoryLimit; |
||
| 39 | private $maxExecutionTime = 60; // 1 min |
||
| 40 | private $pcreBacktrackLimit; |
||
| 41 | private $pcreRecursionLimit; |
||
| 42 | private $raisePhpLimits; |
||
| 43 | |||
| 44 | private $unitsGroupRegex = '(?:ch|cm|em|ex|gd|in|mm|px|pt|pc|q|rem|vh|vmax|vmin|vw|%)'; |
||
| 45 | private $numRegex; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * @param bool|int $raisePhpLimits If true, PHP settings will be raised if needed |
||
| 49 | */ |
||
| 50 | public function __construct($raisePhpLimits = true) |
||
| 51 | { |
||
| 52 | $this->memoryLimit = 128 * 1048576; // 128MB in bytes |
||
| 53 | $this->pcreBacktrackLimit = 1000 * 1000; |
||
| 54 | $this->pcreRecursionLimit = 500 * 1000; |
||
| 55 | |||
| 56 | $this->raisePhpLimits = (bool) $raisePhpLimits; |
||
| 57 | |||
| 58 | $this->numRegex = '(?:\+|-)?\d*\.?\d+' . $this->unitsGroupRegex .'?'; |
||
| 59 | } |
||
| 60 | |||
| 61 | /** |
||
| 62 | * Minifies a string of CSS |
||
| 63 | * @param string $css |
||
| 64 | * @param int|bool $linebreakPos |
||
| 65 | * @return string |
||
| 66 | */ |
||
| 67 | public function run($css = '', $linebreakPos = false) |
||
| 68 | { |
||
| 69 | if (empty($css)) { |
||
| 70 | return ''; |
||
| 71 | } |
||
| 72 | |||
| 73 | if ($this->raisePhpLimits) { |
||
| 74 | $this->doRaisePhpLimits(); |
||
| 75 | } |
||
| 76 | |||
| 77 | $this->comments = array(); |
||
| 78 | $this->atRuleBlocks = array(); |
||
| 79 | $this->preservedTokens = array(); |
||
| 80 | |||
| 81 | // process data urls |
||
| 82 | $css = $this->processDataUrls($css); |
||
| 83 | |||
| 84 | // process comments |
||
| 85 | $css = preg_replace_callback('/(?<!\\\\)\/\*(.*?)\*(?<!\\\\)\//Ss', array($this, 'processComments'), $css); |
||
| 86 | |||
| 87 | // process strings so their content doesn't get accidentally minified |
||
| 88 | $css = preg_replace_callback( |
||
| 89 | '/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", |
||
| 90 | array($this, 'processStrings'), |
||
| 91 | $css |
||
| 92 | ); |
||
| 93 | |||
| 94 | // Safe chunking: process at rule blocks so after chunking nothing gets stripped out |
||
| 95 | $css = preg_replace_callback( |
||
| 96 | '/@(?:document|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|supports).+?\}\s*\}/si', |
||
| 97 | array($this, 'processAtRuleBlocks'), |
||
| 98 | $css |
||
| 99 | ); |
||
| 100 | |||
| 101 | // Let's divide css code in chunks of {$this->chunkLength} chars aprox. |
||
| 102 | // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit" |
||
| 103 | // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really |
||
| 104 | // long strings and a (sub)pattern matches a number of chars greater than |
||
| 105 | // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently |
||
| 106 | // returning NULL and $css would be empty. |
||
| 107 | $charset = ''; |
||
| 108 | $charsetRegexp = '/(@charset)( [^;]+;)/i'; |
||
| 109 | $cssChunks = array(); |
||
| 110 | $l = strlen($css); |
||
| 111 | |||
| 112 | // if the number of characters is <= {$this->chunkLength}, do not chunk |
||
| 113 | if ($l <= $this->chunkLength) { |
||
| 114 | $cssChunks[] = $css; |
||
| 115 | } else { |
||
| 116 | // chunk css code securely |
||
| 117 | for ($startIndex = 0, $i = $this->chunkLength; $i < $l; $i++) { |
||
| 118 | if ($css[$i - 1] === '}' && $i - $startIndex >= $this->chunkLength) { |
||
| 119 | $cssChunks[] = $this->strSlice($css, $startIndex, $i); |
||
| 120 | $startIndex = $i; |
||
| 121 | // Move forward saving iterations when possible! |
||
| 122 | if ($startIndex + $this->chunkLength < $l) { |
||
| 123 | $i += $this->chunkLength; |
||
| 124 | } |
||
| 125 | } |
||
| 126 | } |
||
| 127 | |||
| 128 | // Final chunk |
||
| 129 | $cssChunks[] = $this->strSlice($css, $startIndex); |
||
| 130 | } |
||
| 131 | |||
| 132 | // Minify each chunk |
||
| 133 | View Code Duplication | for ($i = 0, $n = count($cssChunks); $i < $n; $i++) { |
|
| 134 | $cssChunks[$i] = $this->minify($cssChunks[$i], $linebreakPos); |
||
| 135 | // Keep the first @charset at-rule found |
||
| 136 | if (empty($charset) && preg_match($charsetRegexp, $cssChunks[$i], $matches)) { |
||
| 137 | $charset = strtolower($matches[1]) . $matches[2]; |
||
| 138 | } |
||
| 139 | // Delete all @charset at-rules |
||
| 140 | $cssChunks[$i] = preg_replace($charsetRegexp, '', $cssChunks[$i]); |
||
| 141 | } |
||
| 142 | |||
| 143 | // Update the first chunk and push the charset to the top of the file. |
||
| 144 | $cssChunks[0] = $charset . $cssChunks[0]; |
||
| 145 | |||
| 146 | return trim(implode('', $cssChunks)); |
||
| 147 | } |
||
| 148 | |||
| 149 | /** |
||
| 150 | * Sets the approximate number of characters to use when splitting a string in chunks. |
||
| 151 | * @param int $length |
||
| 152 | */ |
||
| 153 | public function set_chunk_length($length) |
||
| 154 | { |
||
| 155 | $length = (int) $length; |
||
| 156 | $this->chunkLength = $length < $this->minChunkLength ? $this->minChunkLength : $length; |
||
| 157 | } |
||
| 158 | |||
| 159 | /** |
||
| 160 | * Sets the memory limit for this script |
||
| 161 | * @param int|string $limit |
||
| 162 | */ |
||
| 163 | public function set_memory_limit($limit) |
||
| 164 | { |
||
| 165 | $this->memoryLimit = $this->normalizeInt($limit); |
||
| 166 | } |
||
| 167 | |||
| 168 | /** |
||
| 169 | * Sets the maximum execution time for this script |
||
| 170 | * @param int|string $seconds |
||
| 171 | */ |
||
| 172 | public function set_max_execution_time($seconds) |
||
| 173 | { |
||
| 174 | $this->maxExecutionTime = (int) $seconds; |
||
| 175 | } |
||
| 176 | |||
| 177 | /** |
||
| 178 | * Sets the PCRE backtrack limit for this script |
||
| 179 | * @param int $limit |
||
| 180 | */ |
||
| 181 | public function set_pcre_backtrack_limit($limit) |
||
| 182 | { |
||
| 183 | $this->pcreBacktrackLimit = (int) $limit; |
||
| 184 | } |
||
| 185 | |||
| 186 | /** |
||
| 187 | * Sets the PCRE recursion limit for this script |
||
| 188 | * @param int $limit |
||
| 189 | */ |
||
| 190 | public function set_pcre_recursion_limit($limit) |
||
| 191 | { |
||
| 192 | $this->pcreRecursionLimit = (int) $limit; |
||
| 193 | } |
||
| 194 | |||
| 195 | /** |
||
| 196 | * Tries to configure PHP to use at least the suggested minimum settings |
||
| 197 | * @return void |
||
| 198 | */ |
||
| 199 | private function doRaisePhpLimits() |
||
| 200 | { |
||
| 201 | $phpLimits = array( |
||
| 202 | 'memory_limit' => $this->memoryLimit, |
||
| 203 | 'max_execution_time' => $this->maxExecutionTime, |
||
| 204 | 'pcre.backtrack_limit' => $this->pcreBacktrackLimit, |
||
| 205 | 'pcre.recursion_limit' => $this->pcreRecursionLimit |
||
| 206 | ); |
||
| 207 | |||
| 208 | // If current settings are higher respect them. |
||
| 209 | foreach ($phpLimits as $name => $suggested) { |
||
| 210 | $current = $this->normalizeInt(ini_get($name)); |
||
| 211 | |||
| 212 | if ($current > $suggested) { |
||
| 213 | continue; |
||
| 214 | } |
||
| 215 | |||
| 216 | // memoryLimit exception: allow -1 for "no memory limit". |
||
| 217 | if ($name === 'memory_limit' && $current === -1) { |
||
| 218 | continue; |
||
| 219 | } |
||
| 220 | |||
| 221 | // maxExecutionTime exception: allow 0 for "no memory limit". |
||
| 222 | if ($name === 'max_execution_time' && $current === 0) { |
||
| 223 | continue; |
||
| 224 | } |
||
| 225 | |||
| 226 | ini_set($name, $suggested); |
||
| 227 | } |
||
| 228 | } |
||
| 229 | |||
| 230 | /** |
||
| 231 | * Registers a preserved token |
||
| 232 | * @param $token |
||
| 233 | * @return string The token ID string |
||
| 234 | */ |
||
| 235 | private function registerPreservedToken($token) |
||
| 236 | { |
||
| 237 | $this->preservedTokens[] = $token; |
||
| 238 | return self::TOKEN . (count($this->preservedTokens) - 1) .'___'; |
||
| 239 | } |
||
| 240 | |||
| 241 | /** |
||
| 242 | * Gets the regular expression to match the specified token ID string |
||
| 243 | * @param $id |
||
| 244 | * @return string |
||
| 245 | */ |
||
| 246 | private function getPreservedTokenPlaceholderRegexById($id) |
||
| 247 | { |
||
| 248 | return '/'. self::TOKEN . $id .'___/'; |
||
| 249 | } |
||
| 250 | |||
| 251 | /** |
||
| 252 | * Registers a candidate comment token |
||
| 253 | * @param $comment |
||
| 254 | * @return string The comment token ID string |
||
| 255 | */ |
||
| 256 | private function registerComment($comment) |
||
| 257 | { |
||
| 258 | $this->comments[] = $comment; |
||
| 259 | return '/*'. self::COMMENT . (count($this->comments) - 1) .'___*/'; |
||
| 260 | } |
||
| 261 | |||
| 262 | /** |
||
| 263 | * Gets the candidate comment token ID string for the specified comment token ID |
||
| 264 | * @param $id |
||
| 265 | * @return string |
||
| 266 | */ |
||
| 267 | private function getCommentPlaceholderById($id) |
||
| 268 | { |
||
| 269 | return self::COMMENT . $id .'___'; |
||
| 270 | } |
||
| 271 | |||
| 272 | /** |
||
| 273 | * Gets the regular expression to match the specified comment token ID string |
||
| 274 | * @param $id |
||
| 275 | * @return string |
||
| 276 | */ |
||
| 277 | private function getCommentPlaceholderRegexById($id) |
||
| 278 | { |
||
| 279 | return '/'. $this->getCommentPlaceholderById($id) .'/'; |
||
| 280 | } |
||
| 281 | |||
| 282 | /** |
||
| 283 | * Registers an at rule block token |
||
| 284 | * @param $block |
||
| 285 | * @return string The comment token ID string |
||
| 286 | */ |
||
| 287 | private function registerAtRuleBlock($block) |
||
| 288 | { |
||
| 289 | $this->atRuleBlocks[] = $block; |
||
| 290 | return self::AT_RULE_BLOCK . (count($this->atRuleBlocks) - 1) .'___'; |
||
| 291 | } |
||
| 292 | |||
| 293 | /** |
||
| 294 | * Gets the regular expression to match the specified at rule block token ID string |
||
| 295 | * @param $id |
||
| 296 | * @return string |
||
| 297 | */ |
||
| 298 | private function getAtRuleBlockPlaceholderRegexById($id) |
||
| 299 | { |
||
| 300 | return '/'. self::AT_RULE_BLOCK . $id .'___/'; |
||
| 301 | } |
||
| 302 | |||
| 303 | /** |
||
| 304 | * Minifies the given input CSS string |
||
| 305 | * @param string $css |
||
| 306 | * @param int|bool $linebreakPos |
||
| 307 | * @return string |
||
| 308 | */ |
||
| 309 | private function minify($css, $linebreakPos) |
||
| 310 | { |
||
| 311 | // Restore preserved at rule blocks |
||
| 312 | for ($i = 0, $max = count($this->atRuleBlocks); $i < $max; $i++) { |
||
| 313 | $css = preg_replace( |
||
| 314 | $this->getAtRuleBlockPlaceholderRegexById($i), |
||
| 315 | $this->escapeReplacementString($this->atRuleBlocks[$i]), |
||
| 316 | $css, |
||
| 317 | 1 |
||
| 318 | ); |
||
| 319 | } |
||
| 320 | |||
| 321 | // strings are safe, now wrestle the comments |
||
| 322 | for ($i = 0, $max = count($this->comments); $i < $max; $i++) { |
||
| 323 | $comment = $this->comments[$i]; |
||
| 324 | $commentPlaceholder = $this->getCommentPlaceholderById($i); |
||
| 325 | $commentPlaceholderRegex = $this->getCommentPlaceholderRegexById($i); |
||
| 326 | |||
| 327 | // ! in the first position of the comment means preserve |
||
| 328 | // so push to the preserved tokens keeping the ! |
||
| 329 | if (preg_match('/^!/', $comment)) { |
||
| 330 | $preservedTokenPlaceholder = $this->registerPreservedToken($comment); |
||
| 331 | $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); |
||
| 332 | // Preserve new lines for /*! important comments |
||
| 333 | $css = preg_replace('/\R+\s*(\/\*'. $preservedTokenPlaceholder .')/', self::NL.'$1', $css); |
||
| 334 | $css = preg_replace('/('. $preservedTokenPlaceholder .'\*\/)\s*\R+/', '$1'.self::NL, $css); |
||
| 335 | continue; |
||
| 336 | } |
||
| 337 | |||
| 338 | // \ in the last position looks like hack for Mac/IE5 |
||
| 339 | // shorten that to /*\*/ and the next one to /**/ |
||
| 340 | if (preg_match('/\\\\$/', $comment)) { |
||
| 341 | $preservedTokenPlaceholder = $this->registerPreservedToken('\\'); |
||
| 342 | $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); |
||
| 343 | $i = $i + 1; // attn: advancing the loop |
||
| 344 | $preservedTokenPlaceholder = $this->registerPreservedToken(''); |
||
| 345 | $css = preg_replace($this->getCommentPlaceholderRegexById($i), $preservedTokenPlaceholder, $css, 1); |
||
| 346 | continue; |
||
| 347 | } |
||
| 348 | |||
| 349 | // keep empty comments after child selectors (IE7 hack) |
||
| 350 | // e.g. html >/**/ body |
||
| 351 | if (strlen($comment) === 0) { |
||
| 352 | $startIndex = $this->indexOf($css, $commentPlaceholder); |
||
| 353 | if ($startIndex > 2) { |
||
| 354 | if (substr($css, $startIndex - 3, 1) === '>') { |
||
| 355 | $preservedTokenPlaceholder = $this->registerPreservedToken(''); |
||
| 356 | $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1); |
||
| 357 | continue; |
||
| 358 | } |
||
| 359 | } |
||
| 360 | } |
||
| 361 | |||
| 362 | // in all other cases kill the comment |
||
| 363 | $css = preg_replace('/\/\*' . $commentPlaceholder . '\*\//', '', $css, 1); |
||
| 364 | } |
||
| 365 | |||
| 366 | // Normalize all whitespace strings to single spaces. Easier to work with that way. |
||
| 367 | $css = preg_replace('/\s+/', ' ', $css); |
||
| 368 | |||
| 369 | // Remove spaces before & after newlines |
||
| 370 | $css = preg_replace('/\s*'. self::NL .'\s*/', self::NL, $css); |
||
| 371 | |||
| 372 | // Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters |
||
| 373 | $css = preg_replace_callback( |
||
| 374 | '/\s*filter:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^)]+)\)/', |
||
| 375 | array($this, 'processOldIeSpecificMatrixDefinition'), |
||
| 376 | $css |
||
| 377 | ); |
||
| 378 | |||
| 379 | // Shorten & preserve calculations calc(...) since spaces are important |
||
| 380 | $css = preg_replace_callback('/calc(\(((?:[^()]+|(?1))*)\))/i', array($this, 'processCalc'), $css); |
||
| 381 | |||
| 382 | // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed |
||
| 383 | // +1.2em to 1.2em, +.8px to .8px, +2% to 2% |
||
| 384 | $css = preg_replace('/((?<!\\\\):|\s)\+(\.?\d+)/S', '$1$2', $css); |
||
| 385 | |||
| 386 | // Remove leading zeros from integer and float numbers preceded by : or a white-space |
||
| 387 | // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05 |
||
|
0 ignored issues
–
show
|
|||
| 388 | $css = preg_replace('/((?<!\\\\):|\s)(-?)0+(\.?\d+)/S', '$1$2$3', $css); |
||
| 389 | |||
| 390 | // Remove trailing zeros from float numbers preceded by : or a white-space |
||
| 391 | // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px |
||
| 392 | $css = preg_replace('/((?<!\\\\):|\s)(-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css); |
||
| 393 | |||
| 394 | // Remove trailing .0 -> -9.0 to -9 |
||
| 395 | $css = preg_replace('/((?<!\\\\):|\s)(-?\d+)\.0([^\d])/S', '$1$2$3', $css); |
||
| 396 | |||
| 397 | // Replace 0 length numbers with 0 |
||
| 398 | $css = preg_replace('/((?<!\\\\):|\s)-?\.?0+([^\d])/S', '${1}0$2', $css); |
||
| 399 | |||
| 400 | // Remove the spaces before the things that should not have spaces before them. |
||
| 401 | // But, be careful not to turn "p :link {...}" into "p:link{...}" |
||
| 402 | // Swap out any pseudo-class colons with the token, and then swap back. |
||
| 403 | $css = preg_replace_callback('/(?:^|\})[^{]*\s+:/', array($this, 'processColon'), $css); |
||
| 404 | |||
| 405 | // Remove spaces before the things that should not have spaces before them. |
||
| 406 | $css = preg_replace('/\s+([!{};:>+()\]~=,])/', '$1', $css); |
||
| 407 | |||
| 408 | // Restore spaces for !important |
||
| 409 | $css = preg_replace('/!important/i', ' !important', $css); |
||
| 410 | |||
| 411 | // bring back the colon |
||
| 412 | $css = preg_replace('/'. self::CLASSCOLON .'/', ':', $css); |
||
| 413 | |||
| 414 | // retain space for special IE6 cases |
||
| 415 | $css = preg_replace_callback('/:first-(line|letter)(\{|,)/i', array($this, 'lowercasePseudoFirst'), $css); |
||
| 416 | |||
| 417 | // no space after the end of a preserved comment |
||
| 418 | $css = preg_replace('/\*\/ /', '*/', $css); |
||
| 419 | |||
| 420 | // lowercase some popular @directives |
||
| 421 | $css = preg_replace_callback( |
||
| 422 | '/@(document|font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|namespace|page|' . |
||
| 423 | 'supports|viewport)/i', |
||
| 424 | array($this, 'lowercaseDirectives'), |
||
| 425 | $css |
||
| 426 | ); |
||
| 427 | |||
| 428 | // lowercase some more common pseudo-elements |
||
| 429 | $css = preg_replace_callback( |
||
| 430 | '/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|' . |
||
| 431 | 'last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', |
||
| 432 | array($this, 'lowercasePseudoElements'), |
||
| 433 | $css |
||
| 434 | ); |
||
| 435 | |||
| 436 | // lowercase some more common functions |
||
| 437 | $css = preg_replace_callback( |
||
| 438 | '/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', |
||
| 439 | array($this, 'lowercaseCommonFunctions'), |
||
| 440 | $css |
||
| 441 | ); |
||
| 442 | |||
| 443 | // lower case some common function that can be values |
||
| 444 | // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us |
||
| 445 | $css = preg_replace_callback( |
||
| 446 | '/([:,( ]\s*)(attr|color-stop|from|rgba|to|url|-webkit-gradient|' . |
||
| 447 | '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient))/iS', |
||
| 448 | array($this, 'lowercaseCommonFunctionsValues'), |
||
| 449 | $css |
||
| 450 | ); |
||
| 451 | |||
| 452 | // Put the space back in some cases, to support stuff like |
||
| 453 | // @media screen and (-webkit-min-device-pixel-ratio:0){ |
||
| 454 | $css = preg_replace_callback('/(\s|\)\s)(and|not|or)\(/i', array($this, 'processAtRulesOperators'), $css); |
||
| 455 | |||
| 456 | // Remove the spaces after the things that should not have spaces after them. |
||
| 457 | $css = preg_replace('/([!{}:;>+(\[~=,])\s+/S', '$1', $css); |
||
| 458 | |||
| 459 | // remove unnecessary semicolons |
||
| 460 | $css = preg_replace('/;+\}/', '}', $css); |
||
| 461 | |||
| 462 | // Fix for issue: #2528146 |
||
| 463 | // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack) |
||
| 464 | // to avoid issues on Symbian S60 3.x browsers. |
||
| 465 | $css = preg_replace('/(\*[a-z0-9\-]+\s*:[^;}]+)(\})/', '$1;$2', $css); |
||
| 466 | |||
| 467 | // Shorten zero values for safe properties only |
||
| 468 | $css = $this->shortenZeroValues($css); |
||
| 469 | |||
| 470 | // Shorten font-weight values |
||
| 471 | $css = preg_replace('/(font-weight:)bold\b/i', '${1}700', $css); |
||
| 472 | $css = preg_replace('/(font-weight:)normal\b/i', '${1}400', $css); |
||
| 473 | |||
| 474 | // Shorten suitable shorthand properties with repeated non-zero values |
||
| 475 | $css = preg_replace( |
||
| 476 | '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') (?:\2) (?:\3)(;|\}| !)/i', |
||
| 477 | '$1:$2 $3$4', |
||
| 478 | $css |
||
| 479 | ); |
||
| 480 | $css = preg_replace( |
||
| 481 | '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') (?:\3)(;|\}| !)/i', |
||
| 482 | '$1:$2 $3 $4$5', |
||
| 483 | $css |
||
| 484 | ); |
||
| 485 | |||
| 486 | // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space) |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
39% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 487 | // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space) |
||
| 488 | // This makes it more likely that it'll get further compressed in the next step. |
||
| 489 | $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'rgbToHex'), $css); |
||
| 490 | $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'hslToHex'), $css); |
||
| 491 | |||
| 492 | // Shorten colors from #AABBCC to #ABC or shorter color name. |
||
| 493 | $css = $this->shortenHexColors($css); |
||
| 494 | |||
| 495 | // Shorten long named colors: white -> #fff. |
||
| 496 | $css = $this->shortenNamedColors($css); |
||
| 497 | |||
| 498 | // shorter opacity IE filter |
||
| 499 | $css = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $css); |
||
| 500 | |||
| 501 | // Find a fraction that is used for Opera's -o-device-pixel-ratio query |
||
| 502 | // Add token to add the "\" back in later |
||
| 503 | $css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css); |
||
| 504 | |||
| 505 | // Patch new lines to avoid being removed when followed by empty rules cases |
||
| 506 | $css = preg_replace('/'. self::NL .'/', self::NL .'}', $css); |
||
| 507 | |||
| 508 | // Remove empty rules. |
||
| 509 | $css = preg_replace('/[^{};\/]+\{\}/S', '', $css); |
||
| 510 | |||
| 511 | // Restore new lines for /*! important comments |
||
| 512 | $css = preg_replace('/'. self::NL .'}/', "\n", $css); |
||
| 513 | |||
| 514 | // Add "/" back to fix Opera -o-device-pixel-ratio query |
||
| 515 | $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css); |
||
| 516 | |||
| 517 | // Replace multiple semi-colons in a row by a single one |
||
| 518 | // See SF bug #1980989 |
||
| 519 | $css = preg_replace('/;;+/', ';', $css); |
||
| 520 | |||
| 521 | // Lowercase all uppercase properties |
||
| 522 | $css = preg_replace_callback('/(\{|;)([A-Z\-]+)(:)/', array($this, 'lowercaseProperties'), $css); |
||
| 523 | |||
| 524 | // Some source control tools don't like it when files containing lines longer |
||
| 525 | // than, say 8000 characters, are checked in. The linebreak option is used in |
||
| 526 | // that case to split long lines after a specific column. |
||
| 527 | if ($linebreakPos !== false && (int) $linebreakPos >= 0) { |
||
| 528 | $linebreakPos = (int) $linebreakPos; |
||
| 529 | for ($startIndex = $i = 1, $l = strlen($css); $i < $l; $i++) { |
||
| 530 | if ($css[$i - 1] === '}' && $i - $startIndex > $linebreakPos) { |
||
| 531 | $css = $this->strSlice($css, 0, $i) . "\n" . $this->strSlice($css, $i); |
||
| 532 | $l = strlen($css); |
||
| 533 | $startIndex = $i; |
||
| 534 | } |
||
| 535 | } |
||
| 536 | } |
||
| 537 | |||
| 538 | // restore preserved comments and strings in reverse order |
||
| 539 | for ($i = count($this->preservedTokens) - 1; $i >= 0; $i--) { |
||
| 540 | $css = preg_replace( |
||
| 541 | $this->getPreservedTokenPlaceholderRegexById($i), |
||
| 542 | $this->escapeReplacementString($this->preservedTokens[$i]), |
||
| 543 | $css, |
||
| 544 | 1 |
||
| 545 | ); |
||
| 546 | } |
||
| 547 | |||
| 548 | // Trim the final string for any leading or trailing white space but respect newlines! |
||
| 549 | $css = preg_replace('/(^ | $)/', '', $css); |
||
| 550 | |||
| 551 | return $css; |
||
| 552 | } |
||
| 553 | |||
| 554 | /** |
||
| 555 | * Searches & replaces all data urls with tokens before we start compressing, |
||
| 556 | * to avoid performance issues running some of the subsequent regexes against large string chunks. |
||
| 557 | * @param string $css |
||
| 558 | * @return string |
||
| 559 | */ |
||
| 560 | private function processDataUrls($css) |
||
| 561 | { |
||
| 562 | // Leave data urls alone to increase parse performance. |
||
| 563 | $maxIndex = strlen($css) - 1; |
||
| 564 | $appenIndex = $index = $lastIndex = $offset = 0; |
||
| 565 | $sb = array(); |
||
| 566 | $pattern = '/url\(\s*(["\']?)data:/i'; |
||
| 567 | |||
| 568 | // Since we need to account for non-base64 data urls, we need to handle |
||
| 569 | // ' and ) being part of the data string. Hence switching to indexOf, |
||
| 570 | // to determine whether or not we have matching string terminators and |
||
| 571 | // handling sb appends directly, instead of using matcher.append* methods. |
||
| 572 | while (preg_match($pattern, $css, $m, 0, $offset)) { |
||
| 573 | $index = $this->indexOf($css, $m[0], $offset); |
||
| 574 | $lastIndex = $index + strlen($m[0]); |
||
| 575 | $startIndex = $index + 4; // "url(".length() |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
50% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 576 | $endIndex = $lastIndex - 1; |
||
| 577 | $terminator = $m[1]; // ', " or empty (not quoted) |
||
| 578 | $terminatorFound = false; |
||
| 579 | |||
| 580 | if (strlen($terminator) === 0) { |
||
| 581 | $terminator = ')'; |
||
| 582 | } |
||
| 583 | |||
| 584 | while ($terminatorFound === false && $endIndex+1 <= $maxIndex) { |
||
| 585 | $endIndex = $this->indexOf($css, $terminator, $endIndex + 1); |
||
| 586 | // endIndex == 0 doesn't really apply here |
||
| 587 | if ($endIndex > 0 && substr($css, $endIndex - 1, 1) !== '\\') { |
||
| 588 | $terminatorFound = true; |
||
| 589 | if (')' !== $terminator) { |
||
| 590 | $endIndex = $this->indexOf($css, ')', $endIndex); |
||
| 591 | } |
||
| 592 | } |
||
| 593 | } |
||
| 594 | |||
| 595 | // Enough searching, start moving stuff over to the buffer |
||
| 596 | $sb[] = $this->strSlice($css, $appenIndex, $index); |
||
| 597 | |||
| 598 | if ($terminatorFound) { |
||
| 599 | $token = $this->strSlice($css, $startIndex, $endIndex); |
||
| 600 | // Remove all spaces only for base64 encoded URLs. |
||
| 601 | $token = preg_replace_callback( |
||
| 602 | '/.+base64,.+/s', |
||
| 603 | array($this, 'removeSpacesFromDataUrls'), |
||
| 604 | trim($token) |
||
| 605 | ); |
||
| 606 | $preservedTokenPlaceholder = $this->registerPreservedToken($token); |
||
| 607 | $sb[] = 'url('. $preservedTokenPlaceholder .')'; |
||
| 608 | $appenIndex = $endIndex + 1; |
||
| 609 | } else { |
||
| 610 | // No end terminator found, re-add the whole match. Should we throw/warn here? |
||
| 611 | $sb[] = $this->strSlice($css, $index, $lastIndex); |
||
| 612 | $appenIndex = $lastIndex; |
||
| 613 | } |
||
| 614 | |||
| 615 | $offset = $lastIndex; |
||
| 616 | } |
||
| 617 | |||
| 618 | $sb[] = $this->strSlice($css, $appenIndex); |
||
| 619 | |||
| 620 | return implode('', $sb); |
||
| 621 | } |
||
| 622 | |||
| 623 | /** |
||
| 624 | * Shortens all zero values for a set of safe properties |
||
| 625 | * e.g. padding: 0px 1px; -> padding:0 1px |
||
| 626 | * e.g. padding: 0px 0rem 0em 0.0pc; -> padding:0 |
||
| 627 | * @param string $css |
||
| 628 | * @return string |
||
| 629 | */ |
||
| 630 | private function shortenZeroValues($css) |
||
| 631 | { |
||
| 632 | $unitsGroupReg = $this->unitsGroupRegex; |
||
| 633 | $numOrPosReg = '('. $this->numRegex .'|top|left|bottom|right|center)'; |
||
| 634 | $oneZeroSafeProperties = array( |
||
| 635 | '(?:line-)?height', |
||
| 636 | '(?:(?:min|max)-)?width', |
||
| 637 | 'top', |
||
| 638 | 'left', |
||
| 639 | 'background-position', |
||
| 640 | 'bottom', |
||
| 641 | 'right', |
||
| 642 | 'border(?:-(?:top|left|bottom|right))?(?:-width)?', |
||
| 643 | 'border-(?:(?:top|bottom)-(?:left|right)-)?radius', |
||
| 644 | 'column-(?:gap|width)', |
||
| 645 | 'margin(?:-(?:top|left|bottom|right))?', |
||
| 646 | 'outline-width', |
||
| 647 | 'padding(?:-(?:top|left|bottom|right))?' |
||
| 648 | ); |
||
| 649 | $nZeroSafeProperties = array( |
||
| 650 | 'margin', |
||
| 651 | 'padding', |
||
| 652 | 'background-position' |
||
| 653 | ); |
||
| 654 | |||
| 655 | $regStart = '/(;|\{)'; |
||
| 656 | $regEnd = '/i'; |
||
| 657 | |||
| 658 | // First zero regex start |
||
| 659 | $oneZeroRegStart = $regStart .'('. implode('|', $oneZeroSafeProperties) .'):'; |
||
| 660 | |||
| 661 | // Multiple zeros regex start |
||
| 662 | $nZerosRegStart = $regStart .'('. implode('|', $nZeroSafeProperties) .'):'; |
||
| 663 | |||
| 664 | $css = preg_replace( |
||
| 665 | array( |
||
| 666 | $oneZeroRegStart .'0'. $unitsGroupReg . $regEnd, |
||
| 667 | $nZerosRegStart . $numOrPosReg .' 0'. $unitsGroupReg . $regEnd, |
||
| 668 | $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd, |
||
| 669 | $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd |
||
| 670 | ), |
||
| 671 | array( |
||
| 672 | '$1$2:0', |
||
| 673 | '$1$2:$3 0', |
||
| 674 | '$1$2:$3 $4 0', |
||
| 675 | '$1$2:$3 $4 $5 0' |
||
| 676 | ), |
||
| 677 | $css |
||
| 678 | ); |
||
| 679 | |||
| 680 | // Remove background-position |
||
| 681 | array_pop($nZeroSafeProperties); |
||
| 682 | |||
| 683 | // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0 for safe properties only. |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
40% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 684 | $css = preg_replace( |
||
| 685 | '/('. implode('|', $nZeroSafeProperties) .'):0(?: 0){1,3}(;|\}| !)'. $regEnd, |
||
| 686 | '$1:0$2', |
||
| 687 | $css |
||
| 688 | ); |
||
| 689 | |||
| 690 | // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property. |
||
|
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
38% of this comment could be valid code. Did you maybe forget this after debugging?
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it. The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production. This check looks for comments that seem to be mostly valid code and reports them. Loading history...
|
|||
| 691 | $css = preg_replace('/(background-position):0(?: 0){2,3}(;|\}| !)'. $regEnd, '$1:0 0$2', $css); |
||
| 692 | |||
| 693 | return $css; |
||
| 694 | } |
||
| 695 | |||
| 696 | /** |
||
| 697 | * Shortens all named colors with a shorter HEX counterpart for a set of safe properties |
||
| 698 | * e.g. white -> #fff |
||
| 699 | * @param string $css |
||
| 700 | * @return string |
||
| 701 | */ |
||
| 702 | private function shortenNamedColors($css) |
||
| 703 | { |
||
| 704 | $patterns = array(); |
||
| 705 | $replacements = array(); |
||
| 706 | $longNamedColors = include 'data/named-to-hex-color-map.php'; |
||
| 707 | $propertiesWithColors = array( |
||
| 708 | 'color', |
||
| 709 | 'background(?:-color)?', |
||
| 710 | 'border(?:-(?:top|right|bottom|left|color)(?:-color)?)?', |
||
| 711 | 'outline(?:-color)?', |
||
| 712 | '(?:text|box)-shadow' |
||
| 713 | ); |
||
| 714 | |||
| 715 | $regStart = '/(;|\{)('. implode('|', $propertiesWithColors) .'):([^;}]*)\b'; |
||
| 716 | $regEnd = '\b/iS'; |
||
| 717 | |||
| 718 | foreach ($longNamedColors as $colorName => $colorCode) { |
||
| 719 | $patterns[] = $regStart . $colorName . $regEnd; |
||
| 720 | $replacements[] = '$1$2:$3'. $colorCode; |
||
| 721 | } |
||
| 722 | |||
| 723 | // Run at least 4 times to cover most cases (same color used several times for the same property) |
||
| 724 | for ($i = 0; $i < 4; $i++) { |
||
| 725 | $css = preg_replace($patterns, $replacements, $css); |
||
| 726 | } |
||
| 727 | |||
| 728 | return $css; |
||
| 729 | } |
||
| 730 | |||
| 731 | /** |
||
| 732 | * Compresses HEX color values of the form #AABBCC to #ABC or short color name. |
||
| 733 | * |
||
| 734 | * DOES NOT compress CSS ID selectors which match the above pattern (which would break things). |
||
| 735 | * e.g. #AddressForm { ... } |
||
| 736 | * |
||
| 737 | * DOES NOT compress IE filters, which have hex color values (which would break things). |
||
| 738 | * e.g. filter: chroma(color="#FFFFFF"); |
||
| 739 | * |
||
| 740 | * DOES NOT compress invalid hex values. |
||
| 741 | * e.g. background-color: #aabbccdd |
||
| 742 | * |
||
| 743 | * @param string $css |
||
| 744 | * @return string |
||
| 745 | */ |
||
| 746 | private function shortenHexColors($css) |
||
| 747 | { |
||
| 748 | // Look for hex colors inside { ... } (to avoid IDs) and |
||
| 749 | // which don't have a =, or a " in front of them (to avoid filters) |
||
| 750 | $pattern = |
||
| 751 | '/(=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS'; |
||
| 752 | $_index = $index = $lastIndex = $offset = 0; |
||
| 753 | $longHexColors = include 'data/hex-to-named-color-map.php'; |
||
| 754 | $sb = array(); |
||
| 755 | |||
| 756 | while (preg_match($pattern, $css, $m, 0, $offset)) { |
||
| 757 | $index = $this->indexOf($css, $m[0], $offset); |
||
| 758 | $lastIndex = $index + strlen($m[0]); |
||
| 759 | $isFilter = $m[1] !== null && $m[1] !== ''; |
||
| 760 | |||
| 761 | $sb[] = $this->strSlice($css, $_index, $index); |
||
| 762 | |||
| 763 | if ($isFilter) { |
||
| 764 | // Restore, maintain case, otherwise filter will break |
||
| 765 | $sb[] = $m[1] .'#'. $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]; |
||
| 766 | } else { |
||
| 767 | if (strtolower($m[2]) == strtolower($m[3]) && |
||
| 768 | strtolower($m[4]) == strtolower($m[5]) && |
||
| 769 | strtolower($m[6]) == strtolower($m[7])) { |
||
| 770 | // Compress. |
||
| 771 | $hex = '#'. strtolower($m[3] . $m[5] . $m[7]); |
||
| 772 | } else { |
||
| 773 | // Non compressible color, restore but lower case. |
||
| 774 | $hex = '#'. strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]); |
||
| 775 | } |
||
| 776 | // replace Hex colors with shorter color names |
||
| 777 | $sb[] = array_key_exists($hex, $longHexColors) ? $longHexColors[$hex] : $hex; |
||
| 778 | } |
||
| 779 | |||
| 780 | $_index = $offset = $lastIndex - strlen($m[8]); |
||
| 781 | } |
||
| 782 | |||
| 783 | $sb[] = $this->strSlice($css, $_index); |
||
| 784 | |||
| 785 | return implode('', $sb); |
||
| 786 | } |
||
| 787 | |||
| 788 | // --------------------------------------------------------------------------------------------- |
||
| 789 | // CALLBACKS |
||
| 790 | // --------------------------------------------------------------------------------------------- |
||
| 791 | |||
| 792 | private function processComments($matches) |
||
| 793 | { |
||
| 794 | $match = !empty($matches[1]) ? $matches[1] : ''; |
||
| 795 | return $this->registerComment($match); |
||
| 796 | } |
||
| 797 | |||
| 798 | private function processStrings($matches) |
||
| 799 | { |
||
| 800 | $match = $matches[0]; |
||
| 801 | $quote = substr($match, 0, 1); |
||
| 802 | $match = $this->strSlice($match, 1, -1); |
||
| 803 | |||
| 804 | // maybe the string contains a comment-like substring? |
||
| 805 | // one, maybe more? put'em back then |
||
| 806 | if (($pos = strpos($match, self::COMMENT)) !== false) { |
||
| 807 | for ($i = 0, $max = count($this->comments); $i < $max; $i++) { |
||
| 808 | $match = preg_replace( |
||
| 809 | $this->getCommentPlaceholderRegexById($i), |
||
| 810 | $this->escapeReplacementString($this->comments[$i]), |
||
| 811 | $match, |
||
| 812 | 1 |
||
| 813 | ); |
||
| 814 | } |
||
| 815 | } |
||
| 816 | |||
| 817 | // minify alpha opacity in filter strings |
||
| 818 | $match = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $match); |
||
| 819 | |||
| 820 | $preservedTokenPlaceholder = $this->registerPreservedToken($match); |
||
| 821 | return $quote . $preservedTokenPlaceholder . $quote; |
||
| 822 | } |
||
| 823 | |||
| 824 | private function processAtRuleBlocks($matches) |
||
| 825 | { |
||
| 826 | return $this->registerAtRuleBlock($matches[0]); |
||
| 827 | } |
||
| 828 | |||
| 829 | private function processCalc($matches) |
||
| 830 | { |
||
| 831 | $token = preg_replace( |
||
| 832 | '/\)([+\-]{1})/', |
||
| 833 | ') $1', |
||
| 834 | preg_replace( |
||
| 835 | '/([+\-]{1})\(/', |
||
| 836 | '$1 (', |
||
| 837 | trim(preg_replace('/\s*([*\/(),])\s*/', '$1', $matches[2])) |
||
| 838 | ) |
||
| 839 | ); |
||
| 840 | $preservedTokenPlaceholder = $this->registerPreservedToken($token); |
||
| 841 | return 'calc('. $preservedTokenPlaceholder .')'; |
||
| 842 | } |
||
| 843 | |||
| 844 | private function processOldIeSpecificMatrixDefinition($matches) |
||
| 845 | { |
||
| 846 | $preservedTokenPlaceholder = $this->registerPreservedToken($matches[1]); |
||
| 847 | return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $preservedTokenPlaceholder .')'; |
||
| 848 | } |
||
| 849 | |||
| 850 | private function processColon($matches) |
||
| 851 | { |
||
| 852 | return preg_replace('/\:/', self::CLASSCOLON, $matches[0]); |
||
| 853 | } |
||
| 854 | |||
| 855 | private function removeSpacesFromDataUrls($matches) |
||
| 856 | { |
||
| 857 | return preg_replace('/\s+/', '', $matches[0]); |
||
| 858 | } |
||
| 859 | |||
| 860 | private function rgbToHex($matches) |
||
| 861 | { |
||
| 862 | $hexColors = array(); |
||
| 863 | $rgbColors = explode(',', $matches[1]); |
||
| 864 | |||
| 865 | // Values outside the sRGB color space should be clipped (0-255) |
||
| 866 | for ($i = 0, $l = count($rgbColors); $i < $l; $i++) { |
||
| 867 | $hexColors[$i] = sprintf("%02x", $this->clampNumberSrgb($this->rgbPercentageToRgbInteger($rgbColors[$i]))); |
||
| 868 | } |
||
| 869 | |||
| 870 | // Fix for issue #2528093 |
||
| 871 | if (!preg_match('/[\s,);}]/', $matches[2])) { |
||
| 872 | $matches[2] = ' '. $matches[2]; |
||
| 873 | } |
||
| 874 | |||
| 875 | return '#'. implode('', $hexColors) . $matches[2]; |
||
| 876 | } |
||
| 877 | |||
| 878 | private function hslToHex($matches) |
||
| 879 | { |
||
| 880 | $hslValues = explode(',', $matches[1]); |
||
| 881 | |||
| 882 | $rgbColors = $this->hslToRgb($hslValues); |
||
| 883 | |||
| 884 | return $this->rgbToHex(array('', implode(',', $rgbColors), $matches[2])); |
||
| 885 | } |
||
| 886 | |||
| 887 | private function processAtRulesOperators($matches) |
||
| 888 | { |
||
| 889 | return $matches[1] . strtolower($matches[2]) .' ('; |
||
| 890 | } |
||
| 891 | |||
| 892 | private function lowercasePseudoFirst($matches) |
||
| 893 | { |
||
| 894 | return ':first-'. strtolower($matches[1]) .' '. $matches[2]; |
||
| 895 | } |
||
| 896 | |||
| 897 | private function lowercaseDirectives($matches) |
||
| 898 | { |
||
| 899 | return '@'. strtolower($matches[1]); |
||
| 900 | } |
||
| 901 | |||
| 902 | private function lowercasePseudoElements($matches) |
||
| 903 | { |
||
| 904 | return ':'. strtolower($matches[1]); |
||
| 905 | } |
||
| 906 | |||
| 907 | private function lowercaseCommonFunctions($matches) |
||
| 908 | { |
||
| 909 | return ':'. strtolower($matches[1]) .'('; |
||
| 910 | } |
||
| 911 | |||
| 912 | private function lowercaseCommonFunctionsValues($matches) |
||
| 913 | { |
||
| 914 | return $matches[1] . strtolower($matches[2]); |
||
| 915 | } |
||
| 916 | |||
| 917 | private function lowercaseProperties($matches) |
||
| 918 | { |
||
| 919 | return $matches[1] . strtolower($matches[2]) . $matches[3]; |
||
| 920 | } |
||
| 921 | |||
| 922 | // --------------------------------------------------------------------------------------------- |
||
| 923 | // HELPERS |
||
| 924 | // --------------------------------------------------------------------------------------------- |
||
| 925 | |||
| 926 | /** |
||
| 927 | * Clamps a number between a minimum and a maximum value. |
||
| 928 | * @param int|float $n the number to clamp |
||
| 929 | * @param int|float $min the lower end number allowed |
||
| 930 | * @param int|float $max the higher end number allowed |
||
| 931 | * @return int|float |
||
| 932 | */ |
||
| 933 | private function clampNumber($n, $min, $max) |
||
| 934 | { |
||
| 935 | return min(max($n, $min), $max); |
||
| 936 | } |
||
| 937 | |||
| 938 | /** |
||
| 939 | * Clamps a RGB color number outside the sRGB color space |
||
| 940 | * @param int|float $n the number to clamp |
||
| 941 | * @return int|float |
||
| 942 | */ |
||
| 943 | private function clampNumberSrgb($n) |
||
| 944 | { |
||
| 945 | return $this->clampNumber($n, 0, 255); |
||
| 946 | } |
||
| 947 | |||
| 948 | /** |
||
| 949 | * Escapes backreferences such as \1 and $1 in a regular expression replacement string |
||
| 950 | * @param $string |
||
| 951 | * @return string |
||
| 952 | */ |
||
| 953 | private function escapeReplacementString($string) |
||
| 954 | { |
||
| 955 | return addcslashes($string, '\\$'); |
||
| 956 | } |
||
| 957 | |||
| 958 | /** |
||
| 959 | * Converts a HSL color into a RGB color |
||
| 960 | * @param array $hslValues |
||
| 961 | * @return array |
||
| 962 | */ |
||
| 963 | private function hslToRgb($hslValues) |
||
| 964 | { |
||
| 965 | $h = floatval($hslValues[0]); |
||
| 966 | $s = floatval(str_replace('%', '', $hslValues[1])); |
||
| 967 | $l = floatval(str_replace('%', '', $hslValues[2])); |
||
| 968 | |||
| 969 | // Wrap and clamp, then fraction! |
||
| 970 | $h = ((($h % 360) + 360) % 360) / 360; |
||
| 971 | $s = $this->clampNumber($s, 0, 100) / 100; |
||
| 972 | $l = $this->clampNumber($l, 0, 100) / 100; |
||
| 973 | |||
| 974 | View Code Duplication | if ($s == 0) { |
|
| 975 | $r = $g = $b = $this->roundNumber(255 * $l); |
||
| 976 | } else { |
||
| 977 | $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l); |
||
| 978 | $v1 = (2 * $l) - $v2; |
||
| 979 | $r = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h + (1/3))); |
||
| 980 | $g = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h)); |
||
| 981 | $b = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h - (1/3))); |
||
| 982 | } |
||
| 983 | |||
| 984 | return array($r, $g, $b); |
||
| 985 | } |
||
| 986 | |||
| 987 | /** |
||
| 988 | * Tests and selects the correct formula for each RGB color channel |
||
| 989 | * @param $v1 |
||
| 990 | * @param $v2 |
||
| 991 | * @param $vh |
||
| 992 | * @return mixed |
||
| 993 | */ |
||
| 994 | View Code Duplication | private function hueToRgb($v1, $v2, $vh) |
|
| 995 | { |
||
| 996 | $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh); |
||
| 997 | |||
| 998 | if ($vh * 6 < 1) { |
||
| 999 | return $v1 + ($v2 - $v1) * 6 * $vh; |
||
| 1000 | } |
||
| 1001 | |||
| 1002 | if ($vh * 2 < 1) { |
||
| 1003 | return $v2; |
||
| 1004 | } |
||
| 1005 | |||
| 1006 | if ($vh * 3 < 2) { |
||
| 1007 | return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6; |
||
| 1008 | } |
||
| 1009 | |||
| 1010 | return $v1; |
||
| 1011 | } |
||
| 1012 | |||
| 1013 | /** |
||
| 1014 | * PHP port of Javascript's "indexOf" function for strings only |
||
| 1015 | * Author: Tubal Martin |
||
| 1016 | * |
||
| 1017 | * @param string $haystack |
||
| 1018 | * @param string $needle |
||
| 1019 | * @param int $offset index (optional) |
||
| 1020 | * @return int |
||
| 1021 | */ |
||
| 1022 | private function indexOf($haystack, $needle, $offset = 0) |
||
| 1023 | { |
||
| 1024 | $index = strpos($haystack, $needle, $offset); |
||
| 1025 | |||
| 1026 | return ($index !== false) ? $index : -1; |
||
| 1027 | } |
||
| 1028 | |||
| 1029 | /** |
||
| 1030 | * Convert strings like "64M" or "30" to int values |
||
| 1031 | * @param mixed $size |
||
| 1032 | * @return int |
||
| 1033 | */ |
||
| 1034 | private function normalizeInt($size) |
||
| 1035 | { |
||
| 1036 | if (is_string($size)) { |
||
| 1037 | $letter = substr($size, -1); |
||
| 1038 | $size = intval($size); |
||
| 1039 | switch ($letter) { |
||
| 1040 | case 'M': |
||
| 1041 | case 'm': |
||
| 1042 | return (int) $size * 1048576; |
||
| 1043 | case 'K': |
||
| 1044 | case 'k': |
||
| 1045 | return (int) $size * 1024; |
||
| 1046 | case 'G': |
||
| 1047 | case 'g': |
||
| 1048 | return (int) $size * 1073741824; |
||
| 1049 | } |
||
| 1050 | } |
||
| 1051 | return (int) $size; |
||
| 1052 | } |
||
| 1053 | |||
| 1054 | /** |
||
| 1055 | * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5 |
||
| 1056 | * @param $rgbPercentage |
||
| 1057 | * @return int |
||
| 1058 | */ |
||
| 1059 | private function rgbPercentageToRgbInteger($rgbPercentage) |
||
| 1060 | { |
||
| 1061 | if (strpos($rgbPercentage, '%') !== false) { |
||
| 1062 | $rgbPercentage = $this->roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55); |
||
| 1063 | } |
||
| 1064 | |||
| 1065 | return intval($rgbPercentage, 10); |
||
| 1066 | } |
||
| 1067 | |||
| 1068 | /** |
||
| 1069 | * Rounds a number to its closest integer |
||
| 1070 | * @param $n |
||
| 1071 | * @return int |
||
| 1072 | */ |
||
| 1073 | private function roundNumber($n) |
||
| 1074 | { |
||
| 1075 | return intval(round(floatval($n)), 10); |
||
| 1076 | } |
||
| 1077 | |||
| 1078 | /** |
||
| 1079 | * PHP port of Javascript's "slice" function for strings only |
||
| 1080 | * Author: Tubal Martin |
||
| 1081 | * |
||
| 1082 | * @param string $str |
||
| 1083 | * @param int $start index |
||
| 1084 | * @param int|bool $end index (optional) |
||
| 1085 | * @return string |
||
| 1086 | */ |
||
| 1087 | View Code Duplication | private function strSlice($str, $start = 0, $end = false) |
|
| 1088 | { |
||
| 1089 | if ($end !== false && ($start < 0 || $end <= 0)) { |
||
| 1090 | $max = strlen($str); |
||
| 1091 | |||
| 1092 | if ($start < 0) { |
||
| 1093 | if (($start = $max + $start) < 0) { |
||
| 1094 | return ''; |
||
| 1095 | } |
||
| 1096 | } |
||
| 1097 | |||
| 1098 | if ($end < 0) { |
||
| 1099 | if (($end = $max + $end) < 0) { |
||
| 1100 | return ''; |
||
| 1101 | } |
||
| 1102 | } |
||
| 1103 | |||
| 1104 | if ($end <= $start) { |
||
| 1105 | return ''; |
||
| 1106 | } |
||
| 1107 | } |
||
| 1108 | |||
| 1109 | $slice = ($end === false) ? substr($str, $start) : substr($str, $start, $end - $start); |
||
| 1110 | return ($slice === false) ? '' : $slice; |
||
| 1111 | } |
||
| 1112 | } |
||
| 1113 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.