Projet

Général

Profil

Paste
Télécharger (17,3 ko) Statistiques
| Branche: | Révision:

root / htmltest / sites / all / modules / ctools / includes / css.inc @ 3753f249

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_merge('ctools_css_cache')
81
    ->key(array('cid' => $id))
82
    ->fields(array(
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
}