MDL-33468 css_optimiser: Added support for prioritising reset rules
[moodle.git] / lib / csslib.php
blob5118529707cf1f43114297480aa481e619a1b24b
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 {@see css_optimiser} class for greater detail.
22 * @package core_css
23 * @category css
24 * @copyright 2012 Sam Hemelryk
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 //NOTE: do not verify MOODLE_INTERNAL here, this is used from themes too
30 /**
31 * Stores CSS in a file at the given path.
33 * This function either succeeds or throws an exception.
35 * @param theme_config $theme The theme that the CSS belongs to.
36 * @param string $csspath The path to store the CSS at.
37 * @param array $cssfiles The CSS files to store.
39 function css_store_css(theme_config $theme, $csspath, array $cssfiles) {
40 global $CFG;
42 if (!empty($CFG->enablecssoptimiser)) {
43 // This is an experimental feature introduced in Moodle 2.3
44 // The CSS optimiser organises the CSS in order to reduce the overall number
45 // of rules and styles being sent to the client. It does this by collating
46 // the CSS before it is cached removing excess styles and rules and stripping
47 // out any extraneous content such as comments and empty rules.
48 $optimiser = new css_optimiser;
49 $css = '';
50 foreach ($cssfiles as $file) {
51 $css .= file_get_contents($file)."\n";
53 $css = $theme->post_process($css);
54 $css = $optimiser->process($css);
56 // If cssoptimisestats is set then stats from the optimisation are collected
57 // and output at the beginning of the CSS
58 if (!empty($CFG->cssoptimiserstats)) {
59 $css = $optimiser->output_stats_css().$css;
61 } else {
62 // This is the default behaviour.
63 // The cssoptimise setting was introduced in Moodle 2.3 and will hopefully
64 // in the future be changed from an experimental setting to the default.
65 // The css_minify_css will method will use the Minify library remove
66 // comments, additional whitespace and other minor measures to reduce the
67 // the overall CSS being sent.
68 // However it has the distinct disadvantage of having to minify the CSS
69 // before running the post process functions. Potentially things may break
70 // here if theme designers try to push things with CSS post processing.
71 $css = $theme->post_process(css_minify_css($cssfiles));
74 clearstatcache();
75 if (!file_exists(dirname($csspath))) {
76 @mkdir(dirname($csspath), $CFG->directorypermissions, true);
79 // Prevent serving of incomplete file from concurrent request,
80 // the rename() should be more atomic than fwrite().
81 ignore_user_abort(true);
82 if ($fp = fopen($csspath.'.tmp', 'xb')) {
83 fwrite($fp, $css);
84 fclose($fp);
85 rename($csspath.'.tmp', $csspath);
86 @chmod($csspath, $CFG->filepermissions);
87 @unlink($csspath.'.tmp'); // just in case anything fails
89 ignore_user_abort(false);
90 if (connection_aborted()) {
91 die;
95 /**
96 * Sends IE specific CSS
98 * In writing the CSS parser I have a theory that we could optimise the CSS
99 * then split it based upon the number of selectors to ensure we dont' break IE
100 * and that we include only as many sub-stylesheets as we require.
101 * Of course just a theory but may be fun to code.
103 * @param string $themename The name of the theme we are sending CSS for.
104 * @param string $rev The revision to ensure we utilise the cache.
105 * @param string $etag The revision to ensure we utilise the cache.
106 * @param bool $slasharguments
108 function css_send_ie_css($themename, $rev, $etag, $slasharguments) {
109 global $CFG;
111 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
113 $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot);
115 $css = "/** Unfortunately IE6/7 does not support more than 4096 selectors in one CSS file, which means we have to use some ugly hacks :-( **/";
116 if ($slasharguments) {
117 $css .= "\n@import url($relroot/styles.php/$themename/$rev/plugins);";
118 $css .= "\n@import url($relroot/styles.php/$themename/$rev/parents);";
119 $css .= "\n@import url($relroot/styles.php/$themename/$rev/theme);";
120 } else {
121 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=plugins);";
122 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=parents);";
123 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=theme);";
126 header('Etag: '.$etag);
127 header('Content-Disposition: inline; filename="styles.php"');
128 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
129 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
130 header('Pragma: ');
131 header('Cache-Control: public, max-age='.$lifetime);
132 header('Accept-Ranges: none');
133 header('Content-Type: text/css; charset=utf-8');
134 header('Content-Length: '.strlen($css));
136 echo $css;
137 die;
141 * Sends a cached CSS file
143 * This function sends the cached CSS file. Remember it is generated on the first
144 * request, then optimised/minified, and finally cached for serving.
146 * @param string $csspath The path to the CSS file we want to serve.
147 * @param string $etag The revision to make sure we utilise any caches.
149 function css_send_cached_css($csspath, $etag) {
150 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
152 header('Etag: '.$etag);
153 header('Content-Disposition: inline; filename="styles.php"');
154 header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
155 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
156 header('Pragma: ');
157 header('Cache-Control: public, max-age='.$lifetime);
158 header('Accept-Ranges: none');
159 header('Content-Type: text/css; charset=utf-8');
160 if (!min_enable_zlib_compression()) {
161 header('Content-Length: '.filesize($csspath));
164 readfile($csspath);
165 die;
169 * Sends CSS directly without caching it.
171 * This function takes a raw CSS string, optimises it if required, and then
172 * serves it.
173 * Turning both themedesignermode and CSS optimiser on at the same time is aweful
174 * for performance because of the optimiser running here. However it was done so
175 * that theme designers could utilise the optimised output during development to
176 * help them optimise their CSS... not that they should write lazy CSS.
178 * @param string CSS
180 function css_send_uncached_css($css) {
181 global $CFG;
183 header('Content-Disposition: inline; filename="styles_debug.php"');
184 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
185 header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) .' GMT');
186 header('Pragma: ');
187 header('Accept-Ranges: none');
188 header('Content-Type: text/css; charset=utf-8');
190 if (is_array($css)) {
191 $css = implode("\n\n", $css);
194 if (!empty($CFG->enablecssoptimiser)) {
195 $css = str_replace("\n", "\r\n", $css);
197 $optimiser = new css_optimiser;
198 $css = $optimiser->process($css);
199 if (!empty($CFG->cssoptimiserstats)) {
200 $css = $optimiser->output_stats_css().$css;
204 echo $css;
206 die;
210 * Send file not modified headers
211 * @param int $lastmodified
212 * @param string $etag
214 function css_send_unmodified($lastmodified, $etag) {
215 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
216 header('HTTP/1.1 304 Not Modified');
217 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
218 header('Cache-Control: public, max-age='.$lifetime);
219 header('Content-Type: text/css; charset=utf-8');
220 header('Etag: '.$etag);
221 if ($lastmodified) {
222 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
224 die;
228 * Sends a 404 message about CSS not being found.
230 function css_send_css_not_found() {
231 header('HTTP/1.0 404 not found');
232 die('CSS was not found, sorry.');
236 * Uses the minify library to compress CSS.
238 * This is used if $CFG->enablecssoptimiser has been turned off. This was
239 * the original CSS optimisation library.
240 * It removes whitespace and shrinks things but does no apparent optimisation.
241 * Note the minify library is still being used for JavaScript.
243 * @param array $files An array of files to minify
244 * @return string The minified CSS
246 function css_minify_css($files) {
247 global $CFG;
249 if (empty($files)) {
250 return '';
253 set_include_path($CFG->libdir . '/minify/lib' . PATH_SEPARATOR . get_include_path());
254 require_once('Minify.php');
256 if (0 === stripos(PHP_OS, 'win')) {
257 Minify::setDocRoot(); // IIS may need help
259 // disable all caching, we do it in moodle
260 Minify::setCache(null, false);
262 $options = array(
263 'bubbleCssImports' => false,
264 // Don't gzip content we just want text for storage
265 'encodeOutput' => false,
266 // Maximum age to cache, not used but required
267 'maxAge' => (60*60*24*20),
268 // The files to minify
269 'files' => $files,
270 // Turn orr URI rewriting
271 'rewriteCssUris' => false,
272 // This returns the CSS rather than echoing it for display
273 'quiet' => true
276 $error = 'unknown';
277 try {
278 $result = Minify::serve('Files', $options);
279 if ($result['success']) {
280 return $result['content'];
282 } catch (Exception $e) {
283 $error = $e->getMessage();
284 $error = str_replace("\r", ' ', $error);
285 $error = str_replace("\n", ' ', $error);
288 // minification failed - try to inform the theme developer and include the non-minified version
289 $css = <<<EOD
290 /* Error: $error */
291 /* Problem detected during theme CSS minimisation, please review the following code */
292 /* ================================================================================ */
295 EOD;
296 foreach ($files as $cssfile) {
297 $css .= file_get_contents($cssfile)."\n";
299 return $css;
303 * Determines if the given value is a valid CSS colour.
305 * A CSS colour can be one of the following:
306 * - Hex colour: #AA66BB
307 * - RGB colour: rgb(0-255, 0-255, 0-255)
308 * - RGBA colour: rgba(0-255, 0-255, 0-255, 0-1)
309 * - HSL colour: hsl(0-360, 0-100%, 0-100%)
310 * - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1)
312 * Or a recognised browser colour mapping {@see css_optimiser::$htmlcolours}
314 * @param string $value The colour value to check
315 * @return bool
317 function css_is_colour($value) {
318 $value = trim($value);
319 if (in_array(strtolower($value), array('inherit'))) {
320 return true;
321 } else if (preg_match('/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/', $value)) {
322 return true;
323 } else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) {
324 return true;
325 } else if (preg_match('#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i', $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
326 // It is an RGB colour
327 return true;
328 } else if (preg_match('#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i', $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
329 // It is an RGBA colour
330 return true;
331 } else if (preg_match('#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i', $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
332 // It is an HSL colour
333 return true;
334 } else if (preg_match('#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i', $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
335 // It is an HSLA colour
336 return true;
338 // Doesn't look like a colour.
339 return false;
343 * Returns true is the passed value looks like a CSS width.
344 * In order to pass this test the value must be purely numerical or end with a
345 * valid CSS unit term.
347 * @param string|int $value
348 * @return boolean
350 function css_is_width($value) {
351 $value = trim($value);
352 if (in_array(strtolower($value), array('auto', 'inherit'))) {
353 return true;
355 if ((string)$value === '0' || preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) {
356 return true;
358 return false;
362 * A simple sorting function to sort two array values on the number of items they contain
364 * @param array $a
365 * @param array $b
366 * @return int
368 function css_sort_by_count(array $a, array $b) {
369 $a = count($a);
370 $b = count($b);
371 if ($a == $b) {
372 return 0;
374 return ($a > $b) ? -1 : 1;
378 * A basic CSS optimiser that strips out unwanted things and then processing the
379 * CSS organising styles and moving duplicates and useless CSS.
381 * This CSS optimiser works by reading through a CSS string one character at a
382 * time and building an object structure of the CSS.
383 * As part of that processing styles are expanded out as much as they can be to
384 * ensure we collect all mappings, at the end of the processing those styles are
385 * then combined into an optimised form to keep them as short as possible.
387 * @package core_css
388 * @category css
389 * @copyright 2012 Sam Hemelryk
390 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
392 class css_optimiser {
395 * Used when the processor is about to start processing.
396 * Processing states. Used internally.
398 const PROCESSING_START = 0;
401 * Used when the processor is currently processing a selector.
402 * Processing states. Used internally.
404 const PROCESSING_SELECTORS = 0;
407 * Used when the processor is currently processing a style.
408 * Processing states. Used internally.
410 const PROCESSING_STYLES = 1;
413 * Used when the processor is currently processing a comment.
414 * Processing states. Used internally.
416 const PROCESSING_COMMENT = 2;
419 * Used when the processor is currently processing an @ rule.
420 * Processing states. Used internally.
422 const PROCESSING_ATRULE = 3;
425 * The raw string length before optimisation.
426 * Stats variables set during and after processing
427 * @var int
429 protected $rawstrlen = 0;
432 * The number of comments that were removed during optimisation.
433 * Stats variables set during and after processing
434 * @var int
436 protected $commentsincss = 0;
439 * The number of rules in the CSS before optimisation.
440 * Stats variables set during and after processing
441 * @var int
443 protected $rawrules = 0;
446 * The number of selectors using in CSS rules before optimisation.
447 * Stats variables set during and after processing
448 * @var int
450 protected $rawselectors = 0;
453 * The string length after optimisation.
454 * Stats variables set during and after processing
455 * @var int
457 protected $optimisedstrlen = 0;
460 * The number of rules after optimisation.
461 * Stats variables set during and after processing
462 * @var int
464 protected $optimisedrules = 0;
467 * The number of selectors used in rules after optimisation.
468 * Stats variables set during and after processing
469 * @var int
471 protected $optimisedselectors = 0;
474 * The start time of the optimisation.
475 * Stats variables set during and after processing
476 * @var int
478 protected $timestart = 0;
481 * The end time of the optimisation.
482 * Stats variables set during and after processing
483 * @var int
485 protected $timecomplete = 0;
488 * Will be set to any errors that may have occured during processing.
489 * This is updated only at the end of processing NOT during.
491 * @var array
493 protected $errors = array();
496 * Processes incoming CSS optimising it and then returning it.
498 * @param string $css The raw CSS to optimise
499 * @return string The optimised CSS
501 public function process($css) {
502 global $CFG;
504 $css = trim($css);
506 $this->reset_stats();
507 $this->timestart = microtime(true);
508 $this->rawstrlen = strlen($css);
510 if ($this->rawstrlen === 0) {
511 $this->errors[] = 'Skipping file as it has no content.';
512 return '';
515 // First up we need to remove all line breaks - this allows us to instantly
516 // reduce our processing requirements and as we will process everything
517 // into a new structure there's really nothing lost.
518 $css = preg_replace('#\r?\n#', ' ', $css);
520 // Next remove the comments... no need to them in an optimised world and
521 // knowing they're all gone allows us to REALLY make our processing simpler
522 $css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss);
524 $medias = array(
525 'all' => new css_media()
527 $imports = array();
528 $charset = false;
529 // Keyframes are used for CSS animation they will be processed right at the very end.
530 $keyframes = array();
532 $currentprocess = self::PROCESSING_START;
533 $currentrule = css_rule::init();
534 $currentselector = css_selector::init();
535 $inquotes = false; // ' or "
536 $inbraces = false; // {
537 $inbrackets = false; // [
538 $inparenthesis = false; // (
539 $currentmedia = $medias['all'];
540 $currentatrule = null;
541 $suspectatrule = false;
543 $buffer = '';
544 $char = null;
546 // Next we are going to iterate over every single character in $css.
547 // This is why we removed line breaks and comments!
548 for ($i = 0; $i < $this->rawstrlen; $i++) {
549 $lastchar = $char;
550 $char = substr($css, $i, 1);
551 if ($char == '@' && $buffer == '') {
552 $suspectatrule = true;
554 switch ($currentprocess) {
555 // Start processing an at rule e.g. @media, @page
556 case self::PROCESSING_ATRULE:
557 switch ($char) {
558 case ';':
559 if (!$inbraces) {
560 $buffer .= $char;
561 if ($currentatrule == 'import') {
562 $imports[] = $buffer;
563 $currentprocess = self::PROCESSING_SELECTORS;
564 } else if ($currentatrule == 'charset') {
565 $charset = $buffer;
566 $currentprocess = self::PROCESSING_SELECTORS;
569 $buffer = '';
570 $currentatrule = false;
571 // continue 1: The switch processing chars
572 // continue 2: The switch processing the state
573 // continue 3: The for loop
574 continue 3;
575 case '{':
576 if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)#', $buffer, $matches)) {
577 $mediatypes = str_replace(' ', '', $matches[1]);
578 if (!array_key_exists($mediatypes, $medias)) {
579 $medias[$mediatypes] = new css_media($mediatypes);
581 $currentmedia = $medias[$mediatypes];
582 $currentprocess = self::PROCESSING_SELECTORS;
583 $buffer = '';
584 } else if ($currentatrule == 'keyframes' && preg_match('#@((\-moz\-|\-webkit\-)?keyframes)\s*([^\s]+)#', $buffer, $matches)) {
585 $keyframefor = $matches[1];
586 $keyframename = $matches[3];
587 $keyframe = new css_keyframe($keyframefor, $keyframename);
588 $keyframes[] = $keyframe;
589 $currentmedia = $keyframe;
590 $currentprocess = self::PROCESSING_SELECTORS;
591 $buffer = '';
593 // continue 1: The switch processing chars
594 // continue 2: The switch processing the state
595 // continue 3: The for loop
596 continue 3;
598 break;
599 // Start processing selectors
600 case self::PROCESSING_START:
601 case self::PROCESSING_SELECTORS:
602 switch ($char) {
603 case '[':
604 $inbrackets ++;
605 $buffer .= $char;
606 // continue 1: The switch processing chars
607 // continue 2: The switch processing the state
608 // continue 3: The for loop
609 continue 3;
610 case ']':
611 $inbrackets --;
612 $buffer .= $char;
613 // continue 1: The switch processing chars
614 // continue 2: The switch processing the state
615 // continue 3: The for loop
616 continue 3;
617 case ' ':
618 if ($inbrackets) {
619 // continue 1: The switch processing chars
620 // continue 2: The switch processing the state
621 // continue 3: The for loop
622 continue 3;
624 if (!empty($buffer)) {
625 if ($suspectatrule && preg_match('#@(media|import|charset|(\-moz\-|\-webkit\-)?(keyframes))\s*#', $buffer, $matches)) {
626 $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
627 $currentprocess = self::PROCESSING_ATRULE;
628 $buffer .= $char;
629 } else {
630 $currentselector->add($buffer);
631 $buffer = '';
634 $suspectatrule = false;
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 ($buffer !== '') {
647 $currentselector->add($buffer);
649 $currentrule->add_selector($currentselector);
650 $currentselector = css_selector::init();
651 $currentprocess = self::PROCESSING_STYLES;
653 $buffer = '';
654 // continue 1: The switch processing chars
655 // continue 2: The switch processing the state
656 // continue 3: The for loop
657 continue 3;
658 case '}':
659 if ($inbrackets) {
660 // continue 1: The switch processing chars
661 // continue 2: The switch processing the state
662 // continue 3: The for loop
663 continue 3;
665 if ($currentatrule == 'media') {
666 $currentmedia = $medias['all'];
667 $currentatrule = false;
668 $buffer = '';
669 } else if (strpos($currentatrule, 'keyframes') !== false) {
670 $currentmedia = $medias['all'];
671 $currentatrule = false;
672 $buffer = '';
674 // continue 1: The switch processing chars
675 // continue 2: The switch processing the state
676 // continue 3: The for loop
677 continue 3;
678 case ',':
679 if ($inbrackets) {
680 // continue 1: The switch processing chars
681 // continue 2: The switch processing the state
682 // continue 3: The for loop
683 continue 3;
685 $currentselector->add($buffer);
686 $currentrule->add_selector($currentselector);
687 $currentselector = css_selector::init();
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;
694 break;
695 // Start processing styles
696 case self::PROCESSING_STYLES:
697 if ($char == '"' || $char == "'") {
698 if ($inquotes === false) {
699 $inquotes = $char;
701 if ($inquotes === $char && $lastchar !== '\\') {
702 $inquotes = false;
705 if ($inquotes) {
706 $buffer .= $char;
707 continue 2;
709 switch ($char) {
710 case ';':
711 if ($inparenthesis) {
712 $buffer .= $char;
713 // continue 1: The switch processing chars
714 // continue 2: The switch processing the state
715 // continue 3: The for loop
716 continue 3;
718 $currentrule->add_style($buffer);
719 $buffer = '';
720 $inquotes = false;
721 // continue 1: The switch processing chars
722 // continue 2: The switch processing the state
723 // continue 3: The for loop
724 continue 3;
725 case '}':
726 $currentrule->add_style($buffer);
727 $this->rawselectors += $currentrule->get_selector_count();
729 $currentmedia->add_rule($currentrule);
731 $currentrule = css_rule::init();
732 $currentprocess = self::PROCESSING_SELECTORS;
733 $this->rawrules++;
734 $buffer = '';
735 $inquotes = false;
736 $inparenthesis = false;
737 // continue 1: The switch processing chars
738 // continue 2: The switch processing the state
739 // continue 3: The for loop
740 continue 3;
741 case '(':
742 $inparenthesis = true;
743 $buffer .= $char;
744 // continue 1: The switch processing chars
745 // continue 2: The switch processing the state
746 // continue 3: The for loop
747 continue 3;
748 case ')':
749 $inparenthesis = false;
750 $buffer .= $char;
751 // continue 1: The switch processing chars
752 // continue 2: The switch processing the state
753 // continue 3: The for loop
754 continue 3;
756 break;
758 $buffer .= $char;
761 $css = '';
762 if (!empty($charset)) {
763 $imports[] = $charset;
765 if (!empty($imports)) {
766 $css .= implode("\n", $imports);
767 $css .= "\n\n";
770 foreach ($medias as $media) {
771 $media->organise_rules_by_selectors();
772 $this->optimisedrules += $media->count_rules();
773 $this->optimisedselectors += $media->count_selectors();
774 if ($media->has_errors()) {
775 $this->errors += $media->get_errors();
777 if (in_array('all', $media->get_types())) {
778 $resetrules = $media->get_reset_rules(true);
779 if (!empty($resetrules)) {
780 $css .= css_writer::media('all', $resetrules);
785 foreach ($medias as $media) {
786 $css .= $media->out();
789 if (count($keyframes) > 0) {
790 foreach ($keyframes as $keyframe) {
791 $this->optimisedrules += $keyframe->count_rules();
792 $this->optimisedselectors += $keyframe->count_selectors();
793 if ($keyframe->has_errors()) {
794 $this->errors += $keyframe->get_errors();
796 $css .= $keyframe->out();
800 $this->optimisedstrlen = strlen($css);
802 $this->timecomplete = microtime(true);
803 return trim($css);
807 * Returns an array of stats from the last processing run
808 * @return string
810 public function get_stats() {
811 $stats = array(
812 'timestart' => $this->timestart,
813 'timecomplete' => $this->timecomplete,
814 'timetaken' => round($this->timecomplete - $this->timestart, 4),
815 'commentsincss' => $this->commentsincss,
816 'rawstrlen' => $this->rawstrlen,
817 'rawselectors' => $this->rawselectors,
818 'rawrules' => $this->rawrules,
819 'optimisedstrlen' => $this->optimisedstrlen,
820 'optimisedrules' => $this->optimisedrules,
821 'optimisedselectors' => $this->optimisedselectors,
822 'improvementstrlen' => '-',
823 'improvementrules' => '-',
824 'improvementselectors' => '-',
826 if ($this->rawstrlen > 0) {
827 $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
829 if ($this->rawrules > 0) {
830 $stats['improvementrules'] = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%';
832 if ($this->rawselectors > 0) {
833 $stats['improvementselectors'] = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%';
835 return $stats;
839 * Returns true if any errors have occured during processing
841 * @return bool
843 public function has_errors() {
844 return !empty($this->errors);
848 * Returns an array of errors that have occured
850 * @param bool $clear If set to true the errors will be cleared after being returned.
851 * @return array
853 public function get_errors($clear = false) {
854 $errors = $this->errors;
855 if ($clear) {
856 // Reset the error array
857 $this->errors = array();
859 return $errors;
863 * Returns any errors as a string that can be included in CSS.
865 * @return string
867 public function output_errors_css() {
868 $computedcss = "/****************************************\n";
869 $computedcss .= " *--- Errors found during processing ----\n";
870 foreach ($this->errors as $error) {
871 $computedcss .= preg_replace('#^#m', '* ', $error);
873 $computedcss .= " ****************************************/\n\n";
874 return $computedcss;
878 * Returns a string to display stats about the last generation within CSS output
880 * @return string
882 public function output_stats_css() {
884 $computedcss = "/****************************************\n";
885 $computedcss .= " *------- CSS Optimisation stats --------\n";
887 if ($this->rawstrlen === 0) {
888 $computedcss .= " File not processed as it has no content /\n\n";
889 $computedcss .= " ****************************************/\n\n";
890 return $computedcss;
891 } else if ($this->rawrules === 0) {
892 $computedcss .= " File contained no rules to be processed /\n\n";
893 $computedcss .= " ****************************************/\n\n";
894 return $computedcss;
897 $stats = $this->get_stats();
899 $computedcss .= " * ".date('r')."\n";
900 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
901 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
902 $computedcss .= " *--------------- before ----------------\n";
903 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n";
904 $computedcss .= " * {$stats['rawrules']} \t rules read in\n";
905 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n";
906 $computedcss .= " *---------------- after ----------------\n";
907 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n";
908 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n";
909 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n";
910 $computedcss .= " *---------------- stats ----------------\n";
911 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n";
912 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n";
913 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n";
914 $computedcss .= " ****************************************/\n\n";
916 return $computedcss;
920 * Resets the stats ready for another fresh processing
922 public function reset_stats() {
923 $this->commentsincss = 0;
924 $this->optimisedrules = 0;
925 $this->optimisedselectors = 0;
926 $this->optimisedstrlen = 0;
927 $this->rawrules = 0;
928 $this->rawselectors = 0;
929 $this->rawstrlen = 0;
930 $this->timecomplete = 0;
931 $this->timestart = 0;
935 * An array of the common HTML colours that are supported by most browsers.
937 * This reference table is used to allow us to unify colours, and will aid
938 * us in identifying buggy CSS using unsupported colours.
940 * @staticvar array
941 * @var array
943 public static $htmlcolours = array(
944 'aliceblue' => '#F0F8FF',
945 'antiquewhite' => '#FAEBD7',
946 'aqua' => '#00FFFF',
947 'aquamarine' => '#7FFFD4',
948 'azure' => '#F0FFFF',
949 'beige' => '#F5F5DC',
950 'bisque' => '#FFE4C4',
951 'black' => '#000000',
952 'blanchedalmond' => '#FFEBCD',
953 'blue' => '#0000FF',
954 'blueviolet' => '#8A2BE2',
955 'brown' => '#A52A2A',
956 'burlywood' => '#DEB887',
957 'cadetblue' => '#5F9EA0',
958 'chartreuse' => '#7FFF00',
959 'chocolate' => '#D2691E',
960 'coral' => '#FF7F50',
961 'cornflowerblue' => '#6495ED',
962 'cornsilk' => '#FFF8DC',
963 'crimson' => '#DC143C',
964 'cyan' => '#00FFFF',
965 'darkblue' => '#00008B',
966 'darkcyan' => '#008B8B',
967 'darkgoldenrod' => '#B8860B',
968 'darkgray' => '#A9A9A9',
969 'darkgrey' => '#A9A9A9',
970 'darkgreen' => '#006400',
971 'darkKhaki' => '#BDB76B',
972 'darkmagenta' => '#8B008B',
973 'darkolivegreen' => '#556B2F',
974 'arkorange' => '#FF8C00',
975 'darkorchid' => '#9932CC',
976 'darkred' => '#8B0000',
977 'darksalmon' => '#E9967A',
978 'darkseagreen' => '#8FBC8F',
979 'darkslateblue' => '#483D8B',
980 'darkslategray' => '#2F4F4F',
981 'darkslategrey' => '#2F4F4F',
982 'darkturquoise' => '#00CED1',
983 'darkviolet' => '#9400D3',
984 'deeppink' => '#FF1493',
985 'deepskyblue' => '#00BFFF',
986 'dimgray' => '#696969',
987 'dimgrey' => '#696969',
988 'dodgerblue' => '#1E90FF',
989 'firebrick' => '#B22222',
990 'floralwhite' => '#FFFAF0',
991 'forestgreen' => '#228B22',
992 'fuchsia' => '#FF00FF',
993 'gainsboro' => '#DCDCDC',
994 'ghostwhite' => '#F8F8FF',
995 'gold' => '#FFD700',
996 'goldenrod' => '#DAA520',
997 'gray' => '#808080',
998 'grey' => '#808080',
999 'green' => '#008000',
1000 'greenyellow' => '#ADFF2F',
1001 'honeydew' => '#F0FFF0',
1002 'hotpink' => '#FF69B4',
1003 'indianred ' => '#CD5C5C',
1004 'indigo ' => '#4B0082',
1005 'ivory' => '#FFFFF0',
1006 'khaki' => '#F0E68C',
1007 'lavender' => '#E6E6FA',
1008 'lavenderblush' => '#FFF0F5',
1009 'lawngreen' => '#7CFC00',
1010 'lemonchiffon' => '#FFFACD',
1011 'lightblue' => '#ADD8E6',
1012 'lightcoral' => '#F08080',
1013 'lightcyan' => '#E0FFFF',
1014 'lightgoldenrodyellow' => '#FAFAD2',
1015 'lightgray' => '#D3D3D3',
1016 'lightgrey' => '#D3D3D3',
1017 'lightgreen' => '#90EE90',
1018 'lightpink' => '#FFB6C1',
1019 'lightsalmon' => '#FFA07A',
1020 'lightseagreen' => '#20B2AA',
1021 'lightskyblue' => '#87CEFA',
1022 'lightslategray' => '#778899',
1023 'lightslategrey' => '#778899',
1024 'lightsteelblue' => '#B0C4DE',
1025 'lightyellow' => '#FFFFE0',
1026 'lime' => '#00FF00',
1027 'limegreen' => '#32CD32',
1028 'linen' => '#FAF0E6',
1029 'magenta' => '#FF00FF',
1030 'maroon' => '#800000',
1031 'mediumaquamarine' => '#66CDAA',
1032 'mediumblue' => '#0000CD',
1033 'mediumorchid' => '#BA55D3',
1034 'mediumpurple' => '#9370D8',
1035 'mediumseagreen' => '#3CB371',
1036 'mediumslateblue' => '#7B68EE',
1037 'mediumspringgreen' => '#00FA9A',
1038 'mediumturquoise' => '#48D1CC',
1039 'mediumvioletred' => '#C71585',
1040 'midnightblue' => '#191970',
1041 'mintcream' => '#F5FFFA',
1042 'mistyrose' => '#FFE4E1',
1043 'moccasin' => '#FFE4B5',
1044 'navajowhite' => '#FFDEAD',
1045 'navy' => '#000080',
1046 'oldlace' => '#FDF5E6',
1047 'olive' => '#808000',
1048 'olivedrab' => '#6B8E23',
1049 'orange' => '#FFA500',
1050 'orangered' => '#FF4500',
1051 'orchid' => '#DA70D6',
1052 'palegoldenrod' => '#EEE8AA',
1053 'palegreen' => '#98FB98',
1054 'paleturquoise' => '#AFEEEE',
1055 'palevioletred' => '#D87093',
1056 'papayawhip' => '#FFEFD5',
1057 'peachpuff' => '#FFDAB9',
1058 'peru' => '#CD853F',
1059 'pink' => '#FFC0CB',
1060 'plum' => '#DDA0DD',
1061 'powderblue' => '#B0E0E6',
1062 'purple' => '#800080',
1063 'red' => '#FF0000',
1064 'rosybrown' => '#BC8F8F',
1065 'royalblue' => '#4169E1',
1066 'saddlebrown' => '#8B4513',
1067 'salmon' => '#FA8072',
1068 'sandybrown' => '#F4A460',
1069 'seagreen' => '#2E8B57',
1070 'seashell' => '#FFF5EE',
1071 'sienna' => '#A0522D',
1072 'silver' => '#C0C0C0',
1073 'skyblue' => '#87CEEB',
1074 'slateblue' => '#6A5ACD',
1075 'slategray' => '#708090',
1076 'slategrey' => '#708090',
1077 'snow' => '#FFFAFA',
1078 'springgreen' => '#00FF7F',
1079 'steelblue' => '#4682B4',
1080 'tan' => '#D2B48C',
1081 'teal' => '#008080',
1082 'thistle' => '#D8BFD8',
1083 'tomato' => '#FF6347',
1084 'transparent' => 'transparent',
1085 'turquoise' => '#40E0D0',
1086 'violet' => '#EE82EE',
1087 'wheat' => '#F5DEB3',
1088 'white' => '#FFFFFF',
1089 'whitesmoke' => '#F5F5F5',
1090 'yellow' => '#FFFF00',
1091 'yellowgreen' => '#9ACD32'
1096 * Used to prepare CSS strings
1098 * @package core_css
1099 * @category css
1100 * @copyright 2012 Sam Hemelryk
1101 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1103 abstract class css_writer {
1106 * The current indent level
1107 * @var int
1109 protected static $indent = 0;
1112 * Returns true if the output should still maintain minimum formatting.
1113 * @return bool
1115 protected static function is_pretty() {
1116 global $CFG;
1117 return (!empty($CFG->cssoptimiserpretty));
1121 * Returns the indenting char to use for indenting things nicely.
1122 * @return string
1124 protected static function get_indent() {
1125 if (self::is_pretty()) {
1126 return str_repeat(" ", self::$indent);
1128 return '';
1132 * Increases the current indent
1134 protected static function increase_indent() {
1135 self::$indent++;
1139 * Decreases the current indent
1141 protected static function decrease_indent() {
1142 self::$indent--;
1146 * Returns the string to use as a separator
1147 * @return string
1149 protected static function get_separator() {
1150 return (self::is_pretty())?"\n":' ';
1154 * Returns CSS for media
1156 * @param string $typestring
1157 * @param array $rules An array of css_rule objects
1158 * @return string
1160 public static function media($typestring, array &$rules) {
1161 $nl = self::get_separator();
1163 $output = '';
1164 if ($typestring !== 'all') {
1165 $output .= $nl.$nl."@media {$typestring} {".$nl;
1166 self::increase_indent();
1168 foreach ($rules as $rule) {
1169 $output .= $rule->out().$nl;
1171 if ($typestring !== 'all') {
1172 self::decrease_indent();
1173 $output .= '}';
1175 return $output;
1178 public static function keyframe($for, $name, array &$rules) {
1179 $nl = self::get_separator();
1181 $output = $nl."@{$for} {$name} {";
1182 foreach ($rules as $rule) {
1183 $output .= $rule->out();
1185 $output .= '}';
1186 return $output;
1190 * Returns CSS for a rule
1192 * @param string $selector
1193 * @param string $styles
1194 * @return string
1196 public static function rule($selector, $styles) {
1197 $css = self::get_indent()."{$selector}{{$styles}}";
1198 return $css;
1202 * Returns CSS for the selectors of a rule
1204 * @param array $selectors Array of css_selector objects
1205 * @return string
1207 public static function selectors(array $selectors) {
1208 $nl = self::get_separator();
1209 $selectorstrings = array();
1210 foreach ($selectors as $selector) {
1211 $selectorstrings[] = $selector->out();
1213 return join(','.$nl, $selectorstrings);
1217 * Returns a selector given the components that make it up.
1219 * @param array $components
1220 * @return string
1222 public static function selector(array $components) {
1223 return trim(join(' ', $components));
1227 * Returns a CSS string for the provided styles
1229 * @param array $styles Array of css_style objects
1230 * @return string
1232 public static function styles(array $styles) {
1233 $bits = array();
1234 foreach ($styles as $style) {
1235 $bits[] = $style->out();
1237 return join('', $bits);
1241 * Returns a style CSS
1243 * @param string $name
1244 * @param string $value
1245 * @param bool $important
1246 * @return string
1248 public static function style($name, $value, $important = false) {
1249 $value = trim($value);
1250 if ($important && strpos($value, '!important') === false) {
1251 $value .= ' !important';
1253 return "{$name}:{$value};";
1258 * A structure to represent a CSS selector.
1260 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
1261 * rule.
1263 * @package core_css
1264 * @category css
1265 * @copyright 2012 Sam Hemelryk
1266 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1268 class css_selector {
1271 * An array of selector bits
1272 * @var array
1274 protected $selectors = array();
1277 * The number of selectors.
1278 * @var int
1280 protected $count = 0;
1283 * Is null if there are no selector, true if all selectors are basic and false otherwise.
1284 * @var bool|null
1286 protected $isbasic = null;
1289 * Initialises a new CSS selector
1290 * @return css_selector
1292 public static function init() {
1293 return new css_selector();
1297 * CSS selectors can only be created through the init method above.
1299 protected function __construct() {}
1302 * Adds a selector to the end of the current selector
1303 * @param string $selector
1305 public function add($selector) {
1306 $selector = trim($selector);
1307 $count = 0;
1308 $count += preg_match_all('/(\.|#)/', $selector, $matchesarray);
1309 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
1310 $count ++;
1312 if ($this->isbasic !== false) {
1313 if ($count > 1) {
1314 $this->isbasic = false;
1315 } else {
1316 $this->isbasic = (preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector))?true:false;
1319 $this->count = $count;
1320 $this->selectors[] = $selector;
1323 * Returns the number of individual components that make up this selector
1324 * @return int
1326 public function get_selector_count() {
1327 return $this->count;
1331 * Returns the selector for use in a CSS rule
1332 * @return string
1334 public function out() {
1335 return css_writer::selector($this->selectors);
1339 * Returns true is all of the selectors act only upon basic elements (no classes/ids)
1340 * @return bool
1342 public function is_basic() {
1343 return ($this->isbasic === true);
1348 * A structure to represent a CSS rule.
1350 * @package core_css
1351 * @category css
1352 * @copyright 2012 Sam Hemelryk
1353 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1355 class css_rule {
1358 * An array of CSS selectors {@see css_selector}
1359 * @var array
1361 protected $selectors = array();
1364 * An array of CSS styles {@see css_style}
1365 * @var array
1367 protected $styles = array();
1370 * Created a new CSS rule. This is the only way to create a new CSS rule externally.
1371 * @return css_rule
1373 public static function init() {
1374 return new css_rule();
1378 * Constructs a new css rule.
1380 * @param string $selector The selector or array of selectors that make up this rule.
1381 * @param array $styles An array of styles that belong to this rule.
1383 protected function __construct($selector = null, array $styles = array()) {
1384 if ($selector != null) {
1385 if (is_array($selector)) {
1386 $this->selectors = $selector;
1387 } else {
1388 $this->selectors = array($selector);
1390 $this->add_styles($styles);
1395 * Adds a new CSS selector to this rule
1397 * e.g. $rule->add_selector('.one #two.two');
1399 * @param css_selector $selector Adds a CSS selector to this rule.
1401 public function add_selector(css_selector $selector) {
1402 $this->selectors[] = $selector;
1406 * Adds a new CSS style to this rule.
1408 * @param css_style|string $style Adds a new style to this rule
1410 public function add_style($style) {
1411 if (is_string($style)) {
1412 $style = trim($style);
1413 if (empty($style)) {
1414 return;
1416 $bits = explode(':', $style, 2);
1417 if (count($bits) == 2) {
1418 list($name, $value) = array_map('trim', $bits);
1420 if (isset($name) && isset($value) && $name !== '' && $value !== '') {
1421 $style = css_style::init_automatic($name, $value);
1423 } else if ($style instanceof css_style) {
1424 // Clone the style as it may be coming from another rule and we don't
1425 // want references as it will likely be overwritten by proceeding
1426 // rules
1427 $style = clone($style);
1429 if ($style instanceof css_style) {
1430 $name = $style->get_name();
1431 if (array_key_exists($name, $this->styles)) {
1432 $this->styles[$name]->set_value($style->get_value());
1433 } else {
1434 $this->styles[$name] = $style;
1436 } else if (is_array($style)) {
1437 // We probably shouldn't worry about processing styles here but to
1438 // be truthful it doesn't hurt.
1439 foreach ($style as $astyle) {
1440 $this->add_style($astyle);
1446 * An easy method of adding several styles at once. Just calls add_style.
1448 * This method simply iterates over the array and calls {@see css_rule::add_style()}
1449 * with each.
1451 * @param array $styles Adds an array of styles
1453 public function add_styles(array $styles) {
1454 foreach ($styles as $style) {
1455 $this->add_style($style);
1460 * Returns the array of selectors
1462 * @return array
1464 public function get_selectors() {
1465 return $this->selectors;
1469 * Returns the array of styles
1471 * @return array
1473 public function get_styles() {
1474 return $this->styles;
1478 * Outputs this rule as a fragment of CSS
1480 * @return string
1482 public function out() {
1483 $selectors = css_writer::selectors($this->selectors);
1484 $styles = css_writer::styles($this->get_consolidated_styles());
1485 return css_writer::rule($selectors, $styles);
1489 * Consolidates all styles associated with this rule
1491 * @return array An array of consolidated styles
1493 public function get_consolidated_styles() {
1494 $finalstyles = array();
1495 $consolidate = array();
1496 foreach ($this->styles as $style) {
1497 $consolidatetoclass = $style->consolidate_to();
1498 if ($style->is_valid() && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
1499 $class = 'css_style_'.$consolidatetoclass;
1500 if (!array_key_exists($class, $consolidate)) {
1501 $consolidate[$class] = array();
1503 $consolidate[$class][] = $style;
1504 } else {
1505 $finalstyles[] = $style;
1509 foreach ($consolidate as $class => $styles) {
1510 $styles = $class::consolidate($styles);
1511 foreach ($styles as $style) {
1512 $finalstyles[] = $style;
1515 return $finalstyles;
1519 * Splits this rules into an array of CSS rules. One for each of the selectors
1520 * that make up this rule.
1522 * @return array(css_rule)
1524 public function split_by_selector() {
1525 $return = array();
1526 foreach ($this->selectors as $selector) {
1527 $return[] = new css_rule($selector, $this->styles);
1529 return $return;
1533 * Splits this rule into an array of rules. One for each of the styles that
1534 * make up this rule
1536 * @return array Array of css_rule objects
1538 public function split_by_style() {
1539 $return = array();
1540 foreach ($this->styles as $style) {
1541 $return[] = new css_rule($this->selectors, array($style));
1543 return $return;
1547 * Gets a hash for the styles of this rule
1549 * @return string
1551 public function get_style_hash() {
1552 return md5(css_writer::styles($this->styles));
1556 * Gets a hash for the selectors of this rule
1558 * @return string
1560 public function get_selector_hash() {
1561 return md5(css_writer::selectors($this->selectors));
1565 * Gets the number of selectors that make up this rule.
1567 * @return int
1569 public function get_selector_count() {
1570 $count = 0;
1571 foreach ($this->selectors as $selector) {
1572 $count += $selector->get_selector_count();
1574 return $count;
1578 * Returns true if there are any errors with this rule.
1580 * @return bool
1582 public function has_errors() {
1583 foreach ($this->styles as $style) {
1584 if ($style->has_error()) {
1585 return true;
1588 return false;
1592 * Returns the error strings that were recorded when processing this rule.
1594 * Before calling this function you should first call {@see css_rule::has_errors()}
1595 * to make sure there are errors (hopefully there arn't).
1597 * @return string
1599 public function get_error_string() {
1600 $css = $this->out();
1601 $errors = array();
1602 foreach ($this->styles as $style) {
1603 if ($style->has_error()) {
1604 $errors[] = " * ".$style->get_last_error();
1607 return $css." has the following errors:\n".join("\n", $errors);
1611 * Returns true if this rule could be considered a reset rule.
1613 * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector.
1615 * @return bool
1617 public function is_reset_rule() {
1618 foreach ($this->selectors as $selector) {
1619 if (!$selector->is_basic()) {
1620 return false;
1623 return true;
1627 abstract class css_rule_collection {
1629 * An array of rules within this collection instance
1630 * @var array
1632 protected $rules = array();
1635 * The collection must be able to print itself.
1637 abstract public function out();
1640 * Adds a new CSS rule to this collection instance
1642 * @param css_rule $newrule
1644 public function add_rule(css_rule $newrule) {
1645 foreach ($newrule->split_by_selector() as $rule) {
1646 $hash = $rule->get_selector_hash();
1647 if (!array_key_exists($hash, $this->rules)) {
1648 $this->rules[$hash] = $rule;
1649 } else {
1650 $this->rules[$hash]->add_styles($rule->get_styles());
1656 * Returns the rules used by this collection
1658 * @return array
1660 public function get_rules() {
1661 return $this->rules;
1665 * Organises rules by gropuing selectors based upon the styles and consolidating
1666 * those selectors into single rules.
1668 * @return bool True if the CSS was optimised by this method
1670 public function organise_rules_by_selectors() {
1671 $optimised = array();
1672 $beforecount = count($this->rules);
1673 foreach ($this->rules as $rule) {
1674 $hash = $rule->get_style_hash();
1675 if (!array_key_exists($hash, $optimised)) {
1676 $optimised[$hash] = clone($rule);
1677 } else {
1678 foreach ($rule->get_selectors() as $selector) {
1679 $optimised[$hash]->add_selector($selector);
1683 $this->rules = $optimised;
1684 $aftercount = count($this->rules);
1685 return ($beforecount < $aftercount);
1689 * Returns the total number of rules that exist within this collection
1691 * @return int
1693 public function count_rules() {
1694 return count($this->rules);
1698 * Returns the total number of selectors that exist within this collection
1700 * @return int
1702 public function count_selectors() {
1703 $count = 0;
1704 foreach ($this->rules as $rule) {
1705 $count += $rule->get_selector_count();
1707 return $count;
1711 * Returns true if the collection has any rules that have errors
1713 * @return boolean
1715 public function has_errors() {
1716 foreach ($this->rules as $rule) {
1717 if ($rule->has_errors()) {
1718 return true;
1721 return false;
1725 * Returns any errors that have happened within rules in this collection.
1727 * @return string
1729 public function get_errors() {
1730 $errors = array();
1731 foreach ($this->rules as $rule) {
1732 if ($rule->has_errors()) {
1733 $errors[] = $rule->get_error_string();
1736 return $errors;
1741 * A media class to organise rules by the media they apply to.
1743 * @package core_css
1744 * @category css
1745 * @copyright 2012 Sam Hemelryk
1746 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1748 class css_media extends css_rule_collection {
1751 * An array of the different media types this instance applies to.
1752 * @var array
1754 protected $types = array();
1757 * Initalises a new media instance
1759 * @param string $for The media that the contained rules are destined for.
1761 public function __construct($for = 'all') {
1762 $types = explode(',', $for);
1763 $this->types = array_map('trim', $types);
1767 * Returns the CSS for this media and all of its rules.
1769 * @return string
1771 public function out() {
1772 return css_writer::media(join(',', $this->types), $this->rules);
1776 * Returns an array of media that this media instance applies to
1778 * @return array
1780 public function get_types() {
1781 return $this->types;
1785 * Returns all of the reset rules known by this media set.
1786 * @return array
1788 public function get_reset_rules($remove = false) {
1789 $resetrules = array();
1790 foreach ($this->rules as $key => $rule) {
1791 if ($rule->is_reset_rule()) {
1792 $resetrules[] = clone $rule;
1793 if ($remove) {
1794 unset($this->rules[$key]);
1798 return $resetrules;
1803 * A media class to organise rules by the media they apply to.
1805 * @package core_css
1806 * @category css
1807 * @copyright 2012 Sam Hemelryk
1808 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1810 class css_keyframe extends css_rule_collection {
1812 /** @var string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes */
1813 protected $for;
1815 /** @var string $name The name for the keyframes */
1816 protected $name;
1818 * Constructs a new keyframe
1820 * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
1821 * @param string $name The name for the keyframes
1823 public function __construct($for, $name) {
1824 $this->for = $for;
1825 $this->name = $name;
1828 * Returns the directive of this keyframe
1830 * e.g. keyframes, -moz-keyframes, -webkit-keyframes
1831 * @return string
1833 public function get_for() {
1834 return $this->for;
1837 * Returns the name of this keyframe
1838 * @return string
1840 public function get_name() {
1841 return $this->name;
1844 * Returns the CSS for this collection of keyframes and all of its rules.
1846 * @return string
1848 public function out() {
1849 return css_writer::keyframe($this->for, $this->name, $this->rules);
1854 * An absract class to represent CSS styles
1856 * @package core_css
1857 * @category css
1858 * @copyright 2012 Sam Hemelryk
1859 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1861 abstract class css_style {
1864 * The name of the style
1865 * @var string
1867 protected $name;
1870 * The value for the style
1871 * @var mixed
1873 protected $value;
1876 * If set to true this style was defined with the !important rule.
1877 * Only trolls use !important.
1878 * Don't hide under bridges.. its not good for your skin. Do the proper thing
1879 * and fix the issue don't just force a fix that will undoubtedly one day
1880 * lead to further frustration.
1881 * @var bool
1883 protected $important = false;
1886 * Gets set to true if this style has an error
1887 * @var bool
1889 protected $error = false;
1892 * The last error message that occured
1893 * @var string
1895 protected $errormessage = null;
1899 * Initialises a new style.
1901 * This is the only public way to create a style to ensure they that appropriate
1902 * style class is used if it exists.
1904 * @param string $name The name of the style.
1905 * @param string $value The value of the style.
1906 * @return css_style_generic
1908 public static function init_automatic($name, $value) {
1909 $specificclass = 'css_style_'.preg_replace('#[^a-zA-Z0-9]+#', '', $name);
1910 if (class_exists($specificclass)) {
1911 return $specificclass::init($value);
1913 return new css_style_generic($name, $value);
1917 * Creates a new style when given its name and value
1919 * @param string $name The name of the style.
1920 * @param string $value The value of the style.
1922 protected function __construct($name, $value) {
1923 $this->name = $name;
1924 $this->set_value($value);
1928 * Sets the value for the style
1930 * @param string $value
1932 final public function set_value($value) {
1933 $value = trim($value);
1934 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
1935 if ($important) {
1936 $value = substr($value, 0, -(strlen($matches[1])));
1937 $value = rtrim($value);
1939 if (!$this->important || $important) {
1940 $this->value = $this->clean_value($value);
1941 $this->important = $important;
1943 if (!$this->is_valid()) {
1944 $this->set_error('Invalid value for '.$this->name);
1949 * Returns true if the value associated with this style is valid
1951 * @return bool
1953 public function is_valid() {
1954 return true;
1958 * Returns the name for the style
1960 * @return string
1962 public function get_name() {
1963 return $this->name;
1967 * Returns the value for the style
1969 * @return string
1971 public function get_value() {
1972 $value = $this->value;
1973 if ($this->important) {
1974 $value .= ' !important';
1976 return $value;
1980 * Returns the style ready for use in CSS
1982 * @param string|null $value A value to use to override the value for this style.
1983 * @return string
1985 public function out($value = null) {
1986 if (is_null($value)) {
1987 $value = $this->get_value();
1989 return css_writer::style($this->name, $value, $this->important);
1993 * This can be overridden by a specific style allowing it to clean its values
1994 * consistently.
1996 * @param mixed $value
1997 * @return mixed
1999 protected function clean_value($value) {
2000 return $value;
2004 * If this particular style can be consolidated into another style this function
2005 * should return the style that it can be consolidated into.
2007 * @return string|null
2009 public function consolidate_to() {
2010 return null;
2014 * Sets the last error message.
2016 * @param string $message
2018 protected function set_error($message) {
2019 $this->error = true;
2020 $this->errormessage = $message;
2024 * Returns true if an error has occured
2026 * @return bool
2028 public function has_error() {
2029 return $this->error;
2033 * Returns the last error that occured or null if no errors have happened.
2035 * @return string
2037 public function get_last_error() {
2038 return $this->errormessage;
2043 * A generic CSS style class to use when a more specific class does not exist.
2045 * @package core_css
2046 * @category css
2047 * @copyright 2012 Sam Hemelryk
2048 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2050 class css_style_generic extends css_style {
2053 * Cleans incoming values for typical things that can be optimised.
2055 * @param mixed $value Cleans the provided value optimising it if possible
2056 * @return string
2058 protected function clean_value($value) {
2059 if (trim($value) == '0px') {
2060 $value = 0;
2061 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) {
2062 $value = '#'.strtoupper($matches[1]);
2064 return $value;
2069 * A colour CSS style
2071 * @package core_css
2072 * @category css
2073 * @copyright 2012 Sam Hemelryk
2074 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2076 class css_style_color extends css_style {
2079 * Creates a new colour style
2081 * @param mixed $value Initialises a new colour style
2082 * @return css_style_color
2084 public static function init($value) {
2085 return new css_style_color('color', $value);
2089 * Cleans the colour unifing it to a 6 char hash colour if possible
2090 * Doing this allows us to associate identical colours being specified in
2091 * different ways. e.g. Red, red, #F00, and #F00000
2093 * @param mixed $value Cleans the provided value optimising it if possible
2094 * @return string
2096 protected function clean_value($value) {
2097 $value = trim($value);
2098 if (css_is_colour($value)) {
2099 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) {
2100 $value = '#'.strtoupper($matches[1]);
2101 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) {
2102 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
2103 $value = '#'.strtoupper($value);
2104 } else if (array_key_exists(strtolower($value), css_optimiser::$htmlcolours)) {
2105 $value = css_optimiser::$htmlcolours[strtolower($value)];
2108 return $value;
2112 * Returns the colour style for use within CSS.
2113 * Will return an optimised hash colour.
2115 * e.g #123456
2116 * #123 instead of #112233
2117 * #F00 instead of red
2119 * @param string $overridevalue If provided then this value will be used instead
2120 * of the styles current value.
2121 * @return string
2123 public function out($overridevalue = null) {
2124 if ($overridevalue === null) {
2125 $overridevalue = $this->value;
2127 return parent::out(self::shrink_value($overridevalue));
2131 * Shrinks the colour value is possible.
2133 * @param string $value Shrinks the current value to an optimial form if possible
2134 * @return string
2136 public static function shrink_value($value) {
2137 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) {
2138 return '#'.$matches[1].$matches[2].$matches[3];
2140 return $value;
2144 * Returns true if the value is a valid colour.
2146 * @return bool
2148 public function is_valid() {
2149 return css_is_colour($this->value);
2154 * A width style
2156 * @package core_css
2157 * @category css
2158 * @copyright 2012 Sam Hemelryk
2159 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2161 class css_style_width extends css_style {
2164 * Checks if the width is valid
2165 * @return bool
2167 public function is_valid() {
2168 return css_is_width($this->value);
2172 * Cleans the provided value
2174 * @param mixed $value Cleans the provided value optimising it if possible
2175 * @return string
2177 protected function clean_value($value) {
2178 if (!css_is_width($value)) {
2179 // Note we don't actually change the value to something valid. That
2180 // would be bad for futureproofing.
2181 $this->set_error('Invalid width specified for '.$this->name);
2182 } else if (preg_match('#^0\D+$#', $value)) {
2183 $value = 0;
2185 return trim($value);
2189 * Initialises a new width style
2191 * @param mixed $value The value this style has
2192 * @return css_style_width
2194 public static function init($value) {
2195 return new css_style_width('width', $value);
2200 * A margin style
2202 * @package core_css
2203 * @category css
2204 * @copyright 2012 Sam Hemelryk
2205 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2207 class css_style_margin extends css_style_width {
2210 * Initialises a margin style.
2212 * In this case we split the margin into several other margin styles so that
2213 * we can properly condense overrides and then reconsolidate them later into
2214 * an optimal form.
2216 * @param string $value The value the style has
2217 * @return array An array of margin values that can later be consolidated
2219 public static function init($value) {
2220 $important = '';
2221 if (strpos($value, '!important') !== false) {
2222 $important = ' !important';
2223 $value = str_replace('!important', '', $value);
2226 $value = preg_replace('#\s+#', ' ', trim($value));
2227 $bits = explode(' ', $value, 4);
2229 $top = $right = $bottom = $left = null;
2230 if (count($bits) > 0) {
2231 $top = $right = $bottom = $left = array_shift($bits);
2233 if (count($bits) > 0) {
2234 $right = $left = array_shift($bits);
2236 if (count($bits) > 0) {
2237 $bottom = array_shift($bits);
2239 if (count($bits) > 0) {
2240 $left = array_shift($bits);
2242 return array(
2243 new css_style_margintop('margin-top', $top.$important),
2244 new css_style_marginright('margin-right', $right.$important),
2245 new css_style_marginbottom('margin-bottom', $bottom.$important),
2246 new css_style_marginleft('margin-left', $left.$important)
2251 * Consolidates individual margin styles into a single margin style
2253 * @param array $styles
2254 * @return array An array of consolidated styles
2256 public static function consolidate(array $styles) {
2257 if (count($styles) != 4) {
2258 return $styles;
2260 $top = $right = $bottom = $left = null;
2261 foreach ($styles as $style) {
2262 switch ($style->get_name()) {
2263 case 'margin-top' : $top = $style->get_value();break;
2264 case 'margin-right' : $right = $style->get_value();break;
2265 case 'margin-bottom' : $bottom = $style->get_value();break;
2266 case 'margin-left' : $left = $style->get_value();break;
2269 if ($top == $bottom && $left == $right) {
2270 if ($top == $left) {
2271 return array(new css_style_margin('margin', $top));
2272 } else {
2273 return array(new css_style_margin('margin', "{$top} {$left}"));
2275 } else if ($left == $right) {
2276 return array(new css_style_margin('margin', "{$top} {$right} {$bottom}"));
2277 } else {
2278 return array(new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}"));
2284 * A margin top style
2286 * @package core_css
2287 * @category css
2288 * @copyright 2012 Sam Hemelryk
2289 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2291 class css_style_margintop extends css_style_margin {
2294 * A simple init, just a single style
2296 * @param string $value The value the style has
2297 * @return css_style_margintop
2299 public static function init($value) {
2300 return new css_style_margintop('margin-top', $value);
2304 * This style can be consolidated into a single margin style
2306 * @return string
2308 public function consolidate_to() {
2309 return 'margin';
2314 * A margin right style
2316 * @package core_css
2317 * @category css
2318 * @copyright 2012 Sam Hemelryk
2319 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2321 class css_style_marginright extends css_style_margin {
2324 * A simple init, just a single style
2326 * @param string $value The value the style has
2327 * @return css_style_margintop
2329 public static function init($value) {
2330 return new css_style_marginright('margin-right', $value);
2334 * This style can be consolidated into a single margin style
2336 * @return string
2338 public function consolidate_to() {
2339 return 'margin';
2344 * A margin bottom style
2346 * @package core_css
2347 * @category css
2348 * @copyright 2012 Sam Hemelryk
2349 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2351 class css_style_marginbottom extends css_style_margin {
2354 * A simple init, just a single style
2356 * @param string $value The value the style has
2357 * @return css_style_margintop
2359 public static function init($value) {
2360 return new css_style_marginbottom('margin-bottom', $value);
2364 * This style can be consolidated into a single margin style
2366 * @return string
2368 public function consolidate_to() {
2369 return 'margin';
2374 * A margin left style
2376 * @package core_css
2377 * @category css
2378 * @copyright 2012 Sam Hemelryk
2379 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2381 class css_style_marginleft extends css_style_margin {
2384 * A simple init, just a single style
2386 * @param string $value The value the style has
2387 * @return css_style_margintop
2389 public static function init($value) {
2390 return new css_style_marginleft('margin-left', $value);
2394 * This style can be consolidated into a single margin style
2396 * @return string
2398 public function consolidate_to() {
2399 return 'margin';
2404 * A border style
2406 * @package core_css
2407 * @category css
2408 * @copyright 2012 Sam Hemelryk
2409 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2411 class css_style_border extends css_style {
2414 * Initalises the border style into an array of individual style compontents
2416 * @param string $value The value the style has
2417 * @return css_style_bordercolor
2419 public static function init($value) {
2420 $value = preg_replace('#\s+#', ' ', $value);
2421 $bits = explode(' ', $value, 3);
2423 $return = array();
2424 if (count($bits) > 0) {
2425 $width = array_shift($bits);
2426 if (!css_style_borderwidth::is_border_width($width)) {
2427 $width = '0';
2429 $return[] = new css_style_borderwidth('border-top-width', $width);
2430 $return[] = new css_style_borderwidth('border-right-width', $width);
2431 $return[] = new css_style_borderwidth('border-bottom-width', $width);
2432 $return[] = new css_style_borderwidth('border-left-width', $width);
2434 if (count($bits) > 0) {
2435 $style = array_shift($bits);
2436 $return[] = new css_style_borderstyle('border-top-style', $style);
2437 $return[] = new css_style_borderstyle('border-right-style', $style);
2438 $return[] = new css_style_borderstyle('border-bottom-style', $style);
2439 $return[] = new css_style_borderstyle('border-left-style', $style);
2441 if (count($bits) > 0) {
2442 $colour = array_shift($bits);
2443 $return[] = new css_style_bordercolor('border-top-color', $colour);
2444 $return[] = new css_style_bordercolor('border-right-color', $colour);
2445 $return[] = new css_style_bordercolor('border-bottom-color', $colour);
2446 $return[] = new css_style_bordercolor('border-left-color', $colour);
2448 return $return;
2452 * Consolidates all border styles into a single style
2454 * @param array $styles An array of border styles
2455 * @return array An optimised array of border styles
2457 public static function consolidate(array $styles) {
2459 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2460 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2461 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2463 foreach ($styles as $style) {
2464 switch ($style->get_name()) {
2465 case 'border-top-width': $borderwidths['top'] = $style->get_value(); break;
2466 case 'border-right-width': $borderwidths['right'] = $style->get_value(); break;
2467 case 'border-bottom-width': $borderwidths['bottom'] = $style->get_value(); break;
2468 case 'border-left-width': $borderwidths['left'] = $style->get_value(); break;
2470 case 'border-top-style': $borderstyles['top'] = $style->get_value(); break;
2471 case 'border-right-style': $borderstyles['right'] = $style->get_value(); break;
2472 case 'border-bottom-style': $borderstyles['bottom'] = $style->get_value(); break;
2473 case 'border-left-style': $borderstyles['left'] = $style->get_value(); break;
2475 case 'border-top-color': $bordercolors['top'] = $style->get_value(); break;
2476 case 'border-right-color': $bordercolors['right'] = $style->get_value(); break;
2477 case 'border-bottom-color': $bordercolors['bottom'] = $style->get_value(); break;
2478 case 'border-left-color': $bordercolors['left'] = $style->get_value(); break;
2482 $uniquewidths = count(array_unique($borderwidths));
2483 $uniquestyles = count(array_unique($borderstyles));
2484 $uniquecolors = count(array_unique($bordercolors));
2486 $nullwidths = in_array(null, $borderwidths, true);
2487 $nullstyles = in_array(null, $borderstyles, true);
2488 $nullcolors = in_array(null, $bordercolors, true);
2490 $allwidthsthesame = ($uniquewidths === 1)?1:0;
2491 $allstylesthesame = ($uniquestyles === 1)?1:0;
2492 $allcolorsthesame = ($uniquecolors === 1)?1:0;
2494 $allwidthsnull = $allwidthsthesame && $nullwidths;
2495 $allstylesnull = $allstylesthesame && $nullstyles;
2496 $allcolorsnull = $allcolorsthesame && $nullcolors;
2498 $return = array();
2499 if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
2500 // Everything is null still... boo
2501 return array(new css_style_border('border', ''));
2503 } else if ($allwidthsnull && $allstylesnull) {
2505 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2506 return $return;
2508 } else if ($allwidthsnull && $allcolorsnull) {
2510 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2511 return $return;
2513 } else if ($allcolorsnull && $allstylesnull) {
2515 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2516 return $return;
2520 if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 3) {
2522 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']);
2524 } else if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 2) {
2526 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) {
2528 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']);
2529 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2531 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) {
2533 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']);
2534 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2536 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) {
2538 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']);
2539 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2541 } else {
2542 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2543 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2544 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2547 } else if (!$nullwidths && !$nullcolors && !$nullstyles && max(array_count_values($borderwidths)) == 3 && max(array_count_values($borderstyles)) == 3 && max(array_count_values($bordercolors)) == 3) {
2548 $widthkeys = array();
2549 $stylekeys = array();
2550 $colorkeys = array();
2552 foreach ($borderwidths as $key => $value) {
2553 if (!array_key_exists($value, $widthkeys)) {
2554 $widthkeys[$value] = array();
2556 $widthkeys[$value][] = $key;
2558 usort($widthkeys, 'css_sort_by_count');
2559 $widthkeys = array_values($widthkeys);
2561 foreach ($borderstyles as $key => $value) {
2562 if (!array_key_exists($value, $stylekeys)) {
2563 $stylekeys[$value] = array();
2565 $stylekeys[$value][] = $key;
2567 usort($stylekeys, 'css_sort_by_count');
2568 $stylekeys = array_values($stylekeys);
2570 foreach ($bordercolors as $key => $value) {
2571 if (!array_key_exists($value, $colorkeys)) {
2572 $colorkeys[$value] = array();
2574 $colorkeys[$value][] = $key;
2576 usort($colorkeys, 'css_sort_by_count');
2577 $colorkeys = array_values($colorkeys);
2579 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
2580 $key = $widthkeys[0][0];
2581 self::build_style_string($return, 'css_style_border', 'border', $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2582 $key = $widthkeys[1][0];
2583 self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key, $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2584 } else {
2585 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2586 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2587 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2588 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2590 } else {
2591 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2592 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2593 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2594 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2596 foreach ($return as $key => $style) {
2597 if ($style->get_value() == '') {
2598 unset($return[$key]);
2601 return $return;
2605 * Border styles get consolidated to a single border style.
2607 * @return string
2609 public function consolidate_to() {
2610 return 'border';
2614 * Consolidates a series of border styles into an optimised array of border
2615 * styles by looking at the direction of the border and prioritising that
2616 * during the optimisation.
2618 * @param array $array An array to add styles into during consolidation. Passed by reference.
2619 * @param string $class The class type to initalise
2620 * @param string $style The style to create
2621 * @param string|array $top The top value
2622 * @param string $right The right value
2623 * @param string $bottom The bottom value
2624 * @param string $left The left value
2625 * @return bool
2627 public static function consolidate_styles_by_direction(&$array, $class, $style, $top, $right = null, $bottom = null, $left = null) {
2628 if (is_array($top)) {
2629 $right = $top['right'];
2630 $bottom = $top['bottom'];
2631 $left = $top['left'];
2632 $top = $top['top'];
2635 if ($top == $bottom && $left == $right && $top == $left) {
2636 if (is_null($top)) {
2637 $array[] = new $class($style, '');
2638 } else {
2639 $array[] = new $class($style, $top);
2641 } else if ($top == null || $right == null || $bottom == null || $left == null) {
2642 if ($top !== null) {
2643 $array[] = new $class(str_replace('border-', 'border-top-', $style), $top);
2645 if ($right !== null) {
2646 $array[] = new $class(str_replace('border-', 'border-right-', $style), $right);
2648 if ($bottom !== null) {
2649 $array[] = new $class(str_replace('border-', 'border-bottom-', $style), $bottom);
2651 if ($left !== null) {
2652 $array[] = new $class(str_replace('border-', 'border-left-', $style), $left);
2654 } else if ($top == $bottom && $left == $right) {
2655 $array[] = new $class($style, $top.' '.$right);
2656 } else if ($left == $right) {
2657 $array[] = new $class($style, $top.' '.$right.' '.$bottom);
2658 } else {
2659 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left);
2661 return true;
2665 * Builds a border style for a set of width, style, and colour values
2667 * @param array $array An array into which the generated style is added
2668 * @param string $class The class type to initialise
2669 * @param string $cssstyle The style to use
2670 * @param string $width The width of the border
2671 * @param string $style The style of the border
2672 * @param string $color The colour of the border
2673 * @return bool
2675 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) {
2676 if (!is_null($width) && !is_null($style) && !is_null($color)) {
2677 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color);
2678 } else if (!is_null($width) && !is_null($style) && is_null($color)) {
2679 $array[] = new $class($cssstyle, $width.' '.$style);
2680 } else if (!is_null($width) && is_null($style) && is_null($color)) {
2681 $array[] = new $class($cssstyle, $width);
2682 } else {
2683 if (!is_null($width)) $array[] = new $class($cssstyle, $width);
2684 if (!is_null($style)) $array[] = new $class($cssstyle, $style);
2685 if (!is_null($color)) $array[] = new $class($cssstyle, $color);
2687 return true;
2692 * A border colour style
2694 * @package core_css
2695 * @category css
2696 * @copyright 2012 Sam Hemelryk
2697 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2699 class css_style_bordercolor extends css_style_color {
2702 * Creates a new border colour style
2704 * Based upon the colour style
2706 * @param mixed $value
2707 * @return Array of css_style_bordercolor
2709 public static function init($value) {
2710 $value = preg_replace('#\s+#', ' ', $value);
2711 $bits = explode(' ', $value, 4);
2713 $top = $right = $bottom = $left = null;
2714 if (count($bits) > 0) {
2715 $top = $right = $bottom = $left = array_shift($bits);
2717 if (count($bits) > 0) {
2718 $right = $left = array_shift($bits);
2720 if (count($bits) > 0) {
2721 $bottom = array_shift($bits);
2723 if (count($bits) > 0) {
2724 $left = array_shift($bits);
2726 return array(
2727 css_style_bordertopcolor::init($top),
2728 css_style_borderrightcolor::init($right),
2729 css_style_borderbottomcolor::init($bottom),
2730 css_style_borderleftcolor::init($left)
2735 * Consolidate this to a single border style
2737 * @return string
2739 public function consolidate_to() {
2740 return 'border';
2744 * Cleans the value
2746 * @param string $value Cleans the provided value optimising it if possible
2747 * @return string
2749 protected function clean_value($value) {
2750 $values = explode(' ', $value);
2751 $values = array_map('parent::clean_value', $values);
2752 return join (' ', $values);
2756 * Outputs this style
2758 * @param string $overridevalue
2759 * @return string
2761 public function out($overridevalue = null) {
2762 if ($overridevalue === null) {
2763 $overridevalue = $this->value;
2765 $values = explode(' ', $overridevalue);
2766 $values = array_map('css_style_color::shrink_value', $values);
2767 return parent::out(join (' ', $values));
2772 * A border left style
2774 * @package core_css
2775 * @category css
2776 * @copyright 2012 Sam Hemelryk
2777 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2779 class css_style_borderleft extends css_style_generic {
2782 * Initialises the border left style into individual components
2784 * @param string $value
2785 * @return array Array of css_style_borderleftwidth|css_style_borderleftstyle|css_style_borderleftcolor
2787 public static function init($value) {
2788 $value = preg_replace('#\s+#', ' ', $value);
2789 $bits = explode(' ', $value, 3);
2791 $return = array();
2792 if (count($bits) > 0) {
2793 $return[] = css_style_borderleftwidth::init(array_shift($bits));
2795 if (count($bits) > 0) {
2796 $return[] = css_style_borderleftstyle::init(array_shift($bits));
2798 if (count($bits) > 0) {
2799 $return[] = css_style_borderleftcolor::init(array_shift($bits));
2801 return $return;
2805 * Consolidate this to a single border style
2807 * @return string
2809 public function consolidate_to() {
2810 return 'border';
2815 * A border right style
2817 * @package core_css
2818 * @category css
2819 * @copyright 2012 Sam Hemelryk
2820 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2822 class css_style_borderright extends css_style_generic {
2825 * Initialises the border right style into individual components
2827 * @param string $value The value of the style
2828 * @return array Array of css_style_borderrightwidth|css_style_borderrightstyle|css_style_borderrightcolor
2830 public static function init($value) {
2831 $value = preg_replace('#\s+#', ' ', $value);
2832 $bits = explode(' ', $value, 3);
2834 $return = array();
2835 if (count($bits) > 0) {
2836 $return[] = css_style_borderrightwidth::init(array_shift($bits));
2838 if (count($bits) > 0) {
2839 $return[] = css_style_borderrightstyle::init(array_shift($bits));
2841 if (count($bits) > 0) {
2842 $return[] = css_style_borderrightcolor::init(array_shift($bits));
2844 return $return;
2848 * Consolidate this to a single border style
2850 * @return string
2852 public function consolidate_to() {
2853 return 'border';
2858 * A border top style
2860 * @package core_css
2861 * @category css
2862 * @copyright 2012 Sam Hemelryk
2863 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2865 class css_style_bordertop extends css_style_generic {
2868 * Initialises the border top style into individual components
2870 * @param string $value The value of the style
2871 * @return array Array of css_style_bordertopwidth|css_style_bordertopstyle|css_style_bordertopcolor
2873 public static function init($value) {
2874 $value = preg_replace('#\s+#', ' ', $value);
2875 $bits = explode(' ', $value, 3);
2877 $return = array();
2878 if (count($bits) > 0) {
2879 $return[] = css_style_bordertopwidth::init(array_shift($bits));
2881 if (count($bits) > 0) {
2882 $return[] = css_style_bordertopstyle::init(array_shift($bits));
2884 if (count($bits) > 0) {
2885 $return[] = css_style_bordertopcolor::init(array_shift($bits));
2887 return $return;
2891 * Consolidate this to a single border style
2893 * @return string
2895 public function consolidate_to() {
2896 return 'border';
2901 * A border bottom style
2903 * @package core_css
2904 * @category css
2905 * @copyright 2012 Sam Hemelryk
2906 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2908 class css_style_borderbottom extends css_style_generic {
2911 * Initialises the border bottom style into individual components
2913 * @param string $value The value of the style
2914 * @return array Array of css_style_borderbottomwidth|css_style_borderbottomstyle|css_style_borderbottomcolor
2916 public static function init($value) {
2917 $value = preg_replace('#\s+#', ' ', $value);
2918 $bits = explode(' ', $value, 3);
2920 $return = array();
2921 if (count($bits) > 0) {
2922 $return[] = css_style_borderbottomwidth::init(array_shift($bits));
2924 if (count($bits) > 0) {
2925 $return[] = css_style_borderbottomstyle::init(array_shift($bits));
2927 if (count($bits) > 0) {
2928 $return[] = css_style_borderbottomcolor::init(array_shift($bits));
2930 return $return;
2934 * Consolidate this to a single border style
2936 * @return string
2938 public function consolidate_to() {
2939 return 'border';
2944 * A border width style
2946 * @package core_css
2947 * @category css
2948 * @copyright 2012 Sam Hemelryk
2949 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2951 class css_style_borderwidth extends css_style_width {
2954 * Creates a new border colour style
2956 * Based upon the colour style
2958 * @param string $value The value of the style
2959 * @return array Array of css_style_border*width
2961 public static function init($value) {
2962 $value = preg_replace('#\s+#', ' ', $value);
2963 $bits = explode(' ', $value, 4);
2965 $top = $right = $bottom = $left = null;
2966 if (count($bits) > 0) {
2967 $top = $right = $bottom = $left = array_shift($bits);
2969 if (count($bits) > 0) {
2970 $right = $left = array_shift($bits);
2972 if (count($bits) > 0) {
2973 $bottom = array_shift($bits);
2975 if (count($bits) > 0) {
2976 $left = array_shift($bits);
2978 return array(
2979 css_style_bordertopwidth::init($top),
2980 css_style_borderrightwidth::init($right),
2981 css_style_borderbottomwidth::init($bottom),
2982 css_style_borderleftwidth::init($left)
2987 * Consolidate this to a single border style
2989 * @return string
2991 public function consolidate_to() {
2992 return 'border';
2996 * Checks if the width is valid
2997 * @return bool
2999 public function is_valid() {
3000 return self::is_border_width($this->value);
3004 * Cleans the provided value
3006 * @param mixed $value Cleans the provided value optimising it if possible
3007 * @return string
3009 protected function clean_value($value) {
3010 $isvalid = self::is_border_width($value);
3011 if (!$isvalid) {
3012 $this->set_error('Invalid width specified for '.$this->name);
3013 } else if (preg_match('#^0\D+$#', $value)) {
3014 return '0';
3016 return trim($value);
3019 public static function is_border_width($value) {
3020 $altwidthvalues = array('thin', 'medium', 'thick');
3021 return css_is_width($value) || in_array($value, $altwidthvalues);
3026 * A border style style
3028 * @package core_css
3029 * @category css
3030 * @copyright 2012 Sam Hemelryk
3031 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3033 class css_style_borderstyle extends css_style_generic {
3036 * Creates a new border colour style
3038 * Based upon the colour style
3040 * @param string $value The value of the style
3041 * @return array Array of css_style_border*style
3043 public static function init($value) {
3044 $value = preg_replace('#\s+#', ' ', $value);
3045 $bits = explode(' ', $value, 4);
3047 $top = $right = $bottom = $left = null;
3048 if (count($bits) > 0) {
3049 $top = $right = $bottom = $left = array_shift($bits);
3051 if (count($bits) > 0) {
3052 $right = $left = array_shift($bits);
3054 if (count($bits) > 0) {
3055 $bottom = array_shift($bits);
3057 if (count($bits) > 0) {
3058 $left = array_shift($bits);
3060 return array(
3061 css_style_bordertopstyle::init($top),
3062 css_style_borderrightstyle::init($right),
3063 css_style_borderbottomstyle::init($bottom),
3064 css_style_borderleftstyle::init($left)
3069 * Consolidate this to a single border style
3071 * @return string
3073 public function consolidate_to() {
3074 return 'border';
3079 * A border top colour style
3081 * @package core_css
3082 * @category css
3083 * @copyright 2012 Sam Hemelryk
3084 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3086 class css_style_bordertopcolor extends css_style_bordercolor {
3089 * Initialises this style object
3091 * @param string $value The value of the style
3092 * @return css_style_bordertopcolor
3094 public static function init($value) {
3095 return new css_style_bordertopcolor('border-top-color', $value);
3099 * Consolidate this to a single border style
3101 * @return string
3103 public function consolidate_to() {
3104 return 'border';
3109 * A border left colour style
3111 * @package core_css
3112 * @category css
3113 * @copyright 2012 Sam Hemelryk
3114 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3116 class css_style_borderleftcolor extends css_style_bordercolor {
3119 * Initialises this style object
3121 * @param string $value The value of the style
3122 * @return css_style_borderleftcolor
3124 public static function init($value) {
3125 return new css_style_borderleftcolor('border-left-color', $value);
3129 * Consolidate this to a single border style
3131 * @return string
3133 public function consolidate_to() {
3134 return 'border';
3139 * A border right colour style
3141 * @package core_css
3142 * @category css
3143 * @copyright 2012 Sam Hemelryk
3144 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3146 class css_style_borderrightcolor extends css_style_bordercolor {
3149 * Initialises this style object
3151 * @param string $value The value of the style
3152 * @return css_style_borderrightcolor
3154 public static function init($value) {
3155 return new css_style_borderrightcolor('border-right-color', $value);
3159 * Consolidate this to a single border style
3161 * @return string
3163 public function consolidate_to() {
3164 return 'border';
3169 * A border bottom colour style
3171 * @package core_css
3172 * @category css
3173 * @copyright 2012 Sam Hemelryk
3174 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3176 class css_style_borderbottomcolor extends css_style_bordercolor {
3179 * Initialises this style object
3181 * @param string $value The value of the style
3182 * @return css_style_borderbottomcolor
3184 public static function init($value) {
3185 return new css_style_borderbottomcolor('border-bottom-color', $value);
3189 * Consolidate this to a single border style
3191 * @return string
3193 public function consolidate_to() {
3194 return 'border';
3199 * A border width top style
3201 * @package core_css
3202 * @category css
3203 * @copyright 2012 Sam Hemelryk
3204 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3206 class css_style_bordertopwidth extends css_style_borderwidth {
3209 * Initialises this style object
3211 * @param string $value The value of the style
3212 * @return css_style_bordertopwidth
3214 public static function init($value) {
3215 return new css_style_bordertopwidth('border-top-width', $value);
3219 * Consolidate this to a single border style
3221 * @return string
3223 public function consolidate_to() {
3224 return 'border';
3229 * A border width left style
3231 * @package core_css
3232 * @category css
3233 * @copyright 2012 Sam Hemelryk
3234 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3236 class css_style_borderleftwidth extends css_style_borderwidth {
3239 * Initialises this style object
3241 * @param string $value The value of the style
3242 * @return css_style_borderleftwidth
3244 public static function init($value) {
3245 return new css_style_borderleftwidth('border-left-width', $value);
3249 * Consolidate this to a single border style
3251 * @return string
3253 public function consolidate_to() {
3254 return 'border';
3259 * A border width right style
3261 * @package core_css
3262 * @category css
3263 * @copyright 2012 Sam Hemelryk
3264 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3266 class css_style_borderrightwidth extends css_style_borderwidth {
3269 * Initialises this style object
3271 * @param string $value The value of the style
3272 * @return css_style_borderrightwidth
3274 public static function init($value) {
3275 return new css_style_borderrightwidth('border-right-width', $value);
3279 * Consolidate this to a single border style
3281 * @return string
3283 public function consolidate_to() {
3284 return 'border';
3289 * A border width bottom style
3291 * @package core_css
3292 * @category css
3293 * @copyright 2012 Sam Hemelryk
3294 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3296 class css_style_borderbottomwidth extends css_style_borderwidth {
3299 * Initialises this style object
3301 * @param string $value The value of the style
3302 * @return css_style_borderbottomwidth
3304 public static function init($value) {
3305 return new css_style_borderbottomwidth('border-bottom-width', $value);
3309 * Consolidate this to a single border style
3311 * @return string
3313 public function consolidate_to() {
3314 return 'border';
3319 * A border top style
3321 * @package core_css
3322 * @category css
3323 * @copyright 2012 Sam Hemelryk
3324 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3326 class css_style_bordertopstyle extends css_style_borderstyle {
3329 * Initialises this style object
3331 * @param string $value The value of the style
3332 * @return css_style_bordertopstyle
3334 public static function init($value) {
3335 return new css_style_bordertopstyle('border-top-style', $value);
3339 * Consolidate this to a single border style
3341 * @return string
3343 public function consolidate_to() {
3344 return 'border';
3349 * A border left style
3351 * @package core_css
3352 * @category css
3353 * @copyright 2012 Sam Hemelryk
3354 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3356 class css_style_borderleftstyle extends css_style_borderstyle {
3359 * Initialises this style object
3361 * @param string $value The value of the style
3362 * @return css_style_borderleftstyle
3364 public static function init($value) {
3365 return new css_style_borderleftstyle('border-left-style', $value);
3369 * Consolidate this to a single border style
3371 * @return string
3373 public function consolidate_to() {
3374 return 'border';
3379 * A border right style
3381 * @package core_css
3382 * @category css
3383 * @copyright 2012 Sam Hemelryk
3384 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3386 class css_style_borderrightstyle extends css_style_borderstyle {
3389 * Initialises this style object
3391 * @param string $value The value of the style
3392 * @return css_style_borderrightstyle
3394 public static function init($value) {
3395 return new css_style_borderrightstyle('border-right-style', $value);
3399 * Consolidate this to a single border style
3401 * @return string
3403 public function consolidate_to() {
3404 return 'border';
3409 * A border bottom style
3411 * @package core_css
3412 * @category css
3413 * @copyright 2012 Sam Hemelryk
3414 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3416 class css_style_borderbottomstyle extends css_style_borderstyle {
3419 * Initialises this style object
3421 * @return css_style_borderbottomstyle
3423 public static function init($value) {
3424 return new css_style_borderbottomstyle('border-bottom-style', $value);
3428 * Consolidate this to a single border style
3430 * @return string
3432 public function consolidate_to() {
3433 return 'border';
3438 * A background style
3440 * @package core_css
3441 * @category css
3442 * @copyright 2012 Sam Hemelryk
3443 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3445 class css_style_background extends css_style {
3448 * Initialises a background style
3450 * @param type $value The value of the style
3451 * @return array An array of background component.
3453 public static function init($value) {
3454 // colour - image - repeat - attachment - position
3456 $imageurl = null;
3457 if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
3458 $imageurl = trim($matches[1]);
3459 $value = str_replace($matches[1], '', $value);
3462 $value = preg_replace('#\s+#', ' ', $value);
3463 $bits = explode(' ', $value);
3465 $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit');
3466 $attachments = array('scroll' , 'fixed', 'inherit');
3468 $return = array();
3469 $unknownbits = array();
3471 if (count($bits) > 0 && css_is_colour(reset($bits))) {
3472 $return[] = new css_style_backgroundcolor('background-color', array_shift($bits));
3474 if (count($bits) > 0 && preg_match('#(none|inherit|url\(\))#', reset($bits))) {
3475 $image = array_shift($bits);
3476 if ($image == 'url()') {
3477 $image = "url({$imageurl})";
3479 $return[] = new css_style_backgroundimage('background-image', $image);
3481 if (count($bits) > 0 && in_array(reset($bits), $repeats)) {
3482 $return[] = new css_style_backgroundrepeat('background-repeat', array_shift($bits));
3484 if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
3485 // scroll , fixed, inherit
3486 $return[] = new css_style_backgroundattachment('background-attachment', array_shift($bits));
3488 if (count($bits) > 0) {
3489 $widthbits = array();
3490 foreach ($bits as $bit) {
3491 if (in_array($bit, array('top', 'left', 'bottom', 'right', 'center')) || css_is_width($bit)) {
3492 $widthbits[] = $bit;
3493 } else {
3494 $unknownbits[] = $bit;
3497 $return[] = new css_style_backgroundposition('background-position', join(' ',$widthbits));
3499 if (count($unknownbits)) {
3500 foreach ($unknownbits as $bit) {
3501 if (css_is_colour($bit)) {
3502 $return[] = new css_style_backgroundcolor('background-color', $bit);
3503 } else if (in_array($bit, $repeats)) {
3504 $return[] = new css_style_backgroundrepeat('background-repeat', $bit);
3505 } else if (in_array($bit, $attachments)) {
3506 $return[] = new css_style_backgroundattachment('background-attachment', $bit);
3510 return $return;
3514 * Consolidates background styles into a single background style
3516 * @param array $styles Consolidates the provided array of background styles
3517 * @return array Consolidated optimised background styles
3519 public static function consolidate(array $styles) {
3521 if (count($styles) < 1) {
3522 return $styles;
3525 $color = $image = $repeat = $attachment = $position = null;
3526 foreach ($styles as $style) {
3527 switch ($style->get_name()) {
3528 case 'background-color' : $color = css_style_color::shrink_value($style->get_value()); break;
3529 case 'background-image' : $image = $style->get_value(); break;
3530 case 'background-repeat' : $repeat = $style->get_value(); break;
3531 case 'background-attachment' : $attachment = $style->get_value(); break;
3532 case 'background-position' : $position = $style->get_value(); break;
3536 if ((is_null($image) || is_null($position) || is_null($repeat)) && ($image!= null || $position != null || $repeat != null)) {
3537 return $styles;
3540 $value = array();
3541 if (!is_null($color)) $value[] .= $color;
3542 if (!is_null($image)) $value[] .= $image;
3543 if (!is_null($repeat)) $value[] .= $repeat;
3544 if (!is_null($attachment)) $value[] .= $attachment;
3545 if (!is_null($position)) $value[] .= $position;
3546 return array(new css_style_background('background', join(' ', $value)));
3551 * A background colour style.
3553 * Based upon the colour style.
3555 * @package core_css
3556 * @category css
3557 * @copyright 2012 Sam Hemelryk
3558 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3560 class css_style_backgroundcolor extends css_style_color {
3563 * Creates a new background colour style
3565 * @param string $value The value of the style
3566 * @return css_style_backgroundcolor
3568 public static function init($value) {
3569 return new css_style_backgroundcolor('background-color', $value);
3573 * css_style_backgroundcolor consolidates to css_style_background
3575 * @return string
3577 public function consolidate_to() {
3578 return 'background';
3583 * A background image style.
3585 * @package core_css
3586 * @category css
3587 * @copyright 2012 Sam Hemelryk
3588 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3590 class css_style_backgroundimage extends css_style_generic {
3593 * Creates a new background colour style
3595 * @param string $value The value of the style
3596 * @return css_style_backgroundimage
3598 public static function init($value) {
3599 return new css_style_backgroundimage('background-image', $value);
3603 * Consolidates this style into a single background style
3605 * @return string
3607 public function consolidate_to() {
3608 return 'background';
3613 * A background repeat style.
3615 * @package core_css
3616 * @category css
3617 * @copyright 2012 Sam Hemelryk
3618 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3620 class css_style_backgroundrepeat extends css_style_generic {
3623 * Creates a new background colour style
3625 * @param string $value The value of the style
3626 * @return css_style_backgroundrepeat
3628 public static function init($value) {
3629 return new css_style_backgroundrepeat('background-repeat', $value);
3633 * Consolidates this style into a single background style
3635 * @return string
3637 public function consolidate_to() {
3638 return 'background';
3643 * A background attachment style.
3645 * @package core_css
3646 * @category css
3647 * @copyright 2012 Sam Hemelryk
3648 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3650 class css_style_backgroundattachment extends css_style_generic {
3653 * Creates a new background colour style
3655 * @param string $value The value of the style
3656 * @return css_style_backgroundattachment
3658 public static function init($value) {
3659 return new css_style_backgroundattachment('background-attachment', $value);
3663 * Consolidates this style into a single background style
3665 * @return string
3667 public function consolidate_to() {
3668 return 'background';
3673 * A background position style.
3675 * @package core_css
3676 * @category css
3677 * @copyright 2012 Sam Hemelryk
3678 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3680 class css_style_backgroundposition extends css_style_generic {
3683 * Creates a new background colour style
3685 * @param string $value The value of the style
3686 * @return css_style_backgroundposition
3688 public static function init($value) {
3689 return new css_style_backgroundposition('background-position', $value);
3693 * Consolidates this style into a single background style
3695 * @return string
3697 public function consolidate_to() {
3698 return 'background';
3703 * A padding style.
3705 * @package core_css
3706 * @category css
3707 * @copyright 2012 Sam Hemelryk
3708 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3710 class css_style_padding extends css_style_width {
3713 * Initialises this padding style into several individual padding styles
3715 * @param string $value The value fo the style
3716 * @return array An array of padding styles
3718 public static function init($value) {
3719 $important = '';
3720 if (strpos($value, '!important') !== false) {
3721 $important = ' !important';
3722 $value = str_replace('!important', '', $value);
3725 $value = preg_replace('#\s+#', ' ', trim($value));
3726 $bits = explode(' ', $value, 4);
3728 $top = $right = $bottom = $left = null;
3729 if (count($bits) > 0) {
3730 $top = $right = $bottom = $left = array_shift($bits);
3732 if (count($bits) > 0) {
3733 $right = $left = array_shift($bits);
3735 if (count($bits) > 0) {
3736 $bottom = array_shift($bits);
3738 if (count($bits) > 0) {
3739 $left = array_shift($bits);
3741 return array(
3742 new css_style_paddingtop('padding-top', $top.$important),
3743 new css_style_paddingright('padding-right', $right.$important),
3744 new css_style_paddingbottom('padding-bottom', $bottom.$important),
3745 new css_style_paddingleft('padding-left', $left.$important)
3750 * Consolidates several padding styles into a single style.
3752 * @param array $styles Array of padding styles
3753 * @return array Optimised+consolidated array of padding styles
3755 public static function consolidate(array $styles) {
3756 if (count($styles) != 4) {
3757 return $styles;
3759 $top = $right = $bottom = $left = null;
3760 foreach ($styles as $style) {
3761 switch ($style->get_name()) {
3762 case 'padding-top' : $top = $style->get_value();break;
3763 case 'padding-right' : $right = $style->get_value();break;
3764 case 'padding-bottom' : $bottom = $style->get_value();break;
3765 case 'padding-left' : $left = $style->get_value();break;
3768 if ($top == $bottom && $left == $right) {
3769 if ($top == $left) {
3770 return array(new css_style_padding('padding', $top));
3771 } else {
3772 return array(new css_style_padding('padding', "{$top} {$left}"));
3774 } else if ($left == $right) {
3775 return array(new css_style_padding('padding', "{$top} {$right} {$bottom}"));
3776 } else {
3777 return array(new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}"));
3783 * A padding top style.
3785 * @package core_css
3786 * @category css
3787 * @copyright 2012 Sam Hemelryk
3788 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3790 class css_style_paddingtop extends css_style_padding {
3793 * Initialises this style
3795 * @param string $value The value of the style
3796 * @return css_style_paddingtop
3798 public static function init($value) {
3799 return new css_style_paddingtop('padding-top', $value);
3803 * Consolidates this style into a single padding style
3805 * @return string
3807 public function consolidate_to() {
3808 return 'padding';
3813 * A padding right style.
3815 * @package core_css
3816 * @category css
3817 * @copyright 2012 Sam Hemelryk
3818 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3820 class css_style_paddingright extends css_style_padding {
3823 * Initialises this style
3825 * @param string $value The value of the style
3826 * @return css_style_paddingright
3828 public static function init($value) {
3829 return new css_style_paddingright('padding-right', $value);
3833 * Consolidates this style into a single padding style
3835 * @return string
3837 public function consolidate_to() {
3838 return 'padding';
3843 * A padding bottom style.
3845 * @package core_css
3846 * @category css
3847 * @copyright 2012 Sam Hemelryk
3848 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3850 class css_style_paddingbottom extends css_style_padding {
3853 * Initialises this style
3855 * @param string $value The value of the style
3856 * @return css_style_paddingbottom
3858 public static function init($value) {
3859 return new css_style_paddingbottom('padding-bottom', $value);
3863 * Consolidates this style into a single padding style
3865 * @return string
3867 public function consolidate_to() {
3868 return 'padding';
3873 * A padding left style.
3875 * @package core_css
3876 * @category css
3877 * @copyright 2012 Sam Hemelryk
3878 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3880 class css_style_paddingleft extends css_style_padding {
3883 * Initialises this style
3885 * @param string $value The value of the style
3886 * @return css_style_paddingleft
3888 public static function init($value) {
3889 return new css_style_paddingleft('padding-left', $value);
3893 * Consolidates this style into a single padding style
3895 * @return string
3897 public function consolidate_to() {
3898 return 'padding';
3902 class css_style_cursor extends css_style_generic {
3903 public static function init($value) {
3904 return new css_style_cursor('cursor', $value);
3906 protected function clean_value($value) {
3907 $allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
3908 'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
3909 if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
3910 $this->set_error('Invalid or unexpected cursor value specified: '.$value);
3912 return trim($value);
3916 class css_style_verticalalign extends css_style_generic {
3917 public static function init($value) {
3918 return new css_style_verticalalign('vertical-align', $value);
3920 protected function clean_value($value) {
3921 $allowed = array('baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom', 'inherit');
3922 if (!css_is_width($value) && !in_array($value, $allowed)) {
3923 $this->set_error('Invalid vertical-align value specified: '.$value);
3925 return trim($value);
3928 class css_style_float extends css_style_generic {
3929 public static function init($value) {
3930 return new css_style_float('float', $value);
3932 protected function clean_value($value) {
3933 $allowed = array('left', 'right', 'none', 'inherit');
3934 if (!css_is_width($value) && !in_array($value, $allowed)) {
3935 $this->set_error('Invalid float value specified: '.$value);
3937 return trim($value);