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