2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
18 * This file contains CSS related class, and function for the CSS optimiser
20 * Please see the {@link css_optimiser} class for greater detail.
23 * @subpackage cssoptimiser
24 * @copyright 2012 Sam Hemelryk
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 defined('MOODLE_INTERNAL') ||
die();
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.
38 * @param bool $chunk If set to true these files will be chunked to ensure
39 * that no one file contains more than 4095 selectors.
40 * @param string $chunkurl If the CSS is be chunked then we need to know the URL
41 * to use for the chunked files.
43 function css_store_css(theme_config
$theme, $csspath, array $cssfiles, $chunk = false, $chunkurl = null) {
47 foreach ($cssfiles as $file) {
48 $css .= file_get_contents($file)."\n";
51 // Check if both the CSS optimiser is enabled and the theme supports it.
52 if (!empty($CFG->enablecssoptimiser
) && $theme->supportscssoptimisation
) {
53 // This is an experimental feature introduced in Moodle 2.3
54 // The CSS optimiser organises the CSS in order to reduce the overall number
55 // of rules and styles being sent to the client. It does this by collating
56 // the CSS before it is cached removing excess styles and rules and stripping
57 // out any extraneous content such as comments and empty rules.
58 $optimiser = new css_optimiser
;
59 $css = $theme->post_process($css);
60 $css = $optimiser->process($css);
62 // If cssoptimisestats is set then stats from the optimisation are collected
63 // and output at the beginning of the CSS.
64 if (!empty($CFG->cssoptimiserstats
)) {
65 $css = $optimiser->output_stats_css().$css;
68 // This is the default behaviour.
69 // The cssoptimise setting was introduced in Moodle 2.3 and will hopefully
70 // in the future be changed from an experimental setting to the default.
71 // The css_minify_css will method will use the Minify library remove
72 // comments, additional whitespace and other minor measures to reduce the
73 // the overall CSS being sent.
74 // However it has the distinct disadvantage of having to minify the CSS
75 // before running the post process functions. Potentially things may break
76 // here if theme designers try to push things with CSS post processing.
77 $css = $theme->post_process($css);
78 $css = core_minify
::css($css);
82 if (!file_exists(dirname($csspath))) {
83 @mkdir
(dirname($csspath), $CFG->directorypermissions
, true);
86 // Prevent serving of incomplete file from concurrent request,
87 // the rename() should be more atomic than fwrite().
88 ignore_user_abort(true);
90 // First up write out the single file for all those using decent browsers.
91 css_write_file($csspath, $css);
94 // If we need to chunk the CSS for browsers that are sub-par.
95 $css = css_chunk_by_selector_count($css, $chunkurl);
98 foreach ($css as $content) {
99 if ($count === $files) {
100 // If there is more than one file and this IS the last file.
101 $filename = preg_replace('#\.css$#', '.0.css', $csspath);
103 // If there is more than one file and this is not the last file.
104 $filename = preg_replace('#\.css$#', '.'.$count.'.css', $csspath);
107 css_write_file($filename, $content);
111 ignore_user_abort(false);
112 if (connection_aborted()) {
120 * @param string $filename
121 * @param string $content
123 function css_write_file($filename, $content) {
125 if ($fp = fopen($filename.'.tmp', 'xb')) {
126 fwrite($fp, $content);
128 rename($filename.'.tmp', $filename);
129 @chmod
($filename, $CFG->filepermissions
);
130 @unlink
($filename.'.tmp'); // Just in case anything fails.
135 * Takes CSS and chunks it if the number of selectors within it exceeds $maxselectors.
137 * @param string $css The CSS to chunk.
138 * @param string $importurl The URL to use for import statements.
139 * @param int $maxselectors The number of selectors to limit a chunk to.
140 * @param int $buffer The buffer size to use when chunking. You shouldn't need to reduce this
141 * unless you are lowering the maximum selectors.
142 * @return array An array of CSS chunks.
144 function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $buffer = 50) {
145 // Check if we need to chunk this CSS file.
146 $count = substr_count($css, ',') +
substr_count($css, '{');
147 if ($count < $maxselectors) {
148 // The number of selectors is less then the max - we're fine.
153 // Split the CSS by array, making sure to save the delimiter in the process.
154 $parts = preg_split('#([,\}])#', $css, null, PREG_SPLIT_DELIM_CAPTURE + PREG_SPLIT_NO_EMPTY
);
155 // We need to chunk the array. Each delimiter is stored separately so we multiple by 2.
156 // We also subtract 100 to give us a small buffer just in case.
157 $parts = array_chunk($parts, $maxselectors * 2 - $buffer * 2);
159 $partcount = count($parts);
160 foreach ($parts as $key => $chunk) {
161 if (end($chunk) === ',') {
162 // Damn last element was a comma.
163 // Pretty much the only way to deal with this is to take the styles from the end of the
164 // comma separated chain of selectors and apply it to the last selector we have here in place
166 // Unit tests are essential for making sure this works.
169 while ($styles === false && $i < ($partcount - 1)) {
171 $nextpart = $parts[$i];
172 foreach ($nextpart as $style) {
173 if (strpos($style, '{') !== false) {
174 $styles = preg_replace('#^[^\{]+#', '', $style);
179 if ($styles === false) {
180 $styles = '/** Error chunking CSS **/';
185 array_push($chunk, $styles);
187 $css[] = join('', $chunk);
189 // The array $css now contains CSS split into perfect sized chunks.
190 // Import statements can only appear at the very top of a CSS file.
191 // Imported sheets are applied in the the order they are imported and
192 // are followed by the contents of the CSS.
193 // This is terrible for performance.
194 // It means we must put the import statements at the top of the last chunk
195 // to ensure that things are always applied in the correct order.
196 // This way the chunked files are included in the order they were chunked
197 // followed by the contents of the final chunk in the actual sheet.
199 $slashargs = strpos($importurl, '.php?') === false;
200 $parts = count($css);
201 for ($i = 1; $i < $parts; $i++
) {
203 $importcss .= "@import url({$importurl}/chunk{$i});\n";
205 $importcss .= "@import url({$importurl}&chunk={$i});\n";
208 $importcss .= end($css);
209 $css[key($css)] = $importcss;
215 * Sends a cached CSS file
217 * This function sends the cached CSS file. Remember it is generated on the first
218 * request, then optimised/minified, and finally cached for serving.
220 * @param string $csspath The path to the CSS file we want to serve.
221 * @param string $etag The revision to make sure we utilise any caches.
223 function css_send_cached_css($csspath, $etag) {
224 // 60 days only - the revision may get incremented quite often.
225 $lifetime = 60*60*24*60;
227 header('Etag: "'.$etag.'"');
228 header('Content-Disposition: inline; filename="styles.php"');
229 header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
230 header('Expires: '. gmdate('D, d M Y H:i:s', time() +
$lifetime) .' GMT');
232 header('Cache-Control: public, max-age='.$lifetime);
233 header('Accept-Ranges: none');
234 header('Content-Type: text/css; charset=utf-8');
235 if (!min_enable_zlib_compression()) {
236 header('Content-Length: '.filesize($csspath));
244 * Sends CSS directly without caching it.
246 * This function takes a raw CSS string, optimises it if required, and then
248 * Turning both themedesignermode and CSS optimiser on at the same time is awful
249 * for performance because of the optimiser running here. However it was done so
250 * that theme designers could utilise the optimised output during development to
251 * help them optimise their CSS... not that they should write lazy CSS.
255 function css_send_uncached_css($css) {
256 header('Content-Disposition: inline; filename="styles_debug.php"');
257 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
258 header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME
) .' GMT');
260 header('Accept-Ranges: none');
261 header('Content-Type: text/css; charset=utf-8');
263 if (is_array($css)) {
264 $css = implode("\n\n", $css);
271 * Send file not modified headers
273 * @param int $lastmodified
274 * @param string $etag
276 function css_send_unmodified($lastmodified, $etag) {
277 // 60 days only - the revision may get incremented quite often.
278 $lifetime = 60*60*24*60;
279 header('HTTP/1.1 304 Not Modified');
280 header('Expires: '. gmdate('D, d M Y H:i:s', time() +
$lifetime) .' GMT');
281 header('Cache-Control: public, max-age='.$lifetime);
282 header('Content-Type: text/css; charset=utf-8');
283 header('Etag: "'.$etag.'"');
285 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
291 * Sends a 404 message about CSS not being found.
293 function css_send_css_not_found() {
294 header('HTTP/1.0 404 not found');
295 die('CSS was not found, sorry.');
299 * Determines if the given value is a valid CSS colour.
301 * A CSS colour can be one of the following:
302 * - Hex colour: #AA66BB
303 * - RGB colour: rgb(0-255, 0-255, 0-255)
304 * - RGBA colour: rgba(0-255, 0-255, 0-255, 0-1)
305 * - HSL colour: hsl(0-360, 0-100%, 0-100%)
306 * - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1)
308 * Or a recognised browser colour mapping {@link css_optimiser::$htmlcolours}
310 * @param string $value The colour value to check
313 function css_is_colour($value) {
314 $value = trim($value);
316 $hex = '/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/';
317 $rgb = '#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i';
318 $rgba = '#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
319 $hsl = '#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i';
320 $hsla = '#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
322 if (in_array(strtolower($value), array('inherit'))) {
324 } else if (preg_match($hex, $value)) {
326 } else if (in_array(strtolower($value), array_keys(css_optimiser
::$htmlcolours))) {
328 } else if (preg_match($rgb, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
329 // It is an RGB colour.
331 } else if (preg_match($rgba, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
332 // It is an RGBA colour.
334 } else if (preg_match($hsl, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
335 // It is an HSL colour.
337 } else if (preg_match($hsla, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
338 // It is an HSLA colour.
341 // Doesn't look like a colour.
346 * Returns true is the passed value looks like a CSS width.
347 * In order to pass this test the value must be purely numerical or end with a
348 * valid CSS unit term.
350 * @param string|int $value
353 function css_is_width($value) {
354 $value = trim($value);
355 if (in_array(strtolower($value), array('auto', 'inherit'))) {
358 if ((string)$value === '0' ||
preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) {
365 * A simple sorting function to sort two array values on the number of items they contain
371 function css_sort_by_count(array $a, array $b) {
377 return ($a > $b) ?
-1 : 1;
381 * A basic CSS optimiser that strips out unwanted things and then processes CSS organising and cleaning styles.
383 * This CSS optimiser works by reading through a CSS string one character at a
384 * time and building an object structure of the CSS.
385 * As part of that processing styles are expanded out as much as they can be to
386 * ensure we collect all mappings, at the end of the processing those styles are
387 * then combined into an optimised form to keep them as short as possible.
390 * @subpackage cssoptimiser
391 * @copyright 2012 Sam Hemelryk
392 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
394 class css_optimiser
{
397 * Used when the processor is about to start processing.
398 * Processing states. Used internally.
400 const PROCESSING_START
= 0;
403 * Used when the processor is currently processing a selector.
404 * Processing states. Used internally.
406 const PROCESSING_SELECTORS
= 0;
409 * Used when the processor is currently processing a style.
410 * Processing states. Used internally.
412 const PROCESSING_STYLES
= 1;
415 * Used when the processor is currently processing a comment.
416 * Processing states. Used internally.
418 const PROCESSING_COMMENT
= 2;
421 * Used when the processor is currently processing an @ rule.
422 * Processing states. Used internally.
424 const PROCESSING_ATRULE
= 3;
427 * The raw string length before optimisation.
428 * Stats variables set during and after processing
431 protected $rawstrlen = 0;
434 * The number of comments that were removed during optimisation.
435 * Stats variables set during and after processing
438 protected $commentsincss = 0;
441 * The number of rules in the CSS before optimisation.
442 * Stats variables set during and after processing
445 protected $rawrules = 0;
448 * The number of selectors using in CSS rules before optimisation.
449 * Stats variables set during and after processing
452 protected $rawselectors = 0;
455 * The string length after optimisation.
456 * Stats variables set during and after processing
459 protected $optimisedstrlen = 0;
462 * The number of rules after optimisation.
463 * Stats variables set during and after processing
466 protected $optimisedrules = 0;
469 * The number of selectors used in rules after optimisation.
470 * Stats variables set during and after processing
473 protected $optimisedselectors = 0;
476 * The start time of the optimisation.
477 * Stats variables set during and after processing
480 protected $timestart = 0;
483 * The end time of the optimisation.
484 * Stats variables set during and after processing
487 protected $timecomplete = 0;
490 * Will be set to any errors that may have occured during processing.
491 * This is updated only at the end of processing NOT during.
495 protected $errors = array();
498 * Processes incoming CSS optimising it and then returning it.
500 * @param string $css The raw CSS to optimise
501 * @return string The optimised CSS
503 public function process($css) {
504 // Easiest win there is.
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.';
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
);
529 'all' => new css_media()
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 /* @var css_media $currentmedia */
544 $currentmedia = $medias['all'];
545 $currentatrule = null;
546 $suspectatrule = false;
551 // Next we are going to iterate over every single character in $css.
552 // This is why we removed line breaks and comments!
553 for ($i = 0; $i < $this->rawstrlen
; $i++
) {
555 $char = substr($css, $i, 1);
556 if ($char == '@' && $buffer == '') {
557 $suspectatrule = true;
559 switch ($currentprocess) {
560 // Start processing an @ rule e.g. @media, @page, @keyframes.
561 case self
::PROCESSING_ATRULE
:
566 if ($currentatrule == 'import') {
567 $imports[] = $buffer;
568 $currentprocess = self
::PROCESSING_SELECTORS
;
569 } else if ($currentatrule == 'charset') {
571 $currentprocess = self
::PROCESSING_SELECTORS
;
574 if ($currentatrule !== 'media') {
576 $currentatrule = false;
578 // Continue 1: The switch processing chars
579 // Continue 2: The switch processing the state
580 // Continue 3: The for loop.
583 $regexmediabasic = '#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#';
584 $regexadvmedia = '#\s*@media\s*([^{]+)#';
585 $regexkeyframes = '#@((\-moz\-|\-webkit\-|\-ms\-|\-o\-)?keyframes)\s*([^\s]+)#';
587 if ($currentatrule == 'media' && preg_match($regexmediabasic, $buffer, $matches)) {
588 // Basic media declaration.
589 $mediatypes = str_replace(' ', '', $matches[1]);
590 if (!array_key_exists($mediatypes, $medias)) {
591 $medias[$mediatypes] = new css_media($mediatypes);
593 $currentmedia = $medias[$mediatypes];
594 $currentprocess = self
::PROCESSING_SELECTORS
;
596 } else if ($currentatrule == 'media' && preg_match($regexadvmedia, $buffer, $matches)) {
597 // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/.
598 $mediatypes = $matches[1];
599 $hash = md5($mediatypes);
600 $medias[$hash] = new css_media($mediatypes);
601 $currentmedia = $medias[$hash];
602 $currentprocess = self
::PROCESSING_SELECTORS
;
604 } else if ($currentatrule == 'keyframes' && preg_match($regexkeyframes, $buffer, $matches)) {
605 // Keyframes declaration, we treat it exactly like a @media declaration except we don't allow
606 // them to be overridden to ensure we don't mess anything up. (means we keep everything in order).
607 $keyframefor = $matches[1];
608 $keyframename = $matches[3];
609 $keyframe = new css_keyframe($keyframefor, $keyframename);
610 $keyframes[] = $keyframe;
611 $currentmedia = $keyframe;
612 $currentprocess = self
::PROCESSING_SELECTORS
;
615 // Continue 1: The switch processing chars
616 // Continue 2: The switch processing the state
617 // Continue 3: The for loop.
621 // Start processing selectors.
622 case self
::PROCESSING_START
:
623 case self
::PROCESSING_SELECTORS
:
624 $regexatrule = '#@(media|import|charset|(\-moz\-|\-webkit\-|\-ms\-|\-o\-)?(keyframes))\s*#';
629 // Continue 1: The switch processing chars
630 // Continue 2: The switch processing the state
631 // Continue 3: The for loop.
636 // Continue 1: The switch processing chars
637 // Continue 2: The switch processing the state
638 // Continue 3: The for loop.
642 // Continue 1: The switch processing chars
643 // Continue 2: The switch processing the state
644 // Continue 3: The for loop.
647 if (!empty($buffer)) {
648 // Check for known @ rules.
649 if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
650 $currentatrule = (!empty($matches[3]))?
$matches[3]:$matches[1];
651 $currentprocess = self
::PROCESSING_ATRULE
;
654 $currentselector->add($buffer);
658 $suspectatrule = false;
659 // Continue 1: The switch processing chars
660 // Continue 2: The switch processing the state
661 // Continue 3: The for loop.
665 // Continue 1: The switch processing chars
666 // Continue 2: The switch processing the state
667 // Continue 3: The for loop.
670 // Check for known @ rules.
671 if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
672 // Ahh we've been in an @rule, lets rewind one and have the @rule case process this.
673 $currentatrule = (!empty($matches[3]))?
$matches[3]:$matches[1];
674 $currentprocess = self
::PROCESSING_ATRULE
;
676 $suspectatrule = false;
677 // Continue 1: The switch processing chars
678 // Continue 2: The switch processing the state
679 // Continue 3: The for loop.
682 if ($buffer !== '') {
683 $currentselector->add($buffer);
685 $currentrule->add_selector($currentselector);
686 $currentselector = css_selector
::init();
687 $currentprocess = self
::PROCESSING_STYLES
;
690 // Continue 1: The switch processing chars
691 // Continue 2: The switch processing the state
692 // Continue 3: The for loop.
696 // Continue 1: The switch processing chars
697 // Continue 2: The switch processing the state
698 // Continue 3: The for loop.
701 if ($currentatrule == 'media') {
702 $currentmedia = $medias['all'];
703 $currentatrule = false;
705 } else if (strpos($currentatrule, 'keyframes') !== false) {
706 $currentmedia = $medias['all'];
707 $currentatrule = false;
710 // Continue 1: The switch processing chars
711 // Continue 2: The switch processing the state
712 // Continue 3: The for loop.
716 // Continue 1: The switch processing chars
717 // Continue 2: The switch processing the state
718 // Continue 3: The for loop.
721 $currentselector->add($buffer);
722 $currentrule->add_selector($currentselector);
723 $currentselector = css_selector
::init();
725 // Continue 1: The switch processing chars
726 // Continue 2: The switch processing the state
727 // Continue 3: The for loop.
731 // Start processing styles.
732 case self
::PROCESSING_STYLES
:
733 if ($char == '"' ||
$char == "'") {
734 if ($inquotes === false) {
737 if ($inquotes === $char && $lastchar !== '\\') {
747 if ($inparenthesis) {
749 // Continue 1: The switch processing chars
750 // Continue 2: The switch processing the state
751 // Continue 3: The for loop.
754 $currentrule->add_style($buffer);
757 // Continue 1: The switch processing chars
758 // Continue 2: The switch processing the state
759 // Continue 3: The for loop.
762 $currentrule->add_style($buffer);
763 $this->rawselectors +
= $currentrule->get_selector_count();
765 $currentmedia->add_rule($currentrule);
767 $currentrule = css_rule
::init();
768 $currentprocess = self
::PROCESSING_SELECTORS
;
772 $inparenthesis = false;
773 // Continue 1: The switch processing chars
774 // Continue 2: The switch processing the state
775 // Continue 3: The for loop.
778 $inparenthesis = true;
780 // Continue 1: The switch processing chars
781 // Continue 2: The switch processing the state
782 // Continue 3: The for loop.
785 $inparenthesis = false;
787 // Continue 1: The switch processing chars
788 // Continue 2: The switch processing the state
789 // Continue 3: The for loop.
797 foreach ($medias as $media) {
798 $this->optimise($media);
800 $css = $this->produce_css($charset, $imports, $medias, $keyframes);
802 $this->timecomplete
= microtime(true);
807 * Produces CSS for the given charset, imports, media, and keyframes
808 * @param string $charset
809 * @param array $imports
810 * @param css_media[] $medias
811 * @param css_keyframe[] $keyframes
814 protected function produce_css($charset, array $imports, array $medias, array $keyframes) {
816 if (!empty($charset)) {
817 $imports[] = $charset;
819 if (!empty($imports)) {
820 $css .= implode("\n", $imports);
825 $cssstandard = array();
826 $csskeyframes = array();
828 // Process each media declaration individually.
829 foreach ($medias as $media) {
830 // If this declaration applies to all media types.
831 if (in_array('all', $media->get_types())) {
832 // Collect all rules that represet reset rules and remove them from the media object at the same time.
833 // We do this because we prioritise reset rules to the top of a CSS output. This ensures that they
834 // can't end up out of order because of optimisation.
835 $resetrules = $media->get_reset_rules(true);
836 if (!empty($resetrules)) {
837 $cssreset[] = css_writer
::media('all', $resetrules);
840 // Get the standard cSS.
841 $cssstandard[] = $media->out();
844 // Finally if there are any keyframe declarations process them now.
845 if (count($keyframes) > 0) {
846 foreach ($keyframes as $keyframe) {
847 $this->optimisedrules +
= $keyframe->count_rules();
848 $this->optimisedselectors +
= $keyframe->count_selectors();
849 if ($keyframe->has_errors()) {
850 $this->errors +
= $keyframe->get_errors();
852 $csskeyframes[] = $keyframe->out();
856 // Join it all together.
857 $css .= join('', $cssreset);
858 $css .= join('', $cssstandard);
859 $css .= join('', $csskeyframes);
861 // Record the strlenght of the now optimised CSS.
862 $this->optimisedstrlen
= strlen($css);
864 // Return the now produced CSS.
869 * Optimises the CSS rules within a rule collection of one form or another
871 * @param css_rule_collection $media
872 * @return void This function acts in reference
874 protected function optimise(css_rule_collection
$media) {
875 $media->organise_rules_by_selectors();
876 $this->optimisedrules +
= $media->count_rules();
877 $this->optimisedselectors +
= $media->count_selectors();
878 if ($media->has_errors()) {
879 $this->errors +
= $media->get_errors();
884 * Returns an array of stats from the last processing run
887 public function get_stats() {
889 'timestart' => $this->timestart
,
890 'timecomplete' => $this->timecomplete
,
891 'timetaken' => round($this->timecomplete
- $this->timestart
, 4),
892 'commentsincss' => $this->commentsincss
,
893 'rawstrlen' => $this->rawstrlen
,
894 'rawselectors' => $this->rawselectors
,
895 'rawrules' => $this->rawrules
,
896 'optimisedstrlen' => $this->optimisedstrlen
,
897 'optimisedrules' => $this->optimisedrules
,
898 'optimisedselectors' => $this->optimisedselectors
,
899 'improvementstrlen' => '-',
900 'improvementrules' => '-',
901 'improvementselectors' => '-',
903 // Avoid division by 0 errors by checking we have valid raw values.
904 if ($this->rawstrlen
> 0) {
905 $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen
/ $this->rawstrlen
) * 100, 1).'%';
907 if ($this->rawrules
> 0) {
908 $stats['improvementrules'] = round(100 - ($this->optimisedrules
/ $this->rawrules
) * 100, 1).'%';
910 if ($this->rawselectors
> 0) {
911 $stats['improvementselectors'] = round(100 - ($this->optimisedselectors
/ $this->rawselectors
) * 100, 1).'%';
917 * Returns true if any errors have occured during processing
921 public function has_errors() {
922 return !empty($this->errors
);
926 * Returns an array of errors that have occured
928 * @param bool $clear If set to true the errors will be cleared after being returned.
931 public function get_errors($clear = false) {
932 $errors = $this->errors
;
934 // Reset the error array.
935 $this->errors
= array();
941 * Returns any errors as a string that can be included in CSS.
945 public function output_errors_css() {
946 $computedcss = "/****************************************\n";
947 $computedcss .= " *--- Errors found during processing ----\n";
948 foreach ($this->errors
as $error) {
949 $computedcss .= preg_replace('#^#m', '* ', $error);
951 $computedcss .= " ****************************************/\n\n";
956 * Returns a string to display stats about the last generation within CSS output
960 public function output_stats_css() {
962 $computedcss = "/****************************************\n";
963 $computedcss .= " *------- CSS Optimisation stats --------\n";
965 if ($this->rawstrlen
=== 0) {
966 $computedcss .= " File not processed as it has no content /\n\n";
967 $computedcss .= " ****************************************/\n\n";
969 } else if ($this->rawrules
=== 0) {
970 $computedcss .= " File contained no rules to be processed /\n\n";
971 $computedcss .= " ****************************************/\n\n";
975 $stats = $this->get_stats();
977 $computedcss .= " * ".date('r')."\n";
978 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
979 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
980 $computedcss .= " *--------------- before ----------------\n";
981 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n";
982 $computedcss .= " * {$stats['rawrules']} \t rules read in\n";
983 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n";
984 $computedcss .= " *---------------- after ----------------\n";
985 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n";
986 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n";
987 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n";
988 $computedcss .= " *---------------- stats ----------------\n";
989 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n";
990 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n";
991 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n";
992 $computedcss .= " ****************************************/\n\n";
998 * Resets the stats ready for another fresh processing
1000 public function reset_stats() {
1001 $this->commentsincss
= 0;
1002 $this->optimisedrules
= 0;
1003 $this->optimisedselectors
= 0;
1004 $this->optimisedstrlen
= 0;
1005 $this->rawrules
= 0;
1006 $this->rawselectors
= 0;
1007 $this->rawstrlen
= 0;
1008 $this->timecomplete
= 0;
1009 $this->timestart
= 0;
1013 * An array of the common HTML colours that are supported by most browsers.
1015 * This reference table is used to allow us to unify colours, and will aid
1016 * us in identifying buggy CSS using unsupported colours.
1020 public static $htmlcolours = array(
1021 'aliceblue' => '#F0F8FF',
1022 'antiquewhite' => '#FAEBD7',
1023 'aqua' => '#00FFFF',
1024 'aquamarine' => '#7FFFD4',
1025 'azure' => '#F0FFFF',
1026 'beige' => '#F5F5DC',
1027 'bisque' => '#FFE4C4',
1028 'black' => '#000000',
1029 'blanchedalmond' => '#FFEBCD',
1030 'blue' => '#0000FF',
1031 'blueviolet' => '#8A2BE2',
1032 'brown' => '#A52A2A',
1033 'burlywood' => '#DEB887',
1034 'cadetblue' => '#5F9EA0',
1035 'chartreuse' => '#7FFF00',
1036 'chocolate' => '#D2691E',
1037 'coral' => '#FF7F50',
1038 'cornflowerblue' => '#6495ED',
1039 'cornsilk' => '#FFF8DC',
1040 'crimson' => '#DC143C',
1041 'cyan' => '#00FFFF',
1042 'darkblue' => '#00008B',
1043 'darkcyan' => '#008B8B',
1044 'darkgoldenrod' => '#B8860B',
1045 'darkgray' => '#A9A9A9',
1046 'darkgrey' => '#A9A9A9',
1047 'darkgreen' => '#006400',
1048 'darkKhaki' => '#BDB76B',
1049 'darkmagenta' => '#8B008B',
1050 'darkolivegreen' => '#556B2F',
1051 'arkorange' => '#FF8C00',
1052 'darkorchid' => '#9932CC',
1053 'darkred' => '#8B0000',
1054 'darksalmon' => '#E9967A',
1055 'darkseagreen' => '#8FBC8F',
1056 'darkslateblue' => '#483D8B',
1057 'darkslategray' => '#2F4F4F',
1058 'darkslategrey' => '#2F4F4F',
1059 'darkturquoise' => '#00CED1',
1060 'darkviolet' => '#9400D3',
1061 'deeppink' => '#FF1493',
1062 'deepskyblue' => '#00BFFF',
1063 'dimgray' => '#696969',
1064 'dimgrey' => '#696969',
1065 'dodgerblue' => '#1E90FF',
1066 'firebrick' => '#B22222',
1067 'floralwhite' => '#FFFAF0',
1068 'forestgreen' => '#228B22',
1069 'fuchsia' => '#FF00FF',
1070 'gainsboro' => '#DCDCDC',
1071 'ghostwhite' => '#F8F8FF',
1072 'gold' => '#FFD700',
1073 'goldenrod' => '#DAA520',
1074 'gray' => '#808080',
1075 'grey' => '#808080',
1076 'green' => '#008000',
1077 'greenyellow' => '#ADFF2F',
1078 'honeydew' => '#F0FFF0',
1079 'hotpink' => '#FF69B4',
1080 'indianred ' => '#CD5C5C',
1081 'indigo ' => '#4B0082',
1082 'ivory' => '#FFFFF0',
1083 'khaki' => '#F0E68C',
1084 'lavender' => '#E6E6FA',
1085 'lavenderblush' => '#FFF0F5',
1086 'lawngreen' => '#7CFC00',
1087 'lemonchiffon' => '#FFFACD',
1088 'lightblue' => '#ADD8E6',
1089 'lightcoral' => '#F08080',
1090 'lightcyan' => '#E0FFFF',
1091 'lightgoldenrodyellow' => '#FAFAD2',
1092 'lightgray' => '#D3D3D3',
1093 'lightgrey' => '#D3D3D3',
1094 'lightgreen' => '#90EE90',
1095 'lightpink' => '#FFB6C1',
1096 'lightsalmon' => '#FFA07A',
1097 'lightseagreen' => '#20B2AA',
1098 'lightskyblue' => '#87CEFA',
1099 'lightslategray' => '#778899',
1100 'lightslategrey' => '#778899',
1101 'lightsteelblue' => '#B0C4DE',
1102 'lightyellow' => '#FFFFE0',
1103 'lime' => '#00FF00',
1104 'limegreen' => '#32CD32',
1105 'linen' => '#FAF0E6',
1106 'magenta' => '#FF00FF',
1107 'maroon' => '#800000',
1108 'mediumaquamarine' => '#66CDAA',
1109 'mediumblue' => '#0000CD',
1110 'mediumorchid' => '#BA55D3',
1111 'mediumpurple' => '#9370D8',
1112 'mediumseagreen' => '#3CB371',
1113 'mediumslateblue' => '#7B68EE',
1114 'mediumspringgreen' => '#00FA9A',
1115 'mediumturquoise' => '#48D1CC',
1116 'mediumvioletred' => '#C71585',
1117 'midnightblue' => '#191970',
1118 'mintcream' => '#F5FFFA',
1119 'mistyrose' => '#FFE4E1',
1120 'moccasin' => '#FFE4B5',
1121 'navajowhite' => '#FFDEAD',
1122 'navy' => '#000080',
1123 'oldlace' => '#FDF5E6',
1124 'olive' => '#808000',
1125 'olivedrab' => '#6B8E23',
1126 'orange' => '#FFA500',
1127 'orangered' => '#FF4500',
1128 'orchid' => '#DA70D6',
1129 'palegoldenrod' => '#EEE8AA',
1130 'palegreen' => '#98FB98',
1131 'paleturquoise' => '#AFEEEE',
1132 'palevioletred' => '#D87093',
1133 'papayawhip' => '#FFEFD5',
1134 'peachpuff' => '#FFDAB9',
1135 'peru' => '#CD853F',
1136 'pink' => '#FFC0CB',
1137 'plum' => '#DDA0DD',
1138 'powderblue' => '#B0E0E6',
1139 'purple' => '#800080',
1141 'rosybrown' => '#BC8F8F',
1142 'royalblue' => '#4169E1',
1143 'saddlebrown' => '#8B4513',
1144 'salmon' => '#FA8072',
1145 'sandybrown' => '#F4A460',
1146 'seagreen' => '#2E8B57',
1147 'seashell' => '#FFF5EE',
1148 'sienna' => '#A0522D',
1149 'silver' => '#C0C0C0',
1150 'skyblue' => '#87CEEB',
1151 'slateblue' => '#6A5ACD',
1152 'slategray' => '#708090',
1153 'slategrey' => '#708090',
1154 'snow' => '#FFFAFA',
1155 'springgreen' => '#00FF7F',
1156 'steelblue' => '#4682B4',
1158 'teal' => '#008080',
1159 'thistle' => '#D8BFD8',
1160 'tomato' => '#FF6347',
1161 'transparent' => 'transparent',
1162 'turquoise' => '#40E0D0',
1163 'violet' => '#EE82EE',
1164 'wheat' => '#F5DEB3',
1165 'white' => '#FFFFFF',
1166 'whitesmoke' => '#F5F5F5',
1167 'yellow' => '#FFFF00',
1168 'yellowgreen' => '#9ACD32'
1173 * Used to prepare CSS strings
1176 * @subpackage cssoptimiser
1177 * @copyright 2012 Sam Hemelryk
1178 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1180 abstract class css_writer
{
1183 * The current indent level
1186 protected static $indent = 0;
1189 * Returns true if the output should still maintain minimum formatting.
1192 protected static function is_pretty() {
1194 return (!empty($CFG->cssoptimiserpretty
));
1198 * Returns the indenting char to use for indenting things nicely.
1201 protected static function get_indent() {
1202 if (self
::is_pretty()) {
1203 return str_repeat(" ", self
::$indent);
1209 * Increases the current indent
1211 protected static function increase_indent() {
1216 * Decreases the current indent
1218 protected static function decrease_indent() {
1223 * Returns the string to use as a separator
1226 protected static function get_separator() {
1227 return (self
::is_pretty())?
"\n":' ';
1231 * Returns CSS for media
1233 * @param string $typestring
1234 * @param css_rule[] $rules An array of css_rule objects
1237 public static function media($typestring, array &$rules) {
1238 $nl = self
::get_separator();
1241 if ($typestring !== 'all') {
1242 $output .= "\n@media {$typestring} {".$nl;
1243 self
::increase_indent();
1245 foreach ($rules as $rule) {
1246 $output .= $rule->out().$nl;
1248 if ($typestring !== 'all') {
1249 self
::decrease_indent();
1256 * Returns CSS for a keyframe
1258 * @param string $for The desired declaration. e.g. keyframes, -moz-keyframes, -webkit-keyframes
1259 * @param string $name The name for the keyframe
1260 * @param css_rule[] $rules An array of rules belonging to the keyframe
1263 public static function keyframe($for, $name, array &$rules) {
1264 $output = "\n@{$for} {$name} {";
1265 foreach ($rules as $rule) {
1266 $output .= $rule->out();
1273 * Returns CSS for a rule
1275 * @param string $selector
1276 * @param string $styles
1279 public static function rule($selector, $styles) {
1280 $css = self
::get_indent()."{$selector}{{$styles}}";
1285 * Returns CSS for the selectors of a rule
1287 * @param css_selector[] $selectors Array of css_selector objects
1290 public static function selectors(array $selectors) {
1291 $nl = self
::get_separator();
1292 $selectorstrings = array();
1293 foreach ($selectors as $selector) {
1294 $selectorstrings[] = $selector->out();
1296 return join(','.$nl, $selectorstrings);
1300 * Returns a selector given the components that make it up.
1302 * @param array $components
1305 public static function selector(array $components) {
1306 return trim(join(' ', $components));
1310 * Returns a CSS string for the provided styles
1312 * @param css_style[] $styles Array of css_style objects
1315 public static function styles(array $styles) {
1317 foreach ($styles as $style) {
1318 // Check if the style is an array. If it is then we are outputing an advanced style.
1319 // An advanced style is a style with one or more values, and can occur in situations like background-image
1320 // where browse specific values are being used.
1321 if (is_array($style)) {
1322 /* @var css_style[] $style */
1323 foreach ($style as $advstyle) {
1324 $bits[] = $advstyle->out();
1328 $bits[] = $style->out();
1330 return join('', $bits);
1334 * Returns a style CSS
1336 * @param string $name
1337 * @param string $value
1338 * @param bool $important
1341 public static function style($name, $value, $important = false) {
1342 $value = trim($value);
1343 if ($important && strpos($value, '!important') === false) {
1344 $value .= ' !important';
1346 return "{$name}:{$value};";
1351 * A consolidatable style interface.
1353 * Class that implement this have a short-hand notation for specifying multiple styles.
1356 * @subpackage cssoptimiser
1357 * @copyright 2012 Sam Hemelryk
1358 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1360 interface core_css_consolidatable_style
{
1362 * Used to consolidate several styles into a single "short-hand" style.
1363 * @param array $styles
1366 public static function consolidate(array $styles);
1370 * A structure to represent a CSS selector.
1372 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
1376 * @subpackage cssoptimiser
1377 * @copyright 2012 Sam Hemelryk
1378 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1380 class css_selector
{
1383 * An array of selector bits
1386 protected $selectors = array();
1389 * The number of selectors.
1392 protected $count = 0;
1395 * Is null if there are no selectors, true if all selectors are basic and false otherwise.
1396 * A basic selector is one that consists of just the element type. e.g. div, span, td, a
1399 protected $isbasic = null;
1402 * Initialises a new CSS selector
1403 * @return css_selector
1405 public static function init() {
1406 return new css_selector();
1410 * CSS selectors can only be created through the init method above.
1412 protected function __construct() {
1413 // Nothing to do here by default.
1417 * Adds a selector to the end of the current selector
1418 * @param string $selector
1420 public function add($selector) {
1421 $selector = trim($selector);
1423 $count +
= preg_match_all('/(\.|#)/', $selector, $matchesarray);
1424 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
1427 // If its already false then no need to continue, its not basic.
1428 if ($this->isbasic
!== false) {
1429 // If theres more than one part making up this selector its not basic.
1431 $this->isbasic
= false;
1433 // Check whether it is a basic element (a-z+) with possible psuedo selector.
1434 $this->isbasic
= (bool)preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector);
1437 $this->count
= $count;
1438 $this->selectors
[] = $selector;
1441 * Returns the number of individual components that make up this selector
1444 public function get_selector_count() {
1445 return $this->count
;
1449 * Returns the selector for use in a CSS rule
1452 public function out() {
1453 return css_writer
::selector($this->selectors
);
1457 * Returns true is all of the selectors act only upon basic elements (no classes/ids)
1460 public function is_basic() {
1461 return ($this->isbasic
=== true);
1466 * A structure to represent a CSS rule.
1469 * @subpackage cssoptimiser
1470 * @copyright 2012 Sam Hemelryk
1471 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1476 * An array of CSS selectors {@link css_selector}
1477 * @var css_selector[]
1479 protected $selectors = array();
1482 * An array of CSS styles {@link css_style}
1485 protected $styles = array();
1488 * Created a new CSS rule. This is the only way to create a new CSS rule externally.
1491 public static function init() {
1492 return new css_rule();
1496 * Constructs a new css rule.
1498 * @param string $selector The selector or array of selectors that make up this rule.
1499 * @param css_style[] $styles An array of styles that belong to this rule.
1501 protected function __construct($selector = null, array $styles = array()) {
1502 if ($selector != null) {
1503 if (is_array($selector)) {
1504 $this->selectors
= $selector;
1506 $this->selectors
= array($selector);
1508 $this->add_styles($styles);
1513 * Adds a new CSS selector to this rule
1515 * e.g. $rule->add_selector('.one #two.two');
1517 * @param css_selector $selector Adds a CSS selector to this rule.
1519 public function add_selector(css_selector
$selector) {
1520 $this->selectors
[] = $selector;
1524 * Adds a new CSS style to this rule.
1526 * @param css_style|string $style Adds a new style to this rule
1528 public function add_style($style) {
1529 if (is_string($style)) {
1530 $style = trim($style);
1531 if (empty($style)) {
1534 $bits = explode(':', $style, 2);
1535 if (count($bits) == 2) {
1536 list($name, $value) = array_map('trim', $bits);
1538 if (isset($name) && isset($value) && $name !== '' && $value !== '') {
1539 $style = css_style
::init_automatic($name, $value);
1541 } else if ($style instanceof css_style
) {
1542 // Clone the style as it may be coming from another rule and we don't
1543 // want references as it will likely be overwritten by proceeding
1545 $style = clone($style);
1547 if ($style instanceof css_style
) {
1548 $name = $style->get_name();
1549 $exists = array_key_exists($name, $this->styles
);
1550 // We need to find out if the current style support multiple values, or whether the style
1551 // is already set up to record multiple values. This can happen with background images which can have single
1552 // and multiple values.
1553 if ($style->allows_multiple_values() ||
($exists && is_array($this->styles
[$name]))) {
1555 $this->styles
[$name] = array();
1556 } else if ($this->styles
[$name] instanceof css_style
) {
1557 $this->styles
[$name] = array($this->styles
[$name]);
1559 $this->styles
[$name][] = $style;
1560 } else if ($exists) {
1561 $this->styles
[$name]->set_value($style->get_value());
1563 $this->styles
[$name] = $style;
1565 } else if (is_array($style)) {
1566 // We probably shouldn't worry about processing styles here but to
1567 // be truthful it doesn't hurt.
1568 foreach ($style as $astyle) {
1569 $this->add_style($astyle);
1575 * An easy method of adding several styles at once. Just calls add_style.
1577 * This method simply iterates over the array and calls {@link css_rule::add_style()}
1580 * @param css_style[] $styles Adds an array of styles
1582 public function add_styles(array $styles) {
1583 foreach ($styles as $style) {
1584 $this->add_style($style);
1589 * Returns the array of selectors
1591 * @return css_selector[]
1593 public function get_selectors() {
1594 return $this->selectors
;
1598 * Returns the array of styles
1600 * @return css_style[]
1602 public function get_styles() {
1603 return $this->styles
;
1607 * Outputs this rule as a fragment of CSS
1611 public function out() {
1612 $selectors = css_writer
::selectors($this->selectors
);
1613 $styles = css_writer
::styles($this->get_consolidated_styles());
1614 return css_writer
::rule($selectors, $styles);
1618 * Consolidates all styles associated with this rule
1620 * @return css_style[] An array of consolidated styles
1622 public function get_consolidated_styles() {
1623 /* @var css_style[] $organisedstyles */
1624 $organisedstyles = array();
1625 /* @var css_style[] $finalstyles */
1626 $finalstyles = array();
1627 /* @var core_css_consolidatable_style[] $consolidate */
1628 $consolidate = array();
1629 /* @var css_style[] $advancedstyles */
1630 $advancedstyles = array();
1631 foreach ($this->styles
as $style) {
1632 // If the style is an array then we are processing an advanced style. An advanced style is a style that can have
1633 // one or more values. Background-image is one such example as it can have browser specific styles.
1634 if (is_array($style)) {
1637 foreach ($style as $advstyle) {
1638 /* @var css_style $advstyle */
1640 $advancedstyles[$key] = $advstyle;
1641 if (!$advstyle->allows_multiple_values()) {
1642 if (!is_null($single)) {
1643 unset($advancedstyles[$single]);
1648 if (!is_null($single)) {
1649 $style = $advancedstyles[$single];
1651 $consolidatetoclass = $style->consolidate_to();
1652 if (($style->is_valid() ||
$style->is_special_empty_value()) && !empty($consolidatetoclass) &&
1653 class_exists('css_style_'.$consolidatetoclass)) {
1654 $class = 'css_style_'.$consolidatetoclass;
1655 if (!array_key_exists($class, $consolidate)) {
1656 $consolidate[$class] = array();
1657 $organisedstyles[$class] = true;
1659 $consolidate[$class][] = $style;
1660 unset($advancedstyles[$single]);
1666 $consolidatetoclass = $style->consolidate_to();
1667 if (($style->is_valid() ||
$style->is_special_empty_value()) && !empty($consolidatetoclass) &&
1668 class_exists('css_style_'.$consolidatetoclass)) {
1669 $class = 'css_style_'.$consolidatetoclass;
1670 if (!array_key_exists($class, $consolidate)) {
1671 $consolidate[$class] = array();
1672 $organisedstyles[$class] = true;
1674 $consolidate[$class][] = $style;
1676 $organisedstyles[$style->get_name()] = $style;
1680 foreach ($consolidate as $class => $styles) {
1681 $organisedstyles[$class] = call_user_func(array($class, 'consolidate'), $styles);
1684 foreach ($organisedstyles as $style) {
1685 if (is_array($style)) {
1686 foreach ($style as $s) {
1687 $finalstyles[] = $s;
1690 $finalstyles[] = $style;
1693 $finalstyles = array_merge($finalstyles, $advancedstyles);
1694 return $finalstyles;
1698 * Splits this rules into an array of CSS rules. One for each of the selectors
1699 * that make up this rule.
1701 * @return css_rule[]
1703 public function split_by_selector() {
1705 foreach ($this->selectors
as $selector) {
1706 $return[] = new css_rule($selector, $this->styles
);
1712 * Splits this rule into an array of rules. One for each of the styles that
1715 * @return css_rule[] Array of css_rule objects
1717 public function split_by_style() {
1719 foreach ($this->styles
as $style) {
1720 if (is_array($style)) {
1721 $return[] = new css_rule($this->selectors
, $style);
1724 $return[] = new css_rule($this->selectors
, array($style));
1730 * Gets a hash for the styles of this rule
1734 public function get_style_hash() {
1735 return md5(css_writer
::styles($this->styles
));
1739 * Gets a hash for the selectors of this rule
1743 public function get_selector_hash() {
1744 return md5(css_writer
::selectors($this->selectors
));
1748 * Gets the number of selectors that make up this rule.
1752 public function get_selector_count() {
1754 foreach ($this->selectors
as $selector) {
1755 $count +
= $selector->get_selector_count();
1761 * Returns true if there are any errors with this rule.
1765 public function has_errors() {
1766 foreach ($this->styles
as $style) {
1767 if (is_array($style)) {
1768 /* @var css_style[] $style */
1769 foreach ($style as $advstyle) {
1770 if ($advstyle->has_error()) {
1776 if ($style->has_error()) {
1784 * Returns the error strings that were recorded when processing this rule.
1786 * Before calling this function you should first call {@link css_rule::has_errors()}
1787 * to make sure there are errors (hopefully there arn't).
1791 public function get_error_string() {
1792 $css = $this->out();
1794 foreach ($this->styles
as $style) {
1795 if (is_array($style)) {
1796 /* @var css_style[] $style */
1797 foreach ($style as $advstyle) {
1798 if ($advstyle instanceof css_style
&& $advstyle->has_error()) {
1799 $errors[] = " * ".$advstyle->get_last_error();
1802 } else if ($style instanceof css_style
&& $style->has_error()) {
1803 $errors[] = " * ".$style->get_last_error();
1806 return $css." has the following errors:\n".join("\n", $errors);
1810 * Returns true if this rule could be considered a reset rule.
1812 * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector.
1816 public function is_reset_rule() {
1817 foreach ($this->selectors
as $selector) {
1818 if (!$selector->is_basic()) {
1827 * An abstract CSS rule collection class.
1829 * This class is extended by things such as media and keyframe declaration. They are declarations that
1830 * group rules together for a purpose.
1831 * When no declaration is specified rules accumulate into @media all.
1834 * @subpackage cssoptimiser
1835 * @copyright 2012 Sam Hemelryk
1836 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1838 abstract class css_rule_collection
{
1840 * An array of rules within this collection instance
1843 protected $rules = array();
1846 * The collection must be able to print itself.
1848 abstract public function out();
1851 * Adds a new CSS rule to this collection instance
1853 * @param css_rule $newrule
1855 public function add_rule(css_rule
$newrule) {
1856 foreach ($newrule->split_by_selector() as $rule) {
1857 $hash = $rule->get_selector_hash();
1858 if (!array_key_exists($hash, $this->rules
)) {
1859 $this->rules
[$hash] = $rule;
1861 $this->rules
[$hash]->add_styles($rule->get_styles());
1867 * Returns the rules used by this collection
1869 * @return css_rule[]
1871 public function get_rules() {
1872 return $this->rules
;
1876 * Organises rules by gropuing selectors based upon the styles and consolidating
1877 * those selectors into single rules.
1879 * @return bool True if the CSS was optimised by this method
1881 public function organise_rules_by_selectors() {
1882 /* @var css_rule[] $optimisedrules */
1883 $optimisedrules = array();
1884 $beforecount = count($this->rules
);
1886 /* @var css_rule $lastrule */
1888 foreach ($this->rules
as $rule) {
1889 $hash = $rule->get_style_hash();
1890 if ($lastrule !== null && $lasthash !== null && $hash === $lasthash) {
1891 foreach ($rule->get_selectors() as $selector) {
1892 $lastrule->add_selector($selector);
1896 $lastrule = clone($rule);
1898 $optimisedrules[] = $lastrule;
1900 $this->rules
= array();
1901 foreach ($optimisedrules as $optimised) {
1902 $this->rules
[$optimised->get_selector_hash()] = $optimised;
1904 $aftercount = count($this->rules
);
1905 return ($beforecount < $aftercount);
1909 * Returns the total number of rules that exist within this collection
1913 public function count_rules() {
1914 return count($this->rules
);
1918 * Returns the total number of selectors that exist within this collection
1922 public function count_selectors() {
1924 foreach ($this->rules
as $rule) {
1925 $count +
= $rule->get_selector_count();
1931 * Returns true if the collection has any rules that have errors
1935 public function has_errors() {
1936 foreach ($this->rules
as $rule) {
1937 if ($rule->has_errors()) {
1945 * Returns any errors that have happened within rules in this collection.
1949 public function get_errors() {
1951 foreach ($this->rules
as $rule) {
1952 if ($rule->has_errors()) {
1953 $errors[] = $rule->get_error_string();
1961 * A media class to organise rules by the media they apply to.
1964 * @subpackage cssoptimiser
1965 * @copyright 2012 Sam Hemelryk
1966 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1968 class css_media
extends css_rule_collection
{
1971 * An array of the different media types this instance applies to.
1974 protected $types = array();
1977 * Initalises a new media instance
1979 * @param string $for The media that the contained rules are destined for.
1981 public function __construct($for = 'all') {
1982 $types = explode(',', $for);
1983 $this->types
= array_map('trim', $types);
1987 * Returns the CSS for this media and all of its rules.
1991 public function out() {
1992 return css_writer
::media(join(',', $this->types
), $this->rules
);
1996 * Returns an array of media that this media instance applies to
2000 public function get_types() {
2001 return $this->types
;
2005 * Returns all of the reset rules known by this media set.
2006 * @param bool $remove If set to true reset rules will be removed before being returned.
2009 public function get_reset_rules($remove = false) {
2010 $resetrules = array();
2011 foreach ($this->rules
as $key => $rule) {
2012 if ($rule->is_reset_rule()) {
2013 $resetrules[] = clone $rule;
2015 unset($this->rules
[$key]);
2024 * A media class to organise rules by the media they apply to.
2027 * @subpackage cssoptimiser
2028 * @copyright 2012 Sam Hemelryk
2029 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2031 class css_keyframe
extends css_rule_collection
{
2034 * The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
2040 * The name for the keyframes
2045 * Constructs a new keyframe
2047 * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
2048 * @param string $name The name for the keyframes
2050 public function __construct($for, $name) {
2052 $this->name
= $name;
2055 * Returns the directive of this keyframe
2057 * e.g. keyframes, -moz-keyframes, -webkit-keyframes
2060 public function get_for() {
2064 * Returns the name of this keyframe
2067 public function get_name() {
2071 * Returns the CSS for this collection of keyframes and all of its rules.
2075 public function out() {
2076 return css_writer
::keyframe($this->for, $this->name
, $this->rules
);
2081 * An absract class to represent CSS styles
2084 * @subpackage cssoptimiser
2085 * @copyright 2012 Sam Hemelryk
2086 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2088 abstract class css_style
{
2090 /** Constant used for recongise a special empty value in a CSS style */
2091 const NULL_VALUE
= '@@$NULL$@@';
2094 * The name of the style
2100 * The value for the style
2106 * If set to true this style was defined with the !important rule.
2107 * Only trolls use !important.
2108 * Don't hide under bridges.. its not good for your skin. Do the proper thing
2109 * and fix the issue don't just force a fix that will undoubtedly one day
2110 * lead to further frustration.
2113 protected $important = false;
2116 * Gets set to true if this style has an error
2119 protected $error = false;
2122 * The last error message that occured
2125 protected $errormessage = null;
2128 * Initialises a new style.
2130 * This is the only public way to create a style to ensure they that appropriate
2131 * style class is used if it exists.
2133 * @param string $name The name of the style.
2134 * @param string $value The value of the style.
2135 * @return css_style_generic
2137 public static function init_automatic($name, $value) {
2138 $cleanedname = preg_replace('#[^a-zA-Z0-9]+#', '', $name);
2139 $specificclass = 'css_style_'.$cleanedname;
2140 if (class_exists($specificclass)) {
2141 $style = call_user_func(array($specificclass, 'init'), $value);
2142 if ($cleanedname !== $name && !is_array($style)) {
2143 $style->set_actual_name($name);
2147 return new css_style_generic($name, $value);
2151 * Creates a new style when given its name and value
2153 * @param string $name The name of the style.
2154 * @param string $value The value of the style.
2156 protected function __construct($name, $value) {
2157 $this->name
= $name;
2158 $this->set_value($value);
2162 * Sets the value for the style
2164 * @param string $value
2166 final public function set_value($value) {
2167 $value = trim($value);
2168 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
2170 $value = substr($value, 0, -(strlen($matches[1])));
2171 $value = rtrim($value);
2173 if (!$this->important ||
$important) {
2174 $this->value
= $this->clean_value($value);
2175 $this->important
= $important;
2177 if (!$this->is_valid()) {
2178 $this->set_error('Invalid value for '.$this->name
);
2183 * Returns true if the value associated with this style is valid
2187 public function is_valid() {
2192 * Returns the name for the style
2196 public function get_name() {
2201 * Returns the value for the style
2203 * @param bool $includeimportant If set to true and the rule is important !important postfix will be used.
2206 public function get_value($includeimportant = true) {
2207 $value = $this->value
;
2208 if ($includeimportant && $this->important
) {
2209 $value .= ' !important';
2215 * Returns the style ready for use in CSS
2217 * @param string|null $value A value to use to override the value for this style.
2220 public function out($value = null) {
2221 if (is_null($value)) {
2222 $value = $this->get_value();
2224 return css_writer
::style($this->name
, $value, $this->important
);
2228 * This can be overridden by a specific style allowing it to clean its values
2231 * @param mixed $value
2234 protected function clean_value($value) {
2239 * If this particular style can be consolidated into another style this function
2240 * should return the style that it can be consolidated into.
2242 * @return string|null
2244 public function consolidate_to() {
2249 * Sets the last error message.
2251 * @param string $message
2253 protected function set_error($message) {
2254 $this->error
= true;
2255 $this->errormessage
= $message;
2259 * Returns true if an error has occured
2263 public function has_error() {
2264 return $this->error
;
2268 * Returns the last error that occured or null if no errors have happened.
2272 public function get_last_error() {
2273 return $this->errormessage
;
2277 * Returns true if the value for this style is the special null value.
2279 * This should only be overriden in circumstances where a shorthand style can lead
2280 * to move explicit styles being overwritten. Not a common place occurenace.
2283 * This occurs if the shorthand background property was used but no proper value
2284 * was specified for this style.
2285 * This leads to a null value being used unless otherwise overridden.
2289 public function is_special_empty_value() {
2294 * Returns true if this style permits multiple values.
2296 * This occurs for styles such as background image that can have browser specific values that need to be maintained because
2297 * of course we don't know what browser the user is using, and optimisation occurs before caching.
2298 * Thus we must always server all values we encounter in the order we encounter them for when this is set to true.
2300 * @return boolean False by default, true if the style supports muliple values.
2302 public function allows_multiple_values() {
2307 * Returns true if this style was marked important.
2310 public function is_important() {
2311 return !empty($this->important
);
2315 * Sets the important flag for this style and its current value.
2316 * @param bool $important
2318 public function set_important($important = true) {
2319 $this->important
= (bool) $important;
2323 * Sets the actual name used within the style.
2325 * This method allows us to support browser hacks like *width:0;
2327 * @param string $name
2329 public function set_actual_name($name) {
2330 $this->name
= $name;
2335 * A generic CSS style class to use when a more specific class does not exist.
2338 * @subpackage cssoptimiser
2339 * @copyright 2012 Sam Hemelryk
2340 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2342 class css_style_generic
extends css_style
{
2345 * Cleans incoming values for typical things that can be optimised.
2347 * @param mixed $value Cleans the provided value optimising it if possible
2350 protected function clean_value($value) {
2351 if (trim($value) == '0px') {
2353 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) {
2354 $value = '#'.strtoupper($matches[1]);
2361 * A colour CSS style
2364 * @subpackage cssoptimiser
2365 * @copyright 2012 Sam Hemelryk
2366 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2368 class css_style_color
extends css_style
{
2371 * Creates a new colour style
2373 * @param mixed $value Initialises a new colour style
2374 * @return css_style_color
2376 public static function init($value) {
2377 return new css_style_color('color', $value);
2381 * Cleans the colour unifing it to a 6 char hash colour if possible
2382 * Doing this allows us to associate identical colours being specified in
2383 * different ways. e.g. Red, red, #F00, and #F00000
2385 * @param mixed $value Cleans the provided value optimising it if possible
2388 protected function clean_value($value) {
2389 $value = trim($value);
2390 if (css_is_colour($value)) {
2391 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) {
2392 $value = '#'.strtoupper($matches[1]);
2393 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) {
2394 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
2395 $value = '#'.strtoupper($value);
2396 } else if (array_key_exists(strtolower($value), css_optimiser
::$htmlcolours)) {
2397 $value = css_optimiser
::$htmlcolours[strtolower($value)];
2404 * Returns the colour style for use within CSS.
2405 * Will return an optimised hash colour.
2408 * #123 instead of #112233
2409 * #F00 instead of red
2411 * @param string $overridevalue If provided then this value will be used instead
2412 * of the styles current value.
2415 public function out($overridevalue = null) {
2416 if ($overridevalue === null) {
2417 $overridevalue = $this->value
;
2419 return parent
::out(self
::shrink_value($overridevalue));
2423 * Shrinks the colour value is possible.
2425 * @param string $value Shrinks the current value to an optimial form if possible
2428 public static function shrink_value($value) {
2429 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) {
2430 return '#'.$matches[1].$matches[2].$matches[3];
2436 * Returns true if the value is a valid colour.
2440 public function is_valid() {
2441 return css_is_colour($this->value
);
2449 * @subpackage cssoptimiser
2450 * @copyright 2012 Sam Hemelryk
2451 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2453 class css_style_width
extends css_style
{
2456 * Checks if the width is valid
2459 public function is_valid() {
2460 return css_is_width($this->value
);
2464 * Cleans the provided value
2466 * @param mixed $value Cleans the provided value optimising it if possible
2469 protected function clean_value($value) {
2470 if (!css_is_width($value)) {
2471 // Note we don't actually change the value to something valid. That
2472 // would be bad for futureproofing.
2473 $this->set_error('Invalid width specified for '.$this->name
);
2474 } else if (preg_match('#^0\D+$#', $value)) {
2477 return trim($value);
2481 * Initialises a new width style
2483 * @param mixed $value The value this style has
2484 * @return css_style_width
2486 public static function init($value) {
2487 return new css_style_width('width', $value);
2495 * @subpackage cssoptimiser
2496 * @copyright 2012 Sam Hemelryk
2497 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2499 class css_style_margin
extends css_style_width
implements core_css_consolidatable_style
{
2502 * Initialises a margin style.
2504 * In this case we split the margin into several other margin styles so that
2505 * we can properly condense overrides and then reconsolidate them later into
2508 * @param string $value The value the style has
2509 * @return array An array of margin values that can later be consolidated
2511 public static function init($value) {
2513 if (strpos($value, '!important') !== false) {
2514 $important = ' !important';
2515 $value = str_replace('!important', '', $value);
2518 $value = preg_replace('#\s+#', ' ', trim($value));
2519 $bits = explode(' ', $value, 4);
2521 $top = $right = $bottom = $left = null;
2522 if (count($bits) > 0) {
2523 $top = $right = $bottom = $left = array_shift($bits);
2525 if (count($bits) > 0) {
2526 $right = $left = array_shift($bits);
2528 if (count($bits) > 0) {
2529 $bottom = array_shift($bits);
2531 if (count($bits) > 0) {
2532 $left = array_shift($bits);
2535 new css_style_margintop('margin-top', $top.$important),
2536 new css_style_marginright('margin-right', $right.$important),
2537 new css_style_marginbottom('margin-bottom', $bottom.$important),
2538 new css_style_marginleft('margin-left', $left.$important)
2543 * Consolidates individual margin styles into a single margin style
2545 * @param css_style[] $styles
2546 * @return css_style[] An array of consolidated styles
2548 public static function consolidate(array $styles) {
2549 if (count($styles) != 4) {
2553 $someimportant = false;
2554 $allimportant = null;
2555 $notimportantequal = null;
2557 foreach ($styles as $style) {
2558 if ($style->is_important()) {
2559 $someimportant = true;
2560 if ($allimportant === null) {
2561 $allimportant = true;
2564 if ($allimportant === true) {
2565 $allimportant = false;
2567 if ($firstvalue == null) {
2568 $firstvalue = $style->get_value(false);
2569 $notimportantequal = true;
2570 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
2571 $notimportantequal = false;
2576 if ($someimportant && !$allimportant && !$notimportantequal) {
2580 if ($someimportant && !$allimportant && $notimportantequal) {
2582 new css_style_margin('margin', $firstvalue)
2584 foreach ($styles as $style) {
2585 if ($style->is_important()) {
2595 foreach ($styles as $style) {
2596 switch ($style->get_name()) {
2598 $top = $style->get_value(false);
2600 case 'margin-right' :
2601 $right = $style->get_value(false);
2603 case 'margin-bottom' :
2604 $bottom = $style->get_value(false);
2606 case 'margin-left' :
2607 $left = $style->get_value(false);
2611 if ($top == $bottom && $left == $right) {
2612 if ($top == $left) {
2613 $returnstyle = new css_style_margin('margin', $top);
2615 $returnstyle = new css_style_margin('margin', "{$top} {$left}");
2617 } else if ($left == $right) {
2618 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom}");
2620 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}");
2622 if ($allimportant) {
2623 $returnstyle->set_important();
2625 return array($returnstyle);
2631 * A margin top style
2634 * @subpackage cssoptimiser
2635 * @copyright 2012 Sam Hemelryk
2636 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2638 class css_style_margintop
extends css_style_margin
{
2641 * A simple init, just a single style
2643 * @param string $value The value the style has
2644 * @return css_style_margintop
2646 public static function init($value) {
2647 return new css_style_margintop('margin-top', $value);
2651 * This style can be consolidated into a single margin style
2655 public function consolidate_to() {
2661 * A margin right style
2664 * @subpackage cssoptimiser
2665 * @copyright 2012 Sam Hemelryk
2666 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2668 class css_style_marginright
extends css_style_margin
{
2671 * A simple init, just a single style
2673 * @param string $value The value the style has
2674 * @return css_style_margintop
2676 public static function init($value) {
2677 return new css_style_marginright('margin-right', $value);
2681 * This style can be consolidated into a single margin style
2685 public function consolidate_to() {
2691 * A margin bottom style
2694 * @subpackage cssoptimiser
2695 * @copyright 2012 Sam Hemelryk
2696 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2698 class css_style_marginbottom
extends css_style_margin
{
2701 * A simple init, just a single style
2703 * @param string $value The value the style has
2704 * @return css_style_margintop
2706 public static function init($value) {
2707 return new css_style_marginbottom('margin-bottom', $value);
2711 * This style can be consolidated into a single margin style
2715 public function consolidate_to() {
2721 * A margin left style
2724 * @subpackage cssoptimiser
2725 * @copyright 2012 Sam Hemelryk
2726 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2728 class css_style_marginleft
extends css_style_margin
{
2731 * A simple init, just a single style
2733 * @param string $value The value the style has
2734 * @return css_style_margintop
2736 public static function init($value) {
2737 return new css_style_marginleft('margin-left', $value);
2741 * This style can be consolidated into a single margin style
2745 public function consolidate_to() {
2754 * @subpackage cssoptimiser
2755 * @copyright 2012 Sam Hemelryk
2756 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2758 class css_style_border
extends css_style
implements core_css_consolidatable_style
{
2761 * Initalises the border style into an array of individual style compontents
2763 * @param string $value The value the style has
2764 * @return css_style_bordercolor
2766 public static function init($value) {
2767 $value = preg_replace('#\s+#', ' ', $value);
2768 $bits = explode(' ', $value, 3);
2771 if (count($bits) > 0) {
2772 $width = array_shift($bits);
2773 if (!css_style_borderwidth
::is_border_width($width)) {
2776 $return[] = css_style_bordertopwidth
::init($width);
2777 $return[] = css_style_borderrightwidth
::init($width);
2778 $return[] = css_style_borderbottomwidth
::init($width);
2779 $return[] = css_style_borderleftwidth
::init($width);
2781 if (count($bits) > 0) {
2782 $style = array_shift($bits);
2783 $return[] = css_style_bordertopstyle
::init($style);
2784 $return[] = css_style_borderrightstyle
::init($style);
2785 $return[] = css_style_borderbottomstyle
::init($style);
2786 $return[] = css_style_borderleftstyle
::init($style);
2788 if (count($bits) > 0) {
2789 $colour = array_shift($bits);
2790 $return[] = css_style_bordertopcolor
::init($colour);
2791 $return[] = css_style_borderrightcolor
::init($colour);
2792 $return[] = css_style_borderbottomcolor
::init($colour);
2793 $return[] = css_style_borderleftcolor
::init($colour);
2799 * Consolidates all border styles into a single style
2801 * @param css_style[] $styles An array of border styles
2802 * @return css_style[] An optimised array of border styles
2804 public static function consolidate(array $styles) {
2806 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2807 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2808 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2810 foreach ($styles as $style) {
2811 switch ($style->get_name()) {
2812 case 'border-top-width':
2813 $borderwidths['top'] = $style->get_value();
2815 case 'border-right-width':
2816 $borderwidths['right'] = $style->get_value();
2818 case 'border-bottom-width':
2819 $borderwidths['bottom'] = $style->get_value();
2821 case 'border-left-width':
2822 $borderwidths['left'] = $style->get_value();
2825 case 'border-top-style':
2826 $borderstyles['top'] = $style->get_value();
2828 case 'border-right-style':
2829 $borderstyles['right'] = $style->get_value();
2831 case 'border-bottom-style':
2832 $borderstyles['bottom'] = $style->get_value();
2834 case 'border-left-style':
2835 $borderstyles['left'] = $style->get_value();
2838 case 'border-top-color':
2839 $bordercolors['top'] = css_style_color
::shrink_value($style->get_value());
2841 case 'border-right-color':
2842 $bordercolors['right'] = css_style_color
::shrink_value($style->get_value());
2844 case 'border-bottom-color':
2845 $bordercolors['bottom'] = css_style_color
::shrink_value($style->get_value());
2847 case 'border-left-color':
2848 $bordercolors['left'] = css_style_color
::shrink_value($style->get_value());
2853 $uniquewidths = count(array_unique($borderwidths));
2854 $uniquestyles = count(array_unique($borderstyles));
2855 $uniquecolors = count(array_unique($bordercolors));
2857 $nullwidths = in_array(null, $borderwidths, true);
2858 $nullstyles = in_array(null, $borderstyles, true);
2859 $nullcolors = in_array(null, $bordercolors, true);
2861 $allwidthsthesame = ($uniquewidths === 1)?
1:0;
2862 $allstylesthesame = ($uniquestyles === 1)?
1:0;
2863 $allcolorsthesame = ($uniquecolors === 1)?
1:0;
2865 $allwidthsnull = $allwidthsthesame && $nullwidths;
2866 $allstylesnull = $allstylesthesame && $nullstyles;
2867 $allcolorsnull = $allcolorsthesame && $nullcolors;
2869 /* @var css_style[] $return */
2871 if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
2872 // Everything is null still... boo.
2873 return array(new css_style_border('border', ''));
2875 } else if ($allwidthsnull && $allstylesnull) {
2877 self
::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2880 } else if ($allwidthsnull && $allcolorsnull) {
2882 self
::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2885 } else if ($allcolorsnull && $allstylesnull) {
2887 self
::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2892 if ($allwidthsthesame +
$allstylesthesame +
$allcolorsthesame == 3) {
2894 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']);
2896 } else if ($allwidthsthesame +
$allstylesthesame +
$allcolorsthesame == 2) {
2898 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) {
2900 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']);
2901 self
::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2903 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) {
2905 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']);
2906 self
::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2908 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) {
2910 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']);
2911 self
::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2914 self
::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2915 self
::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2916 self
::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2919 } else if (!$nullwidths && !$nullcolors && !$nullstyles &&
2920 max(array_count_values($borderwidths)) == 3 &&
2921 max(array_count_values($borderstyles)) == 3 &&
2922 max(array_count_values($bordercolors)) == 3) {
2924 $widthkeys = array();
2925 $stylekeys = array();
2926 $colorkeys = array();
2928 foreach ($borderwidths as $key => $value) {
2929 if (!array_key_exists($value, $widthkeys)) {
2930 $widthkeys[$value] = array();
2932 $widthkeys[$value][] = $key;
2934 usort($widthkeys, 'css_sort_by_count');
2935 $widthkeys = array_values($widthkeys);
2937 foreach ($borderstyles as $key => $value) {
2938 if (!array_key_exists($value, $stylekeys)) {
2939 $stylekeys[$value] = array();
2941 $stylekeys[$value][] = $key;
2943 usort($stylekeys, 'css_sort_by_count');
2944 $stylekeys = array_values($stylekeys);
2946 foreach ($bordercolors as $key => $value) {
2947 if (!array_key_exists($value, $colorkeys)) {
2948 $colorkeys[$value] = array();
2950 $colorkeys[$value][] = $key;
2952 usort($colorkeys, 'css_sort_by_count');
2953 $colorkeys = array_values($colorkeys);
2955 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
2956 $key = $widthkeys[0][0];
2957 self
::build_style_string($return, 'css_style_border', 'border',
2958 $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2959 $key = $widthkeys[1][0];
2960 self
::build_style_string($return, 'css_style_border'.$key, 'border-'.$key,
2961 $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
2963 self
::build_style_string($return, 'css_style_bordertop', 'border-top',
2964 $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2965 self
::build_style_string($return, 'css_style_borderright', 'border-right',
2966 $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2967 self
::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
2968 $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2969 self
::build_style_string($return, 'css_style_borderleft', 'border-left',
2970 $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2973 self
::build_style_string($return, 'css_style_bordertop', 'border-top',
2974 $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2975 self
::build_style_string($return, 'css_style_borderright', 'border-right',
2976 $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2977 self
::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
2978 $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2979 self
::build_style_string($return, 'css_style_borderleft', 'border-left',
2980 $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2982 foreach ($return as $key => $style) {
2983 if ($style->get_value() == '') {
2984 unset($return[$key]);
2991 * Border styles get consolidated to a single border style.
2995 public function consolidate_to() {
3000 * Consolidates a series of border styles into an optimised array of border
3001 * styles by looking at the direction of the border and prioritising that
3002 * during the optimisation.
3004 * @param array $array An array to add styles into during consolidation. Passed by reference.
3005 * @param string $class The class type to initalise
3006 * @param string $style The style to create
3007 * @param string|array $top The top value
3008 * @param string $right The right value
3009 * @param string $bottom The bottom value
3010 * @param string $left The left value
3013 public static function consolidate_styles_by_direction(&$array, $class, $style,
3014 $top, $right = null, $bottom = null, $left = null) {
3015 if (is_array($top)) {
3016 $right = $top['right'];
3017 $bottom = $top['bottom'];
3018 $left = $top['left'];
3022 if ($top == $bottom && $left == $right && $top == $left) {
3023 if (is_null($top)) {
3024 $array[] = new $class($style, '');
3026 $array[] = new $class($style, $top);
3028 } else if ($top == null ||
$right == null ||
$bottom == null ||
$left == null) {
3029 if ($top !== null) {
3030 $array[] = new $class(str_replace('border-', 'border-top-', $style), $top);
3032 if ($right !== null) {
3033 $array[] = new $class(str_replace('border-', 'border-right-', $style), $right);
3035 if ($bottom !== null) {
3036 $array[] = new $class(str_replace('border-', 'border-bottom-', $style), $bottom);
3038 if ($left !== null) {
3039 $array[] = new $class(str_replace('border-', 'border-left-', $style), $left);
3041 } else if ($top == $bottom && $left == $right) {
3042 $array[] = new $class($style, $top.' '.$right);
3043 } else if ($left == $right) {
3044 $array[] = new $class($style, $top.' '.$right.' '.$bottom);
3046 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left);
3052 * Builds a border style for a set of width, style, and colour values
3054 * @param array $array An array into which the generated style is added
3055 * @param string $class The class type to initialise
3056 * @param string $cssstyle The style to use
3057 * @param string $width The width of the border
3058 * @param string $style The style of the border
3059 * @param string $color The colour of the border
3062 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) {
3063 if (!is_null($width) && !is_null($style) && !is_null($color)) {
3064 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color);
3065 } else if (!is_null($width) && !is_null($style) && is_null($color)) {
3066 $array[] = new $class($cssstyle, $width.' '.$style);
3067 } else if (!is_null($width) && is_null($style) && is_null($color)) {
3068 $array[] = new $class($cssstyle, $width);
3070 if (!is_null($width)) {
3071 $array[] = new $class($cssstyle, $width);
3073 if (!is_null($style)) {
3074 $array[] = new $class($cssstyle, $style);
3076 if (!is_null($color)) {
3077 $array[] = new $class($cssstyle, $color);
3085 * A border colour style
3088 * @subpackage cssoptimiser
3089 * @copyright 2012 Sam Hemelryk
3090 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3092 class css_style_bordercolor
extends css_style_color
{
3095 * Creates a new border colour style
3097 * Based upon the colour style
3099 * @param mixed $value
3100 * @return Array of css_style_bordercolor
3102 public static function init($value) {
3103 $value = preg_replace('#\s+#', ' ', $value);
3104 $bits = explode(' ', $value, 4);
3106 $top = $right = $bottom = $left = null;
3107 if (count($bits) > 0) {
3108 $top = $right = $bottom = $left = array_shift($bits);
3110 if (count($bits) > 0) {
3111 $right = $left = array_shift($bits);
3113 if (count($bits) > 0) {
3114 $bottom = array_shift($bits);
3116 if (count($bits) > 0) {
3117 $left = array_shift($bits);
3120 css_style_bordertopcolor
::init($top),
3121 css_style_borderrightcolor
::init($right),
3122 css_style_borderbottomcolor
::init($bottom),
3123 css_style_borderleftcolor
::init($left)
3128 * Consolidate this to a single border style
3132 public function consolidate_to() {
3139 * @param string $value Cleans the provided value optimising it if possible
3142 protected function clean_value($value) {
3143 $values = explode(' ', $value);
3144 $values = array_map('parent::clean_value', $values);
3145 return join (' ', $values);
3149 * Outputs this style
3151 * @param string $overridevalue
3154 public function out($overridevalue = null) {
3155 if ($overridevalue === null) {
3156 $overridevalue = $this->value
;
3158 $values = explode(' ', $overridevalue);
3159 $values = array_map('css_style_color::shrink_value', $values);
3160 return parent
::out(join (' ', $values));
3165 * A border left style
3168 * @subpackage cssoptimiser
3169 * @copyright 2012 Sam Hemelryk
3170 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3172 class css_style_borderleft
extends css_style_generic
{
3175 * Initialises the border left style into individual components
3177 * @param string $value
3178 * @return array Array of css_style_borderleftwidth|css_style_borderleftstyle|css_style_borderleftcolor
3180 public static function init($value) {
3181 $value = preg_replace('#\s+#', ' ', $value);
3182 $bits = explode(' ', $value, 3);
3185 if (count($bits) > 0) {
3186 $return[] = css_style_borderleftwidth
::init(array_shift($bits));
3188 if (count($bits) > 0) {
3189 $return[] = css_style_borderleftstyle
::init(array_shift($bits));
3191 if (count($bits) > 0) {
3192 $return[] = css_style_borderleftcolor
::init(array_shift($bits));
3198 * Consolidate this to a single border style
3202 public function consolidate_to() {
3208 * A border right style
3211 * @subpackage cssoptimiser
3212 * @copyright 2012 Sam Hemelryk
3213 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3215 class css_style_borderright
extends css_style_generic
{
3218 * Initialises the border right style into individual components
3220 * @param string $value The value of the style
3221 * @return array Array of css_style_borderrightwidth|css_style_borderrightstyle|css_style_borderrightcolor
3223 public static function init($value) {
3224 $value = preg_replace('#\s+#', ' ', $value);
3225 $bits = explode(' ', $value, 3);
3228 if (count($bits) > 0) {
3229 $return[] = css_style_borderrightwidth
::init(array_shift($bits));
3231 if (count($bits) > 0) {
3232 $return[] = css_style_borderrightstyle
::init(array_shift($bits));
3234 if (count($bits) > 0) {
3235 $return[] = css_style_borderrightcolor
::init(array_shift($bits));
3241 * Consolidate this to a single border style
3245 public function consolidate_to() {
3251 * A border top style
3254 * @subpackage cssoptimiser
3255 * @copyright 2012 Sam Hemelryk
3256 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3258 class css_style_bordertop
extends css_style_generic
{
3261 * Initialises the border top style into individual components
3263 * @param string $value The value of the style
3264 * @return array Array of css_style_bordertopwidth|css_style_bordertopstyle|css_style_bordertopcolor
3266 public static function init($value) {
3267 $value = preg_replace('#\s+#', ' ', $value);
3268 $bits = explode(' ', $value, 3);
3271 if (count($bits) > 0) {
3272 $return[] = css_style_bordertopwidth
::init(array_shift($bits));
3274 if (count($bits) > 0) {
3275 $return[] = css_style_bordertopstyle
::init(array_shift($bits));
3277 if (count($bits) > 0) {
3278 $return[] = css_style_bordertopcolor
::init(array_shift($bits));
3284 * Consolidate this to a single border style
3288 public function consolidate_to() {
3294 * A border bottom style
3297 * @subpackage cssoptimiser
3298 * @copyright 2012 Sam Hemelryk
3299 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3301 class css_style_borderbottom
extends css_style_generic
{
3304 * Initialises the border bottom style into individual components
3306 * @param string $value The value of the style
3307 * @return array Array of css_style_borderbottomwidth|css_style_borderbottomstyle|css_style_borderbottomcolor
3309 public static function init($value) {
3310 $value = preg_replace('#\s+#', ' ', $value);
3311 $bits = explode(' ', $value, 3);
3314 if (count($bits) > 0) {
3315 $return[] = css_style_borderbottomwidth
::init(array_shift($bits));
3317 if (count($bits) > 0) {
3318 $return[] = css_style_borderbottomstyle
::init(array_shift($bits));
3320 if (count($bits) > 0) {
3321 $return[] = css_style_borderbottomcolor
::init(array_shift($bits));
3327 * Consolidate this to a single border style
3331 public function consolidate_to() {
3337 * A border width style
3340 * @subpackage cssoptimiser
3341 * @copyright 2012 Sam Hemelryk
3342 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3344 class css_style_borderwidth
extends css_style_width
{
3347 * Creates a new border colour style
3349 * Based upon the colour style
3351 * @param string $value The value of the style
3352 * @return array Array of css_style_border*width
3354 public static function init($value) {
3355 $value = preg_replace('#\s+#', ' ', $value);
3356 $bits = explode(' ', $value, 4);
3358 $top = $right = $bottom = $left = null;
3359 if (count($bits) > 0) {
3360 $top = $right = $bottom = $left = array_shift($bits);
3362 if (count($bits) > 0) {
3363 $right = $left = array_shift($bits);
3365 if (count($bits) > 0) {
3366 $bottom = array_shift($bits);
3368 if (count($bits) > 0) {
3369 $left = array_shift($bits);
3372 css_style_bordertopwidth
::init($top),
3373 css_style_borderrightwidth
::init($right),
3374 css_style_borderbottomwidth
::init($bottom),
3375 css_style_borderleftwidth
::init($left)
3380 * Consolidate this to a single border style
3384 public function consolidate_to() {
3389 * Checks if the width is valid
3392 public function is_valid() {
3393 return self
::is_border_width($this->value
);
3397 * Cleans the provided value
3399 * @param mixed $value Cleans the provided value optimising it if possible
3402 protected function clean_value($value) {
3403 $isvalid = self
::is_border_width($value);
3405 $this->set_error('Invalid width specified for '.$this->name
);
3406 } else if (preg_match('#^0\D+$#', $value)) {
3409 return trim($value);
3413 * Returns true if the provided value is a permitted border width
3414 * @param string $value The value to check
3417 public static function is_border_width($value) {
3418 $altwidthvalues = array('thin', 'medium', 'thick');
3419 return css_is_width($value) ||
in_array($value, $altwidthvalues);
3424 * A border style style
3427 * @subpackage cssoptimiser
3428 * @copyright 2012 Sam Hemelryk
3429 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3431 class css_style_borderstyle
extends css_style_generic
{
3434 * Creates a new border colour style
3436 * Based upon the colour style
3438 * @param string $value The value of the style
3439 * @return array Array of css_style_border*style
3441 public static function init($value) {
3442 $value = preg_replace('#\s+#', ' ', $value);
3443 $bits = explode(' ', $value, 4);
3445 $top = $right = $bottom = $left = null;
3446 if (count($bits) > 0) {
3447 $top = $right = $bottom = $left = array_shift($bits);
3449 if (count($bits) > 0) {
3450 $right = $left = array_shift($bits);
3452 if (count($bits) > 0) {
3453 $bottom = array_shift($bits);
3455 if (count($bits) > 0) {
3456 $left = array_shift($bits);
3459 css_style_bordertopstyle
::init($top),
3460 css_style_borderrightstyle
::init($right),
3461 css_style_borderbottomstyle
::init($bottom),
3462 css_style_borderleftstyle
::init($left)
3467 * Consolidate this to a single border style
3471 public function consolidate_to() {
3477 * A border top colour style
3480 * @subpackage cssoptimiser
3481 * @copyright 2012 Sam Hemelryk
3482 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3484 class css_style_bordertopcolor
extends css_style_bordercolor
{
3487 * Initialises this style object
3489 * @param string $value The value of the style
3490 * @return css_style_bordertopcolor
3492 public static function init($value) {
3493 return new css_style_bordertopcolor('border-top-color', $value);
3497 * Consolidate this to a single border style
3501 public function consolidate_to() {
3507 * A border left colour style
3510 * @subpackage cssoptimiser
3511 * @copyright 2012 Sam Hemelryk
3512 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3514 class css_style_borderleftcolor
extends css_style_bordercolor
{
3517 * Initialises this style object
3519 * @param string $value The value of the style
3520 * @return css_style_borderleftcolor
3522 public static function init($value) {
3523 return new css_style_borderleftcolor('border-left-color', $value);
3527 * Consolidate this to a single border style
3531 public function consolidate_to() {
3537 * A border right colour style
3540 * @subpackage cssoptimiser
3541 * @copyright 2012 Sam Hemelryk
3542 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3544 class css_style_borderrightcolor
extends css_style_bordercolor
{
3547 * Initialises this style object
3549 * @param string $value The value of the style
3550 * @return css_style_borderrightcolor
3552 public static function init($value) {
3553 return new css_style_borderrightcolor('border-right-color', $value);
3557 * Consolidate this to a single border style
3561 public function consolidate_to() {
3567 * A border bottom colour style
3570 * @subpackage cssoptimiser
3571 * @copyright 2012 Sam Hemelryk
3572 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3574 class css_style_borderbottomcolor
extends css_style_bordercolor
{
3577 * Initialises this style object
3579 * @param string $value The value of the style
3580 * @return css_style_borderbottomcolor
3582 public static function init($value) {
3583 return new css_style_borderbottomcolor('border-bottom-color', $value);
3587 * Consolidate this to a single border style
3591 public function consolidate_to() {
3597 * A border width top style
3600 * @subpackage cssoptimiser
3601 * @copyright 2012 Sam Hemelryk
3602 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3604 class css_style_bordertopwidth
extends css_style_borderwidth
{
3607 * Initialises this style object
3609 * @param string $value The value of the style
3610 * @return css_style_bordertopwidth
3612 public static function init($value) {
3613 return new css_style_bordertopwidth('border-top-width', $value);
3617 * Consolidate this to a single border style
3621 public function consolidate_to() {
3627 * A border width left style
3630 * @subpackage cssoptimiser
3631 * @copyright 2012 Sam Hemelryk
3632 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3634 class css_style_borderleftwidth
extends css_style_borderwidth
{
3637 * Initialises this style object
3639 * @param string $value The value of the style
3640 * @return css_style_borderleftwidth
3642 public static function init($value) {
3643 return new css_style_borderleftwidth('border-left-width', $value);
3647 * Consolidate this to a single border style
3651 public function consolidate_to() {
3657 * A border width right style
3660 * @subpackage cssoptimiser
3661 * @copyright 2012 Sam Hemelryk
3662 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3664 class css_style_borderrightwidth
extends css_style_borderwidth
{
3667 * Initialises this style object
3669 * @param string $value The value of the style
3670 * @return css_style_borderrightwidth
3672 public static function init($value) {
3673 return new css_style_borderrightwidth('border-right-width', $value);
3677 * Consolidate this to a single border style
3681 public function consolidate_to() {
3687 * A border width bottom style
3690 * @subpackage cssoptimiser
3691 * @copyright 2012 Sam Hemelryk
3692 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3694 class css_style_borderbottomwidth
extends css_style_borderwidth
{
3697 * Initialises this style object
3699 * @param string $value The value of the style
3700 * @return css_style_borderbottomwidth
3702 public static function init($value) {
3703 return new css_style_borderbottomwidth('border-bottom-width', $value);
3707 * Consolidate this to a single border style
3711 public function consolidate_to() {
3717 * A border top style
3720 * @subpackage cssoptimiser
3721 * @copyright 2012 Sam Hemelryk
3722 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3724 class css_style_bordertopstyle
extends css_style_borderstyle
{
3727 * Initialises this style object
3729 * @param string $value The value of the style
3730 * @return css_style_bordertopstyle
3732 public static function init($value) {
3733 return new css_style_bordertopstyle('border-top-style', $value);
3737 * Consolidate this to a single border style
3741 public function consolidate_to() {
3747 * A border left style
3750 * @subpackage cssoptimiser
3751 * @copyright 2012 Sam Hemelryk
3752 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3754 class css_style_borderleftstyle
extends css_style_borderstyle
{
3757 * Initialises this style object
3759 * @param string $value The value of the style
3760 * @return css_style_borderleftstyle
3762 public static function init($value) {
3763 return new css_style_borderleftstyle('border-left-style', $value);
3767 * Consolidate this to a single border style
3771 public function consolidate_to() {
3777 * A border right style
3780 * @subpackage cssoptimiser
3781 * @copyright 2012 Sam Hemelryk
3782 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3784 class css_style_borderrightstyle
extends css_style_borderstyle
{
3787 * Initialises this style object
3789 * @param string $value The value of the style
3790 * @return css_style_borderrightstyle
3792 public static function init($value) {
3793 return new css_style_borderrightstyle('border-right-style', $value);
3797 * Consolidate this to a single border style
3801 public function consolidate_to() {
3807 * A border bottom style
3810 * @subpackage cssoptimiser
3811 * @copyright 2012 Sam Hemelryk
3812 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3814 class css_style_borderbottomstyle
extends css_style_borderstyle
{
3817 * Initialises this style object
3819 * @param string $value The value for the style
3820 * @return css_style_borderbottomstyle
3822 public static function init($value) {
3823 return new css_style_borderbottomstyle('border-bottom-style', $value);
3827 * Consolidate this to a single border style
3831 public function consolidate_to() {
3837 * A background style
3840 * @subpackage cssoptimiser
3841 * @copyright 2012 Sam Hemelryk
3842 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3844 class css_style_background
extends css_style
implements core_css_consolidatable_style
{
3847 * Initialises a background style
3849 * @param string $value The value of the style
3850 * @return array An array of background component.
3852 public static function init($value) {
3853 // Colour - image - repeat - attachment - position.
3855 if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
3856 $imageurl = trim($matches[1]);
3857 $value = str_replace($matches[1], '', $value);
3860 // Switch out the brackets so that they don't get messed up when we explode.
3861 $brackets = array();
3863 while (preg_match('#\([^\)\(]+\)#', $value, $matches)) {
3864 $key = "##BRACKET-{$bracketcount}##";
3866 $brackets[$key] = $matches[0];
3867 $value = str_replace($matches[0], $key, $value);
3870 $important = (stripos($value, '!important') !== false);
3872 // Great some genius put !important in the background shorthand property.
3873 $value = str_replace('!important', '', $value);
3876 $value = preg_replace('#\s+#', ' ', $value);
3877 $bits = explode(' ', $value);
3879 foreach ($bits as $key => $bit) {
3880 $bits[$key] = self
::replace_bracket_placeholders($bit, $brackets);
3882 unset($bracketcount);
3885 $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit');
3886 $attachments = array('scroll' , 'fixed', 'inherit');
3887 $positions = array('top', 'left', 'bottom', 'right', 'center');
3889 /* @var css_style_background[] $return */
3891 $unknownbits = array();
3893 $color = self
::NULL_VALUE
;
3894 if (count($bits) > 0 && css_is_colour(reset($bits))) {
3895 $color = array_shift($bits);
3898 $image = self
::NULL_VALUE
;
3899 if (count($bits) > 0 && preg_match('#^\s*(none|inherit|url\(\))\s*$#', reset($bits))) {
3900 $image = array_shift($bits);
3901 if ($image == 'url()') {
3902 $image = "url({$imageurl})";
3906 $repeat = self
::NULL_VALUE
;
3907 if (count($bits) > 0 && in_array(reset($bits), $repeats)) {
3908 $repeat = array_shift($bits);
3911 $attachment = self
::NULL_VALUE
;
3912 if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
3913 // Scroll , fixed, inherit.
3914 $attachment = array_shift($bits);
3917 $position = self
::NULL_VALUE
;
3918 if (count($bits) > 0) {
3919 $widthbits = array();
3920 foreach ($bits as $bit) {
3921 if (in_array($bit, $positions) ||
css_is_width($bit)) {
3922 $widthbits[] = $bit;
3924 $unknownbits[] = $bit;
3927 if (count($widthbits)) {
3928 $position = join(' ', $widthbits);
3932 if (count($unknownbits)) {
3933 foreach ($unknownbits as $bit) {
3935 if ($color === self
::NULL_VALUE
&& css_is_colour($bit)) {
3937 } else if ($repeat === self
::NULL_VALUE
&& in_array($bit, $repeats)) {
3939 } else if ($attachment === self
::NULL_VALUE
&& in_array($bit, $attachments)) {
3941 } else if ($bit !== '') {
3942 $advanced = css_style_background_advanced
::init($bit);
3944 $advanced->set_important();
3946 $return[] = $advanced;
3951 if ($color === self
::NULL_VALUE
&&
3952 $image === self
::NULL_VALUE
&&
3953 $repeat === self
::NULL_VALUE
&& $attachment === self
::NULL_VALUE
&&
3954 $position === self
::NULL_VALUE
) {
3955 // All primaries are null, return without doing anything else. There may be advanced madness there.
3959 $return[] = css_style_backgroundcolor
::init($color);
3960 $return[] = css_style_backgroundimage
::init($image);
3961 $return[] = css_style_backgroundrepeat
::init($repeat);
3962 $return[] = css_style_backgroundattachment
::init($attachment);
3963 $return[] = css_style_backgroundposition
::init($position);
3966 foreach ($return as $style) {
3967 $style->set_important();
3975 * Static helper method to switch in bracket replacements
3977 * @param string $value
3978 * @param array $placeholders
3981 protected static function replace_bracket_placeholders($value, array $placeholders) {
3982 while (preg_match('/##BRACKET-\d+##/', $value, $matches)) {
3983 $value = str_replace($matches[0], $placeholders[$matches[0]], $value);
3989 * Consolidates background styles into a single background style
3991 * @param css_style_background[] $styles Consolidates the provided array of background styles
3992 * @return css_style[] Consolidated optimised background styles
3994 public static function consolidate(array $styles) {
3996 if (empty($styles)) {
4009 $someimportant = false;
4010 $allimportant = null;
4011 foreach ($styles as $style) {
4012 if ($style instanceof css_style_backgroundimage_advanced
) {
4015 if ($style->is_important()) {
4016 $someimportant = true;
4017 if ($allimportant === null) {
4018 $allimportant = true;
4020 } else if ($allimportant === true) {
4021 $allimportant = false;
4025 /* @var css_style[] $organisedstyles */
4026 $organisedstyles = array();
4027 /* @var css_style[] $advancedstyles */
4028 $advancedstyles = array();
4029 /* @var css_style[] $importantstyles */
4030 $importantstyles = array();
4031 foreach ($styles as $style) {
4032 if ($style instanceof css_style_backgroundimage_advanced
) {
4033 $advancedstyles[] = $style;
4036 if ($someimportant && !$allimportant && $style->is_important()) {
4037 $importantstyles[] = $style;
4040 $organisedstyles[$style->get_name()] = $style;
4041 switch ($style->get_name()) {
4042 case 'background-color' :
4043 $color = css_style_color
::shrink_value($style->get_value(false));
4045 case 'background-image' :
4046 $image = $style->get_value(false);
4048 case 'background-repeat' :
4049 $repeat = $style->get_value(false);
4051 case 'background-attachment' :
4052 $attachment = $style->get_value(false);
4054 case 'background-position' :
4055 $position = $style->get_value(false);
4057 case 'background-clip' :
4058 $clip = $style->get_value();
4060 case 'background-origin' :
4061 $origin = $style->get_value();
4063 case 'background-size' :
4064 $size = $style->get_value();
4069 /* @var css_style[] $consolidatetosingle */
4070 $consolidatetosingle = array();
4071 if (!is_null($color) && !is_null($image) && !is_null($repeat) && !is_null($attachment) && !is_null($position)) {
4072 // We can use the shorthand background-style!
4073 if (!$organisedstyles['background-color']->is_special_empty_value()) {
4074 $consolidatetosingle[] = $color;
4076 if (!$organisedstyles['background-image']->is_special_empty_value()) {
4077 $consolidatetosingle[] = $image;
4079 if (!$organisedstyles['background-repeat']->is_special_empty_value()) {
4080 $consolidatetosingle[] = $repeat;
4082 if (!$organisedstyles['background-attachment']->is_special_empty_value()) {
4083 $consolidatetosingle[] = $attachment;
4085 if (!$organisedstyles['background-position']->is_special_empty_value()) {
4086 $consolidatetosingle[] = $position;
4088 // Reset them all to null so we don't use them again.
4097 // Single background style needs to come first.
4098 if (count($consolidatetosingle) > 0) {
4099 $returnstyle = new css_style_background('background', join(' ', $consolidatetosingle));
4100 if ($allimportant) {
4101 $returnstyle->set_important();
4103 $return[] = $returnstyle;
4105 foreach ($styles as $style) {
4107 switch ($style->get_name()) {
4108 case 'background-color' :
4111 case 'background-image' :
4114 case 'background-repeat' :
4117 case 'background-attachment' :
4118 $value = $attachment;
4120 case 'background-position' :
4123 case 'background-clip' :
4126 case 'background-origin':
4129 case 'background-size':
4133 if (!is_null($value)) {
4137 $return = array_merge($return, $importantstyles, $advancedstyles);
4143 * A advanced background style that allows multiple values to preserve unknown entities
4146 * @subpackage cssoptimiser
4147 * @copyright 2012 Sam Hemelryk
4148 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4150 class css_style_background_advanced
extends css_style_generic
{
4152 * Creates a new background colour style
4154 * @param string $value The value of the style
4155 * @return css_style_backgroundimage
4157 public static function init($value) {
4158 $value = preg_replace('#\s+#', ' ', $value);
4159 return new css_style_background_advanced('background', $value);
4163 * Returns true because the advanced background image supports multiple values.
4164 * e.g. -webkit-linear-gradient and -moz-linear-gradient.
4168 public function allows_multiple_values() {
4174 * A background colour style.
4176 * Based upon the colour style.
4179 * @subpackage cssoptimiser
4180 * @copyright 2012 Sam Hemelryk
4181 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4183 class css_style_backgroundcolor
extends css_style_color
{
4186 * Creates a new background colour style
4188 * @param string $value The value of the style
4189 * @return css_style_backgroundcolor
4191 public static function init($value) {
4192 return new css_style_backgroundcolor('background-color', $value);
4196 * css_style_backgroundcolor consolidates to css_style_background
4200 public function consolidate_to() {
4201 return 'background';
4205 * Returns true if the value for this style is the special null value.
4207 * This occurs if the shorthand background property was used but no proper value
4208 * was specified for this style.
4209 * This leads to a null value being used unless otherwise overridden.
4213 public function is_special_empty_value() {
4214 return ($this->value
=== self
::NULL_VALUE
);
4218 * Returns true if the value for this style is valid
4221 public function is_valid() {
4222 return $this->is_special_empty_value() || parent
::is_valid();
4227 * A background image style.
4230 * @subpackage cssoptimiser
4231 * @copyright 2012 Sam Hemelryk
4232 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4234 class css_style_backgroundimage
extends css_style_generic
{
4237 * Creates a new background image style
4239 * @param string $value The value of the style
4240 * @return css_style_backgroundimage
4242 public static function init($value) {
4243 if ($value !== self
::NULL_VALUE
&& !preg_match('#^\s*(none|inherit|url\()#i', $value)) {
4244 return css_style_backgroundimage_advanced
::init($value);
4246 return new css_style_backgroundimage('background-image', $value);
4250 * Consolidates this style into a single background style
4254 public function consolidate_to() {
4255 return 'background';
4259 * Returns true if the value for this style is the special null value.
4261 * This occurs if the shorthand background property was used but no proper value
4262 * was specified for this style.
4263 * This leads to a null value being used unless otherwise overridden.
4267 public function is_special_empty_value() {
4268 return ($this->value
=== self
::NULL_VALUE
);
4272 * Returns true if the value for this style is valid
4275 public function is_valid() {
4276 return $this->is_special_empty_value() || parent
::is_valid();
4281 * A background image style that supports multiple values and masquerades as a background-image
4284 * @subpackage cssoptimiser
4285 * @copyright 2012 Sam Hemelryk
4286 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4288 class css_style_backgroundimage_advanced
extends css_style_generic
{
4290 * Creates a new background colour style
4292 * @param string $value The value of the style
4293 * @return css_style_backgroundimage
4295 public static function init($value) {
4296 $value = preg_replace('#\s+#', ' ', $value);
4297 return new css_style_backgroundimage_advanced('background-image', $value);
4301 * Returns true because the advanced background image supports multiple values.
4302 * e.g. -webkit-linear-gradient and -moz-linear-gradient.
4306 public function allows_multiple_values() {
4312 * A background repeat style.
4315 * @subpackage cssoptimiser
4316 * @copyright 2012 Sam Hemelryk
4317 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4319 class css_style_backgroundrepeat
extends css_style_generic
{
4322 * Creates a new background colour style
4324 * @param string $value The value of the style
4325 * @return css_style_backgroundrepeat
4327 public static function init($value) {
4328 return new css_style_backgroundrepeat('background-repeat', $value);
4332 * Consolidates this style into a single background style
4336 public function consolidate_to() {
4337 return 'background';
4341 * Returns true if the value for this style is the special null value.
4343 * This occurs if the shorthand background property was used but no proper value
4344 * was specified for this style.
4345 * This leads to a null value being used unless otherwise overridden.
4349 public function is_special_empty_value() {
4350 return ($this->value
=== self
::NULL_VALUE
);
4354 * Returns true if the value for this style is valid
4357 public function is_valid() {
4358 return $this->is_special_empty_value() || parent
::is_valid();
4363 * A background attachment style.
4366 * @subpackage cssoptimiser
4367 * @copyright 2012 Sam Hemelryk
4368 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4370 class css_style_backgroundattachment
extends css_style_generic
{
4373 * Creates a new background colour style
4375 * @param string $value The value of the style
4376 * @return css_style_backgroundattachment
4378 public static function init($value) {
4379 return new css_style_backgroundattachment('background-attachment', $value);
4383 * Consolidates this style into a single background style
4387 public function consolidate_to() {
4388 return 'background';
4392 * Returns true if the value for this style is the special null value.
4394 * This occurs if the shorthand background property was used but no proper value
4395 * was specified for this style.
4396 * This leads to a null value being used unless otherwise overridden.
4400 public function is_special_empty_value() {
4401 return ($this->value
=== self
::NULL_VALUE
);
4405 * Returns true if the value for this style is valid
4408 public function is_valid() {
4409 return $this->is_special_empty_value() || parent
::is_valid();
4414 * A background position style.
4417 * @subpackage cssoptimiser
4418 * @copyright 2012 Sam Hemelryk
4419 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4421 class css_style_backgroundposition
extends css_style_generic
{
4424 * Creates a new background colour style
4426 * @param string $value The value of the style
4427 * @return css_style_backgroundposition
4429 public static function init($value) {
4430 return new css_style_backgroundposition('background-position', $value);
4434 * Consolidates this style into a single background style
4438 public function consolidate_to() {
4439 return 'background';
4443 * Returns true if the value for this style is the special null value.
4445 * This occurs if the shorthand background property was used but no proper value
4446 * was specified for this style.
4447 * This leads to a null value being used unless otherwise overridden.
4451 public function is_special_empty_value() {
4452 return ($this->value
=== self
::NULL_VALUE
);
4456 * Returns true if the value for this style is valid
4459 public function is_valid() {
4460 return $this->is_special_empty_value() || parent
::is_valid();
4465 * A background size style.
4468 * @subpackage cssoptimiser
4469 * @copyright 2012 Sam Hemelryk
4470 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4472 class css_style_backgroundsize
extends css_style_generic
{
4475 * Creates a new background size style
4477 * @param string $value The value of the style
4478 * @return css_style_backgroundposition
4480 public static function init($value) {
4481 return new css_style_backgroundsize('background-size', $value);
4485 * Consolidates this style into a single background style
4489 public function consolidate_to() {
4490 return 'background';
4495 * A background clip style.
4498 * @subpackage cssoptimiser
4499 * @copyright 2012 Sam Hemelryk
4500 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4502 class css_style_backgroundclip
extends css_style_generic
{
4505 * Creates a new background clip style
4507 * @param string $value The value of the style
4508 * @return css_style_backgroundposition
4510 public static function init($value) {
4511 return new css_style_backgroundclip('background-clip', $value);
4515 * Consolidates this style into a single background style
4519 public function consolidate_to() {
4520 return 'background';
4525 * A background origin style.
4528 * @subpackage cssoptimiser
4529 * @copyright 2012 Sam Hemelryk
4530 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4532 class css_style_backgroundorigin
extends css_style_generic
{
4535 * Creates a new background origin style
4537 * @param string $value The value of the style
4538 * @return css_style_backgroundposition
4540 public static function init($value) {
4541 return new css_style_backgroundorigin('background-origin', $value);
4545 * Consolidates this style into a single background style
4549 public function consolidate_to() {
4550 return 'background';
4558 * @subpackage cssoptimiser
4559 * @copyright 2012 Sam Hemelryk
4560 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4562 class css_style_padding
extends css_style_width
implements core_css_consolidatable_style
{
4565 * Initialises this padding style into several individual padding styles
4567 * @param string $value The value fo the style
4568 * @return array An array of padding styles
4570 public static function init($value) {
4572 if (strpos($value, '!important') !== false) {
4573 $important = ' !important';
4574 $value = str_replace('!important', '', $value);
4577 $value = preg_replace('#\s+#', ' ', trim($value));
4578 $bits = explode(' ', $value, 4);
4580 $top = $right = $bottom = $left = null;
4581 if (count($bits) > 0) {
4582 $top = $right = $bottom = $left = array_shift($bits);
4584 if (count($bits) > 0) {
4585 $right = $left = array_shift($bits);
4587 if (count($bits) > 0) {
4588 $bottom = array_shift($bits);
4590 if (count($bits) > 0) {
4591 $left = array_shift($bits);
4594 new css_style_paddingtop('padding-top', $top.$important),
4595 new css_style_paddingright('padding-right', $right.$important),
4596 new css_style_paddingbottom('padding-bottom', $bottom.$important),
4597 new css_style_paddingleft('padding-left', $left.$important)
4602 * Consolidates several padding styles into a single style.
4604 * @param css_style_padding[] $styles Array of padding styles
4605 * @return css_style[] Optimised+consolidated array of padding styles
4607 public static function consolidate(array $styles) {
4608 if (count($styles) != 4) {
4612 $someimportant = false;
4613 $allimportant = null;
4614 $notimportantequal = null;
4616 foreach ($styles as $style) {
4617 if ($style->is_important()) {
4618 $someimportant = true;
4619 if ($allimportant === null) {
4620 $allimportant = true;
4623 if ($allimportant === true) {
4624 $allimportant = false;
4626 if ($firstvalue == null) {
4627 $firstvalue = $style->get_value(false);
4628 $notimportantequal = true;
4629 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
4630 $notimportantequal = false;
4635 if ($someimportant && !$allimportant && !$notimportantequal) {
4639 if ($someimportant && !$allimportant && $notimportantequal) {
4641 new css_style_padding('padding', $firstvalue)
4643 foreach ($styles as $style) {
4644 if ($style->is_important()) {
4654 foreach ($styles as $style) {
4655 switch ($style->get_name()) {
4656 case 'padding-top' :
4657 $top = $style->get_value(false);
4659 case 'padding-right' :
4660 $right = $style->get_value(false);
4662 case 'padding-bottom' :
4663 $bottom = $style->get_value(false);
4665 case 'padding-left' :
4666 $left = $style->get_value(false);
4670 if ($top == $bottom && $left == $right) {
4671 if ($top == $left) {
4672 $returnstyle = new css_style_padding('padding', $top);
4674 $returnstyle = new css_style_padding('padding', "{$top} {$left}");
4676 } else if ($left == $right) {
4677 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom}");
4679 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}");
4681 if ($allimportant) {
4682 $returnstyle->set_important();
4684 return array($returnstyle);
4690 * A padding top style.
4693 * @subpackage cssoptimiser
4694 * @copyright 2012 Sam Hemelryk
4695 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4697 class css_style_paddingtop
extends css_style_padding
{
4700 * Initialises this style
4702 * @param string $value The value of the style
4703 * @return css_style_paddingtop
4705 public static function init($value) {
4706 return new css_style_paddingtop('padding-top', $value);
4710 * Consolidates this style into a single padding style
4714 public function consolidate_to() {
4720 * A padding right style.
4723 * @subpackage cssoptimiser
4724 * @copyright 2012 Sam Hemelryk
4725 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4727 class css_style_paddingright
extends css_style_padding
{
4730 * Initialises this style
4732 * @param string $value The value of the style
4733 * @return css_style_paddingright
4735 public static function init($value) {
4736 return new css_style_paddingright('padding-right', $value);
4740 * Consolidates this style into a single padding style
4744 public function consolidate_to() {
4750 * A padding bottom style.
4753 * @subpackage cssoptimiser
4754 * @copyright 2012 Sam Hemelryk
4755 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4757 class css_style_paddingbottom
extends css_style_padding
{
4760 * Initialises this style
4762 * @param string $value The value of the style
4763 * @return css_style_paddingbottom
4765 public static function init($value) {
4766 return new css_style_paddingbottom('padding-bottom', $value);
4770 * Consolidates this style into a single padding style
4774 public function consolidate_to() {
4780 * A padding left style.
4783 * @subpackage cssoptimiser
4784 * @copyright 2012 Sam Hemelryk
4785 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4787 class css_style_paddingleft
extends css_style_padding
{
4790 * Initialises this style
4792 * @param string $value The value of the style
4793 * @return css_style_paddingleft
4795 public static function init($value) {
4796 return new css_style_paddingleft('padding-left', $value);
4800 * Consolidates this style into a single padding style
4804 public function consolidate_to() {
4813 * @subpackage cssoptimiser
4814 * @copyright 2012 Sam Hemelryk
4815 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4817 class css_style_cursor
extends css_style_generic
{
4819 * Initialises a new cursor style
4820 * @param string $value
4821 * @return css_style_cursor
4823 public static function init($value) {
4824 return new css_style_cursor('cursor', $value);
4827 * Cleans the given value and returns it.
4829 * @param string $value
4832 protected function clean_value($value) {
4833 // Allowed values for the cursor style.
4834 $allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
4835 'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
4836 // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough.
4837 if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
4838 $this->set_error('Invalid or unexpected cursor value specified: '.$value);
4840 return trim($value);
4845 * A vertical alignment style.
4848 * @subpackage cssoptimiser
4849 * @copyright 2012 Sam Hemelryk
4850 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4852 class css_style_verticalalign
extends css_style_generic
{
4854 * Initialises a new vertical alignment style
4855 * @param string $value
4856 * @return css_style_verticalalign
4858 public static function init($value) {
4859 return new css_style_verticalalign('vertical-align', $value);
4862 * Cleans the given value and returns it.
4864 * @param string $value
4867 protected function clean_value($value) {
4868 $allowed = array('baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom', 'inherit');
4869 if (!css_is_width($value) && !in_array($value, $allowed)) {
4870 $this->set_error('Invalid vertical-align value specified: '.$value);
4872 return trim($value);
4880 * @subpackage cssoptimiser
4881 * @copyright 2012 Sam Hemelryk
4882 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4884 class css_style_float
extends css_style_generic
{
4886 * Initialises a new float style
4887 * @param string $value
4888 * @return css_style_float
4890 public static function init($value) {
4891 return new css_style_float('float', $value);
4894 * Cleans the given value and returns it.
4896 * @param string $value
4899 protected function clean_value($value) {
4900 $allowed = array('left', 'right', 'none', 'inherit');
4901 if (!css_is_width($value) && !in_array($value, $allowed)) {
4902 $this->set_error('Invalid float value specified: '.$value);
4904 return trim($value);