MDL-33468 css_optimiser: Fixed validation of individual background styles
[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/**
6bbd4858 18 * This file contains CSS related class, and function for the CSS optimiser
0e641c74 19 *
6bbd4858
SH
20 * Please see the {@see css_optimiser} class for greater detail.
21 *
f37f608e
SH
22 * @package core_css
23 * @category css
6bbd4858 24 * @copyright 2012 Sam Hemelryk
f37f608e 25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
26 */
27
869b1255
PS
28//NOTE: do not verify MOODLE_INTERNAL here, this is used from themes too
29
0e641c74
SH
30/**
31 * Stores CSS in a file at the given path.
32 *
6bbd4858
SH
33 * This function either succeeds or throws an exception.
34 *
f37f608e
SH
35 * @param theme_config $theme The theme that the CSS belongs to.
36 * @param string $csspath The path to store the CSS at.
37 * @param array $cssfiles The CSS files to store.
0e641c74
SH
38 */
39function css_store_css(theme_config $theme, $csspath, array $cssfiles) {
1d1d807e
SH
40 global $CFG;
41
6bbd4858 42 if (!empty($CFG->enablecssoptimiser)) {
f37f608e 43 // This is an experimental feature introduced in Moodle 2.3
1d1d807e
SH
44 // The CSS optimiser organises the CSS in order to reduce the overall number
45 // of rules and styles being sent to the client. It does this by collating
46 // the CSS before it is cached removing excess styles and rules and stripping
47 // out any extraneous content such as comments and empty rules.
48 $optimiser = new css_optimiser;
49 $css = '';
50 foreach ($cssfiles as $file) {
51 $css .= file_get_contents($file)."\n";
52 }
53 $css = $theme->post_process($css);
54 $css = $optimiser->process($css);
0e641c74 55
1d1d807e
SH
56 // If cssoptimisestats is set then stats from the optimisation are collected
57 // and output at the beginning of the CSS
6bbd4858 58 if (!empty($CFG->cssoptimiserstats)) {
1d1d807e
SH
59 $css = $optimiser->output_stats_css().$css;
60 }
61 } else {
62 // This is the default behaviour.
f37f608e 63 // The cssoptimise setting was introduced in Moodle 2.3 and will hopefully
1d1d807e
SH
64 // in the future be changed from an experimental setting to the default.
65 // The css_minify_css will method will use the Minify library remove
66 // comments, additional whitespace and other minor measures to reduce the
67 // the overall CSS being sent.
68 // However it has the distinct disadvantage of having to minify the CSS
69 // before running the post process functions. Potentially things may break
70 // here if theme designers try to push things with CSS post processing.
71 $css = $theme->post_process(css_minify_css($cssfiles));
72 }
0e641c74 73
979d3207
PS
74 clearstatcache();
75 if (!file_exists(dirname($csspath))) {
76 @mkdir(dirname($csspath), $CFG->directorypermissions, true);
77 }
78
79 // Prevent serving of incomplete file from concurrent request,
80 // the rename() should be more atomic than fwrite().
81 ignore_user_abort(true);
82 if ($fp = fopen($csspath.'.tmp', 'xb')) {
83 fwrite($fp, $css);
84 fclose($fp);
85 rename($csspath.'.tmp', $csspath);
86 @chmod($csspath, $CFG->filepermissions);
87 @unlink($csspath.'.tmp'); // just in case anything fails
88 }
89 ignore_user_abort(false);
90 if (connection_aborted()) {
91 die;
92 }
0e641c74
SH
93}
94
95/**
96 * Sends IE specific CSS
97 *
6bbd4858
SH
98 * In writing the CSS parser I have a theory that we could optimise the CSS
99 * then split it based upon the number of selectors to ensure we dont' break IE
100 * and that we include only as many sub-stylesheets as we require.
101 * Of course just a theory but may be fun to code.
102 *
103 * @param string $themename The name of the theme we are sending CSS for.
104 * @param string $rev The revision to ensure we utilise the cache.
2c61e7cc 105 * @param string $etag The revision to ensure we utilise the cache.
7070b7f2 106 * @param bool $slasharguments
0e641c74 107 */
2c61e7cc
PS
108function css_send_ie_css($themename, $rev, $etag, $slasharguments) {
109 global $CFG;
110
111 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
112
113 $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot);
0e641c74 114
f37f608e 115 $css = "/** Unfortunately IE6/7 does not support more than 4096 selectors in one CSS file, which means we have to use some ugly hacks :-( **/";
7070b7f2 116 if ($slasharguments) {
2c61e7cc
PS
117 $css .= "\n@import url($relroot/styles.php/$themename/$rev/plugins);";
118 $css .= "\n@import url($relroot/styles.php/$themename/$rev/parents);";
119 $css .= "\n@import url($relroot/styles.php/$themename/$rev/theme);";
7070b7f2 120 } else {
2c61e7cc
PS
121 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=plugins);";
122 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=parents);";
123 $css .= "\n@import url($relroot/styles.php?theme=$themename&rev=$rev&type=theme);";
7070b7f2 124 }
0e641c74 125
2c61e7cc 126 header('Etag: '.$etag);
0e641c74
SH
127 header('Content-Disposition: inline; filename="styles.php"');
128 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
129 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
130 header('Pragma: ');
8c672cf9 131 header('Cache-Control: public, max-age='.$lifetime);
0e641c74
SH
132 header('Accept-Ranges: none');
133 header('Content-Type: text/css; charset=utf-8');
134 header('Content-Length: '.strlen($css));
135
136 echo $css;
137 die;
138}
139
140/**
141 * Sends a cached CSS file
142 *
f37f608e 143 * This function sends the cached CSS file. Remember it is generated on the first
6bbd4858
SH
144 * request, then optimised/minified, and finally cached for serving.
145 *
146 * @param string $csspath The path to the CSS file we want to serve.
2c61e7cc 147 * @param string $etag The revision to make sure we utilise any caches.
0e641c74 148 */
2c61e7cc
PS
149function css_send_cached_css($csspath, $etag) {
150 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
0e641c74 151
2c61e7cc 152 header('Etag: '.$etag);
0e641c74
SH
153 header('Content-Disposition: inline; filename="styles.php"');
154 header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT');
155 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
156 header('Pragma: ');
8c672cf9 157 header('Cache-Control: public, max-age='.$lifetime);
0e641c74
SH
158 header('Accept-Ranges: none');
159 header('Content-Type: text/css; charset=utf-8');
160 if (!min_enable_zlib_compression()) {
161 header('Content-Length: '.filesize($csspath));
162 }
163
164 readfile($csspath);
165 die;
166}
167
168/**
169 * Sends CSS directly without caching it.
170 *
6bbd4858
SH
171 * This function takes a raw CSS string, optimises it if required, and then
172 * serves it.
173 * Turning both themedesignermode and CSS optimiser on at the same time is aweful
174 * for performance because of the optimiser running here. However it was done so
175 * that theme designers could utilise the optimised output during development to
176 * help them optimise their CSS... not that they should write lazy CSS.
177 *
0e641c74
SH
178 * @param string CSS
179 */
180function css_send_uncached_css($css) {
1d1d807e 181 global $CFG;
0e641c74
SH
182
183 header('Content-Disposition: inline; filename="styles_debug.php"');
184 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
185 header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) .' GMT');
186 header('Pragma: ');
187 header('Accept-Ranges: none');
188 header('Content-Type: text/css; charset=utf-8');
189
190 if (is_array($css)) {
191 $css = implode("\n\n", $css);
192 }
1d1d807e 193
6bbd4858 194 if (!empty($CFG->enablecssoptimiser)) {
1d1d807e
SH
195 $css = str_replace("\n", "\r\n", $css);
196
197 $optimiser = new css_optimiser;
198 $css = $optimiser->process($css);
6bbd4858 199 if (!empty($CFG->cssoptimiserstats)) {
1d1d807e
SH
200 $css = $optimiser->output_stats_css().$css;
201 }
202 }
203
204 echo $css;
0e641c74
SH
205
206 die;
207}
208
869b1255
PS
209/**
210 * Send file not modified headers
211 * @param int $lastmodified
212 * @param string $etag
213 */
214function css_send_unmodified($lastmodified, $etag) {
215 $lifetime = 60*60*24*60; // 60 days only - the revision may get incremented quite often
216 header('HTTP/1.1 304 Not Modified');
217 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
218 header('Cache-Control: public, max-age='.$lifetime);
219 header('Content-Type: text/css; charset=utf-8');
220 header('Etag: '.$etag);
221 if ($lastmodified) {
222 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT');
223 }
224 die;
225}
226
0e641c74
SH
227/**
228 * Sends a 404 message about CSS not being found.
229 */
230function css_send_css_not_found() {
231 header('HTTP/1.0 404 not found');
232 die('CSS was not found, sorry.');
233}
234
0abd4846
SH
235/**
236 * Uses the minify library to compress CSS.
237 *
6bbd4858 238 * This is used if $CFG->enablecssoptimiser has been turned off. This was
0abd4846 239 * the original CSS optimisation library.
6bbd4858
SH
240 * It removes whitespace and shrinks things but does no apparent optimisation.
241 * Note the minify library is still being used for JavaScript.
0abd4846
SH
242 *
243 * @param array $files An array of files to minify
244 * @return string The minified CSS
245 */
1d1d807e
SH
246function css_minify_css($files) {
247 global $CFG;
248
d1f582d9
PS
249 if (empty($files)) {
250 return '';
251 }
252
1d1d807e
SH
253 set_include_path($CFG->libdir . '/minify/lib' . PATH_SEPARATOR . get_include_path());
254 require_once('Minify.php');
255
256 if (0 === stripos(PHP_OS, 'win')) {
257 Minify::setDocRoot(); // IIS may need help
258 }
259 // disable all caching, we do it in moodle
260 Minify::setCache(null, false);
261
262 $options = array(
263 'bubbleCssImports' => false,
264 // Don't gzip content we just want text for storage
265 'encodeOutput' => false,
266 // Maximum age to cache, not used but required
267 'maxAge' => (60*60*24*20),
268 // The files to minify
269 'files' => $files,
270 // Turn orr URI rewriting
271 'rewriteCssUris' => false,
272 // This returns the CSS rather than echoing it for display
273 'quiet' => true
274 );
d1f582d9
PS
275
276 $error = 'unknown';
277 try {
278 $result = Minify::serve('Files', $options);
279 if ($result['success']) {
280 return $result['content'];
281 }
282 } catch (Exception $e) {
283 $error = $e->getMessage();
284 $error = str_replace("\r", ' ', $error);
285 $error = str_replace("\n", ' ', $error);
286 }
287
288 // minification failed - try to inform the theme developer and include the non-minified version
289 $css = <<<EOD
290/* Error: $error */
291/* Problem detected during theme CSS minimisation, please review the following code */
292/* ================================================================================ */
293
294
295EOD;
296 foreach ($files as $cssfile) {
297 $css .= file_get_contents($cssfile)."\n";
298 }
299 return $css;
1d1d807e
SH
300}
301
302/**
f37f608e 303 * Determines if the given value is a valid CSS colour.
6bbd4858
SH
304 *
305 * A CSS colour can be one of the following:
306 * - Hex colour: #AA66BB
307 * - RGB colour: rgb(0-255, 0-255, 0-255)
f37f608e
SH
308 * - RGBA colour: rgba(0-255, 0-255, 0-255, 0-1)
309 * - HSL colour: hsl(0-360, 0-100%, 0-100%)
310 * - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1)
6bbd4858 311 *
f37f608e 312 * Or a recognised browser colour mapping {@see css_optimiser::$htmlcolours}
1d1d807e 313 *
6bbd4858 314 * @param string $value The colour value to check
1d1d807e
SH
315 * @return bool
316 */
317function css_is_colour($value) {
318 $value = trim($value);
6bbd4858
SH
319 if (in_array(strtolower($value), array('inherit'))) {
320 return true;
ff8e5d47 321 } else if (preg_match('/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/', $value)) {
1d1d807e
SH
322 return true;
323 } else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) {
324 return true;
f37f608e 325 } else if (preg_match('#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i', $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
6bbd4858
SH
326 // It is an RGB colour
327 return true;
f37f608e 328 } else if (preg_match('#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i', $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) {
6bbd4858
SH
329 // It is an RGBA colour
330 return true;
f37f608e 331 } else if (preg_match('#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i', $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
6bbd4858
SH
332 // It is an HSL colour
333 return true;
f37f608e 334 } else if (preg_match('#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i', $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) {
6bbd4858
SH
335 // It is an HSLA colour
336 return true;
337 }
338 // Doesn't look like a colour.
339 return false;
340}
341
342/**
343 * Returns true is the passed value looks like a CSS width.
344 * In order to pass this test the value must be purely numerical or end with a
345 * valid CSS unit term.
346 *
347 * @param string|int $value
348 * @return boolean
349 */
350function css_is_width($value) {
351 $value = trim($value);
352 if (in_array(strtolower($value), array('auto', 'inherit'))) {
1d1d807e 353 return true;
6bbd4858 354 }
f2e8d379 355 if ((string)$value === '0' || preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) {
1d1d807e
SH
356 return true;
357 }
358 return false;
359}
360
0abd4846
SH
361/**
362 * A simple sorting function to sort two array values on the number of items they contain
363 *
364 * @param array $a
365 * @param array $b
366 * @return int
367 */
368function css_sort_by_count(array $a, array $b) {
369 $a = count($a);
370 $b = count($b);
371 if ($a == $b) {
372 return 0;
373 }
374 return ($a > $b) ? -1 : 1;
375}
376
0e641c74
SH
377/**
378 * A basic CSS optimiser that strips out unwanted things and then processing the
379 * CSS organising styles and moving duplicates and useless CSS.
380 *
6bbd4858
SH
381 * This CSS optimiser works by reading through a CSS string one character at a
382 * time and building an object structure of the CSS.
383 * As part of that processing styles are expanded out as much as they can be to
384 * ensure we collect all mappings, at the end of the processing those styles are
385 * then combined into an optimised form to keep them as short as possible.
386 *
f37f608e
SH
387 * @package core_css
388 * @category css
6bbd4858 389 * @copyright 2012 Sam Hemelryk
f37f608e 390 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
391 */
392class css_optimiser {
393
f37f608e
SH
394 /**
395 * Used when the processor is about to start processing.
0e641c74
SH
396 * Processing states. Used internally.
397 */
398 const PROCESSING_START = 0;
f37f608e
SH
399
400 /**
401 * Used when the processor is currently processing a selector.
402 * Processing states. Used internally.
403 */
0e641c74 404 const PROCESSING_SELECTORS = 0;
f37f608e
SH
405
406 /**
407 * Used when the processor is currently processing a style.
408 * Processing states. Used internally.
409 */
0e641c74 410 const PROCESSING_STYLES = 1;
f37f608e
SH
411
412 /**
413 * Used when the processor is currently processing a comment.
414 * Processing states. Used internally.
415 */
0e641c74 416 const PROCESSING_COMMENT = 2;
f37f608e
SH
417
418 /**
419 * Used when the processor is currently processing an @ rule.
420 * Processing states. Used internally.
421 */
0e641c74 422 const PROCESSING_ATRULE = 3;
0e641c74 423
f37f608e
SH
424 /**
425 * The raw string length before optimisation.
0e641c74
SH
426 * Stats variables set during and after processing
427 * @var int
428 */
429 protected $rawstrlen = 0;
f37f608e
SH
430
431 /**
432 * The number of comments that were removed during optimisation.
433 * Stats variables set during and after processing
434 * @var int
435 */
0e641c74 436 protected $commentsincss = 0;
f37f608e
SH
437
438 /**
439 * The number of rules in the CSS before optimisation.
440 * Stats variables set during and after processing
441 * @var int
442 */
0e641c74 443 protected $rawrules = 0;
f37f608e
SH
444
445 /**
446 * The number of selectors using in CSS rules before optimisation.
447 * Stats variables set during and after processing
448 * @var int
449 */
0e641c74 450 protected $rawselectors = 0;
f37f608e
SH
451
452 /**
453 * The string length after optimisation.
454 * Stats variables set during and after processing
455 * @var int
456 */
0e641c74 457 protected $optimisedstrlen = 0;
f37f608e
SH
458
459 /**
460 * The number of rules after optimisation.
461 * Stats variables set during and after processing
462 * @var int
463 */
0e641c74 464 protected $optimisedrules = 0;
f37f608e
SH
465
466 /**
467 * The number of selectors used in rules after optimisation.
468 * Stats variables set during and after processing
469 * @var int
470 */
0e641c74 471 protected $optimisedselectors = 0;
f37f608e
SH
472
473 /**
474 * The start time of the optimisation.
475 * Stats variables set during and after processing
476 * @var int
477 */
0e641c74 478 protected $timestart = 0;
f37f608e
SH
479
480 /**
481 * The end time of the optimisation.
482 * Stats variables set during and after processing
483 * @var int
484 */
0e641c74 485 protected $timecomplete = 0;
0e641c74 486
6bbd4858
SH
487 /**
488 * Will be set to any errors that may have occured during processing.
489 * This is updated only at the end of processing NOT during.
490 *
ff8e5d47 491 * @var array
6bbd4858
SH
492 */
493 protected $errors = array();
494
0e641c74
SH
495 /**
496 * Processes incoming CSS optimising it and then returning it.
497 *
498 * @param string $css The raw CSS to optimise
499 * @return string The optimised CSS
500 */
501 public function process($css) {
502 global $CFG;
503
f2e8d379
SH
504 $css = trim($css);
505
0e641c74
SH
506 $this->reset_stats();
507 $this->timestart = microtime(true);
508 $this->rawstrlen = strlen($css);
509
f2e8d379
SH
510 if ($this->rawstrlen === 0) {
511 $this->errors[] = 'Skipping file as it has no content.';
512 return '';
513 }
514
0e641c74
SH
515 // First up we need to remove all line breaks - this allows us to instantly
516 // reduce our processing requirements and as we will process everything
517 // into a new structure there's really nothing lost.
518 $css = preg_replace('#\r?\n#', ' ', $css);
519
520 // Next remove the comments... no need to them in an optimised world and
521 // knowing they're all gone allows us to REALLY make our processing simpler
522 $css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss);
523
524 $medias = array(
525 'all' => new css_media()
526 );
527 $imports = array();
528 $charset = false;
1121f79b
SH
529 // Keyframes are used for CSS animation they will be processed right at the very end.
530 $keyframes = array();
1d1d807e 531
0e641c74 532 $currentprocess = self::PROCESSING_START;
1d1d807e 533 $currentrule = css_rule::init();
0e641c74
SH
534 $currentselector = css_selector::init();
535 $inquotes = false; // ' or "
536 $inbraces = false; // {
537 $inbrackets = false; // [
538 $inparenthesis = false; // (
539 $currentmedia = $medias['all'];
540 $currentatrule = null;
541 $suspectatrule = false;
542
543 $buffer = '';
544 $char = null;
545
546 // Next we are going to iterate over every single character in $css.
f37f608e 547 // This is why we removed line breaks and comments!
0e641c74
SH
548 for ($i = 0; $i < $this->rawstrlen; $i++) {
549 $lastchar = $char;
550 $char = substr($css, $i, 1);
551 if ($char == '@' && $buffer == '') {
552 $suspectatrule = true;
553 }
554 switch ($currentprocess) {
555 // Start processing an at rule e.g. @media, @page
556 case self::PROCESSING_ATRULE:
557 switch ($char) {
558 case ';':
559 if (!$inbraces) {
560 $buffer .= $char;
561 if ($currentatrule == 'import') {
562 $imports[] = $buffer;
563 $currentprocess = self::PROCESSING_SELECTORS;
564 } else if ($currentatrule == 'charset') {
565 $charset = $buffer;
566 $currentprocess = self::PROCESSING_SELECTORS;
567 }
568 }
ecad5058
SH
569 if ($currentatrule !== 'media') {
570 $buffer = '';
571 $currentatrule = false;
572 }
f37f608e
SH
573 // continue 1: The switch processing chars
574 // continue 2: The switch processing the state
575 // continue 3: The for loop
0e641c74
SH
576 continue 3;
577 case '{':
ecad5058
SH
578 if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#', $buffer, $matches)) {
579 // Basic media declaration
0e641c74
SH
580 $mediatypes = str_replace(' ', '', $matches[1]);
581 if (!array_key_exists($mediatypes, $medias)) {
582 $medias[$mediatypes] = new css_media($mediatypes);
583 }
584 $currentmedia = $medias[$mediatypes];
585 $currentprocess = self::PROCESSING_SELECTORS;
586 $buffer = '';
ecad5058
SH
587 } else if ($currentatrule == 'media' && preg_match('#\s*@media\s*([^{]+)#', $buffer, $matches)) {
588 // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/
589 $mediatypes = $matches[1];
590 $hash = md5($mediatypes);
591 $medias[$hash] = new css_media($mediatypes);
592 $currentmedia = $medias[$hash];
593 $currentprocess = self::PROCESSING_SELECTORS;
594 $buffer = '';
1121f79b
SH
595 } else if ($currentatrule == 'keyframes' && preg_match('#@((\-moz\-|\-webkit\-)?keyframes)\s*([^\s]+)#', $buffer, $matches)) {
596 $keyframefor = $matches[1];
597 $keyframename = $matches[3];
598 $keyframe = new css_keyframe($keyframefor, $keyframename);
599 $keyframes[] = $keyframe;
600 $currentmedia = $keyframe;
601 $currentprocess = self::PROCESSING_SELECTORS;
602 $buffer = '';
0e641c74 603 }
f37f608e
SH
604 // continue 1: The switch processing chars
605 // continue 2: The switch processing the state
606 // continue 3: The for loop
0e641c74
SH
607 continue 3;
608 }
609 break;
610 // Start processing selectors
611 case self::PROCESSING_START:
612 case self::PROCESSING_SELECTORS:
613 switch ($char) {
614 case '[':
615 $inbrackets ++;
616 $buffer .= $char;
f37f608e
SH
617 // continue 1: The switch processing chars
618 // continue 2: The switch processing the state
619 // continue 3: The for loop
0e641c74
SH
620 continue 3;
621 case ']':
622 $inbrackets --;
623 $buffer .= $char;
f37f608e
SH
624 // continue 1: The switch processing chars
625 // continue 2: The switch processing the state
626 // continue 3: The for loop
0e641c74
SH
627 continue 3;
628 case ' ':
629 if ($inbrackets) {
f37f608e
SH
630 // continue 1: The switch processing chars
631 // continue 2: The switch processing the state
632 // continue 3: The for loop
0e641c74
SH
633 continue 3;
634 }
635 if (!empty($buffer)) {
1121f79b
SH
636 if ($suspectatrule && preg_match('#@(media|import|charset|(\-moz\-|\-webkit\-)?(keyframes))\s*#', $buffer, $matches)) {
637 $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
0e641c74
SH
638 $currentprocess = self::PROCESSING_ATRULE;
639 $buffer .= $char;
640 } else {
641 $currentselector->add($buffer);
642 $buffer = '';
643 }
644 }
645 $suspectatrule = false;
f37f608e
SH
646 // continue 1: The switch processing chars
647 // continue 2: The switch processing the state
648 // continue 3: The for loop
0e641c74
SH
649 continue 3;
650 case '{':
651 if ($inbrackets) {
f37f608e
SH
652 // continue 1: The switch processing chars
653 // continue 2: The switch processing the state
654 // continue 3: The for loop
0e641c74
SH
655 continue 3;
656 }
0663b535
SH
657 if ($buffer !== '') {
658 $currentselector->add($buffer);
659 }
1d1d807e 660 $currentrule->add_selector($currentselector);
0e641c74
SH
661 $currentselector = css_selector::init();
662 $currentprocess = self::PROCESSING_STYLES;
663
664 $buffer = '';
f37f608e
SH
665 // continue 1: The switch processing chars
666 // continue 2: The switch processing the state
667 // continue 3: The for loop
0e641c74
SH
668 continue 3;
669 case '}':
670 if ($inbrackets) {
f37f608e
SH
671 // continue 1: The switch processing chars
672 // continue 2: The switch processing the state
673 // continue 3: The for loop
0e641c74
SH
674 continue 3;
675 }
676 if ($currentatrule == 'media') {
677 $currentmedia = $medias['all'];
678 $currentatrule = false;
679 $buffer = '';
1121f79b
SH
680 } else if (strpos($currentatrule, 'keyframes') !== false) {
681 $currentmedia = $medias['all'];
682 $currentatrule = false;
683 $buffer = '';
0e641c74 684 }
f37f608e
SH
685 // continue 1: The switch processing chars
686 // continue 2: The switch processing the state
687 // continue 3: The for loop
0e641c74
SH
688 continue 3;
689 case ',':
690 if ($inbrackets) {
f37f608e
SH
691 // continue 1: The switch processing chars
692 // continue 2: The switch processing the state
693 // continue 3: The for loop
0e641c74
SH
694 continue 3;
695 }
696 $currentselector->add($buffer);
1d1d807e 697 $currentrule->add_selector($currentselector);
0e641c74
SH
698 $currentselector = css_selector::init();
699 $buffer = '';
f37f608e
SH
700 // continue 1: The switch processing chars
701 // continue 2: The switch processing the state
702 // continue 3: The for loop
0e641c74
SH
703 continue 3;
704 }
705 break;
706 // Start processing styles
707 case self::PROCESSING_STYLES:
708 if ($char == '"' || $char == "'") {
709 if ($inquotes === false) {
710 $inquotes = $char;
711 }
712 if ($inquotes === $char && $lastchar !== '\\') {
713 $inquotes = false;
714 }
715 }
716 if ($inquotes) {
717 $buffer .= $char;
718 continue 2;
719 }
720 switch ($char) {
721 case ';':
50e49c91
SH
722 if ($inparenthesis) {
723 $buffer .= $char;
724 // continue 1: The switch processing chars
725 // continue 2: The switch processing the state
726 // continue 3: The for loop
727 continue 3;
728 }
1d1d807e 729 $currentrule->add_style($buffer);
0e641c74
SH
730 $buffer = '';
731 $inquotes = false;
f37f608e
SH
732 // continue 1: The switch processing chars
733 // continue 2: The switch processing the state
734 // continue 3: The for loop
0e641c74
SH
735 continue 3;
736 case '}':
1d1d807e
SH
737 $currentrule->add_style($buffer);
738 $this->rawselectors += $currentrule->get_selector_count();
0e641c74 739
1d1d807e 740 $currentmedia->add_rule($currentrule);
0e641c74 741
1d1d807e 742 $currentrule = css_rule::init();
0e641c74
SH
743 $currentprocess = self::PROCESSING_SELECTORS;
744 $this->rawrules++;
745 $buffer = '';
746 $inquotes = false;
50e49c91
SH
747 $inparenthesis = false;
748 // continue 1: The switch processing chars
749 // continue 2: The switch processing the state
750 // continue 3: The for loop
751 continue 3;
752 case '(':
753 $inparenthesis = true;
754 $buffer .= $char;
755 // continue 1: The switch processing chars
756 // continue 2: The switch processing the state
757 // continue 3: The for loop
758 continue 3;
759 case ')':
760 $inparenthesis = false;
761 $buffer .= $char;
f37f608e
SH
762 // continue 1: The switch processing chars
763 // continue 2: The switch processing the state
764 // continue 3: The for loop
0e641c74
SH
765 continue 3;
766 }
767 break;
768 }
769 $buffer .= $char;
770 }
771
772 $css = '';
773 if (!empty($charset)) {
774 $imports[] = $charset;
775 }
776 if (!empty($imports)) {
777 $css .= implode("\n", $imports);
778 $css .= "\n\n";
779 }
0663b535 780
0e641c74
SH
781 foreach ($medias as $media) {
782 $media->organise_rules_by_selectors();
783 $this->optimisedrules += $media->count_rules();
784 $this->optimisedselectors += $media->count_selectors();
6bbd4858 785 if ($media->has_errors()) {
f2e8d379 786 $this->errors += $media->get_errors();
6bbd4858 787 }
0663b535
SH
788 if (in_array('all', $media->get_types())) {
789 $resetrules = $media->get_reset_rules(true);
790 if (!empty($resetrules)) {
791 $css .= css_writer::media('all', $resetrules);
792 }
793 }
794 }
795
796 foreach ($medias as $media) {
0e641c74
SH
797 $css .= $media->out();
798 }
1121f79b
SH
799
800 if (count($keyframes) > 0) {
6a519fdc 801 $css .= "\n";
1121f79b
SH
802 foreach ($keyframes as $keyframe) {
803 $this->optimisedrules += $keyframe->count_rules();
804 $this->optimisedselectors += $keyframe->count_selectors();
805 if ($keyframe->has_errors()) {
806 $this->errors += $keyframe->get_errors();
807 }
808 $css .= $keyframe->out();
809 }
810 }
811
0e641c74
SH
812 $this->optimisedstrlen = strlen($css);
813
814 $this->timecomplete = microtime(true);
0e641c74
SH
815 return trim($css);
816 }
817
818 /**
8589a4a5 819 * Returns an array of stats from the last processing run
0e641c74
SH
820 * @return string
821 */
822 public function get_stats() {
8589a4a5
SH
823 $stats = array(
824 'timestart' => $this->timestart,
825 'timecomplete' => $this->timecomplete,
826 'timetaken' => round($this->timecomplete - $this->timestart, 4),
827 'commentsincss' => $this->commentsincss,
828 'rawstrlen' => $this->rawstrlen,
829 'rawselectors' => $this->rawselectors,
830 'rawrules' => $this->rawrules,
831 'optimisedstrlen' => $this->optimisedstrlen,
832 'optimisedrules' => $this->optimisedrules,
f2e8d379
SH
833 'optimisedselectors' => $this->optimisedselectors,
834 'improvementstrlen' => '-',
835 'improvementrules' => '-',
836 'improvementselectors' => '-',
8589a4a5 837 );
f2e8d379
SH
838 if ($this->rawstrlen > 0) {
839 $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
840 }
841 if ($this->rawrules > 0) {
842 $stats['improvementrules'] = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%';
843 }
844 if ($this->rawselectors > 0) {
845 $stats['improvementselectors'] = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%';
846 }
8589a4a5
SH
847 return $stats;
848 }
849
6bbd4858
SH
850 /**
851 * Returns true if any errors have occured during processing
852 *
853 * @return bool
854 */
855 public function has_errors() {
856 return !empty($this->errors);
857 }
858
859 /**
860 * Returns an array of errors that have occured
861 *
f2e8d379 862 * @param bool $clear If set to true the errors will be cleared after being returned.
6bbd4858
SH
863 * @return array
864 */
f2e8d379
SH
865 public function get_errors($clear = false) {
866 $errors = $this->errors;
867 if ($clear) {
868 // Reset the error array
869 $this->errors = array();
870 }
871 return $errors;
6bbd4858
SH
872 }
873
874 /**
875 * Returns any errors as a string that can be included in CSS.
876 *
877 * @return string
878 */
879 public function output_errors_css() {
880 $computedcss = "/****************************************\n";
881 $computedcss .= " *--- Errors found during processing ----\n";
882 foreach ($this->errors as $error) {
883 $computedcss .= preg_replace('#^#m', '* ', $error);
884 }
885 $computedcss .= " ****************************************/\n\n";
886 return $computedcss;
887 }
888
8589a4a5
SH
889 /**
890 * Returns a string to display stats about the last generation within CSS output
6bbd4858 891 *
8589a4a5
SH
892 * @return string
893 */
894 public function output_stats_css() {
0e641c74
SH
895
896 $computedcss = "/****************************************\n";
897 $computedcss .= " *------- CSS Optimisation stats --------\n";
f2e8d379
SH
898
899 if ($this->rawstrlen === 0) {
900 $computedcss .= " File not processed as it has no content /\n\n";
901 $computedcss .= " ****************************************/\n\n";
902 return $computedcss;
903 } else if ($this->rawrules === 0) {
904 $computedcss .= " File contained no rules to be processed /\n\n";
905 $computedcss .= " ****************************************/\n\n";
906 return $computedcss;
907 }
908
909 $stats = $this->get_stats();
910
0e641c74 911 $computedcss .= " * ".date('r')."\n";
1d1d807e
SH
912 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
913 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
0e641c74 914 $computedcss .= " *--------------- before ----------------\n";
1d1d807e
SH
915 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n";
916 $computedcss .= " * {$stats['rawrules']} \t rules read in\n";
917 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n";
0e641c74 918 $computedcss .= " *---------------- after ----------------\n";
1d1d807e
SH
919 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n";
920 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n";
921 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n";
0e641c74 922 $computedcss .= " *---------------- stats ----------------\n";
1d1d807e
SH
923 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n";
924 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n";
925 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n";
0e641c74
SH
926 $computedcss .= " ****************************************/\n\n";
927
928 return $computedcss;
929 }
930
931 /**
932 * Resets the stats ready for another fresh processing
933 */
934 public function reset_stats() {
935 $this->commentsincss = 0;
936 $this->optimisedrules = 0;
937 $this->optimisedselectors = 0;
938 $this->optimisedstrlen = 0;
939 $this->rawrules = 0;
940 $this->rawselectors = 0;
941 $this->rawstrlen = 0;
942 $this->timecomplete = 0;
943 $this->timestart = 0;
944 }
945
946 /**
947 * An array of the common HTML colours that are supported by most browsers.
948 *
949 * This reference table is used to allow us to unify colours, and will aid
950 * us in identifying buggy CSS using unsupported colours.
951 *
952 * @staticvar array
953 * @var array
954 */
955 public static $htmlcolours = array(
956 'aliceblue' => '#F0F8FF',
957 'antiquewhite' => '#FAEBD7',
958 'aqua' => '#00FFFF',
959 'aquamarine' => '#7FFFD4',
960 'azure' => '#F0FFFF',
961 'beige' => '#F5F5DC',
962 'bisque' => '#FFE4C4',
963 'black' => '#000000',
964 'blanchedalmond' => '#FFEBCD',
965 'blue' => '#0000FF',
966 'blueviolet' => '#8A2BE2',
967 'brown' => '#A52A2A',
968 'burlywood' => '#DEB887',
969 'cadetblue' => '#5F9EA0',
970 'chartreuse' => '#7FFF00',
971 'chocolate' => '#D2691E',
972 'coral' => '#FF7F50',
973 'cornflowerblue' => '#6495ED',
974 'cornsilk' => '#FFF8DC',
975 'crimson' => '#DC143C',
976 'cyan' => '#00FFFF',
977 'darkblue' => '#00008B',
978 'darkcyan' => '#008B8B',
979 'darkgoldenrod' => '#B8860B',
980 'darkgray' => '#A9A9A9',
981 'darkgrey' => '#A9A9A9',
982 'darkgreen' => '#006400',
983 'darkKhaki' => '#BDB76B',
984 'darkmagenta' => '#8B008B',
985 'darkolivegreen' => '#556B2F',
986 'arkorange' => '#FF8C00',
987 'darkorchid' => '#9932CC',
988 'darkred' => '#8B0000',
989 'darksalmon' => '#E9967A',
990 'darkseagreen' => '#8FBC8F',
991 'darkslateblue' => '#483D8B',
992 'darkslategray' => '#2F4F4F',
993 'darkslategrey' => '#2F4F4F',
994 'darkturquoise' => '#00CED1',
995 'darkviolet' => '#9400D3',
996 'deeppink' => '#FF1493',
997 'deepskyblue' => '#00BFFF',
998 'dimgray' => '#696969',
999 'dimgrey' => '#696969',
1000 'dodgerblue' => '#1E90FF',
1001 'firebrick' => '#B22222',
1002 'floralwhite' => '#FFFAF0',
1003 'forestgreen' => '#228B22',
1004 'fuchsia' => '#FF00FF',
1005 'gainsboro' => '#DCDCDC',
1006 'ghostwhite' => '#F8F8FF',
1007 'gold' => '#FFD700',
1008 'goldenrod' => '#DAA520',
1009 'gray' => '#808080',
1010 'grey' => '#808080',
1011 'green' => '#008000',
1012 'greenyellow' => '#ADFF2F',
1013 'honeydew' => '#F0FFF0',
1014 'hotpink' => '#FF69B4',
1015 'indianred ' => '#CD5C5C',
1016 'indigo ' => '#4B0082',
1017 'ivory' => '#FFFFF0',
1018 'khaki' => '#F0E68C',
1019 'lavender' => '#E6E6FA',
1020 'lavenderblush' => '#FFF0F5',
1021 'lawngreen' => '#7CFC00',
1022 'lemonchiffon' => '#FFFACD',
1023 'lightblue' => '#ADD8E6',
1024 'lightcoral' => '#F08080',
1025 'lightcyan' => '#E0FFFF',
1026 'lightgoldenrodyellow' => '#FAFAD2',
1027 'lightgray' => '#D3D3D3',
1028 'lightgrey' => '#D3D3D3',
1029 'lightgreen' => '#90EE90',
1030 'lightpink' => '#FFB6C1',
1031 'lightsalmon' => '#FFA07A',
1032 'lightseagreen' => '#20B2AA',
1033 'lightskyblue' => '#87CEFA',
1034 'lightslategray' => '#778899',
1035 'lightslategrey' => '#778899',
1036 'lightsteelblue' => '#B0C4DE',
1037 'lightyellow' => '#FFFFE0',
1038 'lime' => '#00FF00',
1039 'limegreen' => '#32CD32',
1040 'linen' => '#FAF0E6',
1041 'magenta' => '#FF00FF',
1042 'maroon' => '#800000',
1043 'mediumaquamarine' => '#66CDAA',
1044 'mediumblue' => '#0000CD',
1045 'mediumorchid' => '#BA55D3',
1046 'mediumpurple' => '#9370D8',
1047 'mediumseagreen' => '#3CB371',
1048 'mediumslateblue' => '#7B68EE',
1049 'mediumspringgreen' => '#00FA9A',
1050 'mediumturquoise' => '#48D1CC',
1051 'mediumvioletred' => '#C71585',
1052 'midnightblue' => '#191970',
1053 'mintcream' => '#F5FFFA',
1054 'mistyrose' => '#FFE4E1',
1055 'moccasin' => '#FFE4B5',
1056 'navajowhite' => '#FFDEAD',
1057 'navy' => '#000080',
1058 'oldlace' => '#FDF5E6',
1059 'olive' => '#808000',
1060 'olivedrab' => '#6B8E23',
1061 'orange' => '#FFA500',
1062 'orangered' => '#FF4500',
1063 'orchid' => '#DA70D6',
1064 'palegoldenrod' => '#EEE8AA',
1065 'palegreen' => '#98FB98',
1066 'paleturquoise' => '#AFEEEE',
1067 'palevioletred' => '#D87093',
1068 'papayawhip' => '#FFEFD5',
1069 'peachpuff' => '#FFDAB9',
1070 'peru' => '#CD853F',
1071 'pink' => '#FFC0CB',
1072 'plum' => '#DDA0DD',
1073 'powderblue' => '#B0E0E6',
1074 'purple' => '#800080',
1075 'red' => '#FF0000',
1076 'rosybrown' => '#BC8F8F',
1077 'royalblue' => '#4169E1',
1078 'saddlebrown' => '#8B4513',
1079 'salmon' => '#FA8072',
1080 'sandybrown' => '#F4A460',
1081 'seagreen' => '#2E8B57',
1082 'seashell' => '#FFF5EE',
1083 'sienna' => '#A0522D',
1084 'silver' => '#C0C0C0',
1085 'skyblue' => '#87CEEB',
1086 'slateblue' => '#6A5ACD',
1087 'slategray' => '#708090',
1088 'slategrey' => '#708090',
1089 'snow' => '#FFFAFA',
1090 'springgreen' => '#00FF7F',
1091 'steelblue' => '#4682B4',
1092 'tan' => '#D2B48C',
1093 'teal' => '#008080',
1094 'thistle' => '#D8BFD8',
1095 'tomato' => '#FF6347',
6bbd4858 1096 'transparent' => 'transparent',
0e641c74
SH
1097 'turquoise' => '#40E0D0',
1098 'violet' => '#EE82EE',
1099 'wheat' => '#F5DEB3',
1100 'white' => '#FFFFFF',
1101 'whitesmoke' => '#F5F5F5',
1102 'yellow' => '#FFFF00',
1103 'yellowgreen' => '#9ACD32'
1104 );
1105}
1106
1d1d807e
SH
1107/**
1108 * Used to prepare CSS strings
1109 *
f37f608e
SH
1110 * @package core_css
1111 * @category css
6bbd4858 1112 * @copyright 2012 Sam Hemelryk
f37f608e 1113 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e
SH
1114 */
1115abstract class css_writer {
ff8e5d47 1116
1d1d807e
SH
1117 /**
1118 * The current indent level
1119 * @var int
1120 */
1121 protected static $indent = 0;
1122
1123 /**
1124 * Returns true if the output should still maintain minimum formatting.
1125 * @return bool
1126 */
1127 protected static function is_pretty() {
1128 global $CFG;
6bbd4858 1129 return (!empty($CFG->cssoptimiserpretty));
1d1d807e
SH
1130 }
1131
1132 /**
1133 * Returns the indenting char to use for indenting things nicely.
1134 * @return string
1135 */
1136 protected static function get_indent() {
1137 if (self::is_pretty()) {
1138 return str_repeat(" ", self::$indent);
1139 }
1140 return '';
1141 }
1142
1143 /**
1144 * Increases the current indent
1145 */
1146 protected static function increase_indent() {
1147 self::$indent++;
1148 }
1149
1150 /**
6bbd4858 1151 * Decreases the current indent
1d1d807e
SH
1152 */
1153 protected static function decrease_indent() {
1154 self::$indent--;
1155 }
1156
1157 /**
1158 * Returns the string to use as a separator
1159 * @return string
1160 */
1161 protected static function get_separator() {
1162 return (self::is_pretty())?"\n":' ';
1163 }
1164
1165 /**
1166 * Returns CSS for media
1167 *
1168 * @param string $typestring
1169 * @param array $rules An array of css_rule objects
1170 * @return string
1171 */
1172 public static function media($typestring, array &$rules) {
1173 $nl = self::get_separator();
1174
1175 $output = '';
1176 if ($typestring !== 'all') {
1177 $output .= $nl.$nl."@media {$typestring} {".$nl;
1178 self::increase_indent();
1179 }
1180 foreach ($rules as $rule) {
1181 $output .= $rule->out().$nl;
1182 }
1183 if ($typestring !== 'all') {
1184 self::decrease_indent();
1185 $output .= '}';
1186 }
1187 return $output;
1188 }
1189
1121f79b
SH
1190 public static function keyframe($for, $name, array &$rules) {
1191 $nl = self::get_separator();
1192
1193 $output = $nl."@{$for} {$name} {";
1194 foreach ($rules as $rule) {
1195 $output .= $rule->out();
1196 }
1197 $output .= '}';
1198 return $output;
1199 }
1200
1d1d807e
SH
1201 /**
1202 * Returns CSS for a rule
1203 *
1204 * @param string $selector
1205 * @param string $styles
1206 * @return string
1207 */
1208 public static function rule($selector, $styles) {
1209 $css = self::get_indent()."{$selector}{{$styles}}";
1210 return $css;
1211 }
1212
1213 /**
1214 * Returns CSS for the selectors of a rule
1215 *
1216 * @param array $selectors Array of css_selector objects
1217 * @return string
1218 */
1219 public static function selectors(array $selectors) {
1220 $nl = self::get_separator();
1221 $selectorstrings = array();
1222 foreach ($selectors as $selector) {
1223 $selectorstrings[] = $selector->out();
1224 }
1225 return join(','.$nl, $selectorstrings);
1226 }
1227
1228 /**
1229 * Returns a selector given the components that make it up.
1230 *
1231 * @param array $components
1232 * @return string
1233 */
1234 public static function selector(array $components) {
1235 return trim(join(' ', $components));
1236 }
1237
1238 /**
ff8e5d47 1239 * Returns a CSS string for the provided styles
1d1d807e
SH
1240 *
1241 * @param array $styles Array of css_style objects
ff8e5d47 1242 * @return string
1d1d807e
SH
1243 */
1244 public static function styles(array $styles) {
1245 $bits = array();
1246 foreach ($styles as $style) {
e3036985
SH
1247 if (is_array($style)) {
1248 foreach ($style as $advstyle) {
1249 $bits[] = $advstyle->out();
1250 }
1251 continue;
1252 }
1d1d807e
SH
1253 $bits[] = $style->out();
1254 }
1255 return join('', $bits);
1256 }
1257
1258 /**
1259 * Returns a style CSS
1260 *
1261 * @param string $name
1262 * @param string $value
1263 * @param bool $important
1264 * @return string
1265 */
1266 public static function style($name, $value, $important = false) {
f2e8d379 1267 $value = trim($value);
1d1d807e
SH
1268 if ($important && strpos($value, '!important') === false) {
1269 $value .= ' !important';
1270 }
1271 return "{$name}:{$value};";
1272 }
1273}
1274
0e641c74
SH
1275/**
1276 * A structure to represent a CSS selector.
1277 *
1278 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
1279 * rule.
1280 *
f37f608e
SH
1281 * @package core_css
1282 * @category css
6bbd4858 1283 * @copyright 2012 Sam Hemelryk
f37f608e 1284 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
1285 */
1286class css_selector {
1287
1288 /**
1289 * An array of selector bits
1290 * @var array
1291 */
1292 protected $selectors = array();
1293
1294 /**
1295 * The number of selectors.
1296 * @var int
1297 */
1298 protected $count = 0;
1299
0663b535
SH
1300 /**
1301 * Is null if there are no selector, true if all selectors are basic and false otherwise.
1302 * @var bool|null
1303 */
1304 protected $isbasic = null;
1305
0e641c74
SH
1306 /**
1307 * Initialises a new CSS selector
1d1d807e 1308 * @return css_selector
0e641c74
SH
1309 */
1310 public static function init() {
1311 return new css_selector();
1312 }
1313
1314 /**
1315 * CSS selectors can only be created through the init method above.
1316 */
1317 protected function __construct() {}
1318
1319 /**
1320 * Adds a selector to the end of the current selector
1321 * @param string $selector
1322 */
1323 public function add($selector) {
1324 $selector = trim($selector);
1325 $count = 0;
1326 $count += preg_match_all('/(\.|#)/', $selector, $matchesarray);
1327 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
1328 $count ++;
1329 }
0663b535
SH
1330 if ($this->isbasic !== false) {
1331 if ($count > 1) {
1332 $this->isbasic = false;
1333 } else {
1334 $this->isbasic = (preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector))?true:false;
1335 }
1336 }
0e641c74
SH
1337 $this->count = $count;
1338 $this->selectors[] = $selector;
1339 }
1340 /**
1341 * Returns the number of individual components that make up this selector
1342 * @return int
1343 */
1344 public function get_selector_count() {
1345 return $this->count;
1346 }
1347
1348 /**
1349 * Returns the selector for use in a CSS rule
1350 * @return string
1351 */
1352 public function out() {
1d1d807e 1353 return css_writer::selector($this->selectors);
0e641c74 1354 }
0663b535
SH
1355
1356 /**
1357 * Returns true is all of the selectors act only upon basic elements (no classes/ids)
1358 * @return bool
1359 */
1360 public function is_basic() {
1361 return ($this->isbasic === true);
1362 }
0e641c74
SH
1363}
1364
1365/**
1366 * A structure to represent a CSS rule.
1367 *
f37f608e
SH
1368 * @package core_css
1369 * @category css
6bbd4858 1370 * @copyright 2012 Sam Hemelryk
f37f608e 1371 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
1372 */
1373class css_rule {
1374
1375 /**
1376 * An array of CSS selectors {@see css_selector}
1377 * @var array
1378 */
1379 protected $selectors = array();
1380
1381 /**
1382 * An array of CSS styles {@see css_style}
1383 * @var array
1384 */
1385 protected $styles = array();
1386
1387 /**
1388 * Created a new CSS rule. This is the only way to create a new CSS rule externally.
1389 * @return css_rule
1390 */
1391 public static function init() {
1392 return new css_rule();
1393 }
1394
1395 /**
6bbd4858 1396 * Constructs a new css rule.
0e641c74 1397 *
f37f608e
SH
1398 * @param string $selector The selector or array of selectors that make up this rule.
1399 * @param array $styles An array of styles that belong to this rule.
0e641c74
SH
1400 */
1401 protected function __construct($selector = null, array $styles = array()) {
1402 if ($selector != null) {
1403 if (is_array($selector)) {
1404 $this->selectors = $selector;
1405 } else {
1406 $this->selectors = array($selector);
1407 }
1408 $this->add_styles($styles);
1409 }
1410 }
1411
1412 /**
1413 * Adds a new CSS selector to this rule
1414 *
f37f608e
SH
1415 * e.g. $rule->add_selector('.one #two.two');
1416 *
1417 * @param css_selector $selector Adds a CSS selector to this rule.
0e641c74
SH
1418 */
1419 public function add_selector(css_selector $selector) {
1420 $this->selectors[] = $selector;
1421 }
1422
1423 /**
1424 * Adds a new CSS style to this rule.
1425 *
f37f608e 1426 * @param css_style|string $style Adds a new style to this rule
0e641c74
SH
1427 */
1428 public function add_style($style) {
1429 if (is_string($style)) {
1430 $style = trim($style);
1431 if (empty($style)) {
1432 return;
1433 }
1434 $bits = explode(':', $style, 2);
1435 if (count($bits) == 2) {
1436 list($name, $value) = array_map('trim', $bits);
1437 }
1438 if (isset($name) && isset($value) && $name !== '' && $value !== '') {
3c548abf 1439 $style = css_style::init_automatic($name, $value);
0e641c74 1440 }
1d1d807e 1441 } else if ($style instanceof css_style) {
f37f608e
SH
1442 // Clone the style as it may be coming from another rule and we don't
1443 // want references as it will likely be overwritten by proceeding
1444 // rules
1d1d807e 1445 $style = clone($style);
0e641c74
SH
1446 }
1447 if ($style instanceof css_style) {
1448 $name = $style->get_name();
e3036985
SH
1449 $exists = array_key_exists($name, $this->styles);
1450 // We need to find out if the current style support multiple values, or whether the style
1451 // is already set up to record multiple values. This can happen with background images which can have single
1452 // and multiple values.
1453 if ($style->allows_multiple_values() || ($exists && is_array($this->styles[$name]))) {
1454 if (!$exists) {
1455 $this->styles[$name] = array();
1456 } else if ($this->styles[$name] instanceof css_style) {
1457 $this->styles[$name] = array($this->styles[$name]);
1458 }
1459 $this->styles[$name][] = $style;
1460 } else if ($exists) {
0e641c74
SH
1461 $this->styles[$name]->set_value($style->get_value());
1462 } else {
1463 $this->styles[$name] = $style;
1464 }
1d1d807e 1465 } else if (is_array($style)) {
f37f608e
SH
1466 // We probably shouldn't worry about processing styles here but to
1467 // be truthful it doesn't hurt.
1d1d807e
SH
1468 foreach ($style as $astyle) {
1469 $this->add_style($astyle);
1470 }
0e641c74
SH
1471 }
1472 }
1473
1474 /**
1475 * An easy method of adding several styles at once. Just calls add_style.
1476 *
f37f608e
SH
1477 * This method simply iterates over the array and calls {@see css_rule::add_style()}
1478 * with each.
1479 *
1480 * @param array $styles Adds an array of styles
0e641c74
SH
1481 */
1482 public function add_styles(array $styles) {
1483 foreach ($styles as $style) {
1484 $this->add_style($style);
1485 }
1486 }
1487
0e641c74
SH
1488 /**
1489 * Returns the array of selectors
f37f608e 1490 *
0e641c74
SH
1491 * @return array
1492 */
1493 public function get_selectors() {
1494 return $this->selectors;
1495 }
1496
1497 /**
1498 * Returns the array of styles
f37f608e 1499 *
0e641c74
SH
1500 * @return array
1501 */
1502 public function get_styles() {
1503 return $this->styles;
1504 }
1505
1506 /**
1507 * Outputs this rule as a fragment of CSS
f37f608e 1508 *
0e641c74
SH
1509 * @return string
1510 */
1511 public function out() {
1d1d807e
SH
1512 $selectors = css_writer::selectors($this->selectors);
1513 $styles = css_writer::styles($this->get_consolidated_styles());
1514 return css_writer::rule($selectors, $styles);
1515 }
1516
0abd4846
SH
1517 /**
1518 * Consolidates all styles associated with this rule
1519 *
1520 * @return array An array of consolidated styles
1521 */
1d1d807e 1522 public function get_consolidated_styles() {
6a519fdc 1523 $organisedstyles = array();
1d1d807e
SH
1524 $finalstyles = array();
1525 $consolidate = array();
e3036985 1526 $advancedstyles = array();
1d1d807e 1527 foreach ($this->styles as $style) {
e3036985 1528 if (is_array($style)) {
3daafb92
SH
1529 $single = null;
1530 $count = 0;
1531 foreach ($style as $advstyle) {
1532 $key = $count++;
1533 $advancedstyles[$key] = $advstyle;
1534 if (!$advstyle->allows_multiple_values()) {
1535 if (!is_null($single)) {
1536 unset($advancedstyles[$single]);
1537 }
1538 $single = $key;
1539 }
1540 }
1541 if (!is_null($single)) {
1542 $style = $advancedstyles[$single];
1543
1544 $consolidatetoclass = $style->consolidate_to();
1545 if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
1546 $class = 'css_style_'.$consolidatetoclass;
1547 if (!array_key_exists($class, $consolidate)) {
1548 $consolidate[$class] = array();
1549 $organisedstyles[$class] = true;
1550 }
1551 $consolidate[$class][] = $style;
1552 unset($advancedstyles[$single]);
1553 }
1554 }
1555
e3036985
SH
1556 continue;
1557 }
1d1d807e 1558 $consolidatetoclass = $style->consolidate_to();
6a519fdc 1559 if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
1d1d807e
SH
1560 $class = 'css_style_'.$consolidatetoclass;
1561 if (!array_key_exists($class, $consolidate)) {
1562 $consolidate[$class] = array();
6a519fdc 1563 $organisedstyles[$class] = true;
1d1d807e
SH
1564 }
1565 $consolidate[$class][] = $style;
1566 } else {
6a519fdc 1567 $organisedstyles[$style->get_name()] = $style;
1d1d807e
SH
1568 }
1569 }
1570
1571 foreach ($consolidate as $class => $styles) {
6a519fdc
SH
1572 $organisedstyles[$class] = $class::consolidate($styles);
1573 }
1574
1575 foreach ($organisedstyles as $style) {
1576 if (is_array($style)) {
1577 foreach ($style as $s) {
1578 $finalstyles[] = $s;
1579 }
1580 } else {
1d1d807e
SH
1581 $finalstyles[] = $style;
1582 }
1583 }
e3036985 1584 $finalstyles = array_merge($finalstyles,$advancedstyles);
1d1d807e 1585 return $finalstyles;
0e641c74
SH
1586 }
1587
1588 /**
1589 * Splits this rules into an array of CSS rules. One for each of the selectors
1590 * that make up this rule.
1591 *
1592 * @return array(css_rule)
1593 */
1594 public function split_by_selector() {
1595 $return = array();
1596 foreach ($this->selectors as $selector) {
1597 $return[] = new css_rule($selector, $this->styles);
1598 }
1599 return $return;
1600 }
1601
1602 /**
1603 * Splits this rule into an array of rules. One for each of the styles that
1604 * make up this rule
1605 *
f37f608e 1606 * @return array Array of css_rule objects
0e641c74
SH
1607 */
1608 public function split_by_style() {
1609 $return = array();
1610 foreach ($this->styles as $style) {
e3036985
SH
1611 if (is_array($style)) {
1612 $return[] = new css_rule($this->selectors, $style);
1613 continue;
1614 }
0e641c74
SH
1615 $return[] = new css_rule($this->selectors, array($style));
1616 }
1617 return $return;
1618 }
1619
1620 /**
1621 * Gets a hash for the styles of this rule
f37f608e 1622 *
0e641c74
SH
1623 * @return string
1624 */
1625 public function get_style_hash() {
1d1d807e 1626 return md5(css_writer::styles($this->styles));
0e641c74
SH
1627 }
1628
1629 /**
1630 * Gets a hash for the selectors of this rule
f37f608e 1631 *
0e641c74
SH
1632 * @return string
1633 */
1634 public function get_selector_hash() {
1d1d807e 1635 return md5(css_writer::selectors($this->selectors));
0e641c74
SH
1636 }
1637
1638 /**
1639 * Gets the number of selectors that make up this rule.
f37f608e 1640 *
0e641c74
SH
1641 * @return int
1642 */
1643 public function get_selector_count() {
1644 $count = 0;
1645 foreach ($this->selectors as $selector) {
1646 $count += $selector->get_selector_count();
1647 }
1648 return $count;
1649 }
6bbd4858
SH
1650
1651 /**
1652 * Returns true if there are any errors with this rule.
1653 *
1654 * @return bool
1655 */
1656 public function has_errors() {
1657 foreach ($this->styles as $style) {
e3036985
SH
1658 if (is_array($style)) {
1659 foreach ($style as $advstyle) {
1660 if ($advstyle->has_error()) {
1661 return true;
1662 }
1663 }
1664 continue;
1665 }
6bbd4858
SH
1666 if ($style->has_error()) {
1667 return true;
1668 }
1669 }
1670 return false;
1671 }
1672
f37f608e
SH
1673 /**
1674 * Returns the error strings that were recorded when processing this rule.
1675 *
1676 * Before calling this function you should first call {@see css_rule::has_errors()}
1677 * to make sure there are errors (hopefully there arn't).
1678 *
1679 * @return string
1680 */
6bbd4858
SH
1681 public function get_error_string() {
1682 $css = $this->out();
1683 $errors = array();
1684 foreach ($this->styles as $style) {
1685 if ($style->has_error()) {
1686 $errors[] = " * ".$style->get_last_error();
1687 }
1688 }
1689 return $css." has the following errors:\n".join("\n", $errors);
6bbd4858 1690 }
0663b535
SH
1691
1692 /**
1693 * Returns true if this rule could be considered a reset rule.
1694 *
1695 * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector.
1696 *
1697 * @return bool
1698 */
1699 public function is_reset_rule() {
1700 foreach ($this->selectors as $selector) {
1701 if (!$selector->is_basic()) {
1702 return false;
1703 }
1704 }
1705 return true;
1706 }
0e641c74
SH
1707}
1708
1121f79b 1709abstract class css_rule_collection {
0e641c74 1710 /**
1121f79b 1711 * An array of rules within this collection instance
0e641c74
SH
1712 * @var array
1713 */
1714 protected $rules = array();
1715
1716 /**
1121f79b 1717 * The collection must be able to print itself.
0e641c74 1718 */
1121f79b 1719 abstract public function out();
0e641c74
SH
1720
1721 /**
1121f79b 1722 * Adds a new CSS rule to this collection instance
0e641c74
SH
1723 *
1724 * @param css_rule $newrule
1725 */
1726 public function add_rule(css_rule $newrule) {
1727 foreach ($newrule->split_by_selector() as $rule) {
1728 $hash = $rule->get_selector_hash();
1729 if (!array_key_exists($hash, $this->rules)) {
1730 $this->rules[$hash] = $rule;
1731 } else {
1732 $this->rules[$hash]->add_styles($rule->get_styles());
1733 }
1734 }
1735 }
1736
1737 /**
1121f79b 1738 * Returns the rules used by this collection
0e641c74
SH
1739 *
1740 * @return array
1741 */
1742 public function get_rules() {
1743 return $this->rules;
1744 }
1745
1746 /**
1747 * Organises rules by gropuing selectors based upon the styles and consolidating
1748 * those selectors into single rules.
1749 *
6bbd4858 1750 * @return bool True if the CSS was optimised by this method
0e641c74
SH
1751 */
1752 public function organise_rules_by_selectors() {
1753 $optimised = array();
1754 $beforecount = count($this->rules);
1755 foreach ($this->rules as $rule) {
1756 $hash = $rule->get_style_hash();
1757 if (!array_key_exists($hash, $optimised)) {
1758 $optimised[$hash] = clone($rule);
1759 } else {
1760 foreach ($rule->get_selectors() as $selector) {
1761 $optimised[$hash]->add_selector($selector);
1762 }
1763 }
1764 }
1765 $this->rules = $optimised;
1766 $aftercount = count($this->rules);
1767 return ($beforecount < $aftercount);
1768 }
1769
1770 /**
1121f79b 1771 * Returns the total number of rules that exist within this collection
0e641c74
SH
1772 *
1773 * @return int
1774 */
1775 public function count_rules() {
1776 return count($this->rules);
1777 }
1778
1779 /**
1121f79b 1780 * Returns the total number of selectors that exist within this collection
0e641c74
SH
1781 *
1782 * @return int
1783 */
1784 public function count_selectors() {
1785 $count = 0;
1786 foreach ($this->rules as $rule) {
1787 $count += $rule->get_selector_count();
1788 }
1789 return $count;
1790 }
1791
1121f79b
SH
1792 /**
1793 * Returns true if the collection has any rules that have errors
1794 *
1795 * @return boolean
1796 */
1797 public function has_errors() {
1798 foreach ($this->rules as $rule) {
1799 if ($rule->has_errors()) {
1800 return true;
1801 }
1802 }
1803 return false;
1804 }
1805
1806 /**
1807 * Returns any errors that have happened within rules in this collection.
1808 *
1809 * @return string
1810 */
1811 public function get_errors() {
1812 $errors = array();
1813 foreach ($this->rules as $rule) {
1814 if ($rule->has_errors()) {
1815 $errors[] = $rule->get_error_string();
1816 }
1817 }
1818 return $errors;
1819 }
1820}
1821
1822/**
1823 * A media class to organise rules by the media they apply to.
1824 *
1825 * @package core_css
1826 * @category css
1827 * @copyright 2012 Sam Hemelryk
1828 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1829 */
1830class css_media extends css_rule_collection {
1831
1832 /**
1833 * An array of the different media types this instance applies to.
1834 * @var array
1835 */
1836 protected $types = array();
1837
1838 /**
1839 * Initalises a new media instance
1840 *
1841 * @param string $for The media that the contained rules are destined for.
1842 */
1843 public function __construct($for = 'all') {
1844 $types = explode(',', $for);
1845 $this->types = array_map('trim', $types);
1846 }
1847
0e641c74
SH
1848 /**
1849 * Returns the CSS for this media and all of its rules.
1850 *
1851 * @return string
1852 */
1853 public function out() {
1d1d807e 1854 return css_writer::media(join(',', $this->types), $this->rules);
0e641c74
SH
1855 }
1856
1857 /**
1858 * Returns an array of media that this media instance applies to
1859 *
1860 * @return array
1861 */
1862 public function get_types() {
1863 return $this->types;
1864 }
0663b535
SH
1865
1866 /**
1867 * Returns all of the reset rules known by this media set.
1868 * @return array
1869 */
1870 public function get_reset_rules($remove = false) {
1871 $resetrules = array();
1872 foreach ($this->rules as $key => $rule) {
1873 if ($rule->is_reset_rule()) {
1874 $resetrules[] = clone $rule;
1875 if ($remove) {
1876 unset($this->rules[$key]);
1877 }
1878 }
1879 }
1880 return $resetrules;
1881 }
1121f79b
SH
1882}
1883
1884/**
1885 * A media class to organise rules by the media they apply to.
1886 *
1887 * @package core_css
1888 * @category css
1889 * @copyright 2012 Sam Hemelryk
1890 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1891 */
1892class css_keyframe extends css_rule_collection {
1893
1894 /** @var string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes */
1895 protected $for;
6bbd4858 1896
1121f79b
SH
1897 /** @var string $name The name for the keyframes */
1898 protected $name;
6bbd4858 1899 /**
1121f79b 1900 * Constructs a new keyframe
6bbd4858 1901 *
1121f79b
SH
1902 * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
1903 * @param string $name The name for the keyframes
6bbd4858 1904 */
1121f79b
SH
1905 public function __construct($for, $name) {
1906 $this->for = $for;
1907 $this->name = $name;
6bbd4858 1908 }
6bbd4858 1909 /**
1121f79b 1910 * Returns the directive of this keyframe
6bbd4858 1911 *
1121f79b 1912 * e.g. keyframes, -moz-keyframes, -webkit-keyframes
6bbd4858
SH
1913 * @return string
1914 */
1121f79b
SH
1915 public function get_for() {
1916 return $this->for;
1917 }
1918 /**
1919 * Returns the name of this keyframe
1920 * @return string
1921 */
1922 public function get_name() {
1923 return $this->name;
1924 }
1925 /**
1926 * Returns the CSS for this collection of keyframes and all of its rules.
1927 *
1928 * @return string
1929 */
1930 public function out() {
1931 return css_writer::keyframe($this->for, $this->name, $this->rules);
6bbd4858 1932 }
0e641c74
SH
1933}
1934
1935/**
1936 * An absract class to represent CSS styles
1937 *
f37f608e
SH
1938 * @package core_css
1939 * @category css
6bbd4858 1940 * @copyright 2012 Sam Hemelryk
f37f608e 1941 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
1942 */
1943abstract class css_style {
1944
6a519fdc
SH
1945 /** Constant used for recongise a special empty value in a CSS style */
1946 const NULL_VALUE = '@@$NULL$@@';
1947
0e641c74
SH
1948 /**
1949 * The name of the style
1950 * @var string
1951 */
1952 protected $name;
1953
1954 /**
1955 * The value for the style
1956 * @var mixed
1957 */
1958 protected $value;
1959
1960 /**
1961 * If set to true this style was defined with the !important rule.
f37f608e
SH
1962 * Only trolls use !important.
1963 * Don't hide under bridges.. its not good for your skin. Do the proper thing
1964 * and fix the issue don't just force a fix that will undoubtedly one day
1965 * lead to further frustration.
0e641c74
SH
1966 * @var bool
1967 */
1968 protected $important = false;
1969
6bbd4858
SH
1970 /**
1971 * Gets set to true if this style has an error
1972 * @var bool
1973 */
1974 protected $error = false;
1975
1976 /**
1977 * The last error message that occured
1978 * @var string
1979 */
1980 protected $errormessage = null;
1981
0e641c74
SH
1982 /**
1983 * Initialises a new style.
1984 *
1985 * This is the only public way to create a style to ensure they that appropriate
1986 * style class is used if it exists.
1987 *
f37f608e
SH
1988 * @param string $name The name of the style.
1989 * @param string $value The value of the style.
1d1d807e 1990 * @return css_style_generic
0e641c74 1991 */
3c548abf 1992 public static function init_automatic($name, $value) {
0e641c74
SH
1993 $specificclass = 'css_style_'.preg_replace('#[^a-zA-Z0-9]+#', '', $name);
1994 if (class_exists($specificclass)) {
1995 return $specificclass::init($value);
1996 }
1997 return new css_style_generic($name, $value);
1998 }
1999
2000 /**
2001 * Creates a new style when given its name and value
2002 *
f37f608e
SH
2003 * @param string $name The name of the style.
2004 * @param string $value The value of the style.
0e641c74
SH
2005 */
2006 protected function __construct($name, $value) {
2007 $this->name = $name;
2008 $this->set_value($value);
2009 }
2010
2011 /**
2012 * Sets the value for the style
2013 *
2014 * @param string $value
2015 */
2016 final public function set_value($value) {
2017 $value = trim($value);
2018 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
2019 if ($important) {
2020 $value = substr($value, 0, -(strlen($matches[1])));
f2e8d379 2021 $value = rtrim($value);
0e641c74
SH
2022 }
2023 if (!$this->important || $important) {
2024 $this->value = $this->clean_value($value);
2025 $this->important = $important;
2026 }
6bbd4858
SH
2027 if (!$this->is_valid()) {
2028 $this->set_error('Invalid value for '.$this->name);
2029 }
0e641c74
SH
2030 }
2031
0abd4846
SH
2032 /**
2033 * Returns true if the value associated with this style is valid
2034 *
2035 * @return bool
2036 */
50836f26
SH
2037 public function is_valid() {
2038 return true;
2039 }
2040
0e641c74
SH
2041 /**
2042 * Returns the name for the style
2043 *
2044 * @return string
2045 */
2046 public function get_name() {
2047 return $this->name;
2048 }
2049
2050 /**
2051 * Returns the value for the style
2052 *
2053 * @return string
2054 */
3daafb92 2055 public function get_value($includeimportant = true) {
0e641c74 2056 $value = $this->value;
3daafb92 2057 if ($includeimportant && $this->important) {
0e641c74
SH
2058 $value .= ' !important';
2059 }
2060 return $value;
2061 }
2062
2063 /**
2064 * Returns the style ready for use in CSS
2065 *
f37f608e 2066 * @param string|null $value A value to use to override the value for this style.
0e641c74
SH
2067 * @return string
2068 */
2069 public function out($value = null) {
1d1d807e 2070 if (is_null($value)) {
0e641c74 2071 $value = $this->get_value();
0e641c74 2072 }
1d1d807e 2073 return css_writer::style($this->name, $value, $this->important);
0e641c74
SH
2074 }
2075
2076 /**
2077 * This can be overridden by a specific style allowing it to clean its values
2078 * consistently.
2079 *
2080 * @param mixed $value
2081 * @return mixed
2082 */
2083 protected function clean_value($value) {
2084 return $value;
2085 }
1d1d807e 2086
0abd4846
SH
2087 /**
2088 * If this particular style can be consolidated into another style this function
2089 * should return the style that it can be consolidated into.
2090 *
2091 * @return string|null
2092 */
1d1d807e
SH
2093 public function consolidate_to() {
2094 return null;
2095 }
6bbd4858
SH
2096
2097 /**
2098 * Sets the last error message.
2099 *
ff8e5d47 2100 * @param string $message
6bbd4858
SH
2101 */
2102 protected function set_error($message) {
2103 $this->error = true;
2104 $this->errormessage = $message;
2105 }
2106
2107 /**
2108 * Returns true if an error has occured
f37f608e 2109 *
6bbd4858
SH
2110 * @return bool
2111 */
2112 public function has_error() {
2113 return $this->error;
2114 }
2115
2116 /**
2117 * Returns the last error that occured or null if no errors have happened.
2118 *
2119 * @return string
2120 */
2121 public function get_last_error() {
2122 return $this->errormessage;
2123 }
6a519fdc
SH
2124
2125 /**
2126 * Returns true if the value for this style is the special null value.
2127 *
2128 * This should only be overriden in circumstances where a shorthand style can lead
2129 * to move explicit styles being overwritten. Not a common place occurenace.
2130 *
2131 * Example:
2132 * This occurs if the shorthand background property was used but no proper value
2133 * was specified for this style.
2134 * This leads to a null value being used unless otherwise overridden.
2135 *
2136 * @return bool
2137 */
2138 public function is_special_empty_value() {
2139 return false;
2140 }
e3036985
SH
2141
2142 public function allows_multiple_values() {
2143 return false;
2144 }
3daafb92
SH
2145
2146 public function is_important() {
2147 return !empty($this->important);
2148 }
2149
2150 public function set_important($important = true) {
2151 $this->important = (bool) $important;
2152 }
0e641c74
SH
2153}
2154
2155/**
2156 * A generic CSS style class to use when a more specific class does not exist.
2157 *
f37f608e
SH
2158 * @package core_css
2159 * @category css
6bbd4858 2160 * @copyright 2012 Sam Hemelryk
f37f608e 2161 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
2162 */
2163class css_style_generic extends css_style {
f37f608e 2164
0e641c74
SH
2165 /**
2166 * Cleans incoming values for typical things that can be optimised.
2167 *
ff8e5d47 2168 * @param mixed $value Cleans the provided value optimising it if possible
0e641c74
SH
2169 * @return string
2170 */
2171 protected function clean_value($value) {
2172 if (trim($value) == '0px') {
2173 $value = 0;
2174 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) {
2175 $value = '#'.strtoupper($matches[1]);
2176 }
2177 return $value;
2178 }
2179}
2180
2181/**
2182 * A colour CSS style
2183 *
f37f608e
SH
2184 * @package core_css
2185 * @category css
6bbd4858 2186 * @copyright 2012 Sam Hemelryk
f37f608e 2187 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
2188 */
2189class css_style_color extends css_style {
f37f608e 2190
0e641c74
SH
2191 /**
2192 * Creates a new colour style
2193 *
f37f608e 2194 * @param mixed $value Initialises a new colour style
0e641c74
SH
2195 * @return css_style_color
2196 */
2197 public static function init($value) {
2198 return new css_style_color('color', $value);
2199 }
2200
2201 /**
2202 * Cleans the colour unifing it to a 6 char hash colour if possible
2203 * Doing this allows us to associate identical colours being specified in
2204 * different ways. e.g. Red, red, #F00, and #F00000
2205 *
ff8e5d47 2206 * @param mixed $value Cleans the provided value optimising it if possible
0e641c74
SH
2207 * @return string
2208 */
2209 protected function clean_value($value) {
2210 $value = trim($value);
ff8e5d47
SH
2211 if (css_is_colour($value)) {
2212 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) {
2213 $value = '#'.strtoupper($matches[1]);
2214 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) {
2215 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
2216 $value = '#'.strtoupper($value);
2217 } else if (array_key_exists(strtolower($value), css_optimiser::$htmlcolours)) {
2218 $value = css_optimiser::$htmlcolours[strtolower($value)];
2219 }
0e641c74
SH
2220 }
2221 return $value;
2222 }
2223
2224 /**
2225 * Returns the colour style for use within CSS.
2226 * Will return an optimised hash colour.
2227 *
2228 * e.g #123456
2229 * #123 instead of #112233
2230 * #F00 instead of red
2231 *
ff8e5d47
SH
2232 * @param string $overridevalue If provided then this value will be used instead
2233 * of the styles current value.
0e641c74
SH
2234 * @return string
2235 */
2236 public function out($overridevalue = null) {
50836f26
SH
2237 if ($overridevalue === null) {
2238 $overridevalue = $this->value;
2239 }
2240 return parent::out(self::shrink_value($overridevalue));
2241 }
2242
0abd4846
SH
2243 /**
2244 * Shrinks the colour value is possible.
2245 *
ff8e5d47 2246 * @param string $value Shrinks the current value to an optimial form if possible
0abd4846
SH
2247 * @return string
2248 */
50836f26
SH
2249 public static function shrink_value($value) {
2250 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) {
2251 return '#'.$matches[1].$matches[2].$matches[3];
0e641c74 2252 }
50836f26
SH
2253 return $value;
2254 }
2255
0abd4846
SH
2256 /**
2257 * Returns true if the value is a valid colour.
2258 *
2259 * @return bool
2260 */
50836f26
SH
2261 public function is_valid() {
2262 return css_is_colour($this->value);
0e641c74
SH
2263 }
2264}
2265
6bbd4858
SH
2266/**
2267 * A width style
2268 *
f37f608e
SH
2269 * @package core_css
2270 * @category css
6bbd4858 2271 * @copyright 2012 Sam Hemelryk
f37f608e 2272 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6bbd4858
SH
2273 */
2274class css_style_width extends css_style {
2275
2276 /**
2277 * Checks if the width is valid
2278 * @return bool
2279 */
2280 public function is_valid() {
2281 return css_is_width($this->value);
2282 }
2283
2284 /**
2285 * Cleans the provided value
2286 *
ff8e5d47 2287 * @param mixed $value Cleans the provided value optimising it if possible
6bbd4858
SH
2288 * @return string
2289 */
2290 protected function clean_value($value) {
2291 if (!css_is_width($value)) {
2292 // Note we don't actually change the value to something valid. That
2293 // would be bad for futureproofing.
2294 $this->set_error('Invalid width specified for '.$this->name);
2295 } else if (preg_match('#^0\D+$#', $value)) {
2296 $value = 0;
2297 }
2298 return trim($value);
2299 }
2300
2301 /**
2302 * Initialises a new width style
2303 *
ff8e5d47 2304 * @param mixed $value The value this style has
6bbd4858
SH
2305 * @return css_style_width
2306 */
2307 public static function init($value) {
2308 return new css_style_width('width', $value);
2309 }
2310}
2311
0abd4846
SH
2312/**
2313 * A margin style
2314 *
f37f608e
SH
2315 * @package core_css
2316 * @category css
6bbd4858 2317 * @copyright 2012 Sam Hemelryk
f37f608e 2318 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2319 */
6bbd4858 2320class css_style_margin extends css_style_width {
0abd4846
SH
2321
2322 /**
2323 * Initialises a margin style.
2324 *
2325 * In this case we split the margin into several other margin styles so that
2326 * we can properly condense overrides and then reconsolidate them later into
2327 * an optimal form.
2328 *
ff8e5d47 2329 * @param string $value The value the style has
0abd4846
SH
2330 * @return array An array of margin values that can later be consolidated
2331 */
1d1d807e 2332 public static function init($value) {
6bbd4858
SH
2333 $important = '';
2334 if (strpos($value, '!important') !== false) {
2335 $important = ' !important';
2336 $value = str_replace('!important', '', $value);
2337 }
2338
2339 $value = preg_replace('#\s+#', ' ', trim($value));
1d1d807e
SH
2340 $bits = explode(' ', $value, 4);
2341
2342 $top = $right = $bottom = $left = null;
2343 if (count($bits) > 0) {
2344 $top = $right = $bottom = $left = array_shift($bits);
2345 }
2346 if (count($bits) > 0) {
2347 $right = $left = array_shift($bits);
2348 }
2349 if (count($bits) > 0) {
2350 $bottom = array_shift($bits);
2351 }
2352 if (count($bits) > 0) {
2353 $left = array_shift($bits);
2354 }
2355 return array(
6bbd4858
SH
2356 new css_style_margintop('margin-top', $top.$important),
2357 new css_style_marginright('margin-right', $right.$important),
2358 new css_style_marginbottom('margin-bottom', $bottom.$important),
2359 new css_style_marginleft('margin-left', $left.$important)
1d1d807e
SH
2360 );
2361 }
0abd4846
SH
2362
2363 /**
2364 * Consolidates individual margin styles into a single margin style
2365 *
2366 * @param array $styles
2367 * @return array An array of consolidated styles
2368 */
1d1d807e
SH
2369 public static function consolidate(array $styles) {
2370 if (count($styles) != 4) {
2371 return $styles;
2372 }
3daafb92
SH
2373
2374 $someimportant = false;
2375 $allimportant = null;
2376 $notimportantequal = null;
2377 $firstvalue = null;
1d1d807e 2378 foreach ($styles as $style) {
3daafb92
SH
2379 if ($style->is_important()) {
2380 $someimportant = true;
2381 if ($allimportant === null) {
2382 $allimportant = true;
2383 }
2384 } else {
2385 if ($allimportant === true) {
2386 $allimportant = false;
2387 }
2388 if ($firstvalue == null) {
2389 $firstvalue = $style->get_value(false);
2390 $notimportantequal = true;
2391 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) {
2392 $notimportantequal = false;
2393 }
1d1d807e
SH
2394 }
2395 }
3daafb92
SH
2396
2397 if ($someimportant && !$allimportant && !$notimportantequal) {
2398 return $styles;
2399 }
2400
2401 if ($someimportant && !$allimportant && $notimportantequal) {
2402 $return = array(
2403 new css_style_margin('margin', $firstvalue)
2404 );
2405 foreach ($styles as $style) {
2406 if ($style->is_important()) {
2407 $return[] = $style;
2408 }
1d1d807e 2409 }
3daafb92 2410 return $return;
1d1d807e 2411 } else {
3daafb92
SH
2412 $top = null;
2413 $right = null;
2414 $bottom = null;
2415 $left = null;
2416 foreach ($styles as $style) {
2417 switch ($style->get_name()) {
2418 case 'margin-top' : $top = $style->get_value(false);break;
2419 case 'margin-right' : $right = $style->get_value(false);break;
2420 case 'margin-bottom' : $bottom = $style->get_value(false);break;
2421 case 'margin-left' : $left = $style->get_value(false);break;
2422 }
2423 }
2424 if ($top == $bottom && $left == $right) {
2425 if ($top == $left) {
2426 $returnstyle = new css_style_margin('margin', $top);
2427 } else {
2428 $returnstyle = new css_style_margin('margin', "{$top} {$left}");
2429 }
2430 } else if ($left == $right) {
2431 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom}");
2432 } else {
2433 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}");
2434 }
2435 if ($allimportant) {
2436 $returnstyle->set_important();
2437 }
2438 return array($returnstyle);
1d1d807e 2439 }
1d1d807e
SH
2440 }
2441}
2442
0abd4846
SH
2443/**
2444 * A margin top style
2445 *
f37f608e
SH
2446 * @package core_css
2447 * @category css
6bbd4858 2448 * @copyright 2012 Sam Hemelryk
f37f608e 2449 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2450 */
6bbd4858 2451class css_style_margintop extends css_style_margin {
f37f608e 2452
0abd4846
SH
2453 /**
2454 * A simple init, just a single style
2455 *
ff8e5d47 2456 * @param string $value The value the style has
0abd4846
SH
2457 * @return css_style_margintop
2458 */
1d1d807e
SH
2459 public static function init($value) {
2460 return new css_style_margintop('margin-top', $value);
2461 }
f37f608e 2462
0abd4846
SH
2463 /**
2464 * This style can be consolidated into a single margin style
2465 *
2466 * @return string
2467 */
1d1d807e
SH
2468 public function consolidate_to() {
2469 return 'margin';
2470 }
2471}
2472
0abd4846
SH
2473/**
2474 * A margin right style
2475 *
f37f608e
SH
2476 * @package core_css
2477 * @category css
6bbd4858 2478 * @copyright 2012 Sam Hemelryk
f37f608e 2479 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2480 */
6bbd4858
SH
2481class css_style_marginright extends css_style_margin {
2482
0abd4846
SH
2483 /**
2484 * A simple init, just a single style
2485 *
ff8e5d47 2486 * @param string $value The value the style has
0abd4846
SH
2487 * @return css_style_margintop
2488 */
1d1d807e
SH
2489 public static function init($value) {
2490 return new css_style_marginright('margin-right', $value);
2491 }
6bbd4858 2492
0abd4846
SH
2493 /**
2494 * This style can be consolidated into a single margin style
2495 *
2496 * @return string
2497 */
1d1d807e
SH
2498 public function consolidate_to() {
2499 return 'margin';
2500 }
2501}
2502
0abd4846
SH
2503/**
2504 * A margin bottom style
2505 *
f37f608e
SH
2506 * @package core_css
2507 * @category css
6bbd4858 2508 * @copyright 2012 Sam Hemelryk
f37f608e 2509 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2510 */
6bbd4858
SH
2511class css_style_marginbottom extends css_style_margin {
2512
0abd4846
SH
2513 /**
2514 * A simple init, just a single style
2515 *
ff8e5d47 2516 * @param string $value The value the style has
0abd4846
SH
2517 * @return css_style_margintop
2518 */
1d1d807e
SH
2519 public static function init($value) {
2520 return new css_style_marginbottom('margin-bottom', $value);
2521 }
6bbd4858 2522
0abd4846
SH
2523 /**
2524 * This style can be consolidated into a single margin style
2525 *
2526 * @return string
2527 */
1d1d807e
SH
2528 public function consolidate_to() {
2529 return 'margin';
2530 }
2531}
2532
0abd4846
SH
2533/**
2534 * A margin left style
2535 *
f37f608e
SH
2536 * @package core_css
2537 * @category css
6bbd4858 2538 * @copyright 2012 Sam Hemelryk
f37f608e 2539 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2540 */
6bbd4858
SH
2541class css_style_marginleft extends css_style_margin {
2542
0abd4846
SH
2543 /**
2544 * A simple init, just a single style
2545 *
ff8e5d47 2546 * @param string $value The value the style has
0abd4846
SH
2547 * @return css_style_margintop
2548 */
1d1d807e
SH
2549 public static function init($value) {
2550 return new css_style_marginleft('margin-left', $value);
2551 }
6bbd4858 2552
0abd4846
SH
2553 /**
2554 * This style can be consolidated into a single margin style
2555 *
2556 * @return string
2557 */
1d1d807e
SH
2558 public function consolidate_to() {
2559 return 'margin';
2560 }
2561}
2562
2563/**
2564 * A border style
2565 *
f37f608e
SH
2566 * @package core_css
2567 * @category css
6bbd4858 2568 * @copyright 2012 Sam Hemelryk
f37f608e 2569 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e
SH
2570 */
2571class css_style_border extends css_style {
6bbd4858 2572
0abd4846
SH
2573 /**
2574 * Initalises the border style into an array of individual style compontents
2575 *
ff8e5d47 2576 * @param string $value The value the style has
0abd4846
SH
2577 * @return css_style_bordercolor
2578 */
1d1d807e
SH
2579 public static function init($value) {
2580 $value = preg_replace('#\s+#', ' ', $value);
2581 $bits = explode(' ', $value, 3);
2582
2583 $return = array();
2584 if (count($bits) > 0) {
2585 $width = array_shift($bits);
f2e8d379 2586 if (!css_style_borderwidth::is_border_width($width)) {
6bbd4858
SH
2587 $width = '0';
2588 }
2589 $return[] = new css_style_borderwidth('border-top-width', $width);
2590 $return[] = new css_style_borderwidth('border-right-width', $width);
2591 $return[] = new css_style_borderwidth('border-bottom-width', $width);
2592 $return[] = new css_style_borderwidth('border-left-width', $width);
1d1d807e
SH
2593 }
2594 if (count($bits) > 0) {
2595 $style = array_shift($bits);
6bbd4858
SH
2596 $return[] = new css_style_borderstyle('border-top-style', $style);
2597 $return[] = new css_style_borderstyle('border-right-style', $style);
2598 $return[] = new css_style_borderstyle('border-bottom-style', $style);
2599 $return[] = new css_style_borderstyle('border-left-style', $style);
1d1d807e
SH
2600 }
2601 if (count($bits) > 0) {
2602 $colour = array_shift($bits);
6bbd4858
SH
2603 $return[] = new css_style_bordercolor('border-top-color', $colour);
2604 $return[] = new css_style_bordercolor('border-right-color', $colour);
2605 $return[] = new css_style_bordercolor('border-bottom-color', $colour);
2606 $return[] = new css_style_bordercolor('border-left-color', $colour);
1d1d807e
SH
2607 }
2608 return $return;
2609 }
6bbd4858 2610
0abd4846
SH
2611 /**
2612 * Consolidates all border styles into a single style
2613 *
ff8e5d47
SH
2614 * @param array $styles An array of border styles
2615 * @return array An optimised array of border styles
0abd4846 2616 */
1d1d807e
SH
2617 public static function consolidate(array $styles) {
2618
2619 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2620 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2621 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2622
2623 foreach ($styles as $style) {
2624 switch ($style->get_name()) {
6bbd4858
SH
2625 case 'border-top-width': $borderwidths['top'] = $style->get_value(); break;
2626 case 'border-right-width': $borderwidths['right'] = $style->get_value(); break;
2627 case 'border-bottom-width': $borderwidths['bottom'] = $style->get_value(); break;
2628 case 'border-left-width': $borderwidths['left'] = $style->get_value(); break;
2629
2630 case 'border-top-style': $borderstyles['top'] = $style->get_value(); break;
2631 case 'border-right-style': $borderstyles['right'] = $style->get_value(); break;
2632 case 'border-bottom-style': $borderstyles['bottom'] = $style->get_value(); break;
2633 case 'border-left-style': $borderstyles['left'] = $style->get_value(); break;
2634
3daafb92
SH
2635 case 'border-top-color': $bordercolors['top'] = css_style_color::shrink_value($style->get_value()); break;
2636 case 'border-right-color': $bordercolors['right'] = css_style_color::shrink_value($style->get_value()); break;
2637 case 'border-bottom-color': $bordercolors['bottom'] = css_style_color::shrink_value($style->get_value()); break;
2638 case 'border-left-color': $bordercolors['left'] = css_style_color::shrink_value($style->get_value()); break;
1d1d807e
SH
2639 }
2640 }
2641
2642 $uniquewidths = count(array_unique($borderwidths));
2643 $uniquestyles = count(array_unique($borderstyles));
2644 $uniquecolors = count(array_unique($bordercolors));
2645
6bbd4858
SH
2646 $nullwidths = in_array(null, $borderwidths, true);
2647 $nullstyles = in_array(null, $borderstyles, true);
2648 $nullcolors = in_array(null, $bordercolors, true);
1d1d807e 2649
50836f26
SH
2650 $allwidthsthesame = ($uniquewidths === 1)?1:0;
2651 $allstylesthesame = ($uniquestyles === 1)?1:0;
2652 $allcolorsthesame = ($uniquecolors === 1)?1:0;
1d1d807e
SH
2653
2654 $allwidthsnull = $allwidthsthesame && $nullwidths;
2655 $allstylesnull = $allstylesthesame && $nullstyles;
2656 $allcolorsnull = $allcolorsthesame && $nullcolors;
2657
2658 $return = array();
2659 if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
2660 // Everything is null still... boo
2661 return array(new css_style_border('border', ''));
2662
2663 } else if ($allwidthsnull && $allstylesnull) {
2664
2665 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2666 return $return;
2667
2668 } else if ($allwidthsnull && $allcolorsnull) {
2669
2670 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2671 return $return;
2672
2673 } else if ($allcolorsnull && $allstylesnull) {
2674
2675 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2676 return $return;
2677
2678 }
2679
2680 if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 3) {
2681
2682 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']);
0abd4846 2683
1d1d807e
SH
2684 } else if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 2) {
2685
2686 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) {
2687
2688 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']);
2689 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
0abd4846 2690
1d1d807e
SH
2691 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) {
2692
2693 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']);
2694 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
0abd4846 2695
1d1d807e
SH
2696 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) {
2697
2698 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']);
2699 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2700
2701 } else {
2702 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2703 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2704 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2705 }
2706
50836f26 2707 } 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
2708 $widthkeys = array();
2709 $stylekeys = array();
2710 $colorkeys = array();
2711
2712 foreach ($borderwidths as $key => $value) {
2713 if (!array_key_exists($value, $widthkeys)) {
2714 $widthkeys[$value] = array();
2715 }
2716 $widthkeys[$value][] = $key;
2717 }
2718 usort($widthkeys, 'css_sort_by_count');
2719 $widthkeys = array_values($widthkeys);
2720
2721 foreach ($borderstyles as $key => $value) {
2722 if (!array_key_exists($value, $stylekeys)) {
2723 $stylekeys[$value] = array();
2724 }
2725 $stylekeys[$value][] = $key;
2726 }
2727 usort($stylekeys, 'css_sort_by_count');
2728 $stylekeys = array_values($stylekeys);
2729
2730 foreach ($bordercolors as $key => $value) {
2731 if (!array_key_exists($value, $colorkeys)) {
2732 $colorkeys[$value] = array();
2733 }
2734 $colorkeys[$value][] = $key;
2735 }
2736 usort($colorkeys, 'css_sort_by_count');
2737 $colorkeys = array_values($colorkeys);
2738
2739 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
2740 $key = $widthkeys[0][0];
50836f26 2741 self::build_style_string($return, 'css_style_border', 'border', $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
1d1d807e 2742 $key = $widthkeys[1][0];
50836f26 2743 self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key, $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
1d1d807e
SH
2744 } else {
2745 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2746 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2747 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2748 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2749 }
2750 } else {
2751 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2752 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2753 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2754 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2755 }
2756 foreach ($return as $key => $style) {
2757 if ($style->get_value() == '') {
2758 unset($return[$key]);
2759 }
2760 }
2761 return $return;
2762 }
6bbd4858 2763
0abd4846
SH
2764 /**
2765 * Border styles get consolidated to a single border style.
2766 *
2767 * @return string
2768 */
1d1d807e
SH
2769 public function consolidate_to() {
2770 return 'border';
2771 }
6bbd4858 2772
0abd4846 2773 /**
f37f608e
SH
2774 * Consolidates a series of border styles into an optimised array of border
2775 * styles by looking at the direction of the border and prioritising that
2776 * during the optimisation.
0abd4846
SH
2777 *
2778 * @param array $array An array to add styles into during consolidation. Passed by reference.
2779 * @param string $class The class type to initalise
2780 * @param string $style The style to create
2781 * @param string|array $top The top value
2782 * @param string $right The right value
2783 * @param string $bottom The bottom value
2784 * @param string $left The left value
2785 * @return bool
2786 */
1d1d807e 2787 public static function consolidate_styles_by_direction(&$array, $class, $style, $top, $right = null, $bottom = null, $left = null) {
1d1d807e
SH
2788 if (is_array($top)) {
2789 $right = $top['right'];
2790 $bottom = $top['bottom'];
2791 $left = $top['left'];
2792 $top = $top['top'];
2793 }
2794
2795 if ($top == $bottom && $left == $right && $top == $left) {
6bbd4858 2796 if (is_null($top)) {
1d1d807e
SH
2797 $array[] = new $class($style, '');
2798 } else {
2799 $array[] = new $class($style, $top);
2800 }
2801 } else if ($top == null || $right == null || $bottom == null || $left == null) {
2802 if ($top !== null) {
6bbd4858 2803 $array[] = new $class(str_replace('border-', 'border-top-', $style), $top);
1d1d807e
SH
2804 }
2805 if ($right !== null) {
6bbd4858 2806 $array[] = new $class(str_replace('border-', 'border-right-', $style), $right);
1d1d807e
SH
2807 }
2808 if ($bottom !== null) {
6bbd4858 2809 $array[] = new $class(str_replace('border-', 'border-bottom-', $style), $bottom);
1d1d807e
SH
2810 }
2811 if ($left !== null) {
6bbd4858 2812 $array[] = new $class(str_replace('border-', 'border-left-', $style), $left);
1d1d807e
SH
2813 }
2814 } else if ($top == $bottom && $left == $right) {
2815 $array[] = new $class($style, $top.' '.$right);
2816 } else if ($left == $right) {
2817 $array[] = new $class($style, $top.' '.$right.' '.$bottom);
2818 } else {
2819 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left);
2820 }
2821 return true;
2822 }
0abd4846
SH
2823
2824 /**
2825 * Builds a border style for a set of width, style, and colour values
2826 *
2827 * @param array $array An array into which the generated style is added
2828 * @param string $class The class type to initialise
2829 * @param string $cssstyle The style to use
2830 * @param string $width The width of the border
2831 * @param string $style The style of the border
2832 * @param string $color The colour of the border
2833 * @return bool
2834 */
1d1d807e
SH
2835 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) {
2836 if (!is_null($width) && !is_null($style) && !is_null($color)) {
2837 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color);
2838 } else if (!is_null($width) && !is_null($style) && is_null($color)) {
2839 $array[] = new $class($cssstyle, $width.' '.$style);
2840 } else if (!is_null($width) && is_null($style) && is_null($color)) {
6bbd4858 2841 $array[] = new $class($cssstyle, $width);
1d1d807e 2842 } else {
6bbd4858
SH
2843 if (!is_null($width)) $array[] = new $class($cssstyle, $width);
2844 if (!is_null($style)) $array[] = new $class($cssstyle, $style);
2845 if (!is_null($color)) $array[] = new $class($cssstyle, $color);
1d1d807e
SH
2846 }
2847 return true;
2848 }
2849}
2850
1d1d807e
SH
2851/**
2852 * A border colour style
2853 *
f37f608e
SH
2854 * @package core_css
2855 * @category css
6bbd4858 2856 * @copyright 2012 Sam Hemelryk
f37f608e 2857 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e
SH
2858 */
2859class css_style_bordercolor extends css_style_color {
6bbd4858 2860
1d1d807e
SH
2861 /**
2862 * Creates a new border colour style
2863 *
2864 * Based upon the colour style
2865 *
2866 * @param mixed $value
ff8e5d47 2867 * @return Array of css_style_bordercolor
1d1d807e
SH
2868 */
2869 public static function init($value) {
2870 $value = preg_replace('#\s+#', ' ', $value);
2871 $bits = explode(' ', $value, 4);
2872
2873 $top = $right = $bottom = $left = null;
2874 if (count($bits) > 0) {
2875 $top = $right = $bottom = $left = array_shift($bits);
2876 }
2877 if (count($bits) > 0) {
2878 $right = $left = array_shift($bits);
2879 }
2880 if (count($bits) > 0) {
2881 $bottom = array_shift($bits);
2882 }
2883 if (count($bits) > 0) {
2884 $left = array_shift($bits);
2885 }
2886 return array(
6bbd4858
SH
2887 css_style_bordertopcolor::init($top),
2888 css_style_borderrightcolor::init($right),
2889 css_style_borderbottomcolor::init($bottom),
2890 css_style_borderleftcolor::init($left)
1d1d807e
SH
2891 );
2892 }
6bbd4858 2893
0abd4846
SH
2894 /**
2895 * Consolidate this to a single border style
2896 *
2897 * @return string
2898 */
1d1d807e
SH
2899 public function consolidate_to() {
2900 return 'border';
2901 }
6bbd4858 2902
0abd4846
SH
2903 /**
2904 * Cleans the value
2905 *
ff8e5d47 2906 * @param string $value Cleans the provided value optimising it if possible
0abd4846
SH
2907 * @return string
2908 */
1d1d807e
SH
2909 protected function clean_value($value) {
2910 $values = explode(' ', $value);
2911 $values = array_map('parent::clean_value', $values);
2912 return join (' ', $values);
2913 }
6bbd4858 2914
0abd4846
SH
2915 /**
2916 * Outputs this style
2917 *
2918 * @param string $overridevalue
2919 * @return string
2920 */
50836f26
SH
2921 public function out($overridevalue = null) {
2922 if ($overridevalue === null) {
2923 $overridevalue = $this->value;
2924 }
2925 $values = explode(' ', $overridevalue);
2926 $values = array_map('css_style_color::shrink_value', $values);
2927 return parent::out(join (' ', $values));
2928 }
1d1d807e
SH
2929}
2930
0abd4846
SH
2931/**
2932 * A border left style
2933 *
f37f608e
SH
2934 * @package core_css
2935 * @category css
6bbd4858 2936 * @copyright 2012 Sam Hemelryk
f37f608e 2937 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2938 */
1d1d807e 2939class css_style_borderleft extends css_style_generic {
6bbd4858 2940
0abd4846
SH
2941 /**
2942 * Initialises the border left style into individual components
2943 *
2944 * @param string $value
ff8e5d47 2945 * @return array Array of css_style_borderleftwidth|css_style_borderleftstyle|css_style_borderleftcolor
0abd4846 2946 */
1d1d807e
SH
2947 public static function init($value) {
2948 $value = preg_replace('#\s+#', ' ', $value);
2949 $bits = explode(' ', $value, 3);
2950
2951 $return = array();
2952 if (count($bits) > 0) {
6bbd4858 2953 $return[] = css_style_borderleftwidth::init(array_shift($bits));
1d1d807e
SH
2954 }
2955 if (count($bits) > 0) {
6bbd4858 2956 $return[] = css_style_borderleftstyle::init(array_shift($bits));
1d1d807e
SH
2957 }
2958 if (count($bits) > 0) {
6bbd4858 2959 $return[] = css_style_borderleftcolor::init(array_shift($bits));
1d1d807e
SH
2960 }
2961 return $return;
2962 }
6bbd4858 2963
0abd4846
SH
2964 /**
2965 * Consolidate this to a single border style
2966 *
2967 * @return string
2968 */
1d1d807e
SH
2969 public function consolidate_to() {
2970 return 'border';
2971 }
2972}
2973
0abd4846
SH
2974/**
2975 * A border right style
2976 *
f37f608e
SH
2977 * @package core_css
2978 * @category css
6bbd4858 2979 * @copyright 2012 Sam Hemelryk
f37f608e 2980 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2981 */
1d1d807e 2982class css_style_borderright extends css_style_generic {
6bbd4858 2983
0abd4846
SH
2984 /**
2985 * Initialises the border right style into individual components
2986 *
ff8e5d47
SH
2987 * @param string $value The value of the style
2988 * @return array Array of css_style_borderrightwidth|css_style_borderrightstyle|css_style_borderrightcolor
0abd4846 2989 */
1d1d807e
SH
2990 public static function init($value) {
2991 $value = preg_replace('#\s+#', ' ', $value);
2992 $bits = explode(' ', $value, 3);
2993
2994 $return = array();
2995 if (count($bits) > 0) {
6bbd4858 2996 $return[] = css_style_borderrightwidth::init(array_shift($bits));
1d1d807e
SH
2997 }
2998 if (count($bits) > 0) {
6bbd4858 2999 $return[] = css_style_borderrightstyle::init(array_shift($bits));
1d1d807e
SH
3000 }
3001 if (count($bits) > 0) {
6bbd4858 3002 $return[] = css_style_borderrightcolor::init(array_shift($bits));
1d1d807e
SH
3003 }
3004 return $return;
3005 }
6bbd4858 3006
0abd4846
SH
3007 /**
3008 * Consolidate this to a single border style
3009 *
3010 * @return string
3011 */
1d1d807e
SH
3012 public function consolidate_to() {
3013 return 'border';
3014 }
3015}
3016
0abd4846
SH
3017/**
3018 * A border top style
3019 *
f37f608e
SH
3020 * @package core_css
3021 * @category css
6bbd4858 3022 * @copyright 2012 Sam Hemelryk
f37f608e 3023 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3024 */
1d1d807e 3025class css_style_bordertop extends css_style_generic {
6bbd4858 3026
0abd4846
SH
3027 /**
3028 * Initialises the border top style into individual components
3029 *
ff8e5d47
SH
3030 * @param string $value The value of the style
3031 * @return array Array of css_style_bordertopwidth|css_style_bordertopstyle|css_style_bordertopcolor
0abd4846 3032 */
1d1d807e
SH
3033 public static function init($value) {
3034 $value = preg_replace('#\s+#', ' ', $value);
3035 $bits = explode(' ', $value, 3);
3036
3037 $return = array();
3038 if (count($bits) > 0) {
6bbd4858 3039 $return[] = css_style_bordertopwidth::init(array_shift($bits));
1d1d807e
SH
3040 }
3041 if (count($bits) > 0) {
6bbd4858 3042 $return[] = css_style_bordertopstyle::init(array_shift($bits));
1d1d807e
SH
3043 }
3044 if (count($bits) > 0) {
6bbd4858 3045 $return[] = css_style_bordertopcolor::init(array_shift($bits));
1d1d807e
SH
3046 }
3047 return $return;
3048 }
6bbd4858 3049
0abd4846
SH
3050 /**
3051 * Consolidate this to a single border style
3052 *
3053 * @return string
3054 */
1d1d807e
SH
3055 public function consolidate_to() {
3056 return 'border';
3057 }
3058}
3059
0abd4846
SH
3060/**
3061 * A border bottom style
3062 *
f37f608e
SH
3063 * @package core_css
3064 * @category css
6bbd4858 3065 * @copyright 2012 Sam Hemelryk
f37f608e 3066 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3067 */
1d1d807e 3068class css_style_borderbottom extends css_style_generic {
6bbd4858 3069
0abd4846
SH
3070 /**
3071 * Initialises the border bottom style into individual components
3072 *
ff8e5d47
SH
3073 * @param string $value The value of the style
3074 * @return array Array of css_style_borderbottomwidth|css_style_borderbottomstyle|css_style_borderbottomcolor
0abd4846 3075 */
1d1d807e
SH
3076 public static function init($value) {
3077 $value = preg_replace('#\s+#', ' ', $value);
3078 $bits = explode(' ', $value, 3);
3079
3080 $return = array();
3081 if (count($bits) > 0) {
6bbd4858 3082 $return[] = css_style_borderbottomwidth::init(array_shift($bits));
1d1d807e
SH
3083 }
3084 if (count($bits) > 0) {
6bbd4858 3085 $return[] = css_style_borderbottomstyle::init(array_shift($bits));
1d1d807e
SH
3086 }
3087 if (count($bits) > 0) {
6bbd4858 3088 $return[] = css_style_borderbottomcolor::init(array_shift($bits));
1d1d807e
SH
3089 }
3090 return $return;
3091 }
6bbd4858 3092
0abd4846
SH
3093 /**
3094 * Consolidate this to a single border style
3095 *
3096 * @return string
3097 */
1d1d807e
SH
3098 public function consolidate_to() {
3099 return 'border';
3100 }
3101}
3102
3103/**
3104 * A border width style
3105 *
f37f608e
SH
3106 * @package core_css
3107 * @category css
6bbd4858 3108 * @copyright 2012 Sam Hemelryk
f37f608e 3109 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e 3110 */
6bbd4858
SH
3111class css_style_borderwidth extends css_style_width {
3112
1d1d807e
SH
3113 /**
3114 * Creates a new border colour style
3115 *
3116 * Based upon the colour style
3117 *
ff8e5d47
SH
3118 * @param string $value The value of the style
3119 * @return array Array of css_style_border*width
1d1d807e
SH
3120 */
3121 public static function init($value) {
3122 $value = preg_replace('#\s+#', ' ', $value);
3123 $bits = explode(' ', $value, 4);
3124
3125 $top = $right = $bottom = $left = null;
3126 if (count($bits) > 0) {
3127 $top = $right = $bottom = $left = array_shift($bits);
3128 }
3129 if (count($bits) > 0) {
3130 $right = $left = array_shift($bits);
3131 }
3132 if (count($bits) > 0) {
3133 $bottom = array_shift($bits);
3134 }
3135 if (count($bits) > 0) {
3136 $left = array_shift($bits);
3137 }
3138 return array(
6bbd4858
SH
3139 css_style_bordertopwidth::init($top),
3140 css_style_borderrightwidth::init($right),
3141 css_style_borderbottomwidth::init($bottom),
3142 css_style_borderleftwidth::init($left)
1d1d807e
SH
3143 );
3144 }
6bbd4858 3145
0abd4846
SH
3146 /**
3147 * Consolidate this to a single border style
3148 *
3149 * @return string
3150 */
1d1d807e
SH
3151 public function consolidate_to() {
3152 return 'border';
3153 }
f2e8d379
SH
3154
3155 /**
3156 * Checks if the width is valid
3157 * @return bool
3158 */
3159 public function is_valid() {
3160 return self::is_border_width($this->value);
3161 }
3162
3163 /**
3164 * Cleans the provided value
3165 *
3166 * @param mixed $value Cleans the provided value optimising it if possible
3167 * @return string
3168 */
3169 protected function clean_value($value) {
3170 $isvalid = self::is_border_width($value);
3171 if (!$isvalid) {
3172 $this->set_error('Invalid width specified for '.$this->name);
3173 } else if (preg_match('#^0\D+$#', $value)) {
3174 return '0';
3175 }
3176 return trim($value);
3177 }
3178
3179 public static function is_border_width($value) {
3180 $altwidthvalues = array('thin', 'medium', 'thick');
3181 return css_is_width($value) || in_array($value, $altwidthvalues);
3182 }
1d1d807e
SH
3183}
3184
3185/**
3186 * A border style style
3187 *
f37f608e
SH
3188 * @package core_css
3189 * @category css
6bbd4858 3190 * @copyright 2012 Sam Hemelryk
f37f608e 3191 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e
SH
3192 */
3193class css_style_borderstyle extends css_style_generic {
6bbd4858 3194
1d1d807e
SH
3195 /**
3196 * Creates a new border colour style
3197 *
3198 * Based upon the colour style
3199 *
ff8e5d47
SH
3200 * @param string $value The value of the style
3201 * @return array Array of css_style_border*style
1d1d807e
SH
3202 */
3203 public static function init($value) {
3204 $value = preg_replace('#\s+#', ' ', $value);
3205 $bits = explode(' ', $value, 4);
3206
3207 $top = $right = $bottom = $left = null;
3208 if (count($bits) > 0) {
3209 $top = $right = $bottom = $left = array_shift($bits);
3210 }
3211 if (count($bits) > 0) {
3212 $right = $left = array_shift($bits);
3213 }
3214 if (count($bits) > 0) {
3215 $bottom = array_shift($bits);
3216 }
3217 if (count($bits) > 0) {
3218 $left = array_shift($bits);
3219 }
3220 return array(
6bbd4858
SH
3221 css_style_bordertopstyle::init($top),
3222 css_style_borderrightstyle::init($right),
3223 css_style_borderbottomstyle::init($bottom),
3224 css_style_borderleftstyle::init($left)
1d1d807e
SH
3225 );
3226 }
6bbd4858 3227
0abd4846
SH
3228 /**
3229 * Consolidate this to a single border style
3230 *
3231 * @return string
3232 */
1d1d807e
SH
3233 public function consolidate_to() {
3234 return 'border';
3235 }
3236}
3237
0abd4846
SH
3238/**
3239 * A border top colour style
3240 *
f37f608e
SH
3241 * @package core_css
3242 * @category css
6bbd4858 3243 * @copyright 2012 Sam Hemelryk
f37f608e 3244 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3245 */
6bbd4858
SH
3246class css_style_bordertopcolor extends css_style_bordercolor {
3247
0abd4846
SH
3248 /**
3249 * Initialises this style object
3250 *
ff8e5d47 3251 * @param string $value The value of the style
6bbd4858 3252 * @return css_style_bordertopcolor
0abd4846 3253 */
50836f26 3254 public static function init($value) {
6bbd4858 3255 return new css_style_bordertopcolor('border-top-color', $value);
50836f26 3256 }
6bbd4858 3257
0abd4846
SH
3258 /**
3259 * Consolidate this to a single border style
3260 *
3261 * @return string
3262 */
50836f26
SH
3263 public function consolidate_to() {
3264 return 'border';
3265 }
3266}
0abd4846
SH
3267
3268/**
3269 * A border left colour style
3270 *
f37f608e
SH
3271 * @package core_css
3272 * @category css
6bbd4858 3273 * @copyright 2012 Sam Hemelryk
f37f608e 3274 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3275 */
6bbd4858
SH
3276class css_style_borderleftcolor extends css_style_bordercolor {
3277
0abd4846
SH
3278 /**
3279 * Initialises this style object
3280 *
ff8e5d47 3281 * @param string $value The value of the style
6bbd4858 3282 * @return css_style_borderleftcolor
0abd4846 3283 */
50836f26 3284 public static function init($value) {
6bbd4858 3285 return new css_style_borderleftcolor('border-left-color', $value);
50836f26 3286 }
6bbd4858 3287
0abd4846
SH
3288 /**
3289 * Consolidate this to a single border style
3290 *
3291 * @return string
3292 */
50836f26
SH
3293 public function consolidate_to() {
3294 return 'border';
3295 }
3296}
0abd4846
SH
3297
3298/**
3299 * A border right colour style
3300 *
f37f608e
SH
3301 * @package core_css
3302 * @category css
6bbd4858 3303 * @copyright 2012 Sam Hemelryk
f37f608e 3304 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3305 */
6bbd4858
SH
3306class css_style_borderrightcolor extends css_style_bordercolor {
3307
0abd4846
SH
3308 /**
3309 * Initialises this style object
3310 *
ff8e5d47 3311 * @param string $value The value of the style
6bbd4858 3312 * @return css_style_borderrightcolor
0abd4846 3313 */
50836f26 3314 public static function init($value) {
6bbd4858 3315 return new css_style_borderrightcolor('border-right-color', $value);
50836f26 3316 }
6bbd4858 3317
0abd4846
SH
3318 /**
3319 * Consolidate this to a single border style
3320 *
3321 * @return string
3322 */
50836f26
SH
3323 public function consolidate_to() {
3324 return 'border';
3325 }
3326}
0abd4846
SH
3327
3328/**
3329 * A border bottom colour style
3330 *
f37f608e
SH
3331 * @package core_css
3332 * @category css
6bbd4858 3333 * @copyright 2012 Sam Hemelryk
f37f608e 3334 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3335 */
6bbd4858
SH
3336class css_style_borderbottomcolor extends css_style_bordercolor {
3337
0abd4846
SH
3338 /**
3339 * Initialises this style object
3340 *
ff8e5d47 3341 * @param string $value The value of the style
6bbd4858 3342 * @return css_style_borderbottomcolor
0abd4846 3343 */
50836f26 3344 public static function init($value) {
6bbd4858 3345 return new css_style_borderbottomcolor('border-bottom-color', $value);
50836f26 3346 }
6bbd4858 3347
0abd4846
SH
3348 /**
3349 * Consolidate this to a single border style
3350 *
3351 * @return string
3352 */
50836f26
SH
3353 public function consolidate_to() {
3354 return 'border';
3355 }
3356}
3357
0abd4846
SH
3358/**
3359 * A border width top style
3360 *
f37f608e
SH
3361 * @package core_css
3362 * @category css
6bbd4858 3363 * @copyright 2012 Sam Hemelryk
f37f608e 3364 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3365 */
6bbd4858
SH
3366class css_style_bordertopwidth extends css_style_borderwidth {
3367
0abd4846
SH
3368 /**
3369 * Initialises this style object
3370 *
ff8e5d47 3371 * @param string $value The value of the style
6bbd4858 3372 * @return css_style_bordertopwidth
0abd4846 3373 */
50836f26 3374 public static function init($value) {
6bbd4858 3375 return new css_style_bordertopwidth('border-top-width', $value);
50836f26 3376 }
6bbd4858 3377
0abd4846
SH
3378 /**
3379 * Consolidate this to a single border style
3380 *
3381 * @return string
3382 */
50836f26
SH
3383 public function consolidate_to() {
3384 return 'border';
3385 }
3386}
0abd4846
SH
3387
3388/**
3389 * A border width left style
3390 *
f37f608e
SH
3391 * @package core_css
3392 * @category css
6bbd4858 3393 * @copyright 2012 Sam Hemelryk
f37f608e 3394 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3395 */
6bbd4858
SH
3396class css_style_borderleftwidth extends css_style_borderwidth {
3397
0abd4846
SH
3398 /**
3399 * Initialises this style object
3400 *
ff8e5d47 3401 * @param string $value The value of the style
6bbd4858 3402 * @return css_style_borderleftwidth
0abd4846 3403 */
50836f26 3404 public static function init($value) {
6bbd4858 3405 return new css_style_borderleftwidth('border-left-width', $value);
50836f26 3406 }
6bbd4858 3407
0abd4846
SH
3408 /**
3409 * Consolidate this to a single border style
3410 *
3411 * @return string
3412 */
50836f26
SH
3413 public function consolidate_to() {
3414 return 'border';
3415 }
3416}
0abd4846
SH
3417
3418/**
3419 * A border width right style
3420 *
f37f608e
SH
3421 * @package core_css
3422 * @category css
6bbd4858 3423 * @copyright 2012 Sam Hemelryk
f37f608e 3424 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3425 */
6bbd4858
SH
3426class css_style_borderrightwidth extends css_style_borderwidth {
3427
0abd4846
SH
3428 /**
3429 * Initialises this style object
3430 *
ff8e5d47 3431 * @param string $value The value of the style
6bbd4858 3432 * @return css_style_borderrightwidth
0abd4846 3433 */
50836f26 3434 public static function init($value) {
6bbd4858 3435 return new css_style_borderrightwidth('border-right-width', $value);
50836f26 3436 }
6bbd4858 3437
0abd4846
SH
3438 /**
3439 * Consolidate this to a single border style
3440 *
3441 * @return string
3442 */
50836f26
SH
3443 public function consolidate_to() {
3444 return 'border';
3445 }
3446}
0abd4846
SH
3447
3448/**
3449 * A border width bottom style
3450 *
f37f608e
SH
3451 * @package core_css
3452 * @category css
6bbd4858 3453 * @copyright 2012 Sam Hemelryk
f37f608e 3454 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3455 */
6bbd4858
SH
3456class css_style_borderbottomwidth extends css_style_borderwidth {
3457
0abd4846
SH
3458 /**
3459 * Initialises this style object
3460 *
ff8e5d47 3461 * @param string $value The value of the style
6bbd4858 3462 * @return css_style_borderbottomwidth
0abd4846 3463 */
50836f26 3464 public static function init($value) {
6bbd4858 3465 return new css_style_borderbottomwidth('border-bottom-width', $value);
50836f26 3466 }
6bbd4858 3467
0abd4846
SH
3468 /**
3469 * Consolidate this to a single border style
3470 *
3471 * @return string
3472 */
50836f26
SH
3473 public function consolidate_to() {
3474 return 'border';
3475 }
3476}
3477
0abd4846
SH
3478/**
3479 * A border top style
3480 *
f37f608e
SH
3481 * @package core_css
3482 * @category css
6bbd4858 3483 * @copyright 2012 Sam Hemelryk
f37f608e 3484 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3485 */
6bbd4858
SH
3486class css_style_bordertopstyle extends css_style_borderstyle {
3487
0abd4846
SH
3488 /**
3489 * Initialises this style object
3490 *
ff8e5d47 3491 * @param string $value The value of the style
6bbd4858 3492 * @return css_style_bordertopstyle
0abd4846 3493 */
50836f26 3494 public static function init($value) {
6bbd4858 3495 return new css_style_bordertopstyle('border-top-style', $value);
50836f26 3496 }
6bbd4858 3497
0abd4846
SH
3498 /**
3499 * Consolidate this to a single border style
3500 *
3501 * @return string
3502 */
50836f26
SH
3503 public function consolidate_to() {
3504 return 'border';
3505 }
3506}
0abd4846
SH
3507
3508/**
3509 * A border left style
3510 *
f37f608e
SH
3511 * @package core_css
3512 * @category css
6bbd4858 3513 * @copyright 2012 Sam Hemelryk
f37f608e 3514 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3515 */
6bbd4858
SH
3516class css_style_borderleftstyle extends css_style_borderstyle {
3517
0abd4846
SH
3518 /**
3519 * Initialises this style object
3520 *
ff8e5d47 3521 * @param string $value The value of the style
6bbd4858 3522 * @return css_style_borderleftstyle
0abd4846 3523 */
50836f26 3524 public static function init($value) {
6bbd4858 3525 return new css_style_borderleftstyle('border-left-style', $value);
50836f26 3526 }
6bbd4858 3527
0abd4846
SH
3528 /**
3529 * Consolidate this to a single border style
3530 *
3531 * @return string
3532 */
50836f26
SH
3533 public function consolidate_to() {
3534 return 'border';
3535 }
3536}
0abd4846
SH
3537
3538/**
3539 * A border right style
3540 *
f37f608e
SH
3541 * @package core_css
3542 * @category css
6bbd4858 3543 * @copyright 2012 Sam Hemelryk
f37f608e 3544 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3545 */
6bbd4858
SH
3546class css_style_borderrightstyle extends css_style_borderstyle {
3547
0abd4846
SH
3548 /**
3549 * Initialises this style object
3550 *
ff8e5d47 3551 * @param string $value The value of the style
6bbd4858 3552 * @return css_style_borderrightstyle
0abd4846 3553 */
50836f26 3554 public static function init($value) {
6bbd4858 3555 return new css_style_borderrightstyle('border-right-style', $value);
50836f26 3556 }
6bbd4858 3557
0abd4846
SH
3558 /**
3559 * Consolidate this to a single border style
3560 *
3561 * @return string
3562 */
50836f26
SH
3563 public function consolidate_to() {