Merge branch 'install_24_STABLE' of git://git.moodle.cz/moodle-install into MOODLE_24...
[moodle.git] / lib / csslib.php
blob95f99abd7fc4869941e3e88a2e02dc7bbccd8a65
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * This file contains CSS related class, and function for the CSS optimiser
20 * Please see the {@link css_optimiser} class for greater detail.
22 * @package core
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 // Check if both the CSS optimiser is enabled and the theme supports it.
43 if (!empty($CFG->enablecssoptimiser) && $theme->supportscssoptimisation) {
44 // This is an experimental feature introduced in Moodle 2.3
45 // The CSS optimiser organises the CSS in order to reduce the overall number
46 // of rules and styles being sent to the client. It does this by collating
47 // the CSS before it is cached removing excess styles and rules and stripping
48 // out any extraneous content such as comments and empty rules.
49 $optimiser = new css_optimiser;
50 $css = '';
51 foreach ($cssfiles as $file) {
52 $css .= file_get_contents($file)."\n";
54 $css = $theme->post_process($css);
55 $css = $optimiser->process($css);
57 // If cssoptimisestats is set then stats from the optimisation are collected
58 // and output at the beginning of the CSS
59 if (!empty($CFG->cssoptimiserstats)) {
60 $css = $optimiser->output_stats_css().$css;
62 } else {
63 // This is the default behaviour.
64 // The cssoptimise setting was introduced in Moodle 2.3 and will hopefully
65 // in the future be changed from an experimental setting to the default.
66 // The css_minify_css will method will use the Minify library remove
67 // comments, additional whitespace and other minor measures to reduce the
68 // the overall CSS being sent.
69 // However it has the distinct disadvantage of having to minify the CSS
70 // before running the post process functions. Potentially things may break
71 // here if theme designers try to push things with CSS post processing.
72 $css = $theme->post_process(css_minify_css($cssfiles));
75 clearstatcache();
76 if (!file_exists(dirname($csspath))) {
77 @mkdir(dirname($csspath), $CFG->directorypermissions, true);
80 // Prevent serving of incomplete file from concurrent request,
81 // the rename() should be more atomic than fwrite().
82 ignore_user_abort(true);
83 if ($fp = fopen($csspath.'.tmp', 'xb')) {
84 fwrite($fp, $css);
85 fclose($fp);
86 rename($csspath.'.tmp', $csspath);
87 @chmod($csspath, $CFG->filepermissions);
88 @unlink($csspath.'.tmp'); // just in case anything fails
90 ignore_user_abort(false);
91 if (connection_aborted()) {
92 die;
96 /**
97 * Sends IE specific CSS
99 * In writing the CSS parser I have a theory that we could optimise the CSS
100 * then split it based upon the number of selectors to ensure we dont' break IE
101 * and that we include only as many sub-stylesheets as we require.
102 * Of course just a theory but may be fun to code.
104 * @param string $themename The name of the theme we are sending CSS for.
105 * @param string $rev The revision to ensure we utilise the cache.
106 * @param string $etag The revision to ensure we utilise the cache.
107 * @param bool $slasharguments
109 function css_send_ie_css($themename, $rev, $etag, $slasharguments) {
110 global $CFG;
112 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
114 $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot);
116 $css = "/** Unfortunately IE6/7 does not support more than 4096 selectors in one CSS file, which means we have to use some ugly hacks :-( **/";
117 if ($slasharguments) {
118 $css .= "\n@import url($relroot/styles.php/$themename/$rev/plugins);";
119 $css .= "\n@import url($relroot/styles.php/$themename/$rev/parents);";
120 $css .= "\n@import url($relroot/styles.php/$themename/$rev/theme);";
121 } else {
122 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=plugins);";
123 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=parents);";
124 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=theme);";
127 header('Etag: '.$etag);
128 header('Content-Disposition: inline; filename="styles.php"');
129 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
130 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
131 header('Pragma: ');
132 header('Cache-Control: public, max-age='.$lifetime);
133 header('Accept-Ranges: none');
134 header('Content-Type: text/css; charset=utf-8');
135 header('Content-Length: '.strlen($css));
137 echo $css;
138 die;
142 * Sends a cached CSS file
144 * This function sends the cached CSS file. Remember it is generated on the first
145 * request, then optimised/minified, and finally cached for serving.
147 * @param string $csspath The path to the CSS file we want to serve.
148 * @param string $etag The revision to make sure we utilise any caches.
150 function css_send_cached_css($csspath, $etag) {
151 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
153 header('Etag: '.$etag);
154 header('Content-Disposition: inline; filename="styles.php"');
155 header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
156 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
157 header('Pragma: ');
158 header('Cache-Control: public, max-age='.$lifetime);
159 header('Accept-Ranges: none');
160 header('Content-Type: text/css; charset=utf-8');
161 if (!min_enable_zlib_compression()) {
162 header('Content-Length: '.filesize($csspath));
165 readfile($csspath);
166 die;
170 * Sends CSS directly without caching it.
172 * This function takes a raw CSS string, optimises it if required, and then
173 * serves it.
174 * Turning both themedesignermode and CSS optimiser on at the same time is awful
175 * for performance because of the optimiser running here. However it was done so
176 * that theme designers could utilise the optimised output during development to
177 * help them optimise their CSS... not that they should write lazy CSS.
179 * @param string $css
181 function css_send_uncached_css($css, $themesupportsoptimisation = true) {
182 global $CFG;
184 header('Content-Disposition: inline; filename="styles_debug.php"');
185 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
186 header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) .' GMT');
187 header('Pragma: ');
188 header('Accept-Ranges: none');
189 header('Content-Type: text/css; charset=utf-8');
191 if (is_array($css)) {
192 $css = implode("\n\n", $css);
195 echo $css;
197 die;
201 * Send file not modified headers
202 * @param int $lastmodified
203 * @param string $etag
205 function css_send_unmodified($lastmodified, $etag) {
206 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
207 header('HTTP/1.1 304 Not Modified');
208 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
209 header('Cache-Control: public, max-age='.$lifetime);
210 header('Content-Type: text/css; charset=utf-8');
211 header('Etag: '.$etag);
212 if ($lastmodified) {
213 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
215 die;
219 * Sends a 404 message about CSS not being found.
221 function css_send_css_not_found() {
222 header('HTTP/1.0 404 not found');
223 die('CSS was not found, sorry.');
227 * Uses the minify library to compress CSS.
229 * This is used if $CFG->enablecssoptimiser has been turned off. This was
230 * the original CSS optimisation library.
231 * It removes whitespace and shrinks things but does no apparent optimisation.
232 * Note the minify library is still being used for JavaScript.
234 * @param array $files An array of files to minify
235 * @return string The minified CSS
237 function css_minify_css($files) {
238 global $CFG;
240 if (empty($files)) {
241 return '';
244 set_include_path($CFG->libdir . '/minify/lib' . PATH_SEPARATOR . get_include_path());
245 require_once('Minify.php');
247 if (0 === stripos(PHP_OS, 'win')) {
248 Minify::setDocRoot(); // IIS may need help
250 // disable all caching, we do it in moodle
251 Minify::setCache(null, false);
253 $options = array(
254 // JSMin is not GNU GPL compatible, use the plus version instead.
255 'minifiers' => array(Minify::TYPE_JS => array('JSMinPlus', 'minify')),
256 'bubbleCssImports' => false,
257 // Don't gzip content we just want text for storage
258 'encodeOutput' => false,
259 // Maximum age to cache, not used but required
260 'maxAge' => (60*60*24*20),
261 // The files to minify
262 'files' => $files,
263 // Turn orr URI rewriting
264 'rewriteCssUris' => false,
265 // This returns the CSS rather than echoing it for display
266 'quiet' => true
269 $error = 'unknown';
270 try {
271 $result = Minify::serve('Files', $options);
272 if ($result['success']) {
273 return $result['content'];
275 } catch (Exception $e) {
276 $error = $e->getMessage();
277 $error = str_replace("\r", ' ', $error);
278 $error = str_replace("\n", ' ', $error);
281 // minification failed - try to inform the theme developer and include the non-minified version
282 $css = <<<EOD
283 /* Error: $error */
284 /* Problem detected during theme CSS minimisation, please review the following code */
285 /* ================================================================================ */
288 EOD;
289 foreach ($files as $cssfile) {
290 $css .= file_get_contents($cssfile)."\n";
292 return $css;
296 * Determines if the given value is a valid CSS colour.
298 * A CSS colour can be one of the following:
299 * - Hex colour: #AA66BB
300 * - RGB colour: rgb(0-255, 0-255, 0-255)
301 * - RGBA colour: rgba(0-255, 0-255, 0-255, 0-1)
302 * - HSL colour: hsl(0-360, 0-100%, 0-100%)
303 * - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1)
305 * Or a recognised browser colour mapping {@link css_optimiser::$htmlcolours}
307 * @param string $value The colour value to check
308 * @return bool
310 function css_is_colour($value) {
311 $value = trim($value);
313 $hex = '/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/';
314 $rgb = '#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i';
315 $rgba = '#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
316 $hsl = '#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i';
317 $hsla = '#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
319 if (in_array(strtolower($value), array('inherit'))) {
320 return true;
321 } else if (preg_match($hex, $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, $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, $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, $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, $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
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 // Easiest win there is
505 $css = trim($css);
507 $this->reset_stats();
508 $this->timestart = microtime(true);
509 $this->rawstrlen = strlen($css);
511 // Don't try to process files with no content... it just doesn't make sense.
512 // But we should produce an error for them, an empty CSS file will lead to a
513 // useless request for those running theme designer mode.
514 if ($this->rawstrlen === 0) {
515 $this->errors[] = 'Skipping file as it has no content.';
516 return '';
519 // First up we need to remove all line breaks - this allows us to instantly
520 // reduce our processing requirements and as we will process everything
521 // into a new structure there's really nothing lost.
522 $css = preg_replace('#\r?\n#', ' ', $css);
524 // Next remove the comments... no need to them in an optimised world and
525 // knowing they're all gone allows us to REALLY make our processing simpler
526 $css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss);
528 $medias = array(
529 'all' => new css_media()
531 $imports = array();
532 $charset = false;
533 // Keyframes are used for CSS animation they will be processed right at the very end.
534 $keyframes = array();
536 $currentprocess = self::PROCESSING_START;
537 $currentrule = css_rule::init();
538 $currentselector = css_selector::init();
539 $inquotes = false; // ' or "
540 $inbraces = false; // {
541 $inbrackets = false; // [
542 $inparenthesis = false; // (
543 $currentmedia = $medias['all'];
544 $currentatrule = null;
545 $suspectatrule = false;
547 $buffer = '';
548 $char = null;
550 // Next we are going to iterate over every single character in $css.
551 // This is why we removed line breaks and comments!
552 for ($i = 0; $i < $this->rawstrlen; $i++) {
553 $lastchar = $char;
554 $char = substr($css, $i, 1);
555 if ($char == '@' && $buffer == '') {
556 $suspectatrule = true;
558 switch ($currentprocess) {
559 // Start processing an @ rule e.g. @media, @page, @keyframes
560 case self::PROCESSING_ATRULE:
561 switch ($char) {
562 case ';':
563 if (!$inbraces) {
564 $buffer .= $char;
565 if ($currentatrule == 'import') {
566 $imports[] = $buffer;
567 $currentprocess = self::PROCESSING_SELECTORS;
568 } else if ($currentatrule == 'charset') {
569 $charset = $buffer;
570 $currentprocess = self::PROCESSING_SELECTORS;
573 if ($currentatrule !== 'media') {
574 $buffer = '';
575 $currentatrule = false;
577 // continue 1: The switch processing chars
578 // continue 2: The switch processing the state
579 // continue 3: The for loop
580 continue 3;
581 case '{':
582 if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#', $buffer, $matches)) {
583 // Basic media declaration
584 $mediatypes = str_replace(' ', '', $matches[1]);
585 if (!array_key_exists($mediatypes, $medias)) {
586 $medias[$mediatypes] = new css_media($mediatypes);
588 $currentmedia = $medias[$mediatypes];
589 $currentprocess = self::PROCESSING_SELECTORS;
590 $buffer = '';
591 } else if ($currentatrule == 'media' && preg_match('#\s*@media\s*([^{]+)#', $buffer, $matches)) {
592 // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/
593 $mediatypes = $matches[1];
594 $hash = md5($mediatypes);
595 $medias[$hash] = new css_media($mediatypes);
596 $currentmedia = $medias[$hash];
597 $currentprocess = self::PROCESSING_SELECTORS;
598 $buffer = '';
599 } else if ($currentatrule == 'keyframes' && preg_match('#@((\-moz\-|\-webkit\-)?keyframes)\s*([^\s]+)#', $buffer, $matches)) {
600 // Keyframes declaration, we treat it exactly like a @media declaration except we don't allow
601 // them to be overridden to ensure we don't mess anything up. (means we keep everything in order)
602 $keyframefor = $matches[1];
603 $keyframename = $matches[3];
604 $keyframe = new css_keyframe($keyframefor, $keyframename);
605 $keyframes[] = $keyframe;
606 $currentmedia = $keyframe;
607 $currentprocess = self::PROCESSING_SELECTORS;
608 $buffer = '';
610 // continue 1: The switch processing chars
611 // continue 2: The switch processing the state
612 // continue 3: The for loop
613 continue 3;
615 break;
616 // Start processing selectors
617 case self::PROCESSING_START:
618 case self::PROCESSING_SELECTORS:
619 switch ($char) {
620 case '[':
621 $inbrackets ++;
622 $buffer .= $char;
623 // continue 1: The switch processing chars
624 // continue 2: The switch processing the state
625 // continue 3: The for loop
626 continue 3;
627 case ']':
628 $inbrackets --;
629 $buffer .= $char;
630 // continue 1: The switch processing chars
631 // continue 2: The switch processing the state
632 // continue 3: The for loop
633 continue 3;
634 case ' ':
635 if ($inbrackets) {
636 // continue 1: The switch processing chars
637 // continue 2: The switch processing the state
638 // continue 3: The for loop
639 continue 3;
641 if (!empty($buffer)) {
642 // Check for known @ rules
643 if ($suspectatrule && preg_match('#@(media|import|charset|(\-moz\-|\-webkit\-)?(keyframes))\s*#', $buffer, $matches)) {
644 $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
645 $currentprocess = self::PROCESSING_ATRULE;
646 $buffer .= $char;
647 } else {
648 $currentselector->add($buffer);
649 $buffer = '';
652 $suspectatrule = false;
653 // continue 1: The switch processing chars
654 // continue 2: The switch processing the state
655 // continue 3: The for loop
656 continue 3;
657 case '{':
658 if ($inbrackets) {
659 // continue 1: The switch processing chars
660 // continue 2: The switch processing the state
661 // continue 3: The for loop
662 continue 3;
664 if ($buffer !== '') {
665 $currentselector->add($buffer);
667 $currentrule->add_selector($currentselector);
668 $currentselector = css_selector::init();
669 $currentprocess = self::PROCESSING_STYLES;
671 $buffer = '';
672 // continue 1: The switch processing chars
673 // continue 2: The switch processing the state
674 // continue 3: The for loop
675 continue 3;
676 case '}':
677 if ($inbrackets) {
678 // continue 1: The switch processing chars
679 // continue 2: The switch processing the state
680 // continue 3: The for loop
681 continue 3;
683 if ($currentatrule == 'media') {
684 $currentmedia = $medias['all'];
685 $currentatrule = false;
686 $buffer = '';
687 } else if (strpos($currentatrule, 'keyframes') !== false) {
688 $currentmedia = $medias['all'];
689 $currentatrule = false;
690 $buffer = '';
692 // continue 1: The switch processing chars
693 // continue 2: The switch processing the state
694 // continue 3: The for loop
695 continue 3;
696 case ',':
697 if ($inbrackets) {
698 // continue 1: The switch processing chars
699 // continue 2: The switch processing the state
700 // continue 3: The for loop
701 continue 3;
703 $currentselector->add($buffer);
704 $currentrule->add_selector($currentselector);
705 $currentselector = css_selector::init();
706 $buffer = '';
707 // continue 1: The switch processing chars
708 // continue 2: The switch processing the state
709 // continue 3: The for loop
710 continue 3;
712 break;
713 // Start processing styles
714 case self::PROCESSING_STYLES:
715 if ($char == '"' || $char == "'") {
716 if ($inquotes === false) {
717 $inquotes = $char;
719 if ($inquotes === $char && $lastchar !== '\\') {
720 $inquotes = false;
723 if ($inquotes) {
724 $buffer .= $char;
725 continue 2;
727 switch ($char) {
728 case ';':
729 if ($inparenthesis) {
730 $buffer .= $char;
731 // continue 1: The switch processing chars
732 // continue 2: The switch processing the state
733 // continue 3: The for loop
734 continue 3;
736 $currentrule->add_style($buffer);
737 $buffer = '';
738 $inquotes = false;
739 // continue 1: The switch processing chars
740 // continue 2: The switch processing the state
741 // continue 3: The for loop
742 continue 3;
743 case '}':
744 $currentrule->add_style($buffer);
745 $this->rawselectors += $currentrule->get_selector_count();
747 $currentmedia->add_rule($currentrule);
749 $currentrule = css_rule::init();
750 $currentprocess = self::PROCESSING_SELECTORS;
751 $this->rawrules++;
752 $buffer = '';
753 $inquotes = false;
754 $inparenthesis = false;
755 // continue 1: The switch processing chars
756 // continue 2: The switch processing the state
757 // continue 3: The for loop
758 continue 3;
759 case '(':
760 $inparenthesis = true;
761 $buffer .= $char;
762 // continue 1: The switch processing chars
763 // continue 2: The switch processing the state
764 // continue 3: The for loop
765 continue 3;
766 case ')':
767 $inparenthesis = false;
768 $buffer .= $char;
769 // continue 1: The switch processing chars
770 // continue 2: The switch processing the state
771 // continue 3: The for loop
772 continue 3;
774 break;
776 $buffer .= $char;
779 foreach ($medias as $media) {
780 $this->optimise($media);
782 $css = $this->produce_css($charset, $imports, $medias, $keyframes);
784 $this->timecomplete = microtime(true);
785 return trim($css);
789 * Produces CSS for the given charset, imports, media, and keyframes
790 * @param string $charset
791 * @param array $imports
792 * @param array $medias
793 * @param array $keyframes
794 * @return string
796 protected function produce_css($charset, array $imports, array $medias, array $keyframes) {
797 $css = '';
798 if (!empty($charset)) {
799 $imports[] = $charset;
801 if (!empty($imports)) {
802 $css .= implode("\n", $imports);
803 $css .= "\n\n";
806 $cssreset = array();
807 $cssstandard = array();
808 $csskeyframes = array();
810 // Process each media declaration individually
811 foreach ($medias as $media) {
812 // If this declaration applies to all media types
813 if (in_array('all', $media->get_types())) {
814 // Collect all rules that represet reset rules and remove them from the media object at the same time.
815 // We do this because we prioritise reset rules to the top of a CSS output. This ensures that they
816 // can't end up out of order because of optimisation.
817 $resetrules = $media->get_reset_rules(true);
818 if (!empty($resetrules)) {
819 $cssreset[] = css_writer::media('all', $resetrules);
822 // Get the standard cSS
823 $cssstandard[] = $media->out();
826 // Finally if there are any keyframe declarations process them now.
827 if (count($keyframes) > 0) {
828 foreach ($keyframes as $keyframe) {
829 $this->optimisedrules += $keyframe->count_rules();
830 $this->optimisedselectors += $keyframe->count_selectors();
831 if ($keyframe->has_errors()) {
832 $this->errors += $keyframe->get_errors();
834 $csskeyframes[] = $keyframe->out();
838 // Join it all together
839 $css .= join('', $cssreset);
840 $css .= join('', $cssstandard);
841 $css .= join('', $csskeyframes);
843 // Record the strlenght of the now optimised CSS.
844 $this->optimisedstrlen = strlen($css);
846 // Return the now produced CSS
847 return $css;
851 * Optimises the CSS rules within a rule collection of one form or another
853 * @param css_rule_collection $media
854 * @return void This function acts in reference
856 protected function optimise(css_rule_collection $media) {
857 $media->organise_rules_by_selectors();
858 $this->optimisedrules += $media->count_rules();
859 $this->optimisedselectors += $media->count_selectors();
860 if ($media->has_errors()) {
861 $this->errors += $media->get_errors();
866 * Returns an array of stats from the last processing run
867 * @return string
869 public function get_stats() {
870 $stats = array(
871 'timestart' => $this->timestart,
872 'timecomplete' => $this->timecomplete,
873 'timetaken' => round($this->timecomplete - $this->timestart, 4),
874 'commentsincss' => $this->commentsincss,
875 'rawstrlen' => $this->rawstrlen,
876 'rawselectors' => $this->rawselectors,
877 'rawrules' => $this->rawrules,
878 'optimisedstrlen' => $this->optimisedstrlen,
879 'optimisedrules' => $this->optimisedrules,
880 'optimisedselectors' => $this->optimisedselectors,
881 'improvementstrlen' => '-',
882 'improvementrules' => '-',
883 'improvementselectors' => '-',
885 // Avoid division by 0 errors by checking we have valid raw values
886 if ($this->rawstrlen > 0) {
887 $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
889 if ($this->rawrules > 0) {
890 $stats['improvementrules'] = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%';
892 if ($this->rawselectors > 0) {
893 $stats['improvementselectors'] = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%';
895 return $stats;
899 * Returns true if any errors have occured during processing
901 * @return bool
903 public function has_errors() {
904 return !empty($this->errors);
908 * Returns an array of errors that have occured
910 * @param bool $clear If set to true the errors will be cleared after being returned.
911 * @return array
913 public function get_errors($clear = false) {
914 $errors = $this->errors;
915 if ($clear) {
916 // Reset the error array
917 $this->errors = array();
919 return $errors;
923 * Returns any errors as a string that can be included in CSS.
925 * @return string
927 public function output_errors_css() {
928 $computedcss = "/****************************************\n";
929 $computedcss .= " *--- Errors found during processing ----\n";
930 foreach ($this->errors as $error) {
931 $computedcss .= preg_replace('#^#m', '* ', $error);
933 $computedcss .= " ****************************************/\n\n";
934 return $computedcss;
938 * Returns a string to display stats about the last generation within CSS output
940 * @return string
942 public function output_stats_css() {
944 $computedcss = "/****************************************\n";
945 $computedcss .= " *------- CSS Optimisation stats --------\n";
947 if ($this->rawstrlen === 0) {
948 $computedcss .= " File not processed as it has no content /\n\n";
949 $computedcss .= " ****************************************/\n\n";
950 return $computedcss;
951 } else if ($this->rawrules === 0) {
952 $computedcss .= " File contained no rules to be processed /\n\n";
953 $computedcss .= " ****************************************/\n\n";
954 return $computedcss;
957 $stats = $this->get_stats();
959 $computedcss .= " * ".date('r')."\n";
960 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
961 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
962 $computedcss .= " *--------------- before ----------------\n";
963 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n";
964 $computedcss .= " * {$stats['rawrules']} \t rules read in\n";
965 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n";
966 $computedcss .= " *---------------- after ----------------\n";
967 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n";
968 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n";
969 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n";
970 $computedcss .= " *---------------- stats ----------------\n";
971 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n";
972 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n";
973 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n";
974 $computedcss .= " ****************************************/\n\n";
976 return $computedcss;
980 * Resets the stats ready for another fresh processing
982 public function reset_stats() {
983 $this->commentsincss = 0;
984 $this->optimisedrules = 0;
985 $this->optimisedselectors = 0;
986 $this->optimisedstrlen = 0;
987 $this->rawrules = 0;
988 $this->rawselectors = 0;
989 $this->rawstrlen = 0;
990 $this->timecomplete = 0;
991 $this->timestart = 0;
995 * An array of the common HTML colours that are supported by most browsers.
997 * This reference table is used to allow us to unify colours, and will aid
998 * us in identifying buggy CSS using unsupported colours.
1000 * @staticvar array
1001 * @var array
1003 public static $htmlcolours = array(
1004 'aliceblue' => '#F0F8FF',
1005 'antiquewhite' => '#FAEBD7',
1006 'aqua' => '#00FFFF',
1007 'aquamarine' => '#7FFFD4',
1008 'azure' => '#F0FFFF',
1009 'beige' => '#F5F5DC',
1010 'bisque' => '#FFE4C4',
1011 'black' => '#000000',
1012 'blanchedalmond' => '#FFEBCD',
1013 'blue' => '#0000FF',
1014 'blueviolet' => '#8A2BE2',
1015 'brown' => '#A52A2A',
1016 'burlywood' => '#DEB887',
1017 'cadetblue' => '#5F9EA0',
1018 'chartreuse' => '#7FFF00',
1019 'chocolate' => '#D2691E',
1020 'coral' => '#FF7F50',
1021 'cornflowerblue' => '#6495ED',
1022 'cornsilk' => '#FFF8DC',
1023 'crimson' => '#DC143C',
1024 'cyan' => '#00FFFF',
1025 'darkblue' => '#00008B',
1026 'darkcyan' => '#008B8B',
1027 'darkgoldenrod' => '#B8860B',
1028 'darkgray' => '#A9A9A9',
1029 'darkgrey' => '#A9A9A9',
1030 'darkgreen' => '#006400',
1031 'darkKhaki' => '#BDB76B',
1032 'darkmagenta' => '#8B008B',
1033 'darkolivegreen' => '#556B2F',
1034 'arkorange' => '#FF8C00',
1035 'darkorchid' => '#9932CC',
1036 'darkred' => '#8B0000',
1037 'darksalmon' => '#E9967A',
1038 'darkseagreen' => '#8FBC8F',
1039 'darkslateblue' => '#483D8B',
1040 'darkslategray' => '#2F4F4F',
1041 'darkslategrey' => '#2F4F4F',
1042 'darkturquoise' => '#00CED1',
1043 'darkviolet' => '#9400D3',
1044 'deeppink' => '#FF1493',
1045 'deepskyblue' => '#00BFFF',
1046 'dimgray' => '#696969',
1047 'dimgrey' => '#696969',
1048 'dodgerblue' => '#1E90FF',
1049 'firebrick' => '#B22222',
1050 'floralwhite' => '#FFFAF0',
1051 'forestgreen' => '#228B22',
1052 'fuchsia' => '#FF00FF',
1053 'gainsboro' => '#DCDCDC',
1054 'ghostwhite' => '#F8F8FF',
1055 'gold' => '#FFD700',
1056 'goldenrod' => '#DAA520',
1057 'gray' => '#808080',
1058 'grey' => '#808080',
1059 'green' => '#008000',
1060 'greenyellow' => '#ADFF2F',
1061 'honeydew' => '#F0FFF0',
1062 'hotpink' => '#FF69B4',
1063 'indianred ' => '#CD5C5C',
1064 'indigo ' => '#4B0082',
1065 'ivory' => '#FFFFF0',
1066 'khaki' => '#F0E68C',
1067 'lavender' => '#E6E6FA',
1068 'lavenderblush' => '#FFF0F5',
1069 'lawngreen' => '#7CFC00',
1070 'lemonchiffon' => '#FFFACD',
1071 'lightblue' => '#ADD8E6',
1072 'lightcoral' => '#F08080',
1073 'lightcyan' => '#E0FFFF',
1074 'lightgoldenrodyellow' => '#FAFAD2',
1075 'lightgray' => '#D3D3D3',
1076 'lightgrey' => '#D3D3D3',
1077 'lightgreen' => '#90EE90',
1078 'lightpink' => '#FFB6C1',
1079 'lightsalmon' => '#FFA07A',
1080 'lightseagreen' => '#20B2AA',
1081 'lightskyblue' => '#87CEFA',
1082 'lightslategray' => '#778899',
1083 'lightslategrey' => '#778899',
1084 'lightsteelblue' => '#B0C4DE',
1085 'lightyellow' => '#FFFFE0',
1086 'lime' => '#00FF00',
1087 'limegreen' => '#32CD32',
1088 'linen' => '#FAF0E6',
1089 'magenta' => '#FF00FF',
1090 'maroon' => '#800000',
1091 'mediumaquamarine' => '#66CDAA',
1092 'mediumblue' => '#0000CD',
1093 'mediumorchid' => '#BA55D3',
1094 'mediumpurple' => '#9370D8',
1095 'mediumseagreen' => '#3CB371',
1096 'mediumslateblue' => '#7B68EE',
1097 'mediumspringgreen' => '#00FA9A',
1098 'mediumturquoise' => '#48D1CC',
1099 'mediumvioletred' => '#C71585',
1100 'midnightblue' => '#191970',
1101 'mintcream' => '#F5FFFA',
1102 'mistyrose' => '#FFE4E1',
1103 'moccasin' => '#FFE4B5',
1104 'navajowhite' => '#FFDEAD',
1105 'navy' => '#000080',
1106 'oldlace' => '#FDF5E6',
1107 'olive' => '#808000',
1108 'olivedrab' => '#6B8E23',
1109 'orange' => '#FFA500',
1110 'orangered' => '#FF4500',
1111 'orchid' => '#DA70D6',
1112 'palegoldenrod' => '#EEE8AA',
1113 'palegreen' => '#98FB98',
1114 'paleturquoise' => '#AFEEEE',
1115 'palevioletred' => '#D87093',
1116 'papayawhip' => '#FFEFD5',
1117 'peachpuff' => '#FFDAB9',
1118 'peru' => '#CD853F',
1119 'pink' => '#FFC0CB',
1120 'plum' => '#DDA0DD',
1121 'powderblue' => '#B0E0E6',
1122 'purple' => '#800080',
1123 'red' => '#FF0000',
1124 'rosybrown' => '#BC8F8F',
1125 'royalblue' => '#4169E1',
1126 'saddlebrown' => '#8B4513',
1127 'salmon' => '#FA8072',
1128 'sandybrown' => '#F4A460',
1129 'seagreen' => '#2E8B57',
1130 'seashell' => '#FFF5EE',
1131 'sienna' => '#A0522D',
1132 'silver' => '#C0C0C0',
1133 'skyblue' => '#87CEEB',
1134 'slateblue' => '#6A5ACD',
1135 'slategray' => '#708090',
1136 'slategrey' => '#708090',
1137 'snow' => '#FFFAFA',
1138 'springgreen' => '#00FF7F',
1139 'steelblue' => '#4682B4',
1140 'tan' => '#D2B48C',
1141 'teal' => '#008080',
1142 'thistle' => '#D8BFD8',
1143 'tomato' => '#FF6347',
1144 'transparent' => 'transparent',
1145 'turquoise' => '#40E0D0',
1146 'violet' => '#EE82EE',
1147 'wheat' => '#F5DEB3',
1148 'white' => '#FFFFFF',
1149 'whitesmoke' => '#F5F5F5',
1150 'yellow' => '#FFFF00',
1151 'yellowgreen' => '#9ACD32'
1156 * Used to prepare CSS strings
1158 * @package core
1159 * @category css
1160 * @copyright 2012 Sam Hemelryk
1161 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1163 abstract class css_writer {
1166 * The current indent level
1167 * @var int
1169 protected static $indent = 0;
1172 * Returns true if the output should still maintain minimum formatting.
1173 * @return bool
1175 protected static function is_pretty() {
1176 global $CFG;
1177 return (!empty($CFG->cssoptimiserpretty));
1181 * Returns the indenting char to use for indenting things nicely.
1182 * @return string
1184 protected static function get_indent() {
1185 if (self::is_pretty()) {
1186 return str_repeat(" ", self::$indent);
1188 return '';
1192 * Increases the current indent
1194 protected static function increase_indent() {
1195 self::$indent++;
1199 * Decreases the current indent
1201 protected static function decrease_indent() {
1202 self::$indent--;
1206 * Returns the string to use as a separator
1207 * @return string
1209 protected static function get_separator() {
1210 return (self::is_pretty())?"\n":' ';
1214 * Returns CSS for media
1216 * @param string $typestring
1217 * @param array $rules An array of css_rule objects
1218 * @return string
1220 public static function media($typestring, array &$rules) {
1221 $nl = self::get_separator();
1223 $output = '';
1224 if ($typestring !== 'all') {
1225 $output .= "\n@media {$typestring} {".$nl;
1226 self::increase_indent();
1228 foreach ($rules as $rule) {
1229 $output .= $rule->out().$nl;
1231 if ($typestring !== 'all') {
1232 self::decrease_indent();
1233 $output .= '}';
1235 return $output;
1239 * Returns CSS for a keyframe
1241 * @param string $for The desired declaration. e.g. keyframes, -moz-keyframes, -webkit-keyframes
1242 * @param string $name The name for the keyframe
1243 * @param array $rules An array of rules belonging to the keyframe
1244 * @return string
1246 public static function keyframe($for, $name, array &$rules) {
1247 $nl = self::get_separator();
1249 $output = "\n@{$for} {$name} {";
1250 foreach ($rules as $rule) {
1251 $output .= $rule->out();
1253 $output .= '}';
1254 return $output;
1258 * Returns CSS for a rule
1260 * @param string $selector
1261 * @param string $styles
1262 * @return string
1264 public static function rule($selector, $styles) {
1265 $css = self::get_indent()."{$selector}{{$styles}}";
1266 return $css;
1270 * Returns CSS for the selectors of a rule
1272 * @param array $selectors Array of css_selector objects
1273 * @return string
1275 public static function selectors(array $selectors) {
1276 $nl = self::get_separator();
1277 $selectorstrings = array();
1278 foreach ($selectors as $selector) {
1279 $selectorstrings[] = $selector->out();
1281 return join(','.$nl, $selectorstrings);
1285 * Returns a selector given the components that make it up.
1287 * @param array $components
1288 * @return string
1290 public static function selector(array $components) {
1291 return trim(join(' ', $components));
1295 * Returns a CSS string for the provided styles
1297 * @param array $styles Array of css_style objects
1298 * @return string
1300 public static function styles(array $styles) {
1301 $bits = array();
1302 foreach ($styles as $style) {
1303 // Check if the style is an array. If it is then we are outputing an advanced style.
1304 // An advanced style is a style with one or more values, and can occur in situations like background-image
1305 // where browse specific values are being used.
1306 if (is_array($style)) {
1307 foreach ($style as $advstyle) {
1308 $bits[] = $advstyle->out();
1310 continue;
1312 $bits[] = $style->out();
1314 return join('', $bits);
1318 * Returns a style CSS
1320 * @param string $name
1321 * @param string $value
1322 * @param bool $important
1323 * @return string
1325 public static function style($name, $value, $important = false) {
1326 $value = trim($value);
1327 if ($important && strpos($value, '!important') === false) {
1328 $value .= ' !important';
1330 return "{$name}:{$value};";
1335 * A structure to represent a CSS selector.
1337 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
1338 * rule.
1340 * @package core
1341 * @category css
1342 * @copyright 2012 Sam Hemelryk
1343 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1345 class css_selector {
1348 * An array of selector bits
1349 * @var array
1351 protected $selectors = array();
1354 * The number of selectors.
1355 * @var int
1357 protected $count = 0;
1360 * Is null if there are no selectors, true if all selectors are basic and false otherwise.
1361 * A basic selector is one that consists of just the element type. e.g. div, span, td, a
1362 * @var bool|null
1364 protected $isbasic = null;
1367 * Initialises a new CSS selector
1368 * @return css_selector
1370 public static function init() {
1371 return new css_selector();
1375 * CSS selectors can only be created through the init method above.
1377 protected function __construct() {}
1380 * Adds a selector to the end of the current selector
1381 * @param string $selector
1383 public function add($selector) {
1384 $selector = trim($selector);
1385 $count = 0;
1386 $count += preg_match_all('/(\.|#)/', $selector, $matchesarray);
1387 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
1388 $count ++;
1390 // If its already false then no need to continue, its not basic
1391 if ($this->isbasic !== false) {
1392 // If theres more than one part making up this selector its not basic
1393 if ($count > 1) {
1394 $this->isbasic = false;
1395 } else {
1396 // Check whether it is a basic element (a-z+) with possible psuedo selector
1397 $this->isbasic = (bool)preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector);
1400 $this->count = $count;
1401 $this->selectors[] = $selector;
1404 * Returns the number of individual components that make up this selector
1405 * @return int
1407 public function get_selector_count() {
1408 return $this->count;
1412 * Returns the selector for use in a CSS rule
1413 * @return string
1415 public function out() {
1416 return css_writer::selector($this->selectors);
1420 * Returns true is all of the selectors act only upon basic elements (no classes/ids)
1421 * @return bool
1423 public function is_basic() {
1424 return ($this->isbasic === true);
1429 * A structure to represent a CSS rule.
1431 * @package core
1432 * @category css
1433 * @copyright 2012 Sam Hemelryk
1434 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1436 class css_rule {
1439 * An array of CSS selectors {@link css_selector}
1440 * @var array
1442 protected $selectors = array();
1445 * An array of CSS styles {@link css_style}
1446 * @var array
1448 protected $styles = array();
1451 * Created a new CSS rule. This is the only way to create a new CSS rule externally.
1452 * @return css_rule
1454 public static function init() {
1455 return new css_rule();
1459 * Constructs a new css rule.
1461 * @param string $selector The selector or array of selectors that make up this rule.
1462 * @param array $styles An array of styles that belong to this rule.
1464 protected function __construct($selector = null, array $styles = array()) {
1465 if ($selector != null) {
1466 if (is_array($selector)) {
1467 $this->selectors = $selector;
1468 } else {
1469 $this->selectors = array($selector);
1471 $this->add_styles($styles);
1476 * Adds a new CSS selector to this rule
1478 * e.g. $rule->add_selector('.one #two.two');
1480 * @param css_selector $selector Adds a CSS selector to this rule.
1482 public function add_selector(css_selector $selector) {
1483 $this->selectors[] = $selector;
1487 * Adds a new CSS style to this rule.
1489 * @param css_style|string $style Adds a new style to this rule
1491 public function add_style($style) {
1492 if (is_string($style)) {
1493 $style = trim($style);
1494 if (empty($style)) {
1495 return;
1497 $bits = explode(':', $style, 2);
1498 if (count($bits) == 2) {
1499 list($name, $value) = array_map('trim', $bits);
1501 if (isset($name) && isset($value) && $name !== '' && $value !== '') {
1502 $style = css_style::init_automatic($name, $value);
1504 } else if ($style instanceof css_style) {
1505 // Clone the style as it may be coming from another rule and we don't
1506 // want references as it will likely be overwritten by proceeding
1507 // rules
1508 $style = clone($style);
1510 if ($style instanceof css_style) {
1511 $name = $style->get_name();
1512 $exists = array_key_exists($name, $this->styles);
1513 // We need to find out if the current style support multiple values, or whether the style
1514 // is already set up to record multiple values. This can happen with background images which can have single
1515 // and multiple values.
1516 if ($style->allows_multiple_values() || ($exists && is_array($this->styles[$name]))) {
1517 if (!$exists) {
1518 $this->styles[$name] = array();
1519 } else if ($this->styles[$name] instanceof css_style) {
1520 $this->styles[$name] = array($this->styles[$name]);
1522 $this->styles[$name][] = $style;
1523 } else if ($exists) {
1524 $this->styles[$name]->set_value($style->get_value());
1525 } else {
1526 $this->styles[$name] = $style;
1528 } else if (is_array($style)) {
1529 // We probably shouldn't worry about processing styles here but to
1530 // be truthful it doesn't hurt.
1531 foreach ($style as $astyle) {
1532 $this->add_style($astyle);
1538 * An easy method of adding several styles at once. Just calls add_style.
1540 * This method simply iterates over the array and calls {@link css_rule::add_style()}
1541 * with each.
1543 * @param array $styles Adds an array of styles
1545 public function add_styles(array $styles) {
1546 foreach ($styles as $style) {
1547 $this->add_style($style);
1552 * Returns the array of selectors
1554 * @return array
1556 public function get_selectors() {
1557 return $this->selectors;
1561 * Returns the array of styles
1563 * @return array
1565 public function get_styles() {
1566 return $this->styles;
1570 * Outputs this rule as a fragment of CSS
1572 * @return string
1574 public function out() {
1575 $selectors = css_writer::selectors($this->selectors);
1576 $styles = css_writer::styles($this->get_consolidated_styles());
1577 return css_writer::rule($selectors, $styles);
1581 * Consolidates all styles associated with this rule
1583 * @return array An array of consolidated styles
1585 public function get_consolidated_styles() {
1586 $organisedstyles = array();
1587 $finalstyles = array();
1588 $consolidate = array();
1589 $advancedstyles = array();
1590 foreach ($this->styles as $style) {
1591 // If the style is an array then we are processing an advanced style. An advanced style is a style that can have
1592 // one or more values. Background-image is one such example as it can have browser specific styles.
1593 if (is_array($style)) {
1594 $single = null;
1595 $count = 0;
1596 foreach ($style as $advstyle) {
1597 $key = $count++;
1598 $advancedstyles[$key] = $advstyle;
1599 if (!$advstyle->allows_multiple_values()) {
1600 if (!is_null($single)) {
1601 unset($advancedstyles[$single]);
1603 $single = $key;
1606 if (!is_null($single)) {
1607 $style = $advancedstyles[$single];
1609 $consolidatetoclass = $style->consolidate_to();
1610 if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
1611 $class = 'css_style_'.$consolidatetoclass;
1612 if (!array_key_exists($class, $consolidate)) {
1613 $consolidate[$class] = array();
1614 $organisedstyles[$class] = true;
1616 $consolidate[$class][] = $style;
1617 unset($advancedstyles[$single]);
1621 continue;
1623 $consolidatetoclass = $style->consolidate_to();
1624 if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
1625 $class = 'css_style_'.$consolidatetoclass;
1626 if (!array_key_exists($class, $consolidate)) {
1627 $consolidate[$class] = array();
1628 $organisedstyles[$class] = true;
1630 $consolidate[$class][] = $style;
1631 } else {
1632 $organisedstyles[$style->get_name()] = $style;
1636 foreach ($consolidate as $class => $styles) {
1637 $organisedstyles[$class] = $class::consolidate($styles);
1640 foreach ($organisedstyles as $style) {
1641 if (is_array($style)) {
1642 foreach ($style as $s) {
1643 $finalstyles[] = $s;
1645 } else {
1646 $finalstyles[] = $style;
1649 $finalstyles = array_merge($finalstyles, $advancedstyles);
1650 return $finalstyles;
1654 * Splits this rules into an array of CSS rules. One for each of the selectors
1655 * that make up this rule.
1657 * @return array(css_rule)
1659 public function split_by_selector() {
1660 $return = array();
1661 foreach ($this->selectors as $selector) {
1662 $return[] = new css_rule($selector, $this->styles);
1664 return $return;
1668 * Splits this rule into an array of rules. One for each of the styles that
1669 * make up this rule
1671 * @return array Array of css_rule objects
1673 public function split_by_style() {
1674 $return = array();
1675 foreach ($this->styles as $style) {
1676 if (is_array($style)) {
1677 $return[] = new css_rule($this->selectors, $style);
1678 continue;
1680 $return[] = new css_rule($this->selectors, array($style));
1682 return $return;
1686 * Gets a hash for the styles of this rule
1688 * @return string
1690 public function get_style_hash() {
1691 return md5(css_writer::styles($this->styles));
1695 * Gets a hash for the selectors of this rule
1697 * @return string
1699 public function get_selector_hash() {
1700 return md5(css_writer::selectors($this->selectors));
1704 * Gets the number of selectors that make up this rule.
1706 * @return int
1708 public function get_selector_count() {
1709 $count = 0;
1710 foreach ($this->selectors as $selector) {
1711 $count += $selector->get_selector_count();
1713 return $count;
1717 * Returns true if there are any errors with this rule.
1719 * @return bool
1721 public function has_errors() {
1722 foreach ($this->styles as $style) {
1723 if (is_array($style)) {
1724 foreach ($style as $advstyle) {
1725 if ($advstyle->has_error()) {
1726 return true;
1729 continue;
1731 if ($style->has_error()) {
1732 return true;
1735 return false;
1739 * Returns the error strings that were recorded when processing this rule.
1741 * Before calling this function you should first call {@link css_rule::has_errors()}
1742 * to make sure there are errors (hopefully there arn't).
1744 * @return string
1746 public function get_error_string() {
1747 $css = $this->out();
1748 $errors = array();
1749 foreach ($this->styles as $style) {
1750 if (is_array($style)) {
1751 foreach ($style as $s) {
1752 if ($style instanceof css_style && $style->has_error()) {
1753 $errors[] = " * ".$style->get_last_error();
1756 } else if ($style instanceof css_style && $style->has_error()) {
1757 $errors[] = " * ".$style->get_last_error();
1760 return $css." has the following errors:\n".join("\n", $errors);
1764 * Returns true if this rule could be considered a reset rule.
1766 * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector.
1768 * @return bool
1770 public function is_reset_rule() {
1771 foreach ($this->selectors as $selector) {
1772 if (!$selector->is_basic()) {
1773 return false;
1776 return true;
1781 * An abstract CSS rule collection class.
1783 * This class is extended by things such as media and keyframe declaration. They are declarations that
1784 * group rules together for a purpose.
1785 * When no declaration is specified rules accumulate into @media all.
1787 * @package core
1788 * @category css
1789 * @copyright 2012 Sam Hemelryk
1790 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1792 abstract class css_rule_collection {
1794 * An array of rules within this collection instance
1795 * @var array
1797 protected $rules = array();
1800 * The collection must be able to print itself.
1802 abstract public function out();
1805 * Adds a new CSS rule to this collection instance
1807 * @param css_rule $newrule
1809 public function add_rule(css_rule $newrule) {
1810 foreach ($newrule->split_by_selector() as $rule) {
1811 $hash = $rule->get_selector_hash();
1812 if (!array_key_exists($hash, $this->rules)) {
1813 $this->rules[$hash] = $rule;
1814 } else {
1815 $this->rules[$hash]->add_styles($rule->get_styles());
1821 * Returns the rules used by this collection
1823 * @return array
1825 public function get_rules() {
1826 return $this->rules;
1830 * Organises rules by gropuing selectors based upon the styles and consolidating
1831 * those selectors into single rules.
1833 * @return bool True if the CSS was optimised by this method
1835 public function organise_rules_by_selectors() {
1836 $optimised = array();
1837 $beforecount = count($this->rules);
1838 $lasthash = null;
1839 $lastrule = null;
1840 foreach ($this->rules as $rule) {
1841 $hash = $rule->get_style_hash();
1842 if ($lastrule !== null && $lasthash !== null && $hash === $lasthash) {
1843 foreach ($rule->get_selectors() as $selector) {
1844 $lastrule->add_selector($selector);
1846 continue;
1848 $lastrule = clone($rule);
1849 $lasthash = $hash;
1850 $optimised[] = $lastrule;
1852 $this->rules = array();
1853 foreach ($optimised as $optimised) {
1854 $this->rules[$optimised->get_selector_hash()] = $optimised;
1856 $aftercount = count($this->rules);
1857 return ($beforecount < $aftercount);
1861 * Returns the total number of rules that exist within this collection
1863 * @return int
1865 public function count_rules() {
1866 return count($this->rules);
1870 * Returns the total number of selectors that exist within this collection
1872 * @return int
1874 public function count_selectors() {
1875 $count = 0;
1876 foreach ($this->rules as $rule) {
1877 $count += $rule->get_selector_count();
1879 return $count;
1883 * Returns true if the collection has any rules that have errors
1885 * @return boolean
1887 public function has_errors() {
1888 foreach ($this->rules as $rule) {
1889 if ($rule->has_errors()) {
1890 return true;
1893 return false;
1897 * Returns any errors that have happened within rules in this collection.
1899 * @return string
1901 public function get_errors() {
1902 $errors = array();
1903 foreach ($this->rules as $rule) {
1904 if ($rule->has_errors()) {
1905 $errors[] = $rule->get_error_string();
1908 return $errors;
1913 * A media class to organise rules by the media they apply to.
1915 * @package core
1916 * @category css
1917 * @copyright 2012 Sam Hemelryk
1918 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1920 class css_media extends css_rule_collection {
1923 * An array of the different media types this instance applies to.
1924 * @var array
1926 protected $types = array();
1929 * Initalises a new media instance
1931 * @param string $for The media that the contained rules are destined for.
1933 public function __construct($for = 'all') {
1934 $types = explode(',', $for);
1935 $this->types = array_map('trim', $types);
1939 * Returns the CSS for this media and all of its rules.
1941 * @return string
1943 public function out() {
1944 return css_writer::media(join(',', $this->types), $this->rules);
1948 * Returns an array of media that this media instance applies to
1950 * @return array
1952 public function get_types() {
1953 return $this->types;
1957 * Returns all of the reset rules known by this media set.
1958 * @param bool $remove If set to true reset rules will be removed before being returned.
1959 * @return array
1961 public function get_reset_rules($remove = false) {
1962 $resetrules = array();
1963 foreach ($this->rules as $key => $rule) {
1964 if ($rule->is_reset_rule()) {
1965 $resetrules[] = clone $rule;
1966 if ($remove) {
1967 unset($this->rules[$key]);
1971 return $resetrules;
1976 * A media class to organise rules by the media they apply to.
1978 * @package core
1979 * @category css
1980 * @copyright 2012 Sam Hemelryk
1981 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1983 class css_keyframe extends css_rule_collection {
1985 /** @var string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes */
1986 protected $for;
1988 /** @var string $name The name for the keyframes */
1989 protected $name;
1991 * Constructs a new keyframe
1993 * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
1994 * @param string $name The name for the keyframes
1996 public function __construct($for, $name) {
1997 $this->for = $for;
1998 $this->name = $name;
2001 * Returns the directive of this keyframe
2003 * e.g. keyframes, -moz-keyframes, -webkit-keyframes
2004 * @return string
2006 public function get_for() {
2007 return $this->for;
2010 * Returns the name of this keyframe
2011 * @return string
2013 public function get_name() {
2014 return $this->name;
2017 * Returns the CSS for this collection of keyframes and all of its rules.
2019 * @return string
2021 public function out() {
2022 return css_writer::keyframe($this->for, $this->name, $this->rules);
2027 * An absract class to represent CSS styles
2029 * @package core
2030 * @category css
2031 * @copyright 2012 Sam Hemelryk
2032 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2034 abstract class css_style {
2036 /** Constant used for recongise a special empty value in a CSS style */
2037 const NULL_VALUE = '@@$NULL$@@';
2040 * The name of the style
2041 * @var string
2043 protected $name;
2046 * The value for the style
2047 * @var mixed
2049 protected $value;
2052 * If set to true this style was defined with the !important rule.
2053 * Only trolls use !important.
2054 * Don't hide under bridges.. its not good for your skin. Do the proper thing
2055 * and fix the issue don't just force a fix that will undoubtedly one day
2056 * lead to further frustration.
2057 * @var bool
2059 protected $important = false;
2062 * Gets set to true if this style has an error
2063 * @var bool
2065 protected $error = false;
2068 * The last error message that occured
2069 * @var string
2071 protected $errormessage = null;
2074 * Initialises a new style.
2076 * This is the only public way to create a style to ensure they that appropriate
2077 * style class is used if it exists.
2079 * @param string $name The name of the style.
2080 * @param string $value The value of the style.
2081 * @return css_style_generic
2083 public static function init_automatic($name, $value) {
2084 $specificclass = 'css_style_'.preg_replace('#[^a-zA-Z0-9]+#', '', $name);
2085 if (class_exists($specificclass)) {
2086 return $specificclass::init($value);
2088 return new css_style_generic($name, $value);
2092 * Creates a new style when given its name and value
2094 * @param string $name The name of the style.
2095 * @param string $value The value of the style.
2097 protected function __construct($name, $value) {
2098 $this->name = $name;
2099 $this->set_value($value);
2103 * Sets the value for the style
2105 * @param string $value
2107 final public function set_value($value) {
2108 $value = trim($value);
2109 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
2110 if ($important) {
2111 $value = substr($value, 0, -(strlen($matches[1])));
2112 $value = rtrim($value);
2114 if (!$this->important || $important) {
2115 $this->value = $this->clean_value($value);
2116 $this->important = $important;
2118 if (!$this->is_valid()) {
2119 $this->set_error('Invalid value for '.$this->name);
2124 * Returns true if the value associated with this style is valid
2126 * @return bool
2128 public function is_valid() {
2129 return true;
2133 * Returns the name for the style
2135 * @return string
2137 public function get_name() {
2138 return $this->name;
2142 * Returns the value for the style
2144 * @param bool $includeimportant If set to true and the rule is important !important postfix will be used.
2145 * @return string
2147 public function get_value($includeimportant = true) {
2148 $value = $this->value;
2149 if ($includeimportant && $this->important) {
2150 $value .= ' !important';
2152 return $value;
2156 * Returns the style ready for use in CSS
2158 * @param string|null $value A value to use to override the value for this style.
2159 * @return string
2161 public function out($value = null) {
2162 if (is_null($value)) {
2163 $value = $this->get_value();
2165 return css_writer::style($this->name, $value, $this->important);
2169 * This can be overridden by a specific style allowing it to clean its values
2170 * consistently.
2172 * @param mixed $value
2173 * @return mixed
2175 protected function clean_value($value) {
2176 return $value;
2180 * If this particular style can be consolidated into another style this function
2181 * should return the style that it can be consolidated into.
2183 * @return string|null
2185 public function consolidate_to() {
2186 return null;
2190 * Sets the last error message.
2192 * @param string $message
2194 protected function set_error($message) {
2195 $this->error = true;
2196 $this->errormessage = $message;
2200 * Returns true if an error has occured
2202 * @return bool
2204 public function has_error() {
2205 return $this->error;
2209 * Returns the last error that occured or null if no errors have happened.
2211 * @return string
2213 public function get_last_error() {
2214 return $this->errormessage;
2218 * Returns true if the value for this style is the special null value.
2220 * This should only be overriden in circumstances where a shorthand style can lead
2221 * to move explicit styles being overwritten. Not a common place occurenace.
2223 * Example:
2224 * This occurs if the shorthand background property was used but no proper value
2225 * was specified for this style.
2226 * This leads to a null value being used unless otherwise overridden.
2228 * @return bool
2230 public function is_special_empty_value() {
2231 return false;
2235 * Returns true if this style permits multiple values.
2237 * This occurs for styles such as background image that can have browser specific values that need to be maintained because
2238 * of course we don't know what browser the user is using, and optimisation occurs before caching.
2239 * Thus we must always server all values we encounter in the order we encounter them for when this is set to true.
2241 * @return boolean False by default, true if the style supports muliple values.
2243 public function allows_multiple_values() {
2244 return false;
2248 * Returns true if this style was marked important.
2249 * @return bool
2251 public function is_important() {
2252 return !empty($this->important);
2256 * Sets the important flag for this style and its current value.
2257 * @param bool $important
2259 public function set_important($important = true) {
2260 $this->important = (bool) $important;
2265 * A generic CSS style class to use when a more specific class does not exist.
2267 * @package core
2268 * @category css
2269 * @copyright 2012 Sam Hemelryk
2270 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2272 class css_style_generic extends css_style {
2275 * Cleans incoming values for typical things that can be optimised.
2277 * @param mixed $value Cleans the provided value optimising it if possible
2278 * @return string
2280 protected function clean_value($value) {
2281 if (trim($value) == '0px') {
2282 $value = 0;
2283 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) {
2284 $value = '#'.strtoupper($matches[1]);
2286 return $value;
2291 * A colour CSS style
2293 * @package core
2294 * @category css
2295 * @copyright 2012 Sam Hemelryk
2296 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2298 class css_style_color extends css_style {
2301 * Creates a new colour style
2303 * @param mixed $value Initialises a new colour style
2304 * @return css_style_color
2306 public static function init($value) {
2307 return new css_style_color('color', $value);
2311 * Cleans the colour unifing it to a 6 char hash colour if possible
2312 * Doing this allows us to associate identical colours being specified in
2313 * different ways. e.g. Red, red, #F00, and #F00000
2315 * @param mixed $value Cleans the provided value optimising it if possible
2316 * @return string
2318 protected function clean_value($value) {
2319 $value = trim($value);
2320 if (css_is_colour($value)) {
2321 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) {
2322 $value = '#'.strtoupper($matches[1]);
2323 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) {
2324 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
2325 $value = '#'.strtoupper($value);
2326 } else if (array_key_exists(strtolower($value), css_optimiser::$htmlcolours)) {
2327 $value = css_optimiser::$htmlcolours[strtolower($value)];
2330 return $value;
2334 * Returns the colour style for use within CSS.
2335 * Will return an optimised hash colour.
2337 * e.g #123456
2338 * #123 instead of #112233
2339 * #F00 instead of red
2341 * @param string $overridevalue If provided then this value will be used instead
2342 * of the styles current value.
2343 * @return string
2345 public function out($overridevalue = null) {
2346 if ($overridevalue === null) {
2347 $overridevalue = $this->value;
2349 return parent::out(self::shrink_value($overridevalue));
2353 * Shrinks the colour value is possible.
2355 * @param string $value Shrinks the current value to an optimial form if possible
2356 * @return string
2358 public static function shrink_value($value) {
2359 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) {
2360 return '#'.$matches[1].$matches[2].$matches[3];
2362 return $value;
2366 * Returns true if the value is a valid colour.
2368 * @return bool
2370 public function is_valid() {
2371 return css_is_colour($this->value);
2376 * A width style
2378 * @package core
2379 * @category css
2380 * @copyright 2012 Sam Hemelryk
2381 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2383 class css_style_width extends css_style {
2386 * Checks if the width is valid
2387 * @return bool
2389 public function is_valid() {
2390 return css_is_width($this->value);
2394 * Cleans the provided value
2396 * @param mixed $value Cleans the provided value optimising it if possible
2397 * @return string
2399 protected function clean_value($value) {
2400 if (!css_is_width($value)) {
2401 // Note we don't actually change the value to something valid. That
2402 // would be bad for futureproofing.
2403 $this->set_error('Invalid width specified for '.$this->name);
2404 } else if (preg_match('#^0\D+$#', $value)) {
2405 $value = 0;
2407 return trim($value);
2411 * Initialises a new width style
2413 * @param mixed $value The value this style has
2414 * @return css_style_width
2416 public static function init($value) {
2417 return new css_style_width('width', $value);
2422 * A margin style
2424 * @package core
2425 * @category css
2426 * @copyright 2012 Sam Hemelryk
2427 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2429 class css_style_margin extends css_style_width {
2432 * Initialises a margin style.
2434 * In this case we split the margin into several other margin styles so that
2435 * we can properly condense overrides and then reconsolidate them later into
2436 * an optimal form.
2438 * @param string $value The value the style has
2439 * @return array An array of margin values that can later be consolidated
2441 public static function init($value) {
2442 $important = '';
2443 if (strpos($value, '!important') !== false) {
2444 $important = ' !important';
2445 $value = str_replace('!important', '', $value);
2448 $value = preg_replace('#\s+#', ' ', trim($value));
2449 $bits = explode(' ', $value, 4);
2451 $top = $right = $bottom = $left = null;
2452 if (count($bits) > 0) {
2453 $top = $right = $bottom = $left = array_shift($bits);
2455 if (count($bits) > 0) {
2456 $right = $left = array_shift($bits);
2458 if (count($bits) > 0) {
2459 $bottom = array_shift($bits);
2461 if (count($bits) > 0) {
2462 $left = array_shift($bits);
2464 return array(
2465 new css_style_margintop('margin-top', $top.$important),
2466 new css_style_marginright('margin-right', $right.$important),
2467 new css_style_marginbottom('margin-bottom', $bottom.$important),
2468 new css_style_marginleft('margin-left', $left.$important)
2473 * Consolidates individual margin styles into a single margin style
2475 * @param array $styles
2476 * @return array An array of consolidated styles
2478 public static function consolidate(array $styles) {
2479 if (count($styles) != 4) {
2480 return $styles;
2483 $someimportant = false;
2484 $allimportant = null;
2485 $notimportantequal = null;
2486 $firstvalue = null;
2487 foreach ($styles as $style) {
2488 if ($style->is_important()) {
2489 $someimportant = true;
2490 if ($allimportant === null) {
2491 $allimportant = true;
2493 } else {
2494 if ($allimportant === true) {
2495 $allimportant = false;
2497 if ($firstvalue == null) {
2498 $firstvalue = $style->get_value(false);
2499 $notimportantequal = true;
2500 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
2501 $notimportantequal = false;
2506 if ($someimportant && !$allimportant && !$notimportantequal) {
2507 return $styles;
2510 if ($someimportant && !$allimportant && $notimportantequal) {
2511 $return = array(
2512 new css_style_margin('margin', $firstvalue)
2514 foreach ($styles as $style) {
2515 if ($style->is_important()) {
2516 $return[] = $style;
2519 return $return;
2520 } else {
2521 $top = null;
2522 $right = null;
2523 $bottom = null;
2524 $left = null;
2525 foreach ($styles as $style) {
2526 switch ($style->get_name()) {
2527 case 'margin-top' :
2528 $top = $style->get_value(false);
2529 break;
2530 case 'margin-right' :
2531 $right = $style->get_value(false);
2532 break;
2533 case 'margin-bottom' :
2534 $bottom = $style->get_value(false);
2535 break;
2536 case 'margin-left' :
2537 $left = $style->get_value(false);
2538 break;
2541 if ($top == $bottom && $left == $right) {
2542 if ($top == $left) {
2543 $returnstyle = new css_style_margin('margin', $top);
2544 } else {
2545 $returnstyle = new css_style_margin('margin', "{$top} {$left}");
2547 } else if ($left == $right) {
2548 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom}");
2549 } else {
2550 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}");
2552 if ($allimportant) {
2553 $returnstyle->set_important();
2555 return array($returnstyle);
2561 * A margin top style
2563 * @package core
2564 * @category css
2565 * @copyright 2012 Sam Hemelryk
2566 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2568 class css_style_margintop extends css_style_margin {
2571 * A simple init, just a single style
2573 * @param string $value The value the style has
2574 * @return css_style_margintop
2576 public static function init($value) {
2577 return new css_style_margintop('margin-top', $value);
2581 * This style can be consolidated into a single margin style
2583 * @return string
2585 public function consolidate_to() {
2586 return 'margin';
2591 * A margin right style
2593 * @package core
2594 * @category css
2595 * @copyright 2012 Sam Hemelryk
2596 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2598 class css_style_marginright extends css_style_margin {
2601 * A simple init, just a single style
2603 * @param string $value The value the style has
2604 * @return css_style_margintop
2606 public static function init($value) {
2607 return new css_style_marginright('margin-right', $value);
2611 * This style can be consolidated into a single margin style
2613 * @return string
2615 public function consolidate_to() {
2616 return 'margin';
2621 * A margin bottom style
2623 * @package core
2624 * @category css
2625 * @copyright 2012 Sam Hemelryk
2626 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2628 class css_style_marginbottom extends css_style_margin {
2631 * A simple init, just a single style
2633 * @param string $value The value the style has
2634 * @return css_style_margintop
2636 public static function init($value) {
2637 return new css_style_marginbottom('margin-bottom', $value);
2641 * This style can be consolidated into a single margin style
2643 * @return string
2645 public function consolidate_to() {
2646 return 'margin';
2651 * A margin left style
2653 * @package core
2654 * @category css
2655 * @copyright 2012 Sam Hemelryk
2656 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2658 class css_style_marginleft extends css_style_margin {
2661 * A simple init, just a single style
2663 * @param string $value The value the style has
2664 * @return css_style_margintop
2666 public static function init($value) {
2667 return new css_style_marginleft('margin-left', $value);
2671 * This style can be consolidated into a single margin style
2673 * @return string
2675 public function consolidate_to() {
2676 return 'margin';
2681 * A border style
2683 * @package core
2684 * @category css
2685 * @copyright 2012 Sam Hemelryk
2686 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2688 class css_style_border extends css_style {
2691 * Initalises the border style into an array of individual style compontents
2693 * @param string $value The value the style has
2694 * @return css_style_bordercolor
2696 public static function init($value) {
2697 $value = preg_replace('#\s+#', ' ', $value);
2698 $bits = explode(' ', $value, 3);
2700 $return = array();
2701 if (count($bits) > 0) {
2702 $width = array_shift($bits);
2703 if (!css_style_borderwidth::is_border_width($width)) {
2704 $width = '0';
2706 $return[] = new css_style_borderwidth('border-top-width', $width);
2707 $return[] = new css_style_borderwidth('border-right-width', $width);
2708 $return[] = new css_style_borderwidth('border-bottom-width', $width);
2709 $return[] = new css_style_borderwidth('border-left-width', $width);
2711 if (count($bits) > 0) {
2712 $style = array_shift($bits);
2713 $return[] = new css_style_borderstyle('border-top-style', $style);
2714 $return[] = new css_style_borderstyle('border-right-style', $style);
2715 $return[] = new css_style_borderstyle('border-bottom-style', $style);
2716 $return[] = new css_style_borderstyle('border-left-style', $style);
2718 if (count($bits) > 0) {
2719 $colour = array_shift($bits);
2720 $return[] = new css_style_bordercolor('border-top-color', $colour);
2721 $return[] = new css_style_bordercolor('border-right-color', $colour);
2722 $return[] = new css_style_bordercolor('border-bottom-color', $colour);
2723 $return[] = new css_style_bordercolor('border-left-color', $colour);
2725 return $return;
2729 * Consolidates all border styles into a single style
2731 * @param array $styles An array of border styles
2732 * @return array An optimised array of border styles
2734 public static function consolidate(array $styles) {
2736 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2737 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2738 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2740 foreach ($styles as $style) {
2741 switch ($style->get_name()) {
2742 case 'border-top-width':
2743 $borderwidths['top'] = $style->get_value();
2744 break;
2745 case 'border-right-width':
2746 $borderwidths['right'] = $style->get_value();
2747 break;
2748 case 'border-bottom-width':
2749 $borderwidths['bottom'] = $style->get_value();
2750 break;
2751 case 'border-left-width':
2752 $borderwidths['left'] = $style->get_value();
2753 break;
2755 case 'border-top-style':
2756 $borderstyles['top'] = $style->get_value();
2757 break;
2758 case 'border-right-style':
2759 $borderstyles['right'] = $style->get_value();
2760 break;
2761 case 'border-bottom-style':
2762 $borderstyles['bottom'] = $style->get_value();
2763 break;
2764 case 'border-left-style':
2765 $borderstyles['left'] = $style->get_value();
2766 break;
2768 case 'border-top-color':
2769 $bordercolors['top'] = css_style_color::shrink_value($style->get_value());
2770 break;
2771 case 'border-right-color':
2772 $bordercolors['right'] = css_style_color::shrink_value($style->get_value());
2773 break;
2774 case 'border-bottom-color':
2775 $bordercolors['bottom'] = css_style_color::shrink_value($style->get_value());
2776 break;
2777 case 'border-left-color':
2778 $bordercolors['left'] = css_style_color::shrink_value($style->get_value());
2779 break;
2783 $uniquewidths = count(array_unique($borderwidths));
2784 $uniquestyles = count(array_unique($borderstyles));
2785 $uniquecolors = count(array_unique($bordercolors));
2787 $nullwidths = in_array(null, $borderwidths, true);
2788 $nullstyles = in_array(null, $borderstyles, true);
2789 $nullcolors = in_array(null, $bordercolors, true);
2791 $allwidthsthesame = ($uniquewidths === 1)?1:0;
2792 $allstylesthesame = ($uniquestyles === 1)?1:0;
2793 $allcolorsthesame = ($uniquecolors === 1)?1:0;
2795 $allwidthsnull = $allwidthsthesame && $nullwidths;
2796 $allstylesnull = $allstylesthesame && $nullstyles;
2797 $allcolorsnull = $allcolorsthesame && $nullcolors;
2799 $return = array();
2800 if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
2801 // Everything is null still... boo
2802 return array(new css_style_border('border', ''));
2804 } else if ($allwidthsnull && $allstylesnull) {
2806 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2807 return $return;
2809 } else if ($allwidthsnull && $allcolorsnull) {
2811 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2812 return $return;
2814 } else if ($allcolorsnull && $allstylesnull) {
2816 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2817 return $return;
2821 if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 3) {
2823 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']);
2825 } else if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 2) {
2827 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) {
2829 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']);
2830 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2832 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) {
2834 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']);
2835 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2837 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) {
2839 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']);
2840 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2842 } else {
2843 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2844 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2845 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2848 } else if (!$nullwidths && !$nullcolors && !$nullstyles && max(array_count_values($borderwidths)) == 3 && max(array_count_values($borderstyles)) == 3 && max(array_count_values($bordercolors)) == 3) {
2849 $widthkeys = array();
2850 $stylekeys = array();
2851 $colorkeys = array();
2853 foreach ($borderwidths as $key => $value) {
2854 if (!array_key_exists($value, $widthkeys)) {
2855 $widthkeys[$value] = array();
2857 $widthkeys[$value][] = $key;
2859 usort($widthkeys, 'css_sort_by_count');
2860 $widthkeys = array_values($widthkeys);
2862 foreach ($borderstyles as $key => $value) {
2863 if (!array_key_exists($value, $stylekeys)) {
2864 $stylekeys[$value] = array();
2866 $stylekeys[$value][] = $key;
2868 usort($stylekeys, 'css_sort_by_count');
2869 $stylekeys = array_values($stylekeys);
2871 foreach ($bordercolors as $key => $value) {
2872 if (!array_key_exists($value, $colorkeys)) {
2873 $colorkeys[$value] = array();
2875 $colorkeys[$value][] = $key;
2877 usort($colorkeys, 'css_sort_by_count');
2878 $colorkeys = array_values($colorkeys);
2880 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
2881 $key = $widthkeys[0][0];
2882 self::build_style_string($return, 'css_style_border', 'border', $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2883 $key = $widthkeys[1][0];
2884 self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key, $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2885 } else {
2886 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2887 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2888 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2889 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2891 } else {
2892 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2893 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2894 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2895 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2897 foreach ($return as $key => $style) {
2898 if ($style->get_value() == '') {
2899 unset($return[$key]);
2902 return $return;
2906 * Border styles get consolidated to a single border style.
2908 * @return string
2910 public function consolidate_to() {
2911 return 'border';
2915 * Consolidates a series of border styles into an optimised array of border
2916 * styles by looking at the direction of the border and prioritising that
2917 * during the optimisation.
2919 * @param array $array An array to add styles into during consolidation. Passed by reference.
2920 * @param string $class The class type to initalise
2921 * @param string $style The style to create
2922 * @param string|array $top The top value
2923 * @param string $right The right value
2924 * @param string $bottom The bottom value
2925 * @param string $left The left value
2926 * @return bool
2928 public static function consolidate_styles_by_direction(&$array, $class, $style, $top, $right = null, $bottom = null, $left = null) {
2929 if (is_array($top)) {
2930 $right = $top['right'];
2931 $bottom = $top['bottom'];
2932 $left = $top['left'];
2933 $top = $top['top'];
2936 if ($top == $bottom && $left == $right && $top == $left) {
2937 if (is_null($top)) {
2938 $array[] = new $class($style, '');
2939 } else {
2940 $array[] = new $class($style, $top);
2942 } else if ($top == null || $right == null || $bottom == null || $left == null) {
2943 if ($top !== null) {
2944 $array[] = new $class(str_replace('border-', 'border-top-', $style), $top);
2946 if ($right !== null) {
2947 $array[] = new $class(str_replace('border-', 'border-right-', $style), $right);
2949 if ($bottom !== null) {
2950 $array[] = new $class(str_replace('border-', 'border-bottom-', $style), $bottom);
2952 if ($left !== null) {
2953 $array[] = new $class(str_replace('border-', 'border-left-', $style), $left);
2955 } else if ($top == $bottom && $left == $right) {
2956 $array[] = new $class($style, $top.' '.$right);
2957 } else if ($left == $right) {
2958 $array[] = new $class($style, $top.' '.$right.' '.$bottom);
2959 } else {
2960 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left);
2962 return true;
2966 * Builds a border style for a set of width, style, and colour values
2968 * @param array $array An array into which the generated style is added
2969 * @param string $class The class type to initialise
2970 * @param string $cssstyle The style to use
2971 * @param string $width The width of the border
2972 * @param string $style The style of the border
2973 * @param string $color The colour of the border
2974 * @return bool
2976 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) {
2977 if (!is_null($width) && !is_null($style) && !is_null($color)) {
2978 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color);
2979 } else if (!is_null($width) && !is_null($style) && is_null($color)) {
2980 $array[] = new $class($cssstyle, $width.' '.$style);
2981 } else if (!is_null($width) && is_null($style) && is_null($color)) {
2982 $array[] = new $class($cssstyle, $width);
2983 } else {
2984 if (!is_null($width)) {
2985 $array[] = new $class($cssstyle, $width);
2987 if (!is_null($style)) {
2988 $array[] = new $class($cssstyle, $style);
2990 if (!is_null($color)) {
2991 $array[] = new $class($cssstyle, $color);
2994 return true;
2999 * A border colour style
3001 * @package core
3002 * @category css
3003 * @copyright 2012 Sam Hemelryk
3004 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3006 class css_style_bordercolor extends css_style_color {
3009 * Creates a new border colour style
3011 * Based upon the colour style
3013 * @param mixed $value
3014 * @return Array of css_style_bordercolor
3016 public static function init($value) {
3017 $value = preg_replace('#\s+#', ' ', $value);
3018 $bits = explode(' ', $value, 4);
3020 $top = $right = $bottom = $left = null;
3021 if (count($bits) > 0) {
3022 $top = $right = $bottom = $left = array_shift($bits);
3024 if (count($bits) > 0) {
3025 $right = $left = array_shift($bits);
3027 if (count($bits) > 0) {
3028 $bottom = array_shift($bits);
3030 if (count($bits) > 0) {
3031 $left = array_shift($bits);
3033 return array(
3034 css_style_bordertopcolor::init($top),
3035 css_style_borderrightcolor::init($right),
3036 css_style_borderbottomcolor::init($bottom),
3037 css_style_borderleftcolor::init($left)
3042 * Consolidate this to a single border style
3044 * @return string
3046 public function consolidate_to() {
3047 return 'border';
3051 * Cleans the value
3053 * @param string $value Cleans the provided value optimising it if possible
3054 * @return string
3056 protected function clean_value($value) {
3057 $values = explode(' ', $value);
3058 $values = array_map('parent::clean_value', $values);
3059 return join (' ', $values);
3063 * Outputs this style
3065 * @param string $overridevalue
3066 * @return string
3068 public function out($overridevalue = null) {
3069 if ($overridevalue === null) {
3070 $overridevalue = $this->value;
3072 $values = explode(' ', $overridevalue);
3073 $values = array_map('css_style_color::shrink_value', $values);
3074 return parent::out(join (' ', $values));
3079 * A border left style
3081 * @package core
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_borderleft extends css_style_generic {
3089 * Initialises the border left style into individual components
3091 * @param string $value
3092 * @return array Array of css_style_borderleftwidth|css_style_borderleftstyle|css_style_borderleftcolor
3094 public static function init($value) {
3095 $value = preg_replace('#\s+#', ' ', $value);
3096 $bits = explode(' ', $value, 3);
3098 $return = array();
3099 if (count($bits) > 0) {
3100 $return[] = css_style_borderleftwidth::init(array_shift($bits));
3102 if (count($bits) > 0) {
3103 $return[] = css_style_borderleftstyle::init(array_shift($bits));
3105 if (count($bits) > 0) {
3106 $return[] = css_style_borderleftcolor::init(array_shift($bits));
3108 return $return;
3112 * Consolidate this to a single border style
3114 * @return string
3116 public function consolidate_to() {
3117 return 'border';
3122 * A border right style
3124 * @package core
3125 * @category css
3126 * @copyright 2012 Sam Hemelryk
3127 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3129 class css_style_borderright extends css_style_generic {
3132 * Initialises the border right style into individual components
3134 * @param string $value The value of the style
3135 * @return array Array of css_style_borderrightwidth|css_style_borderrightstyle|css_style_borderrightcolor
3137 public static function init($value) {
3138 $value = preg_replace('#\s+#', ' ', $value);
3139 $bits = explode(' ', $value, 3);
3141 $return = array();
3142 if (count($bits) > 0) {
3143 $return[] = css_style_borderrightwidth::init(array_shift($bits));
3145 if (count($bits) > 0) {
3146 $return[] = css_style_borderrightstyle::init(array_shift($bits));
3148 if (count($bits) > 0) {
3149 $return[] = css_style_borderrightcolor::init(array_shift($bits));
3151 return $return;
3155 * Consolidate this to a single border style
3157 * @return string
3159 public function consolidate_to() {
3160 return 'border';
3165 * A border top style
3167 * @package core
3168 * @category css
3169 * @copyright 2012 Sam Hemelryk
3170 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3172 class css_style_bordertop extends css_style_generic {
3175 * Initialises the border top style into individual components
3177 * @param string $value The value of the style
3178 * @return array Array of css_style_bordertopwidth|css_style_bordertopstyle|css_style_bordertopcolor
3180 public static function init($value) {
3181 $value = preg_replace('#\s+#', ' ', $value);
3182 $bits = explode(' ', $value, 3);
3184 $return = array();
3185 if (count($bits) > 0) {
3186 $return[] = css_style_bordertopwidth::init(array_shift($bits));
3188 if (count($bits) > 0) {
3189 $return[] = css_style_bordertopstyle::init(array_shift($bits));
3191 if (count($bits) > 0) {
3192 $return[] = css_style_bordertopcolor::init(array_shift($bits));
3194 return $return;
3198 * Consolidate this to a single border style
3200 * @return string
3202 public function consolidate_to() {
3203 return 'border';
3208 * A border bottom style
3210 * @package core
3211 * @category css
3212 * @copyright 2012 Sam Hemelryk
3213 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3215 class css_style_borderbottom extends css_style_generic {
3218 * Initialises the border bottom style into individual components
3220 * @param string $value The value of the style
3221 * @return array Array of css_style_borderbottomwidth|css_style_borderbottomstyle|css_style_borderbottomcolor
3223 public static function init($value) {
3224 $value = preg_replace('#\s+#', ' ', $value);
3225 $bits = explode(' ', $value, 3);
3227 $return = array();
3228 if (count($bits) > 0) {
3229 $return[] = css_style_borderbottomwidth::init(array_shift($bits));
3231 if (count($bits) > 0) {
3232 $return[] = css_style_borderbottomstyle::init(array_shift($bits));
3234 if (count($bits) > 0) {
3235 $return[] = css_style_borderbottomcolor::init(array_shift($bits));
3237 return $return;
3241 * Consolidate this to a single border style
3243 * @return string
3245 public function consolidate_to() {
3246 return 'border';
3251 * A border width style
3253 * @package core
3254 * @category css
3255 * @copyright 2012 Sam Hemelryk
3256 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3258 class css_style_borderwidth extends css_style_width {
3261 * Creates a new border colour style
3263 * Based upon the colour style
3265 * @param string $value The value of the style
3266 * @return array Array of css_style_border*width
3268 public static function init($value) {
3269 $value = preg_replace('#\s+#', ' ', $value);
3270 $bits = explode(' ', $value, 4);
3272 $top = $right = $bottom = $left = null;
3273 if (count($bits) > 0) {
3274 $top = $right = $bottom = $left = array_shift($bits);
3276 if (count($bits) > 0) {
3277 $right = $left = array_shift($bits);
3279 if (count($bits) > 0) {
3280 $bottom = array_shift($bits);
3282 if (count($bits) > 0) {
3283 $left = array_shift($bits);
3285 return array(
3286 css_style_bordertopwidth::init($top),
3287 css_style_borderrightwidth::init($right),
3288 css_style_borderbottomwidth::init($bottom),
3289 css_style_borderleftwidth::init($left)
3294 * Consolidate this to a single border style
3296 * @return string
3298 public function consolidate_to() {
3299 return 'border';
3303 * Checks if the width is valid
3304 * @return bool
3306 public function is_valid() {
3307 return self::is_border_width($this->value);
3311 * Cleans the provided value
3313 * @param mixed $value Cleans the provided value optimising it if possible
3314 * @return string
3316 protected function clean_value($value) {
3317 $isvalid = self::is_border_width($value);
3318 if (!$isvalid) {
3319 $this->set_error('Invalid width specified for '.$this->name);
3320 } else if (preg_match('#^0\D+$#', $value)) {
3321 return '0';
3323 return trim($value);
3327 * Returns true if the provided value is a permitted border width
3328 * @param string $value The value to check
3329 * @return bool
3331 public static function is_border_width($value) {
3332 $altwidthvalues = array('thin', 'medium', 'thick');
3333 return css_is_width($value) || in_array($value, $altwidthvalues);
3338 * A border style style
3340 * @package core
3341 * @category css
3342 * @copyright 2012 Sam Hemelryk
3343 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3345 class css_style_borderstyle extends css_style_generic {
3348 * Creates a new border colour style
3350 * Based upon the colour style
3352 * @param string $value The value of the style
3353 * @return array Array of css_style_border*style
3355 public static function init($value) {
3356 $value = preg_replace('#\s+#', ' ', $value);
3357 $bits = explode(' ', $value, 4);
3359 $top = $right = $bottom = $left = null;
3360 if (count($bits) > 0) {
3361 $top = $right = $bottom = $left = array_shift($bits);
3363 if (count($bits) > 0) {
3364 $right = $left = array_shift($bits);
3366 if (count($bits) > 0) {
3367 $bottom = array_shift($bits);
3369 if (count($bits) > 0) {
3370 $left = array_shift($bits);
3372 return array(
3373 css_style_bordertopstyle::init($top),
3374 css_style_borderrightstyle::init($right),
3375 css_style_borderbottomstyle::init($bottom),
3376 css_style_borderleftstyle::init($left)
3381 * Consolidate this to a single border style
3383 * @return string
3385 public function consolidate_to() {
3386 return 'border';
3391 * A border top colour style
3393 * @package core
3394 * @category css
3395 * @copyright 2012 Sam Hemelryk
3396 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3398 class css_style_bordertopcolor extends css_style_bordercolor {
3401 * Initialises this style object
3403 * @param string $value The value of the style
3404 * @return css_style_bordertopcolor
3406 public static function init($value) {
3407 return new css_style_bordertopcolor('border-top-color', $value);
3411 * Consolidate this to a single border style
3413 * @return string
3415 public function consolidate_to() {
3416 return 'border';
3421 * A border left colour style
3423 * @package core
3424 * @category css
3425 * @copyright 2012 Sam Hemelryk
3426 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3428 class css_style_borderleftcolor extends css_style_bordercolor {
3431 * Initialises this style object
3433 * @param string $value The value of the style
3434 * @return css_style_borderleftcolor
3436 public static function init($value) {
3437 return new css_style_borderleftcolor('border-left-color', $value);
3441 * Consolidate this to a single border style
3443 * @return string
3445 public function consolidate_to() {
3446 return 'border';
3451 * A border right colour style
3453 * @package core
3454 * @category css
3455 * @copyright 2012 Sam Hemelryk
3456 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3458 class css_style_borderrightcolor extends css_style_bordercolor {
3461 * Initialises this style object
3463 * @param string $value The value of the style
3464 * @return css_style_borderrightcolor
3466 public static function init($value) {
3467 return new css_style_borderrightcolor('border-right-color', $value);
3471 * Consolidate this to a single border style
3473 * @return string
3475 public function consolidate_to() {
3476 return 'border';
3481 * A border bottom colour style
3483 * @package core
3484 * @category css
3485 * @copyright 2012 Sam Hemelryk
3486 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3488 class css_style_borderbottomcolor extends css_style_bordercolor {
3491 * Initialises this style object
3493 * @param string $value The value of the style
3494 * @return css_style_borderbottomcolor
3496 public static function init($value) {
3497 return new css_style_borderbottomcolor('border-bottom-color', $value);
3501 * Consolidate this to a single border style
3503 * @return string
3505 public function consolidate_to() {
3506 return 'border';
3511 * A border width top style
3513 * @package core
3514 * @category css
3515 * @copyright 2012 Sam Hemelryk
3516 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3518 class css_style_bordertopwidth extends css_style_borderwidth {
3521 * Initialises this style object
3523 * @param string $value The value of the style
3524 * @return css_style_bordertopwidth
3526 public static function init($value) {
3527 return new css_style_bordertopwidth('border-top-width', $value);
3531 * Consolidate this to a single border style
3533 * @return string
3535 public function consolidate_to() {
3536 return 'border';
3541 * A border width left style
3543 * @package core
3544 * @category css
3545 * @copyright 2012 Sam Hemelryk
3546 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3548 class css_style_borderleftwidth extends css_style_borderwidth {
3551 * Initialises this style object
3553 * @param string $value The value of the style
3554 * @return css_style_borderleftwidth
3556 public static function init($value) {
3557 return new css_style_borderleftwidth('border-left-width', $value);
3561 * Consolidate this to a single border style
3563 * @return string
3565 public function consolidate_to() {
3566 return 'border';
3571 * A border width right style
3573 * @package core
3574 * @category css
3575 * @copyright 2012 Sam Hemelryk
3576 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3578 class css_style_borderrightwidth extends css_style_borderwidth {
3581 * Initialises this style object
3583 * @param string $value The value of the style
3584 * @return css_style_borderrightwidth
3586 public static function init($value) {
3587 return new css_style_borderrightwidth('border-right-width', $value);
3591 * Consolidate this to a single border style
3593 * @return string
3595 public function consolidate_to() {
3596 return 'border';
3601 * A border width bottom style
3603 * @package core
3604 * @category css
3605 * @copyright 2012 Sam Hemelryk
3606 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3608 class css_style_borderbottomwidth extends css_style_borderwidth {
3611 * Initialises this style object
3613 * @param string $value The value of the style
3614 * @return css_style_borderbottomwidth
3616 public static function init($value) {
3617 return new css_style_borderbottomwidth('border-bottom-width', $value);
3621 * Consolidate this to a single border style
3623 * @return string
3625 public function consolidate_to() {
3626 return 'border';
3631 * A border top style
3633 * @package core
3634 * @category css
3635 * @copyright 2012 Sam Hemelryk
3636 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3638 class css_style_bordertopstyle extends css_style_borderstyle {
3641 * Initialises this style object
3643 * @param string $value The value of the style
3644 * @return css_style_bordertopstyle
3646 public static function init($value) {
3647 return new css_style_bordertopstyle('border-top-style', $value);
3651 * Consolidate this to a single border style
3653 * @return string
3655 public function consolidate_to() {
3656 return 'border';
3661 * A border left style
3663 * @package core
3664 * @category css
3665 * @copyright 2012 Sam Hemelryk
3666 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3668 class css_style_borderleftstyle extends css_style_borderstyle {
3671 * Initialises this style object
3673 * @param string $value The value of the style
3674 * @return css_style_borderleftstyle
3676 public static function init($value) {
3677 return new css_style_borderleftstyle('border-left-style', $value);
3681 * Consolidate this to a single border style
3683 * @return string
3685 public function consolidate_to() {
3686 return 'border';
3691 * A border right style
3693 * @package core
3694 * @category css
3695 * @copyright 2012 Sam Hemelryk
3696 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3698 class css_style_borderrightstyle extends css_style_borderstyle {
3701 * Initialises this style object
3703 * @param string $value The value of the style
3704 * @return css_style_borderrightstyle
3706 public static function init($value) {
3707 return new css_style_borderrightstyle('border-right-style', $value);
3711 * Consolidate this to a single border style
3713 * @return string
3715 public function consolidate_to() {
3716 return 'border';
3721 * A border bottom style
3723 * @package core
3724 * @category css
3725 * @copyright 2012 Sam Hemelryk
3726 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3728 class css_style_borderbottomstyle extends css_style_borderstyle {
3731 * Initialises this style object
3733 * @param string $value The value for the style
3734 * @return css_style_borderbottomstyle
3736 public static function init($value) {
3737 return new css_style_borderbottomstyle('border-bottom-style', $value);
3741 * Consolidate this to a single border style
3743 * @return string
3745 public function consolidate_to() {
3746 return 'border';
3751 * A background style
3753 * @package core
3754 * @category css
3755 * @copyright 2012 Sam Hemelryk
3756 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3758 class css_style_background extends css_style {
3761 * Initialises a background style
3763 * @param string $value The value of the style
3764 * @return array An array of background component.
3766 public static function init($value) {
3767 // colour - image - repeat - attachment - position
3769 $imageurl = null;
3770 if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
3771 $imageurl = trim($matches[1]);
3772 $value = str_replace($matches[1], '', $value);
3775 // Switch out the brackets so that they don't get messed up when we explode
3776 $brackets = array();
3777 $bracketcount = 0;
3778 while (preg_match('#\([^\)\(]+\)#', $value, $matches)) {
3779 $key = "##BRACKET-{$bracketcount}##";
3780 $bracketcount++;
3781 $brackets[$key] = $matches[0];
3782 $value = str_replace($matches[0], $key, $value);
3785 $important = (stripos($value, '!important') !== false);
3786 if ($important) {
3787 // Great some genius put !important in the background shorthand property
3788 $value = str_replace('!important', '', $value);
3791 $value = preg_replace('#\s+#', ' ', $value);
3792 $bits = explode(' ', $value);
3794 foreach ($bits as $key => $bit) {
3795 $bits[$key] = self::replace_bracket_placeholders($bit, $brackets);
3797 unset($bracketcount);
3798 unset($brackets);
3800 $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit');
3801 $attachments = array('scroll' , 'fixed', 'inherit');
3802 $positions = array('top', 'left', 'bottom', 'right', 'center');
3804 $return = array();
3805 $unknownbits = array();
3807 $color = self::NULL_VALUE;
3808 if (count($bits) > 0 && css_is_colour(reset($bits))) {
3809 $color = array_shift($bits);
3812 $image = self::NULL_VALUE;
3813 if (count($bits) > 0 && preg_match('#^\s*(none|inherit|url\(\))\s*$#', reset($bits))) {
3814 $image = array_shift($bits);
3815 if ($image == 'url()') {
3816 $image = "url({$imageurl})";
3820 $repeat = self::NULL_VALUE;
3821 if (count($bits) > 0 && in_array(reset($bits), $repeats)) {
3822 $repeat = array_shift($bits);
3825 $attachment = self::NULL_VALUE;
3826 if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
3827 // scroll , fixed, inherit
3828 $attachment = array_shift($bits);
3831 $position = self::NULL_VALUE;
3832 if (count($bits) > 0) {
3833 $widthbits = array();
3834 foreach ($bits as $bit) {
3835 if (in_array($bit, $positions) || css_is_width($bit)) {
3836 $widthbits[] = $bit;
3837 } else {
3838 $unknownbits[] = $bit;
3841 if (count($widthbits)) {
3842 $position = join(' ', $widthbits);
3846 if (count($unknownbits)) {
3847 foreach ($unknownbits as $bit) {
3848 $bit = trim($bit);
3849 if ($color === self::NULL_VALUE && css_is_colour($bit)) {
3850 $color = $bit;
3851 } else if ($repeat === self::NULL_VALUE && in_array($bit, $repeats)) {
3852 $repeat = $bit;
3853 } else if ($attachment === self::NULL_VALUE && in_array($bit, $attachments)) {
3854 $attachment = $bit;
3855 } else if ($bit !== '') {
3856 $advanced = css_style_background_advanced::init($bit);
3857 if ($important) {
3858 $advanced->set_important();
3860 $return[] = $advanced;
3865 if ($color === self::NULL_VALUE && $image === self::NULL_VALUE && $repeat === self::NULL_VALUE && $attachment === self::NULL_VALUE && $position === self::NULL_VALUE) {
3866 // All primaries are null, return without doing anything else. There may be advanced madness there.
3867 return $return;
3870 $return[] = new css_style_backgroundcolor('background-color', $color);
3871 $return[] = new css_style_backgroundimage('background-image', $image);
3872 $return[] = new css_style_backgroundrepeat('background-repeat', $repeat);
3873 $return[] = new css_style_backgroundattachment('background-attachment', $attachment);
3874 $return[] = new css_style_backgroundposition('background-position', $position);
3876 if ($important) {
3877 foreach ($return as $style) {
3878 $style->set_important();
3882 return $return;
3886 * Static helper method to switch in bracket replacements
3888 * @param string $value
3889 * @param array $placeholders
3890 * @return string
3892 protected static function replace_bracket_placeholders($value, array $placeholders) {
3893 while (preg_match('/##BRACKET-\d+##/', $value, $matches)) {
3894 $value = str_replace($matches[0], $placeholders[$matches[0]], $value);
3896 return $value;
3900 * Consolidates background styles into a single background style
3902 * @param array $styles Consolidates the provided array of background styles
3903 * @return array Consolidated optimised background styles
3905 public static function consolidate(array $styles) {
3907 if (empty($styles)) {
3908 return $styles;
3911 $color = null;
3912 $image = null;
3913 $repeat = null;
3914 $attachment = null;
3915 $position = null;
3916 $size = null;
3917 $origin = null;
3918 $clip = null;
3920 $someimportant = false;
3921 $allimportant = null;
3922 foreach ($styles as $style) {
3923 if ($style instanceof css_style_backgroundimage_advanced) {
3924 continue;
3926 if ($style->is_important()) {
3927 $someimportant = true;
3928 if ($allimportant === null) {
3929 $allimportant = true;
3931 } else if ($allimportant === true) {
3932 $allimportant = false;
3936 $organisedstyles = array();
3937 $advancedstyles = array();
3938 $importantstyles = array();
3939 foreach ($styles as $style) {
3940 if ($style instanceof css_style_backgroundimage_advanced) {
3941 $advancedstyles[] = $style;
3942 continue;
3944 if ($someimportant && !$allimportant && $style->is_important()) {
3945 $importantstyles[] = $style;
3946 continue;
3948 $organisedstyles[$style->get_name()] = $style;
3949 switch ($style->get_name()) {
3950 case 'background-color' :
3951 $color = css_style_color::shrink_value($style->get_value(false));
3952 break;
3953 case 'background-image' :
3954 $image = $style->get_value(false);
3955 break;
3956 case 'background-repeat' :
3957 $repeat = $style->get_value(false);
3958 break;
3959 case 'background-attachment' :
3960 $attachment = $style->get_value(false);
3961 break;
3962 case 'background-position' :
3963 $position = $style->get_value(false);
3964 break;
3965 case 'background-clip' :
3966 $clip = $style->get_value();
3967 break;
3968 case 'background-origin' :
3969 $origin = $style->get_value();
3970 break;
3971 case 'background-size' :
3972 $size = $style->get_value();
3973 break;
3977 $consolidatetosingle = array();
3978 if (!is_null($color) && !is_null($image) && !is_null($repeat) && !is_null($attachment) && !is_null($position)) {
3979 // We can use the shorthand background-style!
3980 if (!$organisedstyles['background-color']->is_special_empty_value()) {
3981 $consolidatetosingle[] = $color;
3983 if (!$organisedstyles['background-image']->is_special_empty_value()) {
3984 $consolidatetosingle[] = $image;
3986 if (!$organisedstyles['background-repeat']->is_special_empty_value()) {
3987 $consolidatetosingle[] = $repeat;
3989 if (!$organisedstyles['background-attachment']->is_special_empty_value()) {
3990 $consolidatetosingle[] = $attachment;
3992 if (!$organisedstyles['background-position']->is_special_empty_value()) {
3993 $consolidatetosingle[] = $position;
3995 // Reset them all to null so we don't use them again.
3996 $color = null;
3997 $image = null;
3998 $repeat = null;
3999 $attachment = null;
4000 $position = null;
4003 $return = array();
4004 // Single background style needs to come first;
4005 if (count($consolidatetosingle) > 0) {
4006 $returnstyle = new css_style_background('background', join(' ', $consolidatetosingle));
4007 if ($allimportant) {
4008 $returnstyle->set_important();
4010 $return[] = $returnstyle;
4012 foreach ($styles as $style) {
4013 $value = null;
4014 switch ($style->get_name()) {
4015 case 'background-color' : $value = $color; break;
4016 case 'background-image' : $value = $image; break;
4017 case 'background-repeat' : $value = $repeat; break;
4018 case 'background-attachment' : $value = $attachment; break;
4019 case 'background-position' : $value = $position; break;
4020 case 'background-clip' : $value = $clip; break;
4021 case 'background-origin' : $value = $origin; break;
4022 case 'background-size' : $value = $size; break;
4024 if (!is_null($value)) {
4025 $return[] = $style;
4028 $return = array_merge($return, $importantstyles, $advancedstyles);
4029 return $return;
4034 * A advanced background style that allows multiple values to preserve unknown entities
4036 * @package core
4037 * @category css
4038 * @copyright 2012 Sam Hemelryk
4039 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4041 class css_style_background_advanced extends css_style_generic {
4043 * Creates a new background colour style
4045 * @param string $value The value of the style
4046 * @return css_style_backgroundimage
4048 public static function init($value) {
4049 $value = preg_replace('#\s+#', ' ', $value);
4050 return new css_style_background_advanced('background', $value);
4054 * Returns true because the advanced background image supports multiple values.
4055 * e.g. -webkit-linear-gradient and -moz-linear-gradient.
4057 * @return boolean
4059 public function allows_multiple_values() {
4060 return true;
4065 * A background colour style.
4067 * Based upon the colour style.
4069 * @package core
4070 * @category css
4071 * @copyright 2012 Sam Hemelryk
4072 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4074 class css_style_backgroundcolor extends css_style_color {
4077 * Creates a new background colour style
4079 * @param string $value The value of the style
4080 * @return css_style_backgroundcolor
4082 public static function init($value) {
4083 return new css_style_backgroundcolor('background-color', $value);
4087 * css_style_backgroundcolor consolidates to css_style_background
4089 * @return string
4091 public function consolidate_to() {
4092 return 'background';
4096 * Returns true if the value for this style is the special null value.
4098 * This occurs if the shorthand background property was used but no proper value
4099 * was specified for this style.
4100 * This leads to a null value being used unless otherwise overridden.
4102 * @return bool
4104 public function is_special_empty_value() {
4105 return ($this->value === self::NULL_VALUE);
4109 * Returns true if the value for this style is valid
4110 * @return bool
4112 public function is_valid() {
4113 return $this->is_special_empty_value() || parent::is_valid();
4118 * A background image style.
4120 * @package core
4121 * @category css
4122 * @copyright 2012 Sam Hemelryk
4123 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4125 class css_style_backgroundimage extends css_style_generic {
4128 * Creates a new background image style
4130 * @param string $value The value of the style
4131 * @return css_style_backgroundimage
4133 public static function init($value) {
4134 if (!preg_match('#^\s*(none|inherit|url\()#i', $value)) {
4135 return css_style_backgroundimage_advanced::init($value);
4137 return new css_style_backgroundimage('background-image', $value);
4141 * Consolidates this style into a single background style
4143 * @return string
4145 public function consolidate_to() {
4146 return 'background';
4150 * Returns true if the value for this style is the special null value.
4152 * This occurs if the shorthand background property was used but no proper value
4153 * was specified for this style.
4154 * This leads to a null value being used unless otherwise overridden.
4156 * @return bool
4158 public function is_special_empty_value() {
4159 return ($this->value === self::NULL_VALUE);
4163 * Returns true if the value for this style is valid
4164 * @return bool
4166 public function is_valid() {
4167 return $this->is_special_empty_value() || parent::is_valid();
4172 * A background image style that supports mulitple values and masquerades as a background-image
4174 * @package core
4175 * @category css
4176 * @copyright 2012 Sam Hemelryk
4177 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4179 class css_style_backgroundimage_advanced extends css_style_generic {
4181 * Creates a new background colour style
4183 * @param string $value The value of the style
4184 * @return css_style_backgroundimage
4186 public static function init($value) {
4187 $value = preg_replace('#\s+#', ' ', $value);
4188 return new css_style_backgroundimage_advanced('background-image', $value);
4192 * Returns true because the advanced background image supports multiple values.
4193 * e.g. -webkit-linear-gradient and -moz-linear-gradient.
4195 * @return boolean
4197 public function allows_multiple_values() {
4198 return true;
4203 * A background repeat style.
4205 * @package core
4206 * @category css
4207 * @copyright 2012 Sam Hemelryk
4208 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4210 class css_style_backgroundrepeat extends css_style_generic {
4213 * Creates a new background colour style
4215 * @param string $value The value of the style
4216 * @return css_style_backgroundrepeat
4218 public static function init($value) {
4219 return new css_style_backgroundrepeat('background-repeat', $value);
4223 * Consolidates this style into a single background style
4225 * @return string
4227 public function consolidate_to() {
4228 return 'background';
4232 * Returns true if the value for this style is the special null value.
4234 * This occurs if the shorthand background property was used but no proper value
4235 * was specified for this style.
4236 * This leads to a null value being used unless otherwise overridden.
4238 * @return bool
4240 public function is_special_empty_value() {
4241 return ($this->value === self::NULL_VALUE);
4245 * Returns true if the value for this style is valid
4246 * @return bool
4248 public function is_valid() {
4249 return $this->is_special_empty_value() || parent::is_valid();
4254 * A background attachment style.
4256 * @package core
4257 * @category css
4258 * @copyright 2012 Sam Hemelryk
4259 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4261 class css_style_backgroundattachment extends css_style_generic {
4264 * Creates a new background colour style
4266 * @param string $value The value of the style
4267 * @return css_style_backgroundattachment
4269 public static function init($value) {
4270 return new css_style_backgroundattachment('background-attachment', $value);
4274 * Consolidates this style into a single background style
4276 * @return string
4278 public function consolidate_to() {
4279 return 'background';
4283 * Returns true if the value for this style is the special null value.
4285 * This occurs if the shorthand background property was used but no proper value
4286 * was specified for this style.
4287 * This leads to a null value being used unless otherwise overridden.
4289 * @return bool
4291 public function is_special_empty_value() {
4292 return ($this->value === self::NULL_VALUE);
4296 * Returns true if the value for this style is valid
4297 * @return bool
4299 public function is_valid() {
4300 return $this->is_special_empty_value() || parent::is_valid();
4305 * A background position style.
4307 * @package core
4308 * @category css
4309 * @copyright 2012 Sam Hemelryk
4310 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4312 class css_style_backgroundposition extends css_style_generic {
4315 * Creates a new background colour style
4317 * @param string $value The value of the style
4318 * @return css_style_backgroundposition
4320 public static function init($value) {
4321 return new css_style_backgroundposition('background-position', $value);
4325 * Consolidates this style into a single background style
4327 * @return string
4329 public function consolidate_to() {
4330 return 'background';
4334 * Returns true if the value for this style is the special null value.
4336 * This occurs if the shorthand background property was used but no proper value
4337 * was specified for this style.
4338 * This leads to a null value being used unless otherwise overridden.
4340 * @return bool
4342 public function is_special_empty_value() {
4343 return ($this->value === self::NULL_VALUE);
4347 * Returns true if the value for this style is valid
4348 * @return bool
4350 public function is_valid() {
4351 return $this->is_special_empty_value() || parent::is_valid();
4356 * A background size style.
4358 * @package core
4359 * @category css
4360 * @copyright 2012 Sam Hemelryk
4361 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4363 class css_style_backgroundsize extends css_style_generic {
4366 * Creates a new background size style
4368 * @param string $value The value of the style
4369 * @return css_style_backgroundposition
4371 public static function init($value) {
4372 return new css_style_backgroundsize('background-size', $value);
4376 * Consolidates this style into a single background style
4378 * @return string
4380 public function consolidate_to() {
4381 return 'background';
4386 * A background clip style.
4388 * @package core
4389 * @category css
4390 * @copyright 2012 Sam Hemelryk
4391 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4393 class css_style_backgroundclip extends css_style_generic {
4396 * Creates a new background clip style
4398 * @param string $value The value of the style
4399 * @return css_style_backgroundposition
4401 public static function init($value) {
4402 return new css_style_backgroundclip('background-clip', $value);
4406 * Consolidates this style into a single background style
4408 * @return string
4410 public function consolidate_to() {
4411 return 'background';
4416 * A background origin style.
4418 * @package core
4419 * @category css
4420 * @copyright 2012 Sam Hemelryk
4421 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4423 class css_style_backgroundorigin extends css_style_generic {
4426 * Creates a new background origin style
4428 * @param string $value The value of the style
4429 * @return css_style_backgroundposition
4431 public static function init($value) {
4432 return new css_style_backgroundorigin('background-origin', $value);
4436 * Consolidates this style into a single background style
4438 * @return string
4440 public function consolidate_to() {
4441 return 'background';
4446 * A padding style.
4448 * @package core
4449 * @category css
4450 * @copyright 2012 Sam Hemelryk
4451 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4453 class css_style_padding extends css_style_width {
4456 * Initialises this padding style into several individual padding styles
4458 * @param string $value The value fo the style
4459 * @return array An array of padding styles
4461 public static function init($value) {
4462 $important = '';
4463 if (strpos($value, '!important') !== false) {
4464 $important = ' !important';
4465 $value = str_replace('!important', '', $value);
4468 $value = preg_replace('#\s+#', ' ', trim($value));
4469 $bits = explode(' ', $value, 4);
4471 $top = $right = $bottom = $left = null;
4472 if (count($bits) > 0) {
4473 $top = $right = $bottom = $left = array_shift($bits);
4475 if (count($bits) > 0) {
4476 $right = $left = array_shift($bits);
4478 if (count($bits) > 0) {
4479 $bottom = array_shift($bits);
4481 if (count($bits) > 0) {
4482 $left = array_shift($bits);
4484 return array(
4485 new css_style_paddingtop('padding-top', $top.$important),
4486 new css_style_paddingright('padding-right', $right.$important),
4487 new css_style_paddingbottom('padding-bottom', $bottom.$important),
4488 new css_style_paddingleft('padding-left', $left.$important)
4493 * Consolidates several padding styles into a single style.
4495 * @param array $styles Array of padding styles
4496 * @return array Optimised+consolidated array of padding styles
4498 public static function consolidate(array $styles) {
4499 if (count($styles) != 4) {
4500 return $styles;
4503 $someimportant = false;
4504 $allimportant = null;
4505 $notimportantequal = null;
4506 $firstvalue = null;
4507 foreach ($styles as $style) {
4508 if ($style->is_important()) {
4509 $someimportant = true;
4510 if ($allimportant === null) {
4511 $allimportant = true;
4513 } else {
4514 if ($allimportant === true) {
4515 $allimportant = false;
4517 if ($firstvalue == null) {
4518 $firstvalue = $style->get_value(false);
4519 $notimportantequal = true;
4520 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
4521 $notimportantequal = false;
4526 if ($someimportant && !$allimportant && !$notimportantequal) {
4527 return $styles;
4530 if ($someimportant && !$allimportant && $notimportantequal) {
4531 $return = array(
4532 new css_style_padding('padding', $firstvalue)
4534 foreach ($styles as $style) {
4535 if ($style->is_important()) {
4536 $return[] = $style;
4539 return $return;
4540 } else {
4541 $top = null;
4542 $right = null;
4543 $bottom = null;
4544 $left = null;
4545 foreach ($styles as $style) {
4546 switch ($style->get_name()) {
4547 case 'padding-top' : $top = $style->get_value(false);break;
4548 case 'padding-right' : $right = $style->get_value(false);break;
4549 case 'padding-bottom' : $bottom = $style->get_value(false);break;
4550 case 'padding-left' : $left = $style->get_value(false);break;
4553 if ($top == $bottom && $left == $right) {
4554 if ($top == $left) {
4555 $returnstyle = new css_style_padding('padding', $top);
4556 } else {
4557 $returnstyle = new css_style_padding('padding', "{$top} {$left}");
4559 } else if ($left == $right) {
4560 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom}");
4561 } else {
4562 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}");
4564 if ($allimportant) {
4565 $returnstyle->set_important();
4567 return array($returnstyle);
4573 * A padding top style.
4575 * @package core
4576 * @category css
4577 * @copyright 2012 Sam Hemelryk
4578 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4580 class css_style_paddingtop extends css_style_padding {
4583 * Initialises this style
4585 * @param string $value The value of the style
4586 * @return css_style_paddingtop
4588 public static function init($value) {
4589 return new css_style_paddingtop('padding-top', $value);
4593 * Consolidates this style into a single padding style
4595 * @return string
4597 public function consolidate_to() {
4598 return 'padding';
4603 * A padding right style.
4605 * @package core
4606 * @category css
4607 * @copyright 2012 Sam Hemelryk
4608 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4610 class css_style_paddingright extends css_style_padding {
4613 * Initialises this style
4615 * @param string $value The value of the style
4616 * @return css_style_paddingright
4618 public static function init($value) {
4619 return new css_style_paddingright('padding-right', $value);
4623 * Consolidates this style into a single padding style
4625 * @return string
4627 public function consolidate_to() {
4628 return 'padding';
4633 * A padding bottom style.
4635 * @package core
4636 * @category css
4637 * @copyright 2012 Sam Hemelryk
4638 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4640 class css_style_paddingbottom extends css_style_padding {
4643 * Initialises this style
4645 * @param string $value The value of the style
4646 * @return css_style_paddingbottom
4648 public static function init($value) {
4649 return new css_style_paddingbottom('padding-bottom', $value);
4653 * Consolidates this style into a single padding style
4655 * @return string
4657 public function consolidate_to() {
4658 return 'padding';
4663 * A padding left style.
4665 * @package core
4666 * @category css
4667 * @copyright 2012 Sam Hemelryk
4668 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4670 class css_style_paddingleft extends css_style_padding {
4673 * Initialises this style
4675 * @param string $value The value of the style
4676 * @return css_style_paddingleft
4678 public static function init($value) {
4679 return new css_style_paddingleft('padding-left', $value);
4683 * Consolidates this style into a single padding style
4685 * @return string
4687 public function consolidate_to() {
4688 return 'padding';
4693 * A cursor style.
4695 * @package core
4696 * @category css
4697 * @copyright 2012 Sam Hemelryk
4698 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4700 class css_style_cursor extends css_style_generic {
4702 * Initialises a new cursor style
4703 * @param string $value
4704 * @return css_style_cursor
4706 public static function init($value) {
4707 return new css_style_cursor('cursor', $value);
4710 * Cleans the given value and returns it.
4712 * @param string $value
4713 * @return string
4715 protected function clean_value($value) {
4716 // Allowed values for the cursor style
4717 $allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
4718 'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
4719 // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough
4720 if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
4721 $this->set_error('Invalid or unexpected cursor value specified: '.$value);
4723 return trim($value);
4728 * A vertical alignment style.
4730 * @package core
4731 * @category css
4732 * @copyright 2012 Sam Hemelryk
4733 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4735 class css_style_verticalalign extends css_style_generic {
4737 * Initialises a new vertical alignment style
4738 * @param string $value
4739 * @return css_style_verticalalign
4741 public static function init($value) {
4742 return new css_style_verticalalign('vertical-align', $value);
4745 * Cleans the given value and returns it.
4747 * @param string $value
4748 * @return string
4750 protected function clean_value($value) {
4751 $allowed = array('baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom', 'inherit');
4752 if (!css_is_width($value) && !in_array($value, $allowed)) {
4753 $this->set_error('Invalid vertical-align value specified: '.$value);
4755 return trim($value);
4760 * A float style.
4762 * @package core
4763 * @category css
4764 * @copyright 2012 Sam Hemelryk
4765 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4767 class css_style_float extends css_style_generic {
4769 * Initialises a new float style
4770 * @param string $value
4771 * @return css_style_float
4773 public static function init($value) {
4774 return new css_style_float('float', $value);
4777 * Cleans the given value and returns it.
4779 * @param string $value
4780 * @return string
4782 protected function clean_value($value) {
4783 $allowed = array('left', 'right', 'none', 'inherit');
4784 if (!css_is_width($value) && !in_array($value, $allowed)) {
4785 $this->set_error('Invalid float value specified: '.$value);
4787 return trim($value);