Mercurial > hg > rr-repo
comparison sites/all/modules/ctools/includes/css.inc @ 0:ff03f76ab3fe
initial version
author | danieleb <danielebarchiesi@me.com> |
---|---|
date | Wed, 21 Aug 2013 18:51:11 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:ff03f76ab3fe |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * @file | |
5 * CSS filtering functions. Contains a disassembler, filter, compressor, and | |
6 * decompressor. | |
7 * | |
8 * The general usage of this tool is: | |
9 * | |
10 * To simply filter CSS: | |
11 * @code | |
12 * $filtered_css = ctools_css_filter($css, TRUE); | |
13 * @endcode | |
14 * | |
15 * In the above, if the second argument is TRUE, the returned CSS will | |
16 * be compressed. Otherwise it will be returned in a well formatted | |
17 * syntax. | |
18 * | |
19 * To cache unfiltered CSS in a file, which will be filtered: | |
20 * | |
21 * @code | |
22 * $filename = ctools_css_cache($css, TRUE); | |
23 * @endcode | |
24 * | |
25 * In the above, if the second argument is FALSE, the CSS will not be filtered. | |
26 * | |
27 * This file will be cached within the Drupal files system. This system cannot | |
28 * detect when this file changes, so it is YOUR responsibility to remove and | |
29 * re-cache this file when the CSS is changed. Your system should also contain | |
30 * a backup method of re-generating the CSS cache in case it is removed, so | |
31 * that it is easy to force a re-cache by simply deleting the contents of the | |
32 * directory. | |
33 * | |
34 * Finally, if for some reason your application cannot store the filename | |
35 * (which is true of Panels where the style can't force the display to | |
36 * resave unconditionally) you can use the ctools storage mechanism. You | |
37 * simply have to come up with a unique Id: | |
38 * | |
39 * @code | |
40 * $filename = ctools_css_store($id, $css, TRUE); | |
41 * @endcode | |
42 * | |
43 * Then later on: | |
44 * @code | |
45 * $filename = ctools_css_retrieve($id); | |
46 * drupal_add_css($filename); | |
47 * @endcode | |
48 * | |
49 * The CSS that was generated will be stored in the database, so even if the | |
50 * file was removed the cached CSS will be used. If the CSS cache is | |
51 * cleared you may be required to regenerate your CSS. This will normally | |
52 * only be cleared by an administrator operation, not during normal usage. | |
53 * | |
54 * You may remove your stored CSS this way: | |
55 * | |
56 * @code | |
57 * ctools_css_clear($id); | |
58 * @endcode | |
59 */ | |
60 | |
61 /** | |
62 * Store CSS with a given id and return the filename to use. | |
63 * | |
64 * This function associates a piece of CSS with an id, and stores the | |
65 * cached filename and the actual CSS for later use with | |
66 * ctools_css_retrieve. | |
67 */ | |
68 function ctools_css_store($id, $css, $filter = TRUE) { | |
69 $filename = db_query('SELECT filename FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchField(); | |
70 if ($filename && file_exists($filename)) { | |
71 file_unmanaged_delete($filename); | |
72 } | |
73 // Remove any previous records. | |
74 db_delete('ctools_css_cache') | |
75 ->condition('cid', $id) | |
76 ->execute(); | |
77 | |
78 $filename = ctools_css_cache($css, $filter); | |
79 | |
80 db_insert('ctools_css_cache') | |
81 ->fields(array( | |
82 'cid' => $id, | |
83 'filename' => $filename, | |
84 'css' => $css, | |
85 'filter' => intval($filter), | |
86 )) | |
87 ->execute(); | |
88 | |
89 return $filename; | |
90 } | |
91 | |
92 /** | |
93 * Retrieve a filename associated with an id of previously cached CSS. | |
94 * | |
95 * This will ensure the file still exists and, if not, create it. | |
96 */ | |
97 function ctools_css_retrieve($id) { | |
98 $cache = db_query('SELECT * FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchObject(); | |
99 if (!$cache) { | |
100 return; | |
101 } | |
102 | |
103 if (!file_exists($cache->filename)) { | |
104 $filename = ctools_css_cache($cache->css, $cache->filter); | |
105 if ($filename != $cache->filename) { | |
106 db_update('ctools_css_cache') | |
107 ->fields(array('filename' => $filename)) | |
108 ->condition('cid', $id) | |
109 ->execute(); | |
110 $cache->filename = $filename; | |
111 } | |
112 } | |
113 | |
114 return $cache->filename; | |
115 } | |
116 | |
117 /** | |
118 * Remove stored CSS and any associated file. | |
119 */ | |
120 function ctools_css_clear($id) { | |
121 $cache = db_query('SELECT * FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchObject(); | |
122 if (!$cache) { | |
123 return; | |
124 } | |
125 | |
126 if (file_exists($cache->filename)) { | |
127 file_unmanaged_delete($cache->filename); | |
128 // If we remove an existing file, there may be cached pages that refer | |
129 // to it. We must get rid of them: FIXME same format in D7? | |
130 cache_clear_all(); | |
131 } | |
132 | |
133 db_delete('ctools_css_cache') | |
134 ->condition('cid', $id) | |
135 ->execute(); | |
136 } | |
137 | |
138 /** | |
139 * Write a chunk of CSS to a temporary cache file and return the file name. | |
140 * | |
141 * This function optionally filters the CSS (always compressed, if so) and | |
142 * generates a unique filename based upon md5. It returns that filename that | |
143 * can be used with drupal_add_css(). Note that as a cache file, technically | |
144 * this file is volatile so it should be checked before it is used to ensure | |
145 * that it exists. | |
146 * | |
147 * You can use file_exists() to test for the file and file_delete() to remove | |
148 * it if it needs to be cleared. | |
149 * | |
150 * @param $css | |
151 * A chunk of well-formed CSS text to cache. | |
152 * @param $filter | |
153 * If TRUE the css will be filtered. If FALSE the text will be cached | |
154 * as-is. | |
155 * | |
156 * @return $filename | |
157 * The filename the CSS will be cached in. | |
158 */ | |
159 function ctools_css_cache($css, $filter = TRUE) { | |
160 if ($filter) { | |
161 $css = ctools_css_filter($css); | |
162 } | |
163 | |
164 // Create the css/ within the files folder. | |
165 $path = 'public://ctools/css'; | |
166 if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { | |
167 // if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) { | |
168 drupal_set_message(t('Unable to create CTools CSS cache directory. Check the permissions on your files directory.'), 'error'); | |
169 return; | |
170 } | |
171 | |
172 // @todo Is this slow? Does it matter if it is? | |
173 $filename = $path . '/' . md5($css) . '.css'; | |
174 | |
175 // This will do renames if the file already exists, ensuring we don't | |
176 // accidentally overwrite other files who share the same md5. Yes this | |
177 // is a very miniscule chance but it's safe. | |
178 $filename = file_unmanaged_save_data($css, $filename); | |
179 | |
180 return $filename; | |
181 } | |
182 | |
183 /** | |
184 * Filter a chunk of CSS text. | |
185 * | |
186 * This function disassembles the CSS into a raw format that makes it easier | |
187 * for our tool to work, then runs it through the filter and reassembles it. | |
188 * If you find that you want the raw data for some reason or another, you | |
189 * can use the disassemble/assemble functions yourself. | |
190 * | |
191 * @param $css | |
192 * The CSS text to filter. | |
193 * @param $compressed | |
194 * If true, generate compressed output; if false, generate pretty output. | |
195 * Defaults to TRUE. | |
196 */ | |
197 function ctools_css_filter($css, $compressed = TRUE) { | |
198 $css_data = ctools_css_disassemble($css); | |
199 | |
200 // Note: By using this function yourself you can control the allowed | |
201 // properties and values list. | |
202 $filtered = ctools_css_filter_css_data($css_data); | |
203 | |
204 return $compressed ? ctools_css_compress($filtered) : ctools_css_assemble($filtered); | |
205 } | |
206 | |
207 /** | |
208 * Re-assemble a css string and format it nicely. | |
209 * | |
210 * @param array $css_data | |
211 * An array of css data, as produced by @see ctools_css_disassemble() | |
212 * disassembler and the @see ctools_css_filter_css_data() filter. | |
213 * | |
214 * @return string $css | |
215 * css optimized for human viewing. | |
216 */ | |
217 function ctools_css_assemble($css_data) { | |
218 // Initialize the output. | |
219 $css = ''; | |
220 // Iterate through all the statements. | |
221 foreach ($css_data as $selector_str => $declaration) { | |
222 // Add the selectors, separating them with commas and line feeds. | |
223 $css .= strpos($selector_str, ',') === FALSE ? $selector_str : str_replace(", ", ",\n", $selector_str); | |
224 // Add the opening curly brace. | |
225 $css .= " {\n"; | |
226 // Iterate through all the declarations. | |
227 foreach ($declaration as $property => $value) { | |
228 $css .= " " . $property . ": " . $value . ";\n"; | |
229 } | |
230 // Add the closing curly brace. | |
231 $css .= "}\n\n"; | |
232 } | |
233 // Return the output. | |
234 return $css; | |
235 } | |
236 | |
237 /** | |
238 * Compress css data (filter it first!) to optimize for use on view. | |
239 * | |
240 * @param array $css_data | |
241 * An array of css data, as produced by @see ctools_css_disassemble() | |
242 * disassembler and the @see ctools_css_filter_css_data() filter. | |
243 * | |
244 * @return string $css | |
245 * css optimized for use. | |
246 */ | |
247 function ctools_css_compress($css_data) { | |
248 // Initialize the output. | |
249 $css = ''; | |
250 // Iterate through all the statements. | |
251 foreach ($css_data as $selector_str => $declaration) { | |
252 if (empty($declaration)) { | |
253 // Skip this statement if filtering removed all parts of the declaration. | |
254 continue; | |
255 } | |
256 // Add the selectors, separating them with commas. | |
257 $css .= $selector_str; | |
258 // And, the opening curly brace. | |
259 $css .= "{"; | |
260 // Iterate through all the statement properties. | |
261 foreach ($declaration as $property => $value) { | |
262 $css .= $property . ':' . $value . ';'; | |
263 } | |
264 // Add the closing curly brace. | |
265 $css .= "}"; | |
266 } | |
267 // Return the output. | |
268 return $css; | |
269 } | |
270 | |
271 /** | |
272 * Disassemble the css string. | |
273 * | |
274 * Strip the css of irrelevant characters, invalid/malformed selectors and | |
275 * declarations, and otherwise prepare it for processing. | |
276 * | |
277 * @param string $css | |
278 * A string containing the css to be disassembled. | |
279 * | |
280 * @return array $disassembled_css | |
281 * An array of disassembled, slightly cleaned-up/formatted css statements. | |
282 */ | |
283 function ctools_css_disassemble($css) { | |
284 $disassembled_css = array(); | |
285 // Remove comments. | |
286 $css = preg_replace("/\/\*(.*)?\*\//Usi", "", $css); | |
287 // Split out each statement. Match either a right curly brace or a semi-colon | |
288 // that precedes a left curly brace with no right curly brace separating them. | |
289 $statements = preg_split('/}|;(?=[^}]*{)/', $css); | |
290 | |
291 // If we have any statements, parse them. | |
292 if (!empty($statements)) { | |
293 // Iterate through all of the statements. | |
294 foreach ($statements as $statement) { | |
295 // Get the selector(s) and declaration. | |
296 if (empty($statement) || !strpos($statement, '{')) { | |
297 continue; | |
298 } | |
299 | |
300 list($selector_str, $declaration) = explode('{', $statement); | |
301 | |
302 // If the selector exists, then disassemble it, check it, and regenerate | |
303 // the selector string. | |
304 $selector_str = empty($selector_str) ? FALSE : _ctools_css_disassemble_selector($selector_str); | |
305 if (empty($selector_str)) { | |
306 // No valid selectors. Bomb out and start the next item. | |
307 continue; | |
308 } | |
309 | |
310 // Disassemble the declaration, check it and tuck it into an array. | |
311 if (!isset($disassembled_css[$selector_str])) { | |
312 $disassembled_css[$selector_str] = array(); | |
313 } | |
314 $disassembled_css[$selector_str] += _ctools_css_disassemble_declaration($declaration); | |
315 } | |
316 } | |
317 return $disassembled_css; | |
318 } | |
319 | |
320 function _ctools_css_disassemble_selector($selector_str) { | |
321 // Get all selectors individually. | |
322 $selectors = explode(",", trim($selector_str)); | |
323 // Iterate through all the selectors, sanity check them and return if they | |
324 // pass. Note that this handles 0, 1, or more valid selectors gracefully. | |
325 foreach ($selectors as $key => $selector) { | |
326 // Replace un-needed characters and do a little cleanup. | |
327 $selector = preg_replace("/[\n|\t|\\|\s]+/", ' ', trim($selector)); | |
328 // Make sure this is still a real selector after cleanup. | |
329 if (!empty($selector)) { | |
330 $selectors[$key] = $selector; | |
331 } | |
332 else { | |
333 // Selector is no good, so we scrap it. | |
334 unset($selectors[$key]); | |
335 } | |
336 } | |
337 // Check for malformed selectors; if found, we skip this declaration. | |
338 if (empty($selectors)) { | |
339 return FALSE; | |
340 } | |
341 return implode(', ', $selectors); | |
342 } | |
343 | |
344 function _ctools_css_disassemble_declaration($declaration) { | |
345 $formatted_statement = array(); | |
346 $propval_pairs = explode(";", $declaration); | |
347 // Make sure we actually have some properties to work with. | |
348 if (!empty($propval_pairs)) { | |
349 // Iterate through the remains and parse them. | |
350 foreach ($propval_pairs as $key => $propval_pair) { | |
351 // Check that we have a ':', otherwise it's an invalid pair. | |
352 if (strpos($propval_pair, ':') === FALSE) { | |
353 continue; | |
354 } | |
355 // Clean up the current property-value pair. | |
356 $propval_pair = preg_replace("/[\n|\t|\\|\s]+/", ' ', trim($propval_pair)); | |
357 // Explode the remaining fragements some more, but clean them up first. | |
358 list($property, $value) = explode(':', $propval_pair, 2); | |
359 // If the property survived, toss it onto the stack. | |
360 if (!empty($property)) { | |
361 $formatted_statement[trim($property)] = trim($value); | |
362 } | |
363 } | |
364 } | |
365 return $formatted_statement; | |
366 } | |
367 | |
368 /** | |
369 * Run disassembled $css through the filter. | |
370 * | |
371 * @param $css | |
372 * CSS code disassembled by ctools_dss_disassemble(). | |
373 * @param $allowed_properties | |
374 * A list of properties that are allowed by the filter. If empty | |
375 * ctools_css_filter_default_allowed_properties() will provide the | |
376 * list. | |
377 * @param $allowed_values | |
378 * A list of values that are allowed by the filter. If empty | |
379 * ctools_css_filter_default_allowed_values() will provide the | |
380 * list. | |
381 * | |
382 * @return | |
383 * An array of disassembled, filtered CSS. | |
384 */ | |
385 function ctools_css_filter_css_data($css, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') { | |
386 //function ctools_css_filter_css_data($css, &$filtered = NULL, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') { | |
387 // Retrieve the default list of allowed properties if none is provided. | |
388 $allowed_properties = !empty($allowed_properties) ? $allowed_properties : ctools_css_filter_default_allowed_properties(); | |
389 // Retrieve the default list of allowed values if none is provided. | |
390 $allowed_values = !empty($allowed_values) ? $allowed_values : ctools_css_filter_default_allowed_values(); | |
391 // Define allowed values regex if none is provided. | |
392 $allowed_values_regex = !empty($allowed_values_regex) ? $allowed_values_regex : '/(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)/'; | |
393 // Define disallowed url() value contents, if none is provided. | |
394 // $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/[url|expression]\s*\(\s*[^\s)]+?\s*\)\s*/'; | |
395 $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/(url|expression)/'; | |
396 | |
397 foreach ($css as $selector_str => $declaration) { | |
398 foreach ($declaration as $property => $value) { | |
399 if (!in_array($property, $allowed_properties)) { | |
400 // $filtered['properties'][$selector_str][$property] = $value; | |
401 unset($css[$selector_str][$property]); | |
402 continue; | |
403 } | |
404 $value = str_replace('!important', '', $value); | |
405 if (preg_match($disallowed_values_regex, $value) || !(in_array($value, $allowed_values) || preg_match($allowed_values_regex, $value))) { | |
406 // $filtered['values'][$selector_str][$property] = $value; | |
407 unset($css[$selector_str][$property]); | |
408 continue; | |
409 } | |
410 } | |
411 } | |
412 return $css; | |
413 } | |
414 | |
415 /** | |
416 * Provide a deafult list of allowed properties by the filter. | |
417 */ | |
418 function ctools_css_filter_default_allowed_properties() { | |
419 return array( | |
420 'azimuth', | |
421 'background', | |
422 'background-color', | |
423 'background-image', | |
424 'background-repeat', | |
425 'background-attachment', | |
426 'background-position', | |
427 'border', | |
428 'border-top-width', | |
429 'border-right-width', | |
430 'border-bottom-width', | |
431 'border-left-width', | |
432 'border-width', | |
433 'border-top-color', | |
434 'border-right-color', | |
435 'border-bottom-color', | |
436 'border-left-color', | |
437 'border-color', | |
438 'border-top-style', | |
439 'border-right-style', | |
440 'border-bottom-style', | |
441 'border-left-style', | |
442 'border-style', | |
443 'border-top', | |
444 'border-right', | |
445 'border-bottom', | |
446 'border-left', | |
447 'clear', | |
448 'color', | |
449 'cursor', | |
450 'direction', | |
451 'display', | |
452 'elevation', | |
453 'float', | |
454 'font', | |
455 'font-family', | |
456 'font-size', | |
457 'font-style', | |
458 'font-variant', | |
459 'font-weight', | |
460 'height', | |
461 'letter-spacing', | |
462 'line-height', | |
463 'margin', | |
464 'margin-top', | |
465 'margin-right', | |
466 'margin-bottom', | |
467 'margin-left', | |
468 'overflow', | |
469 'padding', | |
470 'padding-top', | |
471 'padding-right', | |
472 'padding-bottom', | |
473 'padding-left', | |
474 'pause', | |
475 'pause-after', | |
476 'pause-before', | |
477 'pitch', | |
478 'pitch-range', | |
479 'richness', | |
480 'speak', | |
481 'speak-header', | |
482 'speak-numeral', | |
483 'speak-punctuation', | |
484 'speech-rate', | |
485 'stress', | |
486 'text-align', | |
487 'text-decoration', | |
488 'text-indent', | |
489 'text-transform', | |
490 'unicode-bidi', | |
491 'vertical-align', | |
492 'voice-family', | |
493 'volume', | |
494 'white-space', | |
495 'width', | |
496 'fill', | |
497 'fill-opacity', | |
498 'fill-rule', | |
499 'stroke', | |
500 'stroke-width', | |
501 'stroke-linecap', | |
502 'stroke-linejoin', | |
503 'stroke-opacity', | |
504 ); | |
505 } | |
506 | |
507 /** | |
508 * Provide a default list of allowed values by the filter. | |
509 */ | |
510 function ctools_css_filter_default_allowed_values() { | |
511 return array( | |
512 'auto', | |
513 'aqua', | |
514 'black', | |
515 'block', | |
516 'blue', | |
517 'bold', | |
518 'both', | |
519 'bottom', | |
520 'brown', | |
521 'capitalize', | |
522 'center', | |
523 'collapse', | |
524 'dashed', | |
525 'dotted', | |
526 'fuchsia', | |
527 'gray', | |
528 'green', | |
529 'italic', | |
530 'inherit', | |
531 'left', | |
532 'lime', | |
533 'lowercase', | |
534 'maroon', | |
535 'medium', | |
536 'navy', | |
537 'normal', | |
538 'nowrap', | |
539 'olive', | |
540 'pointer', | |
541 'purple', | |
542 'red', | |
543 'right', | |
544 'solid', | |
545 'silver', | |
546 'teal', | |
547 'top', | |
548 'transparent', | |
549 'underline', | |
550 'uppercase', | |
551 'white', | |
552 'yellow', | |
553 ); | |
554 } | |
555 | |
556 /** | |
557 * Delegated implementation of hook_flush_caches() | |
558 */ | |
559 function ctools_css_flush_caches() { | |
560 // Remove all generated files. | |
561 // @see http://drupal.org/node/573292 | |
562 // file_unmanaged_delete_recursive('public://render'); | |
563 $filedir = file_default_scheme() . '://ctools/css'; | |
564 if (drupal_realpath($filedir) && file_exists($filedir)) { | |
565 // We use the @ because it's possible that files created by the webserver | |
566 // cannot be deleted while using drush to clear the cache. We don't really | |
567 // care that much about that, to be honest, so we use the @ to suppress | |
568 // the error message. | |
569 @file_unmanaged_delete_recursive($filedir); | |
570 } | |
571 | |
572 db_delete('ctools_css_cache')->execute(); | |
573 } |