MDL-29941 csslib: Improved unit tests and fixed bugs with background and border parti...
[moodle.git] / lib / csslib.php
CommitLineData
0e641c74
SH
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
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/>.
16
17/**
18 * This file contains CSS related methods and a CSS optimiser
19 *
20 * @package moodlecore
21 * @copyright 2011 Sam Hemelryk
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25/**
26 * Stores CSS in a file at the given path.
27 *
28 * @param theme_config $theme
29 * @param string $csspath
30 * @param array $cssfiles
31 */
32function css_store_css(theme_config $theme, $csspath, array $cssfiles) {
1d1d807e
SH
33 global $CFG;
34
35 if (!empty($CFG->cssoptimise)) {
36 // This is an experimental feature introduced in Moodle 2.2
37 // The CSS optimiser organises the CSS in order to reduce the overall number
38 // of rules and styles being sent to the client. It does this by collating
39 // the CSS before it is cached removing excess styles and rules and stripping
40 // out any extraneous content such as comments and empty rules.
41 $optimiser = new css_optimiser;
42 $css = '';
43 foreach ($cssfiles as $file) {
44 $css .= file_get_contents($file)."\n";
45 }
46 $css = $theme->post_process($css);
47 $css = $optimiser->process($css);
0e641c74 48
1d1d807e
SH
49 // If cssoptimisestats is set then stats from the optimisation are collected
50 // and output at the beginning of the CSS
51 if (!empty($CFG->cssoptimisestats)) {
52 $css = $optimiser->output_stats_css().$css;
53 }
54 } else {
55 // This is the default behaviour.
56 // The cssoptimise setting was introduced in Moodle 2.2 and will hopefully
57 // in the future be changed from an experimental setting to the default.
58 // The css_minify_css will method will use the Minify library remove
59 // comments, additional whitespace and other minor measures to reduce the
60 // the overall CSS being sent.
61 // However it has the distinct disadvantage of having to minify the CSS
62 // before running the post process functions. Potentially things may break
63 // here if theme designers try to push things with CSS post processing.
64 $css = $theme->post_process(css_minify_css($cssfiles));
65 }
0e641c74
SH
66
67 check_dir_exists(dirname($csspath));
68 $fp = fopen($csspath, 'w');
69 fwrite($fp, $css);
70 fclose($fp);
71 return true;
72}
73
74/**
75 * Sends IE specific CSS
76 *
77 * @param string $themename
78 * @param string $rev
79 */
80function css_send_ie_css($themename, $rev) {
81 $lifetime = 60*60*24*30; // 30 days
82
83 $css = "/** Unfortunately IE6/7 does not support more than 4096 selectors in one CSS file, which means we have to use some ugly hacks :-( **/";
84 $css = "@import url(styles.php?theme=$themename&rev=$rev&type=plugins);";
85 $css = "@import url(styles.php?theme=$themename&rev=$rev&type=parents);";
86 $css = "@import url(styles.php?theme=$themename&rev=$rev&type=theme);";
87
88 header('Etag: '.md5($rev));
89 header('Content-Disposition: inline; filename="styles.php"');
90 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
91 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
92 header('Pragma: ');
93 header('Cache-Control: max-age='.$lifetime);
94 header('Accept-Ranges: none');
95 header('Content-Type: text/css; charset=utf-8');
96 header('Content-Length: '.strlen($css));
97
98 echo $css;
99 die;
100}
101
102/**
103 * Sends a cached CSS file
104 *
105 * @param string $csspath
106 * @param string $rev
107 */
108function css_send_cached_css($csspath, $rev) {
109 $lifetime = 60*60*24*30; // 30 days
110
111 header('Content-Disposition: inline; filename="styles.php"');
112 header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
113 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
114 header('Pragma: ');
115 header('Cache-Control: max-age='.$lifetime);
116 header('Accept-Ranges: none');
117 header('Content-Type: text/css; charset=utf-8');
118 if (!min_enable_zlib_compression()) {
119 header('Content-Length: '.filesize($csspath));
120 }
121
122 readfile($csspath);
123 die;
124}
125
126/**
127 * Sends CSS directly without caching it.
128 *
129 * @param string CSS
130 */
131function css_send_uncached_css($css) {
1d1d807e 132 global $CFG;
0e641c74
SH
133
134 header('Content-Disposition: inline; filename="styles_debug.php"');
135 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
136 header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) .' GMT');
137 header('Pragma: ');
138 header('Accept-Ranges: none');
139 header('Content-Type: text/css; charset=utf-8');
140
141 if (is_array($css)) {
142 $css = implode("\n\n", $css);
143 }
1d1d807e
SH
144
145 if (!empty($CFG->cssoptimise) && !empty($CFG->cssoptimisedebug)) {
146 $css = str_replace("\n", "\r\n", $css);
147
148 $optimiser = new css_optimiser;
149 $css = $optimiser->process($css);
150 if (!empty($CFG->cssoptimisestats)) {
151 $css = $optimiser->output_stats_css().$css;
152 }
153 }
154
155 echo $css;
0e641c74
SH
156
157 die;
158}
159
160/**
161 * Sends a 404 message about CSS not being found.
162 */
163function css_send_css_not_found() {
164 header('HTTP/1.0 404 not found');
165 die('CSS was not found, sorry.');
166}
167
1d1d807e
SH
168function css_minify_css($files) {
169 global $CFG;
170
171 set_include_path($CFG->libdir . '/minify/lib' . PATH_SEPARATOR . get_include_path());
172 require_once('Minify.php');
173
174 if (0 === stripos(PHP_OS, 'win')) {
175 Minify::setDocRoot(); // IIS may need help
176 }
177 // disable all caching, we do it in moodle
178 Minify::setCache(null, false);
179
180 $options = array(
181 'bubbleCssImports' => false,
182 // Don't gzip content we just want text for storage
183 'encodeOutput' => false,
184 // Maximum age to cache, not used but required
185 'maxAge' => (60*60*24*20),
186 // The files to minify
187 'files' => $files,
188 // Turn orr URI rewriting
189 'rewriteCssUris' => false,
190 // This returns the CSS rather than echoing it for display
191 'quiet' => true
192 );
193 $result = Minify::serve('Files', $options);
194 return $result['content'];
195}
196
197/**
198 * Given a value determines if it is a valid CSS colour
199 *
200 * @param string $value
201 * @return bool
202 */
203function css_is_colour($value) {
204 $value = trim($value);
205 if (preg_match('/^#([a-fA-F0-9]{1,6})$/', $value)) {
206 return true;
207 } else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) {
208 return true;
209 } else if (preg_match('#^(rgb|hsl)\s*\(\s*\d{1,3}\%?\s*,\s*\d{1,3}\%?\s*,\s*\d{1,3}\%?\s*\)$#', $value)) {
210 return true;
211 } else if (preg_match('#^(rgb|hsl)a\s*\(\s*\d{1,3}\%?\s*,\s*\d{1,3}\%?\s*,\s*\d{1,3}\%?\s*,\s*\d(\.\d+)?\s*\)$#', $value)) {
212 return true;
213 }
214 return false;
215}
216
0e641c74
SH
217/**
218 * A basic CSS optimiser that strips out unwanted things and then processing the
219 * CSS organising styles and moving duplicates and useless CSS.
220 *
221 * @package moodlecore
222 * @copyright 2011 Sam Hemelryk
223 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
224 */
225class css_optimiser {
226
227 /**#@+
228 * Processing states. Used internally.
229 */
230 const PROCESSING_START = 0;
231 const PROCESSING_SELECTORS = 0;
232 const PROCESSING_STYLES = 1;
233 const PROCESSING_COMMENT = 2;
234 const PROCESSING_ATRULE = 3;
235 /**#@-*/
236
237 /**#@+
238 * Stats variables set during and after processing
239 * @var int
240 */
241 protected $rawstrlen = 0;
242 protected $commentsincss = 0;
243 protected $rawrules = 0;
244 protected $rawselectors = 0;
245 protected $optimisedstrlen = 0;
246 protected $optimisedrules = 0;
247 protected $optimisedselectors = 0;
248 protected $timestart = 0;
249 protected $timecomplete = 0;
250 /**#@-*/
251
252 /**
253 * Processes incoming CSS optimising it and then returning it.
254 *
255 * @param string $css The raw CSS to optimise
256 * @return string The optimised CSS
257 */
258 public function process($css) {
259 global $CFG;
260
261 $this->reset_stats();
262 $this->timestart = microtime(true);
263 $this->rawstrlen = strlen($css);
264
265 // First up we need to remove all line breaks - this allows us to instantly
266 // reduce our processing requirements and as we will process everything
267 // into a new structure there's really nothing lost.
268 $css = preg_replace('#\r?\n#', ' ', $css);
269
270 // Next remove the comments... no need to them in an optimised world and
271 // knowing they're all gone allows us to REALLY make our processing simpler
272 $css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss);
273
274 $medias = array(
275 'all' => new css_media()
276 );
277 $imports = array();
278 $charset = false;
1d1d807e 279
0e641c74 280 $currentprocess = self::PROCESSING_START;
1d1d807e 281 $currentrule = css_rule::init();
0e641c74
SH
282 $currentselector = css_selector::init();
283 $inquotes = false; // ' or "
284 $inbraces = false; // {
285 $inbrackets = false; // [
286 $inparenthesis = false; // (
287 $currentmedia = $medias['all'];
288 $currentatrule = null;
289 $suspectatrule = false;
290
291 $buffer = '';
292 $char = null;
293
294 // Next we are going to iterate over every single character in $css.
295 // This is why re removed line breaks and comments!
296 for ($i = 0; $i < $this->rawstrlen; $i++) {
297 $lastchar = $char;
298 $char = substr($css, $i, 1);
299 if ($char == '@' && $buffer == '') {
300 $suspectatrule = true;
301 }
302 switch ($currentprocess) {
303 // Start processing an at rule e.g. @media, @page
304 case self::PROCESSING_ATRULE:
305 switch ($char) {
306 case ';':
307 if (!$inbraces) {
308 $buffer .= $char;
309 if ($currentatrule == 'import') {
310 $imports[] = $buffer;
311 $currentprocess = self::PROCESSING_SELECTORS;
312 } else if ($currentatrule == 'charset') {
313 $charset = $buffer;
314 $currentprocess = self::PROCESSING_SELECTORS;
315 }
316 }
317 $buffer = '';
318 $currentatrule = false;
319 continue 3;
320 case '{':
321 if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)#', $buffer, $matches)) {
322 $mediatypes = str_replace(' ', '', $matches[1]);
323 if (!array_key_exists($mediatypes, $medias)) {
324 $medias[$mediatypes] = new css_media($mediatypes);
325 }
326 $currentmedia = $medias[$mediatypes];
327 $currentprocess = self::PROCESSING_SELECTORS;
328 $buffer = '';
329 }
330 continue 3;
331 }
332 break;
333 // Start processing selectors
334 case self::PROCESSING_START:
335 case self::PROCESSING_SELECTORS:
336 switch ($char) {
337 case '[':
338 $inbrackets ++;
339 $buffer .= $char;
340 continue 3;
341 case ']':
342 $inbrackets --;
343 $buffer .= $char;
344 continue 3;
345 case ' ':
346 if ($inbrackets) {
347 continue 3;
348 }
349 if (!empty($buffer)) {
350 if ($suspectatrule && preg_match('#@(media|import|charset)\s*#', $buffer, $matches)) {
351 $currentatrule = $matches[1];
352 $currentprocess = self::PROCESSING_ATRULE;
353 $buffer .= $char;
354 } else {
355 $currentselector->add($buffer);
356 $buffer = '';
357 }
358 }
359 $suspectatrule = false;
360 continue 3;
361 case '{':
362 if ($inbrackets) {
363 continue 3;
364 }
1d1d807e 365
0e641c74 366 $currentselector->add($buffer);
1d1d807e 367 $currentrule->add_selector($currentselector);
0e641c74
SH
368 $currentselector = css_selector::init();
369 $currentprocess = self::PROCESSING_STYLES;
370
371 $buffer = '';
372 continue 3;
373 case '}':
374 if ($inbrackets) {
375 continue 3;
376 }
377 if ($currentatrule == 'media') {
378 $currentmedia = $medias['all'];
379 $currentatrule = false;
380 $buffer = '';
381 }
382 continue 3;
383 case ',':
384 if ($inbrackets) {
385 continue 3;
386 }
387 $currentselector->add($buffer);
1d1d807e 388 $currentrule->add_selector($currentselector);
0e641c74
SH
389 $currentselector = css_selector::init();
390 $buffer = '';
391 continue 3;
392 }
393 break;
394 // Start processing styles
395 case self::PROCESSING_STYLES:
396 if ($char == '"' || $char == "'") {
397 if ($inquotes === false) {
398 $inquotes = $char;
399 }
400 if ($inquotes === $char && $lastchar !== '\\') {
401 $inquotes = false;
402 }
403 }
404 if ($inquotes) {
405 $buffer .= $char;
406 continue 2;
407 }
408 switch ($char) {
409 case ';':
1d1d807e 410 $currentrule->add_style($buffer);
0e641c74
SH
411 $buffer = '';
412 $inquotes = false;
413 continue 3;
414 case '}':
1d1d807e
SH
415 $currentrule->add_style($buffer);
416 $this->rawselectors += $currentrule->get_selector_count();
0e641c74 417
1d1d807e 418 $currentmedia->add_rule($currentrule);
0e641c74 419
1d1d807e 420 $currentrule = css_rule::init();
0e641c74
SH
421 $currentprocess = self::PROCESSING_SELECTORS;
422 $this->rawrules++;
423 $buffer = '';
424 $inquotes = false;
425 continue 3;
426 }
427 break;
428 }
429 $buffer .= $char;
430 }
431
432 $css = '';
433 if (!empty($charset)) {
434 $imports[] = $charset;
435 }
436 if (!empty($imports)) {
437 $css .= implode("\n", $imports);
438 $css .= "\n\n";
439 }
440 foreach ($medias as $media) {
441 $media->organise_rules_by_selectors();
442 $this->optimisedrules += $media->count_rules();
443 $this->optimisedselectors += $media->count_selectors();
444 $css .= $media->out();
445 }
446 $this->optimisedstrlen = strlen($css);
447
448 $this->timecomplete = microtime(true);
0e641c74
SH
449 return trim($css);
450 }
451
452 /**
8589a4a5 453 * Returns an array of stats from the last processing run
0e641c74
SH
454 * @return string
455 */
456 public function get_stats() {
8589a4a5
SH
457 $stats = array(
458 'timestart' => $this->timestart,
459 'timecomplete' => $this->timecomplete,
460 'timetaken' => round($this->timecomplete - $this->timestart, 4),
461 'commentsincss' => $this->commentsincss,
462 'rawstrlen' => $this->rawstrlen,
463 'rawselectors' => $this->rawselectors,
464 'rawrules' => $this->rawrules,
465 'optimisedstrlen' => $this->optimisedstrlen,
466 'optimisedrules' => $this->optimisedrules,
1d1d807e 467 'optimisedselectors' => $this->optimisedselectors,
8589a4a5
SH
468 'improvementstrlen' => round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%',
469 'improvementrules' => round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%',
470 'improvementselectors' => round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%',
471 );
472 return $stats;
473 }
474
475 /**
476 * Returns a string to display stats about the last generation within CSS output
477 * @return string
478 */
479 public function output_stats_css() {
480 $stats = $this->get_stats();
481
482 $strlenimprovement = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1);
483 $ruleimprovement = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1);
484 $selectorimprovement = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1);
0e641c74
SH
485 $timetaken = round($this->timecomplete - $this->timestart, 4);
486
487 $computedcss = "/****************************************\n";
488 $computedcss .= " *------- CSS Optimisation stats --------\n";
489 $computedcss .= " * ".date('r')."\n";
1d1d807e
SH
490 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
491 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
0e641c74 492 $computedcss .= " *--------------- before ----------------\n";
1d1d807e
SH
493 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n";
494 $computedcss .= " * {$stats['rawrules']} \t rules read in\n";
495 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n";
0e641c74 496 $computedcss .= " *---------------- after ----------------\n";
1d1d807e
SH
497 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n";
498 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n";
499 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n";
0e641c74 500 $computedcss .= " *---------------- stats ----------------\n";
1d1d807e
SH
501 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n";
502 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n";
503 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n";
0e641c74
SH
504 $computedcss .= " ****************************************/\n\n";
505
506 return $computedcss;
507 }
508
509 /**
510 * Resets the stats ready for another fresh processing
511 */
512 public function reset_stats() {
513 $this->commentsincss = 0;
514 $this->optimisedrules = 0;
515 $this->optimisedselectors = 0;
516 $this->optimisedstrlen = 0;
517 $this->rawrules = 0;
518 $this->rawselectors = 0;
519 $this->rawstrlen = 0;
520 $this->timecomplete = 0;
521 $this->timestart = 0;
522 }
523
524 /**
525 * An array of the common HTML colours that are supported by most browsers.
526 *
527 * This reference table is used to allow us to unify colours, and will aid
528 * us in identifying buggy CSS using unsupported colours.
529 *
530 * @staticvar array
531 * @var array
532 */
533 public static $htmlcolours = array(
534 'aliceblue' => '#F0F8FF',
535 'antiquewhite' => '#FAEBD7',
536 'aqua' => '#00FFFF',
537 'aquamarine' => '#7FFFD4',
538 'azure' => '#F0FFFF',
539 'beige' => '#F5F5DC',
540 'bisque' => '#FFE4C4',
541 'black' => '#000000',
542 'blanchedalmond' => '#FFEBCD',
543 'blue' => '#0000FF',
544 'blueviolet' => '#8A2BE2',
545 'brown' => '#A52A2A',
546 'burlywood' => '#DEB887',
547 'cadetblue' => '#5F9EA0',
548 'chartreuse' => '#7FFF00',
549 'chocolate' => '#D2691E',
550 'coral' => '#FF7F50',
551 'cornflowerblue' => '#6495ED',
552 'cornsilk' => '#FFF8DC',
553 'crimson' => '#DC143C',
554 'cyan' => '#00FFFF',
555 'darkblue' => '#00008B',
556 'darkcyan' => '#008B8B',
557 'darkgoldenrod' => '#B8860B',
558 'darkgray' => '#A9A9A9',
559 'darkgrey' => '#A9A9A9',
560 'darkgreen' => '#006400',
561 'darkKhaki' => '#BDB76B',
562 'darkmagenta' => '#8B008B',
563 'darkolivegreen' => '#556B2F',
564 'arkorange' => '#FF8C00',
565 'darkorchid' => '#9932CC',
566 'darkred' => '#8B0000',
567 'darksalmon' => '#E9967A',
568 'darkseagreen' => '#8FBC8F',
569 'darkslateblue' => '#483D8B',
570 'darkslategray' => '#2F4F4F',
571 'darkslategrey' => '#2F4F4F',
572 'darkturquoise' => '#00CED1',
573 'darkviolet' => '#9400D3',
574 'deeppink' => '#FF1493',
575 'deepskyblue' => '#00BFFF',
576 'dimgray' => '#696969',
577 'dimgrey' => '#696969',
578 'dodgerblue' => '#1E90FF',
579 'firebrick' => '#B22222',
580 'floralwhite' => '#FFFAF0',
581 'forestgreen' => '#228B22',
582 'fuchsia' => '#FF00FF',
583 'gainsboro' => '#DCDCDC',
584 'ghostwhite' => '#F8F8FF',
585 'gold' => '#FFD700',
586 'goldenrod' => '#DAA520',
587 'gray' => '#808080',
588 'grey' => '#808080',
589 'green' => '#008000',
590 'greenyellow' => '#ADFF2F',
591 'honeydew' => '#F0FFF0',
592 'hotpink' => '#FF69B4',
593 'indianred ' => '#CD5C5C',
594 'indigo ' => '#4B0082',
595 'ivory' => '#FFFFF0',
596 'khaki' => '#F0E68C',
597 'lavender' => '#E6E6FA',
598 'lavenderblush' => '#FFF0F5',
599 'lawngreen' => '#7CFC00',
600 'lemonchiffon' => '#FFFACD',
601 'lightblue' => '#ADD8E6',
602 'lightcoral' => '#F08080',
603 'lightcyan' => '#E0FFFF',
604 'lightgoldenrodyellow' => '#FAFAD2',
605 'lightgray' => '#D3D3D3',
606 'lightgrey' => '#D3D3D3',
607 'lightgreen' => '#90EE90',
608 'lightpink' => '#FFB6C1',
609 'lightsalmon' => '#FFA07A',
610 'lightseagreen' => '#20B2AA',
611 'lightskyblue' => '#87CEFA',
612 'lightslategray' => '#778899',
613 'lightslategrey' => '#778899',
614 'lightsteelblue' => '#B0C4DE',
615 'lightyellow' => '#FFFFE0',
616 'lime' => '#00FF00',
617 'limegreen' => '#32CD32',
618 'linen' => '#FAF0E6',
619 'magenta' => '#FF00FF',
620 'maroon' => '#800000',
621 'mediumaquamarine' => '#66CDAA',
622 'mediumblue' => '#0000CD',
623 'mediumorchid' => '#BA55D3',
624 'mediumpurple' => '#9370D8',
625 'mediumseagreen' => '#3CB371',
626 'mediumslateblue' => '#7B68EE',
627 'mediumspringgreen' => '#00FA9A',
628 'mediumturquoise' => '#48D1CC',
629 'mediumvioletred' => '#C71585',
630 'midnightblue' => '#191970',
631 'mintcream' => '#F5FFFA',
632 'mistyrose' => '#FFE4E1',
633 'moccasin' => '#FFE4B5',
634 'navajowhite' => '#FFDEAD',
635 'navy' => '#000080',
636 'oldlace' => '#FDF5E6',
637 'olive' => '#808000',
638 'olivedrab' => '#6B8E23',
639 'orange' => '#FFA500',
640 'orangered' => '#FF4500',
641 'orchid' => '#DA70D6',
642 'palegoldenrod' => '#EEE8AA',
643 'palegreen' => '#98FB98',
644 'paleturquoise' => '#AFEEEE',
645 'palevioletred' => '#D87093',
646 'papayawhip' => '#FFEFD5',
647 'peachpuff' => '#FFDAB9',
648 'peru' => '#CD853F',
649 'pink' => '#FFC0CB',
650 'plum' => '#DDA0DD',
651 'powderblue' => '#B0E0E6',
652 'purple' => '#800080',
653 'red' => '#FF0000',
654 'rosybrown' => '#BC8F8F',
655 'royalblue' => '#4169E1',
656 'saddlebrown' => '#8B4513',
657 'salmon' => '#FA8072',
658 'sandybrown' => '#F4A460',
659 'seagreen' => '#2E8B57',
660 'seashell' => '#FFF5EE',
661 'sienna' => '#A0522D',
662 'silver' => '#C0C0C0',
663 'skyblue' => '#87CEEB',
664 'slateblue' => '#6A5ACD',
665 'slategray' => '#708090',
666 'slategrey' => '#708090',
667 'snow' => '#FFFAFA',
668 'springgreen' => '#00FF7F',
669 'steelblue' => '#4682B4',
670 'tan' => '#D2B48C',
671 'teal' => '#008080',
672 'thistle' => '#D8BFD8',
673 'tomato' => '#FF6347',
674 'turquoise' => '#40E0D0',
675 'violet' => '#EE82EE',
676 'wheat' => '#F5DEB3',
677 'white' => '#FFFFFF',
678 'whitesmoke' => '#F5F5F5',
679 'yellow' => '#FFFF00',
680 'yellowgreen' => '#9ACD32'
681 );
682}
683
1d1d807e
SH
684
685/**
686 * Used to prepare CSS strings
687 *
688 * @package moodlecore
689 * @copyright 2011 Sam Hemelryk
690 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
691 */
692abstract class css_writer {
693 /**
694 * The current indent level
695 * @var int
696 */
697 protected static $indent = 0;
698
699 /**
700 * Returns true if the output should still maintain minimum formatting.
701 * @return bool
702 */
703 protected static function is_pretty() {
704 global $CFG;
705 return (!empty($CFG->cssoptimisepretty));
706 }
707
708 /**
709 * Returns the indenting char to use for indenting things nicely.
710 * @return string
711 */
712 protected static function get_indent() {
713 if (self::is_pretty()) {
714 return str_repeat(" ", self::$indent);
715 }
716 return '';
717 }
718
719 /**
720 * Increases the current indent
721 */
722 protected static function increase_indent() {
723 self::$indent++;
724 }
725
726 /**
727 * Descreases the current indent
728 */
729 protected static function decrease_indent() {
730 self::$indent--;
731 }
732
733 /**
734 * Returns the string to use as a separator
735 * @return string
736 */
737 protected static function get_separator() {
738 return (self::is_pretty())?"\n":' ';
739 }
740
741 /**
742 * Returns CSS for media
743 *
744 * @param string $typestring
745 * @param array $rules An array of css_rule objects
746 * @return string
747 */
748 public static function media($typestring, array &$rules) {
749 $nl = self::get_separator();
750
751 $output = '';
752 if ($typestring !== 'all') {
753 $output .= $nl.$nl."@media {$typestring} {".$nl;
754 self::increase_indent();
755 }
756 foreach ($rules as $rule) {
757 $output .= $rule->out().$nl;
758 }
759 if ($typestring !== 'all') {
760 self::decrease_indent();
761 $output .= '}';
762 }
763 return $output;
764 }
765
766 /**
767 * Returns CSS for a rule
768 *
769 * @param string $selector
770 * @param string $styles
771 * @return string
772 */
773 public static function rule($selector, $styles) {
774 $css = self::get_indent()."{$selector}{{$styles}}";
775 return $css;
776 }
777
778 /**
779 * Returns CSS for the selectors of a rule
780 *
781 * @param array $selectors Array of css_selector objects
782 * @return string
783 */
784 public static function selectors(array $selectors) {
785 $nl = self::get_separator();
786 $selectorstrings = array();
787 foreach ($selectors as $selector) {
788 $selectorstrings[] = $selector->out();
789 }
790 return join(','.$nl, $selectorstrings);
791 }
792
793 /**
794 * Returns a selector given the components that make it up.
795 *
796 * @param array $components
797 * @return string
798 */
799 public static function selector(array $components) {
800 return trim(join(' ', $components));
801 }
802
803 /**
804 *
805 * @param array $styles Array of css_style objects
806 * @return type
807 */
808 public static function styles(array $styles) {
809 $bits = array();
810 foreach ($styles as $style) {
811 $bits[] = $style->out();
812 }
813 return join('', $bits);
814 }
815
816 /**
817 * Returns a style CSS
818 *
819 * @param string $name
820 * @param string $value
821 * @param bool $important
822 * @return string
823 */
824 public static function style($name, $value, $important = false) {
825 if ($important && strpos($value, '!important') === false) {
826 $value .= ' !important';
827 }
828 return "{$name}:{$value};";
829 }
830}
831
0e641c74
SH
832/**
833 * A structure to represent a CSS selector.
834 *
835 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
836 * rule.
837 *
838 * @package moodlecore
839 * @copyright 2011 Sam Hemelryk
840 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
841 */
842class css_selector {
843
844 /**
845 * An array of selector bits
846 * @var array
847 */
848 protected $selectors = array();
849
850 /**
851 * The number of selectors.
852 * @var int
853 */
854 protected $count = 0;
855
856 /**
857 * Initialises a new CSS selector
1d1d807e 858 * @return css_selector
0e641c74
SH
859 */
860 public static function init() {
861 return new css_selector();
862 }
863
864 /**
865 * CSS selectors can only be created through the init method above.
866 */
867 protected function __construct() {}
868
869 /**
870 * Adds a selector to the end of the current selector
871 * @param string $selector
872 */
873 public function add($selector) {
874 $selector = trim($selector);
875 $count = 0;
876 $count += preg_match_all('/(\.|#)/', $selector, $matchesarray);
877 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
878 $count ++;
879 }
880 $this->count = $count;
881 $this->selectors[] = $selector;
882 }
883 /**
884 * Returns the number of individual components that make up this selector
885 * @return int
886 */
887 public function get_selector_count() {
888 return $this->count;
889 }
890
891 /**
892 * Returns the selector for use in a CSS rule
893 * @return string
894 */
895 public function out() {
1d1d807e 896 return css_writer::selector($this->selectors);
0e641c74
SH
897 }
898}
899
900/**
901 * A structure to represent a CSS rule.
902 *
903 * @package moodlecore
904 * @copyright 2011 Sam Hemelryk
905 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
906 */
907class css_rule {
908
909 /**
910 * An array of CSS selectors {@see css_selector}
911 * @var array
912 */
913 protected $selectors = array();
914
915 /**
916 * An array of CSS styles {@see css_style}
917 * @var array
918 */
919 protected $styles = array();
920
921 /**
922 * Created a new CSS rule. This is the only way to create a new CSS rule externally.
923 * @return css_rule
924 */
925 public static function init() {
926 return new css_rule();
927 }
928
929 /**
930 * Constructs a new css rule - this can only be called from within the scope of
931 * this class or its descendants.
932 *
933 * @param type $selector
934 * @param array $styles
935 */
936 protected function __construct($selector = null, array $styles = array()) {
937 if ($selector != null) {
938 if (is_array($selector)) {
939 $this->selectors = $selector;
940 } else {
941 $this->selectors = array($selector);
942 }
943 $this->add_styles($styles);
944 }
945 }
946
947 /**
948 * Adds a new CSS selector to this rule
949 *
950 * @param css_selector $selector
951 */
952 public function add_selector(css_selector $selector) {
953 $this->selectors[] = $selector;
954 }
955
956 /**
957 * Adds a new CSS style to this rule.
958 *
959 * @param css_style|string $style
960 */
961 public function add_style($style) {
962 if (is_string($style)) {
963 $style = trim($style);
964 if (empty($style)) {
965 return;
966 }
967 $bits = explode(':', $style, 2);
968 if (count($bits) == 2) {
969 list($name, $value) = array_map('trim', $bits);
970 }
971 if (isset($name) && isset($value) && $name !== '' && $value !== '') {
972 $style = css_style::init($name, $value);
973 }
1d1d807e
SH
974 } else if ($style instanceof css_style) {
975 $style = clone($style);
0e641c74
SH
976 }
977 if ($style instanceof css_style) {
978 $name = $style->get_name();
979 if (array_key_exists($name, $this->styles)) {
980 $this->styles[$name]->set_value($style->get_value());
981 } else {
982 $this->styles[$name] = $style;
983 }
1d1d807e
SH
984 } else if (is_array($style)) {
985 foreach ($style as $astyle) {
986 $this->add_style($astyle);
987 }
0e641c74
SH
988 }
989 }
990
991 /**
992 * An easy method of adding several styles at once. Just calls add_style.
993 *
994 * @param array $styles
995 */
996 public function add_styles(array $styles) {
997 foreach ($styles as $style) {
998 $this->add_style($style);
999 }
1000 }
1001
0e641c74
SH
1002 /**
1003 * Returns the array of selectors
1004 * @return array
1005 */
1006 public function get_selectors() {
1007 return $this->selectors;
1008 }
1009
1010 /**
1011 * Returns the array of styles
1012 * @return array
1013 */
1014 public function get_styles() {
1015 return $this->styles;
1016 }
1017
1018 /**
1019 * Outputs this rule as a fragment of CSS
1020 * @return string
1021 */
1022 public function out() {
1d1d807e
SH
1023 $selectors = css_writer::selectors($this->selectors);
1024 $styles = css_writer::styles($this->get_consolidated_styles());
1025 return css_writer::rule($selectors, $styles);
1026 }
1027
1028 public function get_consolidated_styles() {
1029 $finalstyles = array();
1030 $consolidate = array();
1031 foreach ($this->styles as $style) {
1032 $consolidatetoclass = $style->consolidate_to();
50836f26 1033 if ($style->is_valid() && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
1d1d807e
SH
1034 $class = 'css_style_'.$consolidatetoclass;
1035 if (!array_key_exists($class, $consolidate)) {
1036 $consolidate[$class] = array();
1037 }
1038 $consolidate[$class][] = $style;
1039 } else {
1040 $finalstyles[] = $style;
1041 }
1042 }
1043
1044 foreach ($consolidate as $class => $styles) {
1045 $styles = $class::consolidate($styles);
1046 foreach ($styles as $style) {
1047 $finalstyles[] = $style;
1048 }
1049 }
1050 return $finalstyles;
0e641c74
SH
1051 }
1052
1053 /**
1054 * Splits this rules into an array of CSS rules. One for each of the selectors
1055 * that make up this rule.
1056 *
1057 * @return array(css_rule)
1058 */
1059 public function split_by_selector() {
1060 $return = array();
1061 foreach ($this->selectors as $selector) {
1062 $return[] = new css_rule($selector, $this->styles);
1063 }
1064 return $return;
1065 }
1066
1067 /**
1068 * Splits this rule into an array of rules. One for each of the styles that
1069 * make up this rule
1070 *
1071 * @return array(css_rule)
1072 */
1073 public function split_by_style() {
1074 $return = array();
1075 foreach ($this->styles as $style) {
1076 $return[] = new css_rule($this->selectors, array($style));
1077 }
1078 return $return;
1079 }
1080
1081 /**
1082 * Gets a hash for the styles of this rule
1083 * @return string
1084 */
1085 public function get_style_hash() {
1d1d807e 1086 return md5(css_writer::styles($this->styles));
0e641c74
SH
1087 }
1088
1089 /**
1090 * Gets a hash for the selectors of this rule
1091 * @return string
1092 */
1093 public function get_selector_hash() {
1d1d807e 1094 return md5(css_writer::selectors($this->selectors));
0e641c74
SH
1095 }
1096
1097 /**
1098 * Gets the number of selectors that make up this rule.
1099 * @return int
1100 */
1101 public function get_selector_count() {
1102 $count = 0;
1103 foreach ($this->selectors as $selector) {
1104 $count += $selector->get_selector_count();
1105 }
1106 return $count;
1107 }
1108}
1109
1110/**
1111 * A media class to organise rules by the media they apply to.
1112 *
1113 * @package moodlecore
1114 * @copyright 2011 Sam Hemelryk
1115 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1116 */
1117class css_media {
1118
1119 /**
1120 * An array of the different media types this instance applies to.
1121 * @var array
1122 */
1123 protected $types = array();
1124
1125 /**
1126 * An array of rules within this media instance
1127 * @var array
1128 */
1129 protected $rules = array();
1130
1131 /**
1132 * Initalises a new media instance
1133 *
1134 * @param type $for
1135 */
1136 public function __construct($for = 'all') {
1137 $types = explode(',', $for);
1138 $this->types = array_map('trim', $types);
1139 }
1140
1141 /**
1142 * Adds a new CSS rule to this media instance
1143 *
1144 * @param css_rule $newrule
1145 */
1146 public function add_rule(css_rule $newrule) {
1147 foreach ($newrule->split_by_selector() as $rule) {
1148 $hash = $rule->get_selector_hash();
1149 if (!array_key_exists($hash, $this->rules)) {
1150 $this->rules[$hash] = $rule;
1151 } else {
1152 $this->rules[$hash]->add_styles($rule->get_styles());
1153 }
1154 }
1155 }
1156
1157 /**
1158 * Returns the rules used by this
1159 *
1160 * @return array
1161 */
1162 public function get_rules() {
1163 return $this->rules;
1164 }
1165
1166 /**
1167 * Organises rules by gropuing selectors based upon the styles and consolidating
1168 * those selectors into single rules.
1169 *
1170 * @return array An array of optimised styles
1171 */
1172 public function organise_rules_by_selectors() {
1173 $optimised = array();
1174 $beforecount = count($this->rules);
1175 foreach ($this->rules as $rule) {
1176 $hash = $rule->get_style_hash();
1177 if (!array_key_exists($hash, $optimised)) {
1178 $optimised[$hash] = clone($rule);
1179 } else {
1180 foreach ($rule->get_selectors() as $selector) {
1181 $optimised[$hash]->add_selector($selector);
1182 }
1183 }
1184 }
1185 $this->rules = $optimised;
1186 $aftercount = count($this->rules);
1187 return ($beforecount < $aftercount);
1188 }
1189
1190 /**
1191 * Returns the total number of rules that exist within this media set
1192 *
1193 * @return int
1194 */
1195 public function count_rules() {
1196 return count($this->rules);
1197 }
1198
1199 /**
1200 * Returns the total number of selectors that exist within this media set
1201 *
1202 * @return int
1203 */
1204 public function count_selectors() {
1205 $count = 0;
1206 foreach ($this->rules as $rule) {
1207 $count += $rule->get_selector_count();
1208 }
1209 return $count;
1210 }
1211
1212 /**
1213 * Returns the CSS for this media and all of its rules.
1214 *
1215 * @return string
1216 */
1217 public function out() {
1d1d807e 1218 return css_writer::media(join(',', $this->types), $this->rules);
0e641c74
SH
1219 }
1220
1221 /**
1222 * Returns an array of media that this media instance applies to
1223 *
1224 * @return array
1225 */
1226 public function get_types() {
1227 return $this->types;
1228 }
1229}
1230
1231/**
1232 * An absract class to represent CSS styles
1233 *
1234 * @package moodlecore
1235 * @copyright 2011 Sam Hemelryk
1236 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1237 */
1238abstract class css_style {
1239
1240 /**
1241 * The name of the style
1242 * @var string
1243 */
1244 protected $name;
1245
1246 /**
1247 * The value for the style
1248 * @var mixed
1249 */
1250 protected $value;
1251
1252 /**
1253 * If set to true this style was defined with the !important rule.
1254 * @var bool
1255 */
1256 protected $important = false;
1257
1258 /**
1259 * Initialises a new style.
1260 *
1261 * This is the only public way to create a style to ensure they that appropriate
1262 * style class is used if it exists.
1263 *
1264 * @param type $name
1265 * @param type $value
1d1d807e 1266 * @return css_style_generic
0e641c74
SH
1267 */
1268 public static function init($name, $value) {
1269 $specificclass = 'css_style_'.preg_replace('#[^a-zA-Z0-9]+#', '', $name);
1270 if (class_exists($specificclass)) {
1271 return $specificclass::init($value);
1272 }
1273 return new css_style_generic($name, $value);
1274 }
1275
1276 /**
1277 * Creates a new style when given its name and value
1278 *
1279 * @param string $name
1280 * @param string $value
1281 */
1282 protected function __construct($name, $value) {
1283 $this->name = $name;
1284 $this->set_value($value);
1285 }
1286
1287 /**
1288 * Sets the value for the style
1289 *
1290 * @param string $value
1291 */
1292 final public function set_value($value) {
1293 $value = trim($value);
1294 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
1295 if ($important) {
1296 $value = substr($value, 0, -(strlen($matches[1])));
1297 }
1298 if (!$this->important || $important) {
1299 $this->value = $this->clean_value($value);
1300 $this->important = $important;
1301 }
1302 }
1303
50836f26
SH
1304 public function is_valid() {
1305 return true;
1306 }
1307
0e641c74
SH
1308 /**
1309 * Returns the name for the style
1310 *
1311 * @return string
1312 */
1313 public function get_name() {
1314 return $this->name;
1315 }
1316
1317 /**
1318 * Returns the value for the style
1319 *
1320 * @return string
1321 */
1322 public function get_value() {
1323 $value = $this->value;
1324 if ($this->important) {
1325 $value .= ' !important';
1326 }
1327 return $value;
1328 }
1329
1330 /**
1331 * Returns the style ready for use in CSS
1332 *
1333 * @param string|null $value
1334 * @return string
1335 */
1336 public function out($value = null) {
1d1d807e 1337 if (is_null($value)) {
0e641c74 1338 $value = $this->get_value();
0e641c74 1339 }
1d1d807e 1340 return css_writer::style($this->name, $value, $this->important);
0e641c74
SH
1341 }
1342
1343 /**
1344 * This can be overridden by a specific style allowing it to clean its values
1345 * consistently.
1346 *
1347 * @param mixed $value
1348 * @return mixed
1349 */
1350 protected function clean_value($value) {
1351 return $value;
1352 }
1d1d807e
SH
1353
1354 public function consolidate_to() {
1355 return null;
1356 }
0e641c74
SH
1357}
1358
1359/**
1360 * A generic CSS style class to use when a more specific class does not exist.
1361 *
1362 * @package moodlecore
1363 * @copyright 2011 Sam Hemelryk
1364 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1365 */
1366class css_style_generic extends css_style {
1367 /**
1368 * Cleans incoming values for typical things that can be optimised.
1369 *
1370 * @param mixed $value
1371 * @return string
1372 */
1373 protected function clean_value($value) {
1374 if (trim($value) == '0px') {
1375 $value = 0;
1376 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) {
1377 $value = '#'.strtoupper($matches[1]);
1378 }
1379 return $value;
1380 }
1381}
1382
1383/**
1384 * A colour CSS style
1385 *
1386 * @package moodlecore
1387 * @copyright 2011 Sam Hemelryk
1388 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1389 */
1390class css_style_color extends css_style {
1391 /**
1392 * Creates a new colour style
1393 *
1394 * @param mixed $value
1395 * @return css_style_color
1396 */
1397 public static function init($value) {
1398 return new css_style_color('color', $value);
1399 }
1400
1401 /**
1402 * Cleans the colour unifing it to a 6 char hash colour if possible
1403 * Doing this allows us to associate identical colours being specified in
1404 * different ways. e.g. Red, red, #F00, and #F00000
1405 *
1406 * @param mixed $value
1407 * @return string
1408 */
1409 protected function clean_value($value) {
1410 $value = trim($value);
1411 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) {
1412 $value = '#'.strtoupper($matches[1]);
1413 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) {
1414 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
1415 $value = '#'.strtoupper($value);
1416 } else if (array_key_exists(strtolower($value), css_optimiser::$htmlcolours)) {
1417 $value = css_optimiser::$htmlcolours[strtolower($value)];
1418 }
1419 return $value;
1420 }
1421
1422 /**
1423 * Returns the colour style for use within CSS.
1424 * Will return an optimised hash colour.
1425 *
1426 * e.g #123456
1427 * #123 instead of #112233
1428 * #F00 instead of red
1429 *
1430 * @param string $overridevalue
1431 * @return string
1432 */
1433 public function out($overridevalue = null) {
50836f26
SH
1434 if ($overridevalue === null) {
1435 $overridevalue = $this->value;
1436 }
1437 return parent::out(self::shrink_value($overridevalue));
1438 }
1439
1440 public static function shrink_value($value) {
1441 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) {
1442 return '#'.$matches[1].$matches[2].$matches[3];
0e641c74 1443 }
50836f26
SH
1444 return $value;
1445 }
1446
1447 public function is_valid() {
1448 return css_is_colour($this->value);
0e641c74
SH
1449 }
1450}
1451
1d1d807e
SH
1452class css_style_margin extends css_style {
1453 public static function init($value) {
1454 $value = preg_replace('#\s+#', ' ', $value);
1455 $bits = explode(' ', $value, 4);
1456
1457 $top = $right = $bottom = $left = null;
1458 if (count($bits) > 0) {
1459 $top = $right = $bottom = $left = array_shift($bits);
1460 }
1461 if (count($bits) > 0) {
1462 $right = $left = array_shift($bits);
1463 }
1464 if (count($bits) > 0) {
1465 $bottom = array_shift($bits);
1466 }
1467 if (count($bits) > 0) {
1468 $left = array_shift($bits);
1469 }
1470 return array(
1471 new css_style_margintop('margin-top', $top),
1472 new css_style_marginright('margin-right', $right),
1473 new css_style_marginbottom('margin-bottom', $bottom),
1474 new css_style_marginleft('margin-left', $left)
1475 );
1476 }
1477 public static function consolidate(array $styles) {
1478 if (count($styles) != 4) {
1479 return $styles;
1480 }
1481 $top = $right = $bottom = $left = null;
1482 foreach ($styles as $style) {
1483 switch ($style->get_name()) {
1484 case 'margin-top' : $top = $style->get_value();break;
1485 case 'margin-right' : $right = $style->get_value();break;
1486 case 'margin-bottom' : $bottom = $style->get_value();break;
1487 case 'margin-left' : $left = $style->get_value();break;
1488 }
1489 }
1490 if ($top == $bottom && $left == $right) {
1491 if ($top == $left) {
1492 return array(new css_style_margin('margin', $top));
1493 } else {
1494 return array(new css_style_margin('margin', "{$top} {$left}"));
1495 }
1496 } else if ($left == $right) {
1497 return array(new css_style_margin('margin', "{$top} {$right} {$bottom}"));
1498 } else {
1499 return array(new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}"));
1500 }
1501
1502 }
1503}
1504
1505class css_style_margintop extends css_style {
1506 public static function init($value) {
1507 return new css_style_margintop('margin-top', $value);
1508 }
1509 public function consolidate_to() {
1510 return 'margin';
1511 }
1512}
1513
1514class css_style_marginright extends css_style {
1515 public static function init($value) {
1516 return new css_style_marginright('margin-right', $value);
1517 }
1518 public function consolidate_to() {
1519 return 'margin';
1520 }
1521}
1522
1523class css_style_marginbottom extends css_style {
1524 public static function init($value) {
1525 return new css_style_marginbottom('margin-bottom', $value);
1526 }
1527 public function consolidate_to() {
1528 return 'margin';
1529 }
1530}
1531
1532class css_style_marginleft extends css_style {
1533 public static function init($value) {
1534 return new css_style_marginleft('margin-left', $value);
1535 }
1536 public function consolidate_to() {
1537 return 'margin';
1538 }
1539}
1540
1541/**
1542 * A border style
1543 *
1544 * @package moodlecore
1545 * @copyright 2011 Sam Hemelryk
1546 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1547 */
1548class css_style_border extends css_style {
1549 public static function init($value) {
1550 $value = preg_replace('#\s+#', ' ', $value);
1551 $bits = explode(' ', $value, 3);
1552
1553 $return = array();
1554 if (count($bits) > 0) {
1555 $width = array_shift($bits);
1556 $return[] = new css_style_borderwidth('border-width-top', $width);
1557 $return[] = new css_style_borderwidth('border-width-right', $width);
1558 $return[] = new css_style_borderwidth('border-width-bottom', $width);
1559 $return[] = new css_style_borderwidth('border-width-left', $width);
1560 }
1561 if (count($bits) > 0) {
1562 $style = array_shift($bits);
1563 $return[] = new css_style_borderstyle('border-style-top', $style);
1564 $return[] = new css_style_borderstyle('border-style-right', $style);
1565 $return[] = new css_style_borderstyle('border-style-bottom', $style);
1566 $return[] = new css_style_borderstyle('border-style-left', $style);
1567 }
1568 if (count($bits) > 0) {
1569 $colour = array_shift($bits);
1570 $return[] = new css_style_bordercolor('border-color-top', $colour);
1571 $return[] = new css_style_bordercolor('border-color-right', $colour);
1572 $return[] = new css_style_bordercolor('border-color-bottom', $colour);
1573 $return[] = new css_style_bordercolor('border-color-left', $colour);
1574 }
1575 return $return;
1576 }
1577 public static function consolidate(array $styles) {
1578
1579 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
1580 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
1581 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
1582
1583 foreach ($styles as $style) {
1584 switch ($style->get_name()) {
1585 case 'border-width-top': $borderwidths['top'] = $style->get_value(); break;
1586 case 'border-width-right': $borderwidths['right'] = $style->get_value(); break;
1587 case 'border-width-bottom': $borderwidths['bottom'] = $style->get_value(); break;
1588 case 'border-width-left': $borderwidths['left'] = $style->get_value(); break;
1589
1590 case 'border-style-top': $borderstyles['top'] = $style->get_value(); break;
1591 case 'border-style-right': $borderstyles['right'] = $style->get_value(); break;
1592 case 'border-style-bottom': $borderstyles['bottom'] = $style->get_value(); break;
1593 case 'border-style-left': $borderstyles['left'] = $style->get_value(); break;
1594
1595 case 'border-color-top': $bordercolors['top'] = $style->get_value(); break;
1596 case 'border-color-right': $bordercolors['right'] = $style->get_value(); break;
1597 case 'border-color-bottom': $bordercolors['bottom'] = $style->get_value(); break;
1598 case 'border-color-left': $bordercolors['left'] = $style->get_value(); break;
1599 }
1600 }
1601
1602 $uniquewidths = count(array_unique($borderwidths));
1603 $uniquestyles = count(array_unique($borderstyles));
1604 $uniquecolors = count(array_unique($bordercolors));
1605
1606 $nullwidths = in_array(null, $borderwidths);
1607 $nullstyles = in_array(null, $borderstyles);
1608 $nullcolors = in_array(null, $bordercolors);
1609
50836f26
SH
1610 $allwidthsthesame = ($uniquewidths === 1)?1:0;
1611 $allstylesthesame = ($uniquestyles === 1)?1:0;
1612 $allcolorsthesame = ($uniquecolors === 1)?1:0;
1d1d807e
SH
1613
1614 $allwidthsnull = $allwidthsthesame && $nullwidths;
1615 $allstylesnull = $allstylesthesame && $nullstyles;
1616 $allcolorsnull = $allcolorsthesame && $nullcolors;
1617
1618 $return = array();
1619 if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
1620 // Everything is null still... boo
1621 return array(new css_style_border('border', ''));
1622
1623 } else if ($allwidthsnull && $allstylesnull) {
1624
1625 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
1626 return $return;
1627
1628 } else if ($allwidthsnull && $allcolorsnull) {
1629
1630 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
1631 return $return;
1632
1633 } else if ($allcolorsnull && $allstylesnull) {
1634
1635 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
1636 return $return;
1637
1638 }
1639
1640 if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 3) {
1641
1642 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']);
1643
1644 } else if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 2) {
1645
1646 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) {
1647
1648 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']);
1649 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
1650
1651 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) {
1652
1653 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']);
1654 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
1655
1656 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) {
1657
1658 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']);
1659 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
1660
1661 } else {
1662 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
1663 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
1664 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
1665 }
1666
50836f26 1667 } else if (!$nullwidths && !$nullcolors && !$nullstyles && max(array_count_values($borderwidths)) == 3 && max(array_count_values($borderstyles)) == 3 && max(array_count_values($bordercolors)) == 3) {
1d1d807e
SH
1668 $widthkeys = array();
1669 $stylekeys = array();
1670 $colorkeys = array();
1671
1672 foreach ($borderwidths as $key => $value) {
1673 if (!array_key_exists($value, $widthkeys)) {
1674 $widthkeys[$value] = array();
1675 }
1676 $widthkeys[$value][] = $key;
1677 }
1678 usort($widthkeys, 'css_sort_by_count');
1679 $widthkeys = array_values($widthkeys);
1680
1681 foreach ($borderstyles as $key => $value) {
1682 if (!array_key_exists($value, $stylekeys)) {
1683 $stylekeys[$value] = array();
1684 }
1685 $stylekeys[$value][] = $key;
1686 }
1687 usort($stylekeys, 'css_sort_by_count');
1688 $stylekeys = array_values($stylekeys);
1689
1690 foreach ($bordercolors as $key => $value) {
1691 if (!array_key_exists($value, $colorkeys)) {
1692 $colorkeys[$value] = array();
1693 }
1694 $colorkeys[$value][] = $key;
1695 }
1696 usort($colorkeys, 'css_sort_by_count');
1697 $colorkeys = array_values($colorkeys);
1698
1699 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
1700 $key = $widthkeys[0][0];
50836f26 1701 self::build_style_string($return, 'css_style_border', 'border', $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
1d1d807e 1702 $key = $widthkeys[1][0];
50836f26 1703 self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key, $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
1d1d807e
SH
1704 } else {
1705 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
1706 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
1707 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
1708 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
1709 }
1710 } else {
1711 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
1712 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
1713 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
1714 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
1715 }
1716 foreach ($return as $key => $style) {
1717 if ($style->get_value() == '') {
1718 unset($return[$key]);
1719 }
1720 }
1721 return $return;
1722 }
1723 public function consolidate_to() {
1724 return 'border';
1725 }
1726 public static function consolidate_styles_by_direction(&$array, $class, $style, $top, $right = null, $bottom = null, $left = null) {
1d1d807e
SH
1727 if (is_array($top)) {
1728 $right = $top['right'];
1729 $bottom = $top['bottom'];
1730 $left = $top['left'];
1731 $top = $top['top'];
1732 }
1733
1734 if ($top == $bottom && $left == $right && $top == $left) {
1735 if ($top == null) {
1736 $array[] = new $class($style, '');
1737 } else {
1738 $array[] = new $class($style, $top);
1739 }
1740 } else if ($top == null || $right == null || $bottom == null || $left == null) {
1741 if ($top !== null) {
1742 $array[] = new $class($style.'-top', $top);
1743 }
1744 if ($right !== null) {
1745 $array[] = new $class($style.'-right', $right);
1746 }
1747 if ($bottom !== null) {
1748 $array[] = new $class($style.'-bottom', $bottom);
1749 }
1750 if ($left !== null) {
1751 $array[] = new $class($style.'-left', $left);
1752 }
1753 } else if ($top == $bottom && $left == $right) {
1754 $array[] = new $class($style, $top.' '.$right);
1755 } else if ($left == $right) {
1756 $array[] = new $class($style, $top.' '.$right.' '.$bottom);
1757 } else {
1758 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left);
1759 }
1760 return true;
1761 }
1762 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) {
1763 if (!is_null($width) && !is_null($style) && !is_null($color)) {
1764 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color);
1765 } else if (!is_null($width) && !is_null($style) && is_null($color)) {
1766 $array[] = new $class($cssstyle, $width.' '.$style);
1767 } else if (!is_null($width) && is_null($style) && is_null($color)) {
1768 $array[] = new $class($cssstyle.'-width', $width);
1769 } else {
1770 if (!is_null($width)) $array[] = new $class($cssstyle.'-width', $width);
1771 if (!is_null($style)) $array[] = new $class($cssstyle.'-style', $style);
1772 if (!is_null($color)) $array[] = new $class($cssstyle.'-color', $color);
1773 }
1774 return true;
1775 }
1776}
1777
1778function css_sort_by_count(array $a, array $b) {
1779 $a = count($a);
1780 $b = count($b);
1781 if ($a == $b) {
1782 return 0;
1783 }
1784 return ($a > $b) ? -1 : 1;
1785}
1786
1787/**
1788 * A border colour style
1789 *
1790 * @package moodlecore
1791 * @copyright 2011 Sam Hemelryk
1792 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1793 */
1794class css_style_bordercolor extends css_style_color {
1795 /**
1796 * Creates a new border colour style
1797 *
1798 * Based upon the colour style
1799 *
1800 * @param mixed $value
1801 * @return css_style_bordercolor
1802 */
1803 public static function init($value) {
1804 $value = preg_replace('#\s+#', ' ', $value);
1805 $bits = explode(' ', $value, 4);
1806
1807 $top = $right = $bottom = $left = null;
1808 if (count($bits) > 0) {
1809 $top = $right = $bottom = $left = array_shift($bits);
1810 }
1811 if (count($bits) > 0) {
1812 $right = $left = array_shift($bits);
1813 }
1814 if (count($bits) > 0) {
1815 $bottom = array_shift($bits);
1816 }
1817 if (count($bits) > 0) {
1818 $left = array_shift($bits);
1819 }
1820 return array(
50836f26
SH
1821 css_style_bordercolortop::init($top),
1822 css_style_bordercolorright::init($right),
1823 css_style_bordercolorbottom::init($bottom),
1824 css_style_bordercolorleft::init($left)
1d1d807e
SH
1825 );
1826 }
1827 public function consolidate_to() {
1828 return 'border';
1829 }
1830 protected function clean_value($value) {
1831 $values = explode(' ', $value);
1832 $values = array_map('parent::clean_value', $values);
1833 return join (' ', $values);
1834 }
50836f26
SH
1835 public function out($overridevalue = null) {
1836 if ($overridevalue === null) {
1837 $overridevalue = $this->value;
1838 }
1839 $values = explode(' ', $overridevalue);
1840 $values = array_map('css_style_color::shrink_value', $values);
1841 return parent::out(join (' ', $values));
1842 }
1d1d807e
SH
1843}
1844
1845class css_style_borderleft extends css_style_generic {
1846 public static function init($value) {
1847 $value = preg_replace('#\s+#', ' ', $value);
1848 $bits = explode(' ', $value, 3);
1849
1850 $return = array();
1851 if (count($bits) > 0) {
50836f26 1852 $return[] = css_style_borderwidthleft::init(array_shift($bits));
1d1d807e
SH
1853 }
1854 if (count($bits) > 0) {
50836f26 1855 $return[] = css_style_borderstyleleft::init(array_shift($bits));
1d1d807e
SH
1856 }
1857 if (count($bits) > 0) {
50836f26 1858 $return[] = css_style_bordercolorleft::init(array_shift($bits));
1d1d807e
SH
1859 }
1860 return $return;
1861 }
1862 public function consolidate_to() {
1863 return 'border';
1864 }
1865}
1866
1867class css_style_borderright extends css_style_generic {
1868 public static function init($value) {
1869 $value = preg_replace('#\s+#', ' ', $value);
1870 $bits = explode(' ', $value, 3);
1871
1872 $return = array();
1873 if (count($bits) > 0) {
50836f26 1874 $return[] = css_style_borderwidthright::init(array_shift($bits));
1d1d807e
SH
1875 }
1876 if (count($bits) > 0) {
50836f26 1877 $return[] = css_style_borderstyleright::init(array_shift($bits));
1d1d807e
SH
1878 }
1879 if (count($bits) > 0) {
50836f26 1880 $return[] = css_style_bordercolorright::init(array_shift($bits));
1d1d807e
SH
1881 }
1882 return $return;
1883 }
1884 public function consolidate_to() {
1885 return 'border';
1886 }
1887}
1888
1889class css_style_bordertop extends css_style_generic {
1890 public static function init($value) {
1891 $value = preg_replace('#\s+#', ' ', $value);
1892 $bits = explode(' ', $value, 3);
1893
1894 $return = array();
1895 if (count($bits) > 0) {
50836f26 1896 $return[] = css_style_borderwidthtop::init(array_shift($bits));
1d1d807e
SH
1897 }
1898 if (count($bits) > 0) {
50836f26 1899 $return[] = css_style_borderstyletop::init(array_shift($bits));
1d1d807e
SH
1900 }
1901 if (count($bits) > 0) {
50836f26 1902 $return[] = css_style_bordercolortop::init(array_shift($bits));
1d1d807e
SH
1903 }
1904 return $return;
1905 }
1906 public function consolidate_to() {
1907 return 'border';
1908 }
1909}
1910
1911class css_style_borderbottom extends css_style_generic {
1912 public static function init($value) {
1913 $value = preg_replace('#\s+#', ' ', $value);
1914 $bits = explode(' ', $value, 3);
1915
1916 $return = array();
1917 if (count($bits) > 0) {
50836f26 1918 $return[] = css_style_borderwidthbottom::init(array_shift($bits));
1d1d807e
SH
1919 }
1920 if (count($bits) > 0) {
50836f26 1921 $return[] = css_style_borderstylebottom::init(array_shift($bits));
1d1d807e
SH
1922 }
1923 if (count($bits) > 0) {
50836f26 1924 $return[] = css_style_bordercolorbottom::init(array_shift($bits));
1d1d807e
SH
1925 }
1926 return $return;
1927 }
1928 public function consolidate_to() {
1929 return 'border';
1930 }
1931}
1932
1933/**
1934 * A border width style
1935 *
1936 * @package moodlecore
1937 * @copyright 2011 Sam Hemelryk
1938 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1939 */
1940class css_style_borderwidth extends css_style_generic {
1941 /**
1942 * Creates a new border colour style
1943 *
1944 * Based upon the colour style
1945 *
1946 * @param mixed $value
1947 * @return css_style_borderwidth
1948 */
1949 public static function init($value) {
1950 $value = preg_replace('#\s+#', ' ', $value);
1951 $bits = explode(' ', $value, 4);
1952
1953 $top = $right = $bottom = $left = null;
1954 if (count($bits) > 0) {
1955 $top = $right = $bottom = $left = array_shift($bits);
1956 }
1957 if (count($bits) > 0) {
1958 $right = $left = array_shift($bits);
1959 }
1960 if (count($bits) > 0) {
1961 $bottom = array_shift($bits);
1962 }
1963 if (count($bits) > 0) {
1964 $left = array_shift($bits);
1965 }
1966 return array(
50836f26
SH
1967 css_style_borderwidthtop::init($top),
1968 css_style_borderwidthright::init($right),
1969 css_style_borderwidthbottom::init($bottom),
1970 css_style_borderwidthleft::init($left)
1d1d807e
SH
1971 );
1972 }
1973 public function consolidate_to() {
1974 return 'border';
1975 }
1976}
1977
1978/**
1979 * A border style style
1980 *
1981 * @package moodlecore
1982 * @copyright 2011 Sam Hemelryk
1983 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1984 */
1985class css_style_borderstyle extends css_style_generic {
1986 /**
1987 * Creates a new border colour style
1988 *
1989 * Based upon the colour style
1990 *
1991 * @param mixed $value
1992 * @return css_style_borderstyle
1993 */
1994 public static function init($value) {
1995 $value = preg_replace('#\s+#', ' ', $value);
1996 $bits = explode(' ', $value, 4);
1997
1998 $top = $right = $bottom = $left = null;
1999 if (count($bits) > 0) {
2000 $top = $right = $bottom = $left = array_shift($bits);
2001 }
2002 if (count($bits) > 0) {
2003 $right = $left = array_shift($bits);
2004 }
2005 if (count($bits) > 0) {
2006 $bottom = array_shift($bits);
2007 }
2008 if (count($bits) > 0) {
2009 $left = array_shift($bits);
2010 }
2011 return array(
50836f26
SH
2012 css_style_borderstyletop::init($top),
2013 css_style_borderstyleright::init($right),
2014 css_style_borderstylebottom::init($bottom),
2015 css_style_borderstyleleft::init($left)
1d1d807e
SH
2016 );
2017 }
2018 public function consolidate_to() {
2019 return 'border';
2020 }
2021}
2022
50836f26
SH
2023class css_style_bordercolortop extends css_style_color {
2024 public static function init($value) {
2025 return new css_style_bordercolortop('border-color-top', $value);
2026 }
2027 public function consolidate_to() {
2028 return 'border';
2029 }
2030}
2031class css_style_bordercolorleft extends css_style_color {
2032 public static function init($value) {
2033 return new css_style_bordercolorleft('border-color-left', $value);
2034 }
2035 public function consolidate_to() {
2036 return 'border';
2037 }
2038}
2039class css_style_bordercolorright extends css_style_color {
2040 public static function init($value) {
2041 return new css_style_bordercolorright('border-color-right', $value);
2042 }
2043 public function consolidate_to() {
2044 return 'border';
2045 }
2046}
2047class css_style_bordercolorbottom extends css_style_color {
2048 public static function init($value) {
2049 return new css_style_bordercolorbottom('border-color-bottom', $value);
2050 }
2051 public function consolidate_to() {
2052 return 'border';
2053 }
2054}
2055
2056class css_style_borderwidthtop extends css_style_generic {
2057 public static function init($value) {
2058 return new css_style_borderwidthtop('border-width-top', $value);
2059 }
2060 public function consolidate_to() {
2061 return 'border';
2062 }
2063}
2064class css_style_borderwidthleft extends css_style_generic {
2065 public static function init($value) {
2066 return new css_style_borderwidthleft('border-width-left', $value);
2067 }
2068 public function consolidate_to() {
2069 return 'border';
2070 }
2071}
2072class css_style_borderwidthright extends css_style_generic {
2073 public static function init($value) {
2074 return new css_style_borderwidthright('border-width-right', $value);
2075 }
2076 public function consolidate_to() {
2077 return 'border';
2078 }
2079}
2080class css_style_borderwidthbottom extends css_style_generic {
2081 public static function init($value) {
2082 return new css_style_borderwidthbottom('border-width-bottom', $value);
2083 }
2084 public function consolidate_to() {
2085 return 'border';
2086 }
2087}
2088
2089
2090class css_style_borderstyletop extends css_style_generic {
2091 public static function init($value) {
2092 return new css_style_borderstyletop('border-style-top', $value);
2093 }
2094 public function consolidate_to() {
2095 return 'border';
2096 }
2097}
2098class css_style_borderstyleleft extends css_style_generic {
2099 public static function init($value) {
2100 return new css_style_borderstyleleft('border-style-left', $value);
2101 }
2102 public function consolidate_to() {
2103 return 'border';
2104 }
2105}
2106class css_style_borderstyleright extends css_style_generic {
2107 public static function init($value) {
2108 return new css_style_borderstyleright('border-style-right', $value);
2109 }
2110 public function consolidate_to() {
2111 return 'border';
2112 }
2113}
2114class css_style_borderstylebottom extends css_style_generic {
2115 public static function init($value) {
2116 return new css_style_borderstylebottom('border-style-bottom', $value);
2117 }
2118 public function consolidate_to() {
2119 return 'border';
2120 }
2121}
2122
2123
1d1d807e
SH
2124class css_style_background extends css_style {
2125 public static function init($value) {
2126 // colour - image - repeat - attachment - position
2127
2128 $imageurl = null;
2129 if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
2130 $imageurl = trim($matches[1]);
2131 $value = str_replace($matches[1], '', $value);
2132 }
2133
2134 $value = preg_replace('#\s+#', ' ', $value);
2135 $bits = explode(' ', $value);
2136
2137 $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit');
2138 $attachments = array('scroll' , 'fixed', 'inherit');
2139
2140 $return = array();
2141 if (count($bits) > 0 && css_is_colour(reset($bits))) {
2142 $return[] = new css_style_backgroundcolor('background-color', array_shift($bits));
2143 }
2144 if (count($bits) > 0 && preg_match('#(none|inherit|url\(\))#', reset($bits))) {
2145 $image = array_shift($bits);
2146 if ($image == 'url()') {
2147 $image = "url({$imageurl})";
2148 }
2149 $return[] = new css_style_backgroundimage('background-image', $image);
2150 }
2151 if (count($bits) > 0 && in_array(reset($bits), $repeats)) {
2152 $return[] = new css_style_backgroundrepeat('background-repeat', array_shift($bits));
2153 }
2154 if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
2155 // scroll , fixed, inherit
2156 $return[] = new css_style_backgroundattachment('background-attachment', array_shift($bits));
2157 }
2158 if (count($bits) > 0) {
2159 $return[] = new css_style_backgroundposition('background-position', join(' ',$bits));
2160 }
2161 return $return;
2162 }
2163 public static function consolidate(array $styles) {
2164
2165 if (count($styles) < 1) {
2166 return $styles;
2167 }
2168
2169 $color = $image = $repeat = $attachment = $position = null;
2170 foreach ($styles as $style) {
2171 switch ($style->get_name()) {
50836f26 2172 case 'background-color' : $color = css_style_color::shrink_value($style->get_value()); break;
1d1d807e
SH
2173 case 'background-image' : $image = $style->get_value(); break;
2174 case 'background-repeat' : $repeat = $style->get_value(); break;
2175 case 'background-attachment' : $attachment = $style->get_value(); break;
2176 case 'background-position' : $position = $style->get_value(); break;
2177 }
2178 }
50836f26
SH
2179
2180 if ((is_null($image) || is_null($position) || is_null($repeat)) && ($image!= null || $position != null || $repeat != null)) {
2181 return $styles;
2182 }
2183
1d1d807e
SH
2184 $value = array();
2185 if (!is_null($color)) $value[] .= $color;
2186 if (!is_null($image)) $value[] .= $image;
2187 if (!is_null($repeat)) $value[] .= $repeat;
2188 if (!is_null($attachment)) $value[] .= $attachment;
2189 if (!is_null($position)) $value[] .= $position;
2190 return array(new css_style_background('background', join(' ', $value)));
2191 }
2192}
2193
0e641c74
SH
2194/**
2195 * A background colour style.
2196 *
2197 * Based upon the colour style.
2198 *
2199 * @package moodlecore
2200 * @copyright 2011 Sam Hemelryk
2201 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2202 */
2203class css_style_backgroundcolor extends css_style_color {
2204 /**
2205 * Creates a new background colour style
2206 *
2207 * @param mixed $value
2208 * @return css_style_backgroundcolor
2209 */
2210 public static function init($value) {
2211 return new css_style_backgroundcolor('background-color', $value);
2212 }
1d1d807e
SH
2213 public function consolidate_to() {
2214 return 'background';
2215 }
0e641c74
SH
2216}
2217
2218/**
1d1d807e 2219 * A background image style.
0e641c74
SH
2220 *
2221 * @package moodlecore
2222 * @copyright 2011 Sam Hemelryk
2223 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2224 */
1d1d807e 2225class css_style_backgroundimage extends css_style_generic {
0e641c74 2226 /**
1d1d807e 2227 * Creates a new background colour style
0e641c74 2228 *
1d1d807e
SH
2229 * @param mixed $value
2230 * @return css_style_backgroundimage
2231 */
2232 public static function init($value) {
2233 return new css_style_backgroundimage('background-image', $value);
2234 }
2235 public function consolidate_to() {
2236 return 'background';
2237 }
2238}
2239
2240/**
2241 * A background repeat style.
2242 *
2243 * @package moodlecore
2244 * @copyright 2011 Sam Hemelryk
2245 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2246 */
2247class css_style_backgroundrepeat extends css_style_generic {
2248 /**
2249 * Creates a new background colour style
0e641c74
SH
2250 *
2251 * @param mixed $value
1d1d807e 2252 * @return css_style_backgroundrepeat
0e641c74
SH
2253 */
2254 public static function init($value) {
1d1d807e
SH
2255 return new css_style_backgroundrepeat('background-repeat', $value);
2256 }
2257 public function consolidate_to() {
2258 return 'background';
0e641c74
SH
2259 }
2260}
2261
2262/**
1d1d807e 2263 * A background attachment style.
0e641c74
SH
2264 *
2265 * @package moodlecore
2266 * @copyright 2011 Sam Hemelryk
2267 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2268 */
1d1d807e
SH
2269class css_style_backgroundattachment extends css_style_generic {
2270 /**
2271 * Creates a new background colour style
2272 *
2273 * @param mixed $value
2274 * @return css_style_backgroundattachment
2275 */
2276 public static function init($value) {
2277 return new css_style_backgroundattachment('background-attachment', $value);
2278 }
2279 public function consolidate_to() {
2280 return 'background';
2281 }
2282}
2283
2284/**
2285 * A background position style.
2286 *
2287 * @package moodlecore
2288 * @copyright 2011 Sam Hemelryk
2289 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2290 */
2291class css_style_backgroundposition extends css_style_generic {
0e641c74 2292 /**
1d1d807e 2293 * Creates a new background colour style
0e641c74
SH
2294 *
2295 * @param mixed $value
1d1d807e 2296 * @return css_style_backgroundposition
0e641c74
SH
2297 */
2298 public static function init($value) {
1d1d807e
SH
2299 return new css_style_backgroundposition('background-position', $value);
2300 }
2301 public function consolidate_to() {
2302 return 'background';
2303 }
2304}
2305
2306class css_style_padding extends css_style {
2307 public static function init($value) {
2308 $value = preg_replace('#\s+#', ' ', $value);
2309 $bits = explode(' ', $value, 4);
2310
2311 $top = $right = $bottom = $left = null;
2312 if (count($bits) > 0) {
2313 $top = $right = $bottom = $left = array_shift($bits);
2314 }
2315 if (count($bits) > 0) {
2316 $right = $left = array_shift($bits);
2317 }
2318 if (count($bits) > 0) {
2319 $bottom = array_shift($bits);
2320 }
2321 if (count($bits) > 0) {
2322 $left = array_shift($bits);
2323 }
2324 return array(
2325 new css_style_paddingtop('padding-top', $top),
2326 new css_style_paddingright('padding-right', $right),
2327 new css_style_paddingbottom('padding-bottom', $bottom),
2328 new css_style_paddingleft('padding-left', $left)
2329 );
2330 }
2331 public static function consolidate(array $styles) {
2332 if (count($styles) != 4) {
2333 return $styles;
2334 }
2335 $top = $right = $bottom = $left = null;
2336 foreach ($styles as $style) {
2337 switch ($style->get_name()) {
2338 case 'padding-top' : $top = $style->get_value();break;
2339 case 'padding-right' : $right = $style->get_value();break;
2340 case 'padding-bottom' : $bottom = $style->get_value();break;
2341 case 'padding-left' : $left = $style->get_value();break;
2342 }
2343 }
2344 if ($top == $bottom && $left == $right) {
2345 if ($top == $left) {
2346 return array(new css_style_padding('padding', $top));
2347 } else {
2348 return array(new css_style_padding('padding', "{$top} {$left}"));
2349 }
2350 } else if ($left == $right) {
2351 return array(new css_style_padding('padding', "{$top} {$right} {$bottom}"));
2352 } else {
2353 return array(new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}"));
2354 }
2355
2356 }
2357}
2358
2359class css_style_paddingtop extends css_style {
2360 public static function init($value) {
2361 return new css_style_paddingtop('padding-top', $value);
2362 }
2363 public function consolidate_to() {
2364 return 'padding';
2365 }
2366}
2367
2368class css_style_paddingright extends css_style {
2369 public static function init($value) {
2370 return new css_style_paddingright('padding-right', $value);
2371 }
2372 public function consolidate_to() {
2373 return 'padding';
2374 }
2375}
2376
2377class css_style_paddingbottom extends css_style {
2378 public static function init($value) {
2379 return new css_style_paddingbottom('padding-bottom', $value);
2380 }
2381 public function consolidate_to() {
2382 return 'padding';
2383 }
2384}
2385
2386class css_style_paddingleft extends css_style {
2387 public static function init($value) {
2388 return new css_style_paddingleft('padding-left', $value);
2389 }
2390 public function consolidate_to() {
2391 return 'padding';
0e641c74
SH
2392 }
2393}