[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This file contains CSS related class, and function for the CSS optimiser. 19 * 20 * Please see the {@link css_optimiser} class for greater detail. 21 * 22 * NOTE: these functions are not expected to be used from any addons. 23 * 24 * @package core 25 * @subpackage cssoptimiser 26 * @copyright 2012 Sam Hemelryk 27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 */ 29 30 defined('MOODLE_INTERNAL') || die(); 31 32 if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) { 33 // This can be also set in config.php file, 34 // it needs to be higher than the time it takes to generate all CSS content. 35 define('THEME_DESIGNER_CACHE_LIFETIME', 10); 36 } 37 38 /** 39 * Stores CSS in a file at the given path. 40 * 41 * This function either succeeds or throws an exception. 42 * 43 * @param theme_config $theme The theme that the CSS belongs to. 44 * @param string $csspath The path to store the CSS at. 45 * @param string $csscontent the complete CSS in one string 46 * @param bool $chunk If set to true these files will be chunked to ensure 47 * that no one file contains more than 4095 selectors. 48 * @param string $chunkurl If the CSS is be chunked then we need to know the URL 49 * to use for the chunked files. 50 */ 51 function css_store_css(theme_config $theme, $csspath, $csscontent, $chunk = false, $chunkurl = null) { 52 global $CFG; 53 54 clearstatcache(); 55 if (!file_exists(dirname($csspath))) { 56 @mkdir(dirname($csspath), $CFG->directorypermissions, true); 57 } 58 59 // Prevent serving of incomplete file from concurrent request, 60 // the rename() should be more atomic than fwrite(). 61 ignore_user_abort(true); 62 63 // First up write out the single file for all those using decent browsers. 64 css_write_file($csspath, $csscontent); 65 66 if ($chunk) { 67 // If we need to chunk the CSS for browsers that are sub-par. 68 $css = css_chunk_by_selector_count($csscontent, $chunkurl); 69 $files = count($css); 70 $count = 1; 71 foreach ($css as $content) { 72 if ($count === $files) { 73 // If there is more than one file and this IS the last file. 74 $filename = preg_replace('#\.css$#', '.0.css', $csspath); 75 } else { 76 // If there is more than one file and this is not the last file. 77 $filename = preg_replace('#\.css$#', '.'.$count.'.css', $csspath); 78 } 79 $count++; 80 css_write_file($filename, $content); 81 } 82 } 83 84 ignore_user_abort(false); 85 if (connection_aborted()) { 86 die; 87 } 88 } 89 90 /** 91 * Writes a CSS file. 92 * 93 * @param string $filename 94 * @param string $content 95 */ 96 function css_write_file($filename, $content) { 97 global $CFG; 98 if ($fp = fopen($filename.'.tmp', 'xb')) { 99 fwrite($fp, $content); 100 fclose($fp); 101 rename($filename.'.tmp', $filename); 102 @chmod($filename, $CFG->filepermissions); 103 @unlink($filename.'.tmp'); // Just in case anything fails. 104 } 105 } 106 107 /** 108 * Takes CSS and chunks it if the number of selectors within it exceeds $maxselectors. 109 * 110 * The chunking will not split a group of selectors, or a media query. That means that 111 * if n > $maxselectors and there are n selectors grouped together, 112 * they will not be chunked and you could end up with more selectors than desired. 113 * The same applies for a media query that has more than n selectors. 114 * 115 * Also, as we do not split group of selectors or media queries, the chunking might 116 * not be as optimal as it could be, having files with less selectors than it could 117 * potentially contain. 118 * 119 * String functions used here are not compliant with unicode characters. But that is 120 * not an issue as the syntax of CSS is using ASCII codes. Even if we have unicode 121 * characters in comments, or in the property 'content: ""', it will behave correcly. 122 * 123 * Please note that this strips out the comments if chunking happens. 124 * 125 * @param string $css The CSS to chunk. 126 * @param string $importurl The URL to use for import statements. 127 * @param int $maxselectors The number of selectors to limit a chunk to. 128 * @param int $buffer Not used any more. 129 * @return array An array of CSS chunks. 130 */ 131 function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $buffer = 50) { 132 133 // Check if we need to chunk this CSS file. 134 $count = substr_count($css, ',') + substr_count($css, '{'); 135 if ($count < $maxselectors) { 136 // The number of selectors is less then the max - we're fine. 137 return array($css); 138 } 139 140 $chunks = array(); // The final chunks. 141 $offsets = array(); // The indexes to chunk at. 142 $offset = 0; // The current offset. 143 $selectorcount = 0; // The number of selectors since the last split. 144 $lastvalidoffset = 0; // The last valid index to split at. 145 $lastvalidoffsetselectorcount = 0; // The number of selectors used at the time were could split. 146 $inrule = 0; // The number of rules we are in, should not be greater than 1. 147 $inmedia = false; // Whether or not we are in a media query. 148 $mediacoming = false; // Whether or not we are expeting a media query. 149 $currentoffseterror = null; // Not null when we have recorded an error for the current split. 150 $offseterrors = array(); // The offsets where we found errors. 151 152 // Remove the comments. Because it's easier, safer and probably a lot of other good reasons. 153 $css = preg_replace('#/\*(.*?)\*/#s', '', $css); 154 $strlen = strlen($css); 155 156 // Walk through the CSS content character by character. 157 for ($i = 1; $i <= $strlen; $i++) { 158 $char = $css[$i - 1]; 159 $offset = $i; 160 161 // Is that a media query that I see coming towards us? 162 if ($char === '@') { 163 if (!$inmedia && substr($css, $offset, 5) === 'media') { 164 $mediacoming = true; 165 } 166 } 167 168 // So we are entering a rule or a media query... 169 if ($char === '{') { 170 if ($mediacoming) { 171 $inmedia = true; 172 $mediacoming = false; 173 } else { 174 $inrule++; 175 $selectorcount++; 176 } 177 } 178 179 // Let's count the number of selectors, but only if we are not in a rule, or in 180 // the definition of a media query, as they can contain commas too. 181 if (!$mediacoming && !$inrule && $char === ',') { 182 $selectorcount++; 183 } 184 185 // We reached the end of something. 186 if ($char === '}') { 187 // Oh, we are in a media query. 188 if ($inmedia) { 189 if (!$inrule) { 190 // This is the end of the media query. 191 $inmedia = false; 192 } else { 193 // We were in a rule, in the media query. 194 $inrule--; 195 } 196 } else { 197 $inrule--; 198 // Handle stupid broken CSS where there are too many } brackets, 199 // as this can cause it to break (with chunking) where it would 200 // coincidentally have worked otherwise. 201 if ($inrule < 0) { 202 $inrule = 0; 203 } 204 } 205 206 // We are not in a media query, and there is no pending rule, it is safe to split here. 207 if (!$inmedia && !$inrule) { 208 $lastvalidoffset = $offset; 209 $lastvalidoffsetselectorcount = $selectorcount; 210 } 211 } 212 213 // Alright, this is splitting time... 214 if ($selectorcount > $maxselectors) { 215 if (!$lastvalidoffset) { 216 // We must have reached more selectors into one set than we were allowed. That means that either 217 // the chunk size value is too small, or that we have a gigantic group of selectors, or that a media 218 // query contains more selectors than the chunk size. We have to ignore this because we do not 219 // support split inside a group of selectors or media query. 220 if ($currentoffseterror === null) { 221 $currentoffseterror = $offset; 222 $offseterrors[] = $currentoffseterror; 223 } 224 } else { 225 // We identify the offset to split at and reset the number of selectors found from there. 226 $offsets[] = $lastvalidoffset; 227 $selectorcount = $selectorcount - $lastvalidoffsetselectorcount; 228 $lastvalidoffset = 0; 229 $currentoffseterror = null; 230 } 231 } 232 } 233 234 // Report offset errors. 235 if (!empty($offseterrors)) { 236 debugging('Could not find a safe place to split at offset(s): ' . implode(', ', $offseterrors) . '. Those were ignored.', 237 DEBUG_DEVELOPER); 238 } 239 240 // Now that we have got the offets, we can chunk the CSS. 241 $offsetcount = count($offsets); 242 foreach ($offsets as $key => $index) { 243 $start = 0; 244 if ($key > 0) { 245 $start = $offsets[$key - 1]; 246 } 247 // From somewhere up to the offset. 248 $chunks[] = substr($css, $start, $index - $start); 249 } 250 // Add the last chunk (if there is one), from the last offset to the end of the string. 251 if (end($offsets) != $strlen) { 252 $chunks[] = substr($css, end($offsets)); 253 } 254 255 // The array $chunks now contains CSS split into perfect sized chunks. 256 // Import statements can only appear at the very top of a CSS file. 257 // Imported sheets are applied in the the order they are imported and 258 // are followed by the contents of the CSS. 259 // This is terrible for performance. 260 // It means we must put the import statements at the top of the last chunk 261 // to ensure that things are always applied in the correct order. 262 // This way the chunked files are included in the order they were chunked 263 // followed by the contents of the final chunk in the actual sheet. 264 $importcss = ''; 265 $slashargs = strpos($importurl, '.php?') === false; 266 $parts = count($chunks); 267 for ($i = 1; $i < $parts; $i++) { 268 if ($slashargs) { 269 $importcss .= "@import url({$importurl}/chunk{$i});\n"; 270 } else { 271 $importcss .= "@import url({$importurl}&chunk={$i});\n"; 272 } 273 } 274 $importcss .= end($chunks); 275 $chunks[key($chunks)] = $importcss; 276 277 return $chunks; 278 } 279 280 /** 281 * Sends a cached CSS file 282 * 283 * This function sends the cached CSS file. Remember it is generated on the first 284 * request, then optimised/minified, and finally cached for serving. 285 * 286 * @param string $csspath The path to the CSS file we want to serve. 287 * @param string $etag The revision to make sure we utilise any caches. 288 */ 289 function css_send_cached_css($csspath, $etag) { 290 // 60 days only - the revision may get incremented quite often. 291 $lifetime = 60*60*24*60; 292 293 header('Etag: "'.$etag.'"'); 294 header('Content-Disposition: inline; filename="styles.php"'); 295 header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($csspath)) .' GMT'); 296 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); 297 header('Pragma: '); 298 header('Cache-Control: public, max-age='.$lifetime); 299 header('Accept-Ranges: none'); 300 header('Content-Type: text/css; charset=utf-8'); 301 if (!min_enable_zlib_compression()) { 302 header('Content-Length: '.filesize($csspath)); 303 } 304 305 readfile($csspath); 306 die; 307 } 308 309 /** 310 * Sends a cached CSS content 311 * 312 * @param string $csscontent The actual CSS markup. 313 * @param string $etag The revision to make sure we utilise any caches. 314 */ 315 function css_send_cached_css_content($csscontent, $etag) { 316 // 60 days only - the revision may get incremented quite often. 317 $lifetime = 60*60*24*60; 318 319 header('Etag: "'.$etag.'"'); 320 header('Content-Disposition: inline; filename="styles.php"'); 321 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT'); 322 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); 323 header('Pragma: '); 324 header('Cache-Control: public, max-age='.$lifetime); 325 header('Accept-Ranges: none'); 326 header('Content-Type: text/css; charset=utf-8'); 327 if (!min_enable_zlib_compression()) { 328 header('Content-Length: '.strlen($csscontent)); 329 } 330 331 echo($csscontent); 332 die; 333 } 334 335 /** 336 * Sends CSS directly without caching it. 337 * 338 * This function takes a raw CSS string, optimises it if required, and then 339 * serves it. 340 * Turning both themedesignermode and CSS optimiser on at the same time is awful 341 * for performance because of the optimiser running here. However it was done so 342 * that theme designers could utilise the optimised output during development to 343 * help them optimise their CSS... not that they should write lazy CSS. 344 * 345 * @param string $css 346 */ 347 function css_send_uncached_css($css) { 348 header('Content-Disposition: inline; filename="styles_debug.php"'); 349 header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT'); 350 header('Expires: '. gmdate('D, d M Y H:i:s', time() + THEME_DESIGNER_CACHE_LIFETIME) .' GMT'); 351 header('Pragma: '); 352 header('Accept-Ranges: none'); 353 header('Content-Type: text/css; charset=utf-8'); 354 355 if (is_array($css)) { 356 $css = implode("\n\n", $css); 357 } 358 echo $css; 359 die; 360 } 361 362 /** 363 * Send file not modified headers 364 * 365 * @param int $lastmodified 366 * @param string $etag 367 */ 368 function css_send_unmodified($lastmodified, $etag) { 369 // 60 days only - the revision may get incremented quite often. 370 $lifetime = 60*60*24*60; 371 header('HTTP/1.1 304 Not Modified'); 372 header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT'); 373 header('Cache-Control: public, max-age='.$lifetime); 374 header('Content-Type: text/css; charset=utf-8'); 375 header('Etag: "'.$etag.'"'); 376 if ($lastmodified) { 377 header('Last-Modified: '. gmdate('D, d M Y H:i:s', $lastmodified) .' GMT'); 378 } 379 die; 380 } 381 382 /** 383 * Sends a 404 message about CSS not being found. 384 */ 385 function css_send_css_not_found() { 386 header('HTTP/1.0 404 not found'); 387 die('CSS was not found, sorry.'); 388 } 389 390 /** 391 * Determines if the given value is a valid CSS colour. 392 * 393 * A CSS colour can be one of the following: 394 * - Hex colour: #AA66BB 395 * - RGB colour: rgb(0-255, 0-255, 0-255) 396 * - RGBA colour: rgba(0-255, 0-255, 0-255, 0-1) 397 * - HSL colour: hsl(0-360, 0-100%, 0-100%) 398 * - HSLA colour: hsla(0-360, 0-100%, 0-100%, 0-1) 399 * 400 * Or a recognised browser colour mapping {@link css_optimiser::$htmlcolours} 401 * 402 * @param string $value The colour value to check 403 * @return bool 404 */ 405 function css_is_colour($value) { 406 $value = trim($value); 407 408 $hex = '/^#([a-fA-F0-9]{1,3}|[a-fA-F0-9]{6})$/'; 409 $rgb = '#^rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$#i'; 410 $rgba = '#^rgba\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i'; 411 $hsl = '#^hsl\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*\)$#i'; 412 $hsla = '#^hsla\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\%\s*,\s*(\d{1,3})\%\s*,\s*(\d{1}(\.\d+)?)\s*\)$#i'; 413 414 if (in_array(strtolower($value), array('inherit'))) { 415 return true; 416 } else if (preg_match($hex, $value)) { 417 return true; 418 } else if (in_array(strtolower($value), array_keys(css_optimiser::$htmlcolours))) { 419 return true; 420 } else if (preg_match($rgb, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) { 421 // It is an RGB colour. 422 return true; 423 } else if (preg_match($rgba, $value, $m) && $m[1] < 256 && $m[2] < 256 && $m[3] < 256) { 424 // It is an RGBA colour. 425 return true; 426 } else if (preg_match($hsl, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) { 427 // It is an HSL colour. 428 return true; 429 } else if (preg_match($hsla, $value, $m) && $m[1] <= 360 && $m[2] <= 100 && $m[3] <= 100) { 430 // It is an HSLA colour. 431 return true; 432 } 433 // Doesn't look like a colour. 434 return false; 435 } 436 437 /** 438 * Returns true is the passed value looks like a CSS width. 439 * In order to pass this test the value must be purely numerical or end with a 440 * valid CSS unit term. 441 * 442 * @param string|int $value 443 * @return boolean 444 */ 445 function css_is_width($value) { 446 $value = trim($value); 447 if (in_array(strtolower($value), array('auto', 'inherit'))) { 448 return true; 449 } 450 if ((string)$value === '0' || preg_match('#^(\-\s*)?(\d*\.)?(\d+)\s*(em|px|pt|\%|in|cm|mm|ex|pc)$#i', $value)) { 451 return true; 452 } 453 return false; 454 } 455 456 /** 457 * A simple sorting function to sort two array values on the number of items they contain 458 * 459 * @param array $a 460 * @param array $b 461 * @return int 462 */ 463 function css_sort_by_count(array $a, array $b) { 464 $a = count($a); 465 $b = count($b); 466 if ($a == $b) { 467 return 0; 468 } 469 return ($a > $b) ? -1 : 1; 470 } 471 472 /** 473 * A basic CSS optimiser that strips out unwanted things and then processes CSS organising and cleaning styles. 474 * 475 * This CSS optimiser works by reading through a CSS string one character at a 476 * time and building an object structure of the CSS. 477 * As part of that processing styles are expanded out as much as they can be to 478 * ensure we collect all mappings, at the end of the processing those styles are 479 * then combined into an optimised form to keep them as short as possible. 480 * 481 * @package core 482 * @subpackage cssoptimiser 483 * @copyright 2012 Sam Hemelryk 484 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 485 */ 486 class css_optimiser { 487 488 /** 489 * Used when the processor is about to start processing. 490 * Processing states. Used internally. 491 */ 492 const PROCESSING_START = 0; 493 494 /** 495 * Used when the processor is currently processing a selector. 496 * Processing states. Used internally. 497 */ 498 const PROCESSING_SELECTORS = 0; 499 500 /** 501 * Used when the processor is currently processing a style. 502 * Processing states. Used internally. 503 */ 504 const PROCESSING_STYLES = 1; 505 506 /** 507 * Used when the processor is currently processing a comment. 508 * Processing states. Used internally. 509 */ 510 const PROCESSING_COMMENT = 2; 511 512 /** 513 * Used when the processor is currently processing an @ rule. 514 * Processing states. Used internally. 515 */ 516 const PROCESSING_ATRULE = 3; 517 518 /** 519 * The raw string length before optimisation. 520 * Stats variables set during and after processing 521 * @var int 522 */ 523 protected $rawstrlen = 0; 524 525 /** 526 * The number of comments that were removed during optimisation. 527 * Stats variables set during and after processing 528 * @var int 529 */ 530 protected $commentsincss = 0; 531 532 /** 533 * The number of rules in the CSS before optimisation. 534 * Stats variables set during and after processing 535 * @var int 536 */ 537 protected $rawrules = 0; 538 539 /** 540 * The number of selectors using in CSS rules before optimisation. 541 * Stats variables set during and after processing 542 * @var int 543 */ 544 protected $rawselectors = 0; 545 546 /** 547 * The string length after optimisation. 548 * Stats variables set during and after processing 549 * @var int 550 */ 551 protected $optimisedstrlen = 0; 552 553 /** 554 * The number of rules after optimisation. 555 * Stats variables set during and after processing 556 * @var int 557 */ 558 protected $optimisedrules = 0; 559 560 /** 561 * The number of selectors used in rules after optimisation. 562 * Stats variables set during and after processing 563 * @var int 564 */ 565 protected $optimisedselectors = 0; 566 567 /** 568 * The start time of the optimisation. 569 * Stats variables set during and after processing 570 * @var int 571 */ 572 protected $timestart = 0; 573 574 /** 575 * The end time of the optimisation. 576 * Stats variables set during and after processing 577 * @var int 578 */ 579 protected $timecomplete = 0; 580 581 /** 582 * Will be set to any errors that may have occured during processing. 583 * This is updated only at the end of processing NOT during. 584 * 585 * @var array 586 */ 587 protected $errors = array(); 588 589 /** 590 * Processes incoming CSS optimising it and then returning it. 591 * 592 * @param string $css The raw CSS to optimise 593 * @return string The optimised CSS 594 */ 595 public function process($css) { 596 // Easiest win there is. 597 $css = trim($css); 598 599 $this->reset_stats(); 600 $this->timestart = microtime(true); 601 $this->rawstrlen = strlen($css); 602 603 // Don't try to process files with no content... it just doesn't make sense. 604 // But we should produce an error for them, an empty CSS file will lead to a 605 // useless request for those running theme designer mode. 606 if ($this->rawstrlen === 0) { 607 $this->errors[] = 'Skipping file as it has no content.'; 608 return ''; 609 } 610 611 // First up we need to remove all line breaks - this allows us to instantly 612 // reduce our processing requirements and as we will process everything 613 // into a new structure there's really nothing lost. 614 $css = preg_replace('#\r?\n#', ' ', $css); 615 616 // Next remove the comments... no need to them in an optimised world and 617 // knowing they're all gone allows us to REALLY make our processing simpler. 618 $css = preg_replace('#/\*(.*?)\*/#m', '', $css, -1, $this->commentsincss); 619 620 $medias = array( 621 'all' => new css_media() 622 ); 623 $imports = array(); 624 $charset = false; 625 // Keyframes are used for CSS animation they will be processed right at the very end. 626 $keyframes = array(); 627 628 $currentprocess = self::PROCESSING_START; 629 $currentrule = css_rule::init(); 630 $currentselector = css_selector::init(); 631 $inquotes = false; // ' or " 632 $inbraces = false; // { 633 $inbrackets = false; // [ 634 $inparenthesis = false; // ( 635 /* @var css_media $currentmedia */ 636 $currentmedia = $medias['all']; 637 $currentatrule = null; 638 $suspectatrule = false; 639 640 $buffer = ''; 641 $char = null; 642 643 // Next we are going to iterate over every single character in $css. 644 // This is why we removed line breaks and comments! 645 for ($i = 0; $i < $this->rawstrlen; $i++) { 646 $lastchar = $char; 647 $char = substr($css, $i, 1); 648 if ($char == '@' && $buffer == '') { 649 $suspectatrule = true; 650 } 651 switch ($currentprocess) { 652 // Start processing an @ rule e.g. @media, @page, @keyframes. 653 case self::PROCESSING_ATRULE: 654 switch ($char) { 655 case ';': 656 if (!$inbraces) { 657 $buffer .= $char; 658 if ($currentatrule == 'import') { 659 $imports[] = $buffer; 660 $currentprocess = self::PROCESSING_SELECTORS; 661 } else if ($currentatrule == 'charset') { 662 $charset = $buffer; 663 $currentprocess = self::PROCESSING_SELECTORS; 664 } 665 } 666 if ($currentatrule !== 'media') { 667 $buffer = ''; 668 $currentatrule = false; 669 } 670 // Continue 1: The switch processing chars 671 // Continue 2: The switch processing the state 672 // Continue 3: The for loop. 673 continue 3; 674 case '{': 675 $regexmediabasic = '#\s*@media\s*([a-zA-Z0-9]+(\s*,\s*[a-zA-Z0-9]+)*)\s*{#'; 676 $regexadvmedia = '#\s*@media\s*([^{]+)#'; 677 $regexkeyframes = '#@((\-moz\-|\-webkit\-|\-ms\-|\-o\-)?keyframes)\s*([^\s]+)#'; 678 679 if ($currentatrule == 'media' && preg_match($regexmediabasic, $buffer, $matches)) { 680 // Basic media declaration. 681 $mediatypes = str_replace(' ', '', $matches[1]); 682 if (!array_key_exists($mediatypes, $medias)) { 683 $medias[$mediatypes] = new css_media($mediatypes); 684 } 685 $currentmedia = $medias[$mediatypes]; 686 $currentprocess = self::PROCESSING_SELECTORS; 687 $buffer = ''; 688 } else if ($currentatrule == 'media' && preg_match($regexadvmedia, $buffer, $matches)) { 689 // Advanced media query declaration http://www.w3.org/TR/css3-mediaqueries/. 690 $mediatypes = $matches[1]; 691 $hash = md5($mediatypes); 692 $medias[$hash] = new css_media($mediatypes); 693 $currentmedia = $medias[$hash]; 694 $currentprocess = self::PROCESSING_SELECTORS; 695 $buffer = ''; 696 } else if ($currentatrule == 'keyframes' && preg_match($regexkeyframes, $buffer, $matches)) { 697 // Keyframes declaration, we treat it exactly like a @media declaration except we don't allow 698 // them to be overridden to ensure we don't mess anything up. (means we keep everything in order). 699 $keyframefor = $matches[1]; 700 $keyframename = $matches[3]; 701 $keyframe = new css_keyframe($keyframefor, $keyframename); 702 $keyframes[] = $keyframe; 703 $currentmedia = $keyframe; 704 $currentprocess = self::PROCESSING_SELECTORS; 705 $buffer = ''; 706 } 707 // Continue 1: The switch processing chars 708 // Continue 2: The switch processing the state 709 // Continue 3: The for loop. 710 continue 3; 711 } 712 break; 713 // Start processing selectors. 714 case self::PROCESSING_START: 715 case self::PROCESSING_SELECTORS: 716 $regexatrule = '#@(media|import|charset|(\-moz\-|\-webkit\-|\-ms\-|\-o\-)?(keyframes))\s*#'; 717 switch ($char) { 718 case '[': 719 $inbrackets ++; 720 $buffer .= $char; 721 // Continue 1: The switch processing chars 722 // Continue 2: The switch processing the state 723 // Continue 3: The for loop. 724 continue 3; 725 case ']': 726 $inbrackets --; 727 $buffer .= $char; 728 // Continue 1: The switch processing chars 729 // Continue 2: The switch processing the state 730 // Continue 3: The for loop. 731 continue 3; 732 case ' ': 733 if ($inbrackets) { 734 // Continue 1: The switch processing chars 735 // Continue 2: The switch processing the state 736 // Continue 3: The for loop. 737 continue 3; 738 } 739 if (!empty($buffer)) { 740 // Check for known @ rules. 741 if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) { 742 $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1]; 743 $currentprocess = self::PROCESSING_ATRULE; 744 $buffer .= $char; 745 } else { 746 $currentselector->add($buffer); 747 $buffer = ''; 748 } 749 } 750 $suspectatrule = false; 751 // Continue 1: The switch processing chars 752 // Continue 2: The switch processing the state 753 // Continue 3: The for loop. 754 continue 3; 755 case '{': 756 if ($inbrackets) { 757 // Continue 1: The switch processing chars 758 // Continue 2: The switch processing the state 759 // Continue 3: The for loop. 760 continue 3; 761 } 762 // Check for known @ rules. 763 if ($suspectatrule && preg_match($regexatrule, $buffer, $matches)) { 764 // Ahh we've been in an @rule, lets rewind one and have the @rule case process this. 765 $currentatrule = (!empty($matches[3]))?$matches[3]:$matches[1]; 766 $currentprocess = self::PROCESSING_ATRULE; 767 $i--; 768 $suspectatrule = false; 769 // Continue 1: The switch processing chars 770 // Continue 2: The switch processing the state 771 // Continue 3: The for loop. 772 continue 3; 773 } 774 if ($buffer !== '') { 775 $currentselector->add($buffer); 776 } 777 $currentrule->add_selector($currentselector); 778 $currentselector = css_selector::init(); 779 $currentprocess = self::PROCESSING_STYLES; 780 781 $buffer = ''; 782 // Continue 1: The switch processing chars 783 // Continue 2: The switch processing the state 784 // Continue 3: The for loop. 785 continue 3; 786 case '}': 787 if ($inbrackets) { 788 // Continue 1: The switch processing chars 789 // Continue 2: The switch processing the state 790 // Continue 3: The for loop. 791 continue 3; 792 } 793 if ($currentatrule == 'media') { 794 $currentmedia = $medias['all']; 795 $currentatrule = false; 796 $buffer = ''; 797 } else if (strpos($currentatrule, 'keyframes') !== false) { 798 $currentmedia = $medias['all']; 799 $currentatrule = false; 800 $buffer = ''; 801 } 802 // Continue 1: The switch processing chars 803 // Continue 2: The switch processing the state 804 // Continue 3: The for loop. 805 continue 3; 806 case ',': 807 if ($inbrackets) { 808 // Continue 1: The switch processing chars 809 // Continue 2: The switch processing the state 810 // Continue 3: The for loop. 811 continue 3; 812 } 813 $currentselector->add($buffer); 814 $currentrule->add_selector($currentselector); 815 $currentselector = css_selector::init(); 816 $buffer = ''; 817 // Continue 1: The switch processing chars 818 // Continue 2: The switch processing the state 819 // Continue 3: The for loop. 820 continue 3; 821 } 822 break; 823 // Start processing styles. 824 case self::PROCESSING_STYLES: 825 if ($char == '"' || $char == "'") { 826 if ($inquotes === false) { 827 $inquotes = $char; 828 } 829 if ($inquotes === $char && $lastchar !== '\\') { 830 $inquotes = false; 831 } 832 } 833 if ($inquotes) { 834 $buffer .= $char; 835 continue 2; 836 } 837 switch ($char) { 838 case ';': 839 if ($inparenthesis) { 840 $buffer .= $char; 841 // Continue 1: The switch processing chars 842 // Continue 2: The switch processing the state 843 // Continue 3: The for loop. 844 continue 3; 845 } 846 $currentrule->add_style($buffer); 847 $buffer = ''; 848 $inquotes = false; 849 // Continue 1: The switch processing chars 850 // Continue 2: The switch processing the state 851 // Continue 3: The for loop. 852 continue 3; 853 case '}': 854 $currentrule->add_style($buffer); 855 $this->rawselectors += $currentrule->get_selector_count(); 856 857 $currentmedia->add_rule($currentrule); 858 859 $currentrule = css_rule::init(); 860 $currentprocess = self::PROCESSING_SELECTORS; 861 $this->rawrules++; 862 $buffer = ''; 863 $inquotes = false; 864 $inparenthesis = false; 865 // Continue 1: The switch processing chars 866 // Continue 2: The switch processing the state 867 // Continue 3: The for loop. 868 continue 3; 869 case '(': 870 $inparenthesis = true; 871 $buffer .= $char; 872 // Continue 1: The switch processing chars 873 // Continue 2: The switch processing the state 874 // Continue 3: The for loop. 875 continue 3; 876 case ')': 877 $inparenthesis = false; 878 $buffer .= $char; 879 // Continue 1: The switch processing chars 880 // Continue 2: The switch processing the state 881 // Continue 3: The for loop. 882 continue 3; 883 } 884 break; 885 } 886 $buffer .= $char; 887 } 888 889 foreach ($medias as $media) { 890 $this->optimise($media); 891 } 892 $css = $this->produce_css($charset, $imports, $medias, $keyframes); 893 894 $this->timecomplete = microtime(true); 895 return trim($css); 896 } 897 898 /** 899 * Produces CSS for the given charset, imports, media, and keyframes 900 * @param string $charset 901 * @param array $imports 902 * @param css_media[] $medias 903 * @param css_keyframe[] $keyframes 904 * @return string 905 */ 906 protected function produce_css($charset, array $imports, array $medias, array $keyframes) { 907 $css = ''; 908 if (!empty($charset)) { 909 $imports[] = $charset; 910 } 911 if (!empty($imports)) { 912 $css .= implode("\n", $imports); 913 $css .= "\n\n"; 914 } 915 916 $cssreset = array(); 917 $cssstandard = array(); 918 $csskeyframes = array(); 919 920 // Process each media declaration individually. 921 foreach ($medias as $media) { 922 // If this declaration applies to all media types. 923 if (in_array('all', $media->get_types())) { 924 // Collect all rules that represet reset rules and remove them from the media object at the same time. 925 // We do this because we prioritise reset rules to the top of a CSS output. This ensures that they 926 // can't end up out of order because of optimisation. 927 $resetrules = $media->get_reset_rules(true); 928 if (!empty($resetrules)) { 929 $cssreset[] = css_writer::media('all', $resetrules); 930 } 931 } 932 // Get the standard cSS. 933 $cssstandard[] = $media->out(); 934 } 935 936 // Finally if there are any keyframe declarations process them now. 937 if (count($keyframes) > 0) { 938 foreach ($keyframes as $keyframe) { 939 $this->optimisedrules += $keyframe->count_rules(); 940 $this->optimisedselectors += $keyframe->count_selectors(); 941 if ($keyframe->has_errors()) { 942 $this->errors += $keyframe->get_errors(); 943 } 944 $csskeyframes[] = $keyframe->out(); 945 } 946 } 947 948 // Join it all together. 949 $css .= join('', $cssreset); 950 $css .= join('', $cssstandard); 951 $css .= join('', $csskeyframes); 952 953 // Record the strlenght of the now optimised CSS. 954 $this->optimisedstrlen = strlen($css); 955 956 // Return the now produced CSS. 957 return $css; 958 } 959 960 /** 961 * Optimises the CSS rules within a rule collection of one form or another 962 * 963 * @param css_rule_collection $media 964 * @return void This function acts in reference 965 */ 966 protected function optimise(css_rule_collection $media) { 967 $media->organise_rules_by_selectors(); 968 $this->optimisedrules += $media->count_rules(); 969 $this->optimisedselectors += $media->count_selectors(); 970 if ($media->has_errors()) { 971 $this->errors += $media->get_errors(); 972 } 973 } 974 975 /** 976 * Returns an array of stats from the last processing run 977 * @return string 978 */ 979 public function get_stats() { 980 $stats = array( 981 'timestart' => $this->timestart, 982 'timecomplete' => $this->timecomplete, 983 'timetaken' => round($this->timecomplete - $this->timestart, 4), 984 'commentsincss' => $this->commentsincss, 985 'rawstrlen' => $this->rawstrlen, 986 'rawselectors' => $this->rawselectors, 987 'rawrules' => $this->rawrules, 988 'optimisedstrlen' => $this->optimisedstrlen, 989 'optimisedrules' => $this->optimisedrules, 990 'optimisedselectors' => $this->optimisedselectors, 991 'improvementstrlen' => '-', 992 'improvementrules' => '-', 993 'improvementselectors' => '-', 994 ); 995 // Avoid division by 0 errors by checking we have valid raw values. 996 if ($this->rawstrlen > 0) { 997 $stats['improvementstrlen'] = round(100 - ($this->optimisedstrlen / $this->rawstrlen) * 100, 1).'%'; 998 } 999 if ($this->rawrules > 0) { 1000 $stats['improvementrules'] = round(100 - ($this->optimisedrules / $this->rawrules) * 100, 1).'%'; 1001 } 1002 if ($this->rawselectors > 0) { 1003 $stats['improvementselectors'] = round(100 - ($this->optimisedselectors / $this->rawselectors) * 100, 1).'%'; 1004 } 1005 return $stats; 1006 } 1007 1008 /** 1009 * Returns true if any errors have occured during processing 1010 * 1011 * @return bool 1012 */ 1013 public function has_errors() { 1014 return !empty($this->errors); 1015 } 1016 1017 /** 1018 * Returns an array of errors that have occured 1019 * 1020 * @param bool $clear If set to true the errors will be cleared after being returned. 1021 * @return array 1022 */ 1023 public function get_errors($clear = false) { 1024 $errors = $this->errors; 1025 if ($clear) { 1026 // Reset the error array. 1027 $this->errors = array(); 1028 } 1029 return $errors; 1030 } 1031 1032 /** 1033 * Returns any errors as a string that can be included in CSS. 1034 * 1035 * @return string 1036 */ 1037 public function output_errors_css() { 1038 $computedcss = "/****************************************\n"; 1039 $computedcss .= " *--- Errors found during processing ----\n"; 1040 foreach ($this->errors as $error) { 1041 $computedcss .= preg_replace('#^#m', '* ', $error); 1042 } 1043 $computedcss .= " ****************************************/\n\n"; 1044 return $computedcss; 1045 } 1046 1047 /** 1048 * Returns a string to display stats about the last generation within CSS output 1049 * 1050 * @return string 1051 */ 1052 public function output_stats_css() { 1053 1054 $computedcss = "/****************************************\n"; 1055 $computedcss .= " *------- CSS Optimisation stats --------\n"; 1056 1057 if ($this->rawstrlen === 0) { 1058 $computedcss .= " File not processed as it has no content /\n\n"; 1059 $computedcss .= " ****************************************/\n\n"; 1060 return $computedcss; 1061 } else if ($this->rawrules === 0) { 1062 $computedcss .= " File contained no rules to be processed /\n\n"; 1063 $computedcss .= " ****************************************/\n\n"; 1064 return $computedcss; 1065 } 1066 1067 $stats = $this->get_stats(); 1068 1069 $computedcss .= " * ".date('r')."\n"; 1070 $computedcss .= " * {$stats['commentsincss']} \t comments removed\n"; 1071 $computedcss .= " * Optimisation took {$stats['timetaken']} seconds\n"; 1072 $computedcss .= " *--------------- before ----------------\n"; 1073 $computedcss .= " * {$stats['rawstrlen']} \t chars read in\n"; 1074 $computedcss .= " * {$stats['rawrules']} \t rules read in\n"; 1075 $computedcss .= " * {$stats['rawselectors']} \t total selectors\n"; 1076 $computedcss .= " *---------------- after ----------------\n"; 1077 $computedcss .= " * {$stats['optimisedstrlen']} \t chars once optimized\n"; 1078 $computedcss .= " * {$stats['optimisedrules']} \t optimized rules\n"; 1079 $computedcss .= " * {$stats['optimisedselectors']} \t total selectors once optimized\n"; 1080 $computedcss .= " *---------------- stats ----------------\n"; 1081 $computedcss .= " * {$stats['improvementstrlen']} \t reduction in chars\n"; 1082 $computedcss .= " * {$stats['improvementrules']} \t reduction in rules\n"; 1083 $computedcss .= " * {$stats['improvementselectors']} \t reduction in selectors\n"; 1084 $computedcss .= " ****************************************/\n\n"; 1085 1086 return $computedcss; 1087 } 1088 1089 /** 1090 * Resets the stats ready for another fresh processing 1091 */ 1092 public function reset_stats() { 1093 $this->commentsincss = 0; 1094 $this->optimisedrules = 0; 1095 $this->optimisedselectors = 0; 1096 $this->optimisedstrlen = 0; 1097 $this->rawrules = 0; 1098 $this->rawselectors = 0; 1099 $this->rawstrlen = 0; 1100 $this->timecomplete = 0; 1101 $this->timestart = 0; 1102 } 1103 1104 /** 1105 * An array of the common HTML colours that are supported by most browsers. 1106 * 1107 * This reference table is used to allow us to unify colours, and will aid 1108 * us in identifying buggy CSS using unsupported colours. 1109 * 1110 * @var string[] 1111 */ 1112 public static $htmlcolours = array( 1113 'aliceblue' => '#F0F8FF', 1114 'antiquewhite' => '#FAEBD7', 1115 'aqua' => '#00FFFF', 1116 'aquamarine' => '#7FFFD4', 1117 'azure' => '#F0FFFF', 1118 'beige' => '#F5F5DC', 1119 'bisque' => '#FFE4C4', 1120 'black' => '#000000', 1121 'blanchedalmond' => '#FFEBCD', 1122 'blue' => '#0000FF', 1123 'blueviolet' => '#8A2BE2', 1124 'brown' => '#A52A2A', 1125 'burlywood' => '#DEB887', 1126 'cadetblue' => '#5F9EA0', 1127 'chartreuse' => '#7FFF00', 1128 'chocolate' => '#D2691E', 1129 'coral' => '#FF7F50', 1130 'cornflowerblue' => '#6495ED', 1131 'cornsilk' => '#FFF8DC', 1132 'crimson' => '#DC143C', 1133 'cyan' => '#00FFFF', 1134 'darkblue' => '#00008B', 1135 'darkcyan' => '#008B8B', 1136 'darkgoldenrod' => '#B8860B', 1137 'darkgray' => '#A9A9A9', 1138 'darkgrey' => '#A9A9A9', 1139 'darkgreen' => '#006400', 1140 'darkKhaki' => '#BDB76B', 1141 'darkmagenta' => '#8B008B', 1142 'darkolivegreen' => '#556B2F', 1143 'arkorange' => '#FF8C00', 1144 'darkorchid' => '#9932CC', 1145 'darkred' => '#8B0000', 1146 'darksalmon' => '#E9967A', 1147 'darkseagreen' => '#8FBC8F', 1148 'darkslateblue' => '#483D8B', 1149 'darkslategray' => '#2F4F4F', 1150 'darkslategrey' => '#2F4F4F', 1151 'darkturquoise' => '#00CED1', 1152 'darkviolet' => '#9400D3', 1153 'deeppink' => '#FF1493', 1154 'deepskyblue' => '#00BFFF', 1155 'dimgray' => '#696969', 1156 'dimgrey' => '#696969', 1157 'dodgerblue' => '#1E90FF', 1158 'firebrick' => '#B22222', 1159 'floralwhite' => '#FFFAF0', 1160 'forestgreen' => '#228B22', 1161 'fuchsia' => '#FF00FF', 1162 'gainsboro' => '#DCDCDC', 1163 'ghostwhite' => '#F8F8FF', 1164 'gold' => '#FFD700', 1165 'goldenrod' => '#DAA520', 1166 'gray' => '#808080', 1167 'grey' => '#808080', 1168 'green' => '#008000', 1169 'greenyellow' => '#ADFF2F', 1170 'honeydew' => '#F0FFF0', 1171 'hotpink' => '#FF69B4', 1172 'indianred ' => '#CD5C5C', 1173 'indigo ' => '#4B0082', 1174 'ivory' => '#FFFFF0', 1175 'khaki' => '#F0E68C', 1176 'lavender' => '#E6E6FA', 1177 'lavenderblush' => '#FFF0F5', 1178 'lawngreen' => '#7CFC00', 1179 'lemonchiffon' => '#FFFACD', 1180 'lightblue' => '#ADD8E6', 1181 'lightcoral' => '#F08080', 1182 'lightcyan' => '#E0FFFF', 1183 'lightgoldenrodyellow' => '#FAFAD2', 1184 'lightgray' => '#D3D3D3', 1185 'lightgrey' => '#D3D3D3', 1186 'lightgreen' => '#90EE90', 1187 'lightpink' => '#FFB6C1', 1188 'lightsalmon' => '#FFA07A', 1189 'lightseagreen' => '#20B2AA', 1190 'lightskyblue' => '#87CEFA', 1191 'lightslategray' => '#778899', 1192 'lightslategrey' => '#778899', 1193 'lightsteelblue' => '#B0C4DE', 1194 'lightyellow' => '#FFFFE0', 1195 'lime' => '#00FF00', 1196 'limegreen' => '#32CD32', 1197 'linen' => '#FAF0E6', 1198 'magenta' => '#FF00FF', 1199 'maroon' => '#800000', 1200 'mediumaquamarine' => '#66CDAA', 1201 'mediumblue' => '#0000CD', 1202 'mediumorchid' => '#BA55D3', 1203 'mediumpurple' => '#9370D8', 1204 'mediumseagreen' => '#3CB371', 1205 'mediumslateblue' => '#7B68EE', 1206 'mediumspringgreen' => '#00FA9A', 1207 'mediumturquoise' => '#48D1CC', 1208 'mediumvioletred' => '#C71585', 1209 'midnightblue' => '#191970', 1210 'mintcream' => '#F5FFFA', 1211 'mistyrose' => '#FFE4E1', 1212 'moccasin' => '#FFE4B5', 1213 'navajowhite' => '#FFDEAD', 1214 'navy' => '#000080', 1215 'oldlace' => '#FDF5E6', 1216 'olive' => '#808000', 1217 'olivedrab' => '#6B8E23', 1218 'orange' => '#FFA500', 1219 'orangered' => '#FF4500', 1220 'orchid' => '#DA70D6', 1221 'palegoldenrod' => '#EEE8AA', 1222 'palegreen' => '#98FB98', 1223 'paleturquoise' => '#AFEEEE', 1224 'palevioletred' => '#D87093', 1225 'papayawhip' => '#FFEFD5', 1226 'peachpuff' => '#FFDAB9', 1227 'peru' => '#CD853F', 1228 'pink' => '#FFC0CB', 1229 'plum' => '#DDA0DD', 1230 'powderblue' => '#B0E0E6', 1231 'purple' => '#800080', 1232 'red' => '#FF0000', 1233 'rosybrown' => '#BC8F8F', 1234 'royalblue' => '#4169E1', 1235 'saddlebrown' => '#8B4513', 1236 'salmon' => '#FA8072', 1237 'sandybrown' => '#F4A460', 1238 'seagreen' => '#2E8B57', 1239 'seashell' => '#FFF5EE', 1240 'sienna' => '#A0522D', 1241 'silver' => '#C0C0C0', 1242 'skyblue' => '#87CEEB', 1243 'slateblue' => '#6A5ACD', 1244 'slategray' => '#708090', 1245 'slategrey' => '#708090', 1246 'snow' => '#FFFAFA', 1247 'springgreen' => '#00FF7F', 1248 'steelblue' => '#4682B4', 1249 'tan' => '#D2B48C', 1250 'teal' => '#008080', 1251 'thistle' => '#D8BFD8', 1252 'tomato' => '#FF6347', 1253 'transparent' => 'transparent', 1254 'turquoise' => '#40E0D0', 1255 'violet' => '#EE82EE', 1256 'wheat' => '#F5DEB3', 1257 'white' => '#FFFFFF', 1258 'whitesmoke' => '#F5F5F5', 1259 'yellow' => '#FFFF00', 1260 'yellowgreen' => '#9ACD32' 1261 ); 1262 } 1263 1264 /** 1265 * Used to prepare CSS strings 1266 * 1267 * @package core 1268 * @subpackage cssoptimiser 1269 * @copyright 2012 Sam Hemelryk 1270 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1271 */ 1272 abstract class css_writer { 1273 1274 /** 1275 * The current indent level 1276 * @var int 1277 */ 1278 protected static $indent = 0; 1279 1280 /** 1281 * Returns true if the output should still maintain minimum formatting. 1282 * @return bool 1283 */ 1284 protected static function is_pretty() { 1285 global $CFG; 1286 return (!empty($CFG->cssoptimiserpretty)); 1287 } 1288 1289 /** 1290 * Returns the indenting char to use for indenting things nicely. 1291 * @return string 1292 */ 1293 protected static function get_indent() { 1294 if (self::is_pretty()) { 1295 return str_repeat(" ", self::$indent); 1296 } 1297 return ''; 1298 } 1299 1300 /** 1301 * Increases the current indent 1302 */ 1303 protected static function increase_indent() { 1304 self::$indent++; 1305 } 1306 1307 /** 1308 * Decreases the current indent 1309 */ 1310 protected static function decrease_indent() { 1311 self::$indent--; 1312 } 1313 1314 /** 1315 * Returns the string to use as a separator 1316 * @return string 1317 */ 1318 protected static function get_separator() { 1319 return (self::is_pretty())?"\n":' '; 1320 } 1321 1322 /** 1323 * Returns CSS for media 1324 * 1325 * @param string $typestring 1326 * @param css_rule[] $rules An array of css_rule objects 1327 * @return string 1328 */ 1329 public static function media($typestring, array &$rules) { 1330 $nl = self::get_separator(); 1331 1332 $output = ''; 1333 if ($typestring !== 'all') { 1334 $output .= "\n@media {$typestring} {".$nl; 1335 self::increase_indent(); 1336 } 1337 foreach ($rules as $rule) { 1338 $output .= $rule->out().$nl; 1339 } 1340 if ($typestring !== 'all') { 1341 self::decrease_indent(); 1342 $output .= '}'; 1343 } 1344 return $output; 1345 } 1346 1347 /** 1348 * Returns CSS for a keyframe 1349 * 1350 * @param string $for The desired declaration. e.g. keyframes, -moz-keyframes, -webkit-keyframes 1351 * @param string $name The name for the keyframe 1352 * @param css_rule[] $rules An array of rules belonging to the keyframe 1353 * @return string 1354 */ 1355 public static function keyframe($for, $name, array &$rules) { 1356 $output = "\n@{$for} {$name} {"; 1357 foreach ($rules as $rule) { 1358 $output .= $rule->out(); 1359 } 1360 $output .= '}'; 1361 return $output; 1362 } 1363 1364 /** 1365 * Returns CSS for a rule 1366 * 1367 * @param string $selector 1368 * @param string $styles 1369 * @return string 1370 */ 1371 public static function rule($selector, $styles) { 1372 $css = self::get_indent()."{$selector}{{$styles}}"; 1373 return $css; 1374 } 1375 1376 /** 1377 * Returns CSS for the selectors of a rule 1378 * 1379 * @param css_selector[] $selectors Array of css_selector objects 1380 * @return string 1381 */ 1382 public static function selectors(array $selectors) { 1383 $nl = self::get_separator(); 1384 $selectorstrings = array(); 1385 foreach ($selectors as $selector) { 1386 $selectorstrings[] = $selector->out(); 1387 } 1388 return join(','.$nl, $selectorstrings); 1389 } 1390 1391 /** 1392 * Returns a selector given the components that make it up. 1393 * 1394 * @param array $components 1395 * @return string 1396 */ 1397 public static function selector(array $components) { 1398 return trim(join(' ', $components)); 1399 } 1400 1401 /** 1402 * Returns a CSS string for the provided styles 1403 * 1404 * @param css_style[] $styles Array of css_style objects 1405 * @return string 1406 */ 1407 public static function styles(array $styles) { 1408 $bits = array(); 1409 foreach ($styles as $style) { 1410 // Check if the style is an array. If it is then we are outputing an advanced style. 1411 // An advanced style is a style with one or more values, and can occur in situations like background-image 1412 // where browse specific values are being used. 1413 if (is_array($style)) { 1414 /* @var css_style[] $style */ 1415 foreach ($style as $advstyle) { 1416 $bits[] = $advstyle->out(); 1417 } 1418 continue; 1419 } 1420 $bits[] = $style->out(); 1421 } 1422 return join('', $bits); 1423 } 1424 1425 /** 1426 * Returns a style CSS 1427 * 1428 * @param string $name 1429 * @param string $value 1430 * @param bool $important 1431 * @return string 1432 */ 1433 public static function style($name, $value, $important = false) { 1434 $value = trim($value); 1435 if ($important && strpos($value, '!important') === false) { 1436 $value .= ' !important'; 1437 } 1438 return "{$name}:{$value};"; 1439 } 1440 } 1441 1442 /** 1443 * A consolidatable style interface. 1444 * 1445 * Class that implement this have a short-hand notation for specifying multiple styles. 1446 * 1447 * @package core 1448 * @subpackage cssoptimiser 1449 * @copyright 2012 Sam Hemelryk 1450 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1451 */ 1452 interface core_css_consolidatable_style { 1453 /** 1454 * Used to consolidate several styles into a single "short-hand" style. 1455 * @param array $styles 1456 * @return mixed 1457 */ 1458 public static function consolidate(array $styles); 1459 } 1460 1461 /** 1462 * A structure to represent a CSS selector. 1463 * 1464 * The selector is the classes, id, elements, and psuedo bits that make up a CSS 1465 * rule. 1466 * 1467 * @package core 1468 * @subpackage cssoptimiser 1469 * @copyright 2012 Sam Hemelryk 1470 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1471 */ 1472 class css_selector { 1473 1474 /** 1475 * An array of selector bits 1476 * @var array 1477 */ 1478 protected $selectors = array(); 1479 1480 /** 1481 * The number of selectors. 1482 * @var int 1483 */ 1484 protected $count = 0; 1485 1486 /** 1487 * Is null if there are no selectors, true if all selectors are basic and false otherwise. 1488 * A basic selector is one that consists of just the element type. e.g. div, span, td, a 1489 * @var bool|null 1490 */ 1491 protected $isbasic = null; 1492 1493 /** 1494 * Initialises a new CSS selector 1495 * @return css_selector 1496 */ 1497 public static function init() { 1498 return new css_selector(); 1499 } 1500 1501 /** 1502 * CSS selectors can only be created through the init method above. 1503 */ 1504 protected function __construct() { 1505 // Nothing to do here by default. 1506 } 1507 1508 /** 1509 * Adds a selector to the end of the current selector 1510 * @param string $selector 1511 */ 1512 public function add($selector) { 1513 $selector = trim($selector); 1514 $count = 0; 1515 $count += preg_match_all('/(\.|#)/', $selector, $matchesarray); 1516 if (strpos($selector, '.') !== 0 && strpos($selector, '#') !== 0) { 1517 $count ++; 1518 } 1519 // If its already false then no need to continue, its not basic. 1520 if ($this->isbasic !== false) { 1521 // If theres more than one part making up this selector its not basic. 1522 if ($count > 1) { 1523 $this->isbasic = false; 1524 } else { 1525 // Check whether it is a basic element (a-z+) with possible psuedo selector. 1526 $this->isbasic = (bool)preg_match('#^[a-z]+(:[a-zA-Z]+)?$#', $selector); 1527 } 1528 } 1529 $this->count = $count; 1530 $this->selectors[] = $selector; 1531 } 1532 /** 1533 * Returns the number of individual components that make up this selector 1534 * @return int 1535 */ 1536 public function get_selector_count() { 1537 return $this->count; 1538 } 1539 1540 /** 1541 * Returns the selector for use in a CSS rule 1542 * @return string 1543 */ 1544 public function out() { 1545 return css_writer::selector($this->selectors); 1546 } 1547 1548 /** 1549 * Returns true is all of the selectors act only upon basic elements (no classes/ids) 1550 * @return bool 1551 */ 1552 public function is_basic() { 1553 return ($this->isbasic === true); 1554 } 1555 } 1556 1557 /** 1558 * A structure to represent a CSS rule. 1559 * 1560 * @package core 1561 * @subpackage cssoptimiser 1562 * @copyright 2012 Sam Hemelryk 1563 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1564 */ 1565 class css_rule { 1566 1567 /** 1568 * An array of CSS selectors {@link css_selector} 1569 * @var css_selector[] 1570 */ 1571 protected $selectors = array(); 1572 1573 /** 1574 * An array of CSS styles {@link css_style} 1575 * @var css_style[] 1576 */ 1577 protected $styles = array(); 1578 1579 /** 1580 * Created a new CSS rule. This is the only way to create a new CSS rule externally. 1581 * @return css_rule 1582 */ 1583 public static function init() { 1584 return new css_rule(); 1585 } 1586 1587 /** 1588 * Constructs a new css rule. 1589 * 1590 * @param string $selector The selector or array of selectors that make up this rule. 1591 * @param css_style[] $styles An array of styles that belong to this rule. 1592 */ 1593 protected function __construct($selector = null, array $styles = array()) { 1594 if ($selector != null) { 1595 if (is_array($selector)) { 1596 $this->selectors = $selector; 1597 } else { 1598 $this->selectors = array($selector); 1599 } 1600 $this->add_styles($styles); 1601 } 1602 } 1603 1604 /** 1605 * Adds a new CSS selector to this rule 1606 * 1607 * e.g. $rule->add_selector('.one #two.two'); 1608 * 1609 * @param css_selector $selector Adds a CSS selector to this rule. 1610 */ 1611 public function add_selector(css_selector $selector) { 1612 $this->selectors[] = $selector; 1613 } 1614 1615 /** 1616 * Adds a new CSS style to this rule. 1617 * 1618 * @param css_style|string $style Adds a new style to this rule 1619 */ 1620 public function add_style($style) { 1621 if (is_string($style)) { 1622 $style = trim($style); 1623 if (empty($style)) { 1624 return; 1625 } 1626 $bits = explode(':', $style, 2); 1627 if (count($bits) == 2) { 1628 list($name, $value) = array_map('trim', $bits); 1629 } 1630 if (isset($name) && isset($value) && $name !== '' && $value !== '') { 1631 $style = css_style::init_automatic($name, $value); 1632 } 1633 } else if ($style instanceof css_style) { 1634 // Clone the style as it may be coming from another rule and we don't 1635 // want references as it will likely be overwritten by proceeding 1636 // rules. 1637 $style = clone($style); 1638 } 1639 if ($style instanceof css_style) { 1640 $name = $style->get_name(); 1641 $exists = array_key_exists($name, $this->styles); 1642 // We need to find out if the current style support multiple values, or whether the style 1643 // is already set up to record multiple values. This can happen with background images which can have single 1644 // and multiple values. 1645 if ($style->allows_multiple_values() || ($exists && is_array($this->styles[$name]))) { 1646 if (!$exists) { 1647 $this->styles[$name] = array(); 1648 } else if ($this->styles[$name] instanceof css_style) { 1649 $this->styles[$name] = array($this->styles[$name]); 1650 } 1651 $this->styles[$name][] = $style; 1652 } else if ($exists) { 1653 $this->styles[$name]->set_value($style->get_value()); 1654 } else { 1655 $this->styles[$name] = $style; 1656 } 1657 } else if (is_array($style)) { 1658 // We probably shouldn't worry about processing styles here but to 1659 // be truthful it doesn't hurt. 1660 foreach ($style as $astyle) { 1661 $this->add_style($astyle); 1662 } 1663 } 1664 } 1665 1666 /** 1667 * An easy method of adding several styles at once. Just calls add_style. 1668 * 1669 * This method simply iterates over the array and calls {@link css_rule::add_style()} 1670 * with each. 1671 * 1672 * @param css_style[] $styles Adds an array of styles 1673 */ 1674 public function add_styles(array $styles) { 1675 foreach ($styles as $style) { 1676 $this->add_style($style); 1677 } 1678 } 1679 1680 /** 1681 * Returns the array of selectors 1682 * 1683 * @return css_selector[] 1684 */ 1685 public function get_selectors() { 1686 return $this->selectors; 1687 } 1688 1689 /** 1690 * Returns the array of styles 1691 * 1692 * @return css_style[] 1693 */ 1694 public function get_styles() { 1695 return $this->styles; 1696 } 1697 1698 /** 1699 * Outputs this rule as a fragment of CSS 1700 * 1701 * @return string 1702 */ 1703 public function out() { 1704 $selectors = css_writer::selectors($this->selectors); 1705 $styles = css_writer::styles($this->get_consolidated_styles()); 1706 return css_writer::rule($selectors, $styles); 1707 } 1708 1709 /** 1710 * Consolidates all styles associated with this rule 1711 * 1712 * @return css_style[] An array of consolidated styles 1713 */ 1714 public function get_consolidated_styles() { 1715 /* @var css_style[] $organisedstyles */ 1716 $organisedstyles = array(); 1717 /* @var css_style[] $finalstyles */ 1718 $finalstyles = array(); 1719 /* @var core_css_consolidatable_style[] $consolidate */ 1720 $consolidate = array(); 1721 /* @var css_style[] $advancedstyles */ 1722 $advancedstyles = array(); 1723 foreach ($this->styles as $style) { 1724 // If the style is an array then we are processing an advanced style. An advanced style is a style that can have 1725 // one or more values. Background-image is one such example as it can have browser specific styles. 1726 if (is_array($style)) { 1727 $single = null; 1728 $count = 0; 1729 foreach ($style as $advstyle) { 1730 /* @var css_style $advstyle */ 1731 $key = $count++; 1732 $advancedstyles[$key] = $advstyle; 1733 if (!$advstyle->allows_multiple_values()) { 1734 if (!is_null($single)) { 1735 unset($advancedstyles[$single]); 1736 } 1737 $single = $key; 1738 } 1739 } 1740 if (!is_null($single)) { 1741 $style = $advancedstyles[$single]; 1742 1743 $consolidatetoclass = $style->consolidate_to(); 1744 if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && 1745 class_exists('css_style_'.$consolidatetoclass)) { 1746 $class = 'css_style_'.$consolidatetoclass; 1747 if (!array_key_exists($class, $consolidate)) { 1748 $consolidate[$class] = array(); 1749 $organisedstyles[$class] = true; 1750 } 1751 $consolidate[$class][] = $style; 1752 unset($advancedstyles[$single]); 1753 } 1754 } 1755 1756 continue; 1757 } 1758 $consolidatetoclass = $style->consolidate_to(); 1759 if (($style->is_valid() || $style->is_special_empty_value()) && !empty($consolidatetoclass) && 1760 class_exists('css_style_'.$consolidatetoclass)) { 1761 $class = 'css_style_'.$consolidatetoclass; 1762 if (!array_key_exists($class, $consolidate)) { 1763 $consolidate[$class] = array(); 1764 $organisedstyles[$class] = true; 1765 } 1766 $consolidate[$class][] = $style; 1767 } else { 1768 $organisedstyles[$style->get_name()] = $style; 1769 } 1770 } 1771 1772 foreach ($consolidate as $class => $styles) { 1773 $organisedstyles[$class] = call_user_func(array($class, 'consolidate'), $styles); 1774 } 1775 1776 foreach ($organisedstyles as $style) { 1777 if (is_array($style)) { 1778 foreach ($style as $s) { 1779 $finalstyles[] = $s; 1780 } 1781 } else { 1782 $finalstyles[] = $style; 1783 } 1784 } 1785 $finalstyles = array_merge($finalstyles, $advancedstyles); 1786 return $finalstyles; 1787 } 1788 1789 /** 1790 * Splits this rules into an array of CSS rules. One for each of the selectors 1791 * that make up this rule. 1792 * 1793 * @return css_rule[] 1794 */ 1795 public function split_by_selector() { 1796 $return = array(); 1797 foreach ($this->selectors as $selector) { 1798 $return[] = new css_rule($selector, $this->styles); 1799 } 1800 return $return; 1801 } 1802 1803 /** 1804 * Splits this rule into an array of rules. One for each of the styles that 1805 * make up this rule 1806 * 1807 * @return css_rule[] Array of css_rule objects 1808 */ 1809 public function split_by_style() { 1810 $return = array(); 1811 foreach ($this->styles as $style) { 1812 if (is_array($style)) { 1813 $return[] = new css_rule($this->selectors, $style); 1814 continue; 1815 } 1816 $return[] = new css_rule($this->selectors, array($style)); 1817 } 1818 return $return; 1819 } 1820 1821 /** 1822 * Gets a hash for the styles of this rule 1823 * 1824 * @return string 1825 */ 1826 public function get_style_hash() { 1827 return md5(css_writer::styles($this->styles)); 1828 } 1829 1830 /** 1831 * Gets a hash for the selectors of this rule 1832 * 1833 * @return string 1834 */ 1835 public function get_selector_hash() { 1836 return md5(css_writer::selectors($this->selectors)); 1837 } 1838 1839 /** 1840 * Gets the number of selectors that make up this rule. 1841 * 1842 * @return int 1843 */ 1844 public function get_selector_count() { 1845 $count = 0; 1846 foreach ($this->selectors as $selector) { 1847 $count += $selector->get_selector_count(); 1848 } 1849 return $count; 1850 } 1851 1852 /** 1853 * Returns true if there are any errors with this rule. 1854 * 1855 * @return bool 1856 */ 1857 public function has_errors() { 1858 foreach ($this->styles as $style) { 1859 if (is_array($style)) { 1860 /* @var css_style[] $style */ 1861 foreach ($style as $advstyle) { 1862 if ($advstyle->has_error()) { 1863 return true; 1864 } 1865 } 1866 continue; 1867 } 1868 if ($style->has_error()) { 1869 return true; 1870 } 1871 } 1872 return false; 1873 } 1874 1875 /** 1876 * Returns the error strings that were recorded when processing this rule. 1877 * 1878 * Before calling this function you should first call {@link css_rule::has_errors()} 1879 * to make sure there are errors (hopefully there arn't). 1880 * 1881 * @return string 1882 */ 1883 public function get_error_string() { 1884 $css = $this->out(); 1885 $errors = array(); 1886 foreach ($this->styles as $style) { 1887 if (is_array($style)) { 1888 /* @var css_style[] $style */ 1889 foreach ($style as $advstyle) { 1890 if ($advstyle instanceof css_style && $advstyle->has_error()) { 1891 $errors[] = " * ".$advstyle->get_last_error(); 1892 } 1893 } 1894 } else if ($style instanceof css_style && $style->has_error()) { 1895 $errors[] = " * ".$style->get_last_error(); 1896 } 1897 } 1898 return $css." has the following errors:\n".join("\n", $errors); 1899 } 1900 1901 /** 1902 * Returns true if this rule could be considered a reset rule. 1903 * 1904 * A reset rule is a rule that acts upon an HTML element and does not include any other parts to its selector. 1905 * 1906 * @return bool 1907 */ 1908 public function is_reset_rule() { 1909 foreach ($this->selectors as $selector) { 1910 if (!$selector->is_basic()) { 1911 return false; 1912 } 1913 } 1914 return true; 1915 } 1916 } 1917 1918 /** 1919 * An abstract CSS rule collection class. 1920 * 1921 * This class is extended by things such as media and keyframe declaration. They are declarations that 1922 * group rules together for a purpose. 1923 * When no declaration is specified rules accumulate into @media all. 1924 * 1925 * @package core 1926 * @subpackage cssoptimiser 1927 * @copyright 2012 Sam Hemelryk 1928 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1929 */ 1930 abstract class css_rule_collection { 1931 /** 1932 * An array of rules within this collection instance 1933 * @var css_rule[] 1934 */ 1935 protected $rules = array(); 1936 1937 /** 1938 * The collection must be able to print itself. 1939 */ 1940 abstract public function out(); 1941 1942 /** 1943 * Adds a new CSS rule to this collection instance 1944 * 1945 * @param css_rule $newrule 1946 */ 1947 public function add_rule(css_rule $newrule) { 1948 foreach ($newrule->split_by_selector() as $rule) { 1949 $hash = $rule->get_selector_hash(); 1950 if (!array_key_exists($hash, $this->rules)) { 1951 $this->rules[$hash] = $rule; 1952 } else { 1953 $this->rules[$hash]->add_styles($rule->get_styles()); 1954 } 1955 } 1956 } 1957 1958 /** 1959 * Returns the rules used by this collection 1960 * 1961 * @return css_rule[] 1962 */ 1963 public function get_rules() { 1964 return $this->rules; 1965 } 1966 1967 /** 1968 * Organises rules by gropuing selectors based upon the styles and consolidating 1969 * those selectors into single rules. 1970 * 1971 * @return bool True if the CSS was optimised by this method 1972 */ 1973 public function organise_rules_by_selectors() { 1974 /* @var css_rule[] $optimisedrules */ 1975 $optimisedrules = array(); 1976 $beforecount = count($this->rules); 1977 $lasthash = null; 1978 /* @var css_rule $lastrule */ 1979 $lastrule = null; 1980 foreach ($this->rules as $rule) { 1981 $hash = $rule->get_style_hash(); 1982 if ($lastrule !== null && $lasthash !== null && $hash === $lasthash) { 1983 foreach ($rule->get_selectors() as $selector) { 1984 $lastrule->add_selector($selector); 1985 } 1986 continue; 1987 } 1988 $lastrule = clone($rule); 1989 $lasthash = $hash; 1990 $optimisedrules[] = $lastrule; 1991 } 1992 $this->rules = array(); 1993 foreach ($optimisedrules as $optimised) { 1994 $this->rules[$optimised->get_selector_hash()] = $optimised; 1995 } 1996 $aftercount = count($this->rules); 1997 return ($beforecount < $aftercount); 1998 } 1999 2000 /** 2001 * Returns the total number of rules that exist within this collection 2002 * 2003 * @return int 2004 */ 2005 public function count_rules() { 2006 return count($this->rules); 2007 } 2008 2009 /** 2010 * Returns the total number of selectors that exist within this collection 2011 * 2012 * @return int 2013 */ 2014 public function count_selectors() { 2015 $count = 0; 2016 foreach ($this->rules as $rule) { 2017 $count += $rule->get_selector_count(); 2018 } 2019 return $count; 2020 } 2021 2022 /** 2023 * Returns true if the collection has any rules that have errors 2024 * 2025 * @return boolean 2026 */ 2027 public function has_errors() { 2028 foreach ($this->rules as $rule) { 2029 if ($rule->has_errors()) { 2030 return true; 2031 } 2032 } 2033 return false; 2034 } 2035 2036 /** 2037 * Returns any errors that have happened within rules in this collection. 2038 * 2039 * @return string[] 2040 */ 2041 public function get_errors() { 2042 $errors = array(); 2043 foreach ($this->rules as $rule) { 2044 if ($rule->has_errors()) { 2045 $errors[] = $rule->get_error_string(); 2046 } 2047 } 2048 return $errors; 2049 } 2050 } 2051 2052 /** 2053 * A media class to organise rules by the media they apply to. 2054 * 2055 * @package core 2056 * @subpackage cssoptimiser 2057 * @copyright 2012 Sam Hemelryk 2058 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2059 */ 2060 class css_media extends css_rule_collection { 2061 2062 /** 2063 * An array of the different media types this instance applies to. 2064 * @var array 2065 */ 2066 protected $types = array(); 2067 2068 /** 2069 * Initalises a new media instance 2070 * 2071 * @param string $for The media that the contained rules are destined for. 2072 */ 2073 public function __construct($for = 'all') { 2074 $types = explode(',', $for); 2075 $this->types = array_map('trim', $types); 2076 } 2077 2078 /** 2079 * Returns the CSS for this media and all of its rules. 2080 * 2081 * @return string 2082 */ 2083 public function out() { 2084 return css_writer::media(join(',', $this->types), $this->rules); 2085 } 2086 2087 /** 2088 * Returns an array of media that this media instance applies to 2089 * 2090 * @return array 2091 */ 2092 public function get_types() { 2093 return $this->types; 2094 } 2095 2096 /** 2097 * Returns all of the reset rules known by this media set. 2098 * @param bool $remove If set to true reset rules will be removed before being returned. 2099 * @return array 2100 */ 2101 public function get_reset_rules($remove = false) { 2102 $resetrules = array(); 2103 foreach ($this->rules as $key => $rule) { 2104 if ($rule->is_reset_rule()) { 2105 $resetrules[] = clone $rule; 2106 if ($remove) { 2107 unset($this->rules[$key]); 2108 } 2109 } 2110 } 2111 return $resetrules; 2112 } 2113 } 2114 2115 /** 2116 * A media class to organise rules by the media they apply to. 2117 * 2118 * @package core 2119 * @subpackage cssoptimiser 2120 * @copyright 2012 Sam Hemelryk 2121 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2122 */ 2123 class css_keyframe extends css_rule_collection { 2124 2125 /** 2126 * The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes 2127 * @var string 2128 */ 2129 protected $for; 2130 2131 /** 2132 * The name for the keyframes 2133 * @var string 2134 */ 2135 protected $name; 2136 /** 2137 * Constructs a new keyframe 2138 * 2139 * @param string $for The directive e.g. keyframes, -moz-keyframes, -webkit-keyframes 2140 * @param string $name The name for the keyframes 2141 */ 2142 public function __construct($for, $name) { 2143 $this->for = $for; 2144 $this->name = $name; 2145 } 2146 /** 2147 * Returns the directive of this keyframe 2148 * 2149 * e.g. keyframes, -moz-keyframes, -webkit-keyframes 2150 * @return string 2151 */ 2152 public function get_for() { 2153 return $this->for; 2154 } 2155 /** 2156 * Returns the name of this keyframe 2157 * @return string 2158 */ 2159 public function get_name() { 2160 return $this->name; 2161 } 2162 /** 2163 * Returns the CSS for this collection of keyframes and all of its rules. 2164 * 2165 * @return string 2166 */ 2167 public function out() { 2168 return css_writer::keyframe($this->for, $this->name, $this->rules); 2169 } 2170 } 2171 2172 /** 2173 * An absract class to represent CSS styles 2174 * 2175 * @package core 2176 * @subpackage cssoptimiser 2177 * @copyright 2012 Sam Hemelryk 2178 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2179 */ 2180 abstract class css_style { 2181 2182 /** Constant used for recongise a special empty value in a CSS style */ 2183 const NULL_VALUE = '@@$NULL$@@'; 2184 2185 /** 2186 * The name of the style 2187 * @var string 2188 */ 2189 protected $name; 2190 2191 /** 2192 * The value for the style 2193 * @var mixed 2194 */ 2195 protected $value; 2196 2197 /** 2198 * If set to true this style was defined with the !important rule. 2199 * Only trolls use !important. 2200 * Don't hide under bridges.. its not good for your skin. Do the proper thing 2201 * and fix the issue don't just force a fix that will undoubtedly one day 2202 * lead to further frustration. 2203 * @var bool 2204 */ 2205 protected $important = false; 2206 2207 /** 2208 * Gets set to true if this style has an error 2209 * @var bool 2210 */ 2211 protected $error = false; 2212 2213 /** 2214 * The last error message that occured 2215 * @var string 2216 */ 2217 protected $errormessage = null; 2218 2219 /** 2220 * Initialises a new style. 2221 * 2222 * This is the only public way to create a style to ensure they that appropriate 2223 * style class is used if it exists. 2224 * 2225 * @param string $name The name of the style. 2226 * @param string $value The value of the style. 2227 * @return css_style_generic 2228 */ 2229 public static function init_automatic($name, $value) { 2230 $cleanedname = preg_replace('#[^a-zA-Z0-9]+#', '', $name); 2231 $specificclass = 'css_style_'.$cleanedname; 2232 if (class_exists($specificclass)) { 2233 $style = call_user_func(array($specificclass, 'init'), $value); 2234 if ($cleanedname !== $name && !is_array($style)) { 2235 $style->set_actual_name($name); 2236 } 2237 return $style; 2238 } 2239 return new css_style_generic($name, $value); 2240 } 2241 2242 /** 2243 * Creates a new style when given its name and value 2244 * 2245 * @param string $name The name of the style. 2246 * @param string $value The value of the style. 2247 */ 2248 protected function __construct($name, $value) { 2249 $this->name = $name; 2250 $this->set_value($value); 2251 } 2252 2253 /** 2254 * Sets the value for the style 2255 * 2256 * @param string $value 2257 */ 2258 final public function set_value($value) { 2259 $value = trim($value); 2260 $important = preg_match('#(\!important\s*;?\s*)$#', $value, $matches); 2261 if ($important) { 2262 $value = substr($value, 0, -(strlen($matches[1]))); 2263 $value = rtrim($value); 2264 } 2265 if (!$this->important || $important) { 2266 $this->value = $this->clean_value($value); 2267 $this->important = $important; 2268 } 2269 if (!$this->is_valid()) { 2270 $this->set_error('Invalid value for '.$this->name); 2271 } 2272 } 2273 2274 /** 2275 * Returns true if the value associated with this style is valid 2276 * 2277 * @return bool 2278 */ 2279 public function is_valid() { 2280 return true; 2281 } 2282 2283 /** 2284 * Returns the name for the style 2285 * 2286 * @return string 2287 */ 2288 public function get_name() { 2289 return $this->name; 2290 } 2291 2292 /** 2293 * Returns the value for the style 2294 * 2295 * @param bool $includeimportant If set to true and the rule is important !important postfix will be used. 2296 * @return string 2297 */ 2298 public function get_value($includeimportant = true) { 2299 $value = $this->value; 2300 if ($includeimportant && $this->important) { 2301 $value .= ' !important'; 2302 } 2303 return $value; 2304 } 2305 2306 /** 2307 * Returns the style ready for use in CSS 2308 * 2309 * @param string|null $value A value to use to override the value for this style. 2310 * @return string 2311 */ 2312 public function out($value = null) { 2313 if (is_null($value)) { 2314 $value = $this->get_value(); 2315 } 2316 return css_writer::style($this->name, $value, $this->important); 2317 } 2318 2319 /** 2320 * This can be overridden by a specific style allowing it to clean its values 2321 * consistently. 2322 * 2323 * @param mixed $value 2324 * @return mixed 2325 */ 2326 protected function clean_value($value) { 2327 return $value; 2328 } 2329 2330 /** 2331 * If this particular style can be consolidated into another style this function 2332 * should return the style that it can be consolidated into. 2333 * 2334 * @return string|null 2335 */ 2336 public function consolidate_to() { 2337 return null; 2338 } 2339 2340 /** 2341 * Sets the last error message. 2342 * 2343 * @param string $message 2344 */ 2345 protected function set_error($message) { 2346 $this->error = true; 2347 $this->errormessage = $message; 2348 } 2349 2350 /** 2351 * Returns true if an error has occured 2352 * 2353 * @return bool 2354 */ 2355 public function has_error() { 2356 return $this->error; 2357 } 2358 2359 /** 2360 * Returns the last error that occured or null if no errors have happened. 2361 * 2362 * @return string 2363 */ 2364 public function get_last_error() { 2365 return $this->errormessage; 2366 } 2367 2368 /** 2369 * Returns true if the value for this style is the special null value. 2370 * 2371 * This should only be overriden in circumstances where a shorthand style can lead 2372 * to move explicit styles being overwritten. Not a common place occurenace. 2373 * 2374 * Example: 2375 * This occurs if the shorthand background property was used but no proper value 2376 * was specified for this style. 2377 * This leads to a null value being used unless otherwise overridden. 2378 * 2379 * @return bool 2380 */ 2381 public function is_special_empty_value() { 2382 return false; 2383 } 2384 2385 /** 2386 * Returns true if this style permits multiple values. 2387 * 2388 * This occurs for styles such as background image that can have browser specific values that need to be maintained because 2389 * of course we don't know what browser the user is using, and optimisation occurs before caching. 2390 * Thus we must always server all values we encounter in the order we encounter them for when this is set to true. 2391 * 2392 * @return boolean False by default, true if the style supports muliple values. 2393 */ 2394 public function allows_multiple_values() { 2395 return false; 2396 } 2397 2398 /** 2399 * Returns true if this style was marked important. 2400 * @return bool 2401 */ 2402 public function is_important() { 2403 return !empty($this->important); 2404 } 2405 2406 /** 2407 * Sets the important flag for this style and its current value. 2408 * @param bool $important 2409 */ 2410 public function set_important($important = true) { 2411 $this->important = (bool) $important; 2412 } 2413 2414 /** 2415 * Sets the actual name used within the style. 2416 * 2417 * This method allows us to support browser hacks like *width:0; 2418 * 2419 * @param string $name 2420 */ 2421 public function set_actual_name($name) { 2422 $this->name = $name; 2423 } 2424 } 2425 2426 /** 2427 * A generic CSS style class to use when a more specific class does not exist. 2428 * 2429 * @package core 2430 * @subpackage cssoptimiser 2431 * @copyright 2012 Sam Hemelryk 2432 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2433 */ 2434 class css_style_generic extends css_style { 2435 2436 /** 2437 * Cleans incoming values for typical things that can be optimised. 2438 * 2439 * @param mixed $value Cleans the provided value optimising it if possible 2440 * @return string 2441 */ 2442 protected function clean_value($value) { 2443 if (trim($value) == '0px') { 2444 $value = 0; 2445 } else if (preg_match('/^#([a-fA-F0-9]{3,6})/', $value, $matches)) { 2446 $value = '#'.strtoupper($matches[1]); 2447 } 2448 return $value; 2449 } 2450 } 2451 2452 /** 2453 * A colour CSS style 2454 * 2455 * @package core 2456 * @subpackage cssoptimiser 2457 * @copyright 2012 Sam Hemelryk 2458 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2459 */ 2460 class css_style_color extends css_style { 2461 2462 /** 2463 * Creates a new colour style 2464 * 2465 * @param mixed $value Initialises a new colour style 2466 * @return css_style_color 2467 */ 2468 public static function init($value) { 2469 return new css_style_color('color', $value); 2470 } 2471 2472 /** 2473 * Cleans the colour unifing it to a 6 char hash colour if possible 2474 * Doing this allows us to associate identical colours being specified in 2475 * different ways. e.g. Red, red, #F00, and #F00000 2476 * 2477 * @param mixed $value Cleans the provided value optimising it if possible 2478 * @return string 2479 */ 2480 protected function clean_value($value) { 2481 $value = trim($value); 2482 if (css_is_colour($value)) { 2483 if (preg_match('/#([a-fA-F0-9]{6})/', $value, $matches)) { 2484 $value = '#'.strtoupper($matches[1]); 2485 } else if (preg_match('/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/', $value, $matches)) { 2486 $value = $matches[1] . $matches[1] . $matches[2] . $matches[2] . $matches[3] . $matches[3]; 2487 $value = '#'.strtoupper($value); 2488 } else if (array_key_exists(strtolower($value), css_optimiser::$htmlcolours)) { 2489 $value = css_optimiser::$htmlcolours[strtolower($value)]; 2490 } 2491 } 2492 return $value; 2493 } 2494 2495 /** 2496 * Returns the colour style for use within CSS. 2497 * Will return an optimised hash colour. 2498 * 2499 * e.g #123456 2500 * #123 instead of #112233 2501 * #F00 instead of red 2502 * 2503 * @param string $overridevalue If provided then this value will be used instead 2504 * of the styles current value. 2505 * @return string 2506 */ 2507 public function out($overridevalue = null) { 2508 if ($overridevalue === null) { 2509 $overridevalue = $this->value; 2510 } 2511 return parent::out(self::shrink_value($overridevalue)); 2512 } 2513 2514 /** 2515 * Shrinks the colour value is possible. 2516 * 2517 * @param string $value Shrinks the current value to an optimial form if possible 2518 * @return string 2519 */ 2520 public static function shrink_value($value) { 2521 if (preg_match('/#([a-fA-F0-9])\1([a-fA-F0-9])\2([a-fA-F0-9])\3/', $value, $matches)) { 2522 return '#'.$matches[1].$matches[2].$matches[3]; 2523 } 2524 return $value; 2525 } 2526 2527 /** 2528 * Returns true if the value is a valid colour. 2529 * 2530 * @return bool 2531 */ 2532 public function is_valid() { 2533 return css_is_colour($this->value); 2534 } 2535 } 2536 2537 /** 2538 * A width style 2539 * 2540 * @package core 2541 * @subpackage cssoptimiser 2542 * @copyright 2012 Sam Hemelryk 2543 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2544 */ 2545 class css_style_width extends css_style { 2546 2547 /** 2548 * Checks if the width is valid 2549 * @return bool 2550 */ 2551 public function is_valid() { 2552 return css_is_width($this->value); 2553 } 2554 2555 /** 2556 * Cleans the provided value 2557 * 2558 * @param mixed $value Cleans the provided value optimising it if possible 2559 * @return string 2560 */ 2561 protected function clean_value($value) { 2562 if (!css_is_width($value)) { 2563 // Note we don't actually change the value to something valid. That 2564 // would be bad for futureproofing. 2565 $this->set_error('Invalid width specified for '.$this->name); 2566 } else if (preg_match('#^0\D+$#', $value)) { 2567 $value = 0; 2568 } 2569 return trim($value); 2570 } 2571 2572 /** 2573 * Initialises a new width style 2574 * 2575 * @param mixed $value The value this style has 2576 * @return css_style_width 2577 */ 2578 public static function init($value) { 2579 return new css_style_width('width', $value); 2580 } 2581 } 2582 2583 /** 2584 * A margin style 2585 * 2586 * @package core 2587 * @subpackage cssoptimiser 2588 * @copyright 2012 Sam Hemelryk 2589 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2590 */ 2591 class css_style_margin extends css_style_width implements core_css_consolidatable_style { 2592 2593 /** 2594 * Initialises a margin style. 2595 * 2596 * In this case we split the margin into several other margin styles so that 2597 * we can properly condense overrides and then reconsolidate them later into 2598 * an optimal form. 2599 * 2600 * @param string $value The value the style has 2601 * @return array An array of margin values that can later be consolidated 2602 */ 2603 public static function init($value) { 2604 $important = ''; 2605 if (strpos($value, '!important') !== false) { 2606 $important = ' !important'; 2607 $value = str_replace('!important', '', $value); 2608 } 2609 2610 $value = preg_replace('#\s+#', ' ', trim($value)); 2611 $bits = explode(' ', $value, 4); 2612 2613 $top = $right = $bottom = $left = null; 2614 if (count($bits) > 0) { 2615 $top = $right = $bottom = $left = array_shift($bits); 2616 } 2617 if (count($bits) > 0) { 2618 $right = $left = array_shift($bits); 2619 } 2620 if (count($bits) > 0) { 2621 $bottom = array_shift($bits); 2622 } 2623 if (count($bits) > 0) { 2624 $left = array_shift($bits); 2625 } 2626 return array( 2627 new css_style_margintop('margin-top', $top.$important), 2628 new css_style_marginright('margin-right', $right.$important), 2629 new css_style_marginbottom('margin-bottom', $bottom.$important), 2630 new css_style_marginleft('margin-left', $left.$important) 2631 ); 2632 } 2633 2634 /** 2635 * Consolidates individual margin styles into a single margin style 2636 * 2637 * @param css_style[] $styles 2638 * @return css_style[] An array of consolidated styles 2639 */ 2640 public static function consolidate(array $styles) { 2641 if (count($styles) != 4) { 2642 return $styles; 2643 } 2644 2645 $someimportant = false; 2646 $allimportant = null; 2647 $notimportantequal = null; 2648 $firstvalue = null; 2649 foreach ($styles as $style) { 2650 if ($style->is_important()) { 2651 $someimportant = true; 2652 if ($allimportant === null) { 2653 $allimportant = true; 2654 } 2655 } else { 2656 if ($allimportant === true) { 2657 $allimportant = false; 2658 } 2659 if ($firstvalue == null) { 2660 $firstvalue = $style->get_value(false); 2661 $notimportantequal = true; 2662 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) { 2663 $notimportantequal = false; 2664 } 2665 } 2666 } 2667 2668 if ($someimportant && !$allimportant && !$notimportantequal) { 2669 return $styles; 2670 } 2671 2672 if ($someimportant && !$allimportant && $notimportantequal) { 2673 $return = array( 2674 new css_style_margin('margin', $firstvalue) 2675 ); 2676 foreach ($styles as $style) { 2677 if ($style->is_important()) { 2678 $return[] = $style; 2679 } 2680 } 2681 return $return; 2682 } else { 2683 $top = null; 2684 $right = null; 2685 $bottom = null; 2686 $left = null; 2687 foreach ($styles as $style) { 2688 switch ($style->get_name()) { 2689 case 'margin-top' : 2690 $top = $style->get_value(false); 2691 break; 2692 case 'margin-right' : 2693 $right = $style->get_value(false); 2694 break; 2695 case 'margin-bottom' : 2696 $bottom = $style->get_value(false); 2697 break; 2698 case 'margin-left' : 2699 $left = $style->get_value(false); 2700 break; 2701 } 2702 } 2703 if ($top == $bottom && $left == $right) { 2704 if ($top == $left) { 2705 $returnstyle = new css_style_margin('margin', $top); 2706 } else { 2707 $returnstyle = new css_style_margin('margin', "{$top} {$left}"); 2708 } 2709 } else if ($left == $right) { 2710 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom}"); 2711 } else { 2712 $returnstyle = new css_style_margin('margin', "{$top} {$right} {$bottom} {$left}"); 2713 } 2714 if ($allimportant) { 2715 $returnstyle->set_important(); 2716 } 2717 return array($returnstyle); 2718 } 2719 } 2720 } 2721 2722 /** 2723 * A margin top style 2724 * 2725 * @package core 2726 * @subpackage cssoptimiser 2727 * @copyright 2012 Sam Hemelryk 2728 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2729 */ 2730 class css_style_margintop extends css_style_margin { 2731 2732 /** 2733 * A simple init, just a single style 2734 * 2735 * @param string $value The value the style has 2736 * @return css_style_margintop 2737 */ 2738 public static function init($value) { 2739 return new css_style_margintop('margin-top', $value); 2740 } 2741 2742 /** 2743 * This style can be consolidated into a single margin style 2744 * 2745 * @return string 2746 */ 2747 public function consolidate_to() { 2748 return 'margin'; 2749 } 2750 } 2751 2752 /** 2753 * A margin right style 2754 * 2755 * @package core 2756 * @subpackage cssoptimiser 2757 * @copyright 2012 Sam Hemelryk 2758 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2759 */ 2760 class css_style_marginright extends css_style_margin { 2761 2762 /** 2763 * A simple init, just a single style 2764 * 2765 * @param string $value The value the style has 2766 * @return css_style_margintop 2767 */ 2768 public static function init($value) { 2769 return new css_style_marginright('margin-right', $value); 2770 } 2771 2772 /** 2773 * This style can be consolidated into a single margin style 2774 * 2775 * @return string 2776 */ 2777 public function consolidate_to() { 2778 return 'margin'; 2779 } 2780 } 2781 2782 /** 2783 * A margin bottom style 2784 * 2785 * @package core 2786 * @subpackage cssoptimiser 2787 * @copyright 2012 Sam Hemelryk 2788 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2789 */ 2790 class css_style_marginbottom extends css_style_margin { 2791 2792 /** 2793 * A simple init, just a single style 2794 * 2795 * @param string $value The value the style has 2796 * @return css_style_margintop 2797 */ 2798 public static function init($value) { 2799 return new css_style_marginbottom('margin-bottom', $value); 2800 } 2801 2802 /** 2803 * This style can be consolidated into a single margin style 2804 * 2805 * @return string 2806 */ 2807 public function consolidate_to() { 2808 return 'margin'; 2809 } 2810 } 2811 2812 /** 2813 * A margin left style 2814 * 2815 * @package core 2816 * @subpackage cssoptimiser 2817 * @copyright 2012 Sam Hemelryk 2818 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2819 */ 2820 class css_style_marginleft extends css_style_margin { 2821 2822 /** 2823 * A simple init, just a single style 2824 * 2825 * @param string $value The value the style has 2826 * @return css_style_margintop 2827 */ 2828 public static function init($value) { 2829 return new css_style_marginleft('margin-left', $value); 2830 } 2831 2832 /** 2833 * This style can be consolidated into a single margin style 2834 * 2835 * @return string 2836 */ 2837 public function consolidate_to() { 2838 return 'margin'; 2839 } 2840 } 2841 2842 /** 2843 * A border style 2844 * 2845 * @package core 2846 * @subpackage cssoptimiser 2847 * @copyright 2012 Sam Hemelryk 2848 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2849 */ 2850 class css_style_border extends css_style implements core_css_consolidatable_style { 2851 2852 /** 2853 * Initalises the border style into an array of individual style compontents 2854 * 2855 * @param string $value The value the style has 2856 * @return css_style_bordercolor 2857 */ 2858 public static function init($value) { 2859 $value = preg_replace('#\s+#', ' ', $value); 2860 $bits = explode(' ', $value, 3); 2861 2862 $return = array(); 2863 if (count($bits) > 0) { 2864 $width = array_shift($bits); 2865 if (!css_style_borderwidth::is_border_width($width)) { 2866 $width = '0'; 2867 } 2868 $return[] = css_style_bordertopwidth::init($width); 2869 $return[] = css_style_borderrightwidth::init($width); 2870 $return[] = css_style_borderbottomwidth::init($width); 2871 $return[] = css_style_borderleftwidth::init($width); 2872 } 2873 if (count($bits) > 0) { 2874 $style = array_shift($bits); 2875 $return[] = css_style_bordertopstyle::init($style); 2876 $return[] = css_style_borderrightstyle::init($style); 2877 $return[] = css_style_borderbottomstyle::init($style); 2878 $return[] = css_style_borderleftstyle::init($style); 2879 } 2880 if (count($bits) > 0) { 2881 $colour = array_shift($bits); 2882 $return[] = css_style_bordertopcolor::init($colour); 2883 $return[] = css_style_borderrightcolor::init($colour); 2884 $return[] = css_style_borderbottomcolor::init($colour); 2885 $return[] = css_style_borderleftcolor::init($colour); 2886 } 2887 return $return; 2888 } 2889 2890 /** 2891 * Consolidates all border styles into a single style 2892 * 2893 * @param css_style[] $styles An array of border styles 2894 * @return css_style[] An optimised array of border styles 2895 */ 2896 public static function consolidate(array $styles) { 2897 2898 $borderwidths = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null); 2899 $borderstyles = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null); 2900 $bordercolors = array('top' => null, 'right' => null, 'bottom' => null, 'left' => null); 2901 2902 foreach ($styles as $style) { 2903 switch ($style->get_name()) { 2904 case 'border-top-width': 2905 $borderwidths['top'] = $style->get_value(); 2906 break; 2907 case 'border-right-width': 2908 $borderwidths['right'] = $style->get_value(); 2909 break; 2910 case 'border-bottom-width': 2911 $borderwidths['bottom'] = $style->get_value(); 2912 break; 2913 case 'border-left-width': 2914 $borderwidths['left'] = $style->get_value(); 2915 break; 2916 2917 case 'border-top-style': 2918 $borderstyles['top'] = $style->get_value(); 2919 break; 2920 case 'border-right-style': 2921 $borderstyles['right'] = $style->get_value(); 2922 break; 2923 case 'border-bottom-style': 2924 $borderstyles['bottom'] = $style->get_value(); 2925 break; 2926 case 'border-left-style': 2927 $borderstyles['left'] = $style->get_value(); 2928 break; 2929 2930 case 'border-top-color': 2931 $bordercolors['top'] = css_style_color::shrink_value($style->get_value()); 2932 break; 2933 case 'border-right-color': 2934 $bordercolors['right'] = css_style_color::shrink_value($style->get_value()); 2935 break; 2936 case 'border-bottom-color': 2937 $bordercolors['bottom'] = css_style_color::shrink_value($style->get_value()); 2938 break; 2939 case 'border-left-color': 2940 $bordercolors['left'] = css_style_color::shrink_value($style->get_value()); 2941 break; 2942 } 2943 } 2944 2945 $uniquewidths = count(array_unique($borderwidths)); 2946 $uniquestyles = count(array_unique($borderstyles)); 2947 $uniquecolors = count(array_unique($bordercolors)); 2948 2949 $nullwidths = in_array(null, $borderwidths, true); 2950 $nullstyles = in_array(null, $borderstyles, true); 2951 $nullcolors = in_array(null, $bordercolors, true); 2952 2953 $allwidthsthesame = ($uniquewidths === 1)?1:0; 2954 $allstylesthesame = ($uniquestyles === 1)?1:0; 2955 $allcolorsthesame = ($uniquecolors === 1)?1:0; 2956 2957 $allwidthsnull = $allwidthsthesame && $nullwidths; 2958 $allstylesnull = $allstylesthesame && $nullstyles; 2959 $allcolorsnull = $allcolorsthesame && $nullcolors; 2960 2961 /* @var css_style[] $return */ 2962 $return = array(); 2963 if ($allwidthsnull && $allstylesnull && $allcolorsnull) { 2964 // Everything is null still... boo. 2965 return array(new css_style_border('border', '')); 2966 2967 } else if ($allwidthsnull && $allstylesnull) { 2968 2969 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors); 2970 return $return; 2971 2972 } else if ($allwidthsnull && $allcolorsnull) { 2973 2974 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles); 2975 return $return; 2976 2977 } else if ($allcolorsnull && $allstylesnull) { 2978 2979 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths); 2980 return $return; 2981 2982 } 2983 2984 if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 3) { 2985 2986 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top'].' '.$bordercolors['top']); 2987 2988 } else if ($allwidthsthesame + $allstylesthesame + $allcolorsthesame == 2) { 2989 2990 if ($allwidthsthesame && $allstylesthesame && !$nullwidths && !$nullstyles) { 2991 2992 $return[] = new css_style_border('border', $borderwidths['top'].' '.$borderstyles['top']); 2993 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors); 2994 2995 } else if ($allwidthsthesame && $allcolorsthesame && !$nullwidths && !$nullcolors) { 2996 2997 $return[] = new css_style_border('border', $borderwidths['top'].' solid '.$bordercolors['top']); 2998 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles); 2999 3000 } else if ($allstylesthesame && $allcolorsthesame && !$nullstyles && !$nullcolors) { 3001 3002 $return[] = new css_style_border('border', '1px '.$borderstyles['top'].' '.$bordercolors['top']); 3003 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths); 3004 3005 } else { 3006 self::consolidate_styles_by_direction($return, 'css_style_borderwidth', 'border-width', $borderwidths); 3007 self::consolidate_styles_by_direction($return, 'css_style_borderstyle', 'border-style', $borderstyles); 3008 self::consolidate_styles_by_direction($return, 'css_style_bordercolor', 'border-color', $bordercolors); 3009 } 3010 3011 } else if (!$nullwidths && !$nullcolors && !$nullstyles && 3012 max(array_count_values($borderwidths)) == 3 && 3013 max(array_count_values($borderstyles)) == 3 && 3014 max(array_count_values($bordercolors)) == 3) { 3015 3016 $widthkeys = array(); 3017 $stylekeys = array(); 3018 $colorkeys = array(); 3019 3020 foreach ($borderwidths as $key => $value) { 3021 if (!array_key_exists($value, $widthkeys)) { 3022 $widthkeys[$value] = array(); 3023 } 3024 $widthkeys[$value][] = $key; 3025 } 3026 usort($widthkeys, 'css_sort_by_count'); 3027 $widthkeys = array_values($widthkeys); 3028 3029 foreach ($borderstyles as $key => $value) { 3030 if (!array_key_exists($value, $stylekeys)) { 3031 $stylekeys[$value] = array(); 3032 } 3033 $stylekeys[$value][] = $key; 3034 } 3035 usort($stylekeys, 'css_sort_by_count'); 3036 $stylekeys = array_values($stylekeys); 3037 3038 foreach ($bordercolors as $key => $value) { 3039 if (!array_key_exists($value, $colorkeys)) { 3040 $colorkeys[$value] = array(); 3041 } 3042 $colorkeys[$value][] = $key; 3043 } 3044 usort($colorkeys, 'css_sort_by_count'); 3045 $colorkeys = array_values($colorkeys); 3046 3047 if ($widthkeys == $stylekeys && $stylekeys == $colorkeys) { 3048 $key = $widthkeys[0][0]; 3049 self::build_style_string($return, 'css_style_border', 'border', 3050 $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]); 3051 $key = $widthkeys[1][0]; 3052 self::build_style_string($return, 'css_style_border'.$key, 'border-'.$key, 3053 $borderwidths[$key], $borderstyles[$key], $bordercolors[$key]); 3054 } else { 3055 self::build_style_string($return, 'css_style_bordertop', 'border-top', 3056 $borderwidths['top'], $borderstyles['top'], $bordercolors['top']); 3057 self::build_style_string($return, 'css_style_borderright', 'border-right', 3058 $borderwidths['right'], $borderstyles['right'], $bordercolors['right']); 3059 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', 3060 $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']); 3061 self::build_style_string($return, 'css_style_borderleft', 'border-left', 3062 $borderwidths['left'], $borderstyles['left'], $bordercolors['left']); 3063 } 3064 } else { 3065 self::build_style_string($return, 'css_style_bordertop', 'border-top', 3066 $borderwidths['top'], $borderstyles['top'], $bordercolors['top']); 3067 self::build_style_string($return, 'css_style_borderright', 'border-right', 3068 $borderwidths['right'], $borderstyles['right'], $bordercolors['right']); 3069 self::build_style_string($return, 'css_style_borderbottom', 'border-bottom', 3070 $borderwidths['bottom'], $borderstyles['bottom'], $bordercolors['bottom']); 3071 self::build_style_string($return, 'css_style_borderleft', 'border-left', 3072 $borderwidths['left'], $borderstyles['left'], $bordercolors['left']); 3073 } 3074 foreach ($return as $key => $style) { 3075 if ($style->get_value() == '') { 3076 unset($return[$key]); 3077 } 3078 } 3079 return $return; 3080 } 3081 3082 /** 3083 * Border styles get consolidated to a single border style. 3084 * 3085 * @return string 3086 */ 3087 public function consolidate_to() { 3088 return 'border'; 3089 } 3090 3091 /** 3092 * Consolidates a series of border styles into an optimised array of border 3093 * styles by looking at the direction of the border and prioritising that 3094 * during the optimisation. 3095 * 3096 * @param array $array An array to add styles into during consolidation. Passed by reference. 3097 * @param string $class The class type to initalise 3098 * @param string $style The style to create 3099 * @param string|array $top The top value 3100 * @param string $right The right value 3101 * @param string $bottom The bottom value 3102 * @param string $left The left value 3103 * @return bool 3104 */ 3105 public static function consolidate_styles_by_direction(&$array, $class, $style, 3106 $top, $right = null, $bottom = null, $left = null) { 3107 if (is_array($top)) { 3108 $right = $top['right']; 3109 $bottom = $top['bottom']; 3110 $left = $top['left']; 3111 $top = $top['top']; 3112 } 3113 3114 if ($top == $bottom && $left == $right && $top == $left) { 3115 if (is_null($top)) { 3116 $array[] = new $class($style, ''); 3117 } else { 3118 $array[] = new $class($style, $top); 3119 } 3120 } else if ($top == null || $right == null || $bottom == null || $left == null) { 3121 if ($top !== null) { 3122 $array[] = new $class(str_replace('border-', 'border-top-', $style), $top); 3123 } 3124 if ($right !== null) { 3125 $array[] = new $class(str_replace('border-', 'border-right-', $style), $right); 3126 } 3127 if ($bottom !== null) { 3128 $array[] = new $class(str_replace('border-', 'border-bottom-', $style), $bottom); 3129 } 3130 if ($left !== null) { 3131 $array[] = new $class(str_replace('border-', 'border-left-', $style), $left); 3132 } 3133 } else if ($top == $bottom && $left == $right) { 3134 $array[] = new $class($style, $top.' '.$right); 3135 } else if ($left == $right) { 3136 $array[] = new $class($style, $top.' '.$right.' '.$bottom); 3137 } else { 3138 $array[] = new $class($style, $top.' '.$right.' '.$bottom.' '.$left); 3139 } 3140 return true; 3141 } 3142 3143 /** 3144 * Builds a border style for a set of width, style, and colour values 3145 * 3146 * @param array $array An array into which the generated style is added 3147 * @param string $class The class type to initialise 3148 * @param string $cssstyle The style to use 3149 * @param string $width The width of the border 3150 * @param string $style The style of the border 3151 * @param string $color The colour of the border 3152 * @return bool 3153 */ 3154 public static function build_style_string(&$array, $class, $cssstyle, $width = null, $style = null, $color = null) { 3155 if (!is_null($width) && !is_null($style) && !is_null($color)) { 3156 $array[] = new $class($cssstyle, $width.' '.$style.' '.$color); 3157 } else if (!is_null($width) && !is_null($style) && is_null($color)) { 3158 $array[] = new $class($cssstyle, $width.' '.$style); 3159 } else if (!is_null($width) && is_null($style) && is_null($color)) { 3160 $array[] = new $class($cssstyle, $width); 3161 } else { 3162 if (!is_null($width)) { 3163 $array[] = new $class($cssstyle, $width); 3164 } 3165 if (!is_null($style)) { 3166 $array[] = new $class($cssstyle, $style); 3167 } 3168 if (!is_null($color)) { 3169 $array[] = new $class($cssstyle, $color); 3170 } 3171 } 3172 return true; 3173 } 3174 } 3175 3176 /** 3177 * A border colour style 3178 * 3179 * @package core 3180 * @subpackage cssoptimiser 3181 * @copyright 2012 Sam Hemelryk 3182 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3183 */ 3184 class css_style_bordercolor extends css_style_color { 3185 3186 /** 3187 * Creates a new border colour style 3188 * 3189 * Based upon the colour style 3190 * 3191 * @param mixed $value 3192 * @return Array of css_style_bordercolor 3193 */ 3194 public static function init($value) { 3195 $value = preg_replace('#\s+#', ' ', $value); 3196 $bits = explode(' ', $value, 4); 3197 3198 $top = $right = $bottom = $left = null; 3199 if (count($bits) > 0) { 3200 $top = $right = $bottom = $left = array_shift($bits); 3201 } 3202 if (count($bits) > 0) { 3203 $right = $left = array_shift($bits); 3204 } 3205 if (count($bits) > 0) { 3206 $bottom = array_shift($bits); 3207 } 3208 if (count($bits) > 0) { 3209 $left = array_shift($bits); 3210 } 3211 return array( 3212 css_style_bordertopcolor::init($top), 3213 css_style_borderrightcolor::init($right), 3214 css_style_borderbottomcolor::init($bottom), 3215 css_style_borderleftcolor::init($left) 3216 ); 3217 } 3218 3219 /** 3220 * Consolidate this to a single border style 3221 * 3222 * @return string 3223 */ 3224 public function consolidate_to() { 3225 return 'border'; 3226 } 3227 3228 /** 3229 * Cleans the value 3230 * 3231 * @param string $value Cleans the provided value optimising it if possible 3232 * @return string 3233 */ 3234 protected function clean_value($value) { 3235 $values = explode(' ', $value); 3236 $values = array_map('parent::clean_value', $values); 3237 return join (' ', $values); 3238 } 3239 3240 /** 3241 * Outputs this style 3242 * 3243 * @param string $overridevalue 3244 * @return string 3245 */ 3246 public function out($overridevalue = null) { 3247 if ($overridevalue === null) { 3248 $overridevalue = $this->value; 3249 } 3250 $values = explode(' ', $overridevalue); 3251 $values = array_map('css_style_color::shrink_value', $values); 3252 return parent::out(join (' ', $values)); 3253 } 3254 } 3255 3256 /** 3257 * A border left style 3258 * 3259 * @package core 3260 * @subpackage cssoptimiser 3261 * @copyright 2012 Sam Hemelryk 3262 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3263 */ 3264 class css_style_borderleft extends css_style_generic { 3265 3266 /** 3267 * Initialises the border left style into individual components 3268 * 3269 * @param string $value 3270 * @return array Array of css_style_borderleftwidth|css_style_borderleftstyle|css_style_borderleftcolor 3271 */ 3272 public static function init($value) { 3273 $value = preg_replace('#\s+#', ' ', $value); 3274 $bits = explode(' ', $value, 3); 3275 3276 $return = array(); 3277 if (count($bits) > 0) { 3278 $return[] = css_style_borderleftwidth::init(array_shift($bits)); 3279 } 3280 if (count($bits) > 0) { 3281 $return[] = css_style_borderleftstyle::init(array_shift($bits)); 3282 } 3283 if (count($bits) > 0) { 3284 $return[] = css_style_borderleftcolor::init(array_shift($bits)); 3285 } 3286 return $return; 3287 } 3288 3289 /** 3290 * Consolidate this to a single border style 3291 * 3292 * @return string 3293 */ 3294 public function consolidate_to() { 3295 return 'border'; 3296 } 3297 } 3298 3299 /** 3300 * A border right style 3301 * 3302 * @package core 3303 * @subpackage cssoptimiser 3304 * @copyright 2012 Sam Hemelryk 3305 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3306 */ 3307 class css_style_borderright extends css_style_generic { 3308 3309 /** 3310 * Initialises the border right style into individual components 3311 * 3312 * @param string $value The value of the style 3313 * @return array Array of css_style_borderrightwidth|css_style_borderrightstyle|css_style_borderrightcolor 3314 */ 3315 public static function init($value) { 3316 $value = preg_replace('#\s+#', ' ', $value); 3317 $bits = explode(' ', $value, 3); 3318 3319 $return = array(); 3320 if (count($bits) > 0) { 3321 $return[] = css_style_borderrightwidth::init(array_shift($bits)); 3322 } 3323 if (count($bits) > 0) { 3324 $return[] = css_style_borderrightstyle::init(array_shift($bits)); 3325 } 3326 if (count($bits) > 0) { 3327 $return[] = css_style_borderrightcolor::init(array_shift($bits)); 3328 } 3329 return $return; 3330 } 3331 3332 /** 3333 * Consolidate this to a single border style 3334 * 3335 * @return string 3336 */ 3337 public function consolidate_to() { 3338 return 'border'; 3339 } 3340 } 3341 3342 /** 3343 * A border top style 3344 * 3345 * @package core 3346 * @subpackage cssoptimiser 3347 * @copyright 2012 Sam Hemelryk 3348 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3349 */ 3350 class css_style_bordertop extends css_style_generic { 3351 3352 /** 3353 * Initialises the border top style into individual components 3354 * 3355 * @param string $value The value of the style 3356 * @return array Array of css_style_bordertopwidth|css_style_bordertopstyle|css_style_bordertopcolor 3357 */ 3358 public static function init($value) { 3359 $value = preg_replace('#\s+#', ' ', $value); 3360 $bits = explode(' ', $value, 3); 3361 3362 $return = array(); 3363 if (count($bits) > 0) { 3364 $return[] = css_style_bordertopwidth::init(array_shift($bits)); 3365 } 3366 if (count($bits) > 0) { 3367 $return[] = css_style_bordertopstyle::init(array_shift($bits)); 3368 } 3369 if (count($bits) > 0) { 3370 $return[] = css_style_bordertopcolor::init(array_shift($bits)); 3371 } 3372 return $return; 3373 } 3374 3375 /** 3376 * Consolidate this to a single border style 3377 * 3378 * @return string 3379 */ 3380 public function consolidate_to() { 3381 return 'border'; 3382 } 3383 } 3384 3385 /** 3386 * A border bottom style 3387 * 3388 * @package core 3389 * @subpackage cssoptimiser 3390 * @copyright 2012 Sam Hemelryk 3391 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3392 */ 3393 class css_style_borderbottom extends css_style_generic { 3394 3395 /** 3396 * Initialises the border bottom style into individual components 3397 * 3398 * @param string $value The value of the style 3399 * @return array Array of css_style_borderbottomwidth|css_style_borderbottomstyle|css_style_borderbottomcolor 3400 */ 3401 public static function init($value) { 3402 $value = preg_replace('#\s+#', ' ', $value); 3403 $bits = explode(' ', $value, 3); 3404 3405 $return = array(); 3406 if (count($bits) > 0) { 3407 $return[] = css_style_borderbottomwidth::init(array_shift($bits)); 3408 } 3409 if (count($bits) > 0) { 3410 $return[] = css_style_borderbottomstyle::init(array_shift($bits)); 3411 } 3412 if (count($bits) > 0) { 3413 $return[] = css_style_borderbottomcolor::init(array_shift($bits)); 3414 } 3415 return $return; 3416 } 3417 3418 /** 3419 * Consolidate this to a single border style 3420 * 3421 * @return string 3422 */ 3423 public function consolidate_to() { 3424 return 'border'; 3425 } 3426 } 3427 3428 /** 3429 * A border width style 3430 * 3431 * @package core 3432 * @subpackage cssoptimiser 3433 * @copyright 2012 Sam Hemelryk 3434 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3435 */ 3436 class css_style_borderwidth extends css_style_width { 3437 3438 /** 3439 * Creates a new border colour style 3440 * 3441 * Based upon the colour style 3442 * 3443 * @param string $value The value of the style 3444 * @return array Array of css_style_border*width 3445 */ 3446 public static function init($value) { 3447 $value = preg_replace('#\s+#', ' ', $value); 3448 $bits = explode(' ', $value, 4); 3449 3450 $top = $right = $bottom = $left = null; 3451 if (count($bits) > 0) { 3452 $top = $right = $bottom = $left = array_shift($bits); 3453 } 3454 if (count($bits) > 0) { 3455 $right = $left = array_shift($bits); 3456 } 3457 if (count($bits) > 0) { 3458 $bottom = array_shift($bits); 3459 } 3460 if (count($bits) > 0) { 3461 $left = array_shift($bits); 3462 } 3463 return array( 3464 css_style_bordertopwidth::init($top), 3465 css_style_borderrightwidth::init($right), 3466 css_style_borderbottomwidth::init($bottom), 3467 css_style_borderleftwidth::init($left) 3468 ); 3469 } 3470 3471 /** 3472 * Consolidate this to a single border style 3473 * 3474 * @return string 3475 */ 3476 public function consolidate_to() { 3477 return 'border'; 3478 } 3479 3480 /** 3481 * Checks if the width is valid 3482 * @return bool 3483 */ 3484 public function is_valid() { 3485 return self::is_border_width($this->value); 3486 } 3487 3488 /** 3489 * Cleans the provided value 3490 * 3491 * @param mixed $value Cleans the provided value optimising it if possible 3492 * @return string 3493 */ 3494 protected function clean_value($value) { 3495 $isvalid = self::is_border_width($value); 3496 if (!$isvalid) { 3497 $this->set_error('Invalid width specified for '.$this->name); 3498 } else if (preg_match('#^0\D+$#', $value)) { 3499 return '0'; 3500 } 3501 return trim($value); 3502 } 3503 3504 /** 3505 * Returns true if the provided value is a permitted border width 3506 * @param string $value The value to check 3507 * @return bool 3508 */ 3509 public static function is_border_width($value) { 3510 $altwidthvalues = array('thin', 'medium', 'thick'); 3511 return css_is_width($value) || in_array($value, $altwidthvalues); 3512 } 3513 } 3514 3515 /** 3516 * A border style style 3517 * 3518 * @package core 3519 * @subpackage cssoptimiser 3520 * @copyright 2012 Sam Hemelryk 3521 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3522 */ 3523 class css_style_borderstyle extends css_style_generic { 3524 3525 /** 3526 * Creates a new border colour style 3527 * 3528 * Based upon the colour style 3529 * 3530 * @param string $value The value of the style 3531 * @return array Array of css_style_border*style 3532 */ 3533 public static function init($value) { 3534 $value = preg_replace('#\s+#', ' ', $value); 3535 $bits = explode(' ', $value, 4); 3536 3537 $top = $right = $bottom = $left = null; 3538 if (count($bits) > 0) { 3539 $top = $right = $bottom = $left = array_shift($bits); 3540 } 3541 if (count($bits) > 0) { 3542 $right = $left = array_shift($bits); 3543 } 3544 if (count($bits) > 0) { 3545 $bottom = array_shift($bits); 3546 } 3547 if (count($bits) > 0) { 3548 $left = array_shift($bits); 3549 } 3550 return array( 3551 css_style_bordertopstyle::init($top), 3552 css_style_borderrightstyle::init($right), 3553 css_style_borderbottomstyle::init($bottom), 3554 css_style_borderleftstyle::init($left) 3555 ); 3556 } 3557 3558 /** 3559 * Consolidate this to a single border style 3560 * 3561 * @return string 3562 */ 3563 public function consolidate_to() { 3564 return 'border'; 3565 } 3566 } 3567 3568 /** 3569 * A border top colour style 3570 * 3571 * @package core 3572 * @subpackage cssoptimiser 3573 * @copyright 2012 Sam Hemelryk 3574 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3575 */ 3576 class css_style_bordertopcolor extends css_style_bordercolor { 3577 3578 /** 3579 * Initialises this style object 3580 * 3581 * @param string $value The value of the style 3582 * @return css_style_bordertopcolor 3583 */ 3584 public static function init($value) { 3585 return new css_style_bordertopcolor('border-top-color', $value); 3586 } 3587 3588 /** 3589 * Consolidate this to a single border style 3590 * 3591 * @return string 3592 */ 3593 public function consolidate_to() { 3594 return 'border'; 3595 } 3596 } 3597 3598 /** 3599 * A border left colour style 3600 * 3601 * @package core 3602 * @subpackage cssoptimiser 3603 * @copyright 2012 Sam Hemelryk 3604 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3605 */ 3606 class css_style_borderleftcolor extends css_style_bordercolor { 3607 3608 /** 3609 * Initialises this style object 3610 * 3611 * @param string $value The value of the style 3612 * @return css_style_borderleftcolor 3613 */ 3614 public static function init($value) { 3615 return new css_style_borderleftcolor('border-left-color', $value); 3616 } 3617 3618 /** 3619 * Consolidate this to a single border style 3620 * 3621 * @return string 3622 */ 3623 public function consolidate_to() { 3624 return 'border'; 3625 } 3626 } 3627 3628 /** 3629 * A border right colour style 3630 * 3631 * @package core 3632 * @subpackage cssoptimiser 3633 * @copyright 2012 Sam Hemelryk 3634 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3635 */ 3636 class css_style_borderrightcolor extends css_style_bordercolor { 3637 3638 /** 3639 * Initialises this style object 3640 * 3641 * @param string $value The value of the style 3642 * @return css_style_borderrightcolor 3643 */ 3644 public static function init($value) { 3645 return new css_style_borderrightcolor('border-right-color', $value); 3646 } 3647 3648 /** 3649 * Consolidate this to a single border style 3650 * 3651 * @return string 3652 */ 3653 public function consolidate_to() { 3654 return 'border'; 3655 } 3656 } 3657 3658 /** 3659 * A border bottom colour style 3660 * 3661 * @package core 3662 * @subpackage cssoptimiser 3663 * @copyright 2012 Sam Hemelryk 3664 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3665 */ 3666 class css_style_borderbottomcolor extends css_style_bordercolor { 3667 3668 /** 3669 * Initialises this style object 3670 * 3671 * @param string $value The value of the style 3672 * @return css_style_borderbottomcolor 3673 */ 3674 public static function init($value) { 3675 return new css_style_borderbottomcolor('border-bottom-color', $value); 3676 } 3677 3678 /** 3679 * Consolidate this to a single border style 3680 * 3681 * @return string 3682 */ 3683 public function consolidate_to() { 3684 return 'border'; 3685 } 3686 } 3687 3688 /** 3689 * A border width top style 3690 * 3691 * @package core 3692 * @subpackage cssoptimiser 3693 * @copyright 2012 Sam Hemelryk 3694 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3695 */ 3696 class css_style_bordertopwidth extends css_style_borderwidth { 3697 3698 /** 3699 * Initialises this style object 3700 * 3701 * @param string $value The value of the style 3702 * @return css_style_bordertopwidth 3703 */ 3704 public static function init($value) { 3705 return new css_style_bordertopwidth('border-top-width', $value); 3706 } 3707 3708 /** 3709 * Consolidate this to a single border style 3710 * 3711 * @return string 3712 */ 3713 public function consolidate_to() { 3714 return 'border'; 3715 } 3716 } 3717 3718 /** 3719 * A border width left style 3720 * 3721 * @package core 3722 * @subpackage cssoptimiser 3723 * @copyright 2012 Sam Hemelryk 3724 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3725 */ 3726 class css_style_borderleftwidth extends css_style_borderwidth { 3727 3728 /** 3729 * Initialises this style object 3730 * 3731 * @param string $value The value of the style 3732 * @return css_style_borderleftwidth 3733 */ 3734 public static function init($value) { 3735 return new css_style_borderleftwidth('border-left-width', $value); 3736 } 3737 3738 /** 3739 * Consolidate this to a single border style 3740 * 3741 * @return string 3742 */ 3743 public function consolidate_to() { 3744 return 'border'; 3745 } 3746 } 3747 3748 /** 3749 * A border width right style 3750 * 3751 * @package core 3752 * @subpackage cssoptimiser 3753 * @copyright 2012 Sam Hemelryk 3754 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3755 */ 3756 class css_style_borderrightwidth extends css_style_borderwidth { 3757 3758 /** 3759 * Initialises this style object 3760 * 3761 * @param string $value The value of the style 3762 * @return css_style_borderrightwidth 3763 */ 3764 public static function init($value) { 3765 return new css_style_borderrightwidth('border-right-width', $value); 3766 } 3767 3768 /** 3769 * Consolidate this to a single border style 3770 * 3771 * @return string 3772 */ 3773 public function consolidate_to() { 3774 return 'border'; 3775 } 3776 } 3777 3778 /** 3779 * A border width bottom style 3780 * 3781 * @package core 3782 * @subpackage cssoptimiser 3783 * @copyright 2012 Sam Hemelryk 3784 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3785 */ 3786 class css_style_borderbottomwidth extends css_style_borderwidth { 3787 3788 /** 3789 * Initialises this style object 3790 * 3791 * @param string $value The value of the style 3792 * @return css_style_borderbottomwidth 3793 */ 3794 public static function init($value) { 3795 return new css_style_borderbottomwidth('border-bottom-width', $value); 3796 } 3797 3798 /** 3799 * Consolidate this to a single border style 3800 * 3801 * @return string 3802 */ 3803 public function consolidate_to() { 3804 return 'border'; 3805 } 3806 } 3807 3808 /** 3809 * A border top style 3810 * 3811 * @package core 3812 * @subpackage cssoptimiser 3813 * @copyright 2012 Sam Hemelryk 3814 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3815 */ 3816 class css_style_bordertopstyle extends css_style_borderstyle { 3817 3818 /** 3819 * Initialises this style object 3820 * 3821 * @param string $value The value of the style 3822 * @return css_style_bordertopstyle 3823 */ 3824 public static function init($value) { 3825 return new css_style_bordertopstyle('border-top-style', $value); 3826 } 3827 3828 /** 3829 * Consolidate this to a single border style 3830 * 3831 * @return string 3832 */ 3833 public function consolidate_to() { 3834 return 'border'; 3835 } 3836 } 3837 3838 /** 3839 * A border left style 3840 * 3841 * @package core 3842 * @subpackage cssoptimiser 3843 * @copyright 2012 Sam Hemelryk 3844 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3845 */ 3846 class css_style_borderleftstyle extends css_style_borderstyle { 3847 3848 /** 3849 * Initialises this style object 3850 * 3851 * @param string $value The value of the style 3852 * @return css_style_borderleftstyle 3853 */ 3854 public static function init($value) { 3855 return new css_style_borderleftstyle('border-left-style', $value); 3856 } 3857 3858 /** 3859 * Consolidate this to a single border style 3860 * 3861 * @return string 3862 */ 3863 public function consolidate_to() { 3864 return 'border'; 3865 } 3866 } 3867 3868 /** 3869 * A border right style 3870 * 3871 * @package core 3872 * @subpackage cssoptimiser 3873 * @copyright 2012 Sam Hemelryk 3874 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3875 */ 3876 class css_style_borderrightstyle extends css_style_borderstyle { 3877 3878 /** 3879 * Initialises this style object 3880 * 3881 * @param string $value The value of the style 3882 * @return css_style_borderrightstyle 3883 */ 3884 public static function init($value) { 3885 return new css_style_borderrightstyle('border-right-style', $value); 3886 } 3887 3888 /** 3889 * Consolidate this to a single border style 3890 * 3891 * @return string 3892 */ 3893 public function consolidate_to() { 3894 return 'border'; 3895 } 3896 } 3897 3898 /** 3899 * A border bottom style 3900 * 3901 * @package core 3902 * @subpackage cssoptimiser 3903 * @copyright 2012 Sam Hemelryk 3904 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3905 */ 3906 class css_style_borderbottomstyle extends css_style_borderstyle { 3907 3908 /** 3909 * Initialises this style object 3910 * 3911 * @param string $value The value for the style 3912 * @return css_style_borderbottomstyle 3913 */ 3914 public static function init($value) { 3915 return new css_style_borderbottomstyle('border-bottom-style', $value); 3916 } 3917 3918 /** 3919 * Consolidate this to a single border style 3920 * 3921 * @return string 3922 */ 3923 public function consolidate_to() { 3924 return 'border'; 3925 } 3926 } 3927 3928 /** 3929 * A background style 3930 * 3931 * @package core 3932 * @subpackage cssoptimiser 3933 * @copyright 2012 Sam Hemelryk 3934 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3935 */ 3936 class css_style_background extends css_style implements core_css_consolidatable_style { 3937 3938 /** 3939 * Initialises a background style 3940 * 3941 * @param string $value The value of the style 3942 * @return array An array of background component. 3943 */ 3944 public static function init($value) { 3945 // Colour - image - repeat - attachment - position. 3946 $imageurl = null; 3947 if (preg_match('#url\(([^\)]+)\)#', $value, $matches)) { 3948 $imageurl = trim($matches[1]); 3949 $value = str_replace($matches[1], '', $value); 3950 } 3951 3952 // Switch out the brackets so that they don't get messed up when we explode. 3953 $brackets = array(); 3954 $bracketcount = 0; 3955 while (preg_match('#\([^\)\(]+\)#', $value, $matches)) { 3956 $key = "##BRACKET-{$bracketcount}##"; 3957 $bracketcount++; 3958 $brackets[$key] = $matches[0]; 3959 $value = str_replace($matches[0], $key, $value); 3960 } 3961 3962 $important = (stripos($value, '!important') !== false); 3963 if ($important) { 3964 // Great some genius put !important in the background shorthand property. 3965 $value = str_replace('!important', '', $value); 3966 } 3967 3968 $value = preg_replace('#\s+#', ' ', $value); 3969 $bits = explode(' ', $value); 3970 3971 foreach ($bits as $key => $bit) { 3972 $bits[$key] = self::replace_bracket_placeholders($bit, $brackets); 3973 } 3974 unset($bracketcount); 3975 unset($brackets); 3976 3977 $repeats = array('repeat', 'repeat-x', 'repeat-y', 'no-repeat', 'inherit'); 3978 $attachments = array('scroll' , 'fixed', 'inherit'); 3979 $positions = array('top', 'left', 'bottom', 'right', 'center'); 3980 3981 /* @var css_style_background[] $return */ 3982 $return = array(); 3983 $unknownbits = array(); 3984 3985 $color = self::NULL_VALUE; 3986 if (count($bits) > 0 && css_is_colour(reset($bits))) { 3987 $color = array_shift($bits); 3988 } 3989 3990 $image = self::NULL_VALUE; 3991 if (count($bits) > 0 && preg_match('#^\s*(none|inherit|url\(\))\s*$#', reset($bits))) { 3992 $image = array_shift($bits); 3993 if ($image == 'url()') { 3994 $image = "url({$imageurl})"; 3995 } 3996 } 3997 3998 $repeat = self::NULL_VALUE; 3999 if (count($bits) > 0 && in_array(reset($bits), $repeats)) { 4000 $repeat = array_shift($bits); 4001 } 4002 4003 $attachment = self::NULL_VALUE; 4004 if (count($bits) > 0 && in_array(reset($bits), $attachments)) { 4005 // Scroll , fixed, inherit. 4006 $attachment = array_shift($bits); 4007 } 4008 4009 $position = self::NULL_VALUE; 4010 if (count($bits) > 0) { 4011 $widthbits = array(); 4012 foreach ($bits as $bit) { 4013 if (in_array($bit, $positions) || css_is_width($bit)) { 4014 $widthbits[] = $bit; 4015 } else { 4016 $unknownbits[] = $bit; 4017 } 4018 } 4019 if (count($widthbits)) { 4020 $position = join(' ', $widthbits); 4021 } 4022 } 4023 4024 if (count($unknownbits)) { 4025 foreach ($unknownbits as $bit) { 4026 $bit = trim($bit); 4027 if ($color === self::NULL_VALUE && css_is_colour($bit)) { 4028 $color = $bit; 4029 } else if ($repeat === self::NULL_VALUE && in_array($bit, $repeats)) { 4030 $repeat = $bit; 4031 } else if ($attachment === self::NULL_VALUE && in_array($bit, $attachments)) { 4032 $attachment = $bit; 4033 } else if ($bit !== '') { 4034 $advanced = css_style_background_advanced::init($bit); 4035 if ($important) { 4036 $advanced->set_important(); 4037 } 4038 $return[] = $advanced; 4039 } 4040 } 4041 } 4042 4043 if ($color === self::NULL_VALUE && 4044 $image === self::NULL_VALUE && 4045 $repeat === self::NULL_VALUE && $attachment === self::NULL_VALUE && 4046 $position === self::NULL_VALUE) { 4047 // All primaries are null, return without doing anything else. There may be advanced madness there. 4048 return $return; 4049 } 4050 4051 $return[] = css_style_backgroundcolor::init($color); 4052 $return[] = css_style_backgroundimage::init($image); 4053 $return[] = css_style_backgroundrepeat::init($repeat); 4054 $return[] = css_style_backgroundattachment::init($attachment); 4055 $return[] = css_style_backgroundposition::init($position); 4056 4057 if ($important) { 4058 foreach ($return as $style) { 4059 $style->set_important(); 4060 } 4061 } 4062 4063 return $return; 4064 } 4065 4066 /** 4067 * Static helper method to switch in bracket replacements 4068 * 4069 * @param string $value 4070 * @param array $placeholders 4071 * @return string 4072 */ 4073 protected static function replace_bracket_placeholders($value, array $placeholders) { 4074 while (preg_match('/##BRACKET-\d+##/', $value, $matches)) { 4075 $value = str_replace($matches[0], $placeholders[$matches[0]], $value); 4076 } 4077 return $value; 4078 } 4079 4080 /** 4081 * Consolidates background styles into a single background style 4082 * 4083 * @param css_style_background[] $styles Consolidates the provided array of background styles 4084 * @return css_style[] Consolidated optimised background styles 4085 */ 4086 public static function consolidate(array $styles) { 4087 4088 if (empty($styles)) { 4089 return $styles; 4090 } 4091 4092 $color = null; 4093 $image = null; 4094 $repeat = null; 4095 $attachment = null; 4096 $position = null; 4097 $size = null; 4098 $origin = null; 4099 $clip = null; 4100 4101 $someimportant = false; 4102 $allimportant = null; 4103 foreach ($styles as $style) { 4104 if ($style instanceof css_style_backgroundimage_advanced) { 4105 continue; 4106 } 4107 if ($style->is_important()) { 4108 $someimportant = true; 4109 if ($allimportant === null) { 4110 $allimportant = true; 4111 } 4112 } else if ($allimportant === true) { 4113 $allimportant = false; 4114 } 4115 } 4116 4117 /* @var css_style[] $organisedstyles */ 4118 $organisedstyles = array(); 4119 /* @var css_style[] $advancedstyles */ 4120 $advancedstyles = array(); 4121 /* @var css_style[] $importantstyles */ 4122 $importantstyles = array(); 4123 foreach ($styles as $style) { 4124 if ($style instanceof css_style_backgroundimage_advanced) { 4125 $advancedstyles[] = $style; 4126 continue; 4127 } 4128 if ($someimportant && !$allimportant && $style->is_important()) { 4129 $importantstyles[] = $style; 4130 continue; 4131 } 4132 $organisedstyles[$style->get_name()] = $style; 4133 switch ($style->get_name()) { 4134 case 'background-color' : 4135 $color = css_style_color::shrink_value($style->get_value(false)); 4136 break; 4137 case 'background-image' : 4138 $image = $style->get_value(false); 4139 break; 4140 case 'background-repeat' : 4141 $repeat = $style->get_value(false); 4142 break; 4143 case 'background-attachment' : 4144 $attachment = $style->get_value(false); 4145 break; 4146 case 'background-position' : 4147 $position = $style->get_value(false); 4148 break; 4149 case 'background-clip' : 4150 $clip = $style->get_value(); 4151 break; 4152 case 'background-origin' : 4153 $origin = $style->get_value(); 4154 break; 4155 case 'background-size' : 4156 $size = $style->get_value(); 4157 break; 4158 } 4159 } 4160 4161 /* @var css_style[] $consolidatetosingle */ 4162 $consolidatetosingle = array(); 4163 if (!is_null($color) && !is_null($image) && !is_null($repeat) && !is_null($attachment) && !is_null($position)) { 4164 // We can use the shorthand background-style! 4165 if (!$organisedstyles['background-color']->is_special_empty_value()) { 4166 $consolidatetosingle[] = $color; 4167 } 4168 if (!$organisedstyles['background-image']->is_special_empty_value()) { 4169 $consolidatetosingle[] = $image; 4170 } 4171 if (!$organisedstyles['background-repeat']->is_special_empty_value()) { 4172 $consolidatetosingle[] = $repeat; 4173 } 4174 if (!$organisedstyles['background-attachment']->is_special_empty_value()) { 4175 $consolidatetosingle[] = $attachment; 4176 } 4177 if (!$organisedstyles['background-position']->is_special_empty_value()) { 4178 $consolidatetosingle[] = $position; 4179 } 4180 // Reset them all to null so we don't use them again. 4181 $color = null; 4182 $image = null; 4183 $repeat = null; 4184 $attachment = null; 4185 $position = null; 4186 } 4187 4188 $return = array(); 4189 // Single background style needs to come first. 4190 if (count($consolidatetosingle) > 0) { 4191 $returnstyle = new css_style_background('background', join(' ', $consolidatetosingle)); 4192 if ($allimportant) { 4193 $returnstyle->set_important(); 4194 } 4195 $return[] = $returnstyle; 4196 } 4197 foreach ($styles as $style) { 4198 $value = null; 4199 switch ($style->get_name()) { 4200 case 'background-color' : 4201 $value = $color; 4202 break; 4203 case 'background-image' : 4204 $value = $image; 4205 break; 4206 case 'background-repeat' : 4207 $value = $repeat; 4208 break; 4209 case 'background-attachment' : 4210 $value = $attachment; 4211 break; 4212 case 'background-position' : 4213 $value = $position; 4214 break; 4215 case 'background-clip' : 4216 $value = $clip; 4217 break; 4218 case 'background-origin': 4219 $value = $origin; 4220 break; 4221 case 'background-size': 4222 $value = $size; 4223 break; 4224 } 4225 if (!is_null($value)) { 4226 $return[] = $style; 4227 } 4228 } 4229 $return = array_merge($return, $importantstyles, $advancedstyles); 4230 return $return; 4231 } 4232 } 4233 4234 /** 4235 * A advanced background style that allows multiple values to preserve unknown entities 4236 * 4237 * @package core 4238 * @subpackage cssoptimiser 4239 * @copyright 2012 Sam Hemelryk 4240 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4241 */ 4242 class css_style_background_advanced extends css_style_generic { 4243 /** 4244 * Creates a new background colour style 4245 * 4246 * @param string $value The value of the style 4247 * @return css_style_backgroundimage 4248 */ 4249 public static function init($value) { 4250 $value = preg_replace('#\s+#', ' ', $value); 4251 return new css_style_background_advanced('background', $value); 4252 } 4253 4254 /** 4255 * Returns true because the advanced background image supports multiple values. 4256 * e.g. -webkit-linear-gradient and -moz-linear-gradient. 4257 * 4258 * @return boolean 4259 */ 4260 public function allows_multiple_values() { 4261 return true; 4262 } 4263 } 4264 4265 /** 4266 * A background colour style. 4267 * 4268 * Based upon the colour style. 4269 * 4270 * @package core 4271 * @subpackage cssoptimiser 4272 * @copyright 2012 Sam Hemelryk 4273 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4274 */ 4275 class css_style_backgroundcolor extends css_style_color { 4276 4277 /** 4278 * Creates a new background colour style 4279 * 4280 * @param string $value The value of the style 4281 * @return css_style_backgroundcolor 4282 */ 4283 public static function init($value) { 4284 return new css_style_backgroundcolor('background-color', $value); 4285 } 4286 4287 /** 4288 * css_style_backgroundcolor consolidates to css_style_background 4289 * 4290 * @return string 4291 */ 4292 public function consolidate_to() { 4293 return 'background'; 4294 } 4295 4296 /** 4297 * Returns true if the value for this style is the special null value. 4298 * 4299 * This occurs if the shorthand background property was used but no proper value 4300 * was specified for this style. 4301 * This leads to a null value being used unless otherwise overridden. 4302 * 4303 * @return bool 4304 */ 4305 public function is_special_empty_value() { 4306 return ($this->value === self::NULL_VALUE); 4307 } 4308 4309 /** 4310 * Returns true if the value for this style is valid 4311 * @return bool 4312 */ 4313 public function is_valid() { 4314 return $this->is_special_empty_value() || parent::is_valid(); 4315 } 4316 } 4317 4318 /** 4319 * A background image style. 4320 * 4321 * @package core 4322 * @subpackage cssoptimiser 4323 * @copyright 2012 Sam Hemelryk 4324 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4325 */ 4326 class css_style_backgroundimage extends css_style_generic { 4327 4328 /** 4329 * Creates a new background image style 4330 * 4331 * @param string $value The value of the style 4332 * @return css_style_backgroundimage 4333 */ 4334 public static function init($value) { 4335 if ($value !== self::NULL_VALUE && !preg_match('#^\s*(none|inherit|url\()#i', $value)) { 4336 return css_style_backgroundimage_advanced::init($value); 4337 } 4338 return new css_style_backgroundimage('background-image', $value); 4339 } 4340 4341 /** 4342 * Consolidates this style into a single background style 4343 * 4344 * @return string 4345 */ 4346 public function consolidate_to() { 4347 return 'background'; 4348 } 4349 4350 /** 4351 * Returns true if the value for this style is the special null value. 4352 * 4353 * This occurs if the shorthand background property was used but no proper value 4354 * was specified for this style. 4355 * This leads to a null value being used unless otherwise overridden. 4356 * 4357 * @return bool 4358 */ 4359 public function is_special_empty_value() { 4360 return ($this->value === self::NULL_VALUE); 4361 } 4362 4363 /** 4364 * Returns true if the value for this style is valid 4365 * @return bool 4366 */ 4367 public function is_valid() { 4368 return $this->is_special_empty_value() || parent::is_valid(); 4369 } 4370 } 4371 4372 /** 4373 * A background image style that supports multiple values and masquerades as a background-image 4374 * 4375 * @package core 4376 * @subpackage cssoptimiser 4377 * @copyright 2012 Sam Hemelryk 4378 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4379 */ 4380 class css_style_backgroundimage_advanced extends css_style_generic { 4381 /** 4382 * Creates a new background colour style 4383 * 4384 * @param string $value The value of the style 4385 * @return css_style_backgroundimage 4386 */ 4387 public static function init($value) { 4388 $value = preg_replace('#\s+#', ' ', $value); 4389 return new css_style_backgroundimage_advanced('background-image', $value); 4390 } 4391 4392 /** 4393 * Returns true because the advanced background image supports multiple values. 4394 * e.g. -webkit-linear-gradient and -moz-linear-gradient. 4395 * 4396 * @return boolean 4397 */ 4398 public function allows_multiple_values() { 4399 return true; 4400 } 4401 } 4402 4403 /** 4404 * A background repeat style. 4405 * 4406 * @package core 4407 * @subpackage cssoptimiser 4408 * @copyright 2012 Sam Hemelryk 4409 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4410 */ 4411 class css_style_backgroundrepeat extends css_style_generic { 4412 4413 /** 4414 * Creates a new background colour style 4415 * 4416 * @param string $value The value of the style 4417 * @return css_style_backgroundrepeat 4418 */ 4419 public static function init($value) { 4420 return new css_style_backgroundrepeat('background-repeat', $value); 4421 } 4422 4423 /** 4424 * Consolidates this style into a single background style 4425 * 4426 * @return string 4427 */ 4428 public function consolidate_to() { 4429 return 'background'; 4430 } 4431 4432 /** 4433 * Returns true if the value for this style is the special null value. 4434 * 4435 * This occurs if the shorthand background property was used but no proper value 4436 * was specified for this style. 4437 * This leads to a null value being used unless otherwise overridden. 4438 * 4439 * @return bool 4440 */ 4441 public function is_special_empty_value() { 4442 return ($this->value === self::NULL_VALUE); 4443 } 4444 4445 /** 4446 * Returns true if the value for this style is valid 4447 * @return bool 4448 */ 4449 public function is_valid() { 4450 return $this->is_special_empty_value() || parent::is_valid(); 4451 } 4452 } 4453 4454 /** 4455 * A background attachment style. 4456 * 4457 * @package core 4458 * @subpackage cssoptimiser 4459 * @copyright 2012 Sam Hemelryk 4460 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4461 */ 4462 class css_style_backgroundattachment extends css_style_generic { 4463 4464 /** 4465 * Creates a new background colour style 4466 * 4467 * @param string $value The value of the style 4468 * @return css_style_backgroundattachment 4469 */ 4470 public static function init($value) { 4471 return new css_style_backgroundattachment('background-attachment', $value); 4472 } 4473 4474 /** 4475 * Consolidates this style into a single background style 4476 * 4477 * @return string 4478 */ 4479 public function consolidate_to() { 4480 return 'background'; 4481 } 4482 4483 /** 4484 * Returns true if the value for this style is the special null value. 4485 * 4486 * This occurs if the shorthand background property was used but no proper value 4487 * was specified for this style. 4488 * This leads to a null value being used unless otherwise overridden. 4489 * 4490 * @return bool 4491 */ 4492 public function is_special_empty_value() { 4493 return ($this->value === self::NULL_VALUE); 4494 } 4495 4496 /** 4497 * Returns true if the value for this style is valid 4498 * @return bool 4499 */ 4500 public function is_valid() { 4501 return $this->is_special_empty_value() || parent::is_valid(); 4502 } 4503 } 4504 4505 /** 4506 * A background position style. 4507 * 4508 * @package core 4509 * @subpackage cssoptimiser 4510 * @copyright 2012 Sam Hemelryk 4511 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4512 */ 4513 class css_style_backgroundposition extends css_style_generic { 4514 4515 /** 4516 * Creates a new background colour style 4517 * 4518 * @param string $value The value of the style 4519 * @return css_style_backgroundposition 4520 */ 4521 public static function init($value) { 4522 return new css_style_backgroundposition('background-position', $value); 4523 } 4524 4525 /** 4526 * Consolidates this style into a single background style 4527 * 4528 * @return string 4529 */ 4530 public function consolidate_to() { 4531 return 'background'; 4532 } 4533 4534 /** 4535 * Returns true if the value for this style is the special null value. 4536 * 4537 * This occurs if the shorthand background property was used but no proper value 4538 * was specified for this style. 4539 * This leads to a null value being used unless otherwise overridden. 4540 * 4541 * @return bool 4542 */ 4543 public function is_special_empty_value() { 4544 return ($this->value === self::NULL_VALUE); 4545 } 4546 4547 /** 4548 * Returns true if the value for this style is valid 4549 * @return bool 4550 */ 4551 public function is_valid() { 4552 return $this->is_special_empty_value() || parent::is_valid(); 4553 } 4554 } 4555 4556 /** 4557 * A background size style. 4558 * 4559 * @package core 4560 * @subpackage cssoptimiser 4561 * @copyright 2012 Sam Hemelryk 4562 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4563 */ 4564 class css_style_backgroundsize extends css_style_generic { 4565 4566 /** 4567 * Creates a new background size style 4568 * 4569 * @param string $value The value of the style 4570 * @return css_style_backgroundposition 4571 */ 4572 public static function init($value) { 4573 return new css_style_backgroundsize('background-size', $value); 4574 } 4575 4576 /** 4577 * Consolidates this style into a single background style 4578 * 4579 * @return string 4580 */ 4581 public function consolidate_to() { 4582 return 'background'; 4583 } 4584 } 4585 4586 /** 4587 * A background clip style. 4588 * 4589 * @package core 4590 * @subpackage cssoptimiser 4591 * @copyright 2012 Sam Hemelryk 4592 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4593 */ 4594 class css_style_backgroundclip extends css_style_generic { 4595 4596 /** 4597 * Creates a new background clip style 4598 * 4599 * @param string $value The value of the style 4600 * @return css_style_backgroundposition 4601 */ 4602 public static function init($value) { 4603 return new css_style_backgroundclip('background-clip', $value); 4604 } 4605 4606 /** 4607 * Consolidates this style into a single background style 4608 * 4609 * @return string 4610 */ 4611 public function consolidate_to() { 4612 return 'background'; 4613 } 4614 } 4615 4616 /** 4617 * A background origin style. 4618 * 4619 * @package core 4620 * @subpackage cssoptimiser 4621 * @copyright 2012 Sam Hemelryk 4622 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4623 */ 4624 class css_style_backgroundorigin extends css_style_generic { 4625 4626 /** 4627 * Creates a new background origin style 4628 * 4629 * @param string $value The value of the style 4630 * @return css_style_backgroundposition 4631 */ 4632 public static function init($value) { 4633 return new css_style_backgroundorigin('background-origin', $value); 4634 } 4635 4636 /** 4637 * Consolidates this style into a single background style 4638 * 4639 * @return string 4640 */ 4641 public function consolidate_to() { 4642 return 'background'; 4643 } 4644 } 4645 4646 /** 4647 * A padding style. 4648 * 4649 * @package core 4650 * @subpackage cssoptimiser 4651 * @copyright 2012 Sam Hemelryk 4652 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4653 */ 4654 class css_style_padding extends css_style_width implements core_css_consolidatable_style { 4655 4656 /** 4657 * Initialises this padding style into several individual padding styles 4658 * 4659 * @param string $value The value fo the style 4660 * @return array An array of padding styles 4661 */ 4662 public static function init($value) { 4663 $important = ''; 4664 if (strpos($value, '!important') !== false) { 4665 $important = ' !important'; 4666 $value = str_replace('!important', '', $value); 4667 } 4668 4669 $value = preg_replace('#\s+#', ' ', trim($value)); 4670 $bits = explode(' ', $value, 4); 4671 4672 $top = $right = $bottom = $left = null; 4673 if (count($bits) > 0) { 4674 $top = $right = $bottom = $left = array_shift($bits); 4675 } 4676 if (count($bits) > 0) { 4677 $right = $left = array_shift($bits); 4678 } 4679 if (count($bits) > 0) { 4680 $bottom = array_shift($bits); 4681 } 4682 if (count($bits) > 0) { 4683 $left = array_shift($bits); 4684 } 4685 return array( 4686 new css_style_paddingtop('padding-top', $top.$important), 4687 new css_style_paddingright('padding-right', $right.$important), 4688 new css_style_paddingbottom('padding-bottom', $bottom.$important), 4689 new css_style_paddingleft('padding-left', $left.$important) 4690 ); 4691 } 4692 4693 /** 4694 * Consolidates several padding styles into a single style. 4695 * 4696 * @param css_style_padding[] $styles Array of padding styles 4697 * @return css_style[] Optimised+consolidated array of padding styles 4698 */ 4699 public static function consolidate(array $styles) { 4700 if (count($styles) != 4) { 4701 return $styles; 4702 } 4703 4704 $someimportant = false; 4705 $allimportant = null; 4706 $notimportantequal = null; 4707 $firstvalue = null; 4708 foreach ($styles as $style) { 4709 if ($style->is_important()) { 4710 $someimportant = true; 4711 if ($allimportant === null) { 4712 $allimportant = true; 4713 } 4714 } else { 4715 if ($allimportant === true) { 4716 $allimportant = false; 4717 } 4718 if ($firstvalue == null) { 4719 $firstvalue = $style->get_value(false); 4720 $notimportantequal = true; 4721 } else if ($notimportantequal && $firstvalue !== $style->get_value(false)) { 4722 $notimportantequal = false; 4723 } 4724 } 4725 } 4726 4727 if ($someimportant && !$allimportant && !$notimportantequal) { 4728 return $styles; 4729 } 4730 4731 if ($someimportant && !$allimportant && $notimportantequal) { 4732 $return = array( 4733 new css_style_padding('padding', $firstvalue) 4734 ); 4735 foreach ($styles as $style) { 4736 if ($style->is_important()) { 4737 $return[] = $style; 4738 } 4739 } 4740 return $return; 4741 } else { 4742 $top = null; 4743 $right = null; 4744 $bottom = null; 4745 $left = null; 4746 foreach ($styles as $style) { 4747 switch ($style->get_name()) { 4748 case 'padding-top' : 4749 $top = $style->get_value(false); 4750 break; 4751 case 'padding-right' : 4752 $right = $style->get_value(false); 4753 break; 4754 case 'padding-bottom' : 4755 $bottom = $style->get_value(false); 4756 break; 4757 case 'padding-left' : 4758 $left = $style->get_value(false); 4759 break; 4760 } 4761 } 4762 if ($top == $bottom && $left == $right) { 4763 if ($top == $left) { 4764 $returnstyle = new css_style_padding('padding', $top); 4765 } else { 4766 $returnstyle = new css_style_padding('padding', "{$top} {$left}"); 4767 } 4768 } else if ($left == $right) { 4769 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom}"); 4770 } else { 4771 $returnstyle = new css_style_padding('padding', "{$top} {$right} {$bottom} {$left}"); 4772 } 4773 if ($allimportant) { 4774 $returnstyle->set_important(); 4775 } 4776 return array($returnstyle); 4777 } 4778 } 4779 } 4780 4781 /** 4782 * A padding top style. 4783 * 4784 * @package core 4785 * @subpackage cssoptimiser 4786 * @copyright 2012 Sam Hemelryk 4787 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4788 */ 4789 class css_style_paddingtop extends css_style_padding { 4790 4791 /** 4792 * Initialises this style 4793 * 4794 * @param string $value The value of the style 4795 * @return css_style_paddingtop 4796 */ 4797 public static function init($value) { 4798 return new css_style_paddingtop('padding-top', $value); 4799 } 4800 4801 /** 4802 * Consolidates this style into a single padding style 4803 * 4804 * @return string 4805 */ 4806 public function consolidate_to() { 4807 return 'padding'; 4808 } 4809 } 4810 4811 /** 4812 * A padding right style. 4813 * 4814 * @package core 4815 * @subpackage cssoptimiser 4816 * @copyright 2012 Sam Hemelryk 4817 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4818 */ 4819 class css_style_paddingright extends css_style_padding { 4820 4821 /** 4822 * Initialises this style 4823 * 4824 * @param string $value The value of the style 4825 * @return css_style_paddingright 4826 */ 4827 public static function init($value) { 4828 return new css_style_paddingright('padding-right', $value); 4829 } 4830 4831 /** 4832 * Consolidates this style into a single padding style 4833 * 4834 * @return string 4835 */ 4836 public function consolidate_to() { 4837 return 'padding'; 4838 } 4839 } 4840 4841 /** 4842 * A padding bottom style. 4843 * 4844 * @package core 4845 * @subpackage cssoptimiser 4846 * @copyright 2012 Sam Hemelryk 4847 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4848 */ 4849 class css_style_paddingbottom extends css_style_padding { 4850 4851 /** 4852 * Initialises this style 4853 * 4854 * @param string $value The value of the style 4855 * @return css_style_paddingbottom 4856 */ 4857 public static function init($value) { 4858 return new css_style_paddingbottom('padding-bottom', $value); 4859 } 4860 4861 /** 4862 * Consolidates this style into a single padding style 4863 * 4864 * @return string 4865 */ 4866 public function consolidate_to() { 4867 return 'padding'; 4868 } 4869 } 4870 4871 /** 4872 * A padding left style. 4873 * 4874 * @package core 4875 * @subpackage cssoptimiser 4876 * @copyright 2012 Sam Hemelryk 4877 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4878 */ 4879 class css_style_paddingleft extends css_style_padding { 4880 4881 /** 4882 * Initialises this style 4883 * 4884 * @param string $value The value of the style 4885 * @return css_style_paddingleft 4886 */ 4887 public static function init($value) { 4888 return new css_style_paddingleft('padding-left', $value); 4889 } 4890 4891 /** 4892 * Consolidates this style into a single padding style 4893 * 4894 * @return string 4895 */ 4896 public function consolidate_to() { 4897 return 'padding'; 4898 } 4899 } 4900 4901 /** 4902 * A cursor style. 4903 * 4904 * @package core 4905 * @subpackage cssoptimiser 4906 * @copyright 2012 Sam Hemelryk 4907 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4908 */ 4909 class css_style_cursor extends css_style_generic { 4910 /** 4911 * Initialises a new cursor style 4912 * @param string $value 4913 * @return css_style_cursor 4914 */ 4915 public static function init($value) { 4916 return new css_style_cursor('cursor', $value); 4917 } 4918 /** 4919 * Cleans the given value and returns it. 4920 * 4921 * @param string $value 4922 * @return string 4923 */ 4924 protected function clean_value($value) { 4925 // Allowed values for the cursor style. 4926 $allowed = array('auto', 'crosshair', 'default', 'e-resize', 'help', 'move', 'n-resize', 'ne-resize', 'nw-resize', 4927 'pointer', 'progress', 's-resize', 'se-resize', 'sw-resize', 'text', 'w-resize', 'wait', 'inherit'); 4928 // Has to be one of the allowed values of an image to use. Loosely match the image... doesn't need to be thorough. 4929 if (!in_array($value, $allowed) && !preg_match('#\.[a-zA-Z0-9_\-]{1,5}$#', $value)) { 4930 $this->set_error('Invalid or unexpected cursor value specified: '.$value); 4931 } 4932 return trim($value); 4933 } 4934 } 4935 4936 /** 4937 * A vertical alignment style. 4938 * 4939 * @package core 4940 * @subpackage cssoptimiser 4941 * @copyright 2012 Sam Hemelryk 4942 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4943 */ 4944 class css_style_verticalalign extends css_style_generic { 4945 /** 4946 * Initialises a new vertical alignment style 4947 * @param string $value 4948 * @return css_style_verticalalign 4949 */ 4950 public static function init($value) { 4951 return new css_style_verticalalign('vertical-align', $value); 4952 } 4953 /** 4954 * Cleans the given value and returns it. 4955 * 4956 * @param string $value 4957 * @return string 4958 */ 4959 protected function clean_value($value) { 4960 $allowed = array('baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom', 'inherit'); 4961 if (!css_is_width($value) && !in_array($value, $allowed)) { 4962 $this->set_error('Invalid vertical-align value specified: '.$value); 4963 } 4964 return trim($value); 4965 } 4966 } 4967 4968 /** 4969 * A float style. 4970 * 4971 * @package core 4972 * @subpackage cssoptimiser 4973 * @copyright 2012 Sam Hemelryk 4974 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4975 */ 4976 class css_style_float extends css_style_generic { 4977 /** 4978 * Initialises a new float style 4979 * @param string $value 4980 * @return css_style_float 4981 */ 4982 public static function init($value) { 4983 return new css_style_float('float', $value); 4984 } 4985 /** 4986 * Cleans the given value and returns it. 4987 * 4988 * @param string $value 4989 * @return string 4990 */ 4991 protected function clean_value($value) { 4992 $allowed = array('left', 'right', 'none', 'inherit'); 4993 if (!css_is_width($value) && !in_array($value, $allowed)) { 4994 $this->set_error('Invalid float value specified: '.$value); 4995 } 4996 return trim($value); 4997 } 4998 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |