PluginProbe
GetResponse Forms by Optin Cat / 1.3.8
GetResponse Forms by Optin Cat v1.3.8
1.3.4 1.3.5 1.3.6 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 2.0.0 All 36 releases
getresponse / includes / skelet / core / lib / CSSmin.php

CSSmin.php in GetResponse Forms by Optin Cat 1.3.8, at includes/skelet/core/lib/CSSmin.php

777 lines 30.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*!
4 * cssmin.php v2.4.8-4
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 TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_';
27 const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_';
28 const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___';
29 const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___';
30
31 private $comments;
32 private $preserved_tokens;
33 private $memory_limit;
34 private $max_execution_time;
35 private $pcre_backtrack_limit;
36 private $pcre_recursion_limit;
37 private $raise_php_limits;
38
39 /**
40 * @param bool|int $raise_php_limits
41 * If true, PHP settings will be raised if needed
42 */
43 public function __construct($raise_php_limits = TRUE)
44 {
45 // Set suggested PHP limits
46 $this->memory_limit = 128 * 1048576; // 128MB in bytes
47 $this->max_execution_time = 60; // 1 min
48 $this->pcre_backtrack_limit = 1000 * 1000;
49 $this->pcre_recursion_limit = 500 * 1000;
50
51 $this->raise_php_limits = (bool) $raise_php_limits;
52 }
53
54 /**
55 * Minify a string of CSS
56 * @param string $css
57 * @param int|bool $linebreak_pos
58 * @return string
59 */
60 public function run($css = '', $linebreak_pos = FALSE)
61 {
62 if (empty($css)) {
63 return '';
64 }
65
66 if ($this->raise_php_limits) {
67 $this->do_raise_php_limits();
68 }
69
70 $this->comments = array();
71 $this->preserved_tokens = array();
72
73 $start_index = 0;
74 $length = strlen($css);
75
76 $css = $this->extract_data_urls($css);
77
78 // collect all comment blocks...
79 while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) {
80 $end_index = $this->index_of($css, '*/', $start_index + 2);
81 if ($end_index < 0) {
82 $end_index = $length;
83 }
84 $comment_found = $this->str_slice($css, $start_index + 2, $end_index);
85 $this->comments[] = $comment_found;
86 $comment_preserve_string = self::COMMENT . (count($this->comments) - 1) . '___';
87 $css = $this->str_slice($css, 0, $start_index + 2) . $comment_preserve_string . $this->str_slice($css, $end_index);
88 // Set correct start_index: Fixes issue #2528130
89 $start_index = $end_index + 2 + strlen($comment_preserve_string) - strlen($comment_found);
90 }
91
92 // preserve strings so their content doesn't get accidentally minified
93 $css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", array($this, 'replace_string'), $css);
94
95 // Let's divide css code in chunks of 5.000 chars aprox.
96 // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit"
97 // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really
98 // long strings and a (sub)pattern matches a number of chars greater than
99 // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently
100 // returning NULL and $css would be empty.
101 $charset = '';
102 $charset_regexp = '/(@charset)( [^;]+;)/i';
103 $css_chunks = array();
104 $css_chunk_length = 5000; // aprox size, not exact
105 $start_index = 0;
106 $i = $css_chunk_length; // save initial iterations
107 $l = strlen($css);
108
109
110 // if the number of characters is 5000 or less, do not chunk
111 if ($l <= $css_chunk_length) {
112 $css_chunks[] = $css;
113 } else {
114 // chunk css code securely
115 while ($i < $l) {
116 $i += 50; // save iterations
117 if ($l - $start_index <= $css_chunk_length || $i >= $l) {
118 $css_chunks[] = $this->str_slice($css, $start_index);
119 break;
120 }
121 if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) {
122 // If there are two ending curly braces }} separated or not by spaces,
123 // join them in the same chunk (i.e. @media blocks)
124 $next_chunk = substr($css, $i);
125 if (preg_match('/^\s*\}/', $next_chunk)) {
126 $i = $i + $this->index_of($next_chunk, '}') + 1;
127 }
128
129 $css_chunks[] = $this->str_slice($css, $start_index, $i);
130 $start_index = $i;
131 }
132 }
133 }
134
135 // Minify each chunk
136 for ($i = 0, $n = count($css_chunks); $i < $n; $i++) {
137 $css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos);
138 // Keep the first @charset at-rule found
139 if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) {
140 $charset = strtolower($matches[1]) . $matches[2];
141 }
142 // Delete all @charset at-rules
143 $css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]);
144 }
145
146 // Update the first chunk and push the charset to the top of the file.
147 $css_chunks[0] = $charset . $css_chunks[0];
148
149 return implode('', $css_chunks);
150 }
151
152 /**
153 * Sets the memory limit for this script
154 * @param int|string $limit
155 */
156 public function set_memory_limit($limit)
157 {
158 $this->memory_limit = $this->normalize_int($limit);
159 }
160
161 /**
162 * Sets the maximum execution time for this script
163 * @param int|string $seconds
164 */
165 public function set_max_execution_time($seconds)
166 {
167 $this->max_execution_time = (int) $seconds;
168 }
169
170 /**
171 * Sets the PCRE backtrack limit for this script
172 * @param int $limit
173 */
174 public function set_pcre_backtrack_limit($limit)
175 {
176 $this->pcre_backtrack_limit = (int) $limit;
177 }
178
179 /**
180 * Sets the PCRE recursion limit for this script
181 * @param int $limit
182 */
183 public function set_pcre_recursion_limit($limit)
184 {
185 $this->pcre_recursion_limit = (int) $limit;
186 }
187
188 /**
189 * Try to configure PHP to use at least the suggested minimum settings
190 */
191 private function do_raise_php_limits()
192 {
193 $php_limits = array(
194 'memory_limit' => $this->memory_limit,
195 'max_execution_time' => $this->max_execution_time,
196 'pcre.backtrack_limit' => $this->pcre_backtrack_limit,
197 'pcre.recursion_limit' => $this->pcre_recursion_limit
198 );
199
200 // If current settings are higher respect them.
201 foreach ($php_limits as $name => $suggested) {
202 $current = $this->normalize_int(ini_get($name));
203 // memory_limit exception: allow -1 for "no memory limit".
204 if ($current > -1 && ($suggested == -1 || $current < $suggested)) {
205 ini_set($name, $suggested);
206 }
207 }
208 }
209
210 /**
211 * Does bulk of the minification
212 * @param string $css
213 * @param int|bool $linebreak_pos
214 * @return string
215 */
216 private function minify($css, $linebreak_pos)
217 {
218 // strings are safe, now wrestle the comments
219 for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
220
221 $token = $this->comments[$i];
222 $placeholder = '/' . self::COMMENT . $i . '___/';
223
224 // ! in the first position of the comment means preserve
225 // so push to the preserved tokens keeping the !
226 if (substr($token, 0, 1) === '!') {
227 $this->preserved_tokens[] = $token;
228 $token_tring = self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
229 $css = preg_replace($placeholder, $token_tring, $css, 1);
230 // Preserve new lines for /*! important comments
231 $css = preg_replace('/\s*[\n\r\f]+\s*(\/\*'. $token_tring .')/S', self::NL.'$1', $css);
232 $css = preg_replace('/('. $token_tring .'\*\/)\s*[\n\r\f]+\s*/', '$1'.self::NL, $css);
233 continue;
234 }
235
236 // \ in the last position looks like hack for Mac/IE5
237 // shorten that to /*\*/ and the next one to /**/
238 if (substr($token, (strlen($token) - 1), 1) === '\\') {
239 $this->preserved_tokens[] = '\\';
240 $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
241 $i = $i + 1; // attn: advancing the loop
242 $this->preserved_tokens[] = '';
243 $css = preg_replace('/' . self::COMMENT . $i . '___/', self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
244 continue;
245 }
246
247 // keep empty comments after child selectors (IE7 hack)
248 // e.g. html >/**/ body
249 if (strlen($token) === 0) {
250 $start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1));
251 if ($start_index > 2) {
252 if (substr($css, $start_index - 3, 1) === '>') {
253 $this->preserved_tokens[] = '';
254 $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
255 }
256 }
257 }
258
259 // in all other cases kill the comment
260 $css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1);
261 }
262
263
264 // Normalize all whitespace strings to single spaces. Easier to work with that way.
265 $css = preg_replace('/\s+/', ' ', $css);
266
267 // Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters
268 $css = preg_replace_callback('/\s*filter\:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^\)]+)\)/', array($this, 'preserve_old_IE_specific_matrix_definition'), $css);
269
270 // Shorten & preserve calculations calc(...) since spaces are important
271 $css = preg_replace_callback('/calc(\(((?:[^\(\)]+|(?1))*)\))/i', array($this, 'replace_calc'), $css);
272
273 // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed
274 // +1.2em to 1.2em, +.8px to .8px, +2% to 2%
275 $css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css);
276
277 // Remove leading zeros from integer and float numbers preceded by : or a white-space
278 // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
279 $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S', '$1$2$3', $css);
280
281 // Remove trailing zeros from float numbers preceded by : or a white-space
282 // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
283 $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css);
284
285 // Remove trailing .0 -> -9.0 to -9
286 $css = preg_replace('/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S', '$1$2$3', $css);
287
288 // Replace 0 length numbers with 0
289 $css = preg_replace('/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S', '${1}0$2', $css);
290
291 // Remove the spaces before the things that should not have spaces before them.
292 // But, be careful not to turn "p :link {...}" into "p:link{...}"
293 // Swap out any pseudo-class colons with the token, and then swap back.
294 $css = preg_replace_callback('/(?:^|\})[^\{]*\s+\:/', array($this, 'replace_colon'), $css);
295
296 // Remove spaces before the things that should not have spaces before them.
297 $css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css);
298
299 // Restore spaces for !important
300 $css = preg_replace('/\!important/i', ' !important', $css);
301
302 // bring back the colon
303 $css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css);
304
305 // retain space for special IE6 cases
306 $css = preg_replace_callback('/\:first\-(line|letter)(\{|,)/i', array($this, 'lowercase_pseudo_first'), $css);
307
308 // no space after the end of a preserved comment
309 $css = preg_replace('/\*\/ /', '*/', $css);
310
311 // lowercase some popular @directives
312 $css = preg_replace_callback('/@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/i', array($this, 'lowercase_directives'), $css);
313
314 // lowercase some more common pseudo-elements
315 $css = preg_replace_callback('/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', array($this, 'lowercase_pseudo_elements'), $css);
316
317 // lowercase some more common functions
318 $css = preg_replace_callback('/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', array($this, 'lowercase_common_functions'), $css);
319
320 // lower case some common function that can be values
321 // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us
322 $css = preg_replace_callback('/([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/iS', array($this, 'lowercase_common_functions_values'), $css);
323
324 // Put the space back in some cases, to support stuff like
325 // @media screen and (-webkit-min-device-pixel-ratio:0){
326 $css = preg_replace('/\band\(/i', 'and (', $css);
327
328 // Remove the spaces after the things that should not have spaces after them.
329 $css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css);
330
331 // remove unnecessary semicolons
332 $css = preg_replace('/;+\}/', '}', $css);
333
334 // Fix for issue: #2528146
335 // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack)
336 // to avoid issues on Symbian S60 3.x browsers.
337 $css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css);
338
339 // Replace 0 <length> and 0 <percentage> values with 0.
340 // <length> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/length
341 // <percentage> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/percentage
342 $css = preg_replace('/([^\\\\]\:|\s)0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in|px|pt|pc|%)/iS', '${1}0', $css);
343
344 // 0% step in a keyframe? restore the % unit
345 $css = preg_replace_callback('/(@[a-z\-]*?keyframes[^\{]+\{)(.*?)(\}\})/iS', array($this, 'replace_keyframe_zero'), $css);
346
347 // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0.
348 $css = preg_replace('/\:0(?: 0){1,3}(;|\}| \!)/', ':0$1', $css);
349
350 // Fix for issue: #2528142
351 // Replace text-shadow:0; with text-shadow:0 0 0;
352 $css = preg_replace('/(text-shadow\:0)(;|\}| \!)/i', '$1 0 0$2', $css);
353
354 // Replace background-position:0; with background-position:0 0;
355 // same for transform-origin
356 // Changing -webkit-mask-position: 0 0 to just a single 0 will result in the second parameter defaulting to 50% (center)
357 $css = preg_replace('/(background\-position|webkit-mask-position|(?:webkit|moz|o|ms|)\-?transform\-origin)\:0(;|\}| \!)/iS', '$1:0 0$2', $css);
358
359 // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
360 // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
361 // This makes it more likely that it'll get further compressed in the next step.
362 $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'rgb_to_hex'), $css);
363 $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'hsl_to_hex'), $css);
364
365 // Shorten colors from #AABBCC to #ABC or short color name.
366 $css = $this->compress_hex_colors($css);
367
368 // border: none to border:0, outline: none to outline:0
369 $css = preg_replace('/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\}| \!)/iS', '$1:0$2', $css);
370
371 // shorter opacity IE filter
372 $css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css);
373
374 // Find a fraction that is used for Opera's -o-device-pixel-ratio query
375 // Add token to add the "\" back in later
376 $css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css);
377
378 // Remove empty rules.
379 $css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css);
380
381 // Add "/" back to fix Opera -o-device-pixel-ratio query
382 $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css);
383
384 // Replace multiple semi-colons in a row by a single one
385 // See SF bug #1980989
386 $css = preg_replace('/;;+/', ';', $css);
387
388 // Restore new lines for /*! important comments
389 $css = preg_replace('/'. self::NL .'/', "\n", $css);
390
391 // Lowercase all uppercase properties
392 $css = preg_replace_callback('/(\{|\;)([A-Z\-]+)(\:)/', array($this, 'lowercase_properties'), $css);
393
394 // Some source control tools don't like it when files containing lines longer
395 // than, say 8000 characters, are checked in. The linebreak option is used in
396 // that case to split long lines after a specific column.
397 if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) {
398 $linebreak_pos = (int) $linebreak_pos;
399 $start_index = $i = 0;
400 while ($i < strlen($css)) {
401 $i++;
402 if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) {
403 $css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i);
404 $start_index = $i;
405 }
406 }
407 }
408
409 // restore preserved comments and strings in reverse order
410 for ($i = count($this->preserved_tokens) - 1; $i >= 0; $i--) {
411 $css = preg_replace('/' . self::TOKEN . $i . '___/', $this->preserved_tokens[$i], $css, 1);
412 }
413
414 // Trim the final string (for any leading or trailing white spaces)
415 return trim($css);
416 }
417
418 /**
419 * Utility method to replace all data urls with tokens before we start
420 * compressing, to avoid performance issues running some of the subsequent
421 * regexes against large strings chunks.
422 *
423 * @param string $css
424 * @return string
425 */
426 private function extract_data_urls($css)
427 {
428 // Leave data urls alone to increase parse performance.
429 $max_index = strlen($css) - 1;
430 $append_index = $index = $last_index = $offset = 0;
431 $sb = array();
432 $pattern = '/url\(\s*(["\']?)data\:/i';
433
434 // Since we need to account for non-base64 data urls, we need to handle
435 // ' and ) being part of the data string. Hence switching to indexOf,
436 // to determine whether or not we have matching string terminators and
437 // handling sb appends directly, instead of using matcher.append* methods.
438
439 while (preg_match($pattern, $css, $m, 0, $offset)) {
440 $index = $this->index_of($css, $m[0], $offset);
441 $last_index = $index + strlen($m[0]);
442 $start_index = $index + 4; // "url(".length()
443 $end_index = $last_index - 1;
444 $terminator = $m[1]; // ', " or empty (not quoted)
445 $found_terminator = FALSE;
446
447 if (strlen($terminator) === 0) {
448 $terminator = ')';
449 }
450
451 while ($found_terminator === FALSE && $end_index+1 <= $max_index) {
452 $end_index = $this->index_of($css, $terminator, $end_index + 1);
453
454 // endIndex == 0 doesn't really apply here
455 if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') {
456 $found_terminator = TRUE;
457 if (')' != $terminator) {
458 $end_index = $this->index_of($css, ')', $end_index);
459 }
460 }
461 }
462
463 // Enough searching, start moving stuff over to the buffer
464 $sb[] = $this->str_slice($css, $append_index, $index);
465
466 if ($found_terminator) {
467 $token = $this->str_slice($css, $start_index, $end_index);
468 $token = preg_replace('/\s+/', '', $token);
469 $this->preserved_tokens[] = $token;
470
471 $preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)';
472 $sb[] = $preserver;
473
474 $append_index = $end_index + 1;
475 } else {
476 // No end terminator found, re-add the whole match. Should we throw/warn here?
477 $sb[] = $this->str_slice($css, $index, $last_index);
478 $append_index = $last_index;
479 }
480
481 $offset = $last_index;
482 }
483
484 $sb[] = $this->str_slice($css, $append_index);
485
486 return implode('', $sb);
487 }
488
489 /**
490 * Utility method to compress hex color values of the form #AABBCC to #ABC or short color name.
491 *
492 * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
493 * e.g. #AddressForm { ... }
494 *
495 * DOES NOT compress IE filters, which have hex color values (which would break things).
496 * e.g. filter: chroma(color="#FFFFFF");
497 *
498 * DOES NOT compress invalid hex values.
499 * e.g. background-color: #aabbccdd
500 *
501 * @param string $css
502 * @return string
503 */
504 private function compress_hex_colors($css)
505 {
506 // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
507 $pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS';
508 $_index = $index = $last_index = $offset = 0;
509 $sb = array();
510 // See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors
511 $short_safe = array(
512 '#808080' => 'gray',
513 '#008000' => 'green',
514 '#800000' => 'maroon',
515 '#000080' => 'navy',
516 '#808000' => 'olive',
517 '#ffa500' => 'orange',
518 '#800080' => 'purple',
519 '#c0c0c0' => 'silver',
520 '#008080' => 'teal',
521 '#f00' => 'red'
522 );
523
524 while (preg_match($pattern, $css, $m, 0, $offset)) {
525 $index = $this->index_of($css, $m[0], $offset);
526 $last_index = $index + strlen($m[0]);
527 $is_filter = $m[1] !== null && $m[1] !== '';
528
529 $sb[] = $this->str_slice($css, $_index, $index);
530
531 if ($is_filter) {
532 // Restore, maintain case, otherwise filter will break
533 $sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7];
534 } else {
535 if (strtolower($m[2]) == strtolower($m[3]) &&
536 strtolower($m[4]) == strtolower($m[5]) &&
537 strtolower($m[6]) == strtolower($m[7])) {
538 // Compress.
539 $hex = '#' . strtolower($m[3] . $m[5] . $m[7]);
540 } else {
541 // Non compressible color, restore but lower case.
542 $hex = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]);
543 }
544 // replace Hex colors to short safe color names
545 $sb[] = array_key_exists($hex, $short_safe) ? $short_safe[$hex] : $hex;
546 }
547
548 $_index = $offset = $last_index - strlen($m[8]);
549 }
550
551 $sb[] = $this->str_slice($css, $_index);
552
553 return implode('', $sb);
554 }
555
556 /* CALLBACKS
557 * ---------------------------------------------------------------------------------------------
558 */
559
560 private function replace_string($matches)
561 {
562 $match = $matches[0];
563 $quote = substr($match, 0, 1);
564 // Must use addcslashes in PHP to avoid parsing of backslashes
565 $match = addcslashes($this->str_slice($match, 1, -1), '\\');
566
567 // maybe the string contains a comment-like substring?
568 // one, maybe more? put'em back then
569 if (($pos = $this->index_of($match, self::COMMENT)) >= 0) {
570 for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
571 $match = preg_replace('/' . self::COMMENT . $i . '___/', $this->comments[$i], $match, 1);
572 }
573 }
574
575 // minify alpha opacity in filter strings
576 $match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match);
577
578 $this->preserved_tokens[] = $match;
579 return $quote . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . $quote;
580 }
581
582 private function replace_colon($matches)
583 {
584 return preg_replace('/\:/', self::CLASSCOLON, $matches[0]);
585 }
586
587 private function replace_calc($matches)
588 {
589 $this->preserved_tokens[] = trim(preg_replace('/\s*([\*\/\(\),])\s*/', '$1', $matches[2]));
590 return 'calc('. self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')';
591 }
592
593 private function preserve_old_IE_specific_matrix_definition($matches)
594 {
595 $this->preserved_tokens[] = $matches[1];
596 return 'filter:progid:DXImageTransform.Microsoft.Matrix(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')';
597 }
598
599 private function replace_keyframe_zero($matches)
600 {
601 return $matches[1] . preg_replace('/0(\{|,[^\)\{]+\{)/', '0%$1', $matches[2]) . $matches[3];
602 }
603
604 private function rgb_to_hex($matches)
605 {
606 // Support for percentage values rgb(100%, 0%, 45%);
607 if ($this->index_of($matches[1], '%') >= 0){
608 $rgbcolors = explode(',', str_replace('%', '', $matches[1]));
609 for ($i = 0; $i < count($rgbcolors); $i++) {
610 $rgbcolors[$i] = $this->round_number(floatval($rgbcolors[$i]) * 2.55);
611 }
612 } else {
613 $rgbcolors = explode(',', $matches[1]);
614 }
615
616 // Values outside the sRGB color space should be clipped (0-255)
617 for ($i = 0; $i < count($rgbcolors); $i++) {
618 $rgbcolors[$i] = $this->clamp_number(intval($rgbcolors[$i], 10), 0, 255);
619 $rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]);
620 }
621
622 // Fix for issue #2528093
623 if (!preg_match('/[\s\,\);\}]/', $matches[2])){
624 $matches[2] = ' ' . $matches[2];
625 }
626
627 return '#' . implode('', $rgbcolors) . $matches[2];
628 }
629
630 private function hsl_to_hex($matches)
631 {
632 $values = explode(',', str_replace('%', '', $matches[1]));
633 $h = floatval($values[0]);
634 $s = floatval($values[1]);
635 $l = floatval($values[2]);
636
637 // Wrap and clamp, then fraction!
638 $h = ((($h % 360) + 360) % 360) / 360;
639 $s = $this->clamp_number($s, 0, 100) / 100;
640 $l = $this->clamp_number($l, 0, 100) / 100;
641
642 if ($s == 0) {
643 $r = $g = $b = $this->round_number(255 * $l);
644 } else {
645 $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
646 $v1 = (2 * $l) - $v2;
647 $r = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h + (1/3)));
648 $g = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h));
649 $b = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h - (1/3)));
650 }
651
652 return $this->rgb_to_hex(array('', $r.','.$g.','.$b, $matches[2]));
653 }
654
655 private function lowercase_pseudo_first($matches)
656 {
657 return ':first-'. strtolower($matches[1]) .' '. $matches[2];
658 }
659
660 private function lowercase_directives($matches)
661 {
662 return '@'. strtolower($matches[1]);
663 }
664
665 private function lowercase_pseudo_elements($matches)
666 {
667 return ':'. strtolower($matches[1]);
668 }
669
670 private function lowercase_common_functions($matches)
671 {
672 return ':'. strtolower($matches[1]) .'(';
673 }
674
675 private function lowercase_common_functions_values($matches)
676 {
677 return $matches[1] . strtolower($matches[2]);
678 }
679
680 private function lowercase_properties($matches)
681 {
682 return $matches[1].strtolower($matches[2]).$matches[3];
683 }
684
685 /* HELPERS
686 * ---------------------------------------------------------------------------------------------
687 */
688
689 private function hue_to_rgb($v1, $v2, $vh)
690 {
691 $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
692 if ($vh * 6 < 1) return $v1 + ($v2 - $v1) * 6 * $vh;
693 if ($vh * 2 < 1) return $v2;
694 if ($vh * 3 < 2) return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6;
695 return $v1;
696 }
697
698 private function round_number($n)
699 {
700 return intval(floor(floatval($n) + 0.5), 10);
701 }
702
703 private function clamp_number($n, $min, $max)
704 {
705 return min(max($n, $min), $max);
706 }
707
708 /**
709 * PHP port of Javascript's "indexOf" function for strings only
710 * Author: Tubal Martin http://blog.margenn.com
711 *
712 * @param string $haystack
713 * @param string $needle
714 * @param int $offset index (optional)
715 * @return int
716 */
717 private function index_of($haystack, $needle, $offset = 0)
718 {
719 $index = strpos($haystack, $needle, $offset);
720
721 return ($index !== FALSE) ? $index : -1;
722 }
723
724 /**
725 * PHP port of Javascript's "slice" function for strings only
726 * Author: Tubal Martin http://blog.margenn.com
727 * Tests: http://margenn.com/tubal/str_slice/
728 *
729 * @param string $str
730 * @param int $start index
731 * @param int|bool $end index (optional)
732 * @return string
733 */
734 private function str_slice($str, $start = 0, $end = FALSE)
735 {
736 if ($end !== FALSE && ($start < 0 || $end <= 0)) {
737 $max = strlen($str);
738
739 if ($start < 0) {
740 if (($start = $max + $start) < 0) {
741 return '';
742 }
743 }
744
745 if ($end < 0) {
746 if (($end = $max + $end) < 0) {
747 return '';
748 }
749 }
750
751 if ($end <= $start) {
752 return '';
753 }
754 }
755
756 $slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start);
757 return ($slice === FALSE) ? '' : $slice;
758 }
759
760 /**
761 * Convert strings like "64M" or "30" to int values
762 * @param mixed $size
763 * @return int
764 */
765 private function normalize_int($size)
766 {
767 if (is_string($size)) {
768 switch (substr($size, -1)) {
769 case 'M': case 'm': return $size * 1048576;
770 case 'K': case 'k': return $size * 1024;
771 case 'G': case 'g': return $size * 1073741824;
772 }
773 }
774
775 return (int) $size;
776 }
777 }