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