PageRenderTime 73ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/csslib.php

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