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.
22 * NOTE: these functions are not expected to be used from any addons.
25 * @subpackage cssoptimiser
26 * @copyright 2012 Sam Hemelryk
27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30 defined('MOODLE_INTERNAL') ||
die();
32 if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) {
33 // This can be also set in config.php file,
34 // it needs to be higher than the time it takes to generate all CSS content.
35 define('THEME_DESIGNER_CACHE_LIFETIME', 10);
39 * Stores CSS in a file at the given path.
41 * This function either succeeds or throws an exception.
43 * @param theme_config $theme The theme that the CSS belongs to.
44 * @param string $csspath The path to store the CSS at.
45 * @param string $csscontent the complete CSS in one string
46 * @param bool $chunk If set to true these files will be chunked to ensure
47 * that no one file contains more than 4095 selectors.
48 * @param string $chunkurl If the CSS is be chunked then we need to know the URL
49 * to use for the chunked files.
51 function css_store_css(theme_config
$theme, $csspath, $csscontent, $chunk = false, $chunkurl = null) {
55 if (!file_exists(dirname($csspath))) {
56 @mkdir
(dirname($csspath), $CFG->directorypermissions
, true);
59 // Prevent serving of incomplete file from concurrent request,
60 // the rename() should be more atomic than fwrite().
61 ignore_user_abort(true);
63 // First up write out the single file for all those using decent browsers.
64 css_write_file($csspath, $csscontent);
67 // If we need to chunk the CSS for browsers that are sub-par.
68 $css = css_chunk_by_selector_count($csscontent, $chunkurl);
71 foreach ($css as $content) {
72 if ($count === $files) {
73 // If there is more than one file and this IS the last file.
74 $filename = preg_replace('#\.css$#', '.0.css', $csspath);
76 // If there is more than one file and this is not the last file.
77 $filename = preg_replace('#\.css$#', '.'.$count.'.css', $csspath);
80 css_write_file($filename, $content);
84 ignore_user_abort(false);
85 if (connection_aborted()) {
93 * @param string $filename
94 * @param string $content
96 function css_write_file($filename, $content) {
98 if ($fp = fopen($filename.'.tmp', 'xb')) {
99 fwrite($fp, $content);
101 rename($filename.'.tmp', $filename);
102 @chmod
($filename, $CFG->filepermissions
);
103 @unlink
($filename.'.tmp'); // Just in case anything fails.
108 * Takes CSS and chunks it if the number of selectors within it exceeds $maxselectors.
110 * The chunking will not split a group of selectors, or a media query. That means that
111 * if n > $maxselectors and there are n selectors grouped together,
112 * they will not be chunked and you could end up with more selectors than desired.
113 * The same applies for a media query that has more than n selectors.
115 * Also, as we do not split group of selectors or media queries, the chunking might
116 * not be as optimal as it could be, having files with less selectors than it could
117 * potentially contain.
119 * String functions used here are not compliant with unicode characters. But that is
120 * not an issue as the syntax of CSS is using ASCII codes. Even if we have unicode
121 * characters in comments, or in the property 'content: ""', it will behave correcly.
123 * Please note that this strips out the comments if chunking happens.
125 * @param string $css The CSS to chunk.
126 * @param string $importurl The URL to use for import statements.
127 * @param int $maxselectors The number of selectors to limit a chunk to.
128 * @param int $buffer Not used any more.
129 * @return array An array of CSS chunks.
131 function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $buffer = 50) {
133 // Check if we need to chunk this CSS file.
134 $count = substr_count($css, ',') +
substr_count($css, '{');
135 if ($count < $maxselectors) {
136 // The number of selectors is less then the max - we're fine.
140 $chunks = array(); // The final chunks.
141 $offsets = array(); // The indexes to chunk at.
142 $offset = 0; // The current offset.
143 $selectorcount = 0; // The number of selectors since the last split.
144 $lastvalidoffset = 0; // The last valid index to split at.
145 $lastvalidoffsetselectorcount = 0; // The number of selectors used at the time were could split.
146 $inrule = 0; // The number of rules we are in, should not be greater than 1.
147 $inmedia = false; // Whether or not we are in a media query.
148 $mediacoming = false; // Whether or not we are expeting a media query.
149 $currentoffseterror = null; // Not null when we have recorded an error for the current split.
150 $offseterrors = array(); // The offsets where we found errors.
152 // Remove the comments. Because it's easier, safer and probably a lot of other good reasons.
153 $css = preg_replace('#/\*(.*?)\*/#s', '', $css);
154 $strlen = strlen($css);
156 // Walk through the CSS content character by character.
157 for ($i = 1; $i <= $strlen; $i++
) {
158 $char = $css[$i - 1];
161 // Is that a media query that I see coming towards us?
163 if (!$inmedia && substr($css, $offset, 5) === 'media') {
168 // So we are entering a rule or a media query...
172 $mediacoming = false;
179 // Let's count the number of selectors, but only if we are not in a rule, or in
180 // the definition of a media query, as they can contain commas too.
181 if (!$mediacoming && !$inrule && $char === ',') {
185 // We reached the end of something.
187 // Oh, we are in a media query.
190 // This is the end of the media query.
193 // We were in a rule, in the media query.
198 // Handle stupid broken CSS where there are too many } brackets,
199 // as this can cause it to break (with chunking) where it would
200 // coincidentally have worked otherwise.
206 // We are not in a media query, and there is no pending rule, it is safe to split here.
207 if (!$inmedia && !$inrule) {
208 $lastvalidoffset = $offset;
209 $lastvalidoffsetselectorcount = $selectorcount;
213 // Alright, this is splitting time...
214 if ($selectorcount > $maxselectors) {
215 if (!$lastvalidoffset) {
216 // We must have reached more selectors into one set than we were allowed. That means that either
217 // the chunk size value is too small, or that we have a gigantic group of selectors, or that a media
218 // query contains more selectors than the chunk size. We have to ignore this because we do not
219 // support split inside a group of selectors or media query.
220 if ($currentoffseterror === null) {
221 $currentoffseterror = $offset;
222 $offseterrors[] = $currentoffseterror;
225 // We identify the offset to split at and reset the number of selectors found from there.
226 $offsets[] = $lastvalidoffset;
227 $selectorcount = $selectorcount - $lastvalidoffsetselectorcount;
228 $lastvalidoffset = 0;
229 $currentoffseterror = null;
234 // Report offset errors.
235 if (!empty($offseterrors)) {
236 debugging('Could not find a safe place to split at offset(s): ' . implode(', ', $offseterrors) . '. Those were ignored.',
240 // Now that we have got the offets, we can chunk the CSS.
241 $offsetcount = count($offsets);
242 foreach ($offsets as $key => $index) {
245 $start = $offsets[$key - 1];
247 // From somewhere up to the offset.
248 $chunks[] = substr($css, $start, $index - $start);
250 // Add the last chunk (if there is one), from the last offset to the end of the string.
251 if (end($offsets) != $strlen) {
252 $chunks[] = substr($css, end($offsets));
255 // The array $chunks now contains CSS split into perfect sized chunks.
256 // Import statements can only appear at the very top of a CSS file.
257 // Imported sheets are applied in the the order they are imported and
258 // are followed by the contents of the CSS.
259 // This is terrible for performance.
260 // It means we must put the import statements at the top of the last chunk
261 // to ensure that things are always applied in the correct order.
262 // This way the chunked files are included in the order they were chunked
263 // followed by the contents of the final chunk in the actual sheet.
265 $slashargs = strpos($importurl, '.php?') === false;
266 $parts = count($chunks);
267 for ($i = 1; $i < $parts; $i++
) {
269 $importcss .= "@import url({$importurl}/chunk{$i});\n";
271 $importcss .= "@import url({$importurl}&chunk={$i});\n";
274 $importcss .= end($chunks);
275 $chunks[key($chunks)] = $importcss;
281 * Sends a cached CSS file
283 * This function sends the cached CSS file. Remember it is generated on the first
284 * request, then optimised/minified, and finally cached for serving.
286 * @param string $csspath The path to the CSS file we want to serve.
287 * @param string $etag The revision to make sure we utilise any caches.
289 function css_send_cached_css($csspath, $etag) {
290 // 60 days only - the revision may get incremented quite often.
291 $lifetime = 60*60*24*60;
293 header('Etag: "'.$etag.'"');
294 header('Content-Disposition: inline; filename="styles.php"');
295 header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
296 header('Expires: '. gmdate('D, d M Y H:i:s', time() +
$lifetime) .' GMT');
298 header('Cache-Control: public, max-age='.$lifetime);
299 header('Accept-Ranges: none');
300 header('Content-Type: text/css; charset=utf-8');
301 if (!min_enable_zlib_compression()) {
302 header('Content-Length: '.filesize($csspath));
310 * Sends a cached CSS content
312 * @param string $csscontent The actual CSS markup.
313 * @param string $etag The revision to make sure we utilise any caches.
315 function css_send_cached_css_content($csscontent, $etag) {
316 // 60 days only - the revision may get incremented quite often.
317 $lifetime = 60*60*24*60;
319 header('Etag: "'.$etag.'"');
320 header('Content-Disposition: inline; filename="styles.php"');
321 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
322 header('Expires: '. gmdate('D, d M Y H:i:s', time() +
$lifetime) .' GMT');
324 header('Cache-Control: public, max-age='.$lifetime);
325 header('Accept-Ranges: none');
326 header('Content-Type: text/css; charset=utf-8');
327 if (!min_enable_zlib_compression()) {
328 header('Content-Length: '.strlen($csscontent));
336 * Sends CSS directly without caching it.
338 * This function takes a raw CSS string, optimises it if required, and then
340 * Turning both themedesignermode and CSS optimiser on at the same time is awful
341 * for performance because of the optimiser running here. However it was done so
342 * that theme designers could utilise the optimised output during development to
343 * help them optimise their CSS... not that they should write lazy CSS.
347 function css_send_uncached_css($css) {
348 header('Content-Disposition: inline; filename="styles_debug.php"');
349 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
350 header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME
) .' GMT');
352 header('Accept-Ranges: none');
353 header('Content-Type: text/css; charset=utf-8');
355 if (is_array($css)) {
356 $css = implode("\n\n", $css);
363 * Send file not modified headers
365 * @param int $lastmodified
366 * @param string $etag
368 function css_send_unmodified($lastmodified, $etag) {
369 // 60 days only - the revision may get incremented quite often.
370 $lifetime = 60*60*24*60;
371 header('HTTP/1.1 304 Not Modified');
372 header('Expires: '. gmdate('D, d M Y H:i:s', time() +
$lifetime) .' GMT');
373 header('Cache-Control: public, max-age='.$lifetime);
374 header('Content-Type: text/css; charset=utf-8');
375 header('Etag: "'.$etag.'"');
377 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
383 * Sends a 404 message about CSS not being found.
385 function css_send_css_not_found() {
386 header('HTTP/1.0 404 not found');
387 die('CSS was not found, sorry.');
391 * Determines if the given value is a valid CSS colour.
393 * A CSS colour can be one of the following:
394 * - Hex colour: #AA66BB
395 * - RGB colour: rgb(0-255, 0-255, 0-255)
396 * - RGBA colour: rgba(0-255, 0-255, 0-255, 0-1)
397 * - HSL colour: hsl(0-360, 0-100%, 0-100%)
398 * - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1)
400 * Or a recognised browser colour mapping {@link css_optimiser::$htmlcolours}
402 * @param string $value The colour value to check
405 function css_is_colour($value) {
406 $value = trim($value);
408 $hex = '/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/';
409 $rgb = '#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i';
410 $rgba = '#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
411 $hsl = '#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i';
412 $hsla = '#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i';
414 if (in_array(strtolower($value), array('inherit'))) {
416 } else if (preg_match($hex, $value)) {
418 } else if (in_array(strtolower($value), array_keys(css_optimiser
::$htmlcolours))) {
420 } else if (preg_match($rgb, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
421 // It is an RGB colour.
423 } else if (preg_match($rgba, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
424 // It is an RGBA colour.
426 } else if (preg_match($hsl, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
427 // It is an HSL colour.
429 } else if (preg_match($hsla, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
430 // It is an HSLA colour.
433 // Doesn't look like a colour.
438 * Returns true is the passed value looks like a CSS width.
439 * In order to pass this test the value must be purely numerical or end with a
440 * valid CSS unit term.
442 * @param string|int $value
445 function css_is_width($value) {
446 $value = trim($value);
447 if (in_array(strtolower($value), array('auto', 'inherit'))) {
450 if ((string)$value === '0' ||
preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) {
457 * A simple sorting function to sort two array values on the number of items they contain
463 function css_sort_by_count(array $a, array $b) {
469 return ($a > $b) ?
-1 : 1;
473 * A basic CSS optimiser that strips out unwanted things and then processes CSS organising and cleaning styles.
475 * This CSS optimiser works by reading through a CSS string one character at a
476 * time and building an object structure of the CSS.
477 * As part of that processing styles are expanded out as much as they can be to
478 * ensure we collect all mappings, at the end of the processing those styles are
479 * then combined into an optimised form to keep them as short as possible.
482 * @subpackage cssoptimiser
483 * @copyright 2012 Sam Hemelryk
484 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
486 class css_optimiser
{
489 * Used when the processor is about to start processing.
490 * Processing states. Used internally.
492 const PROCESSING_START
= 0;
495 * Used when the processor is currently processing a selector.
496 * Processing states. Used internally.
498 const PROCESSING_SELECTORS
= 0;
501 * Used when the processor is currently processing a style.
502 * Processing states. Used internally.
504 const PROCESSING_STYLES
= 1;
507 * Used when the processor is currently processing a comment.
508 * Processing states. Used internally.
510 const PROCESSING_COMMENT
= 2;
513 * Used when the processor is currently processing an @ rule.
514 * Processing states. Used internally.
516 const PROCESSING_ATRULE
= 3;
519 * The raw string length before optimisation.
520 * Stats variables set during and after processing
523 protected $rawstrlen = 0;
526 * The number of comments that were removed during optimisation.
527 * Stats variables set during and after processing
530 protected $commentsincss = 0;
533 * The number of rules in the CSS before optimisation.
534 * Stats variables set during and after processing
537 protected $rawrules = 0;
540 * The number of selectors using in CSS rules before optimisation.
541 * Stats variables set during and after processing
544 protected $rawselectors = 0;
547 * The string length after optimisation.
548 * Stats variables set during and after processing
551 protected $optimisedstrlen = 0;
554 * The number of rules after optimisation.
555 * Stats variables set during and after processing
558 protected $optimisedrules = 0;
561 * The number of selectors used in rules after optimisation.
562 * Stats variables set during and after processing
565 protected $optimisedselectors = 0;
568 * The start time of the optimisation.
569 * Stats variables set during and after processing
572 protected $timestart = 0;
575 * The end time of the optimisation.
576 * Stats variables set during and after processing
579 protected $timecomplete = 0;
582 * Will be set to any errors that may have occured during processing.
583 * This is updated only at the end of processing NOT during.
587 protected $errors = array();
590 * Processes incoming CSS optimising it and then returning it.
592 * @param string $css The raw CSS to optimise
593 * @return string The optimised CSS
595 public function process($css) {
596 // Easiest win there is.
599 $this->reset_stats();
600 $this->timestart
= microtime(true);
601 $this->rawstrlen
= strlen($css);
603 // Don't try to process files with no content... it just doesn't make sense.
604 // But we should produce an error for them, an empty CSS file will lead to a
605 // useless request for those running theme designer mode.
606 if ($this->rawstrlen
=== 0) {
607 $this->errors
[] = 'Skipping file as it has no content.';
611 // First up we need to remove all line breaks - this allows us to instantly
612 // reduce our processing requirements and as we will process everything
613 // into a new structure there's really nothing lost.
614 $css = preg_replace('#\r?\n#', ' ', $css);
616 // Next remove the comments... no need to them in an optimised world and
617 // knowing they're all gone allows us to REALLY make our processing simpler.
618 $css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss
);
621 'all' => new css_media()
625 // Keyframes are used for CSS animation they will be processed right at the very end.
626 $keyframes = array();
628 $currentprocess = self
::PROCESSING_START
;
629 $currentrule = css_rule
::init();
630 $currentselector = css_selector
::init();
631 $inquotes = false; // ' or "
632 $inbraces = false; // {
633 $inbrackets = false; // [
634 $inparenthesis = false; // (
635 /* @var css_media $currentmedia */
636 $currentmedia = $medias['all'];
637 $currentatrule = null;
638 $suspectatrule = false;
643 // Next we are going to iterate over every single character in $css.
644 // This is why we removed line breaks and comments!
645 for ($i = 0; $i < $this->rawstrlen
; $i++
) {
647 $char = substr($css, $i, 1);
648 if ($char == '@' && $buffer == '') {
649 $suspectatrule = true;
651 switch ($currentprocess) {
652 // Start processing an @ rule e.g. @media, @page, @keyframes.
653 case self
::PROCESSING_ATRULE
:
658 if ($currentatrule == 'import') {
659 $imports[] = $buffer;
660 $currentprocess = self
::PROCESSING_SELECTORS
;
661 } else if ($currentatrule == 'charset') {
663 $currentprocess = self
::PROCESSING_SELECTORS
;
666 if ($currentatrule !== 'media') {
668 $currentatrule = false;
670 // Continue 1: The switch processing chars
671 // Continue 2: The switch processing the state
672 // Continue 3: The for loop.
675 $regexmediabasic = '#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#';
676 $regexadvmedia = '#\s*@media\s*([^{]+)#';
677 $regexkeyframes = '#@((\-moz\-|\-webkit\-|\-ms\-|\-o\-)?keyframes)\s*([^\s]+)#';
679 if ($currentatrule == 'media' && preg_match($regexmediabasic, $buffer, $matches)) {
680 // Basic media declaration.
681 $mediatypes = str_replace(' ', '', $matches[1]);
682 if (!array_key_exists($mediatypes, $medias)) {
683 $medias[$mediatypes] = new css_media($mediatypes);
685 $currentmedia = $medias[$mediatypes];
686 $currentprocess = self
::PROCESSING_SELECTORS
;
688 } else if ($currentatrule == 'media' && preg_match($regexadvmedia, $buffer, $matches)) {
689 // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/.
690 $mediatypes = $matches[1];
691 $hash = md5($mediatypes);
692 $medias[$hash] = new css_media($mediatypes);
693 $currentmedia = $medias[$hash];
694 $currentprocess = self
::PROCESSING_SELECTORS
;
696 } else if ($currentatrule == 'keyframes' && preg_match($regexkeyframes, $buffer, $matches)) {
697 // Keyframes declaration, we treat it exactly like a @media declaration except we don't allow
698 // them to be overridden to ensure we don't mess anything up. (means we keep everything in order).
699 $keyframefor = $matches[1];
700 $keyframename = $matches[3];
701 $keyframe = new css_keyframe($keyframefor, $keyframename);
702 $keyframes[] = $keyframe;
703 $currentmedia = $keyframe;
704 $currentprocess = self
::PROCESSING_SELECTORS
;
707 // Continue 1: The switch processing chars
708 // Continue 2: The switch processing the state
709 // Continue 3: The for loop.
713 // Start processing selectors.
714 case self
::PROCESSING_START
:
715 case self
::PROCESSING_SELECTORS
:
716 $regexatrule = '#@(media|import|charset|(\-moz\-|\-webkit\-|\-ms\-|\-o\-)?(keyframes))\s*#';
721 // Continue 1: The switch processing chars
722 // Continue 2: The switch processing the state
723 // Continue 3: The for loop.
728 // Continue 1: The switch processing chars
729 // Continue 2: The switch processing the state
730 // Continue 3: The for loop.
734 // Continue 1: The switch processing chars
735 // Continue 2: The switch processing the state
736 // Continue 3: The for loop.
739 if (!empty($buffer)) {
740 // Check for known @ rules.
741 if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
742 $currentatrule = (!empty($matches[3]))?
$matches[3]:$matches[1];
743 $currentprocess = self
::PROCESSING_ATRULE
;
746 $currentselector->add($buffer);
750 $suspectatrule = false;
751 // Continue 1: The switch processing chars
752 // Continue 2: The switch processing the state
753 // Continue 3: The for loop.
757 // Continue 1: The switch processing chars
758 // Continue 2: The switch processing the state
759 // Continue 3: The for loop.
762 // Check for known @ rules.
763 if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) {
764 // Ahh we've been in an @rule, lets rewind one and have the @rule case process this.
765 $currentatrule = (!empty($matches[3]))?
$matches[3]:$matches[1];
766 $currentprocess = self
::PROCESSING_ATRULE
;
768 $suspectatrule = false;
769 // Continue 1: The switch processing chars
770 // Continue 2: The switch processing the state
771 // Continue 3: The for loop.
774 if ($buffer !== '') {
775 $currentselector->add($buffer);
777 $currentrule->add_selector($currentselector);
778 $currentselector = css_selector
::init();
779 $currentprocess = self
::PROCESSING_STYLES
;
782 // Continue 1: The switch processing chars
783 // Continue 2: The switch processing the state
784 // Continue 3: The for loop.
788 // Continue 1: The switch processing chars
789 // Continue 2: The switch processing the state
790 // Continue 3: The for loop.
793 if ($currentatrule == 'media') {
794 $currentmedia = $medias['all'];
795 $currentatrule = false;
797 } else if (strpos($currentatrule, 'keyframes') !== false) {
798 $currentmedia = $medias['all'];
799 $currentatrule = false;
802 // Continue 1: The switch processing chars
803 // Continue 2: The switch processing the state
804 // Continue 3: The for loop.
808 // Continue 1: The switch processing chars
809 // Continue 2: The switch processing the state
810 // Continue 3: The for loop.
813 $currentselector->add($buffer);
814 $currentrule->add_selector($currentselector);
815 $currentselector = css_selector
::init();
817 // Continue 1: The switch processing chars
818 // Continue 2: The switch processing the state
819 // Continue 3: The for loop.
823 // Start processing styles.
824 case self
::PROCESSING_STYLES
:
825 if ($char == '"' ||
$char == "'") {
826 if ($inquotes === false) {
829 if ($inquotes === $char && $lastchar !== '\\') {
839 if ($inparenthesis) {
841 // Continue 1: The switch processing chars
842 // Continue 2: The switch processing the state
843 // Continue 3: The for loop.
846 $currentrule->add_style($buffer);
849 // Continue 1: The switch processing chars
850 // Continue 2: The switch processing the state
851 // Continue 3: The for loop.
854 $currentrule->add_style($buffer);
855 $this->rawselectors +
= $currentrule->get_selector_count();
857 $currentmedia->add_rule($currentrule);
859 $currentrule = css_rule
::init();
860 $currentprocess = self
::PROCESSING_SELECTORS
;
864 $inparenthesis = false;
865 // Continue 1: The switch processing chars
866 // Continue 2: The switch processing the state
867 // Continue 3: The for loop.
870 $inparenthesis = true;
872 // Continue 1: The switch processing chars
873 // Continue 2: The switch processing the state
874 // Continue 3: The for loop.
877 $inparenthesis = false;
879 // Continue 1: The switch processing chars
880 // Continue 2: The switch processing the state
881 // Continue 3: The for loop.
889 foreach ($medias as $media) {
890 $this->optimise($media);
892 $css = $this->produce_css($charset, $imports, $medias, $keyframes);
894 $this->timecomplete
= microtime(true);
899 * Produces CSS for the given charset, imports, media, and keyframes
900 * @param string $charset
901 * @param array $imports
902 * @param css_media[] $medias
903 * @param css_keyframe[] $keyframes
906 protected function produce_css($charset, array $imports, array $medias, array $keyframes) {
908 if (!empty($charset)) {
909 $imports[] = $charset;
911 if (!empty($imports)) {
912 $css .= implode("\n", $imports);
917 $cssstandard = array();
918 $csskeyframes = array();
920 // Process each media declaration individually.
921 foreach ($medias as $media) {
922 // If this declaration applies to all media types.
923 if (in_array('all', $media->get_types())) {
924 // Collect all rules that represet reset rules and remove them from the media object at the same time.
925 // We do this because we prioritise reset rules to the top of a CSS output. This ensures that they
926 // can't end up out of order because of optimisation.
927 $resetrules = $media->get_reset_rules(true);
928 if (!empty($resetrules)) {
929 $cssreset[] = css_writer
::media('all', $resetrules);
932 // Get the standard cSS.
933 $cssstandard[] = $media->out();
936 // Finally if there are any keyframe declarations process them now.
937 if (count($keyframes) > 0) {
938 foreach ($keyframes as $keyframe) {
939 $this->optimisedrules +
= $keyframe->count_rules();
940 $this->optimisedselectors +
= $keyframe->count_selectors();
941 if ($keyframe->has_errors()) {
942 $this->errors +
= $keyframe->get_errors();
944 $csskeyframes[] = $keyframe->out();
948 // Join it all together.
949 $css .= join('', $cssreset);
950 $css .= join('', $cssstandard);
951 $css .= join('', $csskeyframes);
953 // Record the strlenght of the now optimised CSS.
954 $this->optimisedstrlen
= strlen($css);
956 // Return the now produced CSS.
961 * Optimises the CSS rules within a rule collection of one form or another
963 * @param css_rule_collection $media
964 * @return void This function acts in reference
966 protected function optimise(css_rule_collection
$media) {
967 $media->organise_rules_by_selectors();
968 $this->optimisedrules +
= $media->count_rules();
969 $this->optimisedselectors +
= $media->count_selectors();
970 if ($media->has_errors()) {
971 $this->errors +
= $media->get_errors();
976 * Returns an array of stats from the last processing run
979 public function get_stats() {
981 'timestart' => $this->timestart
,
982 'timecomplete' => $this->timecomplete
,
983 'timetaken' => round($this->timecomplete
- $this->timestart
, 4),
984 'commentsincss' => $this->commentsincss
,
985 'rawstrlen' => $this->rawstrlen
,
986 'rawselectors' => $this->rawselectors
,
987 'rawrules' => $this->rawrules
,
988 'optimisedstrlen' => $this->optimisedstrlen
,
989 'optimisedrules' => $this->optimisedrules
,
990 'optimisedselectors' => $this->optimisedselectors
,
991 'improvementstrlen' => '-',
992 'improvementrules' => '-',
993 'improvementselectors' => '-',
995 // Avoid division by 0 errors by checking we have valid raw values.
996 if ($this->rawstrlen
> 0) {
997 $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen
/ $this->rawstrlen
) * 100, 1).'%';
999 if ($this->rawrules
> 0) {
1000 $stats['improvementrules'] = round(100 - ($this->optimisedrules
/ $this->rawrules
) * 100, 1).'%';
1002 if ($this->rawselectors
> 0) {
1003 $stats['improvementselectors'] = round(100 - ($this->optimisedselectors
/ $this->rawselectors
) * 100, 1).'%';
1009 * Returns true if any errors have occured during processing
1013 public function has_errors() {
1014 return !empty($this->errors
);
1018 * Returns an array of errors that have occured
1020 * @param bool $clear If set to true the errors will be cleared after being returned.
1023 public function get_errors($clear = false) {
1024 $errors = $this->errors
;
1026 // Reset the error array.
1027 $this->errors
= array();
1033 * Returns any errors as a string that can be included in CSS.
1037 public function output_errors_css() {
1038 $computedcss = "/****************************************\n";
1039 $computedcss .= " *--- Errors found during processing ----\n";
1040 foreach ($this->errors
as $error) {
1041 $computedcss .= preg_replace('#^#m', '* ', $error);
1043 $computedcss .= " ****************************************/\n\n";
1044 return $computedcss;
1048 * Returns a string to display stats about the last generation within CSS output
1052 public function output_stats_css() {
1054 $computedcss = "/****************************************\n";
1055 $computedcss .= " *------- CSS Optimisation stats --------\n";
1057 if ($this->rawstrlen
=== 0) {
1058 $computedcss .= " File not processed as it has no content /\n\n";
1059 $computedcss .= " ****************************************/\n\n";
1060 return $computedcss;
1061 } else if ($this->rawrules
=== 0) {
1062 $computedcss .= " File contained no rules to be processed /\n\n";
1063 $computedcss .= " ****************************************/\n\n";
1064 return $computedcss;
1067 $stats = $this->get_stats();
1069 $computedcss .= " * ".date('r')."\n";
1070 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
1071 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
1072 $computedcss .= " *--------------- before ----------------\n";
1073 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n";
1074 $computedcss .= " * {$stats['rawrules']} \t rules read in\n";
1075 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n";
1076 $computedcss .= " *---------------- after ----------------\n";
1077 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n";
1078 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n";
1079 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n";
1080 $computedcss .= " *---------------- stats ----------------\n";
1081 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n";
1082 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n";
1083 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n";
1084 $computedcss .= " ****************************************/\n\n";
1086 return $computedcss;
1090 * Resets the stats ready for another fresh processing
1092 public function reset_stats() {
1093 $this->commentsincss
= 0;
1094 $this->optimisedrules
= 0;
1095 $this->optimisedselectors
= 0;
1096 $this->optimisedstrlen
= 0;
1097 $this->rawrules
= 0;
1098 $this->rawselectors
= 0;
1099 $this->rawstrlen
= 0;
1100 $this->timecomplete
= 0;
1101 $this->timestart
= 0;
1105 * An array of the common HTML colours that are supported by most browsers.
1107 * This reference table is used to allow us to unify colours, and will aid
1108 * us in identifying buggy CSS using unsupported colours.
1112 public static $htmlcolours = array(
1113 'aliceblue' => '#F0F8FF',
1114 'antiquewhite' => '#FAEBD7',
1115 'aqua' => '#00FFFF',
1116 'aquamarine' => '#7FFFD4',
1117 'azure' => '#F0FFFF',
1118 'beige' => '#F5F5DC',
1119 'bisque' => '#FFE4C4',
1120 'black' => '#000000',
1121 'blanchedalmond' => '#FFEBCD',
1122 'blue' => '#0000FF',
1123 'blueviolet' => '#8A2BE2',
1124 'brown' => '#A52A2A',
1125 'burlywood' => '#DEB887',
1126 'cadetblue' => '#5F9EA0',
1127 'chartreuse' => '#7FFF00',
1128 'chocolate' => '#D2691E',
1129 'coral' => '#FF7F50',
1130 'cornflowerblue' => '#6495ED',
1131 'cornsilk' => '#FFF8DC',
1132 'crimson' => '#DC143C',
1133 'cyan' => '#00FFFF',
1134 'darkblue' => '#00008B',
1135 'darkcyan' => '#008B8B',
1136 'darkgoldenrod' => '#B8860B',
1137 'darkgray' => '#A9A9A9',
1138 'darkgrey' => '#A9A9A9',
1139 'darkgreen' => '#006400',
1140 'darkKhaki' => '#BDB76B',
1141 'darkmagenta' => '#8B008B',
1142 'darkolivegreen' => '#556B2F',
1143 'arkorange' => '#FF8C00',
1144 'darkorchid' => '#9932CC',
1145 'darkred' => '#8B0000',
1146 'darksalmon' => '#E9967A',
1147 'darkseagreen' => '#8FBC8F',
1148 'darkslateblue' => '#483D8B',
1149 'darkslategray' => '#2F4F4F',
1150 'darkslategrey' => '#2F4F4F',
1151 'darkturquoise' => '#00CED1',
1152 'darkviolet' => '#9400D3',
1153 'deeppink' => '#FF1493',
1154 'deepskyblue' => '#00BFFF',
1155 'dimgray' => '#696969',
1156 'dimgrey' => '#696969',
1157 'dodgerblue' => '#1E90FF',
1158 'firebrick' => '#B22222',
1159 'floralwhite' => '#FFFAF0',
1160 'forestgreen' => '#228B22',
1161 'fuchsia' => '#FF00FF',
1162 'gainsboro' => '#DCDCDC',
1163 'ghostwhite' => '#F8F8FF',
1164 'gold' => '#FFD700',
1165 'goldenrod' => '#DAA520',
1166 'gray' => '#808080',
1167 'grey' => '#808080',
1168 'green' => '#008000',
1169 'greenyellow' => '#ADFF2F',
1170 'honeydew' => '#F0FFF0',
1171 'hotpink' => '#FF69B4',
1172 'indianred ' => '#CD5C5C',
1173 'indigo ' => '#4B0082',
1174 'ivory' => '#FFFFF0',
1175 'khaki' => '#F0E68C',
1176 'lavender' => '#E6E6FA',
1177 'lavenderblush' => '#FFF0F5',
1178 'lawngreen' => '#7CFC00',
1179 'lemonchiffon' => '#FFFACD',
1180 'lightblue' => '#ADD8E6',
1181 'lightcoral' => '#F08080',
1182 'lightcyan' => '#E0FFFF',
1183 'lightgoldenrodyellow' => '#FAFAD2',
1184 'lightgray' => '#D3D3D3',
1185 'lightgrey' => '#D3D3D3',
1186 'lightgreen' => '#90EE90',
1187 'lightpink' => '#FFB6C1',
1188 'lightsalmon' => '#FFA07A',
1189 'lightseagreen' => '#20B2AA',
1190 'lightskyblue' => '#87CEFA',
1191 'lightslategray' => '#778899',
1192 'lightslategrey' => '#778899',
1193 'lightsteelblue' => '#B0C4DE',
1194 'lightyellow' => '#FFFFE0',
1195 'lime' => '#00FF00',
1196 'limegreen' => '#32CD32',
1197 'linen' => '#FAF0E6',
1198 'magenta' => '#FF00FF',
1199 'maroon' => '#800000',
1200 'mediumaquamarine' => '#66CDAA',
1201 'mediumblue' => '#0000CD',
1202 'mediumorchid' => '#BA55D3',
1203 'mediumpurple' => '#9370D8',
1204 'mediumseagreen' => '#3CB371',
1205 'mediumslateblue' => '#7B68EE',
1206 'mediumspringgreen' => '#00FA9A',
1207 'mediumturquoise' => '#48D1CC',
1208 'mediumvioletred' => '#C71585',
1209 'midnightblue' => '#191970',
1210 'mintcream' => '#F5FFFA',
1211 'mistyrose' => '#FFE4E1',
1212 'moccasin' => '#FFE4B5',
1213 'navajowhite' => '#FFDEAD',
1214 'navy' => '#000080',
1215 'oldlace' => '#FDF5E6',
1216 'olive' => '#808000',
1217 'olivedrab' => '#6B8E23',
1218 'orange' => '#FFA500',
1219 'orangered' => '#FF4500',
1220 'orchid' => '#DA70D6',
1221 'palegoldenrod' => '#EEE8AA',
1222 'palegreen' => '#98FB98',
1223 'paleturquoise' => '#AFEEEE',
1224 'palevioletred' => '#D87093',
1225 'papayawhip' => '#FFEFD5',
1226 'peachpuff' => '#FFDAB9',
1227 'peru' => '#CD853F',
1228 'pink' => '#FFC0CB',
1229 'plum' => '#DDA0DD',
1230 'powderblue' => '#B0E0E6',
1231 'purple' => '#800080',
1233 'rosybrown' => '#BC8F8F',
1234 'royalblue' => '#4169E1',
1235 'saddlebrown' => '#8B4513',
1236 'salmon' => '#FA8072',
1237 'sandybrown' => '#F4A460',
1238 'seagreen' => '#2E8B57',
1239 'seashell' => '#FFF5EE',
1240 'sienna' => '#A0522D',
1241 'silver' => '#C0C0C0',
1242 'skyblue' => '#87CEEB',
1243 'slateblue' => '#6A5ACD',
1244 'slategray' => '#708090',
1245 'slategrey' => '#708090',
1246 'snow' => '#FFFAFA',
1247 'springgreen' => '#00FF7F',
1248 'steelblue' => '#4682B4',
1250 'teal' => '#008080',
1251 'thistle' => '#D8BFD8',
1252 'tomato' => '#FF6347',
1253 'transparent' => 'transparent',
1254 'turquoise' => '#40E0D0',
1255 'violet' => '#EE82EE',
1256 'wheat' => '#F5DEB3',
1257 'white' => '#FFFFFF',
1258 'whitesmoke' => '#F5F5F5',
1259 'yellow' => '#FFFF00',
1260 'yellowgreen' => '#9ACD32'
1265 * Used to prepare CSS strings
1268 * @subpackage cssoptimiser
1269 * @copyright 2012 Sam Hemelryk
1270 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1272 abstract class css_writer
{
1275 * The current indent level
1278 protected static $indent = 0;
1281 * Returns true if the output should still maintain minimum formatting.
1284 protected static function is_pretty() {
1286 return (!empty($CFG->cssoptimiserpretty
));
1290 * Returns the indenting char to use for indenting things nicely.
1293 protected static function get_indent() {
1294 if (self
::is_pretty()) {
1295 return str_repeat(" ", self
::$indent);
1301 * Increases the current indent
1303 protected static function increase_indent() {
1308 * Decreases the current indent
1310 protected static function decrease_indent() {
1315 * Returns the string to use as a separator
1318 protected static function get_separator() {
1319 return (self
::is_pretty())?
"\n":' ';
1323 * Returns CSS for media
1325 * @param string $typestring
1326 * @param css_rule[] $rules An array of css_rule objects
1329 public static function media($typestring, array &$rules) {
1330 $nl = self
::get_separator();
1333 if ($typestring !== 'all') {
1334 $output .= "\n@media {$typestring} {".$nl;
1335 self
::increase_indent();
1337 foreach ($rules as $rule) {
1338 $output .= $rule->out().$nl;
1340 if ($typestring !== 'all') {
1341 self
::decrease_indent();
1348 * Returns CSS for a keyframe
1350 * @param string $for The desired declaration. e.g. keyframes, -moz-keyframes, -webkit-keyframes
1351 * @param string $name The name for the keyframe
1352 * @param css_rule[] $rules An array of rules belonging to the keyframe
1355 public static function keyframe($for, $name, array &$rules) {
1356 $output = "\n@{$for} {$name} {";
1357 foreach ($rules as $rule) {
1358 $output .= $rule->out();
1365 * Returns CSS for a rule
1367 * @param string $selector
1368 * @param string $styles
1371 public static function rule($selector, $styles) {
1372 $css = self
::get_indent()."{$selector}{{$styles}}";
1377 * Returns CSS for the selectors of a rule
1379 * @param css_selector[] $selectors Array of css_selector objects
1382 public static function selectors(array $selectors) {
1383 $nl = self
::get_separator();
1384 $selectorstrings = array();
1385 foreach ($selectors as $selector) {
1386 $selectorstrings[] = $selector->out();
1388 return join(','.$nl, $selectorstrings);
1392 * Returns a selector given the components that make it up.
1394 * @param array $components
1397 public static function selector(array $components) {
1398 return trim(join(' ', $components));
1402 * Returns a CSS string for the provided styles
1404 * @param css_style[] $styles Array of css_style objects
1407 public static function styles(array $styles) {
1409 foreach ($styles as $style) {
1410 // Check if the style is an array. If it is then we are outputing an advanced style.
1411 // An advanced style is a style with one or more values, and can occur in situations like background-image
1412 // where browse specific values are being used.
1413 if (is_array($style)) {
1414 /* @var css_style[] $style */
1415 foreach ($style as $advstyle) {
1416 $bits[] = $advstyle->out();
1420 $bits[] = $style->out();
1422 return join('', $bits);
1426 * Returns a style CSS
1428 * @param string $name
1429 * @param string $value
1430 * @param bool $important
1433 public static function style($name, $value, $important = false) {
1434 $value = trim($value);
1435 if ($important && strpos($value, '!important') === false) {
1436 $value .= ' !important';
1438 return "{$name}:{$value};";
1443 * A consolidatable style interface.
1445 * Class that implement this have a short-hand notation for specifying multiple styles.
1448 * @subpackage cssoptimiser
1449 * @copyright 2012 Sam Hemelryk
1450 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1452 interface core_css_consolidatable_style
{
1454 * Used to consolidate several styles into a single "short-hand" style.
1455 * @param array $styles
1458 public static function consolidate(array $styles);
1462 * A structure to represent a CSS selector.
1464 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
1468 * @subpackage cssoptimiser
1469 * @copyright 2012 Sam Hemelryk
1470 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1472 class css_selector
{
1475 * An array of selector bits
1478 protected $selectors = array();
1481 * The number of selectors.
1484 protected $count = 0;
1487 * Is null if there are no selectors, true if all selectors are basic and false otherwise.
1488 * A basic selector is one that consists of just the element type. e.g. div, span, td, a
1491 protected $isbasic = null;
1494 * Initialises a new CSS selector
1495 * @return css_selector
1497 public static function init() {
1498 return new css_selector();
1502 * CSS selectors can only be created through the init method above.
1504 protected function __construct() {
1505 // Nothing to do here by default.
1509 * Adds a selector to the end of the current selector
1510 * @param string $selector
1512 public function add($selector) {
1513 $selector = trim($selector);
1515 $count +
= preg_match_all('/(\.|#)/', $selector, $matchesarray);
1516 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
1519 // If its already false then no need to continue, its not basic.
1520 if ($this->isbasic
!== false) {
1521 // If theres more than one part making up this selector its not basic.
1523 $this->isbasic
= false;
1525 // Check whether it is a basic element (a-z+) with possible psuedo selector.
1526 $this->isbasic
= (bool)preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector);
1529 $this->count
= $count;
1530 $this->selectors
[] = $selector;
1533 * Returns the number of individual components that make up this selector
1536 public function get_selector_count() {
1537 return $this->count
;
1541 * Returns the selector for use in a CSS rule
1544 public function out() {
1545 return css_writer
::selector($this->selectors
);
1549 * Returns true is all of the selectors act only upon basic elements (no classes/ids)
1552 public function is_basic() {
1553 return ($this->isbasic
=== true);
1558 * A structure to represent a CSS rule.
1561 * @subpackage cssoptimiser
1562 * @copyright 2012 Sam Hemelryk
1563 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1568 * An array of CSS selectors {@link css_selector}
1569 * @var css_selector[]
1571 protected $selectors = array();
1574 * An array of CSS styles {@link css_style}
1577 protected $styles = array();
1580 * Created a new CSS rule. This is the only way to create a new CSS rule externally.
1583 public static function init() {
1584 return new css_rule();
1588 * Constructs a new css rule.
1590 * @param string $selector The selector or array of selectors that make up this rule.
1591 * @param css_style[] $styles An array of styles that belong to this rule.
1593 protected function __construct($selector = null, array $styles = array()) {
1594 if ($selector != null) {
1595 if (is_array($selector)) {
1596 $this->selectors
= $selector;
1598 $this->selectors
= array($selector);
1600 $this->add_styles($styles);
1605 * Adds a new CSS selector to this rule
1607 * e.g. $rule->add_selector('.one #two.two');
1609 * @param css_selector $selector Adds a CSS selector to this rule.
1611 public function add_selector(css_selector
$selector) {
1612 $this->selectors
[] = $selector;
1616 * Adds a new CSS style to this rule.
1618 * @param css_style|string $style Adds a new style to this rule
1620 public function add_style($style) {
1621 if (is_string($style)) {
1622 $style = trim($style);
1623 if (empty($style)) {
1626 $bits = explode(':', $style, 2);
1627 if (count($bits) == 2) {
1628 list($name, $value) = array_map('trim', $bits);
1630 if (isset($name) && isset($value) && $name !== '' && $value !== '') {
1631 $style = css_style
::init_automatic($name, $value);
1633 } else if ($style instanceof css_style
) {
1634 // Clone the style as it may be coming from another rule and we don't
1635 // want references as it will likely be overwritten by proceeding
1637 $style = clone($style);
1639 if ($style instanceof css_style
) {
1640 $name = $style->get_name();
1641 $exists = array_key_exists($name, $this->styles
);
1642 // We need to find out if the current style support multiple values, or whether the style
1643 // is already set up to record multiple values. This can happen with background images which can have single
1644 // and multiple values.
1645 if ($style->allows_multiple_values() ||
($exists && is_array($this->styles
[$name]))) {
1647 $this->styles
[$name] = array();
1648 } else if ($this->styles
[$name] instanceof css_style
) {
1649 $this->styles
[$name] = array($this->styles
[$name]);
1651 $this->styles
[$name][] = $style;
1652 } else if ($exists) {
1653 $this->styles
[$name]->set_value($style->get_value());
1655 $this->styles
[$name] = $style;
1657 } else if (is_array($style)) {
1658 // We probably shouldn't worry about processing styles here but to
1659 // be truthful it doesn't hurt.
1660 foreach ($style as $astyle) {
1661 $this->add_style($astyle);
1667 * An easy method of adding several styles at once. Just calls add_style.
1669 * This method simply iterates over the array and calls {@link css_rule::add_style()}
1672 * @param css_style[] $styles Adds an array of styles
1674 public function add_styles(array $styles) {
1675 foreach ($styles as $style) {
1676 $this->add_style($style);
1681 * Returns the array of selectors
1683 * @return css_selector[]
1685 public function get_selectors() {
1686 return $this->selectors
;
1690 * Returns the array of styles
1692 * @return css_style[]
1694 public function get_styles() {
1695 return $this->styles
;
1699 * Outputs this rule as a fragment of CSS
1703 public function out() {
1704 $selectors = css_writer
::selectors($this->selectors
);
1705 $styles = css_writer
::styles($this->get_consolidated_styles());
1706 return css_writer
::rule($selectors, $styles);
1710 * Consolidates all styles associated with this rule
1712 * @return css_style[] An array of consolidated styles
1714 public function get_consolidated_styles() {
1715 /* @var css_style[] $organisedstyles */
1716 $organisedstyles = array();
1717 /* @var css_style[] $finalstyles */
1718 $finalstyles = array();
1719 /* @var core_css_consolidatable_style[] $consolidate */
1720 $consolidate = array();
1721 /* @var css_style[] $advancedstyles */
1722 $advancedstyles = array();
1723 foreach ($this->styles
as $style) {
1724 // If the style is an array then we are processing an advanced style. An advanced style is a style that can have
1725 // one or more values. Background-image is one such example as it can have browser specific styles.
1726 if (is_array($style)) {
1729 foreach ($style as $advstyle) {
1730 /* @var css_style $advstyle */
1732 $advancedstyles[$key] = $advstyle;
1733 if (!$advstyle->allows_multiple_values()) {
1734 if (!is_null($single)) {
1735 unset($advancedstyles[$single]);
1740 if (!is_null($single)) {
1741 $style = $advancedstyles[$single];
1743 $consolidatetoclass = $style->consolidate_to();
1744 if (($style->is_valid() ||
$style->is_special_empty_value()) && !empty($consolidatetoclass) &&
1745 class_exists('css_style_'.$consolidatetoclass)) {
1746 $class = 'css_style_'.$consolidatetoclass;
1747 if (!array_key_exists($class, $consolidate)) {
1748 $consolidate[$class] = array();
1749 $organisedstyles[$class] = true;
1751 $consolidate[$class][] = $style;
1752 unset($advancedstyles[$single]);
1758 $consolidatetoclass = $style->consolidate_to();
1759 if (($style->is_valid() ||
$style->is_special_empty_value()) && !empty($consolidatetoclass) &&
1760 class_exists('css_style_'.$consolidatetoclass)) {
1761 $class = 'css_style_'.$consolidatetoclass;
1762 if (!array_key_exists($class, $consolidate)) {
1763 $consolidate[$class] = array();
1764 $organisedstyles[$class] = true;
1766 $consolidate[$class][] = $style;
1768 $organisedstyles[$style->get_name()] = $style;
1772 foreach ($consolidate as $class => $styles) {
1773 $organisedstyles[$class] = call_user_func(array($class, 'consolidate'), $styles);
1776 foreach ($organisedstyles as $style) {
1777 if (is_array($style)) {
1778 foreach ($style as $s) {
1779 $finalstyles[] = $s;
1782 $finalstyles[] = $style;
1785 $finalstyles = array_merge($finalstyles, $advancedstyles);
1786 return $finalstyles;
1790 * Splits this rules into an array of CSS rules. One for each of the selectors
1791 * that make up this rule.
1793 * @return css_rule[]
1795 public function split_by_selector() {
1797 foreach ($this->selectors
as $selector) {
1798 $return[] = new css_rule($selector, $this->styles
);
1804 * Splits this rule into an array of rules. One for each of the styles that
1807 * @return css_rule[] Array of css_rule objects
1809 public function split_by_style() {
1811 foreach ($this->styles
as $style) {
1812 if (is_array($style)) {
1813 $return[] = new css_rule($this->selectors
, $style);
1816 $return[] = new css_rule($this->selectors
, array($style));
1822 * Gets a hash for the styles of this rule
1826 public function get_style_hash() {
1827 return md5(css_writer
::styles($this->styles
));
1831 * Gets a hash for the selectors of this rule
1835 public function get_selector_hash() {
1836 return md5(css_writer
::selectors($this->selectors
));
1840 * Gets the number of selectors that make up this rule.
1844 public function get_selector_count() {
1846 foreach ($this->selectors
as $selector) {
1847 $count +
= $selector->get_selector_count();
1853 * Returns true if there are any errors with this rule.
1857 public function has_errors() {
1858 foreach ($this->styles
as $style) {
1859 if (is_array($style)) {
1860 /* @var css_style[] $style */
1861 foreach ($style as $advstyle) {
1862 if ($advstyle->has_error()) {
1868 if ($style->has_error()) {
1876 * Returns the error strings that were recorded when processing this rule.
1878 * Before calling this function you should first call {@link css_rule::has_errors()}
1879 * to make sure there are errors (hopefully there arn't).
1883 public function get_error_string() {
1884 $css = $this->out();
1886 foreach ($this->styles
as $style) {
1887 if (is_array($style)) {
1888 /* @var css_style[] $style */
1889 foreach ($style as $advstyle) {
1890 if ($advstyle instanceof css_style
&& $advstyle->has_error()) {
1891 $errors[] = " * ".$advstyle->get_last_error();
1894 } else if ($style instanceof css_style
&& $style->has_error()) {
1895 $errors[] = " * ".$style->get_last_error();
1898 return $css." has the following errors:\n".join("\n", $errors);
1902 * Returns true if this rule could be considered a reset rule.
1904 * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector.
1908 public function is_reset_rule() {
1909 foreach ($this->selectors
as $selector) {
1910 if (!$selector->is_basic()) {
1919 * An abstract CSS rule collection class.
1921 * This class is extended by things such as media and keyframe declaration. They are declarations that
1922 * group rules together for a purpose.
1923 * When no declaration is specified rules accumulate into @media all.
1926 * @subpackage cssoptimiser
1927 * @copyright 2012 Sam Hemelryk
1928 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1930 abstract class css_rule_collection
{
1932 * An array of rules within this collection instance
1935 protected $rules = array();
1938 * The collection must be able to print itself.
1940 abstract public function out();
1943 * Adds a new CSS rule to this collection instance
1945 * @param css_rule $newrule
1947 public function add_rule(css_rule
$newrule) {
1948 foreach ($newrule->split_by_selector() as $rule) {
1949 $hash = $rule->get_selector_hash();
1950 if (!array_key_exists($hash, $this->rules
)) {
1951 $this->rules
[$hash] = $rule;
1953 $this->rules
[$hash]->add_styles($rule->get_styles());
1959 * Returns the rules used by this collection
1961 * @return css_rule[]
1963 public function get_rules() {
1964 return $this->rules
;
1968 * Organises rules by gropuing selectors based upon the styles and consolidating
1969 * those selectors into single rules.
1971 * @return bool True if the CSS was optimised by this method
1973 public function organise_rules_by_selectors() {
1974 /* @var css_rule[] $optimisedrules */
1975 $optimisedrules = array();
1976 $beforecount = count($this->rules
);
1978 /* @var css_rule $lastrule */
1980 foreach ($this->rules
as $rule) {
1981 $hash = $rule->get_style_hash();
1982 if ($lastrule !== null && $lasthash !== null && $hash === $lasthash) {
1983 foreach ($rule->get_selectors() as $selector) {
1984 $lastrule->add_selector($selector);
1988 $lastrule = clone($rule);
1990 $optimisedrules[] = $lastrule;
1992 $this->rules
= array();
1993 foreach ($optimisedrules as $optimised) {
1994 $this->rules
[$optimised->get_selector_hash()] = $optimised;
1996 $aftercount = count($this->rules
);
1997 return ($beforecount < $aftercount);
2001 * Returns the total number of rules that exist within this collection
2005 public function count_rules() {
2006 return count($this->rules
);
2010 * Returns the total number of selectors that exist within this collection
2014 public function count_selectors() {
2016 foreach ($this->rules
as $rule) {
2017 $count +
= $rule->get_selector_count();
2023 * Returns true if the collection has any rules that have errors
2027 public function has_errors() {
2028 foreach ($this->rules
as $rule) {
2029 if ($rule->has_errors()) {
2037 * Returns any errors that have happened within rules in this collection.
2041 public function get_errors() {
2043 foreach ($this->rules
as $rule) {
2044 if ($rule->has_errors()) {
2045 $errors[] = $rule->get_error_string();
2053 * A media class to organise rules by the media they apply to.
2056 * @subpackage cssoptimiser
2057 * @copyright 2012 Sam Hemelryk
2058 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2060 class css_media
extends css_rule_collection
{
2063 * An array of the different media types this instance applies to.
2066 protected $types = array();
2069 * Initalises a new media instance
2071 * @param string $for The media that the contained rules are destined for.
2073 public function __construct($for = 'all') {
2074 $types = explode(',', $for);
2075 $this->types
= array_map('trim', $types);
2079 * Returns the CSS for this media and all of its rules.
2083 public function out() {
2084 return css_writer
::media(join(',', $this->types
), $this->rules
);
2088 * Returns an array of media that this media instance applies to
2092 public function get_types() {
2093 return $this->types
;
2097 * Returns all of the reset rules known by this media set.
2098 * @param bool $remove If set to true reset rules will be removed before being returned.
2101 public function get_reset_rules($remove = false) {
2102 $resetrules = array();
2103 foreach ($this->rules
as $key => $rule) {
2104 if ($rule->is_reset_rule()) {
2105 $resetrules[] = clone $rule;
2107 unset($this->rules
[$key]);
2116 * A media class to organise rules by the media they apply to.
2119 * @subpackage cssoptimiser
2120 * @copyright 2012 Sam Hemelryk
2121 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2123 class css_keyframe
extends css_rule_collection
{
2126 * The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
2132 * The name for the keyframes
2137 * Constructs a new keyframe
2139 * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
2140 * @param string $name The name for the keyframes
2142 public function __construct($for, $name) {
2144 $this->name
= $name;
2147 * Returns the directive of this keyframe
2149 * e.g. keyframes, -moz-keyframes, -webkit-keyframes
2152 public function get_for() {
2156 * Returns the name of this keyframe
2159 public function get_name() {
2163 * Returns the CSS for this collection of keyframes and all of its rules.
2167 public function out() {
2168 return css_writer
::keyframe($this->for, $this->name
, $this->rules
);
2173 * An absract class to represent CSS styles
2176 * @subpackage cssoptimiser
2177 * @copyright 2012 Sam Hemelryk
2178 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2180 abstract class css_style
{
2182 /** Constant used for recongise a special empty value in a CSS style */
2183 const NULL_VALUE
= '@@$NULL$@@';
2186 * The name of the style
2192 * The value for the style
2198 * If set to true this style was defined with the !important rule.
2199 * Only trolls use !important.
2200 * Don't hide under bridges.. its not good for your skin. Do the proper thing
2201 * and fix the issue don't just force a fix that will undoubtedly one day
2202 * lead to further frustration.
2205 protected $important = false;
2208 * Gets set to true if this style has an error
2211 protected $error = false;
2214 * The last error message that occured
2217 protected $errormessage = null;
2220 * Initialises a new style.
2222 * This is the only public way to create a style to ensure they that appropriate
2223 * style class is used if it exists.
2225 * @param string $name The name of the style.
2226 * @param string $value The value of the style.
2227 * @return css_style_generic
2229 public static function init_automatic($name, $value) {
2230 $cleanedname = preg_replace('#[^a-zA-Z0-9]+#', '', $name);
2231 $specificclass = 'css_style_'.$cleanedname;
2232 if (class_exists($specificclass)) {
2233 $style = call_user_func(array($specificclass, 'init'), $value);
2234 if ($cleanedname !== $name && !is_array($style)) {
2235 $style->set_actual_name($name);
2239 return new css_style_generic($name, $value);
2243 * Creates a new style when given its name and value
2245 * @param string $name The name of the style.
2246 * @param string $value The value of the style.
2248 protected function __construct($name, $value) {
2249 $this->name
= $name;
2250 $this->set_value($value);
2254 * Sets the value for the style
2256 * @param string $value
2258 final public function set_value($value) {
2259 $value = trim($value);
2260 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
2262 $value = substr($value, 0, -(strlen($matches[1])));
2263 $value = rtrim($value);
2265 if (!$this->important ||
$important) {
2266 $this->value
= $this->clean_value($value);
2267 $this->important
= $important;
2269 if (!$this->is_valid()) {
2270 $this->set_error('Invalid value for '.$this->name
);
2275 * Returns true if the value associated with this style is valid
2279 public function is_valid() {
2284 * Returns the name for the style
2288 public function get_name() {
2293 * Returns the value for the style
2295 * @param bool $includeimportant If set to true and the rule is important !important postfix will be used.
2298 public function get_value($includeimportant = true) {
2299 $value = $this->value
;
2300 if ($includeimportant && $this->important
) {
2301 $value .= ' !important';
2307 * Returns the style ready for use in CSS
2309 * @param string|null $value A value to use to override the value for this style.
2312 public function out($value = null) {
2313 if (is_null($value)) {
2314 $value = $this->get_value();
2316 return css_writer
::style($this->name
, $value, $this->important
);
2320 * This can be overridden by a specific style allowing it to clean its values
2323 * @param mixed $value
2326 protected function clean_value($value) {
2331 * If this particular style can be consolidated into another style this function
2332 * should return the style that it can be consolidated into.
2334 * @return string|null
2336 public function consolidate_to() {
2341 * Sets the last error message.
2343 * @param string $message
2345 protected function set_error($message) {
2346 $this->error
= true;
2347 $this->errormessage
= $message;
2351 * Returns true if an error has occured
2355 public function has_error() {
2356 return $this->error
;
2360 * Returns the last error that occured or null if no errors have happened.
2364 public function get_last_error() {
2365 return $this->errormessage
;
2369 * Returns true if the value for this style is the special null value.
2371 * This should only be overriden in circumstances where a shorthand style can lead
2372 * to move explicit styles being overwritten. Not a common place occurenace.
2375 * This occurs if the shorthand background property was used but no proper value
2376 * was specified for this style.
2377 * This leads to a null value being used unless otherwise overridden.
2381 public function is_special_empty_value() {
2386 * Returns true if this style permits multiple values.
2388 * This occurs for styles such as background image that can have browser specific values that need to be maintained because
2389 * of course we don't know what browser the user is using, and optimisation occurs before caching.
2390 * Thus we must always server all values we encounter in the order we encounter them for when this is set to true.
2392 * @return boolean False by default, true if the style supports muliple values.
2394 public function allows_multiple_values() {
2399 * Returns true if this style was marked important.
2402 public function is_important() {
2403 return !empty($this->important
);
2407 * Sets the important flag for this style and its current value.
2408 * @param bool $important
2410 public function set_important($important = true) {
2411 $this->important
= (bool) $important;
2415 * Sets the actual name used within the style.
2417 * This method allows us to support browser hacks like *width:0;
2419 * @param string $name
2421 public function set_actual_name($name) {
2422 $this->name
= $name;
2427 * A generic CSS style class to use when a more specific class does not exist.
2430 * @subpackage cssoptimiser
2431 * @copyright 2012 Sam Hemelryk
2432 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2434 class css_style_generic
extends css_style
{
2437 * Cleans incoming values for typical things that can be optimised.
2439 * @param mixed $value Cleans the provided value optimising it if possible
2442 protected function clean_value($value) {
2443 if (trim($value) == '0px') {
2445 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) {
2446 $value = '#'.strtoupper($matches[1]);
2453 * A colour CSS style
2456 * @subpackage cssoptimiser
2457 * @copyright 2012 Sam Hemelryk
2458 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2460 class css_style_color
extends css_style
{
2463 * Creates a new colour style
2465 * @param mixed $value Initialises a new colour style
2466 * @return css_style_color
2468 public static function init($value) {
2469 return new css_style_color('color', $value);
2473 * Cleans the colour unifing it to a 6 char hash colour if possible
2474 * Doing this allows us to associate identical colours being specified in
2475 * different ways. e.g. Red, red, #F00, and #F00000
2477 * @param mixed $value Cleans the provided value optimising it if possible
2480 protected function clean_value($value) {
2481 $value = trim($value);
2482 if (css_is_colour($value)) {
2483 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) {
2484 $value = '#'.strtoupper($matches[1]);
2485 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) {
2486 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
2487 $value = '#'.strtoupper($value);
2488 } else if (array_key_exists(strtolower($value), css_optimiser
::$htmlcolours)) {
2489 $value = css_optimiser
::$htmlcolours[strtolower($value)];
2496 * Returns the colour style for use within CSS.
2497 * Will return an optimised hash colour.
2500 * #123 instead of #112233
2501 * #F00 instead of red
2503 * @param string $overridevalue If provided then this value will be used instead
2504 * of the styles current value.
2507 public function out($overridevalue = null) {
2508 if ($overridevalue === null) {
2509 $overridevalue = $this->value
;
2511 return parent
::out(self
::shrink_value($overridevalue));
2515 * Shrinks the colour value is possible.
2517 * @param string $value Shrinks the current value to an optimial form if possible
2520 public static function shrink_value($value) {
2521 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) {
2522 return '#'.$matches[1].$matches[2].$matches[3];
2528 * Returns true if the value is a valid colour.
2532 public function is_valid() {
2533 return css_is_colour($this->value
);
2541 * @subpackage cssoptimiser
2542 * @copyright 2012 Sam Hemelryk
2543 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2545 class css_style_width
extends css_style
{
2548 * Checks if the width is valid
2551 public function is_valid() {
2552 return css_is_width($this->value
);
2556 * Cleans the provided value
2558 * @param mixed $value Cleans the provided value optimising it if possible
2561 protected function clean_value($value) {
2562 if (!css_is_width($value)) {
2563 // Note we don't actually change the value to something valid. That
2564 // would be bad for futureproofing.
2565 $this->set_error('Invalid width specified for '.$this->name
);
2566 } else if (preg_match('#^0\D+$#', $value)) {
2569 return trim($value);
2573 * Initialises a new width style
2575 * @param mixed $value The value this style has
2576 * @return css_style_width
2578 public static function init($value) {
2579 return new css_style_width('width', $value);
2587 * @subpackage cssoptimiser
2588 * @copyright 2012 Sam Hemelryk
2589 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2591 class css_style_margin
extends css_style_width
implements core_css_consolidatable_style
{
2594 * Initialises a margin style.
2596 * In this case we split the margin into several other margin styles so that
2597 * we can properly condense overrides and then reconsolidate them later into
2600 * @param string $value The value the style has
2601 * @return array An array of margin values that can later be consolidated
2603 public static function init($value) {
2605 if (strpos($value, '!important') !== false) {
2606 $important = ' !important';
2607 $value = str_replace('!important', '', $value);
2610 $value = preg_replace('#\s+#', ' ', trim($value));
2611 $bits = explode(' ', $value, 4);
2613 $top = $right = $bottom = $left = null;
2614 if (count($bits) > 0) {
2615 $top = $right = $bottom = $left = array_shift($bits);
2617 if (count($bits) > 0) {
2618 $right = $left = array_shift($bits);
2620 if (count($bits) > 0) {
2621 $bottom = array_shift($bits);
2623 if (count($bits) > 0) {
2624 $left = array_shift($bits);
2627 new css_style_margintop('margin-top', $top.$important),
2628 new css_style_marginright('margin-right', $right.$important),
2629 new css_style_marginbottom('margin-bottom', $bottom.$important),
2630 new css_style_marginleft('margin-left', $left.$important)
2635 * Consolidates individual margin styles into a single margin style
2637 * @param css_style[] $styles
2638 * @return css_style[] An array of consolidated styles
2640 public static function consolidate(array $styles) {
2641 if (count($styles) != 4) {
2645 $someimportant = false;
2646 $allimportant = null;
2647 $notimportantequal = null;
2649 foreach ($styles as $style) {
2650 if ($style->is_important()) {
2651 $someimportant = true;
2652 if ($allimportant === null) {
2653 $allimportant = true;
2656 if ($allimportant === true) {
2657 $allimportant = false;
2659 if ($firstvalue == null) {
2660 $firstvalue = $style->get_value(false);
2661 $notimportantequal = true;
2662 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
2663 $notimportantequal = false;
2668 if ($someimportant && !$allimportant && !$notimportantequal) {
2672 if ($someimportant && !$allimportant && $notimportantequal) {
2674 new css_style_margin('margin', $firstvalue)
2676 foreach ($styles as $style) {
2677 if ($style->is_important()) {
2687 foreach ($styles as $style) {
2688 switch ($style->get_name()) {
2690 $top = $style->get_value(false);
2692 case 'margin-right' :
2693 $right = $style->get_value(false);
2695 case 'margin-bottom' :
2696 $bottom = $style->get_value(false);
2698 case 'margin-left' :
2699 $left = $style->get_value(false);
2703 if ($top == $bottom && $left == $right) {
2704 if ($top == $left) {
2705 $returnstyle = new css_style_margin('margin', $top);
2707 $returnstyle = new css_style_margin('margin', "{$top} {$left}");
2709 } else if ($left == $right) {
2710 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom}");
2712 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}");
2714 if ($allimportant) {
2715 $returnstyle->set_important();
2717 return array($returnstyle);
2723 * A margin top style
2726 * @subpackage cssoptimiser
2727 * @copyright 2012 Sam Hemelryk
2728 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2730 class css_style_margintop
extends css_style_margin
{
2733 * A simple init, just a single style
2735 * @param string $value The value the style has
2736 * @return css_style_margintop
2738 public static function init($value) {
2739 return new css_style_margintop('margin-top', $value);
2743 * This style can be consolidated into a single margin style
2747 public function consolidate_to() {
2753 * A margin right style
2756 * @subpackage cssoptimiser
2757 * @copyright 2012 Sam Hemelryk
2758 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2760 class css_style_marginright
extends css_style_margin
{
2763 * A simple init, just a single style
2765 * @param string $value The value the style has
2766 * @return css_style_margintop
2768 public static function init($value) {
2769 return new css_style_marginright('margin-right', $value);
2773 * This style can be consolidated into a single margin style
2777 public function consolidate_to() {
2783 * A margin bottom style
2786 * @subpackage cssoptimiser
2787 * @copyright 2012 Sam Hemelryk
2788 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2790 class css_style_marginbottom
extends css_style_margin
{
2793 * A simple init, just a single style
2795 * @param string $value The value the style has
2796 * @return css_style_margintop
2798 public static function init($value) {
2799 return new css_style_marginbottom('margin-bottom', $value);
2803 * This style can be consolidated into a single margin style
2807 public function consolidate_to() {
2813 * A margin left style
2816 * @subpackage cssoptimiser
2817 * @copyright 2012 Sam Hemelryk
2818 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2820 class css_style_marginleft
extends css_style_margin
{
2823 * A simple init, just a single style
2825 * @param string $value The value the style has
2826 * @return css_style_margintop
2828 public static function init($value) {
2829 return new css_style_marginleft('margin-left', $value);
2833 * This style can be consolidated into a single margin style
2837 public function consolidate_to() {
2846 * @subpackage cssoptimiser
2847 * @copyright 2012 Sam Hemelryk
2848 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2850 class css_style_border
extends css_style
implements core_css_consolidatable_style
{
2853 * Initalises the border style into an array of individual style compontents
2855 * @param string $value The value the style has
2856 * @return css_style_bordercolor
2858 public static function init($value) {
2859 $value = preg_replace('#\s+#', ' ', $value);
2860 $bits = explode(' ', $value, 3);
2863 if (count($bits) > 0) {
2864 $width = array_shift($bits);
2865 if (!css_style_borderwidth
::is_border_width($width)) {
2868 $return[] = css_style_bordertopwidth
::init($width);
2869 $return[] = css_style_borderrightwidth
::init($width);
2870 $return[] = css_style_borderbottomwidth
::init($width);
2871 $return[] = css_style_borderleftwidth
::init($width);
2873 if (count($bits) > 0) {
2874 $style = array_shift($bits);
2875 $return[] = css_style_bordertopstyle
::init($style);
2876 $return[] = css_style_borderrightstyle
::init($style);
2877 $return[] = css_style_borderbottomstyle
::init($style);
2878 $return[] = css_style_borderleftstyle
::init($style);
2880 if (count($bits) > 0) {
2881 $colour = array_shift($bits);
2882 $return[] = css_style_bordertopcolor
::init($colour);
2883 $return[] = css_style_borderrightcolor
::init($colour);
2884 $return[] = css_style_borderbottomcolor
::init($colour);
2885 $return[] = css_style_borderleftcolor
::init($colour);
2891 * Consolidates all border styles into a single style
2893 * @param css_style[] $styles An array of border styles
2894 * @return css_style[] An optimised array of border styles
2896 public static function consolidate(array $styles) {
2898 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2899 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2900 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2902 foreach ($styles as $style) {
2903 switch ($style->get_name()) {
2904 case 'border-top-width':
2905 $borderwidths['top'] = $style->get_value();
2907 case 'border-right-width':
2908 $borderwidths['right'] = $style->get_value();
2910 case 'border-bottom-width':
2911 $borderwidths['bottom'] = $style->get_value();
2913 case 'border-left-width':
2914 $borderwidths['left'] = $style->get_value();
2917 case 'border-top-style':
2918 $borderstyles['top'] = $style->get_value();
2920 case 'border-right-style':
2921 $borderstyles['right'] = $style->get_value();
2923 case 'border-bottom-style':
2924 $borderstyles['bottom'] = $style->get_value();
2926 case 'border-left-style':
2927 $borderstyles['left'] = $style->get_value();
2930 case 'border-top-color':
2931 $bordercolors['top'] = css_style_color
::shrink_value($style->get_value());
2933 case 'border-right-color':
2934 $bordercolors['right'] = css_style_color
::shrink_value($style->get_value());
2936 case 'border-bottom-color':
2937 $bordercolors['bottom'] = css_style_color
::shrink_value($style->get_value());
2939 case 'border-left-color':
2940 $bordercolors['left'] = css_style_color
::shrink_value($style->get_value());
2945 $uniquewidths = count(array_unique($borderwidths));
2946 $uniquestyles = count(array_unique($borderstyles));
2947 $uniquecolors = count(array_unique($bordercolors));
2949 $nullwidths = in_array(null, $borderwidths, true);
2950 $nullstyles = in_array(null, $borderstyles, true);
2951 $nullcolors = in_array(null, $bordercolors, true);
2953 $allwidthsthesame = ($uniquewidths === 1)?
1:0;
2954 $allstylesthesame = ($uniquestyles === 1)?
1:0;
2955 $allcolorsthesame = ($uniquecolors === 1)?
1:0;
2957 $allwidthsnull = $allwidthsthesame && $nullwidths;
2958 $allstylesnull = $allstylesthesame && $nullstyles;
2959 $allcolorsnull = $allcolorsthesame && $nullcolors;
2961 /* @var css_style[] $return */
2963 if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
2964 // Everything is null still... boo.
2965 return array(new css_style_border('border', ''));
2967 } else if ($allwidthsnull && $allstylesnull) {
2969 self
::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2972 } else if ($allwidthsnull && $allcolorsnull) {
2974 self
::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2977 } else if ($allcolorsnull && $allstylesnull) {
2979 self
::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2984 if ($allwidthsthesame +
$allstylesthesame +
$allcolorsthesame == 3) {
2986 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']);
2988 } else if ($allwidthsthesame +
$allstylesthesame +
$allcolorsthesame == 2) {
2990 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) {
2992 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']);
2993 self
::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2995 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) {
2997 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']);
2998 self
::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
3000 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) {
3002 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']);
3003 self
::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
3006 self
::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
3007 self
::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
3008 self
::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
3011 } else if (!$nullwidths && !$nullcolors && !$nullstyles &&
3012 max(array_count_values($borderwidths)) == 3 &&
3013 max(array_count_values($borderstyles)) == 3 &&
3014 max(array_count_values($bordercolors)) == 3) {
3016 $widthkeys = array();
3017 $stylekeys = array();
3018 $colorkeys = array();
3020 foreach ($borderwidths as $key => $value) {
3021 if (!array_key_exists($value, $widthkeys)) {
3022 $widthkeys[$value] = array();
3024 $widthkeys[$value][] = $key;
3026 usort($widthkeys, 'css_sort_by_count');
3027 $widthkeys = array_values($widthkeys);
3029 foreach ($borderstyles as $key => $value) {
3030 if (!array_key_exists($value, $stylekeys)) {
3031 $stylekeys[$value] = array();
3033 $stylekeys[$value][] = $key;
3035 usort($stylekeys, 'css_sort_by_count');
3036 $stylekeys = array_values($stylekeys);
3038 foreach ($bordercolors as $key => $value) {
3039 if (!array_key_exists($value, $colorkeys)) {
3040 $colorkeys[$value] = array();
3042 $colorkeys[$value][] = $key;
3044 usort($colorkeys, 'css_sort_by_count');
3045 $colorkeys = array_values($colorkeys);
3047 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
3048 $key = $widthkeys[0][0];
3049 self
::build_style_string($return, 'css_style_border', 'border',
3050 $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
3051 $key = $widthkeys[1][0];
3052 self
::build_style_string($return, 'css_style_border'.$key, 'border-'.$key,
3053 $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
3055 self
::build_style_string($return, 'css_style_bordertop', 'border-top',
3056 $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
3057 self
::build_style_string($return, 'css_style_borderright', 'border-right',
3058 $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
3059 self
::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
3060 $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
3061 self
::build_style_string($return, 'css_style_borderleft', 'border-left',
3062 $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
3065 self
::build_style_string($return, 'css_style_bordertop', 'border-top',
3066 $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
3067 self
::build_style_string($return, 'css_style_borderright', 'border-right',
3068 $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
3069 self
::build_style_string($return, 'css_style_borderbottom', 'border-bottom',
3070 $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
3071 self
::build_style_string($return, 'css_style_borderleft', 'border-left',
3072 $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
3074 foreach ($return as $key => $style) {
3075 if ($style->get_value() == '') {
3076 unset($return[$key]);
3083 * Border styles get consolidated to a single border style.
3087 public function consolidate_to() {
3092 * Consolidates a series of border styles into an optimised array of border
3093 * styles by looking at the direction of the border and prioritising that
3094 * during the optimisation.
3096 * @param array $array An array to add styles into during consolidation. Passed by reference.
3097 * @param string $class The class type to initalise
3098 * @param string $style The style to create
3099 * @param string|array $top The top value
3100 * @param string $right The right value
3101 * @param string $bottom The bottom value
3102 * @param string $left The left value
3105 public static function consolidate_styles_by_direction(&$array, $class, $style,
3106 $top, $right = null, $bottom = null, $left = null) {
3107 if (is_array($top)) {
3108 $right = $top['right'];
3109 $bottom = $top['bottom'];
3110 $left = $top['left'];
3114 if ($top == $bottom && $left == $right && $top == $left) {
3115 if (is_null($top)) {
3116 $array[] = new $class($style, '');
3118 $array[] = new $class($style, $top);
3120 } else if ($top == null ||
$right == null ||
$bottom == null ||
$left == null) {
3121 if ($top !== null) {
3122 $array[] = new $class(str_replace('border-', 'border-top-', $style), $top);
3124 if ($right !== null) {
3125 $array[] = new $class(str_replace('border-', 'border-right-', $style), $right);
3127 if ($bottom !== null) {
3128 $array[] = new $class(str_replace('border-', 'border-bottom-', $style), $bottom);
3130 if ($left !== null) {
3131 $array[] = new $class(str_replace('border-', 'border-left-', $style), $left);
3133 } else if ($top == $bottom && $left == $right) {
3134 $array[] = new $class($style, $top.' '.$right);
3135 } else if ($left == $right) {
3136 $array[] = new $class($style, $top.' '.$right.' '.$bottom);
3138 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left);
3144 * Builds a border style for a set of width, style, and colour values
3146 * @param array $array An array into which the generated style is added
3147 * @param string $class The class type to initialise
3148 * @param string $cssstyle The style to use
3149 * @param string $width The width of the border
3150 * @param string $style The style of the border
3151 * @param string $color The colour of the border
3154 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) {
3155 if (!is_null($width) && !is_null($style) && !is_null($color)) {
3156 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color);
3157 } else if (!is_null($width) && !is_null($style) && is_null($color)) {
3158 $array[] = new $class($cssstyle, $width.' '.$style);
3159 } else if (!is_null($width) && is_null($style) && is_null($color)) {
3160 $array[] = new $class($cssstyle, $width);
3162 if (!is_null($width)) {
3163 $array[] = new $class($cssstyle, $width);
3165 if (!is_null($style)) {
3166 $array[] = new $class($cssstyle, $style);
3168 if (!is_null($color)) {
3169 $array[] = new $class($cssstyle, $color);
3177 * A border colour style
3180 * @subpackage cssoptimiser
3181 * @copyright 2012 Sam Hemelryk
3182 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3184 class css_style_bordercolor
extends css_style_color
{
3187 * Creates a new border colour style
3189 * Based upon the colour style
3191 * @param mixed $value
3192 * @return Array of css_style_bordercolor
3194 public static function init($value) {
3195 $value = preg_replace('#\s+#', ' ', $value);
3196 $bits = explode(' ', $value, 4);
3198 $top = $right = $bottom = $left = null;
3199 if (count($bits) > 0) {
3200 $top = $right = $bottom = $left = array_shift($bits);
3202 if (count($bits) > 0) {
3203 $right = $left = array_shift($bits);
3205 if (count($bits) > 0) {
3206 $bottom = array_shift($bits);
3208 if (count($bits) > 0) {
3209 $left = array_shift($bits);
3212 css_style_bordertopcolor
::init($top),
3213 css_style_borderrightcolor
::init($right),
3214 css_style_borderbottomcolor
::init($bottom),
3215 css_style_borderleftcolor
::init($left)
3220 * Consolidate this to a single border style
3224 public function consolidate_to() {
3231 * @param string $value Cleans the provided value optimising it if possible
3234 protected function clean_value($value) {
3235 $values = explode(' ', $value);
3236 $values = array_map('parent::clean_value', $values);
3237 return join (' ', $values);
3241 * Outputs this style
3243 * @param string $overridevalue
3246 public function out($overridevalue = null) {
3247 if ($overridevalue === null) {
3248 $overridevalue = $this->value
;
3250 $values = explode(' ', $overridevalue);
3251 $values = array_map('css_style_color::shrink_value', $values);
3252 return parent
::out(join (' ', $values));
3257 * A border left style
3260 * @subpackage cssoptimiser
3261 * @copyright 2012 Sam Hemelryk
3262 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3264 class css_style_borderleft
extends css_style_generic
{
3267 * Initialises the border left style into individual components
3269 * @param string $value
3270 * @return array Array of css_style_borderleftwidth|css_style_borderleftstyle|css_style_borderleftcolor
3272 public static function init($value) {
3273 $value = preg_replace('#\s+#', ' ', $value);
3274 $bits = explode(' ', $value, 3);
3277 if (count($bits) > 0) {
3278 $return[] = css_style_borderleftwidth
::init(array_shift($bits));
3280 if (count($bits) > 0) {
3281 $return[] = css_style_borderleftstyle
::init(array_shift($bits));
3283 if (count($bits) > 0) {
3284 $return[] = css_style_borderleftcolor
::init(array_shift($bits));
3290 * Consolidate this to a single border style
3294 public function consolidate_to() {
3300 * A border right style
3303 * @subpackage cssoptimiser
3304 * @copyright 2012 Sam Hemelryk
3305 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3307 class css_style_borderright
extends css_style_generic
{
3310 * Initialises the border right style into individual components
3312 * @param string $value The value of the style
3313 * @return array Array of css_style_borderrightwidth|css_style_borderrightstyle|css_style_borderrightcolor
3315 public static function init($value) {
3316 $value = preg_replace('#\s+#', ' ', $value);
3317 $bits = explode(' ', $value, 3);
3320 if (count($bits) > 0) {
3321 $return[] = css_style_borderrightwidth
::init(array_shift($bits));
3323 if (count($bits) > 0) {
3324 $return[] = css_style_borderrightstyle
::init(array_shift($bits));
3326 if (count($bits) > 0) {
3327 $return[] = css_style_borderrightcolor
::init(array_shift($bits));
3333 * Consolidate this to a single border style
3337 public function consolidate_to() {
3343 * A border top style
3346 * @subpackage cssoptimiser
3347 * @copyright 2012 Sam Hemelryk
3348 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3350 class css_style_bordertop
extends css_style_generic
{
3353 * Initialises the border top style into individual components
3355 * @param string $value The value of the style
3356 * @return array Array of css_style_bordertopwidth|css_style_bordertopstyle|css_style_bordertopcolor
3358 public static function init($value) {
3359 $value = preg_replace('#\s+#', ' ', $value);
3360 $bits = explode(' ', $value, 3);
3363 if (count($bits) > 0) {
3364 $return[] = css_style_bordertopwidth
::init(array_shift($bits));
3366 if (count($bits) > 0) {
3367 $return[] = css_style_bordertopstyle
::init(array_shift($bits));
3369 if (count($bits) > 0) {
3370 $return[] = css_style_bordertopcolor
::init(array_shift($bits));
3376 * Consolidate this to a single border style
3380 public function consolidate_to() {
3386 * A border bottom style
3389 * @subpackage cssoptimiser
3390 * @copyright 2012 Sam Hemelryk
3391 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3393 class css_style_borderbottom
extends css_style_generic
{
3396 * Initialises the border bottom style into individual components
3398 * @param string $value The value of the style
3399 * @return array Array of css_style_borderbottomwidth|css_style_borderbottomstyle|css_style_borderbottomcolor
3401 public static function init($value) {
3402 $value = preg_replace('#\s+#', ' ', $value);
3403 $bits = explode(' ', $value, 3);
3406 if (count($bits) > 0) {
3407 $return[] = css_style_borderbottomwidth
::init(array_shift($bits));
3409 if (count($bits) > 0) {
3410 $return[] = css_style_borderbottomstyle
::init(array_shift($bits));
3412 if (count($bits) > 0) {
3413 $return[] = css_style_borderbottomcolor
::init(array_shift($bits));
3419 * Consolidate this to a single border style
3423 public function consolidate_to() {
3429 * A border width style
3432 * @subpackage cssoptimiser
3433 * @copyright 2012 Sam Hemelryk
3434 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3436 class css_style_borderwidth
extends css_style_width
{
3439 * Creates a new border colour style
3441 * Based upon the colour style
3443 * @param string $value The value of the style
3444 * @return array Array of css_style_border*width
3446 public static function init($value) {
3447 $value = preg_replace('#\s+#', ' ', $value);
3448 $bits = explode(' ', $value, 4);
3450 $top = $right = $bottom = $left = null;
3451 if (count($bits) > 0) {
3452 $top = $right = $bottom = $left = array_shift($bits);
3454 if (count($bits) > 0) {
3455 $right = $left = array_shift($bits);
3457 if (count($bits) > 0) {
3458 $bottom = array_shift($bits);
3460 if (count($bits) > 0) {
3461 $left = array_shift($bits);
3464 css_style_bordertopwidth
::init($top),
3465 css_style_borderrightwidth
::init($right),
3466 css_style_borderbottomwidth
::init($bottom),
3467 css_style_borderleftwidth
::init($left)
3472 * Consolidate this to a single border style
3476 public function consolidate_to() {
3481 * Checks if the width is valid
3484 public function is_valid() {
3485 return self
::is_border_width($this->value
);
3489 * Cleans the provided value
3491 * @param mixed $value Cleans the provided value optimising it if possible
3494 protected function clean_value($value) {
3495 $isvalid = self
::is_border_width($value);
3497 $this->set_error('Invalid width specified for '.$this->name
);
3498 } else if (preg_match('#^0\D+$#', $value)) {
3501 return trim($value);
3505 * Returns true if the provided value is a permitted border width
3506 * @param string $value The value to check
3509 public static function is_border_width($value) {
3510 $altwidthvalues = array('thin', 'medium', 'thick');
3511 return css_is_width($value) ||
in_array($value, $altwidthvalues);
3516 * A border style style
3519 * @subpackage cssoptimiser
3520 * @copyright 2012 Sam Hemelryk
3521 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3523 class css_style_borderstyle
extends css_style_generic
{
3526 * Creates a new border colour style
3528 * Based upon the colour style
3530 * @param string $value The value of the style
3531 * @return array Array of css_style_border*style
3533 public static function init($value) {
3534 $value = preg_replace('#\s+#', ' ', $value);
3535 $bits = explode(' ', $value, 4);
3537 $top = $right = $bottom = $left = null;
3538 if (count($bits) > 0) {
3539 $top = $right = $bottom = $left = array_shift($bits);
3541 if (count($bits) > 0) {
3542 $right = $left = array_shift($bits);
3544 if (count($bits) > 0) {
3545 $bottom = array_shift($bits);
3547 if (count($bits) > 0) {
3548 $left = array_shift($bits);
3551 css_style_bordertopstyle
::init($top),
3552 css_style_borderrightstyle
::init($right),
3553 css_style_borderbottomstyle
::init($bottom),
3554 css_style_borderleftstyle
::init($left)
3559 * Consolidate this to a single border style
3563 public function consolidate_to() {
3569 * A border top colour style
3572 * @subpackage cssoptimiser
3573 * @copyright 2012 Sam Hemelryk
3574 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3576 class css_style_bordertopcolor
extends css_style_bordercolor
{
3579 * Initialises this style object
3581 * @param string $value The value of the style
3582 * @return css_style_bordertopcolor
3584 public static function init($value) {
3585 return new css_style_bordertopcolor('border-top-color', $value);
3589 * Consolidate this to a single border style
3593 public function consolidate_to() {
3599 * A border left colour style
3602 * @subpackage cssoptimiser
3603 * @copyright 2012 Sam Hemelryk
3604 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3606 class css_style_borderleftcolor
extends css_style_bordercolor
{
3609 * Initialises this style object
3611 * @param string $value The value of the style
3612 * @return css_style_borderleftcolor
3614 public static function init($value) {
3615 return new css_style_borderleftcolor('border-left-color', $value);
3619 * Consolidate this to a single border style
3623 public function consolidate_to() {
3629 * A border right colour style
3632 * @subpackage cssoptimiser
3633 * @copyright 2012 Sam Hemelryk
3634 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3636 class css_style_borderrightcolor
extends css_style_bordercolor
{
3639 * Initialises this style object
3641 * @param string $value The value of the style
3642 * @return css_style_borderrightcolor
3644 public static function init($value) {
3645 return new css_style_borderrightcolor('border-right-color', $value);
3649 * Consolidate this to a single border style
3653 public function consolidate_to() {
3659 * A border bottom colour style
3662 * @subpackage cssoptimiser
3663 * @copyright 2012 Sam Hemelryk
3664 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3666 class css_style_borderbottomcolor
extends css_style_bordercolor
{
3669 * Initialises this style object
3671 * @param string $value The value of the style
3672 * @return css_style_borderbottomcolor
3674 public static function init($value) {
3675 return new css_style_borderbottomcolor('border-bottom-color', $value);
3679 * Consolidate this to a single border style
3683 public function consolidate_to() {
3689 * A border width top style
3692 * @subpackage cssoptimiser
3693 * @copyright 2012 Sam Hemelryk
3694 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3696 class css_style_bordertopwidth
extends css_style_borderwidth
{
3699 * Initialises this style object
3701 * @param string $value The value of the style
3702 * @return css_style_bordertopwidth
3704 public static function init($value) {
3705 return new css_style_bordertopwidth('border-top-width', $value);
3709 * Consolidate this to a single border style
3713 public function consolidate_to() {
3719 * A border width left style
3722 * @subpackage cssoptimiser
3723 * @copyright 2012 Sam Hemelryk
3724 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3726 class css_style_borderleftwidth
extends css_style_borderwidth
{
3729 * Initialises this style object
3731 * @param string $value The value of the style
3732 * @return css_style_borderleftwidth
3734 public static function init($value) {
3735 return new css_style_borderleftwidth('border-left-width', $value);
3739 * Consolidate this to a single border style
3743 public function consolidate_to() {
3749 * A border width right style
3752 * @subpackage cssoptimiser
3753 * @copyright 2012 Sam Hemelryk
3754 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3756 class css_style_borderrightwidth
extends css_style_borderwidth
{
3759 * Initialises this style object
3761 * @param string $value The value of the style
3762 * @return css_style_borderrightwidth
3764 public static function init($value) {
3765 return new css_style_borderrightwidth('border-right-width', $value);
3769 * Consolidate this to a single border style
3773 public function consolidate_to() {
3779 * A border width bottom style
3782 * @subpackage cssoptimiser
3783 * @copyright 2012 Sam Hemelryk
3784 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3786 class css_style_borderbottomwidth
extends css_style_borderwidth
{
3789 * Initialises this style object
3791 * @param string $value The value of the style
3792 * @return css_style_borderbottomwidth
3794 public static function init($value) {
3795 return new css_style_borderbottomwidth('border-bottom-width', $value);
3799 * Consolidate this to a single border style
3803 public function consolidate_to() {
3809 * A border top style
3812 * @subpackage cssoptimiser
3813 * @copyright 2012 Sam Hemelryk
3814 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3816 class css_style_bordertopstyle
extends css_style_borderstyle
{
3819 * Initialises this style object
3821 * @param string $value The value of the style
3822 * @return css_style_bordertopstyle
3824 public static function init($value) {
3825 return new css_style_bordertopstyle('border-top-style', $value);
3829 * Consolidate this to a single border style
3833 public function consolidate_to() {
3839 * A border left style
3842 * @subpackage cssoptimiser
3843 * @copyright 2012 Sam Hemelryk
3844 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3846 class css_style_borderleftstyle
extends css_style_borderstyle
{
3849 * Initialises this style object
3851 * @param string $value The value of the style
3852 * @return css_style_borderleftstyle
3854 public static function init($value) {
3855 return new css_style_borderleftstyle('border-left-style', $value);
3859 * Consolidate this to a single border style
3863 public function consolidate_to() {
3869 * A border right style
3872 * @subpackage cssoptimiser
3873 * @copyright 2012 Sam Hemelryk
3874 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3876 class css_style_borderrightstyle
extends css_style_borderstyle
{
3879 * Initialises this style object
3881 * @param string $value The value of the style
3882 * @return css_style_borderrightstyle
3884 public static function init($value) {
3885 return new css_style_borderrightstyle('border-right-style', $value);
3889 * Consolidate this to a single border style
3893 public function consolidate_to() {
3899 * A border bottom style
3902 * @subpackage cssoptimiser
3903 * @copyright 2012 Sam Hemelryk
3904 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3906 class css_style_borderbottomstyle
extends css_style_borderstyle
{
3909 * Initialises this style object
3911 * @param string $value The value for the style
3912 * @return css_style_borderbottomstyle
3914 public static function init($value) {
3915 return new css_style_borderbottomstyle('border-bottom-style', $value);
3919 * Consolidate this to a single border style
3923 public function consolidate_to() {
3929 * A background style
3932 * @subpackage cssoptimiser
3933 * @copyright 2012 Sam Hemelryk
3934 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
3936 class css_style_background
extends css_style
implements core_css_consolidatable_style
{
3939 * Initialises a background style
3941 * @param string $value The value of the style
3942 * @return array An array of background component.
3944 public static function init($value) {
3945 // Colour - image - repeat - attachment - position.
3947 if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
3948 $imageurl = trim($matches[1]);
3949 $value = str_replace($matches[1], '', $value);
3952 // Switch out the brackets so that they don't get messed up when we explode.
3953 $brackets = array();
3955 while (preg_match('#\([^\)\(]+\)#', $value, $matches)) {
3956 $key = "##BRACKET-{$bracketcount}##";
3958 $brackets[$key] = $matches[0];
3959 $value = str_replace($matches[0], $key, $value);
3962 $important = (stripos($value, '!important') !== false);
3964 // Great some genius put !important in the background shorthand property.
3965 $value = str_replace('!important', '', $value);
3968 $value = preg_replace('#\s+#', ' ', $value);
3969 $bits = explode(' ', $value);
3971 foreach ($bits as $key => $bit) {
3972 $bits[$key] = self
::replace_bracket_placeholders($bit, $brackets);
3974 unset($bracketcount);
3977 $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit');
3978 $attachments = array('scroll' , 'fixed', 'inherit');
3979 $positions = array('top', 'left', 'bottom', 'right', 'center');
3981 /* @var css_style_background[] $return */
3983 $unknownbits = array();
3985 $color = self
::NULL_VALUE
;
3986 if (count($bits) > 0 && css_is_colour(reset($bits))) {
3987 $color = array_shift($bits);
3990 $image = self
::NULL_VALUE
;
3991 if (count($bits) > 0 && preg_match('#^\s*(none|inherit|url\(\))\s*$#', reset($bits))) {
3992 $image = array_shift($bits);
3993 if ($image == 'url()') {
3994 $image = "url({$imageurl})";
3998 $repeat = self
::NULL_VALUE
;
3999 if (count($bits) > 0 && in_array(reset($bits), $repeats)) {
4000 $repeat = array_shift($bits);
4003 $attachment = self
::NULL_VALUE
;
4004 if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
4005 // Scroll , fixed, inherit.
4006 $attachment = array_shift($bits);
4009 $position = self
::NULL_VALUE
;
4010 if (count($bits) > 0) {
4011 $widthbits = array();
4012 foreach ($bits as $bit) {
4013 if (in_array($bit, $positions) ||
css_is_width($bit)) {
4014 $widthbits[] = $bit;
4016 $unknownbits[] = $bit;
4019 if (count($widthbits)) {
4020 $position = join(' ', $widthbits);
4024 if (count($unknownbits)) {
4025 foreach ($unknownbits as $bit) {
4027 if ($color === self
::NULL_VALUE
&& css_is_colour($bit)) {
4029 } else if ($repeat === self
::NULL_VALUE
&& in_array($bit, $repeats)) {
4031 } else if ($attachment === self
::NULL_VALUE
&& in_array($bit, $attachments)) {
4033 } else if ($bit !== '') {
4034 $advanced = css_style_background_advanced
::init($bit);
4036 $advanced->set_important();
4038 $return[] = $advanced;
4043 if ($color === self
::NULL_VALUE
&&
4044 $image === self
::NULL_VALUE
&&
4045 $repeat === self
::NULL_VALUE
&& $attachment === self
::NULL_VALUE
&&
4046 $position === self
::NULL_VALUE
) {
4047 // All primaries are null, return without doing anything else. There may be advanced madness there.
4051 $return[] = css_style_backgroundcolor
::init($color);
4052 $return[] = css_style_backgroundimage
::init($image);
4053 $return[] = css_style_backgroundrepeat
::init($repeat);
4054 $return[] = css_style_backgroundattachment
::init($attachment);
4055 $return[] = css_style_backgroundposition
::init($position);
4058 foreach ($return as $style) {
4059 $style->set_important();
4067 * Static helper method to switch in bracket replacements
4069 * @param string $value
4070 * @param array $placeholders
4073 protected static function replace_bracket_placeholders($value, array $placeholders) {
4074 while (preg_match('/##BRACKET-\d+##/', $value, $matches)) {
4075 $value = str_replace($matches[0], $placeholders[$matches[0]], $value);
4081 * Consolidates background styles into a single background style
4083 * @param css_style_background[] $styles Consolidates the provided array of background styles
4084 * @return css_style[] Consolidated optimised background styles
4086 public static function consolidate(array $styles) {
4088 if (empty($styles)) {
4101 $someimportant = false;
4102 $allimportant = null;
4103 foreach ($styles as $style) {
4104 if ($style instanceof css_style_backgroundimage_advanced
) {
4107 if ($style->is_important()) {
4108 $someimportant = true;
4109 if ($allimportant === null) {
4110 $allimportant = true;
4112 } else if ($allimportant === true) {
4113 $allimportant = false;
4117 /* @var css_style[] $organisedstyles */
4118 $organisedstyles = array();
4119 /* @var css_style[] $advancedstyles */
4120 $advancedstyles = array();
4121 /* @var css_style[] $importantstyles */
4122 $importantstyles = array();
4123 foreach ($styles as $style) {
4124 if ($style instanceof css_style_backgroundimage_advanced
) {
4125 $advancedstyles[] = $style;
4128 if ($someimportant && !$allimportant && $style->is_important()) {
4129 $importantstyles[] = $style;
4132 $organisedstyles[$style->get_name()] = $style;
4133 switch ($style->get_name()) {
4134 case 'background-color' :
4135 $color = css_style_color
::shrink_value($style->get_value(false));
4137 case 'background-image' :
4138 $image = $style->get_value(false);
4140 case 'background-repeat' :
4141 $repeat = $style->get_value(false);
4143 case 'background-attachment' :
4144 $attachment = $style->get_value(false);
4146 case 'background-position' :
4147 $position = $style->get_value(false);
4149 case 'background-clip' :
4150 $clip = $style->get_value();
4152 case 'background-origin' :
4153 $origin = $style->get_value();
4155 case 'background-size' :
4156 $size = $style->get_value();
4161 /* @var css_style[] $consolidatetosingle */
4162 $consolidatetosingle = array();
4163 if (!is_null($color) && !is_null($image) && !is_null($repeat) && !is_null($attachment) && !is_null($position)) {
4164 // We can use the shorthand background-style!
4165 if (!$organisedstyles['background-color']->is_special_empty_value()) {
4166 $consolidatetosingle[] = $color;
4168 if (!$organisedstyles['background-image']->is_special_empty_value()) {
4169 $consolidatetosingle[] = $image;
4171 if (!$organisedstyles['background-repeat']->is_special_empty_value()) {
4172 $consolidatetosingle[] = $repeat;
4174 if (!$organisedstyles['background-attachment']->is_special_empty_value()) {
4175 $consolidatetosingle[] = $attachment;
4177 if (!$organisedstyles['background-position']->is_special_empty_value()) {
4178 $consolidatetosingle[] = $position;
4180 // Reset them all to null so we don't use them again.
4189 // Single background style needs to come first.
4190 if (count($consolidatetosingle) > 0) {
4191 $returnstyle = new css_style_background('background', join(' ', $consolidatetosingle));
4192 if ($allimportant) {
4193 $returnstyle->set_important();
4195 $return[] = $returnstyle;
4197 foreach ($styles as $style) {
4199 switch ($style->get_name()) {
4200 case 'background-color' :
4203 case 'background-image' :
4206 case 'background-repeat' :
4209 case 'background-attachment' :
4210 $value = $attachment;
4212 case 'background-position' :
4215 case 'background-clip' :
4218 case 'background-origin':
4221 case 'background-size':
4225 if (!is_null($value)) {
4229 $return = array_merge($return, $importantstyles, $advancedstyles);
4235 * A advanced background style that allows multiple values to preserve unknown entities
4238 * @subpackage cssoptimiser
4239 * @copyright 2012 Sam Hemelryk
4240 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4242 class css_style_background_advanced
extends css_style_generic
{
4244 * Creates a new background colour style
4246 * @param string $value The value of the style
4247 * @return css_style_backgroundimage
4249 public static function init($value) {
4250 $value = preg_replace('#\s+#', ' ', $value);
4251 return new css_style_background_advanced('background', $value);
4255 * Returns true because the advanced background image supports multiple values.
4256 * e.g. -webkit-linear-gradient and -moz-linear-gradient.
4260 public function allows_multiple_values() {
4266 * A background colour style.
4268 * Based upon the colour style.
4271 * @subpackage cssoptimiser
4272 * @copyright 2012 Sam Hemelryk
4273 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4275 class css_style_backgroundcolor
extends css_style_color
{
4278 * Creates a new background colour style
4280 * @param string $value The value of the style
4281 * @return css_style_backgroundcolor
4283 public static function init($value) {
4284 return new css_style_backgroundcolor('background-color', $value);
4288 * css_style_backgroundcolor consolidates to css_style_background
4292 public function consolidate_to() {
4293 return 'background';
4297 * Returns true if the value for this style is the special null value.
4299 * This occurs if the shorthand background property was used but no proper value
4300 * was specified for this style.
4301 * This leads to a null value being used unless otherwise overridden.
4305 public function is_special_empty_value() {
4306 return ($this->value
=== self
::NULL_VALUE
);
4310 * Returns true if the value for this style is valid
4313 public function is_valid() {
4314 return $this->is_special_empty_value() || parent
::is_valid();
4319 * A background image style.
4322 * @subpackage cssoptimiser
4323 * @copyright 2012 Sam Hemelryk
4324 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4326 class css_style_backgroundimage
extends css_style_generic
{
4329 * Creates a new background image style
4331 * @param string $value The value of the style
4332 * @return css_style_backgroundimage
4334 public static function init($value) {
4335 if ($value !== self
::NULL_VALUE
&& !preg_match('#^\s*(none|inherit|url\()#i', $value)) {
4336 return css_style_backgroundimage_advanced
::init($value);
4338 return new css_style_backgroundimage('background-image', $value);
4342 * Consolidates this style into a single background style
4346 public function consolidate_to() {
4347 return 'background';
4351 * Returns true if the value for this style is the special null value.
4353 * This occurs if the shorthand background property was used but no proper value
4354 * was specified for this style.
4355 * This leads to a null value being used unless otherwise overridden.
4359 public function is_special_empty_value() {
4360 return ($this->value
=== self
::NULL_VALUE
);
4364 * Returns true if the value for this style is valid
4367 public function is_valid() {
4368 return $this->is_special_empty_value() || parent
::is_valid();
4373 * A background image style that supports multiple values and masquerades as a background-image
4376 * @subpackage cssoptimiser
4377 * @copyright 2012 Sam Hemelryk
4378 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4380 class css_style_backgroundimage_advanced
extends css_style_generic
{
4382 * Creates a new background colour style
4384 * @param string $value The value of the style
4385 * @return css_style_backgroundimage
4387 public static function init($value) {
4388 $value = preg_replace('#\s+#', ' ', $value);
4389 return new css_style_backgroundimage_advanced('background-image', $value);
4393 * Returns true because the advanced background image supports multiple values.
4394 * e.g. -webkit-linear-gradient and -moz-linear-gradient.
4398 public function allows_multiple_values() {
4404 * A background repeat style.
4407 * @subpackage cssoptimiser
4408 * @copyright 2012 Sam Hemelryk
4409 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4411 class css_style_backgroundrepeat
extends css_style_generic
{
4414 * Creates a new background colour style
4416 * @param string $value The value of the style
4417 * @return css_style_backgroundrepeat
4419 public static function init($value) {
4420 return new css_style_backgroundrepeat('background-repeat', $value);
4424 * Consolidates this style into a single background style
4428 public function consolidate_to() {
4429 return 'background';
4433 * Returns true if the value for this style is the special null value.
4435 * This occurs if the shorthand background property was used but no proper value
4436 * was specified for this style.
4437 * This leads to a null value being used unless otherwise overridden.
4441 public function is_special_empty_value() {
4442 return ($this->value
=== self
::NULL_VALUE
);
4446 * Returns true if the value for this style is valid
4449 public function is_valid() {
4450 return $this->is_special_empty_value() || parent
::is_valid();
4455 * A background attachment style.
4458 * @subpackage cssoptimiser
4459 * @copyright 2012 Sam Hemelryk
4460 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4462 class css_style_backgroundattachment
extends css_style_generic
{
4465 * Creates a new background colour style
4467 * @param string $value The value of the style
4468 * @return css_style_backgroundattachment
4470 public static function init($value) {
4471 return new css_style_backgroundattachment('background-attachment', $value);
4475 * Consolidates this style into a single background style
4479 public function consolidate_to() {
4480 return 'background';
4484 * Returns true if the value for this style is the special null value.
4486 * This occurs if the shorthand background property was used but no proper value
4487 * was specified for this style.
4488 * This leads to a null value being used unless otherwise overridden.
4492 public function is_special_empty_value() {
4493 return ($this->value
=== self
::NULL_VALUE
);
4497 * Returns true if the value for this style is valid
4500 public function is_valid() {
4501 return $this->is_special_empty_value() || parent
::is_valid();
4506 * A background position style.
4509 * @subpackage cssoptimiser
4510 * @copyright 2012 Sam Hemelryk
4511 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4513 class css_style_backgroundposition
extends css_style_generic
{
4516 * Creates a new background colour style
4518 * @param string $value The value of the style
4519 * @return css_style_backgroundposition
4521 public static function init($value) {
4522 return new css_style_backgroundposition('background-position', $value);
4526 * Consolidates this style into a single background style
4530 public function consolidate_to() {
4531 return 'background';
4535 * Returns true if the value for this style is the special null value.
4537 * This occurs if the shorthand background property was used but no proper value
4538 * was specified for this style.
4539 * This leads to a null value being used unless otherwise overridden.
4543 public function is_special_empty_value() {
4544 return ($this->value
=== self
::NULL_VALUE
);
4548 * Returns true if the value for this style is valid
4551 public function is_valid() {
4552 return $this->is_special_empty_value() || parent
::is_valid();
4557 * A background size style.
4560 * @subpackage cssoptimiser
4561 * @copyright 2012 Sam Hemelryk
4562 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4564 class css_style_backgroundsize
extends css_style_generic
{
4567 * Creates a new background size style
4569 * @param string $value The value of the style
4570 * @return css_style_backgroundposition
4572 public static function init($value) {
4573 return new css_style_backgroundsize('background-size', $value);
4577 * Consolidates this style into a single background style
4581 public function consolidate_to() {
4582 return 'background';
4587 * A background clip style.
4590 * @subpackage cssoptimiser
4591 * @copyright 2012 Sam Hemelryk
4592 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4594 class css_style_backgroundclip
extends css_style_generic
{
4597 * Creates a new background clip style
4599 * @param string $value The value of the style
4600 * @return css_style_backgroundposition
4602 public static function init($value) {
4603 return new css_style_backgroundclip('background-clip', $value);
4607 * Consolidates this style into a single background style
4611 public function consolidate_to() {
4612 return 'background';
4617 * A background origin style.
4620 * @subpackage cssoptimiser
4621 * @copyright 2012 Sam Hemelryk
4622 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4624 class css_style_backgroundorigin
extends css_style_generic
{
4627 * Creates a new background origin style
4629 * @param string $value The value of the style
4630 * @return css_style_backgroundposition
4632 public static function init($value) {
4633 return new css_style_backgroundorigin('background-origin', $value);
4637 * Consolidates this style into a single background style
4641 public function consolidate_to() {
4642 return 'background';
4650 * @subpackage cssoptimiser
4651 * @copyright 2012 Sam Hemelryk
4652 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4654 class css_style_padding
extends css_style_width
implements core_css_consolidatable_style
{
4657 * Initialises this padding style into several individual padding styles
4659 * @param string $value The value fo the style
4660 * @return array An array of padding styles
4662 public static function init($value) {
4664 if (strpos($value, '!important') !== false) {
4665 $important = ' !important';
4666 $value = str_replace('!important', '', $value);
4669 $value = preg_replace('#\s+#', ' ', trim($value));
4670 $bits = explode(' ', $value, 4);
4672 $top = $right = $bottom = $left = null;
4673 if (count($bits) > 0) {
4674 $top = $right = $bottom = $left = array_shift($bits);
4676 if (count($bits) > 0) {
4677 $right = $left = array_shift($bits);
4679 if (count($bits) > 0) {
4680 $bottom = array_shift($bits);
4682 if (count($bits) > 0) {
4683 $left = array_shift($bits);
4686 new css_style_paddingtop('padding-top', $top.$important),
4687 new css_style_paddingright('padding-right', $right.$important),
4688 new css_style_paddingbottom('padding-bottom', $bottom.$important),
4689 new css_style_paddingleft('padding-left', $left.$important)
4694 * Consolidates several padding styles into a single style.
4696 * @param css_style_padding[] $styles Array of padding styles
4697 * @return css_style[] Optimised+consolidated array of padding styles
4699 public static function consolidate(array $styles) {
4700 if (count($styles) != 4) {
4704 $someimportant = false;
4705 $allimportant = null;
4706 $notimportantequal = null;
4708 foreach ($styles as $style) {
4709 if ($style->is_important()) {
4710 $someimportant = true;
4711 if ($allimportant === null) {
4712 $allimportant = true;
4715 if ($allimportant === true) {
4716 $allimportant = false;
4718 if ($firstvalue == null) {
4719 $firstvalue = $style->get_value(false);
4720 $notimportantequal = true;
4721 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
4722 $notimportantequal = false;
4727 if ($someimportant && !$allimportant && !$notimportantequal) {
4731 if ($someimportant && !$allimportant && $notimportantequal) {
4733 new css_style_padding('padding', $firstvalue)
4735 foreach ($styles as $style) {
4736 if ($style->is_important()) {
4746 foreach ($styles as $style) {
4747 switch ($style->get_name()) {
4748 case 'padding-top' :
4749 $top = $style->get_value(false);
4751 case 'padding-right' :
4752 $right = $style->get_value(false);
4754 case 'padding-bottom' :
4755 $bottom = $style->get_value(false);
4757 case 'padding-left' :
4758 $left = $style->get_value(false);
4762 if ($top == $bottom && $left == $right) {
4763 if ($top == $left) {
4764 $returnstyle = new css_style_padding('padding', $top);
4766 $returnstyle = new css_style_padding('padding', "{$top} {$left}");
4768 } else if ($left == $right) {
4769 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom}");
4771 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}");
4773 if ($allimportant) {
4774 $returnstyle->set_important();
4776 return array($returnstyle);
4782 * A padding top style.
4785 * @subpackage cssoptimiser
4786 * @copyright 2012 Sam Hemelryk
4787 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4789 class css_style_paddingtop
extends css_style_padding
{
4792 * Initialises this style
4794 * @param string $value The value of the style
4795 * @return css_style_paddingtop
4797 public static function init($value) {
4798 return new css_style_paddingtop('padding-top', $value);
4802 * Consolidates this style into a single padding style
4806 public function consolidate_to() {
4812 * A padding right style.
4815 * @subpackage cssoptimiser
4816 * @copyright 2012 Sam Hemelryk
4817 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4819 class css_style_paddingright
extends css_style_padding
{
4822 * Initialises this style
4824 * @param string $value The value of the style
4825 * @return css_style_paddingright
4827 public static function init($value) {
4828 return new css_style_paddingright('padding-right', $value);
4832 * Consolidates this style into a single padding style
4836 public function consolidate_to() {
4842 * A padding bottom style.
4845 * @subpackage cssoptimiser
4846 * @copyright 2012 Sam Hemelryk
4847 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4849 class css_style_paddingbottom
extends css_style_padding
{
4852 * Initialises this style
4854 * @param string $value The value of the style
4855 * @return css_style_paddingbottom
4857 public static function init($value) {
4858 return new css_style_paddingbottom('padding-bottom', $value);
4862 * Consolidates this style into a single padding style
4866 public function consolidate_to() {
4872 * A padding left style.
4875 * @subpackage cssoptimiser
4876 * @copyright 2012 Sam Hemelryk
4877 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4879 class css_style_paddingleft
extends css_style_padding
{
4882 * Initialises this style
4884 * @param string $value The value of the style
4885 * @return css_style_paddingleft
4887 public static function init($value) {
4888 return new css_style_paddingleft('padding-left', $value);
4892 * Consolidates this style into a single padding style
4896 public function consolidate_to() {
4905 * @subpackage cssoptimiser
4906 * @copyright 2012 Sam Hemelryk
4907 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4909 class css_style_cursor
extends css_style_generic
{
4911 * Initialises a new cursor style
4912 * @param string $value
4913 * @return css_style_cursor
4915 public static function init($value) {
4916 return new css_style_cursor('cursor', $value);
4919 * Cleans the given value and returns it.
4921 * @param string $value
4924 protected function clean_value($value) {
4925 // Allowed values for the cursor style.
4926 $allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize',
4927 'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit');
4928 // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough.
4929 if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) {
4930 $this->set_error('Invalid or unexpected cursor value specified: '.$value);
4932 return trim($value);
4937 * A vertical alignment style.
4940 * @subpackage cssoptimiser
4941 * @copyright 2012 Sam Hemelryk
4942 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4944 class css_style_verticalalign
extends css_style_generic
{
4946 * Initialises a new vertical alignment style
4947 * @param string $value
4948 * @return css_style_verticalalign
4950 public static function init($value) {
4951 return new css_style_verticalalign('vertical-align', $value);
4954 * Cleans the given value and returns it.
4956 * @param string $value
4959 protected function clean_value($value) {
4960 $allowed = array('baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom', 'inherit');
4961 if (!css_is_width($value) && !in_array($value, $allowed)) {
4962 $this->set_error('Invalid vertical-align value specified: '.$value);
4964 return trim($value);
4972 * @subpackage cssoptimiser
4973 * @copyright 2012 Sam Hemelryk
4974 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4976 class css_style_float
extends css_style_generic
{
4978 * Initialises a new float style
4979 * @param string $value
4980 * @return css_style_float
4982 public static function init($value) {
4983 return new css_style_float('float', $value);
4986 * Cleans the given value and returns it.
4988 * @param string $value
4991 protected function clean_value($value) {
4992 $allowed = array('left', 'right', 'none', 'inherit');
4993 if (!css_is_width($value) && !in_array($value, $allowed)) {
4994 $this->set_error('Invalid float value specified: '.$value);
4996 return trim($value);