MDL-33468 css_optimiser: Added support for prioritising reset rules
[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 }
569 $buffer = '';
570 $currentatrule = false;
f37f608e
SH
571 // continue 1: The switch processing chars
572 // continue 2: The switch processing the state
573 // continue 3: The for loop
0e641c74
SH
574 continue 3;
575 case '{':
576 if ($currentatrule == 'media' && preg_match('#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)#', $buffer, $matches)) {
577 $mediatypes = str_replace(' ', '', $matches[1]);
578 if (!array_key_exists($mediatypes, $medias)) {
579 $medias[$mediatypes] = new css_media($mediatypes);
580 }
581 $currentmedia = $medias[$mediatypes];
582 $currentprocess = self::PROCESSING_SELECTORS;
583 $buffer = '';
1121f79b
SH
584 } else if ($currentatrule == 'keyframes' && preg_match('#@((\-moz\-|\-webkit\-)?keyframes)\s*([^\s]+)#', $buffer, $matches)) {
585 $keyframefor = $matches[1];
586 $keyframename = $matches[3];
587 $keyframe = new css_keyframe($keyframefor, $keyframename);
588 $keyframes[] = $keyframe;
589 $currentmedia = $keyframe;
590 $currentprocess = self::PROCESSING_SELECTORS;
591 $buffer = '';
0e641c74 592 }
f37f608e
SH
593 // continue 1: The switch processing chars
594 // continue 2: The switch processing the state
595 // continue 3: The for loop
0e641c74
SH
596 continue 3;
597 }
598 break;
599 // Start processing selectors
600 case self::PROCESSING_START:
601 case self::PROCESSING_SELECTORS:
602 switch ($char) {
603 case '[':
604 $inbrackets ++;
605 $buffer .= $char;
f37f608e
SH
606 // continue 1: The switch processing chars
607 // continue 2: The switch processing the state
608 // continue 3: The for loop
0e641c74
SH
609 continue 3;
610 case ']':
611 $inbrackets --;
612 $buffer .= $char;
f37f608e
SH
613 // continue 1: The switch processing chars
614 // continue 2: The switch processing the state
615 // continue 3: The for loop
0e641c74
SH
616 continue 3;
617 case ' ':
618 if ($inbrackets) {
f37f608e
SH
619 // continue 1: The switch processing chars
620 // continue 2: The switch processing the state
621 // continue 3: The for loop
0e641c74
SH
622 continue 3;
623 }
624 if (!empty($buffer)) {
1121f79b
SH
625 if ($suspectatrule && preg_match('#@(media|import|charset|(\-moz\-|\-webkit\-)?(keyframes))\s*#', $buffer, $matches)) {
626 $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1];
0e641c74
SH
627 $currentprocess = self::PROCESSING_ATRULE;
628 $buffer .= $char;
629 } else {
630 $currentselector->add($buffer);
631 $buffer = '';
632 }
633 }
634 $suspectatrule = false;
f37f608e
SH
635 // continue 1: The switch processing chars
636 // continue 2: The switch processing the state
637 // continue 3: The for loop
0e641c74
SH
638 continue 3;
639 case '{':
640 if ($inbrackets) {
f37f608e
SH
641 // continue 1: The switch processing chars
642 // continue 2: The switch processing the state
643 // continue 3: The for loop
0e641c74
SH
644 continue 3;
645 }
0663b535
SH
646 if ($buffer !== '') {
647 $currentselector->add($buffer);
648 }
1d1d807e 649 $currentrule->add_selector($currentselector);
0e641c74
SH
650 $currentselector = css_selector::init();
651 $currentprocess = self::PROCESSING_STYLES;
652
653 $buffer = '';
f37f608e
SH
654 // continue 1: The switch processing chars
655 // continue 2: The switch processing the state
656 // continue 3: The for loop
0e641c74
SH
657 continue 3;
658 case '}':
659 if ($inbrackets) {
f37f608e
SH
660 // continue 1: The switch processing chars
661 // continue 2: The switch processing the state
662 // continue 3: The for loop
0e641c74
SH
663 continue 3;
664 }
665 if ($currentatrule == 'media') {
666 $currentmedia = $medias['all'];
667 $currentatrule = false;
668 $buffer = '';
1121f79b
SH
669 } else if (strpos($currentatrule, 'keyframes') !== false) {
670 $currentmedia = $medias['all'];
671 $currentatrule = false;
672 $buffer = '';
0e641c74 673 }
f37f608e
SH
674 // continue 1: The switch processing chars
675 // continue 2: The switch processing the state
676 // continue 3: The for loop
0e641c74
SH
677 continue 3;
678 case ',':
679 if ($inbrackets) {
f37f608e
SH
680 // continue 1: The switch processing chars
681 // continue 2: The switch processing the state
682 // continue 3: The for loop
0e641c74
SH
683 continue 3;
684 }
685 $currentselector->add($buffer);
1d1d807e 686 $currentrule->add_selector($currentselector);
0e641c74
SH
687 $currentselector = css_selector::init();
688 $buffer = '';
f37f608e
SH
689 // continue 1: The switch processing chars
690 // continue 2: The switch processing the state
691 // continue 3: The for loop
0e641c74
SH
692 continue 3;
693 }
694 break;
695 // Start processing styles
696 case self::PROCESSING_STYLES:
697 if ($char == '"' || $char == "'") {
698 if ($inquotes === false) {
699 $inquotes = $char;
700 }
701 if ($inquotes === $char && $lastchar !== '\\') {
702 $inquotes = false;
703 }
704 }
705 if ($inquotes) {
706 $buffer .= $char;
707 continue 2;
708 }
709 switch ($char) {
710 case ';':
50e49c91
SH
711 if ($inparenthesis) {
712 $buffer .= $char;
713 // continue 1: The switch processing chars
714 // continue 2: The switch processing the state
715 // continue 3: The for loop
716 continue 3;
717 }
1d1d807e 718 $currentrule->add_style($buffer);
0e641c74
SH
719 $buffer = '';
720 $inquotes = false;
f37f608e
SH
721 // continue 1: The switch processing chars
722 // continue 2: The switch processing the state
723 // continue 3: The for loop
0e641c74
SH
724 continue 3;
725 case '}':
1d1d807e
SH
726 $currentrule->add_style($buffer);
727 $this->rawselectors += $currentrule->get_selector_count();
0e641c74 728
1d1d807e 729 $currentmedia->add_rule($currentrule);
0e641c74 730
1d1d807e 731 $currentrule = css_rule::init();
0e641c74
SH
732 $currentprocess = self::PROCESSING_SELECTORS;
733 $this->rawrules++;
734 $buffer = '';
735 $inquotes = false;
50e49c91
SH
736 $inparenthesis = false;
737 // continue 1: The switch processing chars
738 // continue 2: The switch processing the state
739 // continue 3: The for loop
740 continue 3;
741 case '(':
742 $inparenthesis = true;
743 $buffer .= $char;
744 // continue 1: The switch processing chars
745 // continue 2: The switch processing the state
746 // continue 3: The for loop
747 continue 3;
748 case ')':
749 $inparenthesis = false;
750 $buffer .= $char;
f37f608e
SH
751 // continue 1: The switch processing chars
752 // continue 2: The switch processing the state
753 // continue 3: The for loop
0e641c74
SH
754 continue 3;
755 }
756 break;
757 }
758 $buffer .= $char;
759 }
760
761 $css = '';
762 if (!empty($charset)) {
763 $imports[] = $charset;
764 }
765 if (!empty($imports)) {
766 $css .= implode("\n", $imports);
767 $css .= "\n\n";
768 }
0663b535 769
0e641c74
SH
770 foreach ($medias as $media) {
771 $media->organise_rules_by_selectors();
772 $this->optimisedrules += $media->count_rules();
773 $this->optimisedselectors += $media->count_selectors();
6bbd4858 774 if ($media->has_errors()) {
f2e8d379 775 $this->errors += $media->get_errors();
6bbd4858 776 }
0663b535
SH
777 if (in_array('all', $media->get_types())) {
778 $resetrules = $media->get_reset_rules(true);
779 if (!empty($resetrules)) {
780 $css .= css_writer::media('all', $resetrules);
781 }
782 }
783 }
784
785 foreach ($medias as $media) {
0e641c74
SH
786 $css .= $media->out();
787 }
1121f79b
SH
788
789 if (count($keyframes) > 0) {
790 foreach ($keyframes as $keyframe) {
791 $this->optimisedrules += $keyframe->count_rules();
792 $this->optimisedselectors += $keyframe->count_selectors();
793 if ($keyframe->has_errors()) {
794 $this->errors += $keyframe->get_errors();
795 }
796 $css .= $keyframe->out();
797 }
798 }
799
0e641c74
SH
800 $this->optimisedstrlen = strlen($css);
801
802 $this->timecomplete = microtime(true);
0e641c74
SH
803 return trim($css);
804 }
805
806 /**
8589a4a5 807 * Returns an array of stats from the last processing run
0e641c74
SH
808 * @return string
809 */
810 public function get_stats() {
8589a4a5
SH
811 $stats = array(
812 'timestart' => $this->timestart,
813 'timecomplete' => $this->timecomplete,
814 'timetaken' => round($this->timecomplete - $this->timestart, 4),
815 'commentsincss' => $this->commentsincss,
816 'rawstrlen' => $this->rawstrlen,
817 'rawselectors' => $this->rawselectors,
818 'rawrules' => $this->rawrules,
819 'optimisedstrlen' => $this->optimisedstrlen,
820 'optimisedrules' => $this->optimisedrules,
f2e8d379
SH
821 'optimisedselectors' => $this->optimisedselectors,
822 'improvementstrlen' => '-',
823 'improvementrules' => '-',
824 'improvementselectors' => '-',
8589a4a5 825 );
f2e8d379
SH
826 if ($this->rawstrlen > 0) {
827 $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%';
828 }
829 if ($this->rawrules > 0) {
830 $stats['improvementrules'] = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%';
831 }
832 if ($this->rawselectors > 0) {
833 $stats['improvementselectors'] = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%';
834 }
8589a4a5
SH
835 return $stats;
836 }
837
6bbd4858
SH
838 /**
839 * Returns true if any errors have occured during processing
840 *
841 * @return bool
842 */
843 public function has_errors() {
844 return !empty($this->errors);
845 }
846
847 /**
848 * Returns an array of errors that have occured
849 *
f2e8d379 850 * @param bool $clear If set to true the errors will be cleared after being returned.
6bbd4858
SH
851 * @return array
852 */
f2e8d379
SH
853 public function get_errors($clear = false) {
854 $errors = $this->errors;
855 if ($clear) {
856 // Reset the error array
857 $this->errors = array();
858 }
859 return $errors;
6bbd4858
SH
860 }
861
862 /**
863 * Returns any errors as a string that can be included in CSS.
864 *
865 * @return string
866 */
867 public function output_errors_css() {
868 $computedcss = "/****************************************\n";
869 $computedcss .= " *--- Errors found during processing ----\n";
870 foreach ($this->errors as $error) {
871 $computedcss .= preg_replace('#^#m', '* ', $error);
872 }
873 $computedcss .= " ****************************************/\n\n";
874 return $computedcss;
875 }
876
8589a4a5
SH
877 /**
878 * Returns a string to display stats about the last generation within CSS output
6bbd4858 879 *
8589a4a5
SH
880 * @return string
881 */
882 public function output_stats_css() {
0e641c74
SH
883
884 $computedcss = "/****************************************\n";
885 $computedcss .= " *------- CSS Optimisation stats --------\n";
f2e8d379
SH
886
887 if ($this->rawstrlen === 0) {
888 $computedcss .= " File not processed as it has no content /\n\n";
889 $computedcss .= " ****************************************/\n\n";
890 return $computedcss;
891 } else if ($this->rawrules === 0) {
892 $computedcss .= " File contained no rules to be processed /\n\n";
893 $computedcss .= " ****************************************/\n\n";
894 return $computedcss;
895 }
896
897 $stats = $this->get_stats();
898
0e641c74 899 $computedcss .= " * ".date('r')."\n";
1d1d807e
SH
900 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n";
901 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n";
0e641c74 902 $computedcss .= " *--------------- before ----------------\n";
1d1d807e
SH
903 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n";
904 $computedcss .= " * {$stats['rawrules']} \t rules read in\n";
905 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n";
0e641c74 906 $computedcss .= " *---------------- after ----------------\n";
1d1d807e
SH
907 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n";
908 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n";
909 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n";
0e641c74 910 $computedcss .= " *---------------- stats ----------------\n";
1d1d807e
SH
911 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n";
912 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n";
913 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n";
0e641c74
SH
914 $computedcss .= " ****************************************/\n\n";
915
916 return $computedcss;
917 }
918
919 /**
920 * Resets the stats ready for another fresh processing
921 */
922 public function reset_stats() {
923 $this->commentsincss = 0;
924 $this->optimisedrules = 0;
925 $this->optimisedselectors = 0;
926 $this->optimisedstrlen = 0;
927 $this->rawrules = 0;
928 $this->rawselectors = 0;
929 $this->rawstrlen = 0;
930 $this->timecomplete = 0;
931 $this->timestart = 0;
932 }
933
934 /**
935 * An array of the common HTML colours that are supported by most browsers.
936 *
937 * This reference table is used to allow us to unify colours, and will aid
938 * us in identifying buggy CSS using unsupported colours.
939 *
940 * @staticvar array
941 * @var array
942 */
943 public static $htmlcolours = array(
944 'aliceblue' => '#F0F8FF',
945 'antiquewhite' => '#FAEBD7',
946 'aqua' => '#00FFFF',
947 'aquamarine' => '#7FFFD4',
948 'azure' => '#F0FFFF',
949 'beige' => '#F5F5DC',
950 'bisque' => '#FFE4C4',
951 'black' => '#000000',
952 'blanchedalmond' => '#FFEBCD',
953 'blue' => '#0000FF',
954 'blueviolet' => '#8A2BE2',
955 'brown' => '#A52A2A',
956 'burlywood' => '#DEB887',
957 'cadetblue' => '#5F9EA0',
958 'chartreuse' => '#7FFF00',
959 'chocolate' => '#D2691E',
960 'coral' => '#FF7F50',
961 'cornflowerblue' => '#6495ED',
962 'cornsilk' => '#FFF8DC',
963 'crimson' => '#DC143C',
964 'cyan' => '#00FFFF',
965 'darkblue' => '#00008B',
966 'darkcyan' => '#008B8B',
967 'darkgoldenrod' => '#B8860B',
968 'darkgray' => '#A9A9A9',
969 'darkgrey' => '#A9A9A9',
970 'darkgreen' => '#006400',
971 'darkKhaki' => '#BDB76B',
972 'darkmagenta' => '#8B008B',
973 'darkolivegreen' => '#556B2F',
974 'arkorange' => '#FF8C00',
975 'darkorchid' => '#9932CC',
976 'darkred' => '#8B0000',
977 'darksalmon' => '#E9967A',
978 'darkseagreen' => '#8FBC8F',
979 'darkslateblue' => '#483D8B',
980 'darkslategray' => '#2F4F4F',
981 'darkslategrey' => '#2F4F4F',
982 'darkturquoise' => '#00CED1',
983 'darkviolet' => '#9400D3',
984 'deeppink' => '#FF1493',
985 'deepskyblue' => '#00BFFF',
986 'dimgray' => '#696969',
987 'dimgrey' => '#696969',
988 'dodgerblue' => '#1E90FF',
989 'firebrick' => '#B22222',
990 'floralwhite' => '#FFFAF0',
991 'forestgreen' => '#228B22',
992 'fuchsia' => '#FF00FF',
993 'gainsboro' => '#DCDCDC',
994 'ghostwhite' => '#F8F8FF',
995 'gold' => '#FFD700',
996 'goldenrod' => '#DAA520',
997 'gray' => '#808080',
998 'grey' => '#808080',
999 'green' => '#008000',
1000 'greenyellow' => '#ADFF2F',
1001 'honeydew' => '#F0FFF0',
1002 'hotpink' => '#FF69B4',
1003 'indianred ' => '#CD5C5C',
1004 'indigo ' => '#4B0082',
1005 'ivory' => '#FFFFF0',
1006 'khaki' => '#F0E68C',
1007 'lavender' => '#E6E6FA',
1008 'lavenderblush' => '#FFF0F5',
1009 'lawngreen' => '#7CFC00',
1010 'lemonchiffon' => '#FFFACD',
1011 'lightblue' => '#ADD8E6',
1012 'lightcoral' => '#F08080',
1013 'lightcyan' => '#E0FFFF',
1014 'lightgoldenrodyellow' => '#FAFAD2',
1015 'lightgray' => '#D3D3D3',
1016 'lightgrey' => '#D3D3D3',
1017 'lightgreen' => '#90EE90',
1018 'lightpink' => '#FFB6C1',
1019 'lightsalmon' => '#FFA07A',
1020 'lightseagreen' => '#20B2AA',
1021 'lightskyblue' => '#87CEFA',
1022 'lightslategray' => '#778899',
1023 'lightslategrey' => '#778899',
1024 'lightsteelblue' => '#B0C4DE',
1025 'lightyellow' => '#FFFFE0',
1026 'lime' => '#00FF00',
1027 'limegreen' => '#32CD32',
1028 'linen' => '#FAF0E6',
1029 'magenta' => '#FF00FF',
1030 'maroon' => '#800000',
1031 'mediumaquamarine' => '#66CDAA',
1032 'mediumblue' => '#0000CD',
1033 'mediumorchid' => '#BA55D3',
1034 'mediumpurple' => '#9370D8',
1035 'mediumseagreen' => '#3CB371',
1036 'mediumslateblue' => '#7B68EE',
1037 'mediumspringgreen' => '#00FA9A',
1038 'mediumturquoise' => '#48D1CC',
1039 'mediumvioletred' => '#C71585',
1040 'midnightblue' => '#191970',
1041 'mintcream' => '#F5FFFA',
1042 'mistyrose' => '#FFE4E1',
1043 'moccasin' => '#FFE4B5',
1044 'navajowhite' => '#FFDEAD',
1045 'navy' => '#000080',
1046 'oldlace' => '#FDF5E6',
1047 'olive' => '#808000',
1048 'olivedrab' => '#6B8E23',
1049 'orange' => '#FFA500',
1050 'orangered' => '#FF4500',
1051 'orchid' => '#DA70D6',
1052 'palegoldenrod' => '#EEE8AA',
1053 'palegreen' => '#98FB98',
1054 'paleturquoise' => '#AFEEEE',
1055 'palevioletred' => '#D87093',
1056 'papayawhip' => '#FFEFD5',
1057 'peachpuff' => '#FFDAB9',
1058 'peru' => '#CD853F',
1059 'pink' => '#FFC0CB',
1060 'plum' => '#DDA0DD',
1061 'powderblue' => '#B0E0E6',
1062 'purple' => '#800080',
1063 'red' => '#FF0000',
1064 'rosybrown' => '#BC8F8F',
1065 'royalblue' => '#4169E1',
1066 'saddlebrown' => '#8B4513',
1067 'salmon' => '#FA8072',
1068 'sandybrown' => '#F4A460',
1069 'seagreen' => '#2E8B57',
1070 'seashell' => '#FFF5EE',
1071 'sienna' => '#A0522D',
1072 'silver' => '#C0C0C0',
1073 'skyblue' => '#87CEEB',
1074 'slateblue' => '#6A5ACD',
1075 'slategray' => '#708090',
1076 'slategrey' => '#708090',
1077 'snow' => '#FFFAFA',
1078 'springgreen' => '#00FF7F',
1079 'steelblue' => '#4682B4',
1080 'tan' => '#D2B48C',
1081 'teal' => '#008080',
1082 'thistle' => '#D8BFD8',
1083 'tomato' => '#FF6347',
6bbd4858 1084 'transparent' => 'transparent',
0e641c74
SH
1085 'turquoise' => '#40E0D0',
1086 'violet' => '#EE82EE',
1087 'wheat' => '#F5DEB3',
1088 'white' => '#FFFFFF',
1089 'whitesmoke' => '#F5F5F5',
1090 'yellow' => '#FFFF00',
1091 'yellowgreen' => '#9ACD32'
1092 );
1093}
1094
1d1d807e
SH
1095/**
1096 * Used to prepare CSS strings
1097 *
f37f608e
SH
1098 * @package core_css
1099 * @category css
6bbd4858 1100 * @copyright 2012 Sam Hemelryk
f37f608e 1101 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e
SH
1102 */
1103abstract class css_writer {
ff8e5d47 1104
1d1d807e
SH
1105 /**
1106 * The current indent level
1107 * @var int
1108 */
1109 protected static $indent = 0;
1110
1111 /**
1112 * Returns true if the output should still maintain minimum formatting.
1113 * @return bool
1114 */
1115 protected static function is_pretty() {
1116 global $CFG;
6bbd4858 1117 return (!empty($CFG->cssoptimiserpretty));
1d1d807e
SH
1118 }
1119
1120 /**
1121 * Returns the indenting char to use for indenting things nicely.
1122 * @return string
1123 */
1124 protected static function get_indent() {
1125 if (self::is_pretty()) {
1126 return str_repeat(" ", self::$indent);
1127 }
1128 return '';
1129 }
1130
1131 /**
1132 * Increases the current indent
1133 */
1134 protected static function increase_indent() {
1135 self::$indent++;
1136 }
1137
1138 /**
6bbd4858 1139 * Decreases the current indent
1d1d807e
SH
1140 */
1141 protected static function decrease_indent() {
1142 self::$indent--;
1143 }
1144
1145 /**
1146 * Returns the string to use as a separator
1147 * @return string
1148 */
1149 protected static function get_separator() {
1150 return (self::is_pretty())?"\n":' ';
1151 }
1152
1153 /**
1154 * Returns CSS for media
1155 *
1156 * @param string $typestring
1157 * @param array $rules An array of css_rule objects
1158 * @return string
1159 */
1160 public static function media($typestring, array &$rules) {
1161 $nl = self::get_separator();
1162
1163 $output = '';
1164 if ($typestring !== 'all') {
1165 $output .= $nl.$nl."@media {$typestring} {".$nl;
1166 self::increase_indent();
1167 }
1168 foreach ($rules as $rule) {
1169 $output .= $rule->out().$nl;
1170 }
1171 if ($typestring !== 'all') {
1172 self::decrease_indent();
1173 $output .= '}';
1174 }
1175 return $output;
1176 }
1177
1121f79b
SH
1178 public static function keyframe($for, $name, array &$rules) {
1179 $nl = self::get_separator();
1180
1181 $output = $nl."@{$for} {$name} {";
1182 foreach ($rules as $rule) {
1183 $output .= $rule->out();
1184 }
1185 $output .= '}';
1186 return $output;
1187 }
1188
1d1d807e
SH
1189 /**
1190 * Returns CSS for a rule
1191 *
1192 * @param string $selector
1193 * @param string $styles
1194 * @return string
1195 */
1196 public static function rule($selector, $styles) {
1197 $css = self::get_indent()."{$selector}{{$styles}}";
1198 return $css;
1199 }
1200
1201 /**
1202 * Returns CSS for the selectors of a rule
1203 *
1204 * @param array $selectors Array of css_selector objects
1205 * @return string
1206 */
1207 public static function selectors(array $selectors) {
1208 $nl = self::get_separator();
1209 $selectorstrings = array();
1210 foreach ($selectors as $selector) {
1211 $selectorstrings[] = $selector->out();
1212 }
1213 return join(','.$nl, $selectorstrings);
1214 }
1215
1216 /**
1217 * Returns a selector given the components that make it up.
1218 *
1219 * @param array $components
1220 * @return string
1221 */
1222 public static function selector(array $components) {
1223 return trim(join(' ', $components));
1224 }
1225
1226 /**
ff8e5d47 1227 * Returns a CSS string for the provided styles
1d1d807e
SH
1228 *
1229 * @param array $styles Array of css_style objects
ff8e5d47 1230 * @return string
1d1d807e
SH
1231 */
1232 public static function styles(array $styles) {
1233 $bits = array();
1234 foreach ($styles as $style) {
1235 $bits[] = $style->out();
1236 }
1237 return join('', $bits);
1238 }
1239
1240 /**
1241 * Returns a style CSS
1242 *
1243 * @param string $name
1244 * @param string $value
1245 * @param bool $important
1246 * @return string
1247 */
1248 public static function style($name, $value, $important = false) {
f2e8d379 1249 $value = trim($value);
1d1d807e
SH
1250 if ($important && strpos($value, '!important') === false) {
1251 $value .= ' !important';
1252 }
1253 return "{$name}:{$value};";
1254 }
1255}
1256
0e641c74
SH
1257/**
1258 * A structure to represent a CSS selector.
1259 *
1260 * The selector is the classes, id, elements, and psuedo bits that make up a CSS
1261 * rule.
1262 *
f37f608e
SH
1263 * @package core_css
1264 * @category css
6bbd4858 1265 * @copyright 2012 Sam Hemelryk
f37f608e 1266 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
1267 */
1268class css_selector {
1269
1270 /**
1271 * An array of selector bits
1272 * @var array
1273 */
1274 protected $selectors = array();
1275
1276 /**
1277 * The number of selectors.
1278 * @var int
1279 */
1280 protected $count = 0;
1281
0663b535
SH
1282 /**
1283 * Is null if there are no selector, true if all selectors are basic and false otherwise.
1284 * @var bool|null
1285 */
1286 protected $isbasic = null;
1287
0e641c74
SH
1288 /**
1289 * Initialises a new CSS selector
1d1d807e 1290 * @return css_selector
0e641c74
SH
1291 */
1292 public static function init() {
1293 return new css_selector();
1294 }
1295
1296 /**
1297 * CSS selectors can only be created through the init method above.
1298 */
1299 protected function __construct() {}
1300
1301 /**
1302 * Adds a selector to the end of the current selector
1303 * @param string $selector
1304 */
1305 public function add($selector) {
1306 $selector = trim($selector);
1307 $count = 0;
1308 $count += preg_match_all('/(\.|#)/', $selector, $matchesarray);
1309 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) {
1310 $count ++;
1311 }
0663b535
SH
1312 if ($this->isbasic !== false) {
1313 if ($count > 1) {
1314 $this->isbasic = false;
1315 } else {
1316 $this->isbasic = (preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector))?true:false;
1317 }
1318 }
0e641c74
SH
1319 $this->count = $count;
1320 $this->selectors[] = $selector;
1321 }
1322 /**
1323 * Returns the number of individual components that make up this selector
1324 * @return int
1325 */
1326 public function get_selector_count() {
1327 return $this->count;
1328 }
1329
1330 /**
1331 * Returns the selector for use in a CSS rule
1332 * @return string
1333 */
1334 public function out() {
1d1d807e 1335 return css_writer::selector($this->selectors);
0e641c74 1336 }
0663b535
SH
1337
1338 /**
1339 * Returns true is all of the selectors act only upon basic elements (no classes/ids)
1340 * @return bool
1341 */
1342 public function is_basic() {
1343 return ($this->isbasic === true);
1344 }
0e641c74
SH
1345}
1346
1347/**
1348 * A structure to represent a CSS rule.
1349 *
f37f608e
SH
1350 * @package core_css
1351 * @category css
6bbd4858 1352 * @copyright 2012 Sam Hemelryk
f37f608e 1353 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
1354 */
1355class css_rule {
1356
1357 /**
1358 * An array of CSS selectors {@see css_selector}
1359 * @var array
1360 */
1361 protected $selectors = array();
1362
1363 /**
1364 * An array of CSS styles {@see css_style}
1365 * @var array
1366 */
1367 protected $styles = array();
1368
1369 /**
1370 * Created a new CSS rule. This is the only way to create a new CSS rule externally.
1371 * @return css_rule
1372 */
1373 public static function init() {
1374 return new css_rule();
1375 }
1376
1377 /**
6bbd4858 1378 * Constructs a new css rule.
0e641c74 1379 *
f37f608e
SH
1380 * @param string $selector The selector or array of selectors that make up this rule.
1381 * @param array $styles An array of styles that belong to this rule.
0e641c74
SH
1382 */
1383 protected function __construct($selector = null, array $styles = array()) {
1384 if ($selector != null) {
1385 if (is_array($selector)) {
1386 $this->selectors = $selector;
1387 } else {
1388 $this->selectors = array($selector);
1389 }
1390 $this->add_styles($styles);
1391 }
1392 }
1393
1394 /**
1395 * Adds a new CSS selector to this rule
1396 *
f37f608e
SH
1397 * e.g. $rule->add_selector('.one #two.two');
1398 *
1399 * @param css_selector $selector Adds a CSS selector to this rule.
0e641c74
SH
1400 */
1401 public function add_selector(css_selector $selector) {
1402 $this->selectors[] = $selector;
1403 }
1404
1405 /**
1406 * Adds a new CSS style to this rule.
1407 *
f37f608e 1408 * @param css_style|string $style Adds a new style to this rule
0e641c74
SH
1409 */
1410 public function add_style($style) {
1411 if (is_string($style)) {
1412 $style = trim($style);
1413 if (empty($style)) {
1414 return;
1415 }
1416 $bits = explode(':', $style, 2);
1417 if (count($bits) == 2) {
1418 list($name, $value) = array_map('trim', $bits);
1419 }
1420 if (isset($name) && isset($value) && $name !== '' && $value !== '') {
3c548abf 1421 $style = css_style::init_automatic($name, $value);
0e641c74 1422 }
1d1d807e 1423 } else if ($style instanceof css_style) {
f37f608e
SH
1424 // Clone the style as it may be coming from another rule and we don't
1425 // want references as it will likely be overwritten by proceeding
1426 // rules
1d1d807e 1427 $style = clone($style);
0e641c74
SH
1428 }
1429 if ($style instanceof css_style) {
1430 $name = $style->get_name();
1431 if (array_key_exists($name, $this->styles)) {
1432 $this->styles[$name]->set_value($style->get_value());
1433 } else {
1434 $this->styles[$name] = $style;
1435 }
1d1d807e 1436 } else if (is_array($style)) {
f37f608e
SH
1437 // We probably shouldn't worry about processing styles here but to
1438 // be truthful it doesn't hurt.
1d1d807e
SH
1439 foreach ($style as $astyle) {
1440 $this->add_style($astyle);
1441 }
0e641c74
SH
1442 }
1443 }
1444
1445 /**
1446 * An easy method of adding several styles at once. Just calls add_style.
1447 *
f37f608e
SH
1448 * This method simply iterates over the array and calls {@see css_rule::add_style()}
1449 * with each.
1450 *
1451 * @param array $styles Adds an array of styles
0e641c74
SH
1452 */
1453 public function add_styles(array $styles) {
1454 foreach ($styles as $style) {
1455 $this->add_style($style);
1456 }
1457 }
1458
0e641c74
SH
1459 /**
1460 * Returns the array of selectors
f37f608e 1461 *
0e641c74
SH
1462 * @return array
1463 */
1464 public function get_selectors() {
1465 return $this->selectors;
1466 }
1467
1468 /**
1469 * Returns the array of styles
f37f608e 1470 *
0e641c74
SH
1471 * @return array
1472 */
1473 public function get_styles() {
1474 return $this->styles;
1475 }
1476
1477 /**
1478 * Outputs this rule as a fragment of CSS
f37f608e 1479 *
0e641c74
SH
1480 * @return string
1481 */
1482 public function out() {
1d1d807e
SH
1483 $selectors = css_writer::selectors($this->selectors);
1484 $styles = css_writer::styles($this->get_consolidated_styles());
1485 return css_writer::rule($selectors, $styles);
1486 }
1487
0abd4846
SH
1488 /**
1489 * Consolidates all styles associated with this rule
1490 *
1491 * @return array An array of consolidated styles
1492 */
1d1d807e
SH
1493 public function get_consolidated_styles() {
1494 $finalstyles = array();
1495 $consolidate = array();
1496 foreach ($this->styles as $style) {
1497 $consolidatetoclass = $style->consolidate_to();
50836f26 1498 if ($style->is_valid() && !empty($consolidatetoclass) && class_exists('css_style_'.$consolidatetoclass)) {
1d1d807e
SH
1499 $class = 'css_style_'.$consolidatetoclass;
1500 if (!array_key_exists($class, $consolidate)) {
1501 $consolidate[$class] = array();
1502 }
1503 $consolidate[$class][] = $style;
1504 } else {
1505 $finalstyles[] = $style;
1506 }
1507 }
1508
1509 foreach ($consolidate as $class => $styles) {
1510 $styles = $class::consolidate($styles);
1511 foreach ($styles as $style) {
1512 $finalstyles[] = $style;
1513 }
1514 }
1515 return $finalstyles;
0e641c74
SH
1516 }
1517
1518 /**
1519 * Splits this rules into an array of CSS rules. One for each of the selectors
1520 * that make up this rule.
1521 *
1522 * @return array(css_rule)
1523 */
1524 public function split_by_selector() {
1525 $return = array();
1526 foreach ($this->selectors as $selector) {
1527 $return[] = new css_rule($selector, $this->styles);
1528 }
1529 return $return;
1530 }
1531
1532 /**
1533 * Splits this rule into an array of rules. One for each of the styles that
1534 * make up this rule
1535 *
f37f608e 1536 * @return array Array of css_rule objects
0e641c74
SH
1537 */
1538 public function split_by_style() {
1539 $return = array();
1540 foreach ($this->styles as $style) {
1541 $return[] = new css_rule($this->selectors, array($style));
1542 }
1543 return $return;
1544 }
1545
1546 /**
1547 * Gets a hash for the styles of this rule
f37f608e 1548 *
0e641c74
SH
1549 * @return string
1550 */
1551 public function get_style_hash() {
1d1d807e 1552 return md5(css_writer::styles($this->styles));
0e641c74
SH
1553 }
1554
1555 /**
1556 * Gets a hash for the selectors of this rule
f37f608e 1557 *
0e641c74
SH
1558 * @return string
1559 */
1560 public function get_selector_hash() {
1d1d807e 1561 return md5(css_writer::selectors($this->selectors));
0e641c74
SH
1562 }
1563
1564 /**
1565 * Gets the number of selectors that make up this rule.
f37f608e 1566 *
0e641c74
SH
1567 * @return int
1568 */
1569 public function get_selector_count() {
1570 $count = 0;
1571 foreach ($this->selectors as $selector) {
1572 $count += $selector->get_selector_count();
1573 }
1574 return $count;
1575 }
6bbd4858
SH
1576
1577 /**
1578 * Returns true if there are any errors with this rule.
1579 *
1580 * @return bool
1581 */
1582 public function has_errors() {
1583 foreach ($this->styles as $style) {
1584 if ($style->has_error()) {
1585 return true;
1586 }
1587 }
1588 return false;
1589 }
1590
f37f608e
SH
1591 /**
1592 * Returns the error strings that were recorded when processing this rule.
1593 *
1594 * Before calling this function you should first call {@see css_rule::has_errors()}
1595 * to make sure there are errors (hopefully there arn't).
1596 *
1597 * @return string
1598 */
6bbd4858
SH
1599 public function get_error_string() {
1600 $css = $this->out();
1601 $errors = array();
1602 foreach ($this->styles as $style) {
1603 if ($style->has_error()) {
1604 $errors[] = " * ".$style->get_last_error();
1605 }
1606 }
1607 return $css." has the following errors:\n".join("\n", $errors);
6bbd4858 1608 }
0663b535
SH
1609
1610 /**
1611 * Returns true if this rule could be considered a reset rule.
1612 *
1613 * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector.
1614 *
1615 * @return bool
1616 */
1617 public function is_reset_rule() {
1618 foreach ($this->selectors as $selector) {
1619 if (!$selector->is_basic()) {
1620 return false;
1621 }
1622 }
1623 return true;
1624 }
0e641c74
SH
1625}
1626
1121f79b 1627abstract class css_rule_collection {
0e641c74 1628 /**
1121f79b 1629 * An array of rules within this collection instance
0e641c74
SH
1630 * @var array
1631 */
1632 protected $rules = array();
1633
1634 /**
1121f79b 1635 * The collection must be able to print itself.
0e641c74 1636 */
1121f79b 1637 abstract public function out();
0e641c74
SH
1638
1639 /**
1121f79b 1640 * Adds a new CSS rule to this collection instance
0e641c74
SH
1641 *
1642 * @param css_rule $newrule
1643 */
1644 public function add_rule(css_rule $newrule) {
1645 foreach ($newrule->split_by_selector() as $rule) {
1646 $hash = $rule->get_selector_hash();
1647 if (!array_key_exists($hash, $this->rules)) {
1648 $this->rules[$hash] = $rule;
1649 } else {
1650 $this->rules[$hash]->add_styles($rule->get_styles());
1651 }
1652 }
1653 }
1654
1655 /**
1121f79b 1656 * Returns the rules used by this collection
0e641c74
SH
1657 *
1658 * @return array
1659 */
1660 public function get_rules() {
1661 return $this->rules;
1662 }
1663
1664 /**
1665 * Organises rules by gropuing selectors based upon the styles and consolidating
1666 * those selectors into single rules.
1667 *
6bbd4858 1668 * @return bool True if the CSS was optimised by this method
0e641c74
SH
1669 */
1670 public function organise_rules_by_selectors() {
1671 $optimised = array();
1672 $beforecount = count($this->rules);
1673 foreach ($this->rules as $rule) {
1674 $hash = $rule->get_style_hash();
1675 if (!array_key_exists($hash, $optimised)) {
1676 $optimised[$hash] = clone($rule);
1677 } else {
1678 foreach ($rule->get_selectors() as $selector) {
1679 $optimised[$hash]->add_selector($selector);
1680 }
1681 }
1682 }
1683 $this->rules = $optimised;
1684 $aftercount = count($this->rules);
1685 return ($beforecount < $aftercount);
1686 }
1687
1688 /**
1121f79b 1689 * Returns the total number of rules that exist within this collection
0e641c74
SH
1690 *
1691 * @return int
1692 */
1693 public function count_rules() {
1694 return count($this->rules);
1695 }
1696
1697 /**
1121f79b 1698 * Returns the total number of selectors that exist within this collection
0e641c74
SH
1699 *
1700 * @return int
1701 */
1702 public function count_selectors() {
1703 $count = 0;
1704 foreach ($this->rules as $rule) {
1705 $count += $rule->get_selector_count();
1706 }
1707 return $count;
1708 }
1709
1121f79b
SH
1710 /**
1711 * Returns true if the collection has any rules that have errors
1712 *
1713 * @return boolean
1714 */
1715 public function has_errors() {
1716 foreach ($this->rules as $rule) {
1717 if ($rule->has_errors()) {
1718 return true;
1719 }
1720 }
1721 return false;
1722 }
1723
1724 /**
1725 * Returns any errors that have happened within rules in this collection.
1726 *
1727 * @return string
1728 */
1729 public function get_errors() {
1730 $errors = array();
1731 foreach ($this->rules as $rule) {
1732 if ($rule->has_errors()) {
1733 $errors[] = $rule->get_error_string();
1734 }
1735 }
1736 return $errors;
1737 }
1738}
1739
1740/**
1741 * A media class to organise rules by the media they apply to.
1742 *
1743 * @package core_css
1744 * @category css
1745 * @copyright 2012 Sam Hemelryk
1746 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1747 */
1748class css_media extends css_rule_collection {
1749
1750 /**
1751 * An array of the different media types this instance applies to.
1752 * @var array
1753 */
1754 protected $types = array();
1755
1756 /**
1757 * Initalises a new media instance
1758 *
1759 * @param string $for The media that the contained rules are destined for.
1760 */
1761 public function __construct($for = 'all') {
1762 $types = explode(',', $for);
1763 $this->types = array_map('trim', $types);
1764 }
1765
0e641c74
SH
1766 /**
1767 * Returns the CSS for this media and all of its rules.
1768 *
1769 * @return string
1770 */
1771 public function out() {
1d1d807e 1772 return css_writer::media(join(',', $this->types), $this->rules);
0e641c74
SH
1773 }
1774
1775 /**
1776 * Returns an array of media that this media instance applies to
1777 *
1778 * @return array
1779 */
1780 public function get_types() {
1781 return $this->types;
1782 }
0663b535
SH
1783
1784 /**
1785 * Returns all of the reset rules known by this media set.
1786 * @return array
1787 */
1788 public function get_reset_rules($remove = false) {
1789 $resetrules = array();
1790 foreach ($this->rules as $key => $rule) {
1791 if ($rule->is_reset_rule()) {
1792 $resetrules[] = clone $rule;
1793 if ($remove) {
1794 unset($this->rules[$key]);
1795 }
1796 }
1797 }
1798 return $resetrules;
1799 }
1121f79b
SH
1800}
1801
1802/**
1803 * A media class to organise rules by the media they apply to.
1804 *
1805 * @package core_css
1806 * @category css
1807 * @copyright 2012 Sam Hemelryk
1808 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1809 */
1810class css_keyframe extends css_rule_collection {
1811
1812 /** @var string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes */
1813 protected $for;
6bbd4858 1814
1121f79b
SH
1815 /** @var string $name The name for the keyframes */
1816 protected $name;
6bbd4858 1817 /**
1121f79b 1818 * Constructs a new keyframe
6bbd4858 1819 *
1121f79b
SH
1820 * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes
1821 * @param string $name The name for the keyframes
6bbd4858 1822 */
1121f79b
SH
1823 public function __construct($for, $name) {
1824 $this->for = $for;
1825 $this->name = $name;
6bbd4858 1826 }
6bbd4858 1827 /**
1121f79b 1828 * Returns the directive of this keyframe
6bbd4858 1829 *
1121f79b 1830 * e.g. keyframes, -moz-keyframes, -webkit-keyframes
6bbd4858
SH
1831 * @return string
1832 */
1121f79b
SH
1833 public function get_for() {
1834 return $this->for;
1835 }
1836 /**
1837 * Returns the name of this keyframe
1838 * @return string
1839 */
1840 public function get_name() {
1841 return $this->name;
1842 }
1843 /**
1844 * Returns the CSS for this collection of keyframes and all of its rules.
1845 *
1846 * @return string
1847 */
1848 public function out() {
1849 return css_writer::keyframe($this->for, $this->name, $this->rules);
6bbd4858 1850 }
0e641c74
SH
1851}
1852
1853/**
1854 * An absract class to represent CSS styles
1855 *
f37f608e
SH
1856 * @package core_css
1857 * @category css
6bbd4858 1858 * @copyright 2012 Sam Hemelryk
f37f608e 1859 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
1860 */
1861abstract class css_style {
1862
1863 /**
1864 * The name of the style
1865 * @var string
1866 */
1867 protected $name;
1868
1869 /**
1870 * The value for the style
1871 * @var mixed
1872 */
1873 protected $value;
1874
1875 /**
1876 * If set to true this style was defined with the !important rule.
f37f608e
SH
1877 * Only trolls use !important.
1878 * Don't hide under bridges.. its not good for your skin. Do the proper thing
1879 * and fix the issue don't just force a fix that will undoubtedly one day
1880 * lead to further frustration.
0e641c74
SH
1881 * @var bool
1882 */
1883 protected $important = false;
1884
6bbd4858
SH
1885 /**
1886 * Gets set to true if this style has an error
1887 * @var bool
1888 */
1889 protected $error = false;
1890
1891 /**
1892 * The last error message that occured
1893 * @var string
1894 */
1895 protected $errormessage = null;
1896
1897
0e641c74
SH
1898 /**
1899 * Initialises a new style.
1900 *
1901 * This is the only public way to create a style to ensure they that appropriate
1902 * style class is used if it exists.
1903 *
f37f608e
SH
1904 * @param string $name The name of the style.
1905 * @param string $value The value of the style.
1d1d807e 1906 * @return css_style_generic
0e641c74 1907 */
3c548abf 1908 public static function init_automatic($name, $value) {
0e641c74
SH
1909 $specificclass = 'css_style_'.preg_replace('#[^a-zA-Z0-9]+#', '', $name);
1910 if (class_exists($specificclass)) {
1911 return $specificclass::init($value);
1912 }
1913 return new css_style_generic($name, $value);
1914 }
1915
1916 /**
1917 * Creates a new style when given its name and value
1918 *
f37f608e
SH
1919 * @param string $name The name of the style.
1920 * @param string $value The value of the style.
0e641c74
SH
1921 */
1922 protected function __construct($name, $value) {
1923 $this->name = $name;
1924 $this->set_value($value);
1925 }
1926
1927 /**
1928 * Sets the value for the style
1929 *
1930 * @param string $value
1931 */
1932 final public function set_value($value) {
1933 $value = trim($value);
1934 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches);
1935 if ($important) {
1936 $value = substr($value, 0, -(strlen($matches[1])));
f2e8d379 1937 $value = rtrim($value);
0e641c74
SH
1938 }
1939 if (!$this->important || $important) {
1940 $this->value = $this->clean_value($value);
1941 $this->important = $important;
1942 }
6bbd4858
SH
1943 if (!$this->is_valid()) {
1944 $this->set_error('Invalid value for '.$this->name);
1945 }
0e641c74
SH
1946 }
1947
0abd4846
SH
1948 /**
1949 * Returns true if the value associated with this style is valid
1950 *
1951 * @return bool
1952 */
50836f26
SH
1953 public function is_valid() {
1954 return true;
1955 }
1956
0e641c74
SH
1957 /**
1958 * Returns the name for the style
1959 *
1960 * @return string
1961 */
1962 public function get_name() {
1963 return $this->name;
1964 }
1965
1966 /**
1967 * Returns the value for the style
1968 *
1969 * @return string
1970 */
1971 public function get_value() {
1972 $value = $this->value;
1973 if ($this->important) {
1974 $value .= ' !important';
1975 }
1976 return $value;
1977 }
1978
1979 /**
1980 * Returns the style ready for use in CSS
1981 *
f37f608e 1982 * @param string|null $value A value to use to override the value for this style.
0e641c74
SH
1983 * @return string
1984 */
1985 public function out($value = null) {
1d1d807e 1986 if (is_null($value)) {
0e641c74 1987 $value = $this->get_value();
0e641c74 1988 }
1d1d807e 1989 return css_writer::style($this->name, $value, $this->important);
0e641c74
SH
1990 }
1991
1992 /**
1993 * This can be overridden by a specific style allowing it to clean its values
1994 * consistently.
1995 *
1996 * @param mixed $value
1997 * @return mixed
1998 */
1999 protected function clean_value($value) {
2000 return $value;
2001 }
1d1d807e 2002
0abd4846
SH
2003 /**
2004 * If this particular style can be consolidated into another style this function
2005 * should return the style that it can be consolidated into.
2006 *
2007 * @return string|null
2008 */
1d1d807e
SH
2009 public function consolidate_to() {
2010 return null;
2011 }
6bbd4858
SH
2012
2013 /**
2014 * Sets the last error message.
2015 *
ff8e5d47 2016 * @param string $message
6bbd4858
SH
2017 */
2018 protected function set_error($message) {
2019 $this->error = true;
2020 $this->errormessage = $message;
2021 }
2022
2023 /**
2024 * Returns true if an error has occured
f37f608e 2025 *
6bbd4858
SH
2026 * @return bool
2027 */
2028 public function has_error() {
2029 return $this->error;
2030 }
2031
2032 /**
2033 * Returns the last error that occured or null if no errors have happened.
2034 *
2035 * @return string
2036 */
2037 public function get_last_error() {
2038 return $this->errormessage;
2039 }
0e641c74
SH
2040}
2041
2042/**
2043 * A generic CSS style class to use when a more specific class does not exist.
2044 *
f37f608e
SH
2045 * @package core_css
2046 * @category css
6bbd4858 2047 * @copyright 2012 Sam Hemelryk
f37f608e 2048 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
2049 */
2050class css_style_generic extends css_style {
f37f608e 2051
0e641c74
SH
2052 /**
2053 * Cleans incoming values for typical things that can be optimised.
2054 *
ff8e5d47 2055 * @param mixed $value Cleans the provided value optimising it if possible
0e641c74
SH
2056 * @return string
2057 */
2058 protected function clean_value($value) {
2059 if (trim($value) == '0px') {
2060 $value = 0;
2061 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) {
2062 $value = '#'.strtoupper($matches[1]);
2063 }
2064 return $value;
2065 }
2066}
2067
2068/**
2069 * A colour CSS style
2070 *
f37f608e
SH
2071 * @package core_css
2072 * @category css
6bbd4858 2073 * @copyright 2012 Sam Hemelryk
f37f608e 2074 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
2075 */
2076class css_style_color extends css_style {
f37f608e 2077
0e641c74
SH
2078 /**
2079 * Creates a new colour style
2080 *
f37f608e 2081 * @param mixed $value Initialises a new colour style
0e641c74
SH
2082 * @return css_style_color
2083 */
2084 public static function init($value) {
2085 return new css_style_color('color', $value);
2086 }
2087
2088 /**
2089 * Cleans the colour unifing it to a 6 char hash colour if possible
2090 * Doing this allows us to associate identical colours being specified in
2091 * different ways. e.g. Red, red, #F00, and #F00000
2092 *
ff8e5d47 2093 * @param mixed $value Cleans the provided value optimising it if possible
0e641c74
SH
2094 * @return string
2095 */
2096 protected function clean_value($value) {
2097 $value = trim($value);
ff8e5d47
SH
2098 if (css_is_colour($value)) {
2099 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) {
2100 $value = '#'.strtoupper($matches[1]);
2101 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) {
2102 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3];
2103 $value = '#'.strtoupper($value);
2104 } else if (array_key_exists(strtolower($value), css_optimiser::$htmlcolours)) {
2105 $value = css_optimiser::$htmlcolours[strtolower($value)];
2106 }
0e641c74
SH
2107 }
2108 return $value;
2109 }
2110
2111 /**
2112 * Returns the colour style for use within CSS.
2113 * Will return an optimised hash colour.
2114 *
2115 * e.g #123456
2116 * #123 instead of #112233
2117 * #F00 instead of red
2118 *
ff8e5d47
SH
2119 * @param string $overridevalue If provided then this value will be used instead
2120 * of the styles current value.
0e641c74
SH
2121 * @return string
2122 */
2123 public function out($overridevalue = null) {
50836f26
SH
2124 if ($overridevalue === null) {
2125 $overridevalue = $this->value;
2126 }
2127 return parent::out(self::shrink_value($overridevalue));
2128 }
2129
0abd4846
SH
2130 /**
2131 * Shrinks the colour value is possible.
2132 *
ff8e5d47 2133 * @param string $value Shrinks the current value to an optimial form if possible
0abd4846
SH
2134 * @return string
2135 */
50836f26
SH
2136 public static function shrink_value($value) {
2137 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) {
2138 return '#'.$matches[1].$matches[2].$matches[3];
0e641c74 2139 }
50836f26
SH
2140 return $value;
2141 }
2142
0abd4846
SH
2143 /**
2144 * Returns true if the value is a valid colour.
2145 *
2146 * @return bool
2147 */
50836f26
SH
2148 public function is_valid() {
2149 return css_is_colour($this->value);
0e641c74
SH
2150 }
2151}
2152
6bbd4858
SH
2153/**
2154 * A width style
2155 *
f37f608e
SH
2156 * @package core_css
2157 * @category css
6bbd4858 2158 * @copyright 2012 Sam Hemelryk
f37f608e 2159 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6bbd4858
SH
2160 */
2161class css_style_width extends css_style {
2162
2163 /**
2164 * Checks if the width is valid
2165 * @return bool
2166 */
2167 public function is_valid() {
2168 return css_is_width($this->value);
2169 }
2170
2171 /**
2172 * Cleans the provided value
2173 *
ff8e5d47 2174 * @param mixed $value Cleans the provided value optimising it if possible
6bbd4858
SH
2175 * @return string
2176 */
2177 protected function clean_value($value) {
2178 if (!css_is_width($value)) {
2179 // Note we don't actually change the value to something valid. That
2180 // would be bad for futureproofing.
2181 $this->set_error('Invalid width specified for '.$this->name);
2182 } else if (preg_match('#^0\D+$#', $value)) {
2183 $value = 0;
2184 }
2185 return trim($value);
2186 }
2187
2188 /**
2189 * Initialises a new width style
2190 *
ff8e5d47 2191 * @param mixed $value The value this style has
6bbd4858
SH
2192 * @return css_style_width
2193 */
2194 public static function init($value) {
2195 return new css_style_width('width', $value);
2196 }
2197}
2198
0abd4846
SH
2199/**
2200 * A margin style
2201 *
f37f608e
SH
2202 * @package core_css
2203 * @category css
6bbd4858 2204 * @copyright 2012 Sam Hemelryk
f37f608e 2205 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2206 */
6bbd4858 2207class css_style_margin extends css_style_width {
0abd4846
SH
2208
2209 /**
2210 * Initialises a margin style.
2211 *
2212 * In this case we split the margin into several other margin styles so that
2213 * we can properly condense overrides and then reconsolidate them later into
2214 * an optimal form.
2215 *
ff8e5d47 2216 * @param string $value The value the style has
0abd4846
SH
2217 * @return array An array of margin values that can later be consolidated
2218 */
1d1d807e 2219 public static function init($value) {
6bbd4858
SH
2220 $important = '';
2221 if (strpos($value, '!important') !== false) {
2222 $important = ' !important';
2223 $value = str_replace('!important', '', $value);
2224 }
2225
2226 $value = preg_replace('#\s+#', ' ', trim($value));
1d1d807e
SH
2227 $bits = explode(' ', $value, 4);
2228
2229 $top = $right = $bottom = $left = null;
2230 if (count($bits) > 0) {
2231 $top = $right = $bottom = $left = array_shift($bits);
2232 }
2233 if (count($bits) > 0) {
2234 $right = $left = array_shift($bits);
2235 }
2236 if (count($bits) > 0) {
2237 $bottom = array_shift($bits);
2238 }
2239 if (count($bits) > 0) {
2240 $left = array_shift($bits);
2241 }
2242 return array(
6bbd4858
SH
2243 new css_style_margintop('margin-top', $top.$important),
2244 new css_style_marginright('margin-right', $right.$important),
2245 new css_style_marginbottom('margin-bottom', $bottom.$important),
2246 new css_style_marginleft('margin-left', $left.$important)
1d1d807e
SH
2247 );
2248 }
0abd4846
SH
2249
2250 /**
2251 * Consolidates individual margin styles into a single margin style
2252 *
2253 * @param array $styles
2254 * @return array An array of consolidated styles
2255 */
1d1d807e
SH
2256 public static function consolidate(array $styles) {
2257 if (count($styles) != 4) {
2258 return $styles;
2259 }
2260 $top = $right = $bottom = $left = null;
2261 foreach ($styles as $style) {
2262 switch ($style->get_name()) {
2263 case 'margin-top' : $top = $style->get_value();break;
2264 case 'margin-right' : $right = $style->get_value();break;
2265 case 'margin-bottom' : $bottom = $style->get_value();break;
2266 case 'margin-left' : $left = $style->get_value();break;
2267 }
2268 }
2269 if ($top == $bottom && $left == $right) {
2270 if ($top == $left) {
2271 return array(new css_style_margin('margin', $top));
2272 } else {
2273 return array(new css_style_margin('margin', "{$top} {$left}"));
2274 }
2275 } else if ($left == $right) {
2276 return array(new css_style_margin('margin', "{$top} {$right} {$bottom}"));
2277 } else {
2278 return array(new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}"));
2279 }
1d1d807e
SH
2280 }
2281}
2282
0abd4846
SH
2283/**
2284 * A margin top style
2285 *
f37f608e
SH
2286 * @package core_css
2287 * @category css
6bbd4858 2288 * @copyright 2012 Sam Hemelryk
f37f608e 2289 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2290 */
6bbd4858 2291class css_style_margintop extends css_style_margin {
f37f608e 2292
0abd4846
SH
2293 /**
2294 * A simple init, just a single style
2295 *
ff8e5d47 2296 * @param string $value The value the style has
0abd4846
SH
2297 * @return css_style_margintop
2298 */
1d1d807e
SH
2299 public static function init($value) {
2300 return new css_style_margintop('margin-top', $value);
2301 }
f37f608e 2302
0abd4846
SH
2303 /**
2304 * This style can be consolidated into a single margin style
2305 *
2306 * @return string
2307 */
1d1d807e
SH
2308 public function consolidate_to() {
2309 return 'margin';
2310 }
2311}
2312
0abd4846
SH
2313/**
2314 * A margin right style
2315 *
f37f608e
SH
2316 * @package core_css
2317 * @category css
6bbd4858 2318 * @copyright 2012 Sam Hemelryk
f37f608e 2319 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2320 */
6bbd4858
SH
2321class css_style_marginright extends css_style_margin {
2322
0abd4846
SH
2323 /**
2324 * A simple init, just a single style
2325 *
ff8e5d47 2326 * @param string $value The value the style has
0abd4846
SH
2327 * @return css_style_margintop
2328 */
1d1d807e
SH
2329 public static function init($value) {
2330 return new css_style_marginright('margin-right', $value);
2331 }
6bbd4858 2332
0abd4846
SH
2333 /**
2334 * This style can be consolidated into a single margin style
2335 *
2336 * @return string
2337 */
1d1d807e
SH
2338 public function consolidate_to() {
2339 return 'margin';
2340 }
2341}
2342
0abd4846
SH
2343/**
2344 * A margin bottom style
2345 *
f37f608e
SH
2346 * @package core_css
2347 * @category css
6bbd4858 2348 * @copyright 2012 Sam Hemelryk
f37f608e 2349 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2350 */
6bbd4858
SH
2351class css_style_marginbottom extends css_style_margin {
2352
0abd4846
SH
2353 /**
2354 * A simple init, just a single style
2355 *
ff8e5d47 2356 * @param string $value The value the style has
0abd4846
SH
2357 * @return css_style_margintop
2358 */
1d1d807e
SH
2359 public static function init($value) {
2360 return new css_style_marginbottom('margin-bottom', $value);
2361 }
6bbd4858 2362
0abd4846
SH
2363 /**
2364 * This style can be consolidated into a single margin style
2365 *
2366 * @return string
2367 */
1d1d807e
SH
2368 public function consolidate_to() {
2369 return 'margin';
2370 }
2371}
2372
0abd4846
SH
2373/**
2374 * A margin left style
2375 *
f37f608e
SH
2376 * @package core_css
2377 * @category css
6bbd4858 2378 * @copyright 2012 Sam Hemelryk
f37f608e 2379 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2380 */
6bbd4858
SH
2381class css_style_marginleft extends css_style_margin {
2382
0abd4846
SH
2383 /**
2384 * A simple init, just a single style
2385 *
ff8e5d47 2386 * @param string $value The value the style has
0abd4846
SH
2387 * @return css_style_margintop
2388 */
1d1d807e
SH
2389 public static function init($value) {
2390 return new css_style_marginleft('margin-left', $value);
2391 }
6bbd4858 2392
0abd4846
SH
2393 /**
2394 * This style can be consolidated into a single margin style
2395 *
2396 * @return string
2397 */
1d1d807e
SH
2398 public function consolidate_to() {
2399 return 'margin';
2400 }
2401}
2402
2403/**
2404 * A border style
2405 *
f37f608e
SH
2406 * @package core_css
2407 * @category css
6bbd4858 2408 * @copyright 2012 Sam Hemelryk
f37f608e 2409 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e
SH
2410 */
2411class css_style_border extends css_style {
6bbd4858 2412
0abd4846
SH
2413 /**
2414 * Initalises the border style into an array of individual style compontents
2415 *
ff8e5d47 2416 * @param string $value The value the style has
0abd4846
SH
2417 * @return css_style_bordercolor
2418 */
1d1d807e
SH
2419 public static function init($value) {
2420 $value = preg_replace('#\s+#', ' ', $value);
2421 $bits = explode(' ', $value, 3);
2422
2423 $return = array();
2424 if (count($bits) > 0) {
2425 $width = array_shift($bits);
f2e8d379 2426 if (!css_style_borderwidth::is_border_width($width)) {
6bbd4858
SH
2427 $width = '0';
2428 }
2429 $return[] = new css_style_borderwidth('border-top-width', $width);
2430 $return[] = new css_style_borderwidth('border-right-width', $width);
2431 $return[] = new css_style_borderwidth('border-bottom-width', $width);
2432 $return[] = new css_style_borderwidth('border-left-width', $width);
1d1d807e
SH
2433 }
2434 if (count($bits) > 0) {
2435 $style = array_shift($bits);
6bbd4858
SH
2436 $return[] = new css_style_borderstyle('border-top-style', $style);
2437 $return[] = new css_style_borderstyle('border-right-style', $style);
2438 $return[] = new css_style_borderstyle('border-bottom-style', $style);
2439 $return[] = new css_style_borderstyle('border-left-style', $style);
1d1d807e
SH
2440 }
2441 if (count($bits) > 0) {
2442 $colour = array_shift($bits);
6bbd4858
SH
2443 $return[] = new css_style_bordercolor('border-top-color', $colour);
2444 $return[] = new css_style_bordercolor('border-right-color', $colour);
2445 $return[] = new css_style_bordercolor('border-bottom-color', $colour);
2446 $return[] = new css_style_bordercolor('border-left-color', $colour);
1d1d807e
SH
2447 }
2448 return $return;
2449 }
6bbd4858 2450
0abd4846
SH
2451 /**
2452 * Consolidates all border styles into a single style
2453 *
ff8e5d47
SH
2454 * @param array $styles An array of border styles
2455 * @return array An optimised array of border styles
0abd4846 2456 */
1d1d807e
SH
2457 public static function consolidate(array $styles) {
2458
2459 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2460 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2461 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null);
2462
2463 foreach ($styles as $style) {
2464 switch ($style->get_name()) {
6bbd4858
SH
2465 case 'border-top-width': $borderwidths['top'] = $style->get_value(); break;
2466 case 'border-right-width': $borderwidths['right'] = $style->get_value(); break;
2467 case 'border-bottom-width': $borderwidths['bottom'] = $style->get_value(); break;
2468 case 'border-left-width': $borderwidths['left'] = $style->get_value(); break;
2469
2470 case 'border-top-style': $borderstyles['top'] = $style->get_value(); break;
2471 case 'border-right-style': $borderstyles['right'] = $style->get_value(); break;
2472 case 'border-bottom-style': $borderstyles['bottom'] = $style->get_value(); break;
2473 case 'border-left-style': $borderstyles['left'] = $style->get_value(); break;
2474
2475 case 'border-top-color': $bordercolors['top'] = $style->get_value(); break;
2476 case 'border-right-color': $bordercolors['right'] = $style->get_value(); break;
2477 case 'border-bottom-color': $bordercolors['bottom'] = $style->get_value(); break;
2478 case 'border-left-color': $bordercolors['left'] = $style->get_value(); break;
1d1d807e
SH
2479 }
2480 }
2481
2482 $uniquewidths = count(array_unique($borderwidths));
2483 $uniquestyles = count(array_unique($borderstyles));
2484 $uniquecolors = count(array_unique($bordercolors));
2485
6bbd4858
SH
2486 $nullwidths = in_array(null, $borderwidths, true);
2487 $nullstyles = in_array(null, $borderstyles, true);
2488 $nullcolors = in_array(null, $bordercolors, true);
1d1d807e 2489
50836f26
SH
2490 $allwidthsthesame = ($uniquewidths === 1)?1:0;
2491 $allstylesthesame = ($uniquestyles === 1)?1:0;
2492 $allcolorsthesame = ($uniquecolors === 1)?1:0;
1d1d807e
SH
2493
2494 $allwidthsnull = $allwidthsthesame && $nullwidths;
2495 $allstylesnull = $allstylesthesame && $nullstyles;
2496 $allcolorsnull = $allcolorsthesame && $nullcolors;
2497
2498 $return = array();
2499 if ($allwidthsnull && $allstylesnull && $allcolorsnull) {
2500 // Everything is null still... boo
2501 return array(new css_style_border('border', ''));
2502
2503 } else if ($allwidthsnull && $allstylesnull) {
2504
2505 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2506 return $return;
2507
2508 } else if ($allwidthsnull && $allcolorsnull) {
2509
2510 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2511 return $return;
2512
2513 } else if ($allcolorsnull && $allstylesnull) {
2514
2515 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2516 return $return;
2517
2518 }
2519
2520 if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 3) {
2521
2522 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']);
0abd4846 2523
1d1d807e
SH
2524 } else if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 2) {
2525
2526 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) {
2527
2528 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']);
2529 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
0abd4846 2530
1d1d807e
SH
2531 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) {
2532
2533 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']);
2534 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
0abd4846 2535
1d1d807e
SH
2536 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) {
2537
2538 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']);
2539 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2540
2541 } else {
2542 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths);
2543 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles);
2544 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors);
2545 }
2546
50836f26 2547 } 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
2548 $widthkeys = array();
2549 $stylekeys = array();
2550 $colorkeys = array();
2551
2552 foreach ($borderwidths as $key => $value) {
2553 if (!array_key_exists($value, $widthkeys)) {
2554 $widthkeys[$value] = array();
2555 }
2556 $widthkeys[$value][] = $key;
2557 }
2558 usort($widthkeys, 'css_sort_by_count');
2559 $widthkeys = array_values($widthkeys);
2560
2561 foreach ($borderstyles as $key => $value) {
2562 if (!array_key_exists($value, $stylekeys)) {
2563 $stylekeys[$value] = array();
2564 }
2565 $stylekeys[$value][] = $key;
2566 }
2567 usort($stylekeys, 'css_sort_by_count');
2568 $stylekeys = array_values($stylekeys);
2569
2570 foreach ($bordercolors as $key => $value) {
2571 if (!array_key_exists($value, $colorkeys)) {
2572 $colorkeys[$value] = array();
2573 }
2574 $colorkeys[$value][] = $key;
2575 }
2576 usort($colorkeys, 'css_sort_by_count');
2577 $colorkeys = array_values($colorkeys);
2578
2579 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) {
2580 $key = $widthkeys[0][0];
50836f26 2581 self::build_style_string($return, 'css_style_border', 'border', $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
1d1d807e 2582 $key = $widthkeys[1][0];
50836f26 2583 self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key, $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]);
1d1d807e
SH
2584 } else {
2585 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2586 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2587 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2588 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2589 }
2590 } else {
2591 self::build_style_string($return, 'css_style_bordertop', 'border-top', $borderwidths['top'], $borderstyles['top'], $bordercolors['top']);
2592 self::build_style_string($return, 'css_style_borderright', 'border-right', $borderwidths['right'], $borderstyles['right'], $bordercolors['right']);
2593 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']);
2594 self::build_style_string($return, 'css_style_borderleft', 'border-left', $borderwidths['left'], $borderstyles['left'], $bordercolors['left']);
2595 }
2596 foreach ($return as $key => $style) {
2597 if ($style->get_value() == '') {
2598 unset($return[$key]);
2599 }
2600 }
2601 return $return;
2602 }
6bbd4858 2603
0abd4846
SH
2604 /**
2605 * Border styles get consolidated to a single border style.
2606 *
2607 * @return string
2608 */
1d1d807e
SH
2609 public function consolidate_to() {
2610 return 'border';
2611 }
6bbd4858 2612
0abd4846 2613 /**
f37f608e
SH
2614 * Consolidates a series of border styles into an optimised array of border
2615 * styles by looking at the direction of the border and prioritising that
2616 * during the optimisation.
0abd4846
SH
2617 *
2618 * @param array $array An array to add styles into during consolidation. Passed by reference.
2619 * @param string $class The class type to initalise
2620 * @param string $style The style to create
2621 * @param string|array $top The top value
2622 * @param string $right The right value
2623 * @param string $bottom The bottom value
2624 * @param string $left The left value
2625 * @return bool
2626 */
1d1d807e 2627 public static function consolidate_styles_by_direction(&$array, $class, $style, $top, $right = null, $bottom = null, $left = null) {
1d1d807e
SH
2628 if (is_array($top)) {
2629 $right = $top['right'];
2630 $bottom = $top['bottom'];
2631 $left = $top['left'];
2632 $top = $top['top'];
2633 }
2634
2635 if ($top == $bottom && $left == $right && $top == $left) {
6bbd4858 2636 if (is_null($top)) {
1d1d807e
SH
2637 $array[] = new $class($style, '');
2638 } else {
2639 $array[] = new $class($style, $top);
2640 }
2641 } else if ($top == null || $right == null || $bottom == null || $left == null) {
2642 if ($top !== null) {
6bbd4858 2643 $array[] = new $class(str_replace('border-', 'border-top-', $style), $top);
1d1d807e
SH
2644 }
2645 if ($right !== null) {
6bbd4858 2646 $array[] = new $class(str_replace('border-', 'border-right-', $style), $right);
1d1d807e
SH
2647 }
2648 if ($bottom !== null) {
6bbd4858 2649 $array[] = new $class(str_replace('border-', 'border-bottom-', $style), $bottom);
1d1d807e
SH
2650 }
2651 if ($left !== null) {
6bbd4858 2652 $array[] = new $class(str_replace('border-', 'border-left-', $style), $left);
1d1d807e
SH
2653 }
2654 } else if ($top == $bottom && $left == $right) {
2655 $array[] = new $class($style, $top.' '.$right);
2656 } else if ($left == $right) {
2657 $array[] = new $class($style, $top.' '.$right.' '.$bottom);
2658 } else {
2659 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left);
2660 }
2661 return true;
2662 }
0abd4846
SH
2663
2664 /**
2665 * Builds a border style for a set of width, style, and colour values
2666 *
2667 * @param array $array An array into which the generated style is added
2668 * @param string $class The class type to initialise
2669 * @param string $cssstyle The style to use
2670 * @param string $width The width of the border
2671 * @param string $style The style of the border
2672 * @param string $color The colour of the border
2673 * @return bool
2674 */
1d1d807e
SH
2675 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) {
2676 if (!is_null($width) && !is_null($style) && !is_null($color)) {
2677 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color);
2678 } else if (!is_null($width) && !is_null($style) && is_null($color)) {
2679 $array[] = new $class($cssstyle, $width.' '.$style);
2680 } else if (!is_null($width) && is_null($style) && is_null($color)) {
6bbd4858 2681 $array[] = new $class($cssstyle, $width);
1d1d807e 2682 } else {
6bbd4858
SH
2683 if (!is_null($width)) $array[] = new $class($cssstyle, $width);
2684 if (!is_null($style)) $array[] = new $class($cssstyle, $style);
2685 if (!is_null($color)) $array[] = new $class($cssstyle, $color);
1d1d807e
SH
2686 }
2687 return true;
2688 }
2689}
2690
1d1d807e
SH
2691/**
2692 * A border colour style
2693 *
f37f608e
SH
2694 * @package core_css
2695 * @category css
6bbd4858 2696 * @copyright 2012 Sam Hemelryk
f37f608e 2697 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e
SH
2698 */
2699class css_style_bordercolor extends css_style_color {
6bbd4858 2700
1d1d807e
SH
2701 /**
2702 * Creates a new border colour style
2703 *
2704 * Based upon the colour style
2705 *
2706 * @param mixed $value
ff8e5d47 2707 * @return Array of css_style_bordercolor
1d1d807e
SH
2708 */
2709 public static function init($value) {
2710 $value = preg_replace('#\s+#', ' ', $value);
2711 $bits = explode(' ', $value, 4);
2712
2713 $top = $right = $bottom = $left = null;
2714 if (count($bits) > 0) {
2715 $top = $right = $bottom = $left = array_shift($bits);
2716 }
2717 if (count($bits) > 0) {
2718 $right = $left = array_shift($bits);
2719 }
2720 if (count($bits) > 0) {
2721 $bottom = array_shift($bits);
2722 }
2723 if (count($bits) > 0) {
2724 $left = array_shift($bits);
2725 }
2726 return array(
6bbd4858
SH
2727 css_style_bordertopcolor::init($top),
2728 css_style_borderrightcolor::init($right),
2729 css_style_borderbottomcolor::init($bottom),
2730 css_style_borderleftcolor::init($left)
1d1d807e
SH
2731 );
2732 }
6bbd4858 2733
0abd4846
SH
2734 /**
2735 * Consolidate this to a single border style
2736 *
2737 * @return string
2738 */
1d1d807e
SH
2739 public function consolidate_to() {
2740 return 'border';
2741 }
6bbd4858 2742
0abd4846
SH
2743 /**
2744 * Cleans the value
2745 *
ff8e5d47 2746 * @param string $value Cleans the provided value optimising it if possible
0abd4846
SH
2747 * @return string
2748 */
1d1d807e
SH
2749 protected function clean_value($value) {
2750 $values = explode(' ', $value);
2751 $values = array_map('parent::clean_value', $values);
2752 return join (' ', $values);
2753 }
6bbd4858 2754
0abd4846
SH
2755 /**
2756 * Outputs this style
2757 *
2758 * @param string $overridevalue
2759 * @return string
2760 */
50836f26
SH
2761 public function out($overridevalue = null) {
2762 if ($overridevalue === null) {
2763 $overridevalue = $this->value;
2764 }
2765 $values = explode(' ', $overridevalue);
2766 $values = array_map('css_style_color::shrink_value', $values);
2767 return parent::out(join (' ', $values));
2768 }
1d1d807e
SH
2769}
2770
0abd4846
SH
2771/**
2772 * A border left style
2773 *
f37f608e
SH
2774 * @package core_css
2775 * @category css
6bbd4858 2776 * @copyright 2012 Sam Hemelryk
f37f608e 2777 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2778 */
1d1d807e 2779class css_style_borderleft extends css_style_generic {
6bbd4858 2780
0abd4846
SH
2781 /**
2782 * Initialises the border left style into individual components
2783 *
2784 * @param string $value
ff8e5d47 2785 * @return array Array of css_style_borderleftwidth|css_style_borderleftstyle|css_style_borderleftcolor
0abd4846 2786 */
1d1d807e
SH
2787 public static function init($value) {
2788 $value = preg_replace('#\s+#', ' ', $value);
2789 $bits = explode(' ', $value, 3);
2790
2791 $return = array();
2792 if (count($bits) > 0) {
6bbd4858 2793 $return[] = css_style_borderleftwidth::init(array_shift($bits));
1d1d807e
SH
2794 }
2795 if (count($bits) > 0) {
6bbd4858 2796 $return[] = css_style_borderleftstyle::init(array_shift($bits));
1d1d807e
SH
2797 }
2798 if (count($bits) > 0) {
6bbd4858 2799 $return[] = css_style_borderleftcolor::init(array_shift($bits));
1d1d807e
SH
2800 }
2801 return $return;
2802 }
6bbd4858 2803
0abd4846
SH
2804 /**
2805 * Consolidate this to a single border style
2806 *
2807 * @return string
2808 */
1d1d807e
SH
2809 public function consolidate_to() {
2810 return 'border';
2811 }
2812}
2813
0abd4846
SH
2814/**
2815 * A border right style
2816 *
f37f608e
SH
2817 * @package core_css
2818 * @category css
6bbd4858 2819 * @copyright 2012 Sam Hemelryk
f37f608e 2820 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2821 */
1d1d807e 2822class css_style_borderright extends css_style_generic {
6bbd4858 2823
0abd4846
SH
2824 /**
2825 * Initialises the border right style into individual components
2826 *
ff8e5d47
SH
2827 * @param string $value The value of the style
2828 * @return array Array of css_style_borderrightwidth|css_style_borderrightstyle|css_style_borderrightcolor
0abd4846 2829 */
1d1d807e
SH
2830 public static function init($value) {
2831 $value = preg_replace('#\s+#', ' ', $value);
2832 $bits = explode(' ', $value, 3);
2833
2834 $return = array();
2835 if (count($bits) > 0) {
6bbd4858 2836 $return[] = css_style_borderrightwidth::init(array_shift($bits));
1d1d807e
SH
2837 }
2838 if (count($bits) > 0) {
6bbd4858 2839 $return[] = css_style_borderrightstyle::init(array_shift($bits));
1d1d807e
SH
2840 }
2841 if (count($bits) > 0) {
6bbd4858 2842 $return[] = css_style_borderrightcolor::init(array_shift($bits));
1d1d807e
SH
2843 }
2844 return $return;
2845 }
6bbd4858 2846
0abd4846
SH
2847 /**
2848 * Consolidate this to a single border style
2849 *
2850 * @return string
2851 */
1d1d807e
SH
2852 public function consolidate_to() {
2853 return 'border';
2854 }
2855}
2856
0abd4846
SH
2857/**
2858 * A border top style
2859 *
f37f608e
SH
2860 * @package core_css
2861 * @category css
6bbd4858 2862 * @copyright 2012 Sam Hemelryk
f37f608e 2863 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2864 */
1d1d807e 2865class css_style_bordertop extends css_style_generic {
6bbd4858 2866
0abd4846
SH
2867 /**
2868 * Initialises the border top style into individual components
2869 *
ff8e5d47
SH
2870 * @param string $value The value of the style
2871 * @return array Array of css_style_bordertopwidth|css_style_bordertopstyle|css_style_bordertopcolor
0abd4846 2872 */
1d1d807e
SH
2873 public static function init($value) {
2874 $value = preg_replace('#\s+#', ' ', $value);
2875 $bits = explode(' ', $value, 3);
2876
2877 $return = array();
2878 if (count($bits) > 0) {
6bbd4858 2879 $return[] = css_style_bordertopwidth::init(array_shift($bits));
1d1d807e
SH
2880 }
2881 if (count($bits) > 0) {
6bbd4858 2882 $return[] = css_style_bordertopstyle::init(array_shift($bits));
1d1d807e
SH
2883 }
2884 if (count($bits) > 0) {
6bbd4858 2885 $return[] = css_style_bordertopcolor::init(array_shift($bits));
1d1d807e
SH
2886 }
2887 return $return;
2888 }
6bbd4858 2889
0abd4846
SH
2890 /**
2891 * Consolidate this to a single border style
2892 *
2893 * @return string
2894 */
1d1d807e
SH
2895 public function consolidate_to() {
2896 return 'border';
2897 }
2898}
2899
0abd4846
SH
2900/**
2901 * A border bottom style
2902 *
f37f608e
SH
2903 * @package core_css
2904 * @category css
6bbd4858 2905 * @copyright 2012 Sam Hemelryk
f37f608e 2906 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 2907 */
1d1d807e 2908class css_style_borderbottom extends css_style_generic {
6bbd4858 2909
0abd4846
SH
2910 /**
2911 * Initialises the border bottom style into individual components
2912 *
ff8e5d47
SH
2913 * @param string $value The value of the style
2914 * @return array Array of css_style_borderbottomwidth|css_style_borderbottomstyle|css_style_borderbottomcolor
0abd4846 2915 */
1d1d807e
SH
2916 public static function init($value) {
2917 $value = preg_replace('#\s+#', ' ', $value);
2918 $bits = explode(' ', $value, 3);
2919
2920 $return = array();
2921 if (count($bits) > 0) {
6bbd4858 2922 $return[] = css_style_borderbottomwidth::init(array_shift($bits));
1d1d807e
SH
2923 }
2924 if (count($bits) > 0) {
6bbd4858 2925 $return[] = css_style_borderbottomstyle::init(array_shift($bits));
1d1d807e
SH
2926 }
2927 if (count($bits) > 0) {
6bbd4858 2928 $return[] = css_style_borderbottomcolor::init(array_shift($bits));
1d1d807e
SH
2929 }
2930 return $return;
2931 }
6bbd4858 2932
0abd4846
SH
2933 /**
2934 * Consolidate this to a single border style
2935 *
2936 * @return string
2937 */
1d1d807e
SH
2938 public function consolidate_to() {
2939 return 'border';
2940 }
2941}
2942
2943/**
2944 * A border width style
2945 *
f37f608e
SH
2946 * @package core_css
2947 * @category css
6bbd4858 2948 * @copyright 2012 Sam Hemelryk
f37f608e 2949 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e 2950 */
6bbd4858
SH
2951class css_style_borderwidth extends css_style_width {
2952
1d1d807e
SH
2953 /**
2954 * Creates a new border colour style
2955 *
2956 * Based upon the colour style
2957 *
ff8e5d47
SH
2958 * @param string $value The value of the style
2959 * @return array Array of css_style_border*width
1d1d807e
SH
2960 */
2961 public static function init($value) {
2962 $value = preg_replace('#\s+#', ' ', $value);
2963 $bits = explode(' ', $value, 4);
2964
2965 $top = $right = $bottom = $left = null;
2966 if (count($bits) > 0) {
2967 $top = $right = $bottom = $left = array_shift($bits);
2968 }
2969 if (count($bits) > 0) {
2970 $right = $left = array_shift($bits);
2971 }
2972 if (count($bits) > 0) {
2973 $bottom = array_shift($bits);
2974 }
2975 if (count($bits) > 0) {
2976 $left = array_shift($bits);
2977 }
2978 return array(
6bbd4858
SH
2979 css_style_bordertopwidth::init($top),
2980 css_style_borderrightwidth::init($right),
2981 css_style_borderbottomwidth::init($bottom),
2982 css_style_borderleftwidth::init($left)
1d1d807e
SH
2983 );
2984 }
6bbd4858 2985
0abd4846
SH
2986 /**
2987 * Consolidate this to a single border style
2988 *
2989 * @return string
2990 */
1d1d807e
SH
2991 public function consolidate_to() {
2992 return 'border';
2993 }
f2e8d379
SH
2994
2995 /**
2996 * Checks if the width is valid
2997 * @return bool
2998 */
2999 public function is_valid() {
3000 return self::is_border_width($this->value);
3001 }
3002
3003 /**
3004 * Cleans the provided value
3005 *
3006 * @param mixed $value Cleans the provided value optimising it if possible
3007 * @return string
3008 */
3009 protected function clean_value($value) {
3010 $isvalid = self::is_border_width($value);
3011 if (!$isvalid) {
3012 $this->set_error('Invalid width specified for '.$this->name);
3013 } else if (preg_match('#^0\D+$#', $value)) {
3014 return '0';
3015 }
3016 return trim($value);
3017 }
3018
3019 public static function is_border_width($value) {
3020 $altwidthvalues = array('thin', 'medium', 'thick');
3021 return css_is_width($value) || in_array($value, $altwidthvalues);
3022 }
1d1d807e
SH
3023}
3024
3025/**
3026 * A border style style
3027 *
f37f608e
SH
3028 * @package core_css
3029 * @category css
6bbd4858 3030 * @copyright 2012 Sam Hemelryk
f37f608e 3031 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1d1d807e
SH
3032 */
3033class css_style_borderstyle extends css_style_generic {
6bbd4858 3034
1d1d807e
SH
3035 /**
3036 * Creates a new border colour style
3037 *
3038 * Based upon the colour style
3039 *
ff8e5d47
SH
3040 * @param string $value The value of the style
3041 * @return array Array of css_style_border*style
1d1d807e
SH
3042 */
3043 public static function init($value) {
3044 $value = preg_replace('#\s+#', ' ', $value);
3045 $bits = explode(' ', $value, 4);
3046
3047 $top = $right = $bottom = $left = null;
3048 if (count($bits) > 0) {
3049 $top = $right = $bottom = $left = array_shift($bits);
3050 }
3051 if (count($bits) > 0) {
3052 $right = $left = array_shift($bits);
3053 }
3054 if (count($bits) > 0) {
3055 $bottom = array_shift($bits);
3056 }
3057 if (count($bits) > 0) {
3058 $left = array_shift($bits);
3059 }
3060 return array(
6bbd4858
SH
3061 css_style_bordertopstyle::init($top),
3062 css_style_borderrightstyle::init($right),
3063 css_style_borderbottomstyle::init($bottom),
3064 css_style_borderleftstyle::init($left)
1d1d807e
SH
3065 );
3066 }
6bbd4858 3067
0abd4846
SH
3068 /**
3069 * Consolidate this to a single border style
3070 *
3071 * @return string
3072 */
1d1d807e
SH
3073 public function consolidate_to() {
3074 return 'border';
3075 }
3076}
3077
0abd4846
SH
3078/**
3079 * A border top colour style
3080 *
f37f608e
SH
3081 * @package core_css
3082 * @category css
6bbd4858 3083 * @copyright 2012 Sam Hemelryk
f37f608e 3084 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3085 */
6bbd4858
SH
3086class css_style_bordertopcolor extends css_style_bordercolor {
3087
0abd4846
SH
3088 /**
3089 * Initialises this style object
3090 *
ff8e5d47 3091 * @param string $value The value of the style
6bbd4858 3092 * @return css_style_bordertopcolor
0abd4846 3093 */
50836f26 3094 public static function init($value) {
6bbd4858 3095 return new css_style_bordertopcolor('border-top-color', $value);
50836f26 3096 }
6bbd4858 3097
0abd4846
SH
3098 /**
3099 * Consolidate this to a single border style
3100 *
3101 * @return string
3102 */
50836f26
SH
3103 public function consolidate_to() {
3104 return 'border';
3105 }
3106}
0abd4846
SH
3107
3108/**
3109 * A border left colour style
3110 *
f37f608e
SH
3111 * @package core_css
3112 * @category css
6bbd4858 3113 * @copyright 2012 Sam Hemelryk
f37f608e 3114 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3115 */
6bbd4858
SH
3116class css_style_borderleftcolor extends css_style_bordercolor {
3117
0abd4846
SH
3118 /**
3119 * Initialises this style object
3120 *
ff8e5d47 3121 * @param string $value The value of the style
6bbd4858 3122 * @return css_style_borderleftcolor
0abd4846 3123 */
50836f26 3124 public static function init($value) {
6bbd4858 3125 return new css_style_borderleftcolor('border-left-color', $value);
50836f26 3126 }
6bbd4858 3127
0abd4846
SH
3128 /**
3129 * Consolidate this to a single border style
3130 *
3131 * @return string
3132 */
50836f26
SH
3133 public function consolidate_to() {
3134 return 'border';
3135 }
3136}
0abd4846
SH
3137
3138/**
3139 * A border right colour style
3140 *
f37f608e
SH
3141 * @package core_css
3142 * @category css
6bbd4858 3143 * @copyright 2012 Sam Hemelryk
f37f608e 3144 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3145 */
6bbd4858
SH
3146class css_style_borderrightcolor extends css_style_bordercolor {
3147
0abd4846
SH
3148 /**
3149 * Initialises this style object
3150 *
ff8e5d47 3151 * @param string $value The value of the style
6bbd4858 3152 * @return css_style_borderrightcolor
0abd4846 3153 */
50836f26 3154 public static function init($value) {
6bbd4858 3155 return new css_style_borderrightcolor('border-right-color', $value);
50836f26 3156 }
6bbd4858 3157
0abd4846
SH
3158 /**
3159 * Consolidate this to a single border style
3160 *
3161 * @return string
3162 */
50836f26
SH
3163 public function consolidate_to() {
3164 return 'border';
3165 }
3166}
0abd4846
SH
3167
3168/**
3169 * A border bottom colour style
3170 *
f37f608e
SH
3171 * @package core_css
3172 * @category css
6bbd4858 3173 * @copyright 2012 Sam Hemelryk
f37f608e 3174 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3175 */
6bbd4858
SH
3176class css_style_borderbottomcolor extends css_style_bordercolor {
3177
0abd4846
SH
3178 /**
3179 * Initialises this style object
3180 *
ff8e5d47 3181 * @param string $value The value of the style
6bbd4858 3182 * @return css_style_borderbottomcolor
0abd4846 3183 */
50836f26 3184 public static function init($value) {
6bbd4858 3185 return new css_style_borderbottomcolor('border-bottom-color', $value);
50836f26 3186 }
6bbd4858 3187
0abd4846
SH
3188 /**
3189 * Consolidate this to a single border style
3190 *
3191 * @return string
3192 */
50836f26
SH
3193 public function consolidate_to() {
3194 return 'border';
3195 }
3196}
3197
0abd4846
SH
3198/**
3199 * A border width top style
3200 *
f37f608e
SH
3201 * @package core_css
3202 * @category css
6bbd4858 3203 * @copyright 2012 Sam Hemelryk
f37f608e 3204 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3205 */
6bbd4858
SH
3206class css_style_bordertopwidth extends css_style_borderwidth {
3207
0abd4846
SH
3208 /**
3209 * Initialises this style object
3210 *
ff8e5d47 3211 * @param string $value The value of the style
6bbd4858 3212 * @return css_style_bordertopwidth
0abd4846 3213 */
50836f26 3214 public static function init($value) {
6bbd4858 3215 return new css_style_bordertopwidth('border-top-width', $value);
50836f26 3216 }
6bbd4858 3217
0abd4846
SH
3218 /**
3219 * Consolidate this to a single border style
3220 *
3221 * @return string
3222 */
50836f26
SH
3223 public function consolidate_to() {
3224 return 'border';
3225 }
3226}
0abd4846
SH
3227
3228/**
3229 * A border width left style
3230 *
f37f608e
SH
3231 * @package core_css
3232 * @category css
6bbd4858 3233 * @copyright 2012 Sam Hemelryk
f37f608e 3234 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3235 */
6bbd4858
SH
3236class css_style_borderleftwidth extends css_style_borderwidth {
3237
0abd4846
SH
3238 /**
3239 * Initialises this style object
3240 *
ff8e5d47 3241 * @param string $value The value of the style
6bbd4858 3242 * @return css_style_borderleftwidth
0abd4846 3243 */
50836f26 3244 public static function init($value) {
6bbd4858 3245 return new css_style_borderleftwidth('border-left-width', $value);
50836f26 3246 }
6bbd4858 3247
0abd4846
SH
3248 /**
3249 * Consolidate this to a single border style
3250 *
3251 * @return string
3252 */
50836f26
SH
3253 public function consolidate_to() {
3254 return 'border';
3255 }
3256}
0abd4846
SH
3257
3258/**
3259 * A border width right style
3260 *
f37f608e
SH
3261 * @package core_css
3262 * @category css
6bbd4858 3263 * @copyright 2012 Sam Hemelryk
f37f608e 3264 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3265 */
6bbd4858
SH
3266class css_style_borderrightwidth extends css_style_borderwidth {
3267
0abd4846
SH
3268 /**
3269 * Initialises this style object
3270 *
ff8e5d47 3271 * @param string $value The value of the style
6bbd4858 3272 * @return css_style_borderrightwidth
0abd4846 3273 */
50836f26 3274 public static function init($value) {
6bbd4858 3275 return new css_style_borderrightwidth('border-right-width', $value);
50836f26 3276 }
6bbd4858 3277
0abd4846
SH
3278 /**
3279 * Consolidate this to a single border style
3280 *
3281 * @return string
3282 */
50836f26
SH
3283 public function consolidate_to() {
3284 return 'border';
3285 }
3286}
0abd4846
SH
3287
3288/**
3289 * A border width bottom style
3290 *
f37f608e
SH
3291 * @package core_css
3292 * @category css
6bbd4858 3293 * @copyright 2012 Sam Hemelryk
f37f608e 3294 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3295 */
6bbd4858
SH
3296class css_style_borderbottomwidth extends css_style_borderwidth {
3297
0abd4846
SH
3298 /**
3299 * Initialises this style object
3300 *
ff8e5d47 3301 * @param string $value The value of the style
6bbd4858 3302 * @return css_style_borderbottomwidth
0abd4846 3303 */
50836f26 3304 public static function init($value) {
6bbd4858 3305 return new css_style_borderbottomwidth('border-bottom-width', $value);
50836f26 3306 }
6bbd4858 3307
0abd4846
SH
3308 /**
3309 * Consolidate this to a single border style
3310 *
3311 * @return string
3312 */
50836f26
SH
3313 public function consolidate_to() {
3314 return 'border';
3315 }
3316}
3317
0abd4846
SH
3318/**
3319 * A border top style
3320 *
f37f608e
SH
3321 * @package core_css
3322 * @category css
6bbd4858 3323 * @copyright 2012 Sam Hemelryk
f37f608e 3324 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3325 */
6bbd4858
SH
3326class css_style_bordertopstyle extends css_style_borderstyle {
3327
0abd4846
SH
3328 /**
3329 * Initialises this style object
3330 *
ff8e5d47 3331 * @param string $value The value of the style
6bbd4858 3332 * @return css_style_bordertopstyle
0abd4846 3333 */
50836f26 3334 public static function init($value) {
6bbd4858 3335 return new css_style_bordertopstyle('border-top-style', $value);
50836f26 3336 }
6bbd4858 3337
0abd4846
SH
3338 /**
3339 * Consolidate this to a single border style
3340 *
3341 * @return string
3342 */
50836f26
SH
3343 public function consolidate_to() {
3344 return 'border';
3345 }
3346}
0abd4846
SH
3347
3348/**
3349 * A border left style
3350 *
f37f608e
SH
3351 * @package core_css
3352 * @category css
6bbd4858 3353 * @copyright 2012 Sam Hemelryk
f37f608e 3354 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3355 */
6bbd4858
SH
3356class css_style_borderleftstyle extends css_style_borderstyle {
3357
0abd4846
SH
3358 /**
3359 * Initialises this style object
3360 *
ff8e5d47 3361 * @param string $value The value of the style
6bbd4858 3362 * @return css_style_borderleftstyle
0abd4846 3363 */
50836f26 3364 public static function init($value) {
6bbd4858 3365 return new css_style_borderleftstyle('border-left-style', $value);
50836f26 3366 }
6bbd4858 3367
0abd4846
SH
3368 /**
3369 * Consolidate this to a single border style
3370 *
3371 * @return string
3372 */
50836f26
SH
3373 public function consolidate_to() {
3374 return 'border';
3375 }
3376}
0abd4846
SH
3377
3378/**
3379 * A border right style
3380 *
f37f608e
SH
3381 * @package core_css
3382 * @category css
6bbd4858 3383 * @copyright 2012 Sam Hemelryk
f37f608e 3384 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3385 */
6bbd4858
SH
3386class css_style_borderrightstyle extends css_style_borderstyle {
3387
0abd4846
SH
3388 /**
3389 * Initialises this style object
3390 *
ff8e5d47 3391 * @param string $value The value of the style
6bbd4858 3392 * @return css_style_borderrightstyle
0abd4846 3393 */
50836f26 3394 public static function init($value) {
6bbd4858 3395 return new css_style_borderrightstyle('border-right-style', $value);
50836f26 3396 }
6bbd4858 3397
0abd4846
SH
3398 /**
3399 * Consolidate this to a single border style
3400 *
3401 * @return string
3402 */
50836f26
SH
3403 public function consolidate_to() {
3404 return 'border';
3405 }
3406}
0abd4846
SH
3407
3408/**
3409 * A border bottom style
3410 *
f37f608e
SH
3411 * @package core_css
3412 * @category css
6bbd4858 3413 * @copyright 2012 Sam Hemelryk
f37f608e 3414 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3415 */
6bbd4858
SH
3416class css_style_borderbottomstyle extends css_style_borderstyle {
3417
0abd4846
SH
3418 /**
3419 * Initialises this style object
3420 *
6bbd4858 3421 * @return css_style_borderbottomstyle
0abd4846 3422 */
50836f26 3423 public static function init($value) {
6bbd4858 3424 return new css_style_borderbottomstyle('border-bottom-style', $value);
50836f26 3425 }
6bbd4858 3426
0abd4846
SH
3427 /**
3428 * Consolidate this to a single border style
3429 *
3430 * @return string
3431 */
50836f26
SH
3432 public function consolidate_to() {
3433 return 'border';
3434 }
3435}
3436
0abd4846
SH
3437/**
3438 * A background style
3439 *
f37f608e
SH
3440 * @package core_css
3441 * @category css
6bbd4858 3442 * @copyright 2012 Sam Hemelryk
f37f608e 3443 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0abd4846 3444 */
1d1d807e 3445class css_style_background extends css_style {
6bbd4858
SH
3446
3447 /**
3448 * Initialises a background style
3449 *
ff8e5d47 3450 * @param type $value The value of the style
6bbd4858
SH
3451 * @return array An array of background component.
3452 */
1d1d807e
SH
3453 public static function init($value) {
3454 // colour - image - repeat - attachment - position
3455
3456 $imageurl = null;
3457 if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) {
3458 $imageurl = trim($matches[1]);
3459 $value = str_replace($matches[1], '', $value);
3460 }
3461
3462 $value = preg_replace('#\s+#', ' ', $value);
3463 $bits = explode(' ', $value);
3464
3465 $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit');
3466 $attachments = array('scroll' , 'fixed', 'inherit');
3467
3468 $return = array();
6bbd4858
SH
3469 $unknownbits = array();
3470
1d1d807e
SH
3471 if (count($bits) > 0 && css_is_colour(reset($bits))) {
3472 $return[] = new css_style_backgroundcolor('background-color', array_shift($bits));
3473 }
3474 if (count($bits) > 0 && preg_match('#(none|inherit|url\(\))#', reset($bits))) {
3475 $image = array_shift($bits);
3476 if ($image == 'url()') {
3477 $image = "url({$imageurl})";
3478 }
3479 $return[] = new css_style_backgroundimage('background-image', $image);
3480 }
3481 if (count($bits) > 0 && in_array(reset($bits), $repeats)) {
3482 $return[] = new css_style_backgroundrepeat('background-repeat', array_shift($bits));
3483 }
3484 if (count($bits) > 0 && in_array(reset($bits), $attachments)) {
3485 // scroll , fixed, inherit
3486 $return[] = new css_style_backgroundattachment('background-attachment', array_shift($bits));
3487 }
3488 if (count($bits) > 0) {
6bbd4858
SH
3489 $widthbits = array();
3490 foreach ($bits as $bit) {
3491 if (in_array($bit, array('top', 'left', 'bottom', 'right', 'center')) || css_is_width($bit)) {
3492 $widthbits[] = $bit;
3493 } else {
3494 $unknownbits[] = $bit;
3495 }
3496 }
3497 $return[] = new css_style_backgroundposition('background-position', join(' ',$widthbits));
3498 }
3499 if (count($unknownbits)) {
3500 foreach ($unknownbits as $bit) {
3501 if (css_is_colour($bit)) {
3502 $return[] = new css_style_backgroundcolor('background-color', $bit);
3503 } else if (in_array($bit, $repeats)) {
3504 $return[] = new css_style_backgroundrepeat('background-repeat', $bit);
3505 } else if (in_array($bit, $attachments)) {
3506 $return[] = new css_style_backgroundattachment('background-attachment', $bit);
3507 }
3508 }
1d1d807e
SH
3509 }
3510 return $return;
3511 }
0abd4846
SH
3512
3513 /**
3514 * Consolidates background styles into a single background style
3515 *
ff8e5d47
SH
3516 * @param array $styles Consolidates the provided array of background styles
3517 * @return array Consolidated optimised background styles
0abd4846 3518 */
1d1d807e
SH
3519 public static function consolidate(array $styles) {
3520
3521 if (count($styles) < 1) {
3522 return $styles;
3523 }
3524
3525 $color = $image = $repeat = $attachment = $position = null;
3526 foreach ($styles as $style) {
3527 switch ($style->get_name()) {
50836f26 3528 case 'background-color' : $color = css_style_color::shrink_value($style->get_value()); break;
1d1d807e
SH
3529 case 'background-image' : $image = $style->get_value(); break;
3530 case 'background-repeat' : $repeat = $style->get_value(); break;
3531 case 'background-attachment' : $attachment = $style->get_value(); break;
3532 case 'background-position' : $position = $style->get_value(); break;
3533 }
3534 }
50836f26
SH
3535
3536 if ((is_null($image) || is_null($position) || is_null($repeat)) && ($image!= null || $position != null || $repeat != null)) {
3537 return $styles;
3538 }
3539
1d1d807e
SH
3540 $value = array();
3541 if (!is_null($color)) $value[] .= $color;
3542 if (!is_null($image)) $value[] .= $image;
3543 if (!is_null($repeat)) $value[] .= $repeat;
3544 if (!is_null($attachment)) $value[] .= $attachment;
3545 if (!is_null($position)) $value[] .= $position;
3546 return array(new css_style_background('background', join(' ', $value)));
3547 }
3548}
3549
0e641c74
SH
3550/**
3551 * A background colour style.
3552 *
3553 * Based upon the colour style.
3554 *
f37f608e
SH
3555 * @package core_css
3556 * @category css
6bbd4858 3557 * @copyright 2012 Sam Hemelryk
f37f608e 3558 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
0e641c74
SH
3559 */
3560class css_style_backgroundcolor extends css_style_color {
6bbd4858 3561
0e641c74
SH
3562 /**
3563 * Creates a new background colour style
3564 *
ff8e5d47 3565 * @param string $value The value of the style
0e641c74
SH
3566 * @return css_style_backgroundcolor
3567 */
3568 public static function init($value) {
3569 return new css_style_backgroundcolor('background-color', $value);