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