Issue #1207782 by eule, mikeytown2: Fix update 6109.
[project/advagg.git] / advagg.module
1 <?php
2
3 /**
4 * @file
5 * Advanced CSS/JS aggregation module
6 *
7 */
8
9 /**
10 * Default value to see if advanced CSS/JS aggregation is enabled.
11 */
12 define('ADVAGG_ENABLED', TRUE);
13
14 /**
15 * Default stale file threshold is 6 days for mtime.
16 */
17 define('ADVAGG_STALE_FILE_THRESHOLD', 518400);
18
19 /**
20 * Default stale file threshold is 3 days for atime.
21 */
22 define('ADVAGG_STALE_FILE_LAST_USED_THRESHOLD', 259200);
23
24 /**
25 * Default file last used check-in is 12 hours.
26 */
27 define('ADVAGG_FILE_LAST_USED_INTERVAL', 1296000);
28
29 /**
30 * Default gzip compression setting.
31 */
32 define('ADVAGG_GZIP_COMPRESSION', FALSE);
33
34 /**
35 * Default generate the aggregate async.
36 */
37 define('ADVAGG_ASYNC_GENERATION', FALSE);
38
39 /**
40 * How long to wait for the server to come back with an async opp.
41 */
42 define('ADVAGG_SOCKET_TIMEOUT', 1);
43
44 /**
45 * Default file checksum mode.
46 */
47 define('ADVAGG_CHECKSUM_MODE', 'mtime');
48
49 /**
50 * Default value for writing debug info to watchdog.
51 */
52 define('ADVAGG_DEBUG', FALSE);
53
54 /**
55 * Default value for number of files that can be added before using @import.
56 */
57 define('ADVAGG_CSS_COUNT_THRESHOLD', 25);
58
59 /**
60 * Default value for not using @import if logged in and not IE.
61 */
62 define('ADVAGG_CSS_LOGGED_IN_IE_DETECT', TRUE);
63
64 /**
65 * Default value for creating a htaccess file in the advagg directories.
66 */
67 define('ADVAGG_DIR_HTACCESS', TRUE);
68
69 /**
70 * Default value for a custom files directory just for advagg directories.
71 */
72 define('ADVAGG_CUSTOM_FILES_DIR', '');
73
74 /**
75 * Default function used to render css output.
76 */
77 define('ADVAGG_CSS_RENDER_FUNCTION', 'advagg_unlimited_css_builder');
78
79 /**
80 * Default function used to render js output.
81 */
82 define('ADVAGG_JS_RENDER_FUNCTION', 'advagg_js_builder');
83
84 /**
85 * Default function used to save files.
86 */
87 define('ADVAGG_FILE_SAVE_FUNCTION', 'advagg_file_saver');
88
89 /**
90 * Default value for rebuilding the bundle on cache flush.
91 */
92 define('ADVAGG_REBUILD_ON_FLUSH', FALSE);
93
94 /**
95 * Default value to see if advanced CSS/JS aggregation is enabled.
96 */
97 define('ADVAGG_CLOSURE', TRUE);
98
99 /**
100 * Default value to see if JS bundle matching should be strict.
101 */
102 define('ADVAGG_STRICT_JS_BUNDLES', TRUE);
103
104 /**
105 * Default mode for aggregate creation.
106 *
107 * 0 - Wait for locks.
108 * 1 - Do not wait for locks.
109 * 2 - Only serve aggregated files if they are already built.
110 */
111 define('ADVAGG_AGGREGATE_MODE', 2);
112
113 /**
114 * Default mode of advagg in regards to the page cache.
115 *
116 * FALSE - Cache all pages.
117 * TRUE - Don't cache page if aggregate could be included on that page & it is
118 * not.
119 */
120 define('ADVAGG_PAGE_CACHE_MODE', TRUE);
121
122 /**
123 * Default mode of advagg_bundle_built() in regards to how file_exists is used.
124 *
125 * FALSE - use file_exists.
126 * TRUE - use cache_get instead of file_exists if possible.
127 */
128 define('ADVAGG_BUNDLE_BUILT_MODE', FALSE);
129
130 /**
131 * Default value to see if we can use the STREAM_CLIENT_ASYNC_CONNECT flag.
132 */
133 define('ADVAGG_ASYNC_SOCKET_CONNECT', FALSE);
134
135 /**
136 * Default value to see if we removed old files/bundles from the database.
137 */
138 define('ADVAGG_PRUNE_ON_CRON', TRUE);
139
140 /**
141 * Implementation of hook_perm().
142 */
143 function advagg_perm() {
144 return array('bypass advanced aggregation');
145 }
146
147 /**
148 * Implementation of hook_menu().
149 */
150 function advagg_menu() {
151 list($css_path, $js_path) = advagg_get_root_files_dir();
152 $file_path = drupal_get_path('module', 'advagg');
153
154 $items = array();
155 $items[$css_path . '/%'] = array(
156 'page callback' => 'advagg_missing_css',
157 'type' => MENU_CALLBACK,
158 'access callback' => TRUE,
159 'file path' => $file_path,
160 'file' => 'advagg.missing.inc',
161 );
162 $items[$js_path . '/%'] = array(
163 'page callback' => 'advagg_missing_js',
164 'type' => MENU_CALLBACK,
165 'access callback' => TRUE,
166 'file path' => $file_path,
167 'file' => 'advagg.missing.inc',
168 );
169 $items['admin/settings/advagg'] = array(
170 'title' => 'Advanced CSS/JS Aggregation',
171 'description' => 'Configuration for Advanced CSS/JS Aggregation.',
172 'page callback' => 'advagg_admin_page',
173 'type' => MENU_NORMAL_ITEM,
174 'access arguments' => array('administer site configuration'),
175 'file path' => $file_path,
176 'file' => 'advagg.admin.inc',
177 );
178 $items['admin/settings/advagg/config'] = array(
179 'title' => 'Configuration',
180 'type' => MENU_DEFAULT_LOCAL_TASK,
181 'weight' => 0,
182 );
183 $items['admin/settings/advagg/info'] = array(
184 'title' => 'Information',
185 'description' => 'More detailed information about advagg.',
186 'page callback' => 'advagg_admin_info_page',
187 'type' => MENU_LOCAL_TASK,
188 'access arguments' => array('administer site configuration'),
189 'file path' => $file_path,
190 'file' => 'advagg.admin.inc',
191 );
192 $items['admin_menu/flush-cache/advagg'] = array(
193 'page callback' => 'advagg_admin_flush_cache',
194 'type' => MENU_CALLBACK,
195 'access arguments' => array('administer site configuration'),
196 'file path' => $file_path,
197 'file' => 'advagg.admin.inc',
198 );
199 return $items;
200 }
201
202 /**
203 * Implementation of hook_admin_menu().
204 *
205 * Add in a cache flush for advagg.
206 */
207 function advagg_admin_menu(&$deleted) {
208 $links = array();
209
210 $links[] = array(
211 'title' => 'Adv CSS/JS Agg',
212 'path' => 'admin_menu/flush-cache/advagg',
213 'query' => 'destination',
214 'parent_path' => 'admin_menu/flush-cache',
215 );
216
217 return $links;
218 }
219
220 /**
221 * Implementation of hook_admin_menu_output_alter().
222 *
223 * Add in a cache flush for advagg.
224 */
225 function advagg_admin_menu_output_alter(&$content) {
226 if (!empty($content['icon']['icon']['flush-cache']['#access']) && !empty($content['icon']['icon']['flush-cache']['requisites']) && empty($content['icon']['icon']['flush-cache']['advagg'])) {
227 $content['icon']['icon']['flush-cache']['advagg'] = $content['icon']['icon']['flush-cache']['requisites'];
228 $content['icon']['icon']['flush-cache']['advagg']['#title'] = t('Adv CSS/JS Agg');
229 $content['icon']['icon']['flush-cache']['advagg']['#href'] = 'admin_menu/flush-cache/advagg';
230 }
231 }
232
233 /**
234 * Implementation of hook_cron().
235 */
236 function advagg_cron() {
237 if (!variable_get('advagg_prune_on_cron', ADVAGG_PRUNE_ON_CRON)) {
238 return;
239 }
240
241 // Set the oldest file/bundle to keep at 2 weeks.
242 $max_time = module_exists('advagg_bundler') ? variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED) : 1209600;
243 $max_file_time = time() - $max_time;
244 $max_bundle_time = time() - ($max_time*3);
245 $bundles_removed = 0;
246 $files_removed = array();
247
248 // Prune old files
249 $results = db_query("SELECT filename, filename_md5 FROM {advagg_files}");
250 while ($row = db_fetch_array($results)) {
251 // If the file exists, do nothing
252 if (file_exists($row['filename'])) {
253 continue;
254 }
255
256 // Remove bundles referencing missing files, if they are older than 2 weeks.
257 $bundles = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s' AND timestamp > %d", $row['filename_md5'], $max_file_time);
258 while ($bundle_md5 = db_result($bundles)) {
259 $bundles_removed++;
260 db_query("DELETE FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5);
261 }
262 $count = db_result(db_query("SELECT COUNT(*) FROM {advagg_bundles} WHERE filename_md5 = '%s'", $row['filename_md5']));
263
264 // If no more bundles reference the missing file then remove the file.
265 if (empty($count)) {
266 db_query("DELETE FROM {advagg_files} WHERE filename_md5 = '%s'", $row['filename_md5']);
267 $files_removed[] = $row['filename'];
268 }
269 }
270
271 // Prune old bundles
272 $bundles_removed += db_result(db_query("
273 SELECT COUNT(*)
274 FROM (
275 SELECT *
276 FROM {advagg_bundles}
277 WHERE timestamp < %d
278 GROUP BY bundle_md5
279 ) AS advagg_count", $max_bundle_time));
280 $results = db_query("DELETE FROM {advagg_bundles} WHERE timestamp < %d", $max_bundle_time);
281
282 // Report to watchdog if anything was done.
283 if (!empty($bundles_removed) || !empty($files_removed)) {
284 watchdog('advagg', 'Cron ran and the following files where removed from the database: %files <br /> %count old bundles where also removed from the database.', array(
285 '%files' => implode(', ', $files_removed),
286 '%count' => $bundles_removed,
287 ));
288 }
289 }
290
291 /**
292 * Implementation of hook_init().
293 */
294 function advagg_init() {
295 global $base_path, $conf, $_advagg;
296
297 // Disable advagg if requested.
298 if (isset($_GET['advagg']) && $_GET['advagg'] == -1 && user_access('bypass advanced aggregation')) {
299 $conf['advagg_enabled'] = FALSE;
300 }
301 // Enable debugging if requested.
302 if (isset($_GET['advagg-debug']) && $_GET['advagg-debug'] == 1 && user_access('bypass advanced aggregation')) {
303 $conf['advagg_debug'] = TRUE;
304 }
305 // Enable core preprocessing if requested.
306 if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 && user_access('bypass advanced aggregation')) {
307 $conf['preprocess_css'] = TRUE;
308 $conf['preprocess_js'] = TRUE;
309 }
310
311 // Disable ctools_ajax_page_preprocess() if this functionality is available.
312 if (variable_get('advagg_enabled', ADVAGG_ENABLED) && function_exists('ctools_ajax_run_page_preprocess')) {
313 ctools_ajax_run_page_preprocess(FALSE);
314 $_advagg['ctools_patched'] = TRUE;
315 }
316
317 // Create a closure function that does not add JavaScript.
318 if (variable_get('advagg_closure', ADVAGG_CLOSURE)) {
319 if (!function_exists('phptemplate_closure')) {
320 $_advagg['closure'] = TRUE;
321
322 /**
323 * Execute hook_footer() which is run at the end of the page right before
324 * the close of the body tag.
325 *
326 * @param $main (optional)
327 * Whether the current page is the front page of the site.
328 * @return
329 * A string containing the results of the hook_footer() calls.
330 */
331 function phptemplate_closure($main = 0) {
332 $footer = implode("\n", module_invoke_all('footer', $main));
333 // If advagg is disabled, then include footer JS here.
334 if (!variable_get('advagg_enabled', ADVAGG_ENABLED)) {
335 $footer .= drupal_get_js('footer');
336 }
337 return $footer;
338 }
339
340 }
341 else {
342 $_advagg['closure'] = FALSE;
343 }
344 }
345 }
346
347 /**
348 * Implementation of hook_theme_registry_alter().
349 *
350 * Make sure our preprocess function runs last for page.
351 *
352 * @param $theme_registry
353 * The existing theme registry data structure.
354 */
355 function advagg_theme_registry_alter(&$theme_registry) {
356 global $_advagg;
357 if (isset($theme_registry['page'])) {
358 // If jquery_update's preprocess function is there already, remove it.
359 if (module_exists('jquery_update') && $key = array_search('jquery_update_preprocess_page', $theme_registry['page']['preprocess functions'])) {
360 unset($theme_registry['page']['preprocess functions'][$key]);
361 }
362
363 // If ctools hasn't been patched remove it from getting pre-processed.
364 if ( !empty($_advagg['ctools_patched'])
365 && module_exists('ctools')
366 && $key = array_search('ctools_ajax_page_preprocess', $theme_registry['page']['preprocess functions'])
367 ) {
368 unset($theme_registry['page']['preprocess functions'][$key]);
369 }
370
371 // Add our own preprocessing function to the end of the array.
372 $theme_registry['page']['preprocess functions'][] = 'advagg_processor';
373
374 // If labjs's is enabled, move it to the bottom.
375 if (module_exists('labjs') && $key = array_search('labjs_preprocess_page', $theme_registry['page']['preprocess functions'])) {
376 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
377 unset($theme_registry['page']['preprocess functions'][$key]);
378 }
379
380 // If designkit is enabled, move it to the bottom.
381 if (module_exists('designkit') && $key = array_search('designkit_preprocess_page', $theme_registry['page']['preprocess functions'])) {
382 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
383 unset($theme_registry['page']['preprocess functions'][$key]);
384 }
385
386 // If conditional styles is enabled, move it to the bottom.
387 if (module_exists('conditional_styles') && $key = array_search('conditional_styles_preprocess_page', $theme_registry['page']['preprocess functions'])) {
388 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
389 unset($theme_registry['page']['preprocess functions'][$key]);
390 }
391 }
392 }
393
394 /**
395 * Get the CSS & JS path for advagg.
396 *
397 * @param $reset
398 * reset the static variables.
399 * @return
400 * array($css_path, $js_path)
401 */
402 function advagg_get_root_files_dir($reset = FALSE) {
403 static $css_path = '';
404 static $js_path = '';
405 if ($reset) {
406 $css_path = '';
407 $js_path = '';
408 }
409
410 if (!empty($css_path) && !empty($js_path)) {
411 return array($css_path, $js_path);
412 }
413
414 $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
415 if (!$public_downloads) {
416 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR);
417 }
418 if (empty($custom_path)) {
419 $css_path = file_create_path('advagg_css');
420 $js_path = file_create_path('advagg_js');
421 return array($css_path, $js_path);
422 }
423 file_check_directory($custom_path, FILE_CREATE_DIRECTORY);
424
425 // Get path name
426 $conf_path = conf_path();
427 if (is_link($conf_path)) {
428 $path = readlink($conf_path);
429 }
430 $conf_path = str_replace("\\", '/', $conf_path);
431 $conf_path = explode('/', $conf_path);
432 $conf_path = array_pop($conf_path);
433 $custom_path = $custom_path . '/' . $conf_path;
434 file_check_directory($custom_path, FILE_CREATE_DIRECTORY);
435
436 $css_path = $custom_path . '/advagg_css';
437 $js_path = $custom_path . '/advagg_js';
438 file_check_directory($css_path, FILE_CREATE_DIRECTORY);
439 file_check_directory($js_path, FILE_CREATE_DIRECTORY);
440 return array($css_path, $js_path);
441 }
442
443 /**
444 * Merge 2 css arrays together.
445 *
446 * @param $array1
447 * first array
448 * @param $array2
449 * second array
450 * @return
451 * combined array
452 */
453 function advagg_merge_css($array1, $array2) {
454 foreach ($array2 as $media => $types) {
455 foreach ($types as $type => $files) {
456 foreach ($files as $file => $preprocess) {
457 $array1[$media][$type][$file] = $preprocess;
458 }
459 }
460 }
461 return $array1;
462 }
463
464 /**
465 * Merge 2 css arrays together.
466 *
467 * @param $array1
468 * first array
469 * @param $array2
470 * second array
471 * @return
472 * combined array
473 */
474 function advagg_merge_inline_css($array1, $array2) {
475 foreach ($array2 as $media => $types) {
476 foreach ($types as $type => $blobs) {
477 foreach ($blobs as $prefix => $data) {
478 foreach ($data as $suffix => $blob) {
479 $array1[$media][$type][$prefix][$suffix] = $blob;
480 }
481 }
482 }
483 }
484 return $array1;
485 }
486
487 /**
488 * Remove .less files from the array.
489 *
490 * @param $css_func
491 * Drupal CSS array.
492 */
493 function advagg_css_array_fixer(&$css_func) {
494 if (!module_exists('less')) {
495 return;
496 }
497
498 // Remove '.css.less' files from the stack.
499 foreach ($css_func as $k => $v) {
500 foreach ($v as $ke => $va) {
501 foreach ($va as $file => $preprocess) {
502 if (advagg_string_ends_with($file, '.css.less')) {
503 unset($css_func[$k][$ke][$file]);
504 }
505 }
506 }
507 }
508 }
509
510 /**
511 * See if a string ends with a substring.
512 *
513 * @param $haystack
514 * The main string being compared.
515 * @param $needle
516 * The secondary string being compared.
517 * @return
518 * bool
519 */
520 function advagg_string_ends_with($haystack, $needle) {
521 // Define substr_compare if it doesn't exist (PHP 4 fix).
522 if (!function_exists('substr_compare')) {
523 /**
524 * Binary safe comparison of two strings from an offset, up to length
525 * characters.
526 *
527 * Compares main_str from position offset with str up to length characters.
528 * @see http://php.net/substr-compare#53084
529 *
530 * @param $main_str
531 * The main string being compared.
532 * @param $str
533 * The secondary string being compared.
534 * @param $offset
535 * The start position for the comparison. If negative, it starts counting
536 * from the end of the string.
537 * @param $length
538 * The length of the comparison. The default value is the largest of the
539 * length of the str compared to the length of main_str less the offset.
540 * @param $case_insensitivity
541 * If TRUE, comparison is case insensitive.
542 * @return
543 * Returns < 0 if main_str from position offset is less than str, > 0 if
544 * it is greater than str, and 0 if they are equal. If offset is equal to
545 * or greater than the length of main_str or length is set and is less than
546 * 1, substr_compare() prints a warning and returns FALSE.
547 */
548 function substr_compare($main_str, $str, $offset, $length = NULL, $case_insensitivity = FALSE) {
549 $offset = (int) $offset;
550
551 // Throw a warning because the offset is invalid
552 if ($offset >= strlen($main_str)) {
553 trigger_error('The start position cannot exceed initial string length.', E_USER_WARNING);
554 return FALSE;
555 }
556
557 // We are comparing the first n-characters of each string, so let's use the PHP function to do it
558 if ($offset == 0 && is_int($length) && $case_insensitivity === TRUE) {
559 return strncasecmp($main_str, $str, $length);
560 }
561
562 // Get the substring that we are comparing
563 if (is_int($length)) {
564 $main_substr = substr($main_str, $offset, $length);
565 $str_substr = substr($str, 0, $length);
566 }
567 else {
568 $main_substr = substr($main_str, $offset);
569 $str_substr = $str;
570 }
571
572 // Return a case-insensitive comparison of the two strings
573 if ($case_insensitivity === TRUE) {
574 return strcasecmp($main_substr, $str_substr);
575 }
576
577 // Return a case-sensitive comparison of the two strings
578 return strcmp($main_substr, $str_substr);
579 }
580 }
581
582 $haystack_len = strlen($haystack);
583 $needle_len = strlen($needle);
584 if ($needle_len > $haystack_len) {
585 return FALSE;
586 }
587 return substr_compare($haystack, $needle, $haystack_len-$needle_len, $needle_len, TRUE) === 0;
588 }
589
590 /**
591 * Implementation of hook_advagg_disable_processor().
592 */
593 function advagg_advagg_disable_processor() {
594 // Disable advagg on the configuration page; in case something bad happened.
595 if (isset($_GET['q']) &&
596 ( $_GET['q'] == 'admin/settings/advagg'
597 || $_GET['q'] == 'admin/settings/advagg/config'
598 || $_GET['q'] == 'batch'
599 )
600 ) {
601 return TRUE;
602 }
603 }
604
605 /**
606 * Process variables for page.tpl.php
607 *
608 * @param $variables
609 * The existing theme data structure.
610 */
611 function advagg_processor(&$variables) {
612 global $_advagg;
613
614 // Invoke hook_advagg_disable_processor
615 $disabled = module_invoke_all('advagg_disable_processor');
616
617 // If disabled, skip
618 if (!variable_get('advagg_enabled', ADVAGG_ENABLED) || in_array(TRUE, $disabled, TRUE)) {
619 if (module_exists('jquery_update')) {
620 return jquery_update_preprocess_page($variables);
621 }
622 else {
623 return;
624 }
625 }
626
627 // CSS
628 $css_var = $variables['css'];
629 $css_orig = $css_var;
630 $css_func = drupal_add_css();
631 advagg_css_array_fixer($css_func);
632 $css = advagg_merge_css($css_func, $css_var);
633 $css_func_inline = advagg_add_css_inline();
634 if (!empty($css_func_inline)) {
635 $css = advagg_merge_inline_css($css, $css_func_inline);
636 }
637 $css_conditional_styles = !empty($variables['conditional_styles']) ? $variables['conditional_styles'] : '';
638 $css_styles = $variables['styles'];
639 // Build HTML code.
640 $processed_css = advagg_process_css($css);
641 if (!empty($processed_css)) {
642 $variables['styles'] = $processed_css;
643 }
644 $variables['styles'] .= "\n". $css_conditional_styles;
645
646 // JS
647 $js_code = array();
648 $js_code['header'] = drupal_add_js(NULL, NULL, 'header');
649 if (variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) {
650 $js_code['footer'] = drupal_add_js(NULL, NULL, 'footer');
651 }
652 $skip_keys = variable_get('advagg_region_skip_keys', array('styles', 'scripts', 'zebra', 'id', 'directory', 'layout', 'head_title', 'base_path', 'front_page', 'head', 'body_classes', 'header', 'footer', 'closure'));
653 foreach ($variables as $key => $value) {
654 if (!in_array($key, $skip_keys) && is_string($value) && !empty($value) && !isset($js_code[$key])) {
655 $js_code[$key] = drupal_add_js(NULL, NULL, $key);
656 }
657 }
658 $js_code_orig = $js_code;
659 // Build HTML code.
660 advagg_jquery_updater($js_code['header']);
661 $js_code = advagg_process_js($js_code);
662 foreach ($js_code as $key => $value) {
663 if ($key == 'header') {
664 $variables['scripts'] = $value;
665 }
666 elseif ($key == 'footer' && variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) {
667 $variables['closure'] .= $value;
668 }
669 else {
670 $variables[$key] .= $value;
671 }
672 }
673
674 // Send requests to server if async enabled.
675 advagg_async_send_http_request();
676
677 // Write debug info to watchdog if debugging enabled.
678 if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
679 $data = array(
680 'css_before_vars' => $css_orig,
681 'css_before_function' => $css_func,
682 'css_before_styles' => $css_styles,
683 'css_before_inline' => $css_func_inline,
684 'css_before_conditional_styles' => $css_conditional_styles,
685 'css_merged' => $css,
686 'css_after' => $processed_css,
687 'js_before' => $js_code_orig,
688 'js_after' => $js_code,
689 );
690 $data['runtime'] = isset($_advagg['debug']) ? $_advagg['debug'] : FALSE;
691 $data = str_replace(' ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br(htmlentities(iconv('utf-8', 'utf-8//IGNORE', print_r($data, TRUE)), ENT_QUOTES, 'UTF-8')));
692 watchdog('advagg', 'Debug info: !data', array('!data' => $data), WATCHDOG_DEBUG);
693 }
694 }
695
696 /**
697 * Special handling for jquery update.
698 *
699 * @param $js
700 * List of files in the header
701 */
702 function advagg_jquery_updater(&$js) {
703 if (!module_exists('jquery_update') || !variable_get('jquery_update_replace', TRUE) || empty($js)) {
704 return;
705 }
706
707 // Replace jquery.js first.
708 $new_jquery = array(jquery_update_jquery_path() => $js['core']['misc/jquery.js']);
709 $js['core'] = array_merge($new_jquery, $js['core']);
710 unset($js['core']['misc/jquery.js']);
711
712 // Loop through each of the required replacements.
713 foreach (jquery_update_get_replacements() as $type => $replacements) {
714 foreach ($replacements as $find => $replace) {
715 // If the file to replace is loaded on this page...
716 if (isset($js[$type][$find])) {
717 // Create a new entry for the replacement file, and unset the original one.
718 $replace = JQUERY_UPDATE_REPLACE_PATH .'/'. $replace;
719 $js[$type][$replace] = $js[$type][$find];
720 unset($js[$type][$find]);
721 }
722 }
723 }
724 }
725
726 /**
727 * Given a list of files; return back the aggregated filename.
728 *
729 * @param $files
730 * List of files in the proposed bundle.
731 * @param $filetype
732 * css or js.
733 * @param $counter
734 * (optional) Counter value.
735 * @param $bundle_md5
736 * (optional) Bundle's machine name.
737 * @return
738 * Aggregated filename.
739 */
740 function advagg_get_filename($files, $filetype, $counter = FALSE, $bundle_md5 = '') {
741 if (empty($files) || empty($filetype)) {
742 return FALSE;
743 }
744
745 global $_advagg;
746 $filenames = array();
747
748 $run_alter = FALSE;
749 if (empty($bundle_md5)) {
750 // Create bundle md5
751 $bundle_md5 = md5(implode('', $files));
752 $run_alter = TRUE;
753
754 // Record root request in db.
755 // Get counter if there.
756 if (empty($counter)) {
757 $counter = db_result(db_query("SELECT counter FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5));
758 }
759 // If this is a brand new bundle then insert file/bundle info into database.
760 if ($counter === FALSE) {
761 $counter = 0;
762 advagg_insert_bundle_db($files, $filetype, $bundle_md5, TRUE);
763 }
764 // If bundle should be root and is not, then make it root.
765 // Refresh timestamp if older then 12 hours.
766 $row = db_fetch_array(db_query("SELECT root, timestamp FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5));
767 if ($row['root'] === 0 || time() - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) {
768 db_query("UPDATE {advagg_bundles} SET root = '1', timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5);
769 }
770 }
771
772 // Set original array.
773 $filenames[] = array(
774 'filetype' => $filetype,
775 'files' => $files,
776 'counter' => $counter,
777 'bundle_md5' => $bundle_md5,
778 );
779
780 // Invoke hook_advagg_filenames_alter() to give installed modules a chance to
781 // alter filenames. One to many relationships come to mind.
782 // Do not run alter if MD5 was given, we want to generate that file only in
783 // this special case.
784 if ($run_alter) {
785 // Force counter to be looked up later.
786 $filenames[0]['counter'] = FALSE;
787 drupal_alter('advagg_filenames', $filenames);
788 }
789
790 // Write to DB if needed and create filenames.
791 $output = array();
792 $used_md5 = array();
793 if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
794 $_advagg['debug']['get_filename_post_alter'][] = array(
795 'key' => $bundle_md5,
796 'filenames' => $filenames,
797 );
798 }
799
800 // Get all counters at once
801 $counters = array();
802 foreach ($filenames as $key => $values) {
803 if (empty($values['counter'])) {
804 $counters[$key] = $values['bundle_md5'];
805 }
806 }
807 $result = advagg_db_multi_select_in('advagg_bundles', 'bundle_md5', "'%s'", $counters, array('counter', 'bundle_md5'), 'GROUP BY bundle_md5');
808 while ($row = db_fetch_array($result)) {
809 $key = array_search($row['bundle_md5'], $counters);
810 if (empty($filenames[$key]['counter']) && $filenames[$key]['counter'] !== 0) {
811 $filenames[$key]['counter'] = intval($row['counter']);
812 }
813 }
814
815 foreach ($filenames as $values) {
816 // Get info from array.
817 $filetype = $values['filetype'];
818 $files = $values['files'];
819 $counter = $values['counter'];
820 $bundle_md5 = $values['bundle_md5'];
821
822 // See if a JS bundle exists that already has the same files in it, just in a
823 // different order.
824 // if ($filetype == 'js' && $run_alter) {
825 // advagg_find_existing_bundle($files, $bundle_md5);
826 // }
827
828 // Do not add the same bundle twice.
829 if (isset($used_md5[$bundle_md5])) {
830 continue;
831 }
832 $used_md5[$bundle_md5] = TRUE;
833
834 // If this is a brand new bundle then insert file/bundle info into database.
835 if (empty($counter) && $counter !== 0) {
836 $counter = 0;
837 advagg_insert_bundle_db($files, $filetype, $bundle_md5, FALSE);
838 }
839
840 // Prefix filename to prevent blocking by firewalls which reject files
841 // starting with "ad*".
842 $output[] = array(
843 'filename' => advagg_build_filename($filetype, $bundle_md5, $counter),
844 'files' => $files,
845 'bundle_md5' => $bundle_md5,
846 );
847 }
848 return $output;
849 }
850
851 /**
852 * Get a bundle from the cache & verify it is good.
853 *
854 * @param $cached_data_key
855 * cache key for the cache_advagg_bundle_reuse table.
856 * @param $debug_name
857 * Name to output in the array if debugging is enabled.
858 * @return
859 * data from the cache.
860 */
861 function advagg_cached_bundle_get($cached_data_key, $debug_name) {
862 global $_advagg;
863 $data = cache_get($cached_data_key, 'cache_advagg_bundle_reuse');
864 if (!empty($data->data)) {
865 $data = $data->data;
866 $bundle_contents = array();
867 $good = TRUE;
868 // Verify cached data is good.
869 foreach ($data as $filename => $extra) {
870 if (is_numeric($filename)) {
871 continue;
872 }
873 // Get md5 from aggregated filename.
874 $b_md5 = explode('/', $filename);
875 $b_md5 = explode('_', array_pop($b_md5));
876 $b_md5 = $b_md5[1];
877
878 // Lookup bundle and make sure it is valid.
879 if (!empty($b_md5)) {
880 list($b_filetype, $b_files) = advagg_get_files_in_bundle($b_md5);
881 $bundle_contents[$filename] = $b_files;
882 if (empty($b_files)) {
883 $good = FALSE;
884 }
885 }
886 }
887 // Debugging.
888 if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
889 $_advagg['debug'][$debug_name][] = array(
890 'key' => $cached_data_key,
891 'files' => $files,
892 'cache' => $data,
893 'bundle_contents' => $bundle_contents,
894 );
895 }
896 if ($good) {
897 return $data;
898 }
899 }
900 return FALSE;
901 }
902
903 /**
904 * Given a list of files, see if a bundle already exists containing all of those
905 * files. If in strict mode then the file count has to be the same.
906 *
907 * @param $files
908 * List of files in the proposed bundle.
909 * @param $bundle_md5
910 * Bundle's machine name.
911 */
912 function advagg_find_existing_bundle(&$files, &$bundle_md5) {
913 // Sort files for better cache hits.
914 $temp_files = $files;
915 sort($temp_files);
916 $cached_data_key = 'advagg_existing_' . md5(implode('', $temp_files));
917
918 // Try cache first; cache table is cache_advagg_bundle_reuse. string is debug name.
919 $cached_data = advagg_cached_bundle_get($cached_data_key, 'advagg_find_existing_bundle');
920 if (!empty($cached_data)) {
921 $files = $cached_data[0]['files'];
922 $bundle_md5 = $cached_data[0]['bundle_md5'];
923 return;
924 }
925
926 // Build union query.
927 $query = 'SELECT root.bundle_md5 FROM {advagg_bundles} AS root';
928 $joins = array();
929 $wheres = array();
930 $args = array();
931 $counter = 0;
932 foreach ($files as $filename) {
933 // Use alpha for table aliases; numerics do not work.
934 $key = strtr($counter, '01234567890', 'abcdefghij');
935
936 $joins[$key] = "\nINNER JOIN {advagg_bundles} AS $key USING(bundle_md5)\n";
937 if ($counter == 0) {
938 $wheres[$key] = "WHERE $key.filename_md5 = '%s'";
939 }
940 else {
941 $wheres[$key] = "AND $key.filename_md5 = '%s'";
942 }
943 $args[$key] = md5($filename);
944 $counter++;
945 }
946 $query .= implode("\n", $joins);
947 $query .= implode("\n", $wheres);
948 $query .= ' GROUP BY bundle_md5';
949
950 // Find matching bundles and select first good one.
951 $files_count = count($files);
952 $results = db_query($query, $args);
953 while ($new_md5 = db_result($results)) {
954 $count = db_result(db_query("SELECT count(*) FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $new_md5));
955 // Make sure bundle has the same number of files if using strict matching.
956 if (!empty($count) && $count == $files_count) {
957 $bundle_md5 = $new_md5;
958 $data = array(array('files' => $files, 'bundle_md5' => $bundle_md5));
959 cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY);
960 return;
961 }
962 }
963 }
964
965 /**
966 * Build the filename.
967 *
968 * @param $filetype
969 * css or js.
970 * @param $counter
971 * Counter value.
972 * @param $bundle_md5
973 * Bundle's machine name.
974 */
975 function advagg_build_filename($filetype, $bundle_md5, $counter) {
976 return $filetype . '_' . $bundle_md5 . '_' . $counter . '.' . $filetype;
977 }
978
979 /**
980 * Insert info into the advagg_files and advagg_bundles database.
981 *
982 * @param $files
983 * List of files in the proposed bundle.
984 * @param $filetype
985 * css or js.
986 * @param $bundle_md5
987 * Bundle's machine name.
988 * @param $root
989 * Is this a root bundle.
990 */
991 function advagg_insert_bundle_db($files, $filetype, $bundle_md5, $root) {
992 $lock_name = 'advagg_insert_bundle_db' . $bundle_md5;
993 if (!lock_acquire($lock_name)) {
994 lock_wait($lock_name);
995 return;
996 }
997
998 foreach ($files as $order => $filename) {
999 $filename_md5 = md5($filename);
1000
1001 // Insert file into the advagg_files table if it doesn't exist.
1002 $checksum = db_result(db_query("SELECT checksum FROM {advagg_files} WHERE filename_md5 = '%s'", $filename_md5));
1003 if (empty($checksum)) {
1004 $checksum = advagg_checksum($filename);
1005 db_query("INSERT INTO {advagg_files} (filename, filename_md5, checksum, filetype, filesize) VALUES ('%s', '%s', '%s', '%s', %d)", $filename, $filename_md5, $checksum, $filetype, @filesize($filename));
1006 }
1007
1008 // Create the entries in the advagg_bundles table.
1009 db_query("INSERT INTO {advagg_bundles} (bundle_md5, filename_md5, counter, porder, root) VALUES ('%s', '%s', '%d', '%d', '%d')", $bundle_md5, $filename_md5, 0, $order, $root, time());
1010 }
1011
1012 lock_release($lock_name);
1013 }
1014
1015 /**
1016 * Save a string to the specified destination. Verify that file size is not zero.
1017 *
1018 * @param $data
1019 * A string containing the contents of the file.
1020 * @param $dest
1021 * A string containing the destination location.
1022 * @return
1023 * Boolean indicating if the file save was successful.
1024 */
1025 function advagg_file_saver($data, $dest, $force, $type) {
1026 // Create the JS file.
1027 $file_save_data = 'file_save_data';
1028 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR);
1029 if (!empty($custom_path)) {
1030 $file_save_data = 'advagg_file_save_data';
1031 }
1032
1033 if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) {
1034 return FALSE;
1035 }
1036
1037 // Make sure filesize is not zero.
1038 advagg_clearstatcache(TRUE, $dest);
1039 if (@filesize($dest) == 0 && !empty($data)) {
1040 if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) {
1041 return FALSE;
1042 }
1043 advagg_clearstatcache(TRUE, $dest);
1044 if (@filesize($dest) == 0 && !empty($data)) {
1045 // Filename is bad, create a new one next time.
1046 file_delete($dest);
1047 return FALSE;
1048 }
1049 }
1050
1051 if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION) && extension_loaded('zlib')) {
1052 $gzip_dest = $dest . '.gz';
1053 advagg_clearstatcache(TRUE, $gzip_dest);
1054 if (!file_exists($gzip_dest) || $force) {
1055 $gzip_data = gzencode($data, 9, FORCE_GZIP);
1056 if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) {
1057 return FALSE;
1058 }
1059
1060 // Make sure filesize is not zero.
1061 advagg_clearstatcache(TRUE, $gzip_dest);
1062 if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) {
1063 if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) {
1064 return FALSE;
1065 }
1066 advagg_clearstatcache(TRUE, $gzip_dest);
1067 if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) {
1068 // Filename is bad, create a new one next time.
1069 file_delete($gzip_dest);
1070 return FALSE;
1071 }
1072 }
1073 }
1074 }
1075
1076 // Make sure .htaccess file exists.
1077 advagg_htaccess_check_generate($dest);
1078
1079 cache_set($dest, time(), 'cache_advagg', CACHE_PERMANENT);
1080 return TRUE;
1081 }
1082
1083
1084
1085 /**
1086 * ***MODIFIED CORE FUNCTIONS BELOW***
1087 *
1088 * @see file_save_data()
1089 * @see file_move()
1090 * @see file_copy()
1091 */
1092
1093 /**
1094 * Save a string to the specified destination.
1095 *
1096 * @see file_save_data()
1097 *
1098 * @param $data A string containing the contents of the file.
1099 * @param $dest A string containing the destination location.
1100 * @param $replace Replace behavior when the destination file already exists.
1101 * - FILE_EXISTS_REPLACE - Replace the existing file
1102 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
1103 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1104 *
1105 * @return A string containing the resulting filename or 0 on error
1106 */
1107 function advagg_file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
1108 $temp = file_directory_temp();
1109 // On Windows, tempnam() requires an absolute path, so we use realpath().
1110 $file = tempnam(realpath($temp), 'file');
1111 if (!$fp = fopen($file, 'wb')) {
1112 drupal_set_message(t('The file could not be created.'), 'error');
1113 return 0;
1114 }
1115 fwrite($fp, $data);
1116 fclose($fp);
1117
1118 if (!advagg_file_move($file, $dest, $replace)) {
1119 return 0;
1120 }
1121
1122 return $file;
1123 }
1124
1125 /**
1126 * Moves a file to a new location.
1127 *
1128 * @see file_move()
1129 *
1130 * - Checks if $source and $dest are valid and readable/writable.
1131 * - Performs a file move if $source is not equal to $dest.
1132 * - If file already exists in $dest either the call will error out, replace the
1133 * file or rename the file based on the $replace parameter.
1134 *
1135 * @param $source
1136 * Either a string specifying the file location of the original file or an
1137 * object containing a 'filepath' property. This parameter is passed by
1138 * reference and will contain the resulting destination filename in case of
1139 * success.
1140 * @param $dest
1141 * A string containing the directory $source should be copied to. If this
1142 * value is omitted, Drupal's 'files' directory will be used.
1143 * @param $replace
1144 * Replace behavior when the destination file already exists.
1145 * - FILE_EXISTS_REPLACE: Replace the existing file.
1146 * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
1147 * unique.
1148 * - FILE_EXISTS_ERROR: Do nothing and return FALSE.
1149 * @return
1150 * TRUE for success, FALSE for failure.
1151 */
1152 function advagg_file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
1153 $path_original = is_object($source) ? $source->filepath : $source;
1154
1155 if (advagg_file_copy($source, $dest, $replace)) {
1156 $path_current = is_object($source) ? $source->filepath : $source;
1157
1158 if ($path_original == $path_current || file_delete($path_original)) {
1159 return 1;
1160 }
1161 drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error');
1162 }
1163 return 0;
1164 }
1165
1166 /**
1167 * Copies a file to a new location.
1168 *
1169 * @see file_copy()
1170 *
1171 * This is a powerful function that in many ways performs like an advanced
1172 * version of copy().
1173 * - Checks if $source and $dest are valid and readable/writable.
1174 * - Performs a file copy if $source is not equal to $dest.
1175 * - If file already exists in $dest either the call will error out, replace the
1176 * file or rename the file based on the $replace parameter.
1177 *
1178 * @param $source
1179 * Either a string specifying the file location of the original file or an
1180 * object containing a 'filepath' property. This parameter is passed by
1181 * reference and will contain the resulting destination filename in case of
1182 * success.
1183 * @param $dest
1184 * A string containing the directory $source should be copied to. If this
1185 * value is omitted, Drupal's 'files' directory will be used.
1186 * @param $replace
1187 * Replace behavior when the destination file already exists.
1188 * - FILE_EXISTS_REPLACE: Replace the existing file.
1189 * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
1190 * unique.
1191 * - FILE_EXISTS_ERROR: Do nothing and return FALSE.
1192 * @return
1193 * TRUE for success, FALSE for failure.
1194 */
1195 function advagg_file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
1196 $directory = dirname($dest);
1197
1198 // Process a file upload object.
1199 if (is_object($source)) {
1200 $file = $source;
1201 $source = $file->filepath;
1202 if (!$basename) {
1203 $basename = $file->filename;
1204 }
1205 }
1206
1207 $source = realpath($source);
1208 advagg_clearstatcache(TRUE, $source);
1209 if (!file_exists($source)) {
1210 drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error');
1211 return 0;
1212 }
1213
1214 // If the destination file is not specified then use the filename of the source file.
1215 $basename = basename($dest);
1216 $basename = $basename ? $basename : basename($source);
1217 $dest = $directory . '/' . $basename;
1218
1219 // Make sure source and destination filenames are not the same, makes no sense
1220 // to copy it if they are. In fact copying the file will most likely result in
1221 // a 0 byte file. Which is bad. Real bad.
1222 if ($source != realpath($dest)) {
1223 if (!$dest = file_destination($dest, $replace)) {
1224 drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $source)), 'error');
1225 return FALSE;
1226 }
1227
1228 if (!@copy($source, $dest)) {
1229 drupal_set_message(t('The selected file %file could not be copied. ' . $dest, array('%file' => $source)), 'error');
1230 return 0;
1231 }
1232
1233 // Give everyone read access so that FTP'd users or
1234 // non-webserver users can see/read these files,
1235 // and give group write permissions so group members
1236 // can alter files uploaded by the webserver.
1237 @chmod($dest, 0664);
1238 }
1239
1240 if (isset($file) && is_object($file)) {
1241 $file->filename = $basename;
1242 $file->filepath = $dest;
1243 $source = $file;
1244 }
1245 else {
1246 $source = $dest;
1247 }
1248
1249 return 1; // Everything went ok.
1250 }
1251
1252 /**
1253 * Generate a checksum for a given filename.
1254 *
1255 * @param $filename
1256 * filename
1257 * @return
1258 * Checksum value.
1259 */
1260 function advagg_checksum($filename) {
1261 advagg_clearstatcache(TRUE, $filename);
1262 if (file_exists($filename)) {
1263 $mode = variable_get('advagg_checksum_mode', ADVAGG_CHECKSUM_MODE);
1264 if ($mode == 'mtime') {
1265 $checksum = @filemtime($filename);
1266 if ($checksum === FALSE) {
1267 touch($filename);
1268 advagg_clearstatcache(TRUE, $filename);
1269 $checksum = @filemtime($filename);
1270 // Use md5 as a last option.
1271 if ($checksum === FALSE) {
1272 $checksum = md5(file_get_contents($filename));
1273 }
1274 }
1275 }
1276 elseif ($mode = 'md5') {
1277 $checksum = md5(file_get_contents($filename));
1278 }
1279 }
1280 else {
1281 $checksum = '-1';
1282 }
1283 return $checksum;
1284 }
1285
1286 /**
1287 * See if this bundle has been built.
1288 *
1289 * @param $filepath
1290 * filename
1291 * @return
1292 * Boolean indicating if the bundle already exists.
1293 */
1294 function advagg_bundle_built($filepath) {
1295 // Don't use the cache if not selected.
1296 if (!variable_get('advagg_bundle_built_mode', ADVAGG_BUNDLE_BUILT_MODE)) {
1297 advagg_clearstatcache(TRUE, $filepath);
1298 return file_exists($filepath);
1299 }
1300
1301 $data = advagg_get_bundle_from_filename(basename($filepath));
1302 if (is_array($data)) {
1303 list($type, $md5, $counter) = $data;
1304 }
1305 else {
1306 return FALSE;
1307 }
1308
1309 $data = cache_get($filepath, 'cache_advagg');
1310 if (isset($data->data)) {
1311 // Refresh timestamp if older then 12 hours.
1312 if (time() - $data->data > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) {
1313 cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT);
1314 db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5);
1315 }
1316 return TRUE;
1317 }
1318
1319 // If not in cache check disk.
1320 advagg_clearstatcache(TRUE, $filepath);
1321 if (file_exists($filepath)) {
1322 if (@filesize($filepath) == 0) {
1323 return FALSE;
1324 }
1325 }
1326 else {
1327 return FALSE;
1328 }
1329 // File existed on disk; place in cache.
1330 cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT);
1331 db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5);
1332 return TRUE;
1333 }
1334
1335 function advagg_get_bundle_from_filename($filename) {
1336 // Verify requested filename has the correct pattern.
1337 if (!preg_match('/^(j|cs)s_[0-9a-f]{32}_\d+\.(j|cs)s$/', $filename)) {
1338 return t('Wrong Pattern.');
1339 }
1340
1341 // Get type
1342 $type = substr($filename, 0, strpos($filename, '_'));
1343
1344 // Get extension
1345 $ext = substr($filename, strpos($filename, '.', 37)+1);
1346
1347 // Make sure extension is the same as the type.
1348 if ($ext != $type) {
1349 return t('Type does not match extension.');
1350 }
1351
1352 // Extract info from wanted filename.
1353 if ($type == 'css') {
1354 $md5 = substr($filename, 4, 32);
1355 $counter = substr($filename, 37, strpos($filename, '.', 38)-37);
1356 }
1357 elseif ($type == 'js') {
1358 $md5 = substr($filename, 3, 32);
1359 $counter = substr($filename, 36, strpos($filename, '.', 37)-36);
1360 }
1361 else {
1362 return t('Wrong file type.');
1363 }
1364
1365 return array($type, $md5, $counter);
1366 }
1367
1368 /**
1369 * Implementation of hook_flush_caches().
1370 */
1371 function advagg_flush_caches() {
1372 // Try to allocate enough time to flush the cache
1373 if (function_exists('set_time_limit')) {
1374 @set_time_limit(240);
1375 }
1376
1377 global $_advagg;
1378 // Only one advagg cache flusher can run at a time.
1379 if (!lock_acquire('advagg_flush_caches')) {
1380 return;
1381 }
1382
1383 // Only run code below if the advagg db tables exist.
1384 if (!db_table_exists('advagg_files')) {
1385 return array('cache_advagg_bundle_reuse');
1386 }
1387
1388 // Find files that have changed.
1389 $needs_refreshing = array();
1390 $results = db_query("SELECT * FROM {advagg_files}");
1391 while ($row = db_fetch_array($results)) {
1392 $checksum = advagg_checksum($row['filename']);
1393 // Let other modules see if the bundles needs to be rebuilt.
1394 // hook_advagg_files_table
1395 // Return TRUE in order to increment the counter.
1396 $hook_results = module_invoke_all('advagg_files_table', $row, $checksum);
1397
1398 // Check each return value; see if an update is needed.
1399 $update = FALSE;
1400 if (!empty($hook_results)) {
1401 foreach ($hook_results as $update) {
1402 if ($update === TRUE) {
1403 break;
1404 }
1405 }
1406 }
1407
1408 // Increment the counter if needed and mark file for bundle refreshment.
1409 if ($checksum != $row['checksum'] || $update == TRUE) {
1410 $needs_refreshing[$row['filename_md5']] = $row['filename'];
1411 // Update checksum; increment counter.
1412 db_query("UPDATE {advagg_files} SET checksum = '%s', counter = counter + 1 WHERE filename_md5 = '%s'", $checksum, $row['filename_md5']);
1413 }
1414 }
1415
1416 // Get the bundles.
1417 $bundles = array();
1418 foreach ($needs_refreshing as $filename_md5 => $filename) {
1419 $results = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s'", $filename_md5);
1420 while ($row = db_fetch_array($results)) {
1421 $bundles[$row['bundle_md5']] = $row['bundle_md5'];
1422 }
1423 }
1424
1425 foreach ($bundles as $bundle_md5) {
1426 // Increment Counter
1427 db_query("UPDATE {advagg_bundles} SET counter = counter + 1, timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5);
1428
1429 if (variable_get('advagg_rebuild_on_flush', ADVAGG_REBUILD_ON_FLUSH)) {
1430 // Rebuild bundles on shutdown in the background. This is needed so that
1431 // the cache_advagg_bundle_reuse table has been cleared.
1432 register_shutdown_function('advagg_rebuild_bundle', $bundle_md5, '', TRUE);
1433 }
1434 }
1435 $_advagg['bundles'] = $bundles;
1436 $_advagg['files'] = $needs_refreshing;
1437
1438 // Garbage collection
1439 list($css_path, $js_path) = advagg_get_root_files_dir();
1440 file_scan_directory($css_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE);
1441 file_scan_directory($js_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE);
1442
1443 lock_release('advagg_flush_caches');
1444 return array('cache_advagg_bundle_reuse');
1445 }
1446
1447 /**
1448 * Rebuild a bundle.
1449 *
1450 * @param $bundle_md5
1451 * Bundle's machine name.
1452 * @param $counter
1453 * Counter value.
1454 * @param $force
1455 * Rebuild even if file already exists.
1456 */
1457 function advagg_rebuild_bundle($bundle_md5, $counter = '', $force = FALSE) {
1458 global $conf, $_advagg;
1459 list($filetype, $files) = advagg_get_files_in_bundle($bundle_md5);
1460
1461 $conf['advagg_async_generation'] = FALSE;
1462 $good = advagg_css_js_file_builder($filetype, $files, '', $counter, $force, $bundle_md5);
1463 if (!$good) {
1464 watchdog('advagg', 'This bundle could not be generated correctly. Bundle MD5: %md5', array('%md5' => $bundle_md5));
1465 }
1466 else {
1467 $_advagg['rebuilt'][] = $bundle_md5;
1468 }
1469 return $good;
1470 }
1471
1472 /**
1473 * Get list of files and the filetype given a bundle md5.
1474 *
1475 * @param $bundle_md5
1476 * Bundle's machine name.
1477 * @return
1478 * array ($filetype, $files)
1479 */
1480 function advagg_get_files_in_bundle($bundle_md5) {
1481 $files = array();
1482 $filetype = NULL;
1483 $results = db_query("SELECT filename, filetype FROM {advagg_files} AS af INNER JOIN {advagg_bundles} AS ab USING ( filename_md5 ) WHERE bundle_md5 = '%s' ORDER BY porder ASC", $bundle_md5);
1484 while ($row = db_fetch_array($results)) {
1485 $files[] = $row['filename'];
1486 $filetype = $row['filetype'];
1487 }
1488
1489 return array($filetype, $files);
1490 }
1491
1492 /**
1493 * Callback to delete files modified more than a set time ago.
1494 *
1495 * @param $filename
1496 * name of a file to check how old it is.
1497 */
1498 function advagg_delete_file_if_stale($filename) {
1499 // Do not process .gz files
1500 if (strpos($filename, '.gz') !== FALSE) {
1501 return;
1502 }
1503 $now = time();
1504 $file_last_mod = variable_get('advagg_stale_file_threshold', ADVAGG_STALE_FILE_THRESHOLD);
1505 $file_last_used = variable_get('advagg_stale_file_last_used_threshold', ADVAGG_STALE_FILE_LAST_USED_THRESHOLD);
1506
1507 // Default stale file threshold is 30 days.
1508 advagg_clearstatcache(TRUE, $filename);
1509 if ($now - filemtime($filename) <= $file_last_mod) {
1510 return;
1511 }
1512
1513 // Check to see if this file is still in use.
1514 $data = cache_get($filename, 'cache_advagg');
1515 if (!empty($data->data)) {
1516 advagg_clearstatcache(TRUE, $filename);
1517 $file_last_a = @fileatime($filename);
1518 $file_last_agz = @fileatime($filename . '.gz');
1519 $file_last_a = max($file_last_a, $file_last_agz);
1520 if ($now - $data->data > $file_last_used && $now - $file_last_a > $file_last_used) {
1521 // Delete file if it hasn't been used in the last 15 days.
1522 file_delete($filename);
1523 file_delete($filename . '.gz');
1524 }
1525 else {
1526 // Touch file so we don't check again for another 30 days
1527 touch($filename);
1528 }
1529 }
1530 else {
1531 // Delete file if it is not in the cache.
1532 file_delete($filename);
1533 file_delete($filename . '.gz');
1534 }
1535 }
1536
1537 /**
1538 * Get data about a file.
1539 *
1540 * @param $filename_md5
1541 * md5 of filename.
1542 * @return
1543 * data array from database.
1544 */
1545 function advagg_get_file_data($filename_md5) {
1546 $data = cache_get($filename_md5, 'cache_advagg_files_data');
1547 if (empty($data->data)) {
1548 return FALSE;
1549 }
1550 return $data->data;
1551 }
1552
1553 /**
1554 * Set data about a file.
1555 *
1556 * @param $filename_md5
1557 * md5 of filename.
1558 * @param $data
1559 * data to store.
1560 */
1561 function advagg_set_file_data($filename_md5, $data) {
1562 cache_set($filename_md5, $data, 'cache_advagg_files_data', CACHE_PERMANENT);
1563 }
1564
1565 /**
1566 * Given path output uri to that file
1567 *
1568 * @param $filename_md5
1569 * md5 of filename.
1570 * @param $data
1571 * data to store.
1572 */
1573 function advagg_build_uri($path) {
1574 $original_path = $path;
1575 // CDN Support.
1576 if (module_exists('cdn')) {
1577 $status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
1578 if ($status == CDN_ENABLED || ($status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING))) {
1579 // Alter URL when the file_create_url() patch is not there.
1580 if (variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE, FALSE)) {
1581 cdn_file_url_alter($path);
1582 }
1583 // Use the patched version of file_create_url().
1584 else {
1585 $path = file_create_url($path);
1586 }
1587 if (strcmp($original_path, $path) != 0) {
1588 return $path;
1589 }
1590 }
1591 }
1592 return base_path() . $path;
1593 }
1594
1595 /**
1596 * ***MODIFIED CORE FUNCTIONS BELOW***
1597 *
1598 * @see drupal_get_css()
1599 * @see drupal_build_css_cache()
1600 * @see drupal_get_js()
1601 * @see drupal_build_js_cache()
1602 */
1603
1604 /**
1605 * Returns an array of values needed for aggregation
1606 *
1607 * @param $noagg
1608 * (optional) Bool indicating that aggregation should be disabled if TRUE.
1609 * @return
1610 * array of values to be imported via list() function.
1611 */
1612 function advagg_process_css_js_prep($noagg = FALSE) {
1613 $preprocess = (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update');
1614 if ($noagg || (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation'))) {
1615 $preprocess = FALSE;
1616 }
1617
1618 $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
1619 if (!$public_downloads) {
1620 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR);
1621 if (!empty($custom_path)) {
1622 $public_downloads = TRUE;
1623 }
1624 }
1625
1626 // A dummy query-string is added to filenames, to gain control over
1627 // browser-caching. The string changes on every update or full cache
1628 // flush, forcing browsers to load a new copy of the files, as the
1629 // URL changed.
1630 $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
1631
1632 return array($preprocess, $public_downloads, $query_string);
1633 }
1634
1635
1636 /**
1637 * Returns a themed representation of all stylesheets that should be attached to
1638 * the page.
1639 *
1640 * @see drupal_get_css()
1641 *
1642 * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
1643 * This ensures proper cascading of styles so themes can easily override
1644 * module styles through CSS selectors.
1645 *
1646 * Themes may replace module-defined CSS files by adding a stylesheet with the
1647 * same filename. For example, themes/garland/system-menus.css would replace
1648 * modules/system/system-menus.css. This allows themes to override complete
1649 * CSS files, rather than specific selectors, when necessary.
1650 *
1651 * If the original CSS file is being overridden by a theme, the theme is
1652 * responsible for supplying an accompanying RTL CSS file to replace the
1653 * module's.
1654 *
1655 * @param $css
1656 * (optional) An array of CSS files. If no array is provided, the default
1657 * stylesheets array is used instead.
1658 * @param $noagg
1659 * (optional) Bool indicating that aggregation should be disabled if TRUE.
1660 * @return
1661 * A string of XHTML CSS tags.
1662 */
1663 function advagg_process_css($css = NULL, $noagg = FALSE) {
1664 global $conf;
1665 $original_css = $css;
1666 if (!isset($css)) {
1667 $css = drupal_add_css();
1668 }
1669 if (empty($css)) {
1670 return FALSE;
1671 }
1672
1673 // Get useful info.
1674 list($preprocess_css, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg);
1675
1676 // Invoke hook_advagg_css_pre_alter() to give installed modules a chance to
1677 // modify the data in the $javascript array if necessary.
1678 drupal_alter('advagg_css_pre', $css, $preprocess_css, $public_downloads);
1679
1680 // Set variables.
1681 $external_no_preprocess = array();
1682 $module_no_preprocess = array();
1683 $output_no_preprocess = array();
1684 $output_preprocess = array();
1685 $theme_no_preprocess = array();
1686 $inline_no_preprocess = array();
1687 $files_included = array();
1688 $files_aggregates_included = array();
1689 $inline_included = array();
1690
1691 // Process input.
1692 foreach ($css as $media => $types) {
1693 // Setup some variables
1694 $files_included[$media] = array();
1695 $files_aggregates_included[$media] = array();
1696 $inline_included[$media] = array();
1697
1698 // If CSS preprocessing is off, we still need to output the styles.
1699 // Additionally, go through any remaining styles if CSS preprocessing is on
1700 // and output the non-cached ones.
1701 foreach ($types as $type => $files) {
1702 if ($type == 'module') {
1703 // Setup theme overrides for module styles.
1704 $theme_styles = array();
1705 foreach (array_keys($css[$media]['theme']) as $theme_style) {
1706 $theme_styles[] = basename($theme_style);
1707 }
1708 }
1709 foreach ($types[$type] as $file => $preprocess) {
1710 // If the theme supplies its own style using the name of the module
1711 // style, skip its inclusion. This includes any RTL styles associated
1712 // with its main LTR counterpart.
1713 if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) {
1714 // Unset the file to prevent its inclusion when CSS aggregation is enabled.
1715 unset($types[$type][$file]);
1716 continue;
1717 }
1718 // If a CSS file is not to be preprocessed and it's an external
1719 // CSS file, it needs to *always* appear at the *very top*,
1720 // regardless of whether preprocessing is on or off.
1721 if ($type == 'external') {
1722 $external_no_preprocess[] = array(
1723 'media' => $media,
1724 'href' => $file,
1725 'prefix' => '',
1726 'suffix' => '',
1727 );
1728 $files_included[$media][$file] = TRUE;
1729 // Unset the file to prevent its inclusion.
1730 unset($types[$type][$file]);
1731 continue;
1732 }
1733 // If a CSS file is not to be preprocessed and it's an inline CSS blob
1734 // it needs to *always* appear at the *very bottom*.
1735 if ($type == 'inline') {
1736 if (is_array($preprocess)) {
1737 foreach ($preprocess as $suffix => $blob) {
1738 $blob = advagg_drupal_load_stylesheet_content($blob, $preprocess);
1739 // Invoke hook_advagg_css_inline_alter() to give installed modules
1740 // a chance to modify the contents of $blob if necessary.
1741 drupal_alter('advagg_css_inline', $blob);
1742
1743 $inline_no_preprocess[] = array(
1744 'media' => $media,
1745 'data' => $blob,
1746 'prefix' => $file,
1747 'suffix' => $suffix,
1748 );
1749 $inline_included[$media][] = $blob;
1750 }
1751 }
1752 else {
1753 $file = advagg_drupal_load_stylesheet_content($file, $preprocess);
1754 // Invoke hook_advagg_css_inline_alter() to give installed modules a
1755 // chance to modify the contents of $file if necessary.
1756 drupal_alter('advagg_css_inline', $file);
1757
1758 $inline_no_preprocess[] = array(
1759 'media' => $media,
1760 'data' => $file,
1761 'prefix' => '',
1762 'suffix' => '',
1763 );
1764 $inline_included[$media][] = $file;
1765 }
1766 // Unset to prevent its inclusion.
1767 unset($types[$type][$file]);
1768 continue;
1769 }
1770
1771 // Only include the stylesheet if it exists.
1772 if (advagg_file_exists($file)) {
1773 if (!$preprocess || !($public_downloads && $preprocess_css)) {
1774 // Create URI for file.
1775 $file_uri = advagg_build_uri($file) . $query_string;
1776 $files_included[$media][$file] = $preprocess;
1777 // If a CSS file is not to be preprocessed and it's a module CSS
1778 // file, it needs to *always* appear at the *top*, regardless of
1779 // whether preprocessing is on or off.
1780 if (!$preprocess && $type == 'module') {
1781 $module_no_preprocess[] = array(
1782 'media' => $media,
1783 'href' => $file_uri,
1784 'prefix' => '',
1785 'suffix' => '',
1786 );
1787 }
1788 // If a CSS file is not to be preprocessed and it's a theme CSS
1789 // file, it needs to *always* appear at the *bottom*, regardless of
1790 // whether preprocessing is on or off.
1791 elseif (!$preprocess && $type == 'theme') {
1792 $theme_no_preprocess[] = array(
1793 'media' => $media,
1794 'href' => $file_uri,
1795 'prefix' => '',
1796 'suffix' => '',
1797 );
1798 }
1799
1800 else {
1801 $output_no_preprocess[] = array(
1802 'media' => $media,
1803 'href' => $file_uri,
1804 'prefix' => '',
1805 'suffix' => '',
1806 );
1807 }
1808 }
1809 }
1810 }
1811 }
1812
1813 if ($public_downloads && $preprocess_css) {
1814 $files_aggregates_included[$media] = $files_included[$media];
1815 $files = array();
1816 foreach ($types as $type) {
1817 foreach ($type as $file => $cache) {
1818 if ($cache) {
1819 $files[] = $file;
1820 $files_included[$media][$file] = TRUE;
1821 unset($files_aggregates_included[$file]);
1822 }
1823 }
1824 }
1825 $preprocess_files = advagg_css_js_file_builder('css', $files, $query_string);
1826 $good = TRUE;
1827
1828 foreach ($preprocess_files as $preprocess_file => $extra) {
1829 // Empty aggregate, skip
1830 if (empty($preprocess_file)) {
1831 continue;
1832 }
1833
1834 if ($extra !== FALSE && is_array($extra)) {
1835 $prefix = $extra['prefix'];
1836 $suffix = $extra['suffix'];
1837 $output_preprocess[] = array(
1838 'media' => $media,
1839 'href' => advagg_build_uri($preprocess_file),
1840 'prefix' => $prefix,
1841 'suffix' => $suffix,
1842 );
1843 $files_aggregates_included[$media][$preprocess_file] = $extra;
1844 }
1845 else {
1846 $good = FALSE;
1847 break;
1848 }
1849 }
1850 if (!$good) {
1851 // Redo with aggregation turned off and return the new value.
1852 watchdog('advagg', 'CSS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR);
1853 $data = advagg_process_css($original_css, TRUE);
1854 return $data;
1855 }
1856 }
1857 }
1858
1859 // Default function called: advagg_unlimited_css_builder
1860 $function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION);
1861 return $function($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included);
1862 }
1863
1864 /**
1865 * Logic to figure out what kind of css tags to use.
1866 *
1867 * @param $external_no_preprocess
1868 * array of css files ($media, $href)
1869 * @param $module_no_preprocess
1870 * array of css files ($media, $href)
1871 * @param $output_no_preprocess
1872 * array of css files ($media, $href)
1873 * @param $output_preprocess
1874 * array of css files ($media, $href, $prefix, $suffix)
1875 * @param $theme_no_preprocess
1876 * array of css files ($media, $href)
1877 * @param $inline_no_preprocess
1878 * array of css data to inline ($media, $data)
1879 * @param $inline_included
1880 * array of inline css included. $a[$media][] = $datablob;
1881 * @param $files_included
1882 * array of css files included. $a[$media][] = $filename
1883 * @param $files_aggregates_included
1884 * array of css files & aggregates included. $a[$media][] = $filename
1885 * @return
1886 * html for loading the css. html for the head.
1887 */
1888 function advagg_unlimited_css_builder($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $files_included, $files_aggregates_included, $inline_included) {
1889 global $user;
1890 $styles = '';
1891 $files = array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess);
1892
1893 // Select method for css html output
1894 if (count($files) < variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD)) {
1895 advagg_unlimited_css_traditional($files, $styles);
1896 }
1897 elseif (variable_get('advagg_css_logged_in_ie_detect', ADVAGG_CSS_LOGGED_IN_IE_DETECT) && $user->uid != 0) {
1898 // Detect IE browsers here
1899 $is_ie = FALSE;
1900 if (isset($_SERVER['HTTP_USER_AGENT'])) {
1901 // Strings for testing found via
1902 // http://chrisschuld.com/projects/browser-php-detecting-a-users-browser-from-php/
1903 // Test for v1 - v1.5 IE
1904 // Test for versions > 1.5
1905 // Test for Pocket IE
1906 if ( stristr($_SERVER['HTTP_USER_AGENT'], 'microsoft internet explorer')
1907 || stristr($_SERVER['HTTP_USER_AGENT'], 'msie')
1908 || stristr($_SERVER['HTTP_USER_AGENT'], 'mspie')
1909 ) {
1910 $is_ie = TRUE;
1911 }
1912 }
1913 // Play Safe and treat as IE if user agent is not set
1914 else {
1915 $is_ie = TRUE;
1916 }
1917
1918 if ($is_ie) {
1919 advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles);
1920 advagg_unlimited_css_import($output_preprocess, $styles);
1921 advagg_unlimited_css_import($theme_no_preprocess, $styles);
1922 advagg_unlimited_css_traditional($inline_no_preprocess, $styles);
1923 }
1924 else {
1925 advagg_unlimited_css_traditional($files, $styles);
1926 }
1927 }
1928 else {
1929 advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles);
1930 advagg_unlimited_css_import($output_preprocess, $styles);
1931 advagg_unlimited_css_import($theme_no_preprocess, $styles);
1932 advagg_unlimited_css_traditional($inline_no_preprocess, $styles);
1933 }
1934
1935 return $styles;
1936 }
1937
1938 /**
1939 * Use link tags for CSS
1940 *
1941 * @param $files
1942 * array of css files ($media, $href, $prefix, $suffix)
1943 * @param &$styles
1944 * html string
1945 */
1946 function advagg_unlimited_css_traditional($files, &$styles) {
1947 $last_prefix = '';
1948 $last_suffix = '';
1949 foreach ($files as $css_file) {
1950 $media = $css_file['media'];
1951 $prefix = empty($css_file['prefix']) ? '' : $css_file['prefix'] . "\n";
1952 $suffix = empty($css_file['suffix']) ? '' : $css_file['suffix'];
1953
1954 // Group prefixes and suffixes.
1955 if (isset($css_file['href'])) {
1956 $href = $css_file['href'];
1957 if ($prefix != $last_prefix) {
1958 $styles .= $last_suffix . "\n" . $prefix . '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. $href . '" />' . "\n";
1959 }
1960 else {
1961 $styles .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. $href . '" />' . "\n";
1962 }
1963 }
1964 else {
1965 $data = $css_file['data'];
1966 if ($prefix != $last_prefix) {
1967 $styles .= $last_suffix . "\n" . $prefix . '<style type="text/css" media="'. $media .'">' . "\n" . $data . "\n" . '</style>' . "\n";
1968 }
1969 else {
1970 $styles .= '<style type="text/css" media="'. $media .'">' . "\n" . $data . "\n" . '</style>' . "\n";
1971 }
1972 }
1973 $last_prefix = $prefix;
1974 $last_suffix = $suffix;
1975 }
1976 $styles .= $last_suffix . "\n";
1977 }
1978
1979 /**
1980 * Use import tags for CSS
1981 *
1982 * @param $files
1983 * array of css files ($media, $href)
1984 * @param &$styles
1985 * html string
1986 */
1987 function advagg_unlimited_css_import($files, &$styles) {
1988 $counter = 0;
1989 $media = NULL;
1990 $import = '';
1991 foreach ($files as $css_file) {
1992 $media_new = $css_file['media'];
1993 $href = $css_file['href'];
1994 if ($media_new != $media || $counter > variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD)) {
1995 if ($media && !empty($import)) {
1996 $styles .= "\n" . '<style type="text/css" media="'. $media .'">' . "\n". $import .'</style>';
1997 $import = '';
1998 }
1999 $counter = 0;
2000 $media = $media_new;
2001 }
2002 $import .= '@import "'. $href .'";'."\n";
2003 $counter++;
2004 }
2005 if ($media && !empty($import)) {
2006 $styles .= "\n".'<style type="text/css" media="'. $media .'">'."\n". $import .'</style>';
2007 }
2008 }
2009
2010 /**
2011 * Returns a themed presentation of all JavaScript code for the current page.
2012 *
2013 * @see drupal_get_js()
2014 *
2015 * References to JavaScript files are placed in a certain order: first, all
2016 * 'core' files, then all 'module' and finally all 'theme' JavaScript files
2017 * are added to the page. Then, all settings are output, followed by 'inline'
2018 * JavaScript code. If running update.php, all preprocessing is disabled.
2019 *
2020 * @param $js_code
2021 * An array with all JavaScript code. Key it the region
2022 * @param $noagg
2023 * (optional) Bool indicating that aggregation should be disabled if TRUE.
2024 * @return
2025 * All JavaScript code segments and includes for the scope as HTML tags.
2026 */
2027 function advagg_process_js($master_set, $noagg = FALSE) {
2028 global $conf;
2029 if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) {
2030 locale_update_js_files();
2031 }
2032
2033 // Get useful info.
2034 list($preprocess_js, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg);
2035
2036 $output = array();
2037 foreach ($master_set as $scope => $javascript) {
2038 if ($scope != 'header' && $scope != 'footer' && empty($javascript)) {
2039 continue;
2040 }
2041
2042 // Invoke hook_advagg_js_pre_alter() to give installed modules a chance to
2043 // modify the data in the $javascript array if necessary.
2044 drupal_alter('advagg_js_pre', $javascript, $preprocess_js, $public_downloads, $scope);
2045 $master_set[$scope] = $javascript;
2046 }
2047
2048 // Invoke hook_advagg_js_header_footer_alter() to give installed modules a chance to
2049 // modify the data in the header and footer JS if necessary.
2050 drupal_alter('advagg_js_header_footer', $master_set, $preprocess_js, $public_downloads);
2051
2052 foreach ($master_set as $scope => $javascript) {
2053 if (empty($javascript)) {
2054 continue;
2055 }
2056
2057 // Set variables.
2058 $setting_no_preprocess = array();
2059 $inline_no_preprocess = array();
2060 $external_no_preprocess = array();
2061 $output_no_preprocess = array('core' => array(), 'module' => array(), 'theme' => array());
2062 $output_preprocess = array();
2063 $preprocess_list = array();
2064 $js_settings_array = array();
2065 $inline_included = array();
2066 $files_included = array();
2067 $files_aggregates_included = array();
2068
2069 // Process input.
2070 foreach ($javascript as $type => $data) {
2071 if (empty($data)) {
2072 continue;
2073 }
2074
2075 switch ($type) {
2076 case 'setting':
2077 $data = call_user_func_array('array_merge_recursive', $data);
2078 $js_settings_array[] = $data;
2079 $js_settings = advagg_drupal_to_js($data);
2080 $js_settings = preg_replace(array('/"DRUPAL_JS_RAW\:/', '/\:DRUPAL_JS_RAW"/'), array('', ''), $js_settings);
2081 $setting_no_preprocess[] = 'jQuery.extend(Drupal.settings, ' . $js_settings . ");";
2082 break;
2083
2084 case 'inline':
2085 foreach ($data as $info) {
2086 // Invoke hook_advagg_js_inline_alter() to give installed modules a
2087 // chance to modify the contents of $info['code'] if necessary.
2088 drupal_alter('advagg_js_inline', $info['code']);
2089 $inline_no_preprocess[] = array($info['code'], $info['defer']);
2090 $inline_included[] = $info['code'];
2091 }
2092 break;
2093
2094 case 'external':
2095 foreach ($data as $path => $info) {
2096 $external_no_preprocess[] = array($path, $info['defer']);
2097 $files_included[$path] = TRUE;
2098 }
2099 break;
2100
2101 default:
2102 // If JS preprocessing is off, we still need to output the scripts.
2103 // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
2104 foreach ($data as $path => $info) {
2105 if (!$info['preprocess'] || !$public_downloads || !$preprocess_js) {
2106 $output_no_preprocess[$type][] = array(advagg_build_uri($path) . ($info['cache'] ? $query_string : '?'. time()), $info['defer']);
2107 $files_included[$path] = $info['preprocess'];
2108 }
2109 else {
2110 $preprocess_list[$path] = $info;
2111 }
2112 }
2113 }
2114 }
2115
2116 // Aggregate any remaining JS files that haven't already been output.
2117 if ($public_downloads && $preprocess_js && count($preprocess_list) > 0) {
2118 $files_aggregates_included = $files_included;
2119 $files = array();
2120 foreach ($preprocess_list as $path => $info) {
2121 if ($info['preprocess']) {
2122 $files[] = $path;
2123 $files_included[$path] = TRUE;
2124 }
2125 }
2126 $preprocess_files = advagg_css_js_file_builder('js', $files, $query_string);
2127 $good = TRUE;
2128 foreach ($preprocess_files as $preprocess_file => $extra) {
2129 // Empty aggregate, skip
2130 if (empty($preprocess_file)) {
2131 continue;
2132 }
2133
2134 if ($extra !== FALSE && is_array($extra)) {
2135 $prefix = $extra['prefix'];
2136 $suffix = $extra['suffix'];
2137 $output_preprocess[] = array(advagg_build_uri($preprocess_file), $prefix, $suffix);
2138 $files_aggregates_included[$preprocess_file] = $extra;
2139 }
2140 else {
2141 $good = FALSE;
2142 break;
2143 }
2144 }
2145 if (!$good) {
2146 // Redo with aggregation turned off and return the new value.
2147 watchdog('advagg', 'JS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR);
2148 $data = advagg_process_js($master_set, TRUE);
2149 return $data;
2150 }
2151 }
2152
2153 // Default function called: advagg_js_builder
2154 $function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION);
2155 $output[$scope] = $function($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included);
2156 }
2157 return $output;
2158 }
2159
2160 /**
2161 * Build and theme JS output for header.
2162 *
2163 * @param $external_no_preprocess
2164 * array(array($src, $defer))
2165 * @param $output_preprocess
2166 * array(array($src, $prefix, $suffix))
2167 * @param $output_no_preprocess
2168 * array(array(array($src, $defer)))
2169 * @param $setting_no_preprocess
2170 * array(array($code))
2171 * @param $inline_no_preprocess
2172 * array(array($code, $defer))
2173 * @param $scope
2174 * header or footer.
2175 * @param $js_settings_array
2176 * array of settings used.
2177 * @param $inline_included
2178 * array of inline scripts used.
2179 * @param $files_included
2180 * array of files used.
2181 * @param $files_aggregates_included
2182 * array of files and aggregates used.
2183 * @return
2184 * String of themed JavaScript.
2185 */
2186 function advagg_js_builder($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) {
2187 $output = '';
2188
2189 // For inline Javascript to validate as XHTML, all Javascript containing
2190 // XHTML needs to be wrapped in CDATA. To make that backwards compatible
2191 // with HTML 4, we need to comment out the CDATA-tag.
2192 $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
2193 $embed_suffix = "\n//--><!]]>\n";
2194
2195 // Keep the order of JS files consistent as some are preprocessed and others are not.
2196 // Make sure any inline or JS setting variables appear last after libraries have loaded.
2197
2198 if (!empty($external_no_preprocess)) {
2199 foreach ($external_no_preprocess as $values) {
2200 list ($src, $defer) = $values;
2201 $output .= '<script type="text/javascript"'. ($defer ? ' defer="defer"' : '') .' src="'. $src . "\"></script>\n";
2202 }
2203 }
2204
2205 if (!empty($output_preprocess)) {
2206 foreach ($output_preprocess as $values) {
2207 list ($src, $prefix, $suffix) = $values;
2208 $output .= $prefix . '<script type="text/javascript" src="'. $src .'"></script>' . $suffix . "\n";
2209 }
2210 }
2211
2212 foreach ($output_no_preprocess as $type => $list) {
2213 if (!empty($list)) {
2214 foreach ($list as $values) {
2215 list ($src, $defer) = $values;
2216 $output .= '<script type="text/javascript"' . ($defer ? ' defer="defer"' : '') . ' src="' . $src . "\"></script>\n";
2217 }
2218 }
2219 }
2220
2221 if (!empty($setting_no_preprocess)) {
2222 foreach ($setting_no_preprocess as $code) {
2223 $output .= '<script type="text/javascript">' . $embed_prefix . $code . $embed_suffix . "</script>\n";
2224 }
2225 }
2226
2227 if (!empty($inline_no_preprocess)) {
2228 foreach ($inline_no_preprocess as $values) {
2229 list ($code, $defer) = $values;
2230 $output .= '<script type="text/javascript"'. ($defer ? ' defer="defer"' : '') .'>' . $embed_prefix . $code . $embed_suffix . "</script>\n";
2231 }
2232 }
2233
2234 return $output;
2235 }
2236
2237 /**
2238 * Always return TRUE, used for array_map in advagg_css_js_file_builder().
2239 */
2240 function advagg_return_true() {
2241 return TRUE;
2242 }
2243
2244 /**
2245 * Disable the page cache if the aggregate is not in the bundle.
2246 */
2247 function advagg_disable_page_cache() {
2248 global $conf;
2249 if (variable_get('advagg_page_cache_mode', ADVAGG_PAGE_CACHE_MODE)) {
2250 $conf['cache'] = CACHE_DISABLED;
2251
2252 // Invoke hook_advagg_disable_page_cache(). Allows 3rd party page cache
2253 // plugins like boost or varnish to not cache this page.
2254 module_invoke_all('advagg_disable_page_cache');
2255 }
2256 }
2257
2258 /**
2259 * Aggregate CSS/JS files, putting them in the files directory.
2260 *
2261 * @see drupal_build_js_cache()
2262 * @see drupal_build_css_cache()
2263 *
2264 * @param $type
2265 * js or css
2266 * @param $files
2267 * An array of JS files to aggregate and compress into one file.
2268 * @param $query_string
2269 * (optional) Query string to add on to the file if bundle isn't ready.
2270 * @param $counter
2271 * (optional) Counter value.
2272 * @param $force
2273 * (optional) Rebuild even if file already exists.
2274 * @param $md5
2275 * (optional) Bundle's machine name.
2276 * @return
2277 * array with the filepath as the key and prefix and suffix in another array.
2278 */
2279 function advagg_css_js_file_builder($type, $files, $query_string = '', $counter = FALSE, $force = FALSE, $md5 = '') {
2280 global $_advagg, $base_path;
2281 $data = '';
2282
2283 // Try cache first. When ever the counter changes this cache gets reset.
2284 $cached_data_key = 'advagg_file_builder_' . md5(implode('', array_filter(array_unique($files))));
2285 if (!$force) {
2286 // Try cache first; cache table is cache_advagg_bundle_reuse.
2287 $cached_data = advagg_cached_bundle_get($cached_data_key, 'file_builder_cache_object');
2288 if (!empty($cached_data)) {
2289 foreach ($cached_data as $filepath => $values) {
2290 // Ping cache.
2291 advagg_bundle_built($filepath);
2292 }
2293 return $cached_data;
2294 }
2295 }
2296
2297 list($css_path, $js_path) = advagg_get_root_files_dir();
2298 if ($type == 'js') {
2299 $file_type_path = $js_path;
2300 }
2301 if ($type == 'css') {
2302 $file_type_path = $css_path;
2303 }
2304
2305 // Send $files, get filename back
2306 $filenames = advagg_get_filename($files, $type, $counter, $md5);
2307
2308 // Debugging.
2309 if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
2310 $_advagg['debug']['file_builder_get_filenames'][] = array(
2311 'key' => $cached_data_key,
2312 'filenames' => $filenames,
2313 );
2314 }
2315 $output = array();
2316 $locks = array();
2317 $cacheable = TRUE;
2318 $files_used = array();
2319 foreach ($filenames as $info) {
2320 $filename = $info['filename'];
2321 $files = $info['files'];
2322 $bundle_md5 = $info['bundle_md5'];
2323 $prefix = '';
2324 $suffix = '';
2325 $filepath = $file_type_path .'/'. $filename;
2326
2327 // Invoke hook_advagg_js_extra_alter() or hook_advagg_css_extra_alter to
2328 // give installed modules a chance to modify the prefix or suffix for a
2329 // given filename.
2330 $values = array($filename, $bundle_md5, $prefix, $suffix);
2331 drupal_alter('advagg_' . $type . '_extra', $values);
2332 list($filename, $bundle_md5, $prefix, $suffix) = $values;
2333
2334 // Check that the file exists & filesize is not zero
2335 $built = advagg_bundle_built($filepath);
2336
2337 if (!$built || $force) {
2338 // Generate on request?
2339 if (variable_get('advagg_async_generation', ADVAGG_ASYNC_GENERATION) && !$force) {
2340 // Build request.
2341 $url = _advagg_build_url($filepath . '?generator=1');
2342 $headers = array(
2343 'Host' => $_SERVER['HTTP_HOST'],
2344 );
2345
2346 // Request file.
2347 if (function_exists('stream_socket_client') && function_exists('stream_select')) {
2348 advagg_async_connect_http_request($url, array('headers' => $headers));
2349 }
2350 else {
2351 // Set timeout.
2352 $socket_timeout = ini_set('default_socket_timeout', variable_get('advagg_socket_timeout', ADVAGG_SOCKET_TIMEOUT));
2353 drupal_http_request($url, $headers, 'GET');
2354 ini_set('default_socket_timeout', $socket_timeout);
2355 }
2356
2357 // Return filepath if we are going to wait for the bundle to be
2358 // generated or if the bundle already exists.
2359 if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) < 2 || advagg_bundle_built($filepath)) {
2360 $output[$filepath] = array('prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)));
2361 }
2362 else {
2363 // Aggregate isn't built yet, send back the files that where going to
2364 // be in it.
2365 foreach ($files as $file) {
2366 $output[$file . $query_string] = array('prefix' => '', 'suffix' => '', 'files' => array($file . $query_string => TRUE));
2367 }
2368 $cacheable = FALSE;
2369 advagg_disable_page_cache();
2370 }
2371 continue;
2372 }
2373
2374 // Only generate once.
2375 $lock_name = 'advagg_' . $filename;
2376 if (!lock_acquire($lock_name)) {
2377 if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) == 0 ) {
2378 $locks[] = array($lock_name => $filepath);
2379 $output[$filepath] = array('prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)));
2380 }
2381 else {
2382 // Aggregate isn't built yet, send back the files that where going
2383 // to be in it.
2384 foreach ($files as $file) {
2385 $output[$file . $query_string] = array('prefix' => '', 'suffix' => '', 'files' => array($file . $query_string => TRUE));
2386 }
2387 $cacheable = FALSE;
2388 advagg_disable_page_cache();
2389 }
2390 continue;
2391 }
2392
2393 if ($type == 'css') {
2394 $data = advagg_build_css_bundle($files);
2395 }
2396 elseif ($type == 'js') {
2397 $data = advagg_build_js_bundle($files);
2398 }
2399
2400 // Invoke hook_advagg_js_alter() or hook_advagg_css_alter to give
2401 // installed modules a chance to modify the data in the bundle if
2402 // necessary.
2403 drupal_alter('advagg_' . $type, $data, $files, $bundle_md5);
2404 $files_used = array_merge($files_used, $files);
2405
2406 // If data is empty then do not include this bundle in the final output.
2407 if (empty($data) && !$force) {
2408 lock_release($lock_name);
2409 continue;
2410 }
2411
2412 // Create the advagg_$type/ within the files folder.
2413 file_check_directory($file_type_path, FILE_CREATE_DIRECTORY);
2414
2415 // Write file. default function called: advagg_file_saver
2416 $function = variable_get('advagg_file_save_function', ADVAGG_FILE_SAVE_FUNCTION);
2417 $good = $function($data, $filepath, $force, $type);
2418
2419 // Release lock.
2420 lock_release($lock_name);
2421
2422 // If file save was not good then downgrade to non aggregated mode.
2423 if (!$good) {
2424 $output[$filepath] = FALSE;
2425 $cacheable = FALSE;
2426 continue;
2427 }
2428 }
2429 else {
2430 $files_used = array_merge($files_used, $files);
2431 }
2432 $output[$filepath] = array('prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)));
2433 }
2434
2435 // Wait for all locks before returning.
2436 if (!empty($locks)) {
2437 foreach ($locks as $lock_name => $filepath) {
2438 lock_wait($lock_name);
2439 if (!advagg_bundle_built($filepath)) {
2440 $output[$filepath] = FALSE;
2441 }
2442 }
2443 }
2444
2445 if (empty($output)) {
2446 $output[] = FALSE;
2447 return $output;
2448 }
2449
2450 // Cache the output
2451 if (!$force && $cacheable) {
2452 $new_cached_data_key = 'advagg_file_builder_' . md5(implode('', array_filter(array_unique($files_used))));
2453 // Verify the files in equals the files out.
2454 if ($new_cached_data_key == $cached_data_key) {
2455 cache_set($cached_data_key, $output, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY);
2456 }
2457 }
2458
2459 return $output;
2460 }
2461
2462 /**
2463 * Given a list of files, grab their contents and glue it into one big string.
2464 *
2465 * @param $files
2466 * array of filenames.
2467 * @return
2468 * string containing all the files.
2469 */
2470 function advagg_build_css_bundle($files) {
2471 $data = '';
2472 // Build aggregate CSS file.
2473 foreach ($files as $file) {
2474 $contents = drupal_load_stylesheet($file, TRUE);
2475 // Return the path to where this CSS file originated from.
2476 $base = base_path() . dirname($file) .'/';
2477 _drupal_build_css_path(NULL, $base);
2478 // Prefix all paths within this CSS file, ignoring external and absolute paths.
2479 $data .= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents);
2480 }
2481
2482 // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
2483 // @import rules must proceed any other style, so we move those to the top.
2484 $regexp = '/@import[^;]+;/i';
2485 preg_match_all($regexp, $data, $matches);
2486 $data = preg_replace($regexp, '', $data);
2487 $data = implode('', $matches[0]) . $data;
2488 return $data;
2489 }
2490
2491 /**
2492 * Given a list of files, grab their contents and glue it into one big string.
2493 *
2494 * @param $files
2495 * array of filenames.
2496 * @return
2497 * string containing all the files.
2498 */
2499 function advagg_build_js_bundle($files) {
2500 if (empty($files)) {
2501 return '';
2502 }
2503 $data = '';
2504 // Build aggregate JS file.
2505 foreach ($files as $file) {
2506 // Append a ';' and a newline after each JS file to prevent them from running together.
2507 if (advagg_file_exists($file)) {
2508 $data .= file_get_contents($file) .";\n";
2509 }
2510 }
2511 return $data;
2512 }
2513
2514 /**
2515 * Use a cache table to see if a file exists.
2516 *
2517 * @param $filename
2518 * name of file
2519 * @return
2520 * TRUE or FALSE
2521 */
2522 function advagg_file_exists($filename) {
2523 static $files = array();
2524 if (empty($files)) {
2525 $data = cache_get('advagg_file_checksum', 'cache');
2526 if (empty($data->data)) {
2527 $result = db_query("SELECT filename, checksum FROM {advagg_files}");
2528 while ($row = db_fetch_array($result)) {
2529 $files[$row['filename']] = $row['checksum'];
2530 }
2531 cache_set('advagg_file_checksum', $files, 'cache', CACHE_TEMPORARY);
2532 }
2533 else {
2534 $files = $data->data;
2535 }
2536 }
2537 if (!empty($files[$filename]) && $files[$filename] != -1) {
2538 return TRUE;
2539 }
2540 else {
2541 advagg_clearstatcache(TRUE, $filename);
2542 return file_exists($filename);
2543 }
2544 }
2545
2546 /**
2547 * Send out a fast 404 and exit.
2548 */
2549 function advagg_missing_fast404($msg = '') {
2550 global $base_path;
2551 if (!headers_sent()) {
2552 header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
2553 header('X-AdvAgg: Failed Validation. ' . $msg);
2554 }
2555
2556 print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
2557 print '<html>';
2558 print '<head><title>404 Not Found</title></head>';
2559 print '<body><h1>Not Found</h1>';
2560 print '<p>The requested URL was not found on this server.</p>';
2561 print '<p><a href="' . $base_path . '">Home</a></p>';
2562 print '<!-- advagg_missing_fast404 -->';
2563 print '</body></html>';
2564 exit();
2565 }
2566
2567 /**
2568 * Generate .htaccess rules and place them in advagg dir
2569 *
2570 * @param $dest
2571 * destination of the file that just got saved.
2572 * @param $force
2573 * force recreate the .htaccess file.
2574 */
2575 function advagg_htaccess_check_generate($dest, $force = FALSE) {
2576 global $base_path;
2577 if (!$force && !variable_get('advagg_dir_htaccess', ADVAGG_DIR_HTACCESS)) {
2578 return TRUE;
2579 }
2580
2581 $dir = dirname($dest);
2582 $htaccess_file = $dir . '/.htaccess';
2583 advagg_clearstatcache(TRUE, $htaccess_file);
2584 if (!$force && file_exists($htaccess_file)) {
2585 return TRUE;
2586 }
2587
2588 list($css_path, $js_path) = advagg_get_root_files_dir();
2589
2590 $type = '';
2591 if ($dir == $js_path) {
2592 $ext = 'js';
2593 $path = $js_path;
2594 $type = 'text/javascript';
2595 }
2596 elseif ($dir == $css_path) {
2597 $ext = 'css';
2598 $path = $css_path;
2599 $type = 'text/css';
2600 }
2601 else {
2602 return FALSE;
2603 }
2604
2605 $data = "\n";
2606 if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION)) {
2607 $data .= "<IfModule mod_rewrite.c>\n";
2608 $data .= " RewriteEngine on\n";
2609 $data .= " RewriteBase ${base_path}${path}\n";
2610 $data .= "\n";
2611 $data .= " # Send 404's back to index.php\n";
2612 $data .= " RewriteCond %{REQUEST_FILENAME} !-s\n";
2613 $data .= " RewriteRule ^(.*)$ ${base_path}index.php?q=$path/$1 [L]\n";
2614 $data .= "\n";
2615 $data .= " # Rules to correctly serve gzip compressed $ext files.\n";
2616 $data .= " # Requires both mod_rewrite and mod_headers to be enabled.\n";
2617 $data .= " <IfModule mod_headers.c>\n";
2618 $data .= " # Serve gzip compressed $ext files if they exist and client accepts gzip.\n";
2619 $data .= " RewriteCond %{HTTP:Accept-encoding} gzip\n";
2620 $data .= " RewriteCond %{REQUEST_FILENAME}\.gz -s\n";
2621 $data .= " RewriteRule ^(.*)\.$ext$ $1\.$ext\.gz [QSA]\n";
2622 $data .= "\n";
2623 $data .= " # Serve correct content types, and prevent mod_deflate double gzip.\n";
2624 $data .= " RewriteRule \.$ext\.gz$ - [T=$type,E=no-gzip:1]\n";
2625 $data .= "\n";
2626 $data .= " <FilesMatch \"\.$ext\.gz$\">\n";
2627 $data .= " # Serve correct encoding type.\n";
2628 $data .= " Header set Content-Encoding gzip\n";
2629 $data .= " # Force proxies to cache gzipped & non-gzipped $ext files separately.\n";
2630 $data .= " Header append Vary Accept-Encoding\n";
2631 $data .= " </FilesMatch>\n";
2632 $data .= " </IfModule>\n";
2633 $data .= "</IfModule>\n";
2634 $data .= "\n";
2635 }
2636 $data .= "<FilesMatch \"^${ext}_[0-9a-f]{32}_.+\.$ext(\.gz)?\">\n";
2637 $data .= " <IfModule mod_expires.c>\n";
2638 $data .= " # Enable expirations.\n";
2639 $data .= " ExpiresActive On\n";
2640 $data .= "\n";
2641 $data .= " # Cache all aggregated $ext files for 1 year after access (A).\n";
2642 $data .= " ExpiresDefault A31556926\n";
2643 $data .= " </IfModule>\n";
2644 $data .= " <IfModule mod_headers.c>\n";
2645 $data .= " # Unset unnecessary headers.\n";
2646 $data .= " Header unset Last-Modified\n";
2647 $data .= " Header unset Pragma\n";
2648 $data .= "\n";
2649 $data .= " # Make these files publicly cacheable.\n";
2650 $data .= " Header append Cache-Control \"public\"\n";
2651 $data .= " </IfModule>\n";
2652 $data .= " FileETag MTime Size\n";
2653 $data .= "</FilesMatch>\n";
2654 $data .= "\n";
2655
2656 if (!advagg_file_save_data($data, $htaccess_file, FILE_EXISTS_REPLACE)) {
2657 return FALSE;
2658 }
2659 return TRUE;
2660 }
2661
2662 /**
2663 * Adds a CSS file to the stylesheet queue.
2664 *
2665 * @param $data
2666 * (optional) The CSS data that will be set. If not set then the inline CSS
2667 * array will be passed back.
2668 * @param $media
2669 * (optional) The media type for the stylesheet, e.g., all, print, screen.
2670 * @param $prefix
2671 * (optional) prefix to add before the inlined css.
2672 * @param $suffix
2673 * (optional) suffix to add after the inlined css.
2674 * @return
2675 * An array of CSS files.
2676 */
2677 function advagg_add_css_inline($data = NULL, $media = 'all', $prefix = NULL, $suffix = NULL) {
2678 static $css = array();
2679
2680 // Store inline data in a static.
2681 if (isset($data)) {
2682 if (!isset($css[$media]['inline'][$prefix][$suffix])) {
2683 $css[$media]['inline'][$prefix][$suffix] = $data;
2684 }
2685 else {
2686 $css[$media]['inline'][$prefix][$suffix] .= "\n" . $data;
2687 }
2688 return;
2689 }
2690 else {
2691 return $css;
2692 }
2693 }
2694
2695 /**
2696 * Converts a PHP variable into its Javascript equivalent.
2697 *
2698 * We use HTML-safe strings, i.e. with <, > and & escaped.
2699 */
2700 function advagg_drupal_to_js($var) {
2701 static $php530;
2702 if (!isset($php530)) {
2703 $php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
2704 }
2705
2706 // json_encode on PHP prior to PHP 5.3.0 doesn't support options.
2707 if ($php530) {
2708 return json_encode($var, JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS);
2709 }
2710
2711 // if json_encode exists, use it.
2712 if (function_exists('json_encode')) {
2713 return str_replace(array("<", ">", "&"), array('\u003c', '\u003e', '\u0026'), json_encode($var));
2714 }
2715
2716 switch (gettype($var)) {
2717 case 'boolean':
2718 return $var ? 'true' : 'false'; // Lowercase necessary!
2719 case 'integer':
2720 case 'double':
2721 return $var;
2722 case 'resource':
2723 case 'string':
2724 // Always use Unicode escape sequences (\u0022) over JSON escape
2725 // sequences (\") to prevent browsers interpreting these as
2726 // special characters.
2727 $replace_pairs = array(
2728 // ", \ and U+0000 - U+001F must be escaped according to RFC 4627.
2729 '\\' => '\u005C',
2730 '"' => '\u0022',
2731 "\x00" => '\u0000',
2732 "\x01" => '\u0001',
2733 "\x02" => '\u0002',
2734 "\x03" => '\u0003',
2735 "\x04" => '\u0004',
2736 "\x05" => '\u0005',
2737 "\x06" => '\u0006',
2738 "\x07" => '\u0007',
2739 "\x08" => '\u0008',
2740 "\x09" => '\u0009',
2741 "\x0a" => '\u000A',
2742 "\x0b" => '\u000B',
2743 "\x0c" => '\u000C',
2744 "\x0d" => '\u000D',
2745 "\x0e" => '\u000E',
2746 "\x0f" => '\u000F',
2747 "\x10" => '\u0010',
2748 "\x11" => '\u0011',
2749 "\x12" => '\u0012',
2750 "\x13" => '\u0013',
2751 "\x14" => '\u0014',
2752 "\x15" => '\u0015',
2753 "\x16" => '\u0016',
2754 "\x17" => '\u0017',
2755 "\x18" => '\u0018',
2756 "\x19" => '\u0019',
2757 "\x1a" => '\u001A',
2758 "\x1b" => '\u001B',
2759 "\x1c" => '\u001C',
2760 "\x1d" => '\u001D',
2761 "\x1e" => '\u001E',
2762 "\x1f" => '\u001F',
2763 // Prevent browsers from interpreting these as as special.
2764 "'" => '\u0027',
2765 '<' => '\u003C',
2766 '>' => '\u003E',
2767 '&' => '\u0026',
2768 // Prevent browsers from interpreting the solidus as special and
2769 // non-compliant JSON parsers from interpreting // as a comment.
2770 '/' => '\u002F',
2771 // While these are allowed unescaped according to ECMA-262, section
2772 // 15.12.2, they cause problems in some JSON parsers.
2773 "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator.
2774 "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator.
2775 );
2776
2777 return '"' . strtr($var, $replace_pairs) . '"';
2778 case 'array':
2779 // Arrays in JSON can't be associative. If the array is empty or if it
2780 // has sequential whole number keys starting with 0, it's not associative
2781 // so we can go ahead and convert it as an array.
2782 if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
2783 $output = array();
2784 foreach ($var as $v) {
2785 $output[] = advagg_drupal_to_js($v);
2786 }
2787 return '[ ' . implode(', ', $output) . ' ]';
2788 }
2789 // Otherwise, fall through to convert the array as an object.
2790 case 'object':
2791 $output = array();
2792 foreach ($var as $k => $v) {
2793 $output[] = advagg_drupal_to_js(strval($k)) . ': ' . advagg_drupal_to_js($v);
2794 }
2795 return '{ ' . implode(', ', $output) .' }';
2796 default:
2797 return 'null';
2798 }
2799 }
2800
2801 /**
2802 * Process the contents of a stylesheet for aggregation.
2803 *
2804 * @param $contents
2805 * The contents of the stylesheet.
2806 * @param $optimize
2807 * (optional) Boolean whether CSS contents should be minified. Defaults to
2808 * FALSE.
2809 * @return
2810 * Contents of the stylesheet including the imported stylesheets.
2811 */
2812 function advagg_drupal_load_stylesheet_content($contents, $optimize = FALSE) {
2813 // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
2814 $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
2815
2816 if ($optimize) {
2817 // Perform some safe CSS optimizations.
2818 // Regexp to match comment blocks.
2819 $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
2820 // Regexp to match double quoted strings.
2821 $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
2822 // Regexp to match single quoted strings.
2823 $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
2824 // Strip all comment blocks, but keep double/single quoted strings.
2825 $contents = preg_replace(
2826 "<($double_quot|$single_quot)|$comment>Ss",
2827 "$1",
2828 $contents
2829 );
2830 // Remove certain whitespace.
2831 // There are different conditions for removing leading and trailing
2832 // whitespace.
2833 // @see http://php.net/manual/en/regexp.reference.subpatterns.php
2834 $contents = preg_replace('<
2835 # Strip leading and trailing whitespace.
2836 \s*([@{};,])\s*
2837 # Strip only leading whitespace from:
2838 # - Closing parenthesis: Retain "@media (bar) and foo".
2839 | \s+([\)])
2840 # Strip only trailing whitespace from:
2841 # - Opening parenthesis: Retain "@media (bar) and foo".
2842 # - Colon: Retain :pseudo-selectors.
2843 | ([\(:])\s+
2844 >xS',
2845 // Only one of the three capturing groups will match, so its reference
2846 // will contain the wanted value and the references for the
2847 // two non-matching groups will be replaced with empty strings.
2848 '$1$2$3',
2849 $contents
2850 );
2851 // End the file with a new line.
2852 $contents = trim($contents);
2853 $contents .= "\n";
2854 }
2855
2856 // Replaces @import commands with the actual stylesheet content.
2857 // This happens recursively but omits external files.
2858 $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_advagg_drupal_load_stylesheet', $contents);
2859 return $contents;
2860 }
2861
2862 /**
2863 * Loads stylesheets recursively and returns contents with corrected paths.
2864 *
2865 * This function is used for recursive loading of stylesheets and
2866 * returns the stylesheet content with all url() paths corrected.
2867 */
2868 function _advagg_drupal_load_stylesheet($matches) {
2869 $filename = $matches[1];
2870 // Load the imported stylesheet and replace @import commands in there as well.
2871 $file = advagg_build_css_bundle(array($filename));
2872
2873 // Determine the file's directory.
2874 $directory = dirname($filename);
2875 // If the file is in the current directory, make sure '.' doesn't appear in
2876 // the url() path.
2877 $directory = $directory == '.' ? '' : $directory . '/';
2878
2879 // Alter all internal url() paths. Leave external paths alone. We don't need
2880 // to normalize absolute paths here (i.e. remove folder/... segments) because
2881 // that will be done later.
2882 return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file);
2883 }
2884
2885 /**
2886 * Perform an HTTP request; does not wait for reply & you will never get it
2887 * back.
2888 *
2889 * @see drupal_http_request()
2890 *
2891 * This is a flexible and powerful HTTP client implementation. Correctly
2892 * handles GET, POST, PUT or any other HTTP requests.
2893 *
2894 * @param $url
2895 * A string containing a fully qualified URI.
2896 * @param array $options
2897 * (optional) An array that can have one or more of the following elements:
2898 * - headers: An array containing request headers to send as name/value pairs.
2899 * - method: A string containing the request method. Defaults to 'GET'.
2900 * - data: A string containing the request body, formatted as
2901 * 'param=value&param=value&...'. Defaults to NULL.
2902 * - max_redirects: An integer representing how many times a redirect
2903 * may be followed. Defaults to 3.
2904 * - timeout: A float representing the maximum number of seconds the function
2905 * call may take. The default is 30 seconds. If a timeout occurs, the error
2906 * code is set to the HTTP_REQUEST_TIMEOUT constant.
2907 * - context: A context resource created with stream_context_create().
2908 * @return bool
2909 * return value from advagg_async_send_http_request().
2910 */
2911 function advagg_async_connect_http_request($url, array $options = array()) {
2912 $result = new stdClass();
2913
2914 // Parse the URL and make sure we can handle the schema.
2915 $uri = @parse_url($url);
2916
2917 if (empty($uri)) {
2918 $result->error = 'unable to parse URL';
2919 $result->code = -1001;
2920 return $result;
2921 }
2922
2923 if (!isset($uri['scheme'])) {
2924 $result->error = 'missing schema';
2925 $result->code = -1002;
2926 return $result;
2927 }
2928
2929 // Merge the default options.
2930 $options += array(
2931 'headers' => array(),
2932 'method' => 'GET',
2933 'data' => NULL,
2934 'max_redirects' => 3,
2935 'timeout' => 30.0,
2936 'context' => NULL,
2937 );
2938 // stream_socket_client() requires timeout to be a float.
2939 $options['timeout'] = (float) $options['timeout'];
2940
2941 switch ($uri['scheme']) {
2942 case 'http':
2943 case 'feed':
2944 $port = isset($uri['port']) ? $uri['port'] : 80;
2945 $socket = 'tcp://' . $uri['host'] . ':' . $port;
2946 // RFC 2616: "non-standard ports MUST, default ports MAY be included".
2947 // We don't add the standard port to prevent from breaking rewrite rules
2948 // checking the host that do not take into account the port number.
2949 if (empty($options['headers']['Host'])) {
2950 $options['headers']['Host'] = $uri['host'];
2951 }
2952 if ($port != 80) {
2953 $options['headers']['Host'] .= ':' . $port;
2954 }
2955 break;
2956 case 'https':
2957 // Note: Only works when PHP is compiled with OpenSSL support.
2958 $port = isset($uri['port']) ? $uri['port'] : 443;
2959 $socket = 'ssl://' . $uri['host'] . ':' . $port;
2960 if (empty($options['headers']['Host'])) {
2961 $options['headers']['Host'] = $uri['host'];
2962 }
2963 if ($port != 443) {
2964 $options['headers']['Host'] .= ':' . $port;
2965 }
2966 break;
2967 default:
2968 $result->error = 'invalid schema ' . $uri['scheme'];
2969 $result->code = -1003;
2970 return $result;
2971 }
2972
2973 $flags = STREAM_CLIENT_CONNECT;
2974 if (variable_get('advagg_async_socket_connect', ADVAGG_ASYNC_SOCKET_CONNECT)) {
2975 $flags = STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT;
2976 }
2977 if (empty($options['context'])) {
2978 $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags);
2979 }
2980 else {
2981 // Create a stream with context. Allows verification of a SSL certificate.
2982 $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags, $options['context']);
2983 }
2984
2985 // Make sure the socket opened properly.
2986 if (!$fp) {
2987 // When a network error occurs, we use a negative number so it does not
2988 // clash with the HTTP status codes.
2989 $result->code = -$errno;
2990 $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
2991
2992 return $result;
2993 }
2994
2995 // Non blocking stream.
2996 stream_set_blocking($fp, 0);
2997
2998 // Construct the path to act on.
2999 $path = isset($uri['path']) ? $uri['path'] : '/';
3000 if (isset($uri['query'])) {
3001 $path .= '?' . $uri['query'];
3002 }
3003
3004 // Merge the default headers.
3005 $options['headers'] += array(
3006 'User-Agent' => 'Drupal (+http://drupal.org/)',
3007 );
3008
3009 // Only add Content-Length if we actually have any content or if it is a POST
3010 // or PUT request. Some non-standard servers get confused by Content-Length in
3011 // at least HEAD/GET requests, and Squid always requires Content-Length in
3012 // POST/PUT requests.
3013 $content_length = strlen($options['data']);
3014 if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
3015 $options['headers']['Content-Length'] = $content_length;
3016 }
3017
3018 // If the server URL has a user then attempt to use basic authentication.
3019 if (isset($uri['user'])) {
3020 $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
3021 }
3022
3023 // If the database prefix is being used by SimpleTest to run the tests in a copied
3024 // database then set the user-agent header to the database prefix so that any
3025 // calls to other Drupal pages will run the SimpleTest prefixed database. The
3026 // user-agent is used to ensure that multiple testing sessions running at the
3027 // same time won't interfere with each other as they would if the database
3028 // prefix were stored statically in a file or database variable.
3029 $test_info = &$GLOBALS['drupal_test_info'];
3030 if (!empty($test_info['test_run_id'])) {
3031 $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
3032 }
3033
3034 $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
3035 foreach ($options['headers'] as $name => $value) {
3036 $request .= $name . ': ' . trim($value) . "\r\n";
3037 }
3038 $request .= "\r\n" . $options['data'];
3039 $result->request = $request;
3040
3041 return advagg_async_send_http_request($fp, $request, $options['timeout']);
3042 }
3043
3044 /**
3045 * Perform an HTTP request; does not wait for reply & you never will get it
3046 * back.
3047 *
3048 * @see drupal_http_request()
3049 *
3050 * This is a flexible and powerful HTTP client implementation. Correctly
3051 * handles GET, POST, PUT or any other HTTP requests.
3052 *
3053 * @param $fp
3054 * (optional) A file pointer.
3055 * @param $request
3056 * (optional) A string containing the request headers to send to the server.
3057 * @param $timeout
3058 * (optional) An integer holding the stream timeout value.
3059 * @return bool
3060 * TRUE if function worked as planed.
3061 */
3062 function advagg_async_send_http_request($fp = NULL, $request = '', $timeout = 30) {
3063 static $requests = array();
3064 static $registered = FALSE;
3065
3066 // Store data in a static, and register a shutdown function.
3067 $args = array($fp, $request, $timeout);
3068 if (!empty($fp)) {
3069 $requests[] = $args;
3070 if (!$registered) {
3071 register_shutdown_function(__FUNCTION__);
3072 $registered = TRUE;
3073 }
3074 return TRUE;
3075 }
3076
3077 // Shutdown function run.
3078 if (empty($requests)) {
3079 return FALSE;
3080 }
3081
3082 $streams = array();
3083 foreach ($requests as $id => $values) {
3084 list($fp, $request, $timeout) = $values;
3085 $streams[$id] = $fp;
3086 }
3087
3088 $retry_count = 2;
3089 // Run the loop as long as we have a stream to write to.
3090 while (!empty($streams)) {
3091 // Set the read and write vars to the streams var.
3092 $read = $write = $streams;
3093 $except = array();
3094
3095 // Do some voodoo and open all streams at once.
3096 $n = @stream_select($read, $write, $except, $timeout);
3097
3098 // We have some streams to write to.
3099 if (!empty($n)) {
3100 // Write to each stream if it is available.
3101 foreach ($write as $id => $w) {
3102 fwrite($w, $requests[$id][1]);
3103 fclose($w);
3104 unset($streams[$id]);
3105 }
3106 }
3107 // Timed out waiting or all $streams are closed at this point.
3108 elseif (!empty($retry_count)) {
3109 $retry_count--;
3110 }
3111 else {
3112 break;
3113 }
3114 }
3115 // Free memory.
3116 $requests = array();
3117 if ($n !== FALSE && empty($streams)) {
3118 return TRUE;
3119 }
3120 else {
3121 return FALSE;
3122 }
3123 }
3124
3125 /**
3126 * Implement hook_advagg_js_header_footer_alter.
3127 */
3128 function advagg_advagg_js_header_footer_alter(&$master_set, $preprocess_js, $public_downloads) {
3129 // Don't run the code below if ctools ajax is not loaded.
3130 if (!defined('CTOOLS_AJAX_INCLUDED')) {
3131 return;
3132 }
3133
3134 // Get all JS files set to be loaded.
3135 $js_files = array();
3136 foreach ($master_set as $scope => $scripts) {
3137 if (empty($scripts)) {
3138 continue;
3139 }
3140 advagg_ctools_process_js_files($js_files, $scope, $scripts);
3141 }
3142
3143 // Add list of CSS & JS files loaded to the settings in the footer.
3144 $loaded = array('CToolsAJAX' => array('scripts' => $js_files));
3145 // Save to the js settings array even though we do not reload it in advagg.
3146 drupal_add_js($loaded, 'setting', 'footer');
3147
3148 // Add it to the settings array in the footer.
3149 if (!isset($master_set['footer']['setting']) || !is_array($master_set['footer']['setting'])) {
3150 $master_set['footer']['setting'] = array();
3151 }
3152 $master_set['footer']['setting'][] = $loaded;
3153 }
3154
3155 /**
3156 * Implement hook_advagg_css_pre_alter.
3157 */
3158 function advagg_advagg_css_pre_alter(&$css, $preprocess_css, $public_downloads) {
3159 // Don't run the code below if ctools ajax is not loaded.
3160 if (!defined('CTOOLS_AJAX_INCLUDED')) {
3161 return;
3162 }
3163
3164 // Get all CSS files set to be loaded.
3165 $css_files = array();
3166 ctools_process_css_files($css_files, $css);
3167
3168 // Save to the js settings array.
3169 drupal_add_js(array('CToolsAJAX' => array('css' => $css_files)), 'setting', 'footer');
3170 }
3171
3172 /**
3173 * Create a list of javascript files that are on the page.
3174 *
3175 * @param $js_files
3176 * Array of js files that are loaded on this page.
3177 * @param $scope
3178 * String usually containing header or footer.
3179 * @param $scripts
3180 * (Optional) array returned from drupal_add_js(). If NULL then it will load
3181 * the array from drupal_add_js for the given scope.
3182 * @return array $settings
3183 * The JS 'setting' array for the given scope.
3184 */
3185 function advagg_ctools_process_js_files(&$js_files, $scope, $scripts = NULL) {
3186 // Automatically extract any 'settings' added via drupal_add_js() and make
3187 // them the first command.
3188 $scripts = drupal_add_js(NULL, NULL, $scope);
3189 if (empty($scripts)) {
3190 $scripts = drupal_add_js(NULL, NULL, $scope);
3191 }
3192
3193 // Get replacements that are going to be made by contrib modules and take
3194 // them into account so we don't double-load scripts.
3195 static $replacements = NULL;
3196 if (!isset($replacements)) {
3197 $replacements = module_invoke_all('js_replacements');
3198 }
3199
3200 $settings = array();
3201 foreach ($scripts as $type => $data) {
3202 switch ($type) {
3203 case 'setting':
3204 $settings = $data;
3205 break;
3206 case 'inline':
3207 case 'theme':
3208 // Presently we ignore inline javascript.
3209 // Theme JS is already added and because of admin themes, this could add
3210 // improper JS to the page.
3211 break;
3212 default:
3213 // If JS preprocessing is off, we still need to output the scripts.
3214 // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
3215 foreach ($data as $path => $info) {
3216 // If the script is being replaced, take that replacement into account.
3217 $final_path = isset($replacements[$type][$path]) ? $replacements[$type][$path] : $path;
3218 $js_files[base_path() . $final_path] = TRUE;
3219 }
3220 }
3221 }
3222 return $settings;
3223 }
3224
3225 /**
3226 * Wrapper around clearstatcache so it can use php 5.3's new features.
3227 *
3228 * @param $clear_realpath_cache
3229 * Bool.
3230 * @param $filename
3231 * String.
3232 * @return
3233 * value from clearstatcache().
3234 */
3235 function advagg_clearstatcache($clear_realpath_cache = FALSE, $filename = NULL) {
3236 static $php530;
3237 if (!isset($php530)) {
3238 $php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
3239 }
3240
3241 if ($php530) {
3242 return clearstatcache($clear_realpath_cache, $filename);
3243 }
3244 else {
3245 return clearstatcache();
3246 }
3247 }
3248
3249 /**
3250 * Select records in the database matching where IN(...).
3251 *
3252 * NOTE Be aware of the servers max_packet_size variable.
3253 *
3254 * @param $table
3255 * The name of the table.
3256 * @param $field
3257 * field name to be compared to
3258 * @param $placeholder
3259 * db_query placeholders; like %d or '%s'
3260 * @param $data
3261 * array of values you wish to compare to
3262 * @param $returns
3263 * array of db fields you return
3264 * @return
3265 * returns db_query() result.
3266 */
3267 function advagg_db_multi_select_in($table, $field, $placeholder, $data, $returns = array(), $groupby = '') {
3268 // Set returns if empty
3269 if (empty($returns)) {
3270 $returns[] = '*';
3271 }
3272 // Get the number of rows that will be inserted
3273 $rows = count($data);
3274 // Create what goes in the IN ()
3275 $in = $placeholder;
3276 // Add the rest of the place holders
3277 for ($i = 1; $i < $rows; $i++) {
3278 $in .= ', ' . $placeholder;
3279 }
3280 // Build the query
3281 $query = "SELECT " . implode(', ', $returns) . " FROM {" . $table . "} WHERE $field IN ($in) $groupby";
3282 // Run the query
3283 return db_query($query, $data);
3284 }
3285
3286 /**
3287 * Return a large array of the CSS & JS files loaded on this page.
3288 *
3289 * @param $js_files_excluded
3290 * array of js files to not include in the output array.
3291 * @param $css_files_excluded
3292 * array of css files to not include in the output array.
3293 * @return
3294 * array.
3295 */
3296 function advagg_get_js_css_get_array($js_files_excluded = array(), $css_files_excluded = array()) {
3297 global $conf, $_advagg;
3298
3299 // Setup variables.
3300 $variables = array(
3301 'css' => array(),
3302 'js' => array(),
3303 );
3304 // Setup render functions.
3305 $css_function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION);
3306 $js_function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION);
3307 $conf['advagg_css_render_function'] = 'advagg_css_array';
3308 $conf['advagg_js_render_function'] = 'advagg_js_array';
3309
3310 // Run CSS code.
3311 $css_array = array();
3312 $variables['css'] = drupal_add_css();
3313 if (module_exists('less')) {
3314 less_preprocess_page($variables, NULL);
3315 }
3316 $css_func_inline = advagg_add_css_inline();
3317 if (!empty($css_func_inline)) {
3318 $variables['css'] = advagg_merge_inline_css($variables['css'], $css_func_inline);
3319 }
3320 // Remove excluded CSS files.
3321 foreach ($variables['css'] as $media => $types) {
3322 foreach ($types as $type => $values) {
3323 foreach ($values as $filename => $preprocess) {
3324 if (in_array($filename, $css_files_excluded)) {
3325 unset($variables['css'][$media][$type][$filename]);
3326 }
3327 }
3328 }
3329 }
3330 $css_array = advagg_process_css($variables['css']);
3331
3332 // Run JS code.
3333 $js_array = array();
3334 $variables['js']['header'] = drupal_add_js(NULL, NULL, 'header');
3335 if (variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) {
3336 $variables['js']['footer'] = drupal_add_js(NULL, NULL, 'footer');
3337 }
3338 advagg_jquery_updater($variables['js']['header']);
3339 // Remove excluded JS files.
3340 foreach ($variables['js'] as $scope => $values) {
3341 foreach ($values as $type => $data) {
3342 foreach ($data as $filename => $info) {
3343 if (in_array($filename, $js_files_excluded)) {
3344 unset($variables['js'][$scope][$type][$filename]);
3345 }
3346 }
3347 }
3348 }
3349 $js_array = advagg_process_js($variables['js']);
3350
3351 // Set render functions back to defaults.
3352 $conf['advagg_css_render_function'] = $css_function;
3353 $conf['advagg_js_render_function'] = $js_function;
3354
3355 // Return arrays.
3356 return array(
3357 'js' => $js_array,
3358 'css' => $css_array,
3359 );
3360 }
3361
3362 /**
3363 * Logic to figure out what kind of css tags to use.
3364 *
3365 * @param $external_no_preprocess
3366 * array of css files ($media, $href)
3367 * @param $module_no_preprocess
3368 * array of css files ($media, $href)
3369 * @param $output_no_preprocess
3370 * array of css files ($media, $href)
3371 * @param $output_preprocess
3372 * array of css files ($media, $href, $prefix, $suffix)
3373 * @param $theme_no_preprocess
3374 * array of css files ($media, $href)
3375 * @param $inline_no_preprocess
3376 * array of css data to inline ($media, $data)
3377 * @param $files_included
3378 * array of css files included. $a[$media][] = $filename
3379 * @param $files_aggregates_included
3380 * array of css files & aggregates included. $a[$media][] = $filename
3381 * @param $inline_included
3382 * array of inline css included. $a[$media][] = $datablob;
3383 * @return
3384 * html for loading the css. html for the head.
3385 */
3386 function advagg_css_array($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included) {
3387 return array(
3388 'inline' => $inline_included,
3389 'files' => $files_included,
3390 'files_aggregates' => $files_aggregates_included,
3391 );
3392 }
3393
3394 /**
3395 * Build and theme JS output for header.
3396 *
3397 * @param $external_no_preprocess
3398 * array(array($src, $defer))
3399 * @param $output_preprocess
3400 * array(array($src, $prefix, $suffix))
3401 * @param $output_no_preprocess
3402 * array(array(array($src, $defer)))
3403 * @param $setting_no_preprocess
3404 * array(array($code))
3405 * @param $inline_no_preprocess
3406 * array(array($code, $defer))
3407 * @param $scope
3408 * header or footer.
3409 * @param $js_settings_array
3410 * array of settings used.
3411 * @param $inline_included
3412 * array of inline scripts used.
3413 * @param $files_included
3414 * array of files used.
3415 * @param $files_aggregates_included
3416 * array of files and aggregates used.
3417 * @return
3418 * String of themed JavaScript.
3419 */
3420 function advagg_js_array($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) {
3421 return array(
3422 'settings' => $js_settings_array,
3423 'inline' => $inline_included,
3424 'files' => $files_included,
3425 'files_aggregates' => $files_aggregates_included,
3426 );
3427 }
3428
3429 /**
3430 * Implementation of hook_file_download().
3431 *
3432 * Return the correct headers for advagg bundles.
3433 */
3434 function advagg_file_download($file, $type = '') {
3435