MDL-44017 Events: Added unit test for report_viewed events
[moodle.git] / lib / csslib.php
blobe6d13b33c79417a18dad4829dd22372045562f4e
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * This file contains CSS related class, and function for the CSS optimiser.
20 * Please see the {@link css_optimiser} class for greater detail.
22 * NOTE: these functions are not expected to be used from any addons.
24 * @package core
25 * @subpackage cssoptimiser
26 * @copyright 2012 Sam Hemelryk
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30 defined('MOODLE_INTERNAL') || die();
32 if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) {
33 // This can be also set in config.php file,
34 // it needs to be higher than the time it takes to generate all CSS content.
35 define('THEME_DESIGNER_CACHE_LIFETIME', 10);
38 /**
39 * Stores CSS in a file at the given path.
41 * This function either succeeds or throws an exception.
43 * @param theme_config $theme The theme that the CSS belongs to.
44 * @param string $csspath The path to store the CSS at.
45 * @param string $csscontent the complete CSS in one string
46 * @param bool $chunk If set to true these files will be chunked to ensure
47 * that no one file contains more than 4095 selectors.
48 * @param string $chunkurl If the CSS is be chunked then we need to know the URL
49 * to use for the chunked files.
51 function css_store_css(theme_config $theme, $csspath, $csscontent, $chunk = false, $chunkurl = null) {
52 global $CFG;
54 clearstatcache();
55 if (!file_exists(dirname($csspath))) {
56 @mkdir(dirname($csspath), $CFG->directorypermissions, true);
59 // Prevent serving of incomplete file from concurrent request,
60 // the rename() should be more atomic than fwrite().
61 ignore_user_abort(true);
63 // First up write out the single file for all those using decent browsers.
64 css_write_file($csspath, $csscontent);
66 if ($chunk) {
67 // If we need to chunk the CSS for browsers that are sub-par.
68 $css = css_chunk_by_selector_count($csscontent, $chunkurl);
69 $files = count($css);
70 $count = 1;
71 foreach ($css as $content) {
72 if ($count === $files) {
73 // If there is more than one file and this IS the last file.
74 $filename = preg_replace('#\.css$#', '.0.css', $csspath);
75 } else {
76 // If there is more than one file and this is not the last file.
77 $filename = preg_replace('#\.css$#', '.'.$count.'.css', $csspath);
79 $count++;
80 css_write_file($filename, $content);
84 ignore_user_abort(false);
85 if (connection_aborted()) {
86 die;
90 /**
91 * Writes a CSS file.
93 * @param string $filename
94 * @param string $content
96 function css_write_file($filename, $content) {
97 global $CFG;
98 if ($fp = fopen($filename.'.tmp', 'xb')) {
99 fwrite($fp, $content);
100 fclose($fp);
101 rename($filename.'.tmp', $filename);
102 @chmod($filename, $CFG->filepermissions);
103 @unlink($filename.'.tmp'); // Just in case anything fails.
108 * Takes CSS and chunks it if the number of selectors within it exceeds $maxselectors.
110 * @param string $css The CSS to chunk.
111 * @param string $importurl The URL to use for import statements.
112 * @param int $maxselectors The number of selectors to limit a chunk to.
113 * @param int $buffer The buffer size to use when chunking. You shouldn't need to reduce this
114 * unless you are lowering the maximum selectors.
115 * @return array An array of CSS chunks.
117 function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $buffer = 50) {
118 // Check if we need to chunk this CSS file.
119 $count = substr_count($css, ',') + substr_count($css, '{');
120 if ($count < $maxselectors) {
121 // The number of selectors is less then the max - we're fine.
122 return array($css);
125 // Chunk time ?!
126 // Split the CSS by array, making sure to save the delimiter in the process.
127 $parts = preg_split('#([,\}])#', $css, null, PREG_SPLIT_DELIM_CAPTURE + PREG_SPLIT_NO_EMPTY);
128 // We need to chunk the array. Each delimiter is stored separately so we multiple by 2.
129 // We also subtract 100 to give us a small buffer just in case.
130 $parts = array_chunk($parts, $maxselectors * 2 - $buffer * 2);
131 $css = array();
132 $partcount = count($parts);
133 foreach ($parts as $key => $chunk) {
134 if (end($chunk) === ',') {
135 // Damn last element was a comma.
136 // Pretty much the only way to deal with this is to take the styles from the end of the
137 // comma separated chain of selectors and apply it to the last selector we have here in place
138 // of the comma.
139 // Unit tests are essential for making sure this works.
140 $styles = false;
141 $i = $key;
142 while ($styles === false && $i < ($partcount - 1)) {
143 $i++;
144 $nextpart = $parts[$i];
145 foreach ($nextpart as $style) {
146 if (strpos($style, '{') !== false) {
147 $styles = preg_replace('#^[^\{]+#', '', $style);
148 break;
152 if ($styles === false) {
153 $styles = '/** Error chunking CSS **/';
154 } else {
155 $styles .= '}';
157 array_pop($chunk);
158 array_push($chunk, $styles);
160 $css[] = join('', $chunk);
162 // The array $css now contains CSS split into perfect sized chunks.
163 // Import statements can only appear at the very top of a CSS file.
164 // Imported sheets are applied in the the order they are imported and
165 // are followed by the contents of the CSS.
166 // This is terrible for performance.
167 // It means we must put the import statements at the top of the last chunk
168 // to ensure that things are always applied in the correct order.
169 // This way the chunked files are included in the order they were chunked
170 // followed by the contents of the final chunk in the actual sheet.
171 $importcss = '';
172 $slashargs = strpos($importurl, '.php?') === false;
173 $parts = count($css);
174 for ($i = 1; $i < $parts; $i++) {
175 if ($slashargs) {
176 $importcss .= "@import url({$importurl}/chunk{$i});\n";
177 } else {
178 $importcss .= "@import url({$importurl}&chunk={$i});\n";
181 $importcss .= end($css);
182 $css[key($css)] = $importcss;
184 return $css;
188 * Sends a cached CSS file
190 * This function sends the cached CSS file. Remember it is generated on the first
191 * request, then optimised/minified, and finally cached for serving.
193 * @param string $csspath The path to the CSS file we want to serve.
194 * @param string $etag The revision to make sure we utilise any caches.
196 function css_send_cached_css($csspath, $etag) {
197 // 60 days only - the revision may get incremented quite often.
198 $lifetime = 60*60*24*60;
200 header('Etag: "'.$etag.'"');
201 header('Content-Disposition: inline; filename="styles.php"');
202 header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
203 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
204 header('Pragma: ');
205 header('Cache-Control: public, max-age='.$lifetime);
206 header('Accept-Ranges: none');
207 header('Content-Type: text/css; charset=utf-8');
208 if (!min_enable_zlib_compression()) {
209 header('Content-Length: '.filesize($csspath));
212 readfile($csspath);
213 die;
217 * Sends a cached CSS content
219 * @param string $csscontent The actual CSS markup.
220 * @param string $etag The revision to make sure we utilise any caches.
222 function css_send_cached_css_content($csscontent, $etag) {
223 // 60 days only - the revision may get incremented quite often.
224 $lifetime = 60*60*24*60;
226 header('Etag: "'.$etag.'"');
227 header('Content-Disposition: inline; filename="styles.php"');
228 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
229 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
230 header('Pragma: ');
231 header('Cache-Control: public, max-age='.$lifetime);
232 header('Accept-Ranges: none');
233 header('Content-Type: text/css; charset=utf-8');
234 if (!min_enable_zlib_compression()) {
235 header('Content-Length: '.strlen($csscontent));
238 echo($csscontent);
239 die;
243 * Sends CSS directly without caching it.
245 * This function takes a raw CSS string, optimises it if required, and then
246 * serves it.
247 * Turning both themedesignermode and CSS optimiser on at the same time is awful
248 * for performance because of the optimiser running here. However it was done so
249 * that theme designers could utilise the optimised output during development to
250 * help them optimise their CSS... not that they should write lazy CSS.
252 * @param string $css
254 function css_send_uncached_css($css) {
255 header('Content-Disposition: inline; filename="styles_debug.php"');
256 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
257 header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) .' GMT');
258 header('Pragma: ');
259 header('Accept-Ranges: none');
260 header('Content-Type: text/css; charset=utf-8');
262 if (is_array($css)) {
263 $css = implode("\n\n", $css);
265 echo $css;
266 die;
270 * Send file not modified headers
272 * @param int $lastmodified
273 * @param string $etag
275 function css_send_unmodified($lastmodified, $etag) {
276 // 60 days only - the revision may get incremented quite often.
277 $lifetime = 60*60*24*60;
278 header('HTTP/1.1 304 Not Modified');
279 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
280 header('Cache-Control: public, max-age='.$lifetime);
281 header('Content-Type: text/css; charset=utf-8');
282 header('Etag: "'.$etag.'"');
283 if ($lastmodified) {
284 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
286 die;
290 * Sends a 404 message about CSS not being found.
292 function css_send_css_not_found() {
293 header('HTTP/1.0 404 not found');
294 die('CSS was not found, sorry.');
298 * Determines if the given value is a valid CSS colour.
300 * A CSS colour can be one of the following:
301 * - Hex colour: #AA66BB
302 * - RGB colour: rgb(0-255, 0-255, 0-255)
303 * - RGBA colour: rgba(0-255, 0-255, 0-255, 0-1)
304 * - HSL colour: hsl(0-360, 0-100%, 0-100%)
305 * - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1)
307 * Or a recognised browser colour mapping {@link css_optimiser::$htmlcolours}
309 * @param string $value The colour value to check
310 * @return bool
312 function css_is_colour($value) {
313 $value = trim($value);
315 $hex = '/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/';
316 $rgb = '#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i';
317 $rgba = '#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
318 $hsl = '#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i';
319 $hsla = '#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
321 if (in_array(strtolower($value), array('inherit'))) {
322 return true;
323 } else if (preg_match($hex, $value)) {
324 return true;
325 } else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) {
326 return true;
327 } else if (preg_match($rgb, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
328 // It is an RGB colour.
329 return true;
330 } else if (preg_match($rgba, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
331 // It is an RGBA colour.
332 return true;
333 } else if (preg_match($hsl, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
334 // It is an HSL colour.
335 return true;
336 } else if (preg_match($hsla, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
337 // It is an HSLA colour.
338 return true;
340 // Doesn't look like a colour.
341 return false;
345 * Returns true is the passed value looks like a CSS width.
346 * In order to pass this test the value must be purely numerical or end with a
347 * valid CSS unit term.
349 * @param string|int $value
350 * @return boolean
352 function css_is_width($value) {
353 $value = trim($value);
354 if (in_array(strtolower($value), array('auto', 'inherit'))) {
355 return true;
357 if ((string)$value === '0' || preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) {
358 return true;
360 return false;
364 * A simple sorting function to sort two array values on the number of items they contain
366 * @param array $a
367 * @param array $b
368 * @return int
370 function css_sort_by_count(array $a, array $b) {
371 $a = count($a);
372 $b = count($b);
373 if ($a == $b) {
374 return 0;
376 return ($a > $b) ? -1 : 1;
380 * A basic CSS optimiser that strips out unwanted things and then processes CSS organising and cleaning styles.
382 * This CSS optimiser works by reading through a CSS string one character at a
383 * time and building an object structure of the CSS.
384 * As part of that processing styles are expanded out as much as they can be to
385 * ensure we collect all mappings, at the end of the processing those styles are
386 * then combined into an optimised form to keep them as short as possible.
388 * @package core
389 * @subpackage cssoptimiser
390 * @copyright 2012 Sam Hemelryk
391 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
393 class css_optimiser {
396 * Used when the processor is about to start processing.
397 * Processing states. Used internally.
399 const PROCESSING_START = 0;
402 * Used when the processor is currently processing a selector.
403 * Processing states. Used internally.
405 const PROCESSING_SELECTORS = 0;
408 * Used when the processor is currently processing a style.
409 * Processing states. Used internally.
411 const PROCESSING_STYLES = 1;
414 * Used when the processor is currently processing a comment.
415 * Processing states. Used internally.
417 const PROCESSING_COMMENT = 2;
420 * Used when the processor is currently processing an @ rule.
421 * Processing states. Used internally.
423 const PROCESSING_ATRULE = 3;
426 * The raw string length before optimisation.
427 * Stats variables set during and after processing
428 * @var int
430 protected $rawstrlen = 0;
433 * The number of comments that were removed during optimisation.
434 * Stats variables set during and after processing
435 * @var int
437 protected $commentsincss = 0;
440 * The number of rules in the CSS before optimisation.
441 * Stats variables set during and after processing
442 * @var int
444 protected $rawrules = 0;
447 * The number of selectors using in CSS rules before optimisation.
448 * Stats variables set during and after processing
449 * @var int
451 protected $rawselectors = 0;
454 * The string length after optimisation.
455 * Stats variables set during and after processing
456 * @var int
458 protected $optimisedstrlen = 0;
461 * The number of rules after optimisation.
462 * Stats variables set during and after processing
463 * @var int
465 protected $optimisedrules = 0;
468 * The number of selectors used in rules after optimisation.
469 * Stats variables set during and after processing
470 * @var int
472 protected $optimisedselectors = 0;
475 * The start time of the optimisation.
476 * Stats variables set during and after processing
477 * @var int
479 protected $timestart = 0;
482 * The end time of the optimisation.
483 * Stats variables set during and after processing
484 * @var int
486 protected $timecomplete = 0;
489 * Will be set to any errors that may have occured during processing.
490 * This is updated only at the end of processing NOT during.
492 * @var array
494 protected $errors = array();
497 * Processes incoming CSS optimising it and then returning it.
499 * @param string $css The raw CSS to optimise
500 * @return string The optimised CSS
502 public function process($css) {
503 // Easiest win there is.
504 $css = trim($css);
506 $this->reset_stats();
507 $this->timestart = microtime(true);
508 $this->rawstrlen = strlen($css);
510 // Don't try to process files with no content... it just doesn't make sense.
511 // But we should produce an error for them, an empty CSS file will lead to a
512 // useless request for those running theme designer mode.
513 if ($this->rawstrlen === 0) {
514 $this->errors[] = 'Skipping file as it has no content.';
515 return '';
518 // First up we need to remove all line breaks - this allows us to instantly
519 // reduce our processing requirements and as we will process everything
520 // into a new structure there's really nothing lost.
521 $css = preg_replace('#\r?\n#', ' ', $css);
523 // Next remove the comments... no need to them in an optimised world and
524 // knowing they're all gone allows us to REALLY make our processing simpler.
525 $css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss);
527 $medias = array(
528 'all' => new css_media()
530 $imports = array();
531 $charset = false;
532 // Keyframes are used for CSS animation they will be processed right at the very end.
533 $keyframes = array();
535 $currentprocess = self::PROCESSING_START;
536 $currentrule = css_rule::init();
537 $currentselector = css_selector::init();
538 $inquotes = false; // ' or "
539 $inbraces = false; // {
540 $inbrackets = false; // [
541 $inparenthesis = false; // (
542 /* @var css_media $currentmedia */
543 $currentmedia = $medias['all'];
544 $currentatrule = null;
545 $suspectatrule = false;
547 $buffer = '';
548 $char = null;
550 // Next we are going to iterate over every single character in $css.
551 // This is why we removed line breaks and comments!
552 for ($i = 0; $i < $this->rawstrlen; $i++) {
553 $lastchar = $char;
554 $char = substr($css, $i, 1);
555 if ($char == '@' && $buffer == '') {
556 $suspectatrule = true;
558 switch ($currentprocess) {
559 // Start processing an @ rule e.g. @media, @page, @keyframes.
560 case self::PROCESSING_ATRULE:
561 switch ($char) {
562 case ';':
563 if (!$inbraces) {
564 $buffer .= $char;
565 if ($currentatrule == 'import') {
566 $imports[] = $buffer;
567 $currentprocess = self::PROCESSING_SELECTORS;
568 } else if ($currentatrule == 'charset') {
569 $charset = $buffer;
570 $currentprocess = self::PROCESSING_SELECTORS;
573 if ($currentatrule !== 'media') {
574 $buffer = '';
575 $currentatrule = false;
577 // Continue 1: The switch processing chars
578 // Continue 2: The switch processing the state
579 // Continue 3: The for loop.
580 continue 3;
581 case '{':
582 $regexmediabasic = '#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#';
583 $regexadvmedia = '#\s*@media\s*([^{]+)#';
584 $regexkeyframes = '#@((\-moz\-|\-webkit\-|\-ms\-|\-o\-)?keyframes)\s*([^\s]+)#';
586 if ($currentatrule == 'media' && preg_match($regexmediabasic, $buffer, $matches)) {
587 // Basic media declaration.
588 $mediatypes = str_replace(' ', '', $matches[1]);
589 if (!array_key_exists($mediatypes, $medias)) {
590 $medias[$mediatypes] = new css_media($mediatypes);
592 $currentmedia = $medias[$mediatypes];
593 $currentprocess = self::PROCESSING_SELECTORS;
594 $buffer = '';
595 } else if ($currentatrule == 'media' && preg_match($regexadvmedia, $buffer, $matches)) {
596 // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/.
597 $mediatypes = $matches[1];
598 $hash = md5($mediatypes);
599 $medias[$hash] = new css_media($mediatypes);
600 $currentmedia = $medias[$hash];
601 $currentprocess = self::PROCESSING_SELECTORS;
602 $buffer = '';
603 } else if ($currentatrule == 'keyframes' && preg_match($regexkeyframes, $buffer, $matches)) {
604 // Keyframes declaration, we treat it exactly like a @media declaration except we don't allow
605 // them to be overridden to ensure we don't mess anything up. (means we keep everything in order).
606 $keyframefor = $matches[1];
607 $keyframename = $matches[3];
608 $keyframe = new css_keyframe($keyframefor, $keyframename);
609 $keyframes[] = $keyframe;
610 $currentmedia = $keyframe;
611 $currentprocess = self::PROCESSING_SELECTORS;
612 $buffer = '';
614 // Continue 1: The switch processing chars
615 // Continue 2: The switch processing the state
616 // Continue 3: The for loop.
617 continue 3;
619 break;
620 // Start processing selectors.
621 case self::PROCESSING_START:
622 case self::PROCESSING_SELECTORS:
623 $regexatrule = '#@(media|import|charset|(\-moz\-|\-webkit\-|\-ms\-|\-o\-)?(keyframes))\s*#';
624 switch ($char) {
625 case '[':
626 $inbrackets ++;
627 $buffer .= $char;
628 // Continue 1: The switch processing chars
629 // Continue 2: The switch processing the state
630 // Continue 3: The for loop.
631 continue 3;
632 case ']':
633 $inbrackets --;
634 $buffer .= $char;
635 // Continue 1: The switch processing chars
636 // Continue 2: The switch processing the state
637 // Continue 3: The for loop.
638 continue 3;
639 case ' ':
640 if ($inbrackets) {
641 // Continue 1: The switch processing chars
642 // Continue 2: The switch processing the state
643 // Continue 3: The for loop.
644 continue 3;
646 if (!empty($buffer)) {
647 // Check for known @ rules.
648 if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
649 $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
650 $currentprocess = self::PROCESSING_ATRULE;
651 $buffer .= $char;
652 } else {
653 $currentselector->add($buffer);
654 $buffer = '';
657 $suspectatrule = false;
658 // Continue 1: The switch processing chars
659 // Continue 2: The switch processing the state
660 // Continue 3: The for loop.
661 continue 3;
662 case '{':
663 if ($inbrackets) {
664 // Continue 1: The switch processing chars
665 // Continue 2: The switch processing the state
666 // Continue 3: The for loop.
667 continue 3;
669 // Check for known @ rules.
670 if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
671 // Ahh we've been in an @rule, lets rewind one and have the @rule case process this.
672 $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
673 $currentprocess = self::PROCESSING_ATRULE;
674 $i--;
675 $suspectatrule = false;
676 // Continue 1: The switch processing chars
677 // Continue 2: The switch processing the state
678 // Continue 3: The for loop.
679 continue 3;
681 if ($buffer !== '') {
682 $currentselector->add($buffer);
684 $currentrule->add_selector($currentselector);
685 $currentselector = css_selector::init();
686 $currentprocess = self::PROCESSING_STYLES;
688 $buffer = '';
689 // Continue 1: The switch processing chars
690 // Continue 2: The switch processing the state
691 // Continue 3: The for loop.
692 continue 3;
693 case '}':
694 if ($inbrackets) {
695 // Continue 1: The switch processing chars
696 // Continue 2: The switch processing the state
697 // Continue 3: The for loop.
698 continue 3;
700 if ($currentatrule == 'media') {
701 $currentmedia = $medias['all'];
702 $currentatrule = false;
703 $buffer = '';
704 } else if (strpos($currentatrule, 'keyframes') !== false) {
705 $currentmedia = $medias['all'];
706 $currentatrule = false;
707 $buffer = '';
709 // Continue 1: The switch processing chars
710 // Continue 2: The switch processing the state
711 // Continue 3: The for loop.
712 continue 3;
713 case ',':
714 if ($inbrackets) {
715 // Continue 1: The switch processing chars
716 // Continue 2: The switch processing the state
717 // Continue 3: The for loop.
718 continue 3;
720 $currentselector->add($buffer);
721 $currentrule->add_selector($currentselector);
722 $currentselector = css_selector::init();
723 $buffer = '';
724 // Continue 1: The switch processing chars
725 // Continue 2: The switch processing the state
726 // Continue 3: The for loop.
727 continue 3;
729 break;
730 // Start processing styles.
731 case self::PROCESSING_STYLES:
732 if ($char == '"' || $char == "'") {
733 if ($inquotes === false) {
734 $inquotes = $char;
736 if ($inquotes === $char && $lastchar !== '\\') {
737 $inquotes = false;
740 if ($inquotes) {
741 $buffer .= $char;
742 continue 2;
744 switch ($char) {
745 case ';':
746 if ($inparenthesis) {
747 $buffer .= $char;
748 // Continue 1: The switch processing chars
749 // Continue 2: The switch processing the state
750 // Continue 3: The for loop.
751 continue 3;
753 $currentrule->add_style($buffer);
754 $buffer = '';
755 $inquotes = false;
756 // Continue 1: The switch processing chars
757 // Continue 2: The switch processing the state
758 // Continue 3: The for loop.
759 continue 3;
760 case '}':
761 $currentrule->add_style($buffer);
762 $this->rawselectors += $currentrule->get_selector_count();
764 $currentmedia->add_rule($currentrule);
766 $currentrule = css_rule::init();
767 $currentprocess = self::PROCESSING_SELECTORS;
768 $this->rawrules++;
769 $buffer = '';
770 $inquotes = false;
771 $inparenthesis = false;
772 // Continue 1: The switch processing chars
773 // Continue 2: The switch processing the state
774 // Continue 3: The for loop.
775 continue 3;
776 case '(':
777 $inparenthesis = true;
778 $buffer .= $char;
779 // Continue 1: The switch processing chars
780 // Continue 2: The switch processing the state
781 // Continue 3: The for loop.
782 continue 3;
783 case ')':
784 $inparenthesis = false;
785 $buffer .= $char;
786 // Continue 1: The switch processing chars
787 // Continue 2: The switch processing the state
788 // Continue 3: The for loop.
789 continue 3;
791 break;
793 $buffer .= $char;
796 foreach ($medias as $media) {
797 $this->optimise($media);
799 $css = $this->produce_css($charset, $imports, $medias, $keyframes);
801 $this->timecomplete = microtime(true);
802 return trim($css);
806 * Produces CSS for the given charset, imports, media, and keyframes
807 * @param string $charset
808 * @param array $imports
809 * @param css_media[] $medias
810 * @param css_keyframe[] $keyframes
811 * @return string
813 protected function produce_css($charset, array $imports, array $medias, array $keyframes) {
814 $css = '';
815 if (!empty($charset)) {
816 $imports[] = $charset;
818 if (!empty($imports)) {
819 $css .= implode("\n", $imports);
820 $css .= "\n\n";
823 $cssreset = array();
824 $cssstandard = array();
825 $csskeyframes = array();
827 // Process each media declaration individually.
828 foreach ($medias as $media) {
829 // If this declaration applies to all media types.
830 if (in_array('all', $media->get_types())) {
831 // Collect all rules that represet reset rules and remove them from the media object at the same time.
832 // We do this because we prioritise reset rules to the top of a CSS output. This ensures that they
833 // can't end up out of order because of optimisation.
834 $resetrules = $media->get_reset_rules(true);
835 if (!empty($resetrules)) {
836 $cssreset[] = css_writer::media('all', $resetrules);
839 // Get the standard cSS.
840 $cssstandard[] = $media->out();
843 // Finally if there are any keyframe declarations process them now.
844 if (count($keyframes) > 0) {
845 foreach ($keyframes as $keyframe) {
846 $this->optimisedrules += $keyframe->count_rules();
847 $this->optimisedselectors += $keyframe->count_selectors();
848 if ($keyframe->has_errors()) {
849 $this->errors += $keyframe->get_errors();
851 $csskeyframes[] = $keyframe->out();
855 // Join it all together.
856 $css .= join('', $cssreset);
857 $css .= join('', $cssstandard);
858 $css .= join('', $csskeyframes);
860 // Record the strlenght of the now optimised CSS.
861 $this->optimisedstrlen = strlen($css);
863 // Return the now produced CSS.
864 return $css;
868 * Optimises the CSS rules within a rule collection of one form or another
870 * @param css_rule_collection $media
871 * @return void This function acts in reference
873 protected function optimise(css_rule_collection $media) {
874 $media->organise_rules_by_selectors();
875 $this->optimisedrules += $media->count_rules();
876 $this->optimisedselectors += $media->count_selectors();
877 if ($media->has_errors()) {
878 $this->errors += $media->get_errors();
883 * Returns an array of stats from the last processing run
884 * @return string
886 public function get_stats() {
887 $stats = array(
888 'timestart' => $this->timestart,
889 'timecomplete' => $this->timecomplete,
890 'timetaken' => round($this->timecomplete - $this->timestart, 4),
891 'commentsincss' => $this->commentsincss,
892 'rawstrlen' => $this->rawstrlen,
893 'rawselectors' => $this->rawselectors,
894 'rawrules' => $this->rawrules,
895 'optimisedstrlen' => $this->optimisedstrlen,
896 'optimisedrules' => $this->optimisedrules,
897 'optimisedselectors' => $this->optimisedselectors,
898 'improvementstrlen' => '-',
899 'improvementrules' => '-',
900 'improvementselectors' => '-',
902 // Avoid division by 0 errors by checking we have valid raw values.
903 if ($this->rawstrlen > 0) {
904 $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
906 if ($this->rawrules > 0) {
907 $stats['improvementrules'] = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%';
909 if ($this->rawselectors > 0) {
910 $stats['improvementselectors'] = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%';
912 return $stats;
916 * Returns true if any errors have occured during processing
918 * @return bool
920 public function has_errors() {
921 return !empty($this->errors);
925 * Returns an array of errors that have occured
927 * @param bool $clear If set to true the errors will be cleared after being returned.
928 * @return array
930 public function get_errors($clear = false) {
931 $errors = $this->errors;
932 if ($clear) {
933 // Reset the error array.
934 $this->errors = array();
936 return $errors;
940 * Returns any errors as a string that can be included in CSS.
942 * @return string
944 public function output_errors_css() {
945 $computedcss = "/****************************************\n";
946 $computedcss .= " *--- Errors found during processing ----\n";
947 foreach ($this->errors as $error) {
948 $computedcss .= preg_replace('#^#m', '* ', $error);
950 $computedcss .= " ****************************************/\n\n";
951 return $computedcss;
955 * Returns a string to display stats about the last generation within CSS output
957 * @return string
959 public function output_stats_css() {
961 $computedcss = "/****************************************\n";
962 $computedcss .= " *------- CSS Optimisation stats --------\n";
964 if ($this->rawstrlen === 0) {
965 $computedcss .= " File not processed as it has no content /\n\n";
966 $computedcss .= " ****************************************/\n\n";
967 return $computedcss;
968 } else if ($this->rawrules === 0) {
969 $computedcss .= " File contained no rules to be processed /\n\n";
970 $computedcss .= " ****************************************/\n\n";
971 return $computedcss;
974 $stats = $this->get_stats();
976 $computedcss .= " * ".date('r')."\n";
977 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
978 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
979 $computedcss .= " *--------------- before ----------------\n";
980 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n";
981 $computedcss .= " * {$stats['rawrules']} \t rules read in\n";
982 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n";
983 $computedcss .= " *---------------- after ----------------\n";
984 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n";
985 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n";
986 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n";
987 $computedcss .= " *---------------- stats ----------------\n";
988 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n";
989 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n";
990 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n";
991 $computedcss .= " ****************************************/\n\n";
993 return $computedcss;
997 * Resets the stats ready for another fresh processing
999 public function reset_stats() {
1000 $this->commentsincss = 0;
1001 $this->optimisedrules = 0;
1002 $this->optimisedselectors = 0;
1003 $this->optimisedstrlen = 0;
1004 $this->rawrules = 0;
1005 $this->rawselectors = 0;
1006 $this->rawstrlen = 0;
1007 $this->timecomplete = 0;
1008 $this->timestart = 0;
1012 * An array of the common HTML colours that are supported by most browsers.
1014 * This reference table is used to allow us to unify colours, and will aid
1015 * us in identifying buggy CSS using unsupported colours.
1017 * @var string[]
1019 public static $htmlcolours = array(
1020 'aliceblue' => '#F0F8FF',
1021 'antiquewhite' => '#FAEBD7',
1022 'aqua' => '#00FFFF',
1023 'aquamarine' => '#7FFFD4',
1024 'azure' => '#F0FFFF',
1025 'beige' => '#F5F5DC',
1026 'bisque' => '#FFE4C4',
1027 'black' => '#000000',
1028 'blanchedalmond' => '#FFEBCD',
1029 'blue' => '#0000FF',
1030 'blueviolet' => '#8A2BE2',
1031 'brown' => '#A52A2A',
1032 'burlywood' => '#DEB887',
1033 'cadetblue' => '#5F9EA0',
1034 'chartreuse' => '#7FFF00',
1035 'chocolate' => '#D2691E',
1036 'coral' => '#FF7F50',
1037 'cornflowerblue' => '#6495ED',
1038 'cornsilk' => '#FFF8DC',
1039 'crimson' => '#DC143C',
1040 'cyan' => '#00FFFF',
1041 'darkblue' => '#00008B',
1042 'darkcyan' => '#008B8B',
1043 'darkgoldenrod' => '#B8860B',
1044 'darkgray' => '#A9A9A9',
1045 'darkgrey' => '#A9A9A9',
1046 'darkgreen' => '#006400',
1047 'darkKhaki' => '#BDB76B',
1048 'darkmagenta' => '#8B008B',
1049 'darkolivegreen' => '#556B2F',
1050 'arkorange' => '#FF8C00',
1051 'darkorchid' => '#9932CC',
1052 'darkred' => '#8B0000',
1053 'darksalmon' => '#E9967A',
1054 'darkseagreen' => '#8FBC8F',
1055 'darkslateblue' => '#483D8B',
1056 'darkslategray' => '#2F4F4F',
1057 'darkslategrey' => '#2F4F4F',
1058 'darkturquoise' => '#00CED1',
1059 'darkviolet' => '#9400D3',
1060 'deeppink' => '#FF1493',
1061 'deepskyblue' => '#00BFFF',
1062 'dimgray' => '#696969',
1063 'dimgrey' => '#696969',
1064 'dodgerblue' => '#1E90FF',
1065 'firebrick' => '#B22222',
1066 'floralwhite' => '#FFFAF0',
1067 'forestgreen' => '#228B22',
1068 'fuchsia' => '#FF00FF',
1069 'gainsboro' => '#DCDCDC',
1070 'ghostwhite' => '#F8F8FF',
1071 'gold' => '#FFD700',
1072 'goldenrod' => '#DAA520',
1073 'gray' => '#808080',
1074 'grey' => '#808080',
1075 'green' => '#008000',
1076 'greenyellow' => '#ADFF2F',
1077 'honeydew' => '#F0FFF0',
1078 'hotpink' => '#FF69B4',
1079 'indianred ' => '#CD5C5C',
1080 'indigo ' => '#4B0082',
1081 'ivory' => '#FFFFF0',
1082 'khaki' => '#F0E68C',
1083 'lavender' => '#E6E6FA',
1084 'lavenderblush' => '#FFF0F5',
1085 'lawngreen' => '#7CFC00',
1086 'lemonchiffon' => '#FFFACD',
1087 'lightblue' => '#ADD8E6',
1088 'lightcoral' => '#F08080',
1089 'lightcyan' => '#E0FFFF',
1090 'lightgoldenrodyellow' => '#FAFAD2',
1091 'lightgray' => '#D3D3D3',
1092 'lightgrey' => '#D3D3D3',
1093 'lightgreen' => '#90EE90',
1094 'lightpink' => '#FFB6C1',
1095 'lightsalmon' => '#FFA07A',
1096 'lightseagreen' => '#20B2AA',
1097 'lightskyblue' => '#87CEFA',
1098 'lightslategray' => '#778899',
1099 'lightslategrey' => '#778899',
1100 'lightsteelblue' => '#B0C4DE',
1101 'lightyellow' => '#FFFFE0',
1102 'lime' => '#00FF00',
1103 'limegreen' => '#32CD32',
1104 'linen' => '#FAF0E6',
1105 'magenta' => '#FF00FF',
1106 'maroon' => '#800000',
1107 'mediumaquamarine' => '#66CDAA',
1108 'mediumblue' => '#0000CD',
1109 'mediumorchid' => '#BA55D3',
1110 'mediumpurple' => '#9370D8',
1111 'mediumseagreen' => '#3CB371',
1112 'mediumslateblue' => '#7B68EE',
1113 'mediumspringgreen' => '#00FA9A',
1114 'mediumturquoise' => '#48D1CC',
1115 'mediumvioletred' => '#C71585',
1116 'midnightblue' => '#191970',
1117 'mintcream' => '#F5FFFA',
1118 'mistyrose' => '#FFE4E1',
1119 'moccasin' => '#FFE4B5',
1120 'navajowhite' => '#FFDEAD',
1121 'navy' => '#000080',
1122 'oldlace' => '#FDF5E6',
1123 'olive' => '#808000',
1124 'olivedrab' => '#6B8E23',
1125 'orange' => '#FFA500',
1126 'orangered' => '#FF4500',
1127 'orchid' => '#DA70D6',
1128 'palegoldenrod' => '#EEE8AA',
1129 'palegreen' => '#98FB98',
1130 'paleturquoise' => '#AFEEEE',
1131 'palevioletred' => '#D87093',
1132 'papayawhip' => '#FFEFD5',
1133 'peachpuff' => '#FFDAB9',
1134 'peru' => '#CD853F',
1135 'pink' => '#FFC0CB',
1136 'plum' => '#DDA0DD',
1137 'powderblue' => '#B0E0E6',
1138 'purple' => '#800080',
1139 'red' => '#FF0000',
1140 'rosybrown' => '#BC8F8F',
1141 'royalblue' => '#4169E1',
1142 'saddlebrown' => '#8B4513',
1143 'salmon' => '#FA8072',
1144 'sandybrown' => '#F4A460',
1145 'seagreen' => '#2E8B57',
1146 'seashell' => '#FFF5EE',
1147 'sienna' => '#A0522D',
1148 'silver' => '#C0C0C0',
1149 'skyblue' => '#87CEEB',
1150 'slateblue' => '#6A5ACD',
1151 'slategray' => '#708090',
1152 'slategrey' => '#708090',
1153 'snow' => '#FFFAFA',
1154 'springgreen' => '#00FF7F',
1155 'steelblue' => '#4682B4',
1156 'tan' => '#D2B48C',
1157 'teal' => '#008080',
1158 'thistle' => '#D8BFD8',
1159 'tomato' => '#FF6347',
1160 'transparent' => 'transparent',
1161 'turquoise' => '#40E0D0',
1162 'violet' => '#EE82EE',
1163 'wheat' => '#F5DEB3',
1164 'white' => '#FFFFFF',
1165 'whitesmoke' => '#F5F5F5',
1166 'yellow' => '#FFFF00',
1167 'yellowgreen' => '#9ACD32'
1172 * Used to prepare CSS strings
1174 * @package core
1175 * @subpackage cssoptimiser
1176 * @copyright 2012 Sam Hemelryk
1177 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1179 abstract class css_writer {
1182 * The current indent level
1183 * @var int
1185 protected static $indent = 0;
1188 * Returns true if the output should still maintain minimum formatting.
1189 * @return bool
1191 protected static function is_pretty() {
1192 global $CFG;
1193 return (!empty($CFG->cssoptimiserpretty));
1197 * Returns the indenting char to use for indenting things nicely.
1198 * @return string
1200 protected static function get_indent() {
1201 if (self::is_pretty()) {
1202 return str_repeat(" ", self::$indent);
1204 return '';
1208 * Increases the current indent
1210 protected static function increase_indent() {
1211 self::$indent++;
1215 * Decreases the current indent
1217 protected static function decrease_indent() {
1218 self::$indent--;
1222 * Returns the string to use as a separator
1223 * @return string
1225 protected static function get_separator() {
1226 return (self::is_pretty())?"\n":' ';
1230 * Returns CSS for media
1232 * @param string $typestring
1233 * @param css_rule[] $rules An array of css_rule objects
1234 * @return string
1236 public static function media($typestring, array &$rules) {
1237 $nl = self::get_separator();
1239 $output = '';
1240 if ($typestring !== 'all') {
1241 $output .= "\n@media {$typestring} {".$nl;
1242 self::increase_indent();
1244 foreach ($rules as $rule) {
1245 $output .= $rule->out().$nl;
1247 if ($typestring !== 'all') {
1248 self::decrease_indent();
1249 $output .= '}';
1251 return $output;
1255 * Returns CSS for a keyframe
1257 * @param string $for The desired declaration. e.g. keyframes, -moz-keyframes, -webkit-keyframes
1258 * @param string $name The name for the keyframe
1259 * @param css_rule[] $rules An array of rules belonging to the keyframe
1260 * @return string
1262 public static function keyframe($for, $name, array &$rules) {
1263 $output = "\n@{$for} {$name} {";
1264 foreach ($rules as $rule) {
1265 $output .= $rule->out();
1267 $output .= '}';
1268 return $output;
1272 * Returns CSS for a rule
1274 * @param string $selector
1275 * @param string $styles
1276 * @return string
1278 public static function rule($selector, $styles) {
1279 $css = self::get_indent()."{$selector}{{$styles}}";
1280 return $css;
1284 * Returns CSS for the selectors of a rule
1286 * @param css_selector[] $selectors Array of css_selector objects
1287 * @return string
1289 public static function selectors(array $selectors) {
1290 $nl = self::get_separator();
1291 $selectorstrings = array();
1292 foreach ($selectors as $selector) {
1293 $selectorstrings[] = $selector->out();
1295 return join(','.$nl, $selectorstrings);
1299 * Returns a selector given the components that make it up.
1301 * @param array $components
1302 * @return string
1304 public static function selector(array $components) {
1305 return trim(join(' ', $components));
1309 * Returns a CSS string for the provided styles
1311 * @param css_style[] $styles Array of css_style objects
1312 * @return string
1314 public static function styles(array $styles) {
1315 $bits = array();
1316 foreach ($styles as $style) {
1317 // Check if the style is an array. If it is then we are outputing an advanced style.
1318 // An advanced style is a style with one or more values, and can occur in situations like background-image
1319 // where browse specific values are being used.
1320 if (is_array($style)) {
1321 /* @var css_style[] $style */
1322 foreach ($style as $advstyle) {
1323 $bits[] = $advstyle->out();
1325 continue;
1327 $bits[] = $style->out();
1329 return join('', $bits);
1333 * Returns a style CSS
1335 * @param string $name
1336 * @param string $value
1337 * @param bool $important
1338 * @return string
1340 public static function style($name, $value, $important = false) {
1341 $value = trim($value);
1342 if ($important && strpos($value, '!important') === false) {
1343 $value .= ' !important';
1345 return "{$name}:{$value};";
1350 * A consolidatable style interface.
1352 * Class that implement this have a short-hand notation for specifying multiple styles.
1354 * @package core
1355 * @subpackage cssoptimiser
1356 * @copyright 2012 Sam Hemelryk
1357 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1359 interface core_css_consolidatable_style {
1361 * Used to consolidate several styles into a single "short-hand" style.
1362 * @param array $styles
1363 * @return mixed
1365 public static function consolidate(array $styles);
1369 * A structure to represent a CSS selector.
1371 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
1372 * rule.
1374 * @package core
1375 * @subpackage cssoptimiser
1376 * @copyright 2012 Sam Hemelryk
1377 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1379 class css_selector {
1382 * An array of selector bits
1383 * @var array
1385 protected $selectors = array();
1388 * The number of selectors.
1389 * @var int
1391 protected $count = 0;
1394 * Is null if there are no selectors, true if all selectors are basic and false otherwise.
1395 * A basic selector is one that consists of just the element type. e.g. div, span, td, a
1396 * @var bool|null
1398 protected $isbasic = null;
1401 * Initialises a new CSS selector
1402 * @return css_selector
1404 public static function init() {
1405 return new css_selector();
1409 * CSS selectors can only be created through the init method above.
1411 protected function __construct() {
1412 // Nothing to do here by default.
1416 * Adds a selector to the end of the current selector
1417 * @param string $selector
1419 public function add($selector) {
1420 $selector = trim($selector);
1421 $count = 0;
1422 $count += preg_match_all('/(\.|#)/', $selector, $matchesarray);
1423 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
1424 $count ++;
1426 // If its already false then no need to continue, its not basic.
1427 if ($this->isbasic !== false) {
1428 // If theres more than one part making up this selector its not basic.
1429 if ($count > 1) {
1430 $this->isbasic = false;
1431 } else {
1432 // Check whether it is a basic element (a-z+) with possible psuedo selector.
1433 $this->isbasic = (bool)preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector);
1436 $this->count = $count;
1437 $this->selectors[] = $selector;
1440 * Returns the number of individual components that make up this selector
1441 * @return int
1443 public function get_selector_count() {
1444 return $this->count;
1448 * Returns the selector for use in a CSS rule
1449 * @return string
1451 public function out() {
1452 return css_writer::selector($this->selectors);
1456 * Returns true is all of the selectors act only upon basic elements (no classes/ids)
1457 * @return bool
1459 public function is_basic() {
1460 return ($this->isbasic === true);
1465 * A structure to represent a CSS rule.
1467 * @package core
1468 * @subpackage cssoptimiser
1469 * @copyright 2012 Sam Hemelryk
1470 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1472 class css_rule {
1475 * An array of CSS selectors {@link css_selector}
1476 * @var css_selector[]
1478 protected $selectors = array();
1481 * An array of CSS styles {@link css_style}
1482 * @var css_style[]
1484 protected $styles = array();
1487 * Created a new CSS rule. This is the only way to create a new CSS rule externally.
1488 * @return css_rule
1490 public static function init() {
1491 return new css_rule();
1495 * Constructs a new css rule.
1497 * @param string $selector The selector or array of selectors that make up this rule.
1498 * @param css_style[] $styles An array of styles that belong to this rule.
1500 protected function __construct($selector = null, array $styles = array()) {
1501 if ($selector != null) {
1502 if (is_array($selector)) {
1503 $this->selectors = $selector;
1504 } else {
1505 $this->selectors = array($selector);
1507 $this->add_styles($styles);
1512 * Adds a new CSS selector to this rule
1514 * e.g. $rule->add_selector('.one #two.two');
1516 * @param css_selector $selector Adds a CSS selector to this rule.
1518 public function add_selector(css_selector $selector) {
1519 $this->selectors[] = $selector;
1523 * Adds a new CSS style to this rule.
1525 * @param css_style|string $style Adds a new style to this rule
1527 public function add_style($style) {
1528 if (is_string($style)) {
1529 $style = trim($style);
1530 if (empty($style)) {
1531 return;
1533 $bits = explode(':', $style, 2);
1534 if (count($bits) == 2) {
1535 list($name, $value) = array_map('trim', $bits);
1537 if (isset($name) && isset($value) && $name !== '' && $value !== '') {
1538 $style = css_style::init_automatic($name, $value);
1540 } else if ($style instanceof css_style) {
1541 // Clone the style as it may be coming from another rule and we don't
1542 // want references as it will likely be overwritten by proceeding
1543 // rules.
1544 $style = clone($style);
1546 if ($style instanceof css_style) {
1547 $name = $style->get_name();
1548 $exists = array_key_exists($name, $this->styles);
1549 // We need to find out if the current style support multiple values, or whether the style
1550 // is already set up to record multiple values. This can happen with background images which can have single
1551 // and multiple values.
1552 if ($style->allows_multiple_values() || ($exists && is_array($this->styles[$name]))) {
1553 if (!$exists) {
1554 $this->styles[$name] = array();
1555 } else if ($this->styles[$name] instanceof css_style) {
1556 $this->styles[$name] = array($this->styles[$name]);
1558 $this->styles[$name][] = $style;
1559 } else if ($exists) {
1560 $this->styles[$name]->set_value($style->get_value());
1561 } else {
1562 $this->styles[$name] = $style;
1564 } else if (is_array($style)) {
1565 // We probably shouldn't worry about processing styles here but to
1566 // be truthful it doesn't hurt.
1567 foreach ($style as $astyle) {
1568 $this->add_style($astyle);
1574 * An easy method of adding several styles at once. Just calls add_style.
1576 * This method simply iterates over the array and calls {@link css_rule::add_style()}
1577 * with each.
1579 * @param css_style[] $styles Adds an array of styles
1581 public function add_styles(array $styles) {
1582 foreach ($styles as $style) {
1583 $this->add_style($style);
1588 * Returns the array of selectors
1590 * @return css_selector[]
1592 public function get_selectors() {
1593 return $this->selectors;
1597 * Returns the array of styles
1599 * @return css_style[]
1601 public function get_styles() {
1602 return $this->styles;
1606 * Outputs this rule as a fragment of CSS
1608 * @return string
1610 public function out() {
1611 $selectors = css_writer::selectors($this->selectors);
1612 $styles = css_writer::styles($this->get_consolidated_styles());
1613 return css_writer::rule($selectors, $styles);
1617 * Consolidates all styles associated with this rule
1619 * @return css_style[] An array of consolidated styles
1621 public function get_consolidated_styles() {
1622 /* @var css_style[] $organisedstyles */
1623 $organisedstyles = array();
1624 /* @var css_style[] $finalstyles */
1625 $finalstyles = array();
1626 /* @var core_css_consolidatable_style[] $consolidate */
1627 $consolidate = array();
1628 /* @var css_style[] $advancedstyles */
1629 $advancedstyles = array();
1630 foreach ($this->styles as $style) {
1631 // If the style is an array then we are processing an advanced style. An advanced style is a style that can have
1632 // one or more values. Background-image is one such example as it can have browser specific styles.
1633 if (is_array($style)) {
1634 $single = null;
1635 $count = 0;
1636 foreach ($style as $advstyle) {
1637 /* @var css_style $advstyle */
1638 $key = $count++;
1639 $advancedstyles[$key] = $advstyle;
1640 if (!$advstyle->allows_multiple_values()) {
1641 if (!is_null($single)) {
1642 unset($advancedstyles[$single]);
1644 $single = $key;
1647 if (!is_null($single)) {
1648 $style = $advancedstyles[$single];
1650 $consolidatetoclass = $style->consolidate_to();
1651 if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) &&
1652 class_exists('css_style_'.$consolidatetoclass)) {
1653 $class = 'css_style_'.$consolidatetoclass;
1654 if (!array_key_exists($class, $consolidate)) {
1655 $consolidate[$class] = array();
1656 $organisedstyles[$class] = true;
1658 $consolidate[$class][] = $style;
1659 unset($advancedstyles[$single]);
1663 continue;
1665 $consolidatetoclass = $style->consolidate_to();
1666 if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) &&
1667 class_exists('css_style_'.$consolidatetoclass)) {
1668 $class = 'css_style_'.$consolidatetoclass;
1669 if (!array_key_exists($class, $consolidate)) {
1670 $consolidate[$class] = array();
1671 $organisedstyles[$class] = true;
1673 $consolidate[$class][] = $style;
1674 } else {
1675 $organisedstyles[$style->get_name()] = $style;
1679 foreach ($consolidate as $class => $styles) {
1680 $organisedstyles[$class] = call_user_func(array($class, 'consolidate'), $styles);
1683 foreach ($organisedstyles as $style) {
1684 if (is_array($style)) {
1685 foreach ($style as $s) {
1686 $finalstyles[] = $s;
1688 } else {
1689 $finalstyles[] = $style;
1692 $finalstyles = array_merge($finalstyles, $advancedstyles);
1693 return $finalstyles;
1697 * Splits this rules into an array of CSS rules. One for each of the selectors
1698 * that make up this rule.
1700 * @return css_rule[]
1702 public function split_by_selector() {
1703 $return = array();
1704 foreach ($this->selectors as $selector) {
1705 $return[] = new css_rule($selector, $this->styles);
1707 return $return;
1711 * Splits this rule into an array of rules. One for each of the styles that
1712 * make up this rule
1714 * @return css_rule[] Array of css_rule objects
1716 public function split_by_style() {
1717 $return = array();
1718 foreach ($this->styles as $style) {
1719 if (is_array($style)) {
1720 $return[] = new css_rule($this->selectors, $style);
1721 continue;
1723 $return[] = new css_rule($this->selectors, array($style));
1725 return $return;
1729 * Gets a hash for the styles of this rule
1731 * @return string
1733 public function get_style_hash() {
1734 return md5(css_writer::styles($this->styles));
1738 * Gets a hash for the selectors of this rule
1740 * @return string
1742 public function get_selector_hash() {
1743 return md5(css_writer::selectors($this->selectors));
1747 * Gets the number of selectors that make up this rule.
1749 * @return int
1751 public function get_selector_count() {
1752 $count = 0;
1753 foreach ($this->selectors as $selector) {
1754 $count += $selector->get_selector_count();
1756 return $count;
1760 * Returns true if there are any errors with this rule.
1762 * @return bool
1764 public function has_errors() {
1765 foreach ($this->styles as $style) {
1766 if (is_array($style)) {
1767 /* @var css_style[] $style */
1768 foreach ($style as $advstyle) {
1769 if ($advstyle->has_error()) {
1770 return true;
1773 continue;
1775 if ($style->has_error()) {
1776 return true;
1779 return false;
1783 * Returns the error strings that were recorded when processing this rule.
1785 * Before calling this function you should first call {@link css_rule::has_errors()}
1786 * to make sure there are errors (hopefully there arn't).
1788 * @return string
1790 public function get_error_string() {
1791 $css = $this->out();
1792 $errors = array();
1793 foreach ($this->styles as $style) {
1794 if (is_array($style)) {
1795 /* @var css_style[] $style */
1796 foreach ($style as $advstyle) {
1797 if ($advstyle instanceof css_style && $advstyle->has_error()) {
1798 $errors[] = " * ".$advstyle->get_last_error();
1801 } else if ($style instanceof css_style && $style->has_error()) {
1802 $errors[] = " * ".$style->get_last_error();
1805 return $css." has the following errors:\n".join("\n", $errors);
1809 * Returns true if this rule could be considered a reset rule.
1811 * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector.
1813 * @return bool
1815 public function is_reset_rule() {
1816 foreach ($this->selectors as $selector) {
1817 if (!$selector->is_basic()) {
1818 return false;
1821 return true;
1826 * An abstract CSS rule collection class.
1828 * This class is extended by things such as media and keyframe declaration. They are declarations that
1829 * group rules together for a purpose.
1830 * When no declaration is specified rules accumulate into @media all.
1832 * @package core
1833 * @subpackage cssoptimiser
1834 * @copyright 2012 Sam Hemelryk
1835 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1837 abstract class css_rule_collection {
1839 * An array of rules within this collection instance
1840 * @var css_rule[]
1842 protected $rules = array();
1845 * The collection must be able to print itself.
1847 abstract public function out();
1850 * Adds a new CSS rule to this collection instance
1852 * @param css_rule $newrule
1854 public function add_rule(css_rule $newrule) {
1855 foreach ($newrule->split_by_selector() as $rule) {
1856 $hash = $rule->get_selector_hash();
1857 if (!array_key_exists($hash, $this->rules)) {
1858 $this->rules[$hash] = $rule;
1859 } else {
1860 $this->rules[$hash]->add_styles($rule->get_styles());
1866 * Returns the rules used by this collection
1868 * @return css_rule[]
1870 public function get_rules() {
1871 return $this->rules;
1875 * Organises rules by gropuing selectors based upon the styles and consolidating
1876 * those selectors into single rules.
1878 * @return bool True if the CSS was optimised by this method
1880 public function organise_rules_by_selectors() {
1881 /* @var css_rule[] $optimisedrules */
1882 $optimisedrules = array();
1883 $beforecount = count($this->rules);
1884 $lasthash = null;
1885 /* @var css_rule $lastrule */
1886 $lastrule = null;
1887 foreach ($this->rules as $rule) {
1888 $hash = $rule->get_style_hash();
1889 if ($lastrule !== null && $lasthash !== null && $hash === $lasthash) {
1890 foreach ($rule->get_selectors() as $selector) {
1891 $lastrule->add_selector($selector);
1893 continue;
1895 $lastrule = clone($rule);
1896 $lasthash = $hash;
1897 $optimisedrules[] = $lastrule;
1899 $this->rules = array();
1900 foreach ($optimisedrules as $optimised) {
1901 $this->rules[$optimised->get_selector_hash()] = $optimised;
1903 $aftercount = count($this->rules);
1904 return ($beforecount < $aftercount);
1908 * Returns the total number of rules that exist within this collection
1910 * @return int
1912 public function count_rules() {
1913 return count($this->rules);
1917 * Returns the total number of selectors that exist within this collection
1919 * @return int
1921 public function count_selectors() {
1922 $count = 0;
1923 foreach ($this->rules as $rule) {
1924 $count += $rule->get_selector_count();
1926 return $count;
1930 * Returns true if the collection has any rules that have errors
1932 * @return boolean
1934 public function has_errors() {
1935 foreach ($this->rules as $rule) {
1936 if ($rule->has_errors()) {
1937 return true;
1940 return false;
1944 * Returns any errors that have happened within rules in this collection.
1946 * @return string[]
1948 public function get_errors() {
1949 $errors = array();
1950 foreach ($this->rules as $rule) {
1951 if ($rule->has_errors()) {
1952 $errors[] = $rule->get_error_string();
1955 return $errors;
1960 * A media class to organise rules by the media they apply to.
1962 * @package core
1963 * @subpackage cssoptimiser
1964 * @copyright 2012 Sam Hemelryk
1965 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1967 class css_media extends css_rule_collection {
1970 * An array of the different media types this instance applies to.
1971 * @var array
1973 protected $types = array();
1976 * Initalises a new media instance
1978 * @param string $for The media that the contained rules are destined for.
1980 public function __construct($for = 'all') {
1981 $types = explode(',', $for);
1982 $this->types = array_map('trim', $types);
1986 * Returns the CSS for this media and all of its rules.
1988 * @return string
1990 public function out() {
1991 return css_writer::media(join(',', $this->types), $this->rules);
1995 * Returns an array of media that this media instance applies to
1997 * @return array
1999 public function get_types() {
2000 return $this->types;
2004 * Returns all of the reset rules known by this media set.
2005 * @param bool $remove If set to true reset rules will be removed before being returned.
2006 * @return array
2008 public function get_reset_rules($remove = false) {
2009 $resetrules = array();
2010 foreach ($this->rules as $key => $rule) {
2011 if ($rule->is_reset_rule()) {
2012 $resetrules[] = clone $rule;
2013 if ($remove) {
2014 unset($this->rules[$key]);
2018 return $resetrules;
2023 * A media class to organise rules by the media they apply to.
2025 * @package core
2026 * @subpackage cssoptimiser
2027 * @copyright 2012 Sam Hemelryk
2028 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2030 class css_keyframe extends css_rule_collection {
2033 * The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
2034 * @var string
2036 protected $for;
2039 * The name for the keyframes
2040 * @var string
2042 protected $name;
2044 * Constructs a new keyframe
2046 * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
2047 * @param string $name The name for the keyframes
2049 public function __construct($for, $name) {
2050 $this->for = $for;
2051 $this->name = $name;
2054 * Returns the directive of this keyframe
2056 * e.g. keyframes, -moz-keyframes, -webkit-keyframes
2057 * @return string
2059 public function get_for() {
2060 return $this->for;
2063 * Returns the name of this keyframe
2064 * @return string
2066 public function get_name() {
2067 return $this->name;
2070 * Returns the CSS for this collection of keyframes and all of its rules.
2072 * @return string
2074 public function out() {
2075 return css_writer::keyframe($this->for, $this->name, $this->rules);
2080 * An absract class to represent CSS styles
2082 * @package core
2083 * @subpackage cssoptimiser
2084 * @copyright 2012 Sam Hemelryk
2085 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2087 abstract class css_style {
2089 /** Constant used for recongise a special empty value in a CSS style */
2090 const NULL_VALUE = '@@$NULL$@@';
2093 * The name of the style
2094 * @var string
2096 protected $name;
2099 * The value for the style
2100 * @var mixed
2102 protected $value;
2105 * If set to true this style was defined with the !important rule.
2106 * Only trolls use !important.
2107 * Don't hide under bridges.. its not good for your skin. Do the proper thing
2108 * and fix the issue don't just force a fix that will undoubtedly one day
2109 * lead to further frustration.
2110 * @var bool
2112 protected $important = false;
2115 * Gets set to true if this style has an error
2116 * @var bool
2118 protected $error = false;
2121 * The last error message that occured
2122 * @var string
2124 protected $errormessage = null;
2127 * Initialises a new style.
2129 * This is the only public way to create a style to ensure they that appropriate
2130 * style class is used if it exists.
2132 * @param string $name The name of the style.
2133 * @param string $value The value of the style.
2134 * @return css_style_generic
2136 public static function init_automatic($name, $value) {
2137 $cleanedname = preg_replace('#[^a-zA-Z0-9]+#', '', $name);
2138 $specificclass = 'css_style_'.$cleanedname;
2139 if (class_exists($specificclass)) {
2140 $style = call_user_func(array($specificclass, 'init'), $value);
2141 if ($cleanedname !== $name && !is_array($style)) {
2142 $style->set_actual_name($name);
2144 return $style;
2146 return new css_style_generic($name, $value);
2150 * Creates a new style when given its name and value
2152 * @param string $name The name of the style.
2153 * @param string $value The value of the style.
2155 protected function __construct($name, $value) {
2156 $this->name = $name;
2157 $this->set_value($value);
2161 * Sets the value for the style
2163 * @param string $value
2165 final public function set_value($value) {
2166 $value = trim($value);
2167 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
2168 if ($important) {
2169 $value = substr($value, 0, -(strlen($matches[1])));
2170 $value = rtrim($value);
2172 if (!$this->important || $important) {
2173 $this->value = $this->clean_value($value);
2174 $this->important = $important;
2176 if (!$this->is_valid()) {
2177 $this->set_error('Invalid value for '.$this->name);
2182 * Returns true if the value associated with this style is valid
2184 * @return bool
2186 public function is_valid() {
2187 return true;
2191 * Returns the name for the style
2193 * @return string
2195 public function get_name() {
2196 return $this->name;
2200 * Returns the value for the style
2202 * @param bool $includeimportant If set to true and the rule is important !important postfix will be used.
2203 * @return string
2205 public function get_value($includeimportant = true) {
2206 $value = $this->value;
2207 if ($includeimportant && $this->important) {
2208 $value .= ' !important';
2210 return $value;
2214 * Returns the style ready for use in CSS
2216 * @param string|null $value A value to use to override the value for this style.
2217 * @return string
2219 public function out($value = null) {
2220 if (is_null($value)) {
2221 $value = $this->get_value();
2223 return css_writer::style($this->name, $value, $this->important);
2227 * This can be overridden by a specific style allowing it to clean its values
2228 * consistently.
2230 * @param mixed $value
2231 * @return mixed
2233 protected function clean_value($value) {
2234 return $value;
2238 * If this particular style can be consolidated into another style this function
2239 * should return the style that it can be consolidated into.
2241 * @return string|null
2243 public function consolidate_to() {
2244 return null;
2248 * Sets the last error message.
2250 * @param string $message
2252 protected function set_error($message) {
2253 $this->error = true;
2254 $this->errormessage = $message;
2258 * Returns true if an error has occured
2260 * @return bool
2262 public function has_error() {
2263 return $this->error;
2267 * Returns the last error that occured or null if no errors have happened.
2269 * @return string
2271 public function get_last_error() {
2272 return $this->errormessage;
2276 * Returns true if the value for this style is the special null value.
2278 * This should only be overriden in circumstances where a shorthand style can lead
2279 * to move explicit styles being overwritten. Not a common place occurenace.
2281 * Example:
2282 * This occurs if the shorthand background property was used but no proper value
2283 * was specified for this style.
2284 * This leads to a null value being used unless otherwise overridden.
2286 * @return bool
2288 public function is_special_empty_value() {
2289 return false;
2293 * Returns true if this style permits multiple values.
2295 * This occurs for styles such as background image that can have browser specific values that need to be maintained because
2296 * of course we don't know what browser the user is using, and optimisation occurs before caching.
2297 * Thus we must always server all values we encounter in the order we encounter them for when this is set to true.
2299 * @return boolean False by default, true if the style supports muliple values.
2301 public function allows_multiple_values() {
2302 return false;
2306 * Returns true if this style was marked important.
2307 * @return bool
2309 public function is_important() {
2310 return !empty($this->important);
2314 * Sets the important flag for this style and its current value.
2315 * @param bool $important
2317 public function set_important($important = true) {
2318 $this->important = (bool) $important;
2322 * Sets the actual name used within the style.
2324 * This method allows us to support browser hacks like *width:0;
2326 * @param string $name
2328 public function set_actual_name($name) {
2329 $this->name = $name;
2334 * A generic CSS style class to use when a more specific class does not exist.
2336 * @package core
2337 * @subpackage cssoptimiser
2338 * @copyright 2012 Sam Hemelryk
2339 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2341 class css_style_generic extends css_style {
2344 * Cleans incoming values for typical things that can be optimised.
2346 * @param mixed $value Cleans the provided value optimising it if possible
2347 * @return string
2349 protected function clean_value($value) {
2350 if (trim($value) == '0px') {
2351 $value = 0;
2352 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) {
2353 $value = '#'.strtoupper($matches[1]);
2355 return $value;
2360 * A colour CSS style
2362 * @package core
2363 * @subpackage cssoptimiser
2364 * @copyright 2012 Sam Hemelryk
2365 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2367 class css_style_color extends css_style {
2370 * Creates a new colour style
2372 * @param mixed $value Initialises a new colour style
2373 * @return css_style_color
2375 public static function init($value) {
2376 return new css_style_color('color', $value);
2380 * Cleans the colour unifing it to a 6 char hash colour if possible
2381 * Doing this allows us to associate identical colours being specified in
2382 * different ways. e.g. Red, red, #F00, and #F00000
2384 * @param mixed $value Cleans the provided value optimising it if possible
2385 * @return string
2387 protected function clean_value($value) {
2388 $value = trim($value);
2389 if (css_is_colour($value)) {
2390 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) {
2391 $value = '#'.strtoupper($matches[1]);
2392 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) {
2393 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
2394 $value = '#'.strtoupper($value);
2395 } else if (array_key_exists(strtolower($value), css_optimiser::$htmlcolours)) {
2396 $value = css_optimiser::$htmlcolours[strtolower($value)];
2399 return $value;
2403 * Returns the colour style for use within CSS.
2404 * Will return an optimised hash colour.
2406 * e.g #123456
2407 * #123 instead of #112233
2408 * #F00 instead of red
2410 * @param string $overridevalue If provided then this value will be used instead
2411 * of the styles current value.
2412 * @return string
2414 public function out($overridevalue = null) {
2415 if ($overridevalue === null) {
2416 $overridevalue = $this->value;
2418 return parent::out(self::shrink_value($overridevalue));
2422 * Shrinks the colour value is possible.
2424 * @param string $value Shrinks the current value to an optimial form if possible
2425 * @return string
2427 public static function shrink_value($value) {
2428 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) {
2429 return '#'.$matches[1].$matches[2].$matches[3];
2431 return $value;
2435 * Returns true if the value is a valid colour.
2437 * @return bool
2439 public function is_valid() {
2440 return css_is_colour($this->value);
2445 * A width style
2447 * @package core
2448 * @subpackage cssoptimiser
2449 * @copyright 2012 Sam Hemelryk
2450 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2452 class css_style_width extends css_style {
2455 * Checks if the width is valid
2456 * @return bool
2458 public function is_valid() {
2459 return css_is_width($this->value);
2463 * Cleans the provided value
2465 * @param mixed $value Cleans the provided value optimising it if possible
2466 * @return string
2468 protected function clean_value($value) {
2469 if (!css_is_width($value)) {
2470 // Note we don't actually change the value to something valid. That
2471 // would be bad for futureproofing.
2472 $this->set_error('Invalid width specified for '.$this->name);
2473 } else if (preg_match('#^0\D+$#', $value)) {
2474 $value = 0;
2476 return trim($value);
2480 * Initialises a new width style
2482 * @param mixed $value The value this style has
2483 * @return css_style_width
2485 public static function init($value) {
2486 return new css_style_width('width', $value);
2491 * A margin style
2493 * @package core
2494 * @subpackage cssoptimiser
2495 * @copyright 2012 Sam Hemelryk
2496 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2498 class css_style_margin extends css_style_width implements core_css_consolidatable_style {
2501 * Initialises a margin style.
2503 * In this case we split the margin into several other margin styles so that
2504 * we can properly condense overrides and then reconsolidate them later into
2505 * an optimal form.
2507 * @param string $value The value the style has
2508 * @return array An array of margin values that can later be consolidated
2510 public static function init($value) {
2511 $important = '';
2512 if (strpos($value, '!important') !== false) {
2513 $important = ' !important';
2514 $value = str_replace('!important', '', $value);
2517 $value = preg_replace('#\s+#', ' ', trim($value));
2518 $bits = explode(' ', $value, 4);
2520 $top = $right = $bottom = $left = null;
2521 if (count($bits) > 0) {
2522 $top = $right = $bottom = $left = array_shift($bits);
2524 if (count($bits) > 0) {
2525 $right = $left = array_shift($bits);
2527 if (count($bits) > 0) {
2528 $bottom = array_shift($bits);
2530 if (count($bits) > 0) {
2531 $left = array_shift($bits);
2533 return array(
2534 new css_style_margintop('margin-top', $top.$important),
2535 new css_style_marginright('margin-right', $right.$important),
2536 new css_style_marginbottom('margin-bottom', $bottom.$important),
2537 new css_style_marginleft('margin-left', $left.$important)
2542 * Consolidates individual margin styles into a single margin style
2544 * @param css_style[] $styles
2545 * @return css_style[] An array of consolidated styles
2547 public static function consolidate(array $styles) {
2548 if (count($styles) != 4) {
2549 return $styles;
2552 $someimportant = false;
2553 $allimportant = null;
2554 $notimportantequal = null;
2555 $firstvalue = null;
2556 foreach ($styles as $style) {
2557 if ($style->is_important()) {
2558 $someimportant = true;
2559 if ($allimportant === null) {
2560 $allimportant = true;
2562 } else {
2563 if ($allimportant === true) {
2564 $allimportant = false;
2566 if ($firstvalue == null) {
2567 $firstvalue = $style->get_value(false);
2568 $notimportantequal = true;
2569 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
2570 $notimportantequal = false;
2575 if ($someimportant && !$allimportant && !$notimportantequal) {
2576 return $styles;
2579 if ($someimportant && !$allimportant && $notimportantequal) {
2580 $return = array(
2581 new css_style_margin('margin', $firstvalue)
2583 foreach ($styles as $style) {
2584 if ($style->is_important()) {
2585 $return[] = $style;
2588 return $return;
2589 } else {
2590 $top = null;
2591 $right = null;
2592 $bottom = null;
2593 $left = null;
2594 foreach ($styles as $style) {
2595 switch ($style->get_name()) {
2596 case 'margin-top' :
2597 $top = $style->get_value(false);
2598 break;
2599 case 'margin-right' :
2600 $right = $style->get_value(false);
2601 break;
2602 case 'margin-bottom' :
2603 $bottom = $style->get_value(false);
2604 break;
2605 case 'margin-left' :
2606 $left = $style->get_value(false);
2607 break;
2610 if ($top == $bottom && $left == $right) {
2611 if ($top == $left) {
2612 $returnstyle = new css_style_margin('margin', $top);
2613 } else {
2614 $returnstyle = new css_style_margin('margin', "{$top} {$left}");
2616 } else if ($left == $right) {
2617 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom}");
2618 } else {
2619 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}");
2621 if ($allimportant) {
2622 $returnstyle->set_important();
2624 return array($returnstyle);
2630 * A margin top style
2632 * @package core
2633 * @subpackage cssoptimiser
2634 * @copyright 2012 Sam Hemelryk
2635 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2637 class css_style_margintop extends css_style_margin {
2640 * A simple init, just a single style
2642 * @param string $value The value the style has
2643 * @return css_style_margintop
2645 public static function init($value) {
2646 return new css_style_margintop('margin-top', $value);
2650 * This style can be consolidated into a single margin style
2652 * @return string
2654 public function consolidate_to() {
2655 return 'margin';
2660 * A margin right style
2662 * @package core
2663 * @subpackage cssoptimiser
2664 * @copyright 2012 Sam Hemelryk
2665 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2667 class css_style_marginright extends css_style_margin {
2670 * A simple init, just a single style
2672 * @param string $value The value the style has
2673 * @return css_style_margintop
2675 public static function init($value) {
2676 return new css_style_marginright('margin-right', $value);
2680 * This style can be consolidated into a single margin style
2682 * @return string
2684 public function consolidate_to() {
2685 return 'margin';
2690 * A margin bottom style
2692 * @package core
2693 * @subpackage cssoptimiser
2694 * @copyright 2012 Sam Hemelryk
2695 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2697 class css_style_marginbottom extends css_style_margin {
2700 * A simple init, just a single style
2702 * @param string $value The value the style has
2703 * @return css_style_margintop
2705 public static function init($value) {
2706 return new css_style_marginbottom('margin-bottom', $value);
2710 * This style can be consolidated into a single margin style
2712 * @return string
2714 public function consolidate_to() {
2715 return 'margin';
2720 * A margin left style
2722 * @package core
2723 * @subpackage cssoptimiser
2724 * @copyright 2012 Sam Hemelryk
2725 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2727 class css_style_marginleft extends css_style_margin {
2730 * A simple init, just a single style
2732 * @param string $value The value the style has
2733 * @return css_style_margintop
2735 public static function init($value) {
2736 return new css_style_marginleft('margin-left', $value);
2740 * This style can be consolidated into a single margin style
2742 * @return string
2744 public function consolidate_to() {
2745 return 'margin';
2750 * A border style
2752 * @package core
2753 * @subpackage cssoptimiser
2754 * @copyright 2012 Sam Hemelryk
2755 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2757 class css_style_border extends css_style implements core_css_consolidatable_style {
2760 * Initalises the border style into an array of individual style compontents
2762 * @param string $value The value the style has
2763 * @return css_style_bordercolor
2765 public static function init($value) {
2766 $value = preg_replace('#\s+#', ' ', $value);
2767 $bits = explode(' ', $value, 3);
2769 $return = array();
2770 if (count($bits) > 0) {
2771 $width = array_shift($bits);
2772 if (!css_style_borderwidth::is_border_width($width)) {
2773 $width = '0';
2775 $return[] = css_style_bordertopwidth::init($width);
2776 $return[] = css_style_borderrightwidth::init($width);
2777 $return[] = css_style_borderbottomwidth::init($width);
2778 $return[] = css_style_borderleftwidth::init($width);
2780 if (count($bits) > 0) {
2781 $style = array_shift($bits);
2782 $return[] = css_style_bordertopstyle::init($style);
2783 $return[] = css_style_borderrightstyle::init($style);
2784 $return[] = css_style_borderbottomstyle::init($style);
2785 $return[] = css_style_borderleftstyle::init($style);
2787 if (count($bits) > 0) {
2788 $colour = array_shift($bits);
2789 $return[] = css_style_bordertopcolor::init($colour);
2790 $return[] = css_style_borderrightcolor::init($colour);
2791 $return[] = css_style_borderbottomcolor::init($colour);
2792 $return[] = css_style_borderleftcolor::init($colour);
2794 return $return;
2798 * Consolidates all border styles into a single style
2800 * @param css_style[] $styles An array of border styles
2801 * @return css_style[] An optimised array of border styles
2803 public static function consolidate(array $styles) {
2805 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2806 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2807 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2809 foreach ($styles as $style) {
2810 switch ($style->get_name()) {
2811 case 'border-top-width':
2812 $borderwidths['top'] = $style->get_value();
2813 break;
2814 case 'border-right-width':
2815 $borderwidths['right'] = $style->get_value();
2816 break;
2817 case 'border-bottom-width':
2818 $borderwidths['bottom'] = $style->get_value();
2819 break;
2820 case 'border-left-width':
2821 $borderwidths['left'] = $style->get_value();
2822 break;
2824 case 'border-top-style':
2825 $borderstyles['top'] = $style->get_value();
2826 break;
2827 case 'border-right-style':
2828 $borderstyles['right'] = $style->get_value();
2829 break;
2830 case 'border-bottom-style':
2831 $borderstyles['bottom'] = $style->get_value();
2832 break;
2833 case 'border-left-style':
2834 $borderstyles['left'] = $style->get_value();
2835 break;
2837 case 'border-top-color':
2838 $bordercolors['top'] = css_style_color::shrink_value($style->get_value());
2839 break;
2840 case 'border-right-color':
2841 $bordercolors['right'] = css_style_color::shrink_value($style->get_value());
2842 break;
2843 case 'border-bottom-color':
2844 $bordercolors['bottom'] = css_style_color::shrink_value($style->get_value());
2845 break;
2846 case 'border-left-color':
2847 $bordercolors['left'] = css_style_color::shrink_value($style->get_value());
2848 break;
2852 $uniquewidths = count(array_unique($borderwidths));
2853 $uniquestyles = count(array_unique($borderstyles));
2854 $uniquecolors = count(array_unique($bordercolors));
2856 $nullwidths = in_array(null, $borderwidths, true);
2857 $nullstyles = in_array(null, $borderstyles, true);
2858 $nullcolors = in_array(null, $bordercolors, true);
2860 $allwidthsthesame = ($uniquewidths === 1)?1:0;
2861 $allstylesthesame = ($uniquestyles === 1)?1:0;
2862 $allcolorsthesame = ($uniquecolors === 1)?1:0;
2864 $allwidthsnull = $allwidthsthesame && $nullwidths;
2865 $allstylesnull = $allstylesthesame && $nullstyles;
2866 $allcolorsnull = $allcolorsthesame && $nullcolors;
2868 /* @var css_style[] $return */
2869 $return = array();
2870 if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
2871 // Everything is null still... boo.
2872 return array(new css_style_border('border', ''));
2874 } else if ($allwidthsnull && $allstylesnull) {
2876 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2877 return $return;
2879 } else if ($allwidthsnull && $allcolorsnull) {
2881 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2882 return $return;
2884 } else if ($allcolorsnull && $allstylesnull) {
2886 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2887 return $return;
2891 if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 3) {
2893 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']);
2895 } else if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 2) {
2897 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) {
2899 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']);
2900 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2902 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) {
2904 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']);
2905 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2907 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) {
2909 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']);
2910 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2912 } else {
2913 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2914 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2915 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2918 } else if (!$nullwidths && !$nullcolors && !$nullstyles &&
2919 max(array_count_values($borderwidths)) == 3 &&
2920 max(array_count_values($borderstyles)) == 3 &&
2921 max(array_count_values($bordercolors)) == 3) {
2923 $widthkeys = array();
2924 $stylekeys = array();
2925 $colorkeys = array();
2927 foreach ($borderwidths as $key => $value) {
2928 if (!array_key_exists($value, $widthkeys)) {
2929 $widthkeys[$value] = array();
2931 $widthkeys[$value][] = $key;
2933 usort($widthkeys, 'css_sort_by_count');
2934 $widthkeys = array_values($widthkeys);
2936 foreach ($borderstyles as $key => $value) {
2937 if (!array_key_exists($value, $stylekeys)) {
2938 $stylekeys[$value] = array();
2940 $stylekeys[$value][] = $key;
2942 usort($stylekeys, 'css_sort_by_count');
2943 $stylekeys = array_values($stylekeys);
2945 foreach ($bordercolors as $key => $value) {
2946 if (!array_key_exists($value, $colorkeys)) {
2947 $colorkeys[$value] = array();
2949 $colorkeys[$value][] = $key;
2951 usort($colorkeys, 'css_sort_by_count');
2952 $colorkeys = array_values($colorkeys);
2954 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
2955 $key = $widthkeys[0][0];
2956 self::build_style_string($return, 'css_style_border', 'border',
2957 $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2958 $key = $widthkeys[1][0];
2959 self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key,
2960 $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2961 } else {
2962 self::build_style_string($return, 'css_style_bordertop', 'border-top',
2963 $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2964 self::build_style_string($return, 'css_style_borderright', 'border-right',
2965 $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2966 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
2967 $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2968 self::build_style_string($return, 'css_style_borderleft', 'border-left',
2969 $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2971 } else {
2972 self::build_style_string($return, 'css_style_bordertop', 'border-top',
2973 $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2974 self::build_style_string($return, 'css_style_borderright', 'border-right',
2975 $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2976 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
2977 $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2978 self::build_style_string($return, 'css_style_borderleft', 'border-left',
2979 $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2981 foreach ($return as $key => $style) {
2982 if ($style->get_value() == '') {
2983 unset($return[$key]);
2986 return $return;
2990 * Border styles get consolidated to a single border style.
2992 * @return string
2994 public function consolidate_to() {
2995 return 'border';
2999 * Consolidates a series of border styles into an optimised array of border
3000 * styles by looking at the direction of the border and prioritising that
3001 * during the optimisation.
3003 * @param array $array An array to add styles into during consolidation. Passed by reference.
3004 * @param string $class The class type to initalise
3005 * @param string $style The style to create
3006 * @param string|array $top The top value
3007 * @param string $right The right value
3008 * @param string $bottom The bottom value
3009 * @param string $left The left value
3010 * @return bool
3012 public static function consolidate_styles_by_direction(&$array, $class, $style,
3013 $top, $right = null, $bottom = null, $left = null) {
3014 if (is_array($top)) {
3015 $right = $top['right'];
3016 $bottom = $top['bottom'];
3017 $left = $top['left'];
3018 $top = $top['top'];
3021 if ($top == $bottom && $left == $right && $top == $left) {
3022 if (is_null($top)) {
3023 $array[] = new $class($style, '');
3024 } else {
3025 $array[] = new $class($style, $top);
3027 } else if ($top == null || $right == null || $bottom == null || $left == null) {
3028 if ($top !== null) {
3029 $array[] = new $class(str_replace('border-', 'border-top-', $style), $top);
3031 if ($right !== null) {
3032 $array[] = new $class(str_replace('border-', 'border-right-', $style), $right);
3034 if ($bottom !== null) {
3035 $array[] = new $class(str_replace('border-', 'border-bottom-', $style), $bottom);
3037 if ($left !== null) {
3038 $array[] = new $class(str_replace('border-', 'border-left-', $style), $left);
3040 } else if ($top == $bottom && $left == $right) {
3041 $array[] = new $class($style, $top.' '.$right);
3042 } else if ($left == $right) {
3043 $array[] = new $class($style, $top.' '.$right.' '.$bottom);
3044 } else {
3045 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left);
3047 return true;
3051 * Builds a border style for a set of width, style, and colour values
3053 * @param array $array An array into which the generated style is added
3054 * @param string $class The class type to initialise
3055 * @param string $cssstyle The style to use
3056 * @param string $width The width of the border
3057 * @param string $style The style of the border
3058 * @param string $color The colour of the border
3059 * @return bool
3061 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) {
3062 if (!is_null($width) && !is_null($style) && !is_null($color)) {
3063 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color);
3064 } else if (!is_null($width) && !is_null($style) && is_null($color)) {
3065 $array[] = new $class($cssstyle, $width.' '.$style);
3066 } else if (!is_null($width) && is_null($style) && is_null($color)) {
3067 $array[] = new $class($cssstyle, $width);
3068 } else {
3069 if (!is_null($width)) {
3070 $array[] = new $class($cssstyle, $width);
3072 if (!is_null($style)) {
3073 $array[] = new $class($cssstyle, $style);
3075 if (!is_null($color)) {
3076 $array[] = new $class($cssstyle, $color);
3079 return true;
3084 * A border colour style
3086 * @package core
3087 * @subpackage cssoptimiser
3088 * @copyright 2012 Sam Hemelryk
3089 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3091 class css_style_bordercolor extends css_style_color {
3094 * Creates a new border colour style
3096 * Based upon the colour style
3098 * @param mixed $value
3099 * @return Array of css_style_bordercolor
3101 public static function init($value) {
3102 $value = preg_replace('#\s+#', ' ', $value);
3103 $bits = explode(' ', $value, 4);
3105 $top = $right = $bottom = $left = null;
3106 if (count($bits) > 0) {
3107 $top = $right = $bottom = $left = array_shift($bits);
3109 if (count($bits) > 0) {
3110 $right = $left = array_shift($bits);
3112 if (count($bits) > 0) {
3113 $bottom = array_shift($bits);
3115 if (count($bits) > 0) {
3116 $left = array_shift($bits);
3118 return array(
3119 css_style_bordertopcolor::init($top),
3120 css_style_borderrightcolor::init($right),
3121 css_style_borderbottomcolor::init($bottom),
3122 css_style_borderleftcolor::init($left)
3127 * Consolidate this to a single border style
3129 * @return string
3131 public function consolidate_to() {
3132 return 'border';
3136 * Cleans the value
3138 * @param string $value Cleans the provided value optimising it if possible
3139 * @return string
3141 protected function clean_value($value) {
3142 $values = explode(' ', $value);
3143 $values = array_map('parent::clean_value', $values);
3144 return join (' ', $values);
3148 * Outputs this style
3150 * @param string $overridevalue
3151 * @return string
3153 public function out($overridevalue = null) {
3154 if ($overridevalue === null) {
3155 $overridevalue = $this->value;
3157 $values = explode(' ', $overridevalue);
3158 $values = array_map('css_style_color::shrink_value', $values);
3159 return parent::out(join (' ', $values));
3164 * A border left style
3166 * @package core
3167 * @subpackage cssoptimiser
3168 * @copyright 2012 Sam Hemelryk
3169 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3171 class css_style_borderleft extends css_style_generic {
3174 * Initialises the border left style into individual components
3176 * @param string $value
3177 * @return array Array of css_style_borderleftwidth|css_style_borderleftstyle|css_style_borderleftcolor
3179 public static function init($value) {
3180 $value = preg_replace('#\s+#', ' ', $value);
3181 $bits = explode(' ', $value, 3);
3183 $return = array();
3184 if (count($bits) > 0) {
3185 $return[] = css_style_borderleftwidth::init(array_shift($bits));
3187 if (count($bits) > 0) {
3188 $return[] = css_style_borderleftstyle::init(array_shift($bits));
3190 if (count($bits) > 0) {
3191 $return[] = css_style_borderleftcolor::init(array_shift($bits));
3193 return $return;
3197 * Consolidate this to a single border style
3199 * @return string
3201 public function consolidate_to() {
3202 return 'border';
3207 * A border right style
3209 * @package core
3210 * @subpackage cssoptimiser
3211 * @copyright 2012 Sam Hemelryk
3212 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3214 class css_style_borderright extends css_style_generic {
3217 * Initialises the border right style into individual components
3219 * @param string $value The value of the style
3220 * @return array Array of css_style_borderrightwidth|css_style_borderrightstyle|css_style_borderrightcolor
3222 public static function init($value) {
3223 $value = preg_replace('#\s+#', ' ', $value);
3224 $bits = explode(' ', $value, 3);
3226 $return = array();
3227 if (count($bits) > 0) {
3228 $return[] = css_style_borderrightwidth::init(array_shift($bits));
3230 if (count($bits) > 0) {
3231 $return[] = css_style_borderrightstyle::init(array_shift($bits));
3233 if (count($bits) > 0) {
3234 $return[] = css_style_borderrightcolor::init(array_shift($bits));
3236 return $return;
3240 * Consolidate this to a single border style
3242 * @return string
3244 public function consolidate_to() {
3245 return 'border';
3250 * A border top style
3252 * @package core
3253 * @subpackage cssoptimiser
3254 * @copyright 2012 Sam Hemelryk
3255 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3257 class css_style_bordertop extends css_style_generic {
3260 * Initialises the border top style into individual components
3262 * @param string $value The value of the style
3263 * @return array Array of css_style_bordertopwidth|css_style_bordertopstyle|css_style_bordertopcolor
3265 public static function init($value) {
3266 $value = preg_replace('#\s+#', ' ', $value);
3267 $bits = explode(' ', $value, 3);
3269 $return = array();
3270 if (count($bits) > 0) {
3271 $return[] = css_style_bordertopwidth::init(array_shift($bits));
3273 if (count($bits) > 0) {
3274 $return[] = css_style_bordertopstyle::init(array_shift($bits));
3276 if (count($bits) > 0) {
3277 $return[] = css_style_bordertopcolor::init(array_shift($bits));
3279 return $return;
3283 * Consolidate this to a single border style
3285 * @return string
3287 public function consolidate_to() {
3288 return 'border';
3293 * A border bottom style
3295 * @package core
3296 * @subpackage cssoptimiser
3297 * @copyright 2012 Sam Hemelryk
3298 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3300 class css_style_borderbottom extends css_style_generic {
3303 * Initialises the border bottom style into individual components
3305 * @param string $value The value of the style
3306 * @return array Array of css_style_borderbottomwidth|css_style_borderbottomstyle|css_style_borderbottomcolor
3308 public static function init($value) {
3309 $value = preg_replace('#\s+#', ' ', $value);
3310 $bits = explode(' ', $value, 3);
3312 $return = array();
3313 if (count($bits) > 0) {
3314 $return[] = css_style_borderbottomwidth::init(array_shift($bits));
3316 if (count($bits) > 0) {
3317 $return[] = css_style_borderbottomstyle::init(array_shift($bits));
3319 if (count($bits) > 0) {
3320 $return[] = css_style_borderbottomcolor::init(array_shift($bits));
3322 return $return;
3326 * Consolidate this to a single border style
3328 * @return string
3330 public function consolidate_to() {
3331 return 'border';
3336 * A border width style
3338 * @package core
3339 * @subpackage cssoptimiser
3340 * @copyright 2012 Sam Hemelryk
3341 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3343 class css_style_borderwidth extends css_style_width {
3346 * Creates a new border colour style
3348 * Based upon the colour style
3350 * @param string $value The value of the style
3351 * @return array Array of css_style_border*width
3353 public static function init($value) {
3354 $value = preg_replace('#\s+#', ' ', $value);
3355 $bits = explode(' ', $value, 4);
3357 $top = $right = $bottom = $left = null;
3358 if (count($bits) > 0) {
3359 $top = $right = $bottom = $left = array_shift($bits);
3361 if (count($bits) > 0) {
3362 $right = $left = array_shift($bits);
3364 if (count($bits) > 0) {
3365 $bottom = array_shift($bits);
3367 if (count($bits) > 0) {
3368 $left = array_shift($bits);
3370 return array(
3371 css_style_bordertopwidth::init($top),
3372 css_style_borderrightwidth::init($right),
3373 css_style_borderbottomwidth::init($bottom),
3374 css_style_borderleftwidth::init($left)
3379 * Consolidate this to a single border style
3381 * @return string
3383 public function consolidate_to() {
3384 return 'border';
3388 * Checks if the width is valid
3389 * @return bool
3391 public function is_valid() {
3392 return self::is_border_width($this->value);
3396 * Cleans the provided value
3398 * @param mixed $value Cleans the provided value optimising it if possible
3399 * @return string
3401 protected function clean_value($value) {
3402 $isvalid = self::is_border_width($value);
3403 if (!$isvalid) {
3404 $this->set_error('Invalid width specified for '.$this->name);
3405 } else if (preg_match('#^0\D+$#', $value)) {
3406 return '0';
3408 return trim($value);
3412 * Returns true if the provided value is a permitted border width
3413 * @param string $value The value to check
3414 * @return bool
3416 public static function is_border_width($value) {
3417 $altwidthvalues = array('thin', 'medium', 'thick');
3418 return css_is_width($value) || in_array($value, $altwidthvalues);
3423 * A border style style
3425 * @package core
3426 * @subpackage cssoptimiser
3427 * @copyright 2012 Sam Hemelryk
3428 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3430 class css_style_borderstyle extends css_style_generic {
3433 * Creates a new border colour style
3435 * Based upon the colour style
3437 * @param string $value The value of the style
3438 * @return array Array of css_style_border*style
3440 public static function init($value) {
3441 $value = preg_replace('#\s+#', ' ', $value);
3442 $bits = explode(' ', $value, 4);
3444 $top = $right = $bottom = $left = null;
3445 if (count($bits) > 0) {
3446 $top = $right = $bottom = $left = array_shift($bits);
3448 if (count($bits) > 0) {
3449 $right = $left = array_shift($bits);
3451 if (count($bits) > 0) {
3452 $bottom = array_shift($bits);
3454 if (count($bits) > 0) {
3455 $left = array_shift($bits);
3457 return array(
3458 css_style_bordertopstyle::init($top),
3459 css_style_borderrightstyle::init($right),
3460 css_style_borderbottomstyle::init($bottom),
3461 css_style_borderleftstyle::init($left)
3466 * Consolidate this to a single border style
3468 * @return string
3470 public function consolidate_to() {
3471 return 'border';
3476 * A border top colour style
3478 * @package core
3479 * @subpackage cssoptimiser
3480 * @copyright 2012 Sam Hemelryk
3481 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3483 class css_style_bordertopcolor extends css_style_bordercolor {
3486 * Initialises this style object
3488 * @param string $value The value of the style
3489 * @return css_style_bordertopcolor
3491 public static function init($value) {
3492 return new css_style_bordertopcolor('border-top-color', $value);
3496 * Consolidate this to a single border style
3498 * @return string
3500 public function consolidate_to() {
3501 return 'border';
3506 * A border left colour style
3508 * @package core
3509 * @subpackage cssoptimiser
3510 * @copyright 2012 Sam Hemelryk
3511 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3513 class css_style_borderleftcolor extends css_style_bordercolor {
3516 * Initialises this style object
3518 * @param string $value The value of the style
3519 * @return css_style_borderleftcolor
3521 public static function init($value) {
3522 return new css_style_borderleftcolor('border-left-color', $value);
3526 * Consolidate this to a single border style
3528 * @return string
3530 public function consolidate_to() {
3531 return 'border';
3536 * A border right colour style
3538 * @package core
3539 * @subpackage cssoptimiser
3540 * @copyright 2012 Sam Hemelryk
3541 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3543 class css_style_borderrightcolor extends css_style_bordercolor {
3546 * Initialises this style object
3548 * @param string $value The value of the style
3549 * @return css_style_borderrightcolor
3551 public static function init($value) {
3552 return new css_style_borderrightcolor('border-right-color', $value);
3556 * Consolidate this to a single border style
3558 * @return string
3560 public function consolidate_to() {
3561 return 'border';
3566 * A border bottom colour style
3568 * @package core
3569 * @subpackage cssoptimiser
3570 * @copyright 2012 Sam Hemelryk
3571 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3573 class css_style_borderbottomcolor extends css_style_bordercolor {
3576 * Initialises this style object
3578 * @param string $value The value of the style
3579 * @return css_style_borderbottomcolor
3581 public static function init($value) {
3582 return new css_style_borderbottomcolor('border-bottom-color', $value);
3586 * Consolidate this to a single border style
3588 * @return string
3590 public function consolidate_to() {
3591 return 'border';
3596 * A border width top style
3598 * @package core
3599 * @subpackage cssoptimiser
3600 * @copyright 2012 Sam Hemelryk
3601 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3603 class css_style_bordertopwidth extends css_style_borderwidth {
3606 * Initialises this style object
3608 * @param string $value The value of the style
3609 * @return css_style_bordertopwidth
3611 public static function init($value) {
3612 return new css_style_bordertopwidth('border-top-width', $value);
3616 * Consolidate this to a single border style
3618 * @return string
3620 public function consolidate_to() {
3621 return 'border';
3626 * A border width left style
3628 * @package core
3629 * @subpackage cssoptimiser
3630 * @copyright 2012 Sam Hemelryk
3631 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3633 class css_style_borderleftwidth extends css_style_borderwidth {
3636 * Initialises this style object
3638 * @param string $value The value of the style
3639 * @return css_style_borderleftwidth
3641 public static function init($value) {
3642 return new css_style_borderleftwidth('border-left-width', $value);
3646 * Consolidate this to a single border style
3648 * @return string
3650 public function consolidate_to() {
3651 return 'border';
3656 * A border width right style
3658 * @package core
3659 * @subpackage cssoptimiser
3660 * @copyright 2012 Sam Hemelryk
3661 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3663 class css_style_borderrightwidth extends css_style_borderwidth {
3666 * Initialises this style object
3668 * @param string $value The value of the style
3669 * @return css_style_borderrightwidth
3671 public static function init($value) {
3672 return new css_style_borderrightwidth('border-right-width', $value);
3676 * Consolidate this to a single border style
3678 * @return string
3680 public function consolidate_to() {
3681 return 'border';
3686 * A border width bottom style
3688 * @package core
3689 * @subpackage cssoptimiser
3690 * @copyright 2012 Sam Hemelryk
3691 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3693 class css_style_borderbottomwidth extends css_style_borderwidth {
3696 * Initialises this style object
3698 * @param string $value The value of the style
3699 * @return css_style_borderbottomwidth
3701 public static function init($value) {
3702 return new css_style_borderbottomwidth('border-bottom-width', $value);
3706 * Consolidate this to a single border style
3708 * @return string
3710 public function consolidate_to() {
3711 return 'border';
3716 * A border top style
3718 * @package core
3719 * @subpackage cssoptimiser
3720 * @copyright 2012 Sam Hemelryk
3721 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3723 class css_style_bordertopstyle extends css_style_borderstyle {
3726 * Initialises this style object
3728 * @param string $value The value of the style
3729 * @return css_style_bordertopstyle
3731 public static function init($value) {
3732 return new css_style_bordertopstyle('border-top-style', $value);
3736 * Consolidate this to a single border style
3738 * @return string
3740 public function consolidate_to() {
3741 return 'border';
3746 * A border left style
3748 * @package core
3749 * @subpackage cssoptimiser
3750 * @copyright 2012 Sam Hemelryk
3751 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3753 class css_style_borderleftstyle extends css_style_borderstyle {
3756 * Initialises this style object
3758 * @param string $value The value of the style
3759 * @return css_style_borderleftstyle
3761 public static function init($value) {
3762 return new css_style_borderleftstyle('border-left-style', $value);
3766 * Consolidate this to a single border style
3768 * @return string
3770 public function consolidate_to() {
3771 return 'border';
3776 * A border right style
3778 * @package core
3779 * @subpackage cssoptimiser
3780 * @copyright 2012 Sam Hemelryk
3781 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3783 class css_style_borderrightstyle extends css_style_borderstyle {
3786 * Initialises this style object
3788 * @param string $value The value of the style
3789 * @return css_style_borderrightstyle
3791 public static function init($value) {
3792 return new css_style_borderrightstyle('border-right-style', $value);
3796 * Consolidate this to a single border style
3798 * @return string
3800 public function consolidate_to() {
3801 return 'border';
3806 * A border bottom style
3808 * @package core
3809 * @subpackage cssoptimiser
3810 * @copyright 2012 Sam Hemelryk
3811 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3813 class css_style_borderbottomstyle extends css_style_borderstyle {
3816 * Initialises this style object
3818 * @param string $value The value for the style
3819 * @return css_style_borderbottomstyle
3821 public static function init($value) {
3822 return new css_style_borderbottomstyle('border-bottom-style', $value);
3826 * Consolidate this to a single border style
3828 * @return string
3830 public function consolidate_to() {
3831 return 'border';
3836 * A background style
3838 * @package core
3839 * @subpackage cssoptimiser
3840 * @copyright 2012 Sam Hemelryk
3841 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3843 class css_style_background extends css_style implements core_css_consolidatable_style {
3846 * Initialises a background style
3848 * @param string $value The value of the style
3849 * @return array An array of background component.
3851 public static function init($value) {
3852 // Colour - image - repeat - attachment - position.
3853 $imageurl = null;
3854 if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
3855 $imageurl = trim($matches[1]);
3856 $value = str_replace($matches[1], '', $value);
3859 // Switch out the brackets so that they don't get messed up when we explode.
3860 $brackets = array();
3861 $bracketcount = 0;
3862 while (preg_match('#\([^\)\(]+\)#', $value, $matches)) {
3863 $key = "##BRACKET-{$bracketcount}##";
3864 $bracketcount++;
3865 $brackets[$key] = $matches[0];
3866 $value = str_replace($matches[0], $key, $value);
3869 $important = (stripos($value, '!important') !== false);
3870 if ($important) {
3871 // Great some genius put !important in the background shorthand property.
3872 $value = str_replace('!important', '', $value);
3875 $value = preg_replace('#\s+#', ' ', $value);
3876 $bits = explode(' ', $value);
3878 foreach ($bits as $key => $bit) {
3879 $bits[$key] = self::replace_bracket_placeholders($bit, $brackets);
3881 unset($bracketcount);
3882 unset($brackets);
3884 $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit');
3885 $attachments = array('scroll' , 'fixed', 'inherit');
3886 $positions = array('top', 'left', 'bottom', 'right', 'center');
3888 /* @var css_style_background[] $return */
3889 $return = array();
3890 $unknownbits = array();
3892 $color = self::NULL_VALUE;
3893 if (count($bits) > 0 && css_is_colour(reset($bits))) {
3894 $color = array_shift($bits);
3897 $image = self::NULL_VALUE;
3898 if (count($bits) > 0 && preg_match('#^\s*(none|inherit|url\(\))\s*$#', reset($bits))) {
3899 $image = array_shift($bits);
3900 if ($image == 'url()') {
3901 $image = "url({$imageurl})";
3905 $repeat = self::NULL_VALUE;
3906 if (count($bits) > 0 && in_array(reset($bits), $repeats)) {
3907 $repeat = array_shift($bits);
3910 $attachment = self::NULL_VALUE;
3911 if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
3912 // Scroll , fixed, inherit.
3913 $attachment = array_shift($bits);
3916 $position = self::NULL_VALUE;
3917 if (count($bits) > 0) {
3918 $widthbits = array();
3919 foreach ($bits as $bit) {
3920 if (in_array($bit, $positions) || css_is_width($bit)) {
3921 $widthbits[] = $bit;
3922 } else {
3923 $unknownbits[] = $bit;
3926 if (count($widthbits)) {
3927 $position = join(' ', $widthbits);
3931 if (count($unknownbits)) {
3932 foreach ($unknownbits as $bit) {
3933 $bit = trim($bit);
3934 if ($color === self::NULL_VALUE && css_is_colour($bit)) {
3935 $color = $bit;
3936 } else if ($repeat === self::NULL_VALUE && in_array($bit, $repeats)) {
3937 $repeat = $bit;
3938 } else if ($attachment === self::NULL_VALUE && in_array($bit, $attachments)) {
3939 $attachment = $bit;
3940 } else if ($bit !== '') {
3941 $advanced = css_style_background_advanced::init($bit);
3942 if ($important) {
3943 $advanced->set_important();
3945 $return[] = $advanced;
3950 if ($color === self::NULL_VALUE &&
3951 $image === self::NULL_VALUE &&
3952 $repeat === self::NULL_VALUE && $attachment === self::NULL_VALUE &&
3953 $position === self::NULL_VALUE) {
3954 // All primaries are null, return without doing anything else. There may be advanced madness there.
3955 return $return;
3958 $return[] = css_style_backgroundcolor::init($color);
3959 $return[] = css_style_backgroundimage::init($image);
3960 $return[] = css_style_backgroundrepeat::init($repeat);
3961 $return[] = css_style_backgroundattachment::init($attachment);
3962 $return[] = css_style_backgroundposition::init($position);
3964 if ($important) {
3965 foreach ($return as $style) {
3966 $style->set_important();
3970 return $return;
3974 * Static helper method to switch in bracket replacements
3976 * @param string $value
3977 * @param array $placeholders
3978 * @return string
3980 protected static function replace_bracket_placeholders($value, array $placeholders) {
3981 while (preg_match('/##BRACKET-\d+##/', $value, $matches)) {
3982 $value = str_replace($matches[0], $placeholders[$matches[0]], $value);
3984 return $value;
3988 * Consolidates background styles into a single background style
3990 * @param css_style_background[] $styles Consolidates the provided array of background styles
3991 * @return css_style[] Consolidated optimised background styles
3993 public static function consolidate(array $styles) {
3995 if (empty($styles)) {
3996 return $styles;
3999 $color = null;
4000 $image = null;
4001 $repeat = null;
4002 $attachment = null;
4003 $position = null;
4004 $size = null;
4005 $origin = null;
4006 $clip = null;
4008 $someimportant = false;
4009 $allimportant = null;
4010 foreach ($styles as $style) {
4011 if ($style instanceof css_style_backgroundimage_advanced) {
4012 continue;
4014 if ($style->is_important()) {
4015 $someimportant = true;
4016 if ($allimportant === null) {
4017 $allimportant = true;
4019 } else if ($allimportant === true) {
4020 $allimportant = false;
4024 /* @var css_style[] $organisedstyles */
4025 $organisedstyles = array();
4026 /* @var css_style[] $advancedstyles */
4027 $advancedstyles = array();
4028 /* @var css_style[] $importantstyles */
4029 $importantstyles = array();
4030 foreach ($styles as $style) {
4031 if ($style instanceof css_style_backgroundimage_advanced) {
4032 $advancedstyles[] = $style;
4033 continue;
4035 if ($someimportant && !$allimportant && $style->is_important()) {
4036 $importantstyles[] = $style;
4037 continue;
4039 $organisedstyles[$style->get_name()] = $style;
4040 switch ($style->get_name()) {
4041 case 'background-color' :
4042 $color = css_style_color::shrink_value($style->get_value(false));
4043 break;
4044 case 'background-image' :
4045 $image = $style->get_value(false);
4046 break;
4047 case 'background-repeat' :
4048 $repeat = $style->get_value(false);
4049 break;
4050 case 'background-attachment' :
4051 $attachment = $style->get_value(false);
4052 break;
4053 case 'background-position' :
4054 $position = $style->get_value(false);
4055 break;
4056 case 'background-clip' :
4057 $clip = $style->get_value();
4058 break;
4059 case 'background-origin' :
4060 $origin = $style->get_value();
4061 break;
4062 case 'background-size' :
4063 $size = $style->get_value();
4064 break;
4068 /* @var css_style[] $consolidatetosingle */
4069 $consolidatetosingle = array();
4070 if (!is_null($color) && !is_null($image) && !is_null($repeat) && !is_null($attachment) && !is_null($position)) {
4071 // We can use the shorthand background-style!
4072 if (!$organisedstyles['background-color']->is_special_empty_value()) {
4073 $consolidatetosingle[] = $color;
4075 if (!$organisedstyles['background-image']->is_special_empty_value()) {
4076 $consolidatetosingle[] = $image;
4078 if (!$organisedstyles['background-repeat']->is_special_empty_value()) {
4079 $consolidatetosingle[] = $repeat;
4081 if (!$organisedstyles['background-attachment']->is_special_empty_value()) {
4082 $consolidatetosingle[] = $attachment;
4084 if (!$organisedstyles['background-position']->is_special_empty_value()) {
4085 $consolidatetosingle[] = $position;
4087 // Reset them all to null so we don't use them again.
4088 $color = null;
4089 $image = null;
4090 $repeat = null;
4091 $attachment = null;
4092 $position = null;
4095 $return = array();
4096 // Single background style needs to come first.
4097 if (count($consolidatetosingle) > 0) {
4098 $returnstyle = new css_style_background('background', join(' ', $consolidatetosingle));
4099 if ($allimportant) {
4100 $returnstyle->set_important();
4102 $return[] = $returnstyle;
4104 foreach ($styles as $style) {
4105 $value = null;
4106 switch ($style->get_name()) {
4107 case 'background-color' :
4108 $value = $color;
4109 break;
4110 case 'background-image' :
4111 $value = $image;
4112 break;
4113 case 'background-repeat' :
4114 $value = $repeat;
4115 break;
4116 case 'background-attachment' :
4117 $value = $attachment;
4118 break;
4119 case 'background-position' :
4120 $value = $position;
4121 break;
4122 case 'background-clip' :
4123 $value = $clip;
4124 break;
4125 case 'background-origin':
4126 $value = $origin;
4127 break;
4128 case 'background-size':
4129 $value = $size;
4130 break;
4132 if (!is_null($value)) {
4133 $return[] = $style;
4136 $return = array_merge($return, $importantstyles, $advancedstyles);
4137 return $return;
4142 * A advanced background style that allows multiple values to preserve unknown entities
4144 * @package core
4145 * @subpackage cssoptimiser
4146 * @copyright 2012 Sam Hemelryk
4147 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4149 class css_style_background_advanced extends css_style_generic {
4151 * Creates a new background colour style
4153 * @param string $value The value of the style
4154 * @return css_style_backgroundimage
4156 public static function init($value) {
4157 $value = preg_replace('#\s+#', ' ', $value);
4158 return new css_style_background_advanced('background', $value);
4162 * Returns true because the advanced background image supports multiple values.
4163 * e.g. -webkit-linear-gradient and -moz-linear-gradient.
4165 * @return boolean
4167 public function allows_multiple_values() {
4168 return true;
4173 * A background colour style.
4175 * Based upon the colour style.
4177 * @package core
4178 * @subpackage cssoptimiser
4179 * @copyright 2012 Sam Hemelryk
4180 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4182 class css_style_backgroundcolor extends css_style_color {
4185 * Creates a new background colour style
4187 * @param string $value The value of the style
4188 * @return css_style_backgroundcolor
4190 public static function init($value) {
4191 return new css_style_backgroundcolor('background-color', $value);
4195 * css_style_backgroundcolor consolidates to css_style_background
4197 * @return string
4199 public function consolidate_to() {
4200 return 'background';
4204 * Returns true if the value for this style is the special null value.
4206 * This occurs if the shorthand background property was used but no proper value
4207 * was specified for this style.
4208 * This leads to a null value being used unless otherwise overridden.
4210 * @return bool
4212 public function is_special_empty_value() {
4213 return ($this->value === self::NULL_VALUE);
4217 * Returns true if the value for this style is valid
4218 * @return bool
4220 public function is_valid() {
4221 return $this->is_special_empty_value() || parent::is_valid();
4226 * A background image style.
4228 * @package core
4229 * @subpackage cssoptimiser
4230 * @copyright 2012 Sam Hemelryk
4231 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4233 class css_style_backgroundimage extends css_style_generic {
4236 * Creates a new background image style
4238 * @param string $value The value of the style
4239 * @return css_style_backgroundimage
4241 public static function init($value) {
4242 if ($value !== self::NULL_VALUE && !preg_match('#^\s*(none|inherit|url\()#i', $value)) {
4243 return css_style_backgroundimage_advanced::init($value);
4245 return new css_style_backgroundimage('background-image', $value);
4249 * Consolidates this style into a single background style
4251 * @return string
4253 public function consolidate_to() {
4254 return 'background';
4258 * Returns true if the value for this style is the special null value.
4260 * This occurs if the shorthand background property was used but no proper value
4261 * was specified for this style.
4262 * This leads to a null value being used unless otherwise overridden.
4264 * @return bool
4266 public function is_special_empty_value() {
4267 return ($this->value === self::NULL_VALUE);
4271 * Returns true if the value for this style is valid
4272 * @return bool
4274 public function is_valid() {
4275 return $this->is_special_empty_value() || parent::is_valid();
4280 * A background image style that supports multiple values and masquerades as a background-image
4282 * @package core
4283 * @subpackage cssoptimiser
4284 * @copyright 2012 Sam Hemelryk
4285 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4287 class css_style_backgroundimage_advanced extends css_style_generic {
4289 * Creates a new background colour style
4291 * @param string $value The value of the style
4292 * @return css_style_backgroundimage
4294 public static function init($value) {
4295 $value = preg_replace('#\s+#', ' ', $value);
4296 return new css_style_backgroundimage_advanced('background-image', $value);
4300 * Returns true because the advanced background image supports multiple values.
4301 * e.g. -webkit-linear-gradient and -moz-linear-gradient.
4303 * @return boolean
4305 public function allows_multiple_values() {
4306 return true;
4311 * A background repeat style.
4313 * @package core
4314 * @subpackage cssoptimiser
4315 * @copyright 2012 Sam Hemelryk
4316 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4318 class css_style_backgroundrepeat extends css_style_generic {
4321 * Creates a new background colour style
4323 * @param string $value The value of the style
4324 * @return css_style_backgroundrepeat
4326 public static function init($value) {
4327 return new css_style_backgroundrepeat('background-repeat', $value);
4331 * Consolidates this style into a single background style
4333 * @return string
4335 public function consolidate_to() {
4336 return 'background';
4340 * Returns true if the value for this style is the special null value.
4342 * This occurs if the shorthand background property was used but no proper value
4343 * was specified for this style.
4344 * This leads to a null value being used unless otherwise overridden.
4346 * @return bool
4348 public function is_special_empty_value() {
4349 return ($this->value === self::NULL_VALUE);
4353 * Returns true if the value for this style is valid
4354 * @return bool
4356 public function is_valid() {
4357 return $this->is_special_empty_value() || parent::is_valid();
4362 * A background attachment style.
4364 * @package core
4365 * @subpackage cssoptimiser
4366 * @copyright 2012 Sam Hemelryk
4367 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4369 class css_style_backgroundattachment extends css_style_generic {
4372 * Creates a new background colour style
4374 * @param string $value The value of the style
4375 * @return css_style_backgroundattachment
4377 public static function init($value) {
4378 return new css_style_backgroundattachment('background-attachment', $value);
4382 * Consolidates this style into a single background style
4384 * @return string
4386 public function consolidate_to() {
4387 return 'background';
4391 * Returns true if the value for this style is the special null value.
4393 * This occurs if the shorthand background property was used but no proper value
4394 * was specified for this style.
4395 * This leads to a null value being used unless otherwise overridden.
4397 * @return bool
4399 public function is_special_empty_value() {
4400 return ($this->value === self::NULL_VALUE);
4404 * Returns true if the value for this style is valid
4405 * @return bool
4407 public function is_valid() {
4408 return $this->is_special_empty_value() || parent::is_valid();
4413 * A background position style.
4415 * @package core
4416 * @subpackage cssoptimiser
4417 * @copyright 2012 Sam Hemelryk
4418 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4420 class css_style_backgroundposition extends css_style_generic {
4423 * Creates a new background colour style
4425 * @param string $value The value of the style
4426 * @return css_style_backgroundposition
4428 public static function init($value) {
4429 return new css_style_backgroundposition('background-position', $value);
4433 * Consolidates this style into a single background style
4435 * @return string
4437 public function consolidate_to() {
4438 return 'background';
4442 * Returns true if the value for this style is the special null value.
4444 * This occurs if the shorthand background property was used but no proper value
4445 * was specified for this style.
4446 * This leads to a null value being used unless otherwise overridden.
4448 * @return bool
4450 public function is_special_empty_value() {
4451 return ($this->value === self::NULL_VALUE);
4455 * Returns true if the value for this style is valid
4456 * @return bool
4458 public function is_valid() {
4459 return $this->is_special_empty_value() || parent::is_valid();
4464 * A background size style.
4466 * @package core
4467 * @subpackage cssoptimiser
4468 * @copyright 2012 Sam Hemelryk
4469 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4471 class css_style_backgroundsize extends css_style_generic {
4474 * Creates a new background size style
4476 * @param string $value The value of the style
4477 * @return css_style_backgroundposition
4479 public static function init($value) {
4480 return new css_style_backgroundsize('background-size', $value);
4484 * Consolidates this style into a single background style
4486 * @return string
4488 public function consolidate_to() {
4489 return 'background';
4494 * A background clip style.
4496 * @package core
4497 * @subpackage cssoptimiser
4498 * @copyright 2012 Sam Hemelryk
4499 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4501 class css_style_backgroundclip extends css_style_generic {
4504 * Creates a new background clip style
4506 * @param string $value The value of the style
4507 * @return css_style_backgroundposition
4509 public static function init($value) {
4510 return new css_style_backgroundclip('background-clip', $value);
4514 * Consolidates this style into a single background style
4516 * @return string
4518 public function consolidate_to() {
4519 return 'background';
4524 * A background origin style.
4526 * @package core
4527 * @subpackage cssoptimiser
4528 * @copyright 2012 Sam Hemelryk
4529 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4531 class css_style_backgroundorigin extends css_style_generic {
4534 * Creates a new background origin style
4536 * @param string $value The value of the style
4537 * @return css_style_backgroundposition
4539 public static function init($value) {
4540 return new css_style_backgroundorigin('background-origin', $value);
4544 * Consolidates this style into a single background style
4546 * @return string
4548 public function consolidate_to() {
4549 return 'background';
4554 * A padding style.
4556 * @package core
4557 * @subpackage cssoptimiser
4558 * @copyright 2012 Sam Hemelryk
4559 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4561 class css_style_padding extends css_style_width implements core_css_consolidatable_style {
4564 * Initialises this padding style into several individual padding styles
4566 * @param string $value The value fo the style
4567 * @return array An array of padding styles
4569 public static function init($value) {
4570 $important = '';
4571 if (strpos($value, '!important') !== false) {
4572 $important = ' !important';
4573 $value = str_replace('!important', '', $value);
4576 $value = preg_replace('#\s+#', ' ', trim($value));
4577 $bits = explode(' ', $value, 4);
4579 $top = $right = $bottom = $left = null;
4580 if (count($bits) > 0) {
4581 $top = $right = $bottom = $left = array_shift($bits);
4583 if (count($bits) > 0) {
4584 $right = $left = array_shift($bits);
4586 if (count($bits) > 0) {
4587 $bottom = array_shift($bits);
4589 if (count($bits) > 0) {
4590 $left = array_shift($bits);
4592 return array(
4593 new css_style_paddingtop('padding-top', $top.$important),
4594 new css_style_paddingright('padding-right', $right.$important),
4595 new css_style_paddingbottom('padding-bottom', $bottom.$important),
4596 new css_style_paddingleft('padding-left', $left.$important)
4601 * Consolidates several padding styles into a single style.
4603 * @param css_style_padding[] $styles Array of padding styles
4604 * @return css_style[] Optimised+consolidated array of padding styles
4606 public static function consolidate(array $styles) {
4607 if (count($styles) != 4) {
4608 return $styles;
4611 $someimportant = false;
4612 $allimportant = null;
4613 $notimportantequal = null;
4614 $firstvalue = null;
4615 foreach ($styles as $style) {
4616 if ($style->is_important()) {
4617 $someimportant = true;
4618 if ($allimportant === null) {
4619 $allimportant = true;
4621 } else {
4622 if ($allimportant === true) {
4623 $allimportant = false;
4625 if ($firstvalue == null) {
4626 $firstvalue = $style->get_value(false);
4627 $notimportantequal = true;
4628 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
4629 $notimportantequal = false;
4634 if ($someimportant && !$allimportant && !$notimportantequal) {
4635 return $styles;
4638 if ($someimportant && !$allimportant && $notimportantequal) {
4639 $return = array(
4640 new css_style_padding('padding', $firstvalue)
4642 foreach ($styles as $style) {
4643 if ($style->is_important()) {
4644 $return[] = $style;
4647 return $return;
4648 } else {
4649 $top = null;
4650 $right = null;
4651 $bottom = null;
4652 $left = null;
4653 foreach ($styles as $style) {
4654 switch ($style->get_name()) {
4655 case 'padding-top' :
4656 $top = $style->get_value(false);
4657 break;
4658 case 'padding-right' :
4659 $right = $style->get_value(false);
4660 break;
4661 case 'padding-bottom' :
4662 $bottom = $style->get_value(false);
4663 break;
4664 case 'padding-left' :
4665 $left = $style->get_value(false);
4666 break;
4669 if ($top == $bottom && $left == $right) {
4670 if ($top == $left) {
4671 $returnstyle = new css_style_padding('padding', $top);
4672 } else {
4673 $returnstyle = new css_style_padding('padding', "{$top} {$left}");
4675 } else if ($left == $right) {
4676 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom}");
4677 } else {
4678 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}");
4680 if ($allimportant) {
4681 $returnstyle->set_important();
4683 return array($returnstyle);
4689 * A padding top style.
4691 * @package core
4692 * @subpackage cssoptimiser
4693 * @copyright 2012 Sam Hemelryk
4694 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4696 class css_style_paddingtop extends css_style_padding {
4699 * Initialises this style
4701 * @param string $value The value of the style
4702 * @return css_style_paddingtop
4704 public static function init($value) {
4705 return new css_style_paddingtop('padding-top', $value);
4709 * Consolidates this style into a single padding style
4711 * @return string
4713 public function consolidate_to() {
4714 return 'padding';
4719 * A padding right style.
4721 * @package core
4722 * @subpackage cssoptimiser
4723 * @copyright 2012 Sam Hemelryk
4724 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4726 class css_style_paddingright extends css_style_padding {
4729 * Initialises this style
4731 * @param string $value The value of the style
4732 * @return css_style_paddingright
4734 public static function init($value) {
4735 return new css_style_paddingright('padding-right', $value);
4739 * Consolidates this style into a single padding style
4741 * @return string
4743 public function consolidate_to() {
4744 return 'padding';
4749 * A padding bottom style.
4751 * @package core
4752 * @subpackage cssoptimiser
4753 * @copyright 2012 Sam Hemelryk
4754 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4756 class css_style_paddingbottom extends css_style_padding {
4759 * Initialises this style
4761 * @param string $value The value of the style
4762 * @return css_style_paddingbottom
4764 public static function init($value) {
4765 return new css_style_paddingbottom('padding-bottom', $value);
4769 * Consolidates this style into a single padding style
4771 * @return string
4773 public function consolidate_to() {
4774 return 'padding';
4779 * A padding left style.
4781 * @package core
4782 * @subpackage cssoptimiser
4783 * @copyright 2012 Sam Hemelryk
4784 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4786 class css_style_paddingleft extends css_style_padding {
4789 * Initialises this style
4791 * @param string $value The value of the style
4792 * @return css_style_paddingleft
4794 public static function init($value) {
4795 return new css_style_paddingleft('padding-left', $value);
4799 * Consolidates this style into a single padding style
4801 * @return string
4803 public function consolidate_to() {
4804 return 'padding';
4809 * A cursor style.
4811 * @package core
4812 * @subpackage cssoptimiser
4813 * @copyright 2012 Sam Hemelryk
4814 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4816 class css_style_cursor extends css_style_generic {
4818 * Initialises a new cursor style
4819 * @param string $value
4820 * @return css_style_cursor
4822 public static function init($value) {
4823 return new css_style_cursor('cursor', $value);
4826 * Cleans the given value and returns it.
4828 * @param string $value
4829 * @return string
4831 protected function clean_value($value) {
4832 // Allowed values for the cursor style.
4833 $allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
4834 'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
4835 // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough.
4836 if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
4837 $this->set_error('Invalid or unexpected cursor value specified: '.$value);
4839 return trim($value);
4844 * A vertical alignment style.
4846 * @package core
4847 * @subpackage cssoptimiser
4848 * @copyright 2012 Sam Hemelryk
4849 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4851 class css_style_verticalalign extends css_style_generic {
4853 * Initialises a new vertical alignment style
4854 * @param string $value
4855 * @return css_style_verticalalign
4857 public static function init($value) {
4858 return new css_style_verticalalign('vertical-align', $value);
4861 * Cleans the given value and returns it.
4863 * @param string $value
4864 * @return string
4866 protected function clean_value($value) {
4867 $allowed = array('baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom', 'inherit');
4868 if (!css_is_width($value) && !in_array($value, $allowed)) {
4869 $this->set_error('Invalid vertical-align value specified: '.$value);
4871 return trim($value);
4876 * A float style.
4878 * @package core
4879 * @subpackage cssoptimiser
4880 * @copyright 2012 Sam Hemelryk
4881 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4883 class css_style_float extends css_style_generic {
4885 * Initialises a new float style
4886 * @param string $value
4887 * @return css_style_float
4889 public static function init($value) {
4890 return new css_style_float('float', $value);
4893 * Cleans the given value and returns it.
4895 * @param string $value
4896 * @return string
4898 protected function clean_value($value) {
4899 $allowed = array('left', 'right', 'none', 'inherit');
4900 if (!css_is_width($value) && !in_array($value, $allowed)) {
4901 $this->set_error('Invalid float value specified: '.$value);
4903 return trim($value);