5 * Advanced CSS/JS aggregation module
10 * Default value to see if advanced CSS/JS aggregation is enabled.
12 define('ADVAGG_ENABLED', TRUE
);
15 * Default stale file threshold is 6 days for mtime.
17 define('ADVAGG_STALE_FILE_THRESHOLD', 518400);
20 * Default stale file threshold is 3 days for atime.
22 define('ADVAGG_STALE_FILE_LAST_USED_THRESHOLD', 259200);
25 * Default file last used check-in is 12 hours.
27 define('ADVAGG_FILE_LAST_USED_INTERVAL', 1296000);
30 * Default gzip compression setting.
32 define('ADVAGG_GZIP_COMPRESSION', FALSE
);
35 * Default generate the aggregate async.
37 define('ADVAGG_ASYNC_GENERATION', FALSE
);
40 * How long to wait for the server to come back with an async opp.
42 define('ADVAGG_SOCKET_TIMEOUT', 1);
45 * Default file checksum mode.
47 define('ADVAGG_CHECKSUM_MODE', 'mtime');
50 * Default value for writing debug info to watchdog.
52 define('ADVAGG_DEBUG', FALSE
);
55 * Default value for number of files that can be added before using @import.
57 define('ADVAGG_CSS_COUNT_THRESHOLD', 25);
60 * Default value for not using @import if logged in and not IE.
62 define('ADVAGG_CSS_LOGGED_IN_IE_DETECT', TRUE
);
65 * Default value for creating a htaccess file in the advagg directories.
67 define('ADVAGG_DIR_HTACCESS', TRUE
);
70 * Default value for a custom files directory just for advagg directories.
72 define('ADVAGG_CUSTOM_FILES_DIR', '');
75 * Default function used to render css output.
77 define('ADVAGG_CSS_RENDER_FUNCTION', 'advagg_unlimited_css_builder');
80 * Default function used to render js output.
82 define('ADVAGG_JS_RENDER_FUNCTION', 'advagg_js_builder');
85 * Default function used to save files.
87 define('ADVAGG_FILE_SAVE_FUNCTION', 'advagg_file_saver');
90 * Default value for rebuilding the bundle on cache flush.
92 define('ADVAGG_REBUILD_ON_FLUSH', FALSE
);
95 * Default value to see if advanced CSS/JS aggregation is enabled.
97 define('ADVAGG_CLOSURE', TRUE
);
100 * Default value to see if JS bundle matching should be strict.
102 define('ADVAGG_STRICT_JS_BUNDLES', TRUE
);
105 * Default mode for aggregate creation.
107 * 0 - Wait for locks.
108 * 1 - Do not wait for locks.
109 * 2 - Only serve aggregated files if they are already built.
111 define('ADVAGG_AGGREGATE_MODE', 2);
114 * Default mode of advagg in regards to the page cache.
116 * FALSE - Cache all pages.
117 * TRUE - Don't cache page if aggregate could be included on that page & it is
120 define('ADVAGG_PAGE_CACHE_MODE', TRUE
);
123 * Default mode of advagg_bundle_built() in regards to how file_exists is used.
125 * FALSE - use file_exists.
126 * TRUE - use cache_get instead of file_exists if possible.
128 define('ADVAGG_BUNDLE_BUILT_MODE', FALSE
);
131 * Default value to see if we can use the STREAM_CLIENT_ASYNC_CONNECT flag.
133 define('ADVAGG_ASYNC_SOCKET_CONNECT', FALSE
);
136 * Default value to see if we removed old files/bundles from the database.
138 define('ADVAGG_PRUNE_ON_CRON', TRUE
);
141 * Default value to see if we cache at the advagg_processor level.
143 define('ADVAGG_USE_FULL_CACHE', TRUE
);
146 * Default value to see if preprocess JavaScript files.
148 define('ADVAGG_PREPROCESS_JS', TRUE
);
151 * Default value to see if preprocess CSS files.
153 define('ADVAGG_PREPROCESS_CSS', TRUE
);
156 * Default value to see if preprocess CSS files.
158 define('ADVAGG_ONLY_CSS_FROM_VARIABLES', FALSE
);
160 // Create a closure function that does not add JavaScript.
161 if (variable_get('advagg_closure', ADVAGG_CLOSURE
)) {
162 if (!function_exists('phptemplate_closure')) {
165 * Execute hook_footer() which is run at the end of the page right before
166 * the close of the body tag.
168 * @param $main (optional)
169 * Whether the current page is the front page of the site.
171 * A string containing the results of the hook_footer() calls.
173 function phptemplate_closure($main = 0) {
175 $_advagg['closure'] = TRUE
;
176 $footer = implode("\n", module_invoke_all('footer', $main));
177 // If advagg is disabled, then include footer JS here.
178 if (!variable_get('advagg_enabled', ADVAGG_ENABLED
)) {
179 $footer .
= drupal_get_js('footer');
187 $_advagg['closure'] = FALSE
;
192 * Implementation of hook_perm().
194 function advagg_perm() {
195 return array('bypass advanced aggregation');
199 * Implementation of hook_menu().
201 function advagg_menu() {
202 list($css_path, $js_path) = advagg_get_root_files_dir();
203 $file_path = drupal_get_path('module', 'advagg');
206 $items[$css_path .
'/%'] = array(
207 'page callback' => 'advagg_missing_css',
208 'type' => MENU_CALLBACK
,
209 'access callback' => TRUE
,
210 'file path' => $file_path,
211 'file' => 'advagg.missing.inc',
213 $items[$js_path .
'/%'] = array(
214 'page callback' => 'advagg_missing_js',
215 'type' => MENU_CALLBACK
,
216 'access callback' => TRUE
,
217 'file path' => $file_path,
218 'file' => 'advagg.missing.inc',
220 $items['admin/settings/advagg'] = array(
221 'title' => 'Advanced CSS/JS Aggregation',
222 'description' => 'Configuration for Advanced CSS/JS Aggregation.',
223 'page callback' => 'advagg_admin_page',
224 'type' => MENU_NORMAL_ITEM
,
225 'access arguments' => array('administer site configuration'),
226 'file path' => $file_path,
227 'file' => 'advagg.admin.inc',
229 $items['admin/settings/advagg/config'] = array(
230 'title' => 'Configuration',
231 'type' => MENU_DEFAULT_LOCAL_TASK
,
234 $items['admin/settings/advagg/info'] = array(
235 'title' => 'Information',
236 'description' => 'More detailed information about advagg.',
237 'page callback' => 'advagg_admin_info_page',
238 'type' => MENU_LOCAL_TASK
,
239 'access arguments' => array('administer site configuration'),
240 'file path' => $file_path,
241 'file' => 'advagg.admin.inc',
243 $items['admin_menu/flush-cache/advagg'] = array(
244 'page callback' => 'advagg_admin_flush_cache',
245 'type' => MENU_CALLBACK
,
246 'access arguments' => array('administer site configuration'),
247 'file path' => $file_path,
248 'file' => 'advagg.admin.inc',
254 * Implementation of hook_admin_menu().
256 * Add in a cache flush for advagg.
258 function advagg_admin_menu(&$deleted) {
262 'title' => 'Adv CSS/JS Agg',
263 'path' => 'admin_menu/flush-cache/advagg',
264 'query' => 'destination',
265 'parent_path' => 'admin_menu/flush-cache',
272 * Implementation of hook_admin_menu_output_alter().
274 * Add in a cache flush for advagg.
276 function advagg_admin_menu_output_alter(&$content) {
277 if (!empty($content['icon']['icon']['flush-cache']['#access']) && !empty($content['icon']['icon']['flush-cache']['requisites']) && empty($content['icon']['icon']['flush-cache']['advagg'])) {
278 $content['icon']['icon']['flush-cache']['advagg'] = $content['icon']['icon']['flush-cache']['requisites'];
279 $content['icon']['icon']['flush-cache']['advagg']['#title'] = t('Adv CSS/JS Agg');
280 $content['icon']['icon']['flush-cache']['advagg']['#href'] = 'admin_menu/flush-cache/advagg';
285 * Implementation of hook_cron().
287 function advagg_cron() {
288 if (!variable_get('advagg_prune_on_cron', ADVAGG_PRUNE_ON_CRON
)) {
292 // Set the oldest file/bundle to keep at 2 weeks.
293 $max_time = module_exists('advagg_bundler') ?
variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED
) : 1209600;
294 $max_file_time = time() - $max_time;
295 $max_bundle_time = time() - ($max_time * 3);
296 $bundles_removed = 0;
297 $files_removed = array();
300 $results = db_query("SELECT filename, filename_md5 FROM {advagg_files}");
301 while ($row = db_fetch_array($results)) {
302 // If the file exists, do nothing
303 if (file_exists($row['filename'])) {
307 // Remove bundles referencing missing files, if they are older than 2 weeks.
308 $bundles = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s' AND timestamp < %d", $row['filename_md5'], $max_file_time);
309 while ($bundle_md5 = db_result($bundles)) {
311 db_query("DELETE FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5);
313 $count = db_result(db_query("SELECT COUNT(*) FROM {advagg_bundles} WHERE filename_md5 = '%s'", $row['filename_md5']));
315 // If no more bundles reference the missing file then remove the file.
317 db_query("DELETE FROM {advagg_files} WHERE filename_md5 = '%s'", $row['filename_md5']);
318 $files_removed[] = $row['filename'];
323 $bundles_removed += db_result(db_query("
324 SELECT COUNT(DISTINCT bundle_md5) AS advagg_count
325 FROM {advagg_bundles}
327 ", $max_bundle_time));
328 $results = db_query("DELETE FROM {advagg_bundles} WHERE timestamp < %d", $max_bundle_time);
330 // Report to watchdog if anything was done.
331 if (!empty($bundles_removed) || !empty($files_removed)) {
332 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(
333 '%files' => implode(', ', $files_removed),
334 '%count' => $bundles_removed,
340 * Implementation of hook_init().
342 function advagg_init() {
343 global $base_path, $conf, $_advagg;
345 // Disable advagg if requested.
346 if (isset($_GET['advagg']) && $_GET['advagg'] == -1 && user_access('bypass advanced aggregation')) {
347 $conf['advagg_enabled'] = FALSE
;
348 $conf['advagg_use_full_cache'] = FALSE
;
350 // Enable debugging if requested.
351 if (isset($_GET['advagg-debug']) && $_GET['advagg-debug'] == 1 && user_access('bypass advanced aggregation')) {
352 $conf['advagg_debug'] = TRUE
;
353 $conf['advagg_use_full_cache'] = FALSE
;
355 // Enable core preprocessing if requested.
356 if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 && user_access('bypass advanced aggregation')) {
357 $conf['preprocess_css'] = TRUE
;
358 $conf['preprocess_js'] = TRUE
;
359 $conf['advagg_use_full_cache'] = FALSE
;
362 // Disable ctools_ajax_page_preprocess() if this functionality is available.
363 if (variable_get('advagg_enabled', ADVAGG_ENABLED
) && function_exists('ctools_ajax_run_page_preprocess')) {
364 ctools_ajax_run_page_preprocess(FALSE
);
365 $_advagg['ctools_patched'] = TRUE
;
370 * Implementation of hook_theme_registry_alter().
372 * Make sure our preprocess function runs last for page.
374 * @param $theme_registry
375 * The existing theme registry data structure.
377 function advagg_theme_registry_alter(&$theme_registry) {
379 if (isset($theme_registry['page'])) {
380 // If jquery_update's preprocess function is there already, remove it.
381 if (module_exists('jquery_update') && $key = array_search('jquery_update_preprocess_page', $theme_registry['page']['preprocess functions'])) {
382 unset($theme_registry['page']['preprocess functions'][$key]);
385 // If ctools hasn't been patched remove it from getting pre-processed.
386 if ( !empty($_advagg['ctools_patched'])
387 && module_exists('ctools')
388 && $key = array_search('ctools_ajax_page_preprocess', $theme_registry['page']['preprocess functions'])
390 unset($theme_registry['page']['preprocess functions'][$key]);
393 // Add our own preprocessing function to the end of the array.
394 $theme_registry['page']['preprocess functions'][] = 'advagg_processor';
396 // If labjs's is enabled, move it to the bottom.
397 if (module_exists('labjs') && $key = array_search('labjs_preprocess_page', $theme_registry['page']['preprocess functions'])) {
398 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
399 unset($theme_registry['page']['preprocess functions'][$key]);
402 // If designkit is enabled, move it to the bottom.
403 if (module_exists('designkit') && $key = array_search('designkit_preprocess_page', $theme_registry['page']['preprocess functions'])) {
404 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
405 unset($theme_registry['page']['preprocess functions'][$key]);
408 // If conditional styles is enabled, move it to the bottom.
409 if (module_exists('conditional_styles') && $key = array_search('conditional_styles_preprocess_page', $theme_registry['page']['preprocess functions'])) {
410 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
411 unset($theme_registry['page']['preprocess functions'][$key]);
417 * Get the CSS & JS path for advagg.
420 * reset the static variables.
422 * array($css_path, $js_path)
424 function advagg_get_root_files_dir($reset = FALSE
) {
425 static
$css_path = '';
426 static
$js_path = '';
432 if (!empty($css_path) && !empty($js_path)) {
433 return array($css_path, $js_path);
436 $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC
) == FILE_DOWNLOADS_PUBLIC
);
437 if (!$public_downloads) {
438 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR
);
440 if (empty($custom_path)) {
441 $file_path = file_directory_path();
442 $css_path = file_create_path($file_path .
'/advagg_css');
443 $js_path = file_create_path($file_path .
'/advagg_js');
444 return array($css_path, $js_path);
446 file_check_directory($custom_path, FILE_CREATE_DIRECTORY
);
449 if (!variable_get('advagg_no_conf_path', FALSE
)) {
450 $conf_path = conf_path();
451 if (is_link($conf_path)) {
452 $path = readlink($conf_path);
454 $conf_path = str_replace("\\", '/', $conf_path);
455 $conf_path = explode('/', $conf_path);
456 $conf_path = array_pop($conf_path);
457 $custom_path = $custom_path .
'/' .
$conf_path;
459 file_check_directory($custom_path, FILE_CREATE_DIRECTORY
);
461 $css_path = $custom_path .
'/advagg_css';
462 $js_path = $custom_path .
'/advagg_js';
463 file_check_directory($css_path, FILE_CREATE_DIRECTORY
);
464 file_check_directory($js_path, FILE_CREATE_DIRECTORY
);
465 return array($css_path, $js_path);
469 * Merge 2 css arrays together.
478 function advagg_merge_css($array1, $array2) {
479 if (!empty($array2)) {
480 foreach ($array2 as
$media => $types) {
481 foreach ($types as
$type => $files) {
482 foreach ($files as
$file => $preprocess) {
483 $array1[$media][$type][$file] = $preprocess;
492 * Merge 2 css arrays together.
501 function advagg_merge_inline_css($array1, $array2) {
502 foreach ($array2 as
$media => $types) {
503 foreach ($types as
$type => $blobs) {
504 foreach ($blobs as
$prefix => $data) {
505 foreach ($data as
$suffix => $blob) {
506 $array1[$media][$type][$prefix][$suffix] = $blob;
515 * Remove .less files from the array.
520 function advagg_css_array_fixer(&$css_func) {
521 if (!module_exists('less')) {
525 // Remove '.css.less' files from the stack.
526 foreach ($css_func as
$k => $v) {
527 foreach ($v as
$ke => $va) {
528 foreach ($va as
$file => $preprocess) {
529 if (substr($file, -5) === '.less') {
530 unset($css_func[$k][$ke][$file]);
538 * Remove css files from the array if there is a color module equivalent of it.
543 function advagg_color_css(&$css) {
544 if (!module_exists('color') || empty($css['all']['theme'])) {
547 $file_path = file_directory_path();
549 // Find all color css files.
551 foreach ($css['all']['theme'] as
$file => $preprocess) {
552 if (strpos($file, $file_path) === FALSE
) {
555 if (strpos($file, '/color/') === FALSE
) {
558 $files[basename($file)] = $file;
561 // Return if no color css files are found.
566 // Remove css files from the stack if there is a color variant of it.
567 foreach ($css['all']['theme'] as
$file => $preprocess) {
568 $basename = basename($file);
569 if (isset($files[$basename]) && !in_array($file, $files)) {
570 unset($css['all']['theme'][$file]);
576 * See if a string ends with a substring.
579 * The main string being compared.
581 * The secondary string being compared.
585 function advagg_string_ends_with($haystack, $needle) {
586 // Define substr_compare if it doesn't exist (PHP 4 fix).
587 if (!function_exists('substr_compare')) {
589 * Binary safe comparison of two strings from an offset, up to length
592 * Compares main_str from position offset with str up to length characters.
593 * @see http://php.net/substr-compare#53084
596 * The main string being compared.
598 * The secondary string being compared.
600 * The start position for the comparison. If negative, it starts counting
601 * from the end of the string.
603 * The length of the comparison. The default value is the largest of the
604 * length of the str compared to the length of main_str less the offset.
605 * @param $case_insensitivity
606 * If TRUE, comparison is case insensitive.
608 * Returns < 0 if main_str from position offset is less than str, > 0 if
609 * it is greater than str, and 0 if they are equal. If offset is equal to
610 * or greater than the length of main_str or length is set and is less than
611 * 1, substr_compare() prints a warning and returns FALSE.
613 function substr_compare($main_str, $str, $offset, $length = NULL
, $case_insensitivity = FALSE
) {
614 $offset = (int) $offset;
616 // Throw a warning because the offset is invalid
617 if ($offset >= strlen($main_str)) {
618 trigger_error('The start position cannot exceed initial string length.', E_USER_WARNING
);
622 // We are comparing the first n-characters of each string, so let's use the PHP function to do it
623 if ($offset == 0 && is_int($length) && $case_insensitivity === TRUE
) {
624 return strncasecmp($main_str, $str, $length);
627 // Get the substring that we are comparing
628 if (is_int($length)) {
629 $main_substr = substr($main_str, $offset, $length);
630 $str_substr = substr($str, 0, $length);
633 $main_substr = substr($main_str, $offset);
637 // Return a case-insensitive comparison of the two strings
638 if ($case_insensitivity === TRUE
) {
639 return strcasecmp($main_substr, $str_substr);
642 // Return a case-sensitive comparison of the two strings
643 return strcmp($main_substr, $str_substr);
647 $haystack_len = strlen($haystack);
648 $needle_len = strlen($needle);
649 if ($needle_len > $haystack_len) {
652 return substr_compare($haystack, $needle, $haystack_len -$needle_len, $needle_len, TRUE
) === 0;
656 * Implementation of hook_advagg_disable_processor().
658 function advagg_advagg_disable_processor() {
659 // Disable advagg on the configuration page; in case something bad happened.
660 if (isset($_GET['q']) &&
661 ( $_GET['q'] == 'admin/settings/advagg'
662 || $_GET['q'] == 'admin/settings/advagg/config'
663 || $_GET['q'] == 'batch'
671 * Return the server schema (http or https).
676 function advagg_get_server_schema() {
677 return ( (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on')
678 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
679 || (isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] == 'on')
680 ) ?
'https' : 'http';
684 * Process variables for page.tpl.php
687 * The existing theme data structure.
689 function advagg_processor(&$variables) {
690 global $_advagg, $conf;
692 // Invoke hook_advagg_disable_processor
693 $disabled = module_invoke_all('advagg_disable_processor');
696 if (!variable_get('advagg_enabled', ADVAGG_ENABLED
) || in_array(TRUE
, $disabled, TRUE
)) {
697 if (module_exists('jquery_update')) {
698 return jquery_update_preprocess_page($variables);
705 // Do not use the cache if advagg is set in the URL.
706 if (isset($_GET['advagg']) && user_access('bypass advanced aggregation')) {
707 $conf['advagg_use_full_cache'] = FALSE
;
709 // Do not use the cache if the disable cookie is set.
710 $cookie_name = 'AdvAggDisabled';
711 $key = md5(drupal_get_private_key());
712 if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
713 $conf['advagg_use_full_cache'] = FALSE
;
717 $schema = advagg_get_server_schema();
720 $css_var = $variables['css'];
721 $css_orig = $css_var;
722 if (!variable_get('advagg_only_css_from_variables', ADVAGG_ONLY_CSS_FROM_VARIABLES
)) {
723 $css_func = drupal_add_css();
724 advagg_css_array_fixer($css_func);
729 $css = advagg_merge_css($css_var, $css_func);
730 $css_func_inline = advagg_add_css_inline();
731 if (!empty($css_func_inline)) {
732 $css = advagg_merge_inline_css($css, $css_func_inline);
734 $css_conditional_styles = !empty($variables['conditional_styles']) ?
$variables['conditional_styles'] : '';
735 $css_styles = $variables['styles'];
736 advagg_color_css($css);
739 if (variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE
)) {
740 // Build the cache ID
741 // md5 of the CSS input
744 // the js rendering function
745 // css/js query string
746 $cid = 'advagg_processor:css:'
747 .
md5(serialize(array($css, $css_conditional_styles))) .
':'
749 .
$_SERVER['HTTP_HOST'] .
':'
750 .
variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION
) .
':'
751 .
substr(variable_get('css_js_query_string', '0'), 0, 1);
752 $cache = cache_get($cid, 'cache_advagg_bundle_reuse');
754 elseif (isset($cid)) {
757 if (!empty($cache->data
)) {
758 $variables['styles'] = $cache->data
;
762 $processed_css = advagg_process_css($css);
763 if (!empty($processed_css)) {
764 $variables['styles'] = $processed_css;
766 $variables['styles'] .
= $css_conditional_styles;
769 if (isset($cid) && variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE
) && lock_acquire($cid)) {
770 cache_set($cid, $variables['styles'], 'cache_advagg_bundle_reuse', CACHE_TEMPORARY
);
777 $js_code['header'] = drupal_add_js(NULL
, NULL
, 'header');
778 if (variable_get('advagg_closure', ADVAGG_CLOSURE
) && !empty($_advagg['closure'])) {
779 $js_code['footer'] = drupal_add_js(NULL
, NULL
, 'footer');
781 $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'));
782 foreach ($variables as
$key => $value) {
783 if (!in_array($key, $skip_keys) && is_string($value) && !empty($value) && !isset($js_code[$key])) {
784 $js_code[$key] = drupal_add_js(NULL
, NULL
, $key);
787 $js_code_orig = $js_code;
790 if (variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE
)) {
791 // Build the cache ID
792 // md5 of the JS input
795 // the js rendering function
796 // css/js query string
797 $cid = 'advagg_processor:js:'
798 .
md5(serialize($js_code)) .
':'
800 .
$_SERVER['HTTP_HOST'] .
':'
801 .
variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION
) .
':'
802 .
substr(variable_get('css_js_query_string', '0'), 0, 1);
803 $cache = cache_get($cid, 'cache_advagg_bundle_reuse');
805 elseif (isset($cid)) {
808 if (!empty($cache->data
)) {
809 $js_code = $cache->data
;
813 advagg_jquery_updater($js_code['header']);
814 $js_code = advagg_process_js($js_code);
817 if (isset($cid) && variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE
) && lock_acquire($cid)) {
818 cache_set($cid, $js_code, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY
);
823 // Place JS in the correct places.
824 foreach ($js_code as
$key => $value) {
825 if ($key == 'header') {
826 $variables['scripts'] = $value;
828 elseif ($key == 'footer' && variable_get('advagg_closure', ADVAGG_CLOSURE
) && !empty($_advagg['closure'])) {
829 $variables['closure'] .
= $value;
832 $variables[$key] .
= $value;
836 // Send requests to server if async enabled.
837 advagg_async_send_http_request();
839 // Write debug info to watchdog if debugging enabled.
840 if (variable_get('advagg_debug', ADVAGG_DEBUG
)) {
842 'css_before_vars' => $css_orig,
843 'css_before_function' => $css_func,
844 'css_before_styles' => $css_styles,
845 'css_before_inline' => $css_func_inline,
846 'css_before_conditional_styles' => $css_conditional_styles,
847 'css_merged' => $css,
848 'css_after' => $processed_css,
849 'js_before' => $js_code_orig,
850 'js_after' => $js_code,
852 $data['runtime'] = isset($_advagg['debug']) ?
$_advagg['debug'] : FALSE
;
853 $data = str_replace(' ', ' ', nl2br(htmlentities(iconv('utf-8', 'utf-8//IGNORE', print_r($data, TRUE
)), ENT_QUOTES
, 'UTF-8')));
854 watchdog('advagg', 'Debug info: !data', array('!data' => $data), WATCHDOG_DEBUG
);
859 * Special handling for jquery update.
862 * List of files in the header
864 function advagg_jquery_updater(&$js) {
865 if (!module_exists('jquery_update') || !variable_get('jquery_update_replace', TRUE
) || empty($js)) {
869 // Replace jquery.js first.
870 $new_jquery = array(jquery_update_jquery_path() => $js['core']['misc/jquery.js']);
871 $js['core'] = array_merge($new_jquery, $js['core']);
872 unset($js['core']['misc/jquery.js']);
874 // Loop through each of the required replacements.
875 $replacement_path = drupal_get_path('module', 'jquery_update') .
'/replace/';
876 foreach (jquery_update_get_replacements() as
$type => $replacements) {
877 foreach ($replacements as
$find => $replace) {
878 // If the file to replace is loaded on this page...
879 if (isset($js[$type][$find])) {
880 // Create a new entry for the replacement file, and unset the original one.
881 $replace = $replacement_path .
$replace;
882 $js[$type][$replace] = $js[$type][$find];
883 unset($js[$type][$find]);
890 * Given a list of files; return back the aggregated filename.
893 * List of files in the proposed bundle.
897 * (optional) Counter value.
899 * (optional) Bundle's machine name.
901 * Aggregated filename.
903 function advagg_get_filename($files, $filetype, $counter = FALSE
, $bundle_md5 = '') {
904 if (empty($files) || empty($filetype)) {
909 $filenames = array();
912 if (empty($bundle_md5)) {
914 $schema = advagg_get_server_schema();
915 $bundle_md5 = md5($schema .
implode('', $files));
918 // Record root request in db.
919 // Get counter if there.
920 if (empty($counter)) {
921 $counter = db_result(db_query("SELECT counter FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5));
923 // If this is a brand new bundle then insert file/bundle info into database.
924 if ($counter === FALSE
) {
926 advagg_insert_bundle_db($files, $filetype, $bundle_md5, TRUE
);
928 // If bundle should be root and is not, then make it root.
929 // Refresh timestamp if older then 12 hours.
930 $row = db_fetch_array(db_query("SELECT root, timestamp FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5));
931 if ($row['root'] === 0 || time() - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL
)) {
932 db_query("UPDATE {advagg_bundles} SET root = '1', timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5);
936 // Set original array.
937 $filenames[] = array(
938 'filetype' => $filetype,
940 'counter' => $counter,
941 'bundle_md5' => $bundle_md5,
944 // Invoke hook_advagg_filenames_alter() to give installed modules a chance to
945 // alter filenames. One to many relationships come to mind.
946 // Do not run alter if MD5 was given, we want to generate that file only in
947 // this special case.
949 // Force counter to be looked up later.
950 $filenames[0]['counter'] = FALSE
;
951 drupal_alter('advagg_filenames', $filenames);
954 // Write to DB if needed and create filenames.
957 if (variable_get('advagg_debug', ADVAGG_DEBUG
)) {
958 $_advagg['debug']['get_filename_post_alter'][] = array(
959 'key' => $bundle_md5,
960 'filenames' => $filenames,
964 // Get all counters at once
966 foreach ($filenames as
$key => $values) {
967 if (empty($values['counter'])) {
968 $counters[$key] = $values['bundle_md5'];
971 $result = advagg_db_multi_select_in('advagg_bundles', 'bundle_md5', "'%s'", $counters, array('counter', 'bundle_md5'), 'GROUP BY bundle_md5');
972 while ($row = db_fetch_array($result)) {
973 $key = array_search($row['bundle_md5'], $counters);
974 if (empty($filenames[$key]['counter']) && $filenames[$key]['counter'] !== 0) {
975 $filenames[$key]['counter'] = intval($row['counter']);
979 foreach ($filenames as
$values) {
980 // Get info from array.
981 $filetype = $values['filetype'];
982 $files = $values['files'];
983 $counter = $values['counter'];
984 $bundle_md5 = $values['bundle_md5'];
986 // See if a JS bundle exists that already has the same files in it, just in a
988 // if ($filetype == 'js' && $run_alter) {
989 // advagg_find_existing_bundle($files, $bundle_md5);
992 // Do not add the same bundle twice.
993 if (isset($used_md5[$bundle_md5])) {
996 $used_md5[$bundle_md5] = TRUE
;
998 // If this is a brand new bundle then insert file/bundle info into database.
999 if (empty($counter) && $counter !== 0) {
1001 advagg_insert_bundle_db($files, $filetype, $bundle_md5, FALSE
);
1004 // Prefix filename to prevent blocking by firewalls which reject files
1005 // starting with "ad*".
1007 'filename' => advagg_build_filename($filetype, $bundle_md5, $counter),
1009 'bundle_md5' => $bundle_md5,
1012 // Refresh timestamp if older then 12 hours.
1013 $row = db_fetch_array(db_query("SELECT timestamp FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5));
1014 if (time() - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL
)) {
1015 db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5);
1023 * Get a bundle from the cache & verify it is good.
1025 * @param $cached_data_key
1026 * cache key for the cache_advagg_bundle_reuse table.
1027 * @param $debug_name
1028 * Name to output in the array if debugging is enabled.
1030 * data from the cache.
1032 function advagg_cached_bundle_get($cached_data_key, $debug_name) {
1034 $data = cache_get($cached_data_key, 'cache_advagg_bundle_reuse');
1035 if (!empty($data->data
)) {
1036 $data = $data->data
;
1037 $bundle_contents = array();
1040 if (variable_get('advagg_debug', ADVAGG_DEBUG
)) {
1041 // Verify cached data is good.
1042 foreach ($data as
$filename => $extra) {
1043 if (is_numeric($filename)) {
1046 // Get md5 from aggregated filename.
1047 $b_md5 = explode('/', $filename);
1048 $b_md5 = explode('_', array_pop($b_md5));
1051 // Lookup bundle and make sure it is valid.
1052 if (!empty($b_md5)) {
1053 list($b_filetype, $b_files) = advagg_get_files_in_bundle($b_md5);
1054 $bundle_contents[$filename] = $b_files;
1055 if (empty($b_files)) {
1061 $_advagg['debug'][$debug_name][] = array(
1062 'key' => $cached_data_key,
1064 'bundle_contents' => $bundle_contents,
1075 * Given a list of files, see if a bundle already exists containing all of those
1076 * files. If in strict mode then the file count has to be the same.
1079 * List of files in the proposed bundle.
1080 * @param $bundle_md5
1081 * Bundle's machine name.
1083 function advagg_find_existing_bundle(&$files, &$bundle_md5) {
1084 // Sort files for better cache hits.
1085 $temp_files = $files;
1087 $schema = advagg_get_server_schema();
1088 $cached_data_key = 'advagg_existing_' .
md5($schema .
implode('', $temp_files));
1090 // Try cache first; cache table is cache_advagg_bundle_reuse. string is debug name.
1091 $cached_data = advagg_cached_bundle_get($cached_data_key, 'advagg_find_existing_bundle');
1092 if (!empty($cached_data)) {
1093 $files = $cached_data[0]['files'];
1094 $bundle_md5 = $cached_data[0]['bundle_md5'];
1098 // Build union query.
1099 $query = 'SELECT root.bundle_md5 FROM {advagg_bundles} AS root';
1104 foreach ($files as
$filename) {
1105 // Use alpha for table aliases; numerics do not work.
1106 $key = strtr($counter, '01234567890', 'abcdefghij');
1108 $joins[$key] = "\nINNER JOIN {advagg_bundles} AS $key USING(bundle_md5)\n";
1109 if ($counter == 0) {
1110 $wheres[$key] = "WHERE $key.filename_md5 = '%s'";
1113 $wheres[$key] = "AND $key.filename_md5 = '%s'";
1115 $args[$key] = md5($filename);
1118 $query .
= implode("\n", $joins);
1119 $query .
= implode("\n", $wheres);
1120 $query .
= ' GROUP BY bundle_md5';
1122 // Find matching bundles and select first good one.
1123 $files_count = count($files);
1124 $results = db_query($query, $args);
1125 while ($new_md5 = db_result($results)) {
1126 $count = db_result(db_query("SELECT count(*) FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $new_md5));
1127 // Make sure bundle has the same number of files if using strict matching.
1128 if (!empty($count) && $count == $files_count) {
1129 $bundle_md5 = $new_md5;
1130 $data = array(array(
1132 'bundle_md5' => $bundle_md5,
1134 cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY
);
1141 * Build the filename.
1147 * @param $bundle_md5
1148 * Bundle's machine name.
1150 function advagg_build_filename($filetype, $bundle_md5, $counter) {
1151 return $filetype .
'_' .
$bundle_md5 .
'_' .
$counter .
'.' .
$filetype;
1155 * Insert info into the advagg_files and advagg_bundles database.
1158 * List of files in the proposed bundle.
1161 * @param $bundle_md5
1162 * Bundle's machine name.
1164 * Is this a root bundle.
1166 function advagg_insert_bundle_db($files, $filetype, $bundle_md5, $root) {
1167 $lock_name = 'advagg_insert_bundle_db' .
$bundle_md5;
1168 if (!lock_acquire($lock_name)) {
1169 // If using async, wait before returning to give the other request time
1171 if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE
) < 2) {
1172 lock_wait($lock_name);
1177 // Double check that the bundle doesn't exist now that we are in a lock.
1178 $bundle_exists = db_result(db_query("SELECT 1 FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5));
1179 if ($bundle_exists) {
1180 lock_release($lock_name);
1184 foreach ($files as
$order => $filename) {
1185 $filename_md5 = md5($filename);
1187 // Insert file into the advagg_files table if it doesn't exist.
1188 $checksum = db_result(db_query("SELECT checksum FROM {advagg_files} WHERE filename_md5 = '%s'", $filename_md5));
1189 if (empty($checksum)) {
1190 $checksum = advagg_checksum($filename);
1191 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));
1194 // Create the entries in the advagg_bundles table.
1195 db_query("INSERT INTO {advagg_bundles} (bundle_md5, filename_md5, counter, porder, root, timestamp) VALUES ('%s', '%s', '%d', '%d', '%d', '%d')", $bundle_md5, $filename_md5, 0, $order, $root, time());
1198 lock_release($lock_name);
1202 * Save a string to the specified destination. Verify that file size is not zero.
1205 * A string containing the contents of the file.
1207 * A string containing the destination location.
1209 * Boolean indicating if the file save was successful.
1211 function advagg_file_saver($data, $dest, $force, $type) {
1212 // Make sure the tmp folder is ready for use
1213 $tmp = file_directory_temp();
1214 file_check_directory($tmp, FILE_CREATE_DIRECTORY
);
1216 // Create the JS file.
1217 $file_save_data = 'file_save_data';
1218 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR
);
1219 if (!empty($custom_path)) {
1220 $file_save_data = 'advagg_file_save_data';
1223 if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE
)) {
1227 // Make sure filesize is not zero.
1228 advagg_clearstatcache(TRUE
, $dest);
1229 if (@
filesize($dest) == 0 && !empty($data)) {
1230 if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE
)) {
1233 advagg_clearstatcache(TRUE
, $dest);
1234 if (@
filesize($dest) == 0 && !empty($data)) {
1235 // Filename is bad, create a new one next time.
1241 if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION
) && extension_loaded('zlib')) {
1242 $gzip_dest = $dest .
'.gz';
1243 advagg_clearstatcache(TRUE
, $gzip_dest);
1244 if (!file_exists($gzip_dest) || $force) {
1245 $gzip_data = gzencode($data, 9, FORCE_GZIP
);
1246 if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE
)) {
1250 // Make sure filesize is not zero.
1251 advagg_clearstatcache(TRUE
, $gzip_dest);
1252 if (@
filesize($gzip_dest) == 0 && !empty($gzip_data)) {
1253 if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE
)) {
1256 advagg_clearstatcache(TRUE
, $gzip_dest);
1257 if (@
filesize($gzip_dest) == 0 && !empty($gzip_data)) {
1258 // Filename is bad, create a new one next time.
1259 file_delete($gzip_dest);
1266 // Make sure .htaccess file exists.
1267 advagg_htaccess_check_generate($dest);
1269 cache_set($dest, time(), 'cache_advagg', CACHE_PERMANENT
);
1276 * ***MODIFIED CORE FUNCTIONS BELOW***
1278 * @see file_save_data()
1284 * Save a string to the specified destination.
1286 * @see file_save_data()
1288 * @param $data A string containing the contents of the file.
1289 * @param $dest A string containing the destination location.
1290 * @param $replace Replace behavior when the destination file already exists.
1291 * - FILE_EXISTS_REPLACE - Replace the existing file
1292 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
1293 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1295 * @return A string containing the resulting filename or 0 on error
1297 function advagg_file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME
) {
1298 $temp = file_directory_temp();
1299 // On Windows, tempnam() requires an absolute path, so we use realpath().
1300 $file = tempnam(realpath($temp), 'file');
1301 if (!$fp = fopen($file, 'wb')) {
1302 drupal_set_message(t('The file could not be created.'), 'error');
1308 if (!advagg_file_move($file, $dest, $replace)) {
1316 * Moves a file to a new location.
1320 * - Checks if $source and $dest are valid and readable/writable.
1321 * - Performs a file move if $source is not equal to $dest.
1322 * - If file already exists in $dest either the call will error out, replace the
1323 * file or rename the file based on the $replace parameter.
1326 * Either a string specifying the file location of the original file or an
1327 * object containing a 'filepath' property. This parameter is passed by
1328 * reference and will contain the resulting destination filename in case of
1331 * A string containing the directory $source should be copied to. If this
1332 * value is omitted, Drupal's 'files' directory will be used.
1334 * Replace behavior when the destination file already exists.
1335 * - FILE_EXISTS_REPLACE: Replace the existing file.
1336 * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
1338 * - FILE_EXISTS_ERROR: Do nothing and return FALSE.
1340 * TRUE for success, FALSE for failure.
1342 function advagg_file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME
) {
1343 $path_original = is_object($source) ?
$source->filepath
: $source;
1345 if (advagg_file_copy($source, $dest, $replace)) {
1346 $path_current = is_object($source) ?
$source->filepath
: $source;
1348 if ($path_original == $path_current || file_delete($path_original)) {
1351 drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error');
1357 * Copies a file to a new location.
1361 * This is a powerful function that in many ways performs like an advanced
1362 * version of copy().
1363 * - Checks if $source and $dest are valid and readable/writable.
1364 * - Performs a file copy if $source is not equal to $dest.
1365 * - If file already exists in $dest either the call will error out, replace the
1366 * file or rename the file based on the $replace parameter.
1369 * Either a string specifying the file location of the original file or an
1370 * object containing a 'filepath' property. This parameter is passed by
1371 * reference and will contain the resulting destination filename in case of
1374 * A string containing the directory $source should be copied to. If this
1375 * value is omitted, Drupal's 'files' directory will be used.
1377 * Replace behavior when the destination file already exists.
1378 * - FILE_EXISTS_REPLACE: Replace the existing file.
1379 * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
1381 * - FILE_EXISTS_ERROR: Do nothing and return FALSE.
1383 * TRUE for success, FALSE for failure.
1385 function advagg_file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME
) {
1386 $directory = dirname($dest);
1388 // Process a file upload object.
1389 if (is_object($source)) {
1391 $source = $file->filepath
;
1393 $basename = $file->filename
;
1397 $source = realpath($source);
1398 advagg_clearstatcache(TRUE
, $source);
1399 if (!file_exists($source)) {
1400 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');
1404 // If the destination file is not specified then use the filename of the source file.
1405 $basename = basename($dest);
1406 $basename = $basename ?
$basename : basename($source);
1407 $dest = $directory .
'/' .
$basename;
1409 // Make sure source and destination filenames are not the same, makes no sense
1410 // to copy it if they are. In fact copying the file will most likely result in
1411 // a 0 byte file. Which is bad. Real bad.
1412 if ($source != realpath($dest)) {
1413 if (!$dest = file_destination($dest, $replace)) {
1414 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');
1418 // Perform the replace operation. Since there could be multiple processes
1419 // writing to the same file, the best option is to create a temporary file in
1420 // the same directory and then rename it to the destination. A temporary file
1421 // is needed if the directory is mounted on a separate machine; thus ensuring
1422 // the rename command stays local.
1424 if ($replace == FILE_EXISTS_REPLACE
) {
1425 // Get a temporary filename in the destination directory.
1426 $temporary_file = tempnam(dirname($dest), 'file');
1427 // Place contents in the temporary file.
1428 if ($temporary_file && @
copy($source, $temporary_file)) {
1429 if (!$result = @
rename($temporary_file, $dest)) {
1430 // Unlink and try again for windows. Rename on windows does not replace
1431 // the file if it already exists.
1433 $result = @
rename($temporary_file, $dest);
1435 // Remove temporary_file if rename failed.
1437 @
unlink($temporary_file);
1441 // Perform the copy operation.
1443 $result = @
copy($source, $dest);
1445 if ($result === FALSE
) {
1446 drupal_set_message(t('The selected file %file could not be copied. ' .
$dest, array('%file' => $source)), 'error');
1450 // Give everyone read access so that FTP'd users or
1451 // non-webserver users can see/read these files,
1452 // and give group write permissions so group members
1453 // can alter files uploaded by the webserver.
1454 @
chmod($dest, 0664);
1457 if (isset($file) && is_object($file)) {
1458 $file->filename
= $basename;
1459 $file->filepath
= $dest;
1466 return 1; // Everything went ok.
1470 * Generate a checksum for a given filename.
1477 function advagg_checksum($filename) {
1478 advagg_clearstatcache(TRUE
, $filename);
1479 if (file_exists($filename)) {
1480 $mode = variable_get('advagg_checksum_mode', ADVAGG_CHECKSUM_MODE
);
1481 if ($mode == 'mtime') {
1482 $checksum = @
filemtime($filename);
1483 if ($checksum === FALSE
) {
1485 advagg_clearstatcache(TRUE
, $filename);
1486 $checksum = @
filemtime($filename);
1487 // Use md5 as a last option.
1488 if ($checksum === FALSE
) {
1489 $checksum = md5(file_get_contents($filename));
1493 elseif ($mode = 'md5') {
1494 $checksum = md5(file_get_contents($filename));
1504 * See if this bundle has been built.
1509 * Boolean indicating if the bundle already exists.
1511 function advagg_bundle_built($filepath) {
1512 // Don't use the cache if not selected.
1513 if (!variable_get('advagg_bundle_built_mode', ADVAGG_BUNDLE_BUILT_MODE
)) {
1514 advagg_clearstatcache(TRUE
, $filepath);
1515 return file_exists($filepath);
1518 $data = advagg_get_bundle_from_filename(basename($filepath));
1519 if (is_array($data)) {
1520 list($type, $md5, $counter) = $data;
1526 $data = cache_get($filepath, 'cache_advagg');
1527 if (isset($data->data
)) {
1528 // Refresh timestamp if older then 12 hours.
1529 if (time() - $data->data
> variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL
)) {
1530 cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT
);
1531 db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5);
1536 // If not in cache check disk.
1537 advagg_clearstatcache(TRUE
, $filepath);
1538 if (file_exists($filepath)) {
1539 if (@
filesize($filepath) == 0) {
1546 // File existed on disk; place in cache.
1547 cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT
);
1548 db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5);
1552 function advagg_get_bundle_from_filename($filename) {
1553 // Verify requested filename has the correct pattern.
1554 if (!preg_match('/^(j|cs)s_[0-9a-f]{32}_\d+\.(j|cs)s$/', $filename)) {
1555 return t('Wrong Pattern.');
1559 $type = substr($filename, 0, strpos($filename, '_'));
1562 $ext = substr($filename, strpos($filename, '.', 37) + 1);
1564 // Make sure extension is the same as the type.
1565 if ($ext != $type) {
1566 return t('Type does not match extension.');
1569 // Extract info from wanted filename.
1570 if ($type == 'css') {
1571 $md5 = substr($filename, 4, 32);
1572 $counter = substr($filename, 37, strpos($filename, '.', 38) -37);
1574 elseif ($type == 'js') {
1575 $md5 = substr($filename, 3, 32);
1576 $counter = substr($filename, 36, strpos($filename, '.', 37) -36);
1579 return t('Wrong file type.');
1582 return array($type, $md5, $counter);
1586 * Implementation of hook_flush_caches().
1588 function advagg_flush_caches() {
1589 // Try to allocate enough time to flush the cache
1590 if (function_exists('set_time_limit')) {
1591 @
set_time_limit(240);
1595 // Only one advagg cache flusher can run at a time.
1596 if (!lock_acquire('advagg_flush_caches')) {
1600 // Only run code below if the advagg db tables exist.
1601 if (!db_table_exists('advagg_files')) {
1602 return array('cache_advagg_bundle_reuse');
1605 // Find files that have changed.
1606 $needs_refreshing = array();
1607 $results = db_query("SELECT * FROM {advagg_files}");
1608 while ($row = db_fetch_array($results)) {
1609 $checksum = advagg_checksum($row['filename']);
1610 // Let other modules see if the bundles needs to be rebuilt.
1611 // hook_advagg_files_table
1612 // Return TRUE in order to increment the counter.
1613 $hook_results = module_invoke_all('advagg_files_table', $row, $checksum);
1615 // Check each return value; see if an update is needed.
1617 if (!empty($hook_results)) {
1618 foreach ($hook_results as
$update) {
1619 if ($update === TRUE
) {
1625 // Increment the counter if needed and mark file for bundle refreshment.
1626 if ($checksum != $row['checksum'] || $update == TRUE
) {
1627 $needs_refreshing[$row['filename_md5']] = $row['filename'];
1628 // Update checksum; increment counter.
1629 db_query("UPDATE {advagg_files} SET checksum = '%s', counter = counter + 1 WHERE filename_md5 = '%s'", $checksum, $row['filename_md5']);
1635 foreach ($needs_refreshing as
$filename_md5 => $filename) {
1636 $results = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s'", $filename_md5);
1637 while ($row = db_fetch_array($results)) {
1638 $bundles[$row['bundle_md5']] = $row['bundle_md5'];
1642 foreach ($bundles as
$bundle_md5) {
1643 // Increment Counter
1644 db_query("UPDATE {advagg_bundles} SET counter = counter + 1, timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5);
1646 if (variable_get('advagg_rebuild_on_flush', ADVAGG_REBUILD_ON_FLUSH
)) {
1647 // Rebuild bundles on shutdown in the background. This is needed so that
1648 // the cache_advagg_bundle_reuse table has been cleared.
1649 register_shutdown_function('advagg_rebuild_bundle', $bundle_md5, '', TRUE
);
1652 $_advagg['bundles'] = $bundles;
1653 $_advagg['files'] = $needs_refreshing;
1655 // Garbage collection
1656 list($css_path, $js_path) = advagg_get_root_files_dir();
1657 file_scan_directory($css_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE
);
1658 file_scan_directory($js_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE
);
1660 lock_release('advagg_flush_caches');
1661 return array('cache_advagg_bundle_reuse');
1667 * @param $bundle_md5
1668 * Bundle's machine name.
1672 * Rebuild even if file already exists.
1674 function advagg_rebuild_bundle($bundle_md5, $counter = '', $force = FALSE
) {
1675 global $conf, $_advagg;
1676 list($filetype, $files) = advagg_get_files_in_bundle($bundle_md5);
1678 $conf['advagg_async_generation'] = FALSE
;
1679 $good = advagg_css_js_file_builder($filetype, $files, '', $counter, $force, $bundle_md5);
1681 watchdog('advagg', 'This bundle could not be generated correctly. Bundle MD5: %md5', array('%md5' => $bundle_md5));
1684 $_advagg['rebuilt'][] = $bundle_md5;
1690 * Get list of files and the filetype given a bundle md5.
1692 * @param $bundle_md5
1693 * Bundle's machine name.
1695 * array ($filetype, $files)
1697 function advagg_get_files_in_bundle($bundle_md5) {
1700 $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);
1701 while ($row = db_fetch_array($results)) {
1702 $files[] = $row['filename'];
1703 $filetype = $row['filetype'];
1706 return array($filetype, $files);
1710 * Callback to delete files modified more than a set time ago.
1713 * name of a file to check how old it is.
1715 function advagg_delete_file_if_stale($filename) {
1716 // Do not process .gz files
1717 if (strpos($filename, '.gz') !== FALSE
) {
1721 $file_last_mod = variable_get('advagg_stale_file_threshold', ADVAGG_STALE_FILE_THRESHOLD
);
1722 $file_last_used = variable_get('advagg_stale_file_last_used_threshold', ADVAGG_STALE_FILE_LAST_USED_THRESHOLD
);
1724 // Default mtime stale file threshold is 6 days.
1725 advagg_clearstatcache(TRUE
, $filename);
1726 if ($now - filemtime($filename) <= $file_last_mod) {
1730 // Check to see if this file is still in use.
1731 $data = cache_get($filename, 'cache_advagg');
1732 if (!empty($data->data
)) {
1733 advagg_clearstatcache(TRUE
, $filename);
1734 $file_last_a = @
fileatime($filename);
1735 $file_last_agz = @
fileatime($filename .
'.gz');
1736 $file_last_a = max($file_last_a, $file_last_agz);
1737 if ($now - $data->data
> $file_last_used && $now - $file_last_a > $file_last_used) {
1738 // Delete file if it hasn't been used in the last 3 days.
1739 file_delete($filename);
1740 file_delete($filename .
'.gz');
1743 // Touch file so we don't check again for another 6 days.
1748 // Delete file if it is not in the cache.
1749 file_delete($filename);
1750 file_delete($filename .
'.gz');
1755 * Get data about a file.
1757 * @param $filename_md5
1760 * data array from database.
1762 function advagg_get_file_data($filename_md5) {
1763 $data = cache_get($filename_md5, 'cache_advagg_files_data');
1764 if (empty($data->data
)) {
1771 * Set data about a file.
1773 * @param $filename_md5
1778 function advagg_set_file_data($filename_md5, $data) {
1779 cache_set($filename_md5, $data, 'cache_advagg_files_data', CACHE_PERMANENT
);
1783 * Given path output uri to that file
1785 * @param $filename_md5
1790 function advagg_build_uri($path) {
1791 static
$hook_file_url_alter = array();
1793 // If the current path is an absolute path, return immediately.
1794 $fragments = parse_url($path);
1795 if (isset($fragments['host'])) {
1799 $original_path = $path;
1801 if (module_exists('cdn')) {
1802 $status = variable_get(CDN_STATUS_VARIABLE
, CDN_DISABLED
);
1803 if ($status == CDN_ENABLED
|| ($status == CDN_TESTING
&& user_access(CDN_PERM_ACCESS_TESTING
))) {
1804 // Alter URL when the file_create_url() patch is not there.
1805 if (variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE
, FALSE
)) {
1806 cdn_file_url_alter($path);
1808 // Use the patched version of file_create_url().
1810 $path = file_create_url($path);
1812 // Return here if the path was changed above.
1813 if (strcmp($original_path, $path) != 0) {
1819 // Other modules besides CDN might want to use hook_file_url_alter.
1820 if (empty($hook_file_url_alter)) {
1821 $hook_file_url_alter = module_implements('file_url_alter');
1823 if (!empty($hook_file_url_alter)) {
1824 $path = file_create_url($path);
1825 // Return here if the path was changed above.
1826 if (strcmp($original_path, $path) != 0) {
1831 // If nothing was altered then use the drupal default.
1832 return base_path() .
$path;
1836 * ***MODIFIED CORE FUNCTIONS BELOW***
1838 * @see drupal_get_css()
1839 * @see drupal_build_css_cache()
1840 * @see drupal_get_js()
1841 * @see drupal_build_js_cache()
1845 * Returns an array of values needed for aggregation
1848 * (optional) Bool indicating that aggregation should be disabled if TRUE.
1850 * (optional) String. js or css.
1852 * array of values to be imported via list() function.
1854 function advagg_process_css_js_prep($noagg = FALSE
, $type = NULL
) {
1856 $preprocess = (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE
!= 'update');
1859 if ($noagg || (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation'))) {
1860 $preprocess = FALSE
;
1861 $conf['advagg_use_full_cache'] = FALSE
;
1865 $cookie_name = 'AdvAggDisabled';
1866 $key = md5(drupal_get_private_key());
1867 if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
1868 $preprocess = FALSE
;
1869 $conf['advagg_use_full_cache'] = FALSE
;
1872 $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC
) == FILE_DOWNLOADS_PUBLIC
);
1873 if (!$public_downloads) {
1874 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR
);
1875 if (!empty($custom_path)) {
1876 $public_downloads = TRUE
;
1881 if ($type == 'js' && !variable_get('advagg_preprocess_js', ADVAGG_PREPROCESS_JS
)) {
1882 $preprocess = FALSE
;
1884 if ($type == 'css' && !variable_get('advagg_preprocess_css', ADVAGG_PREPROCESS_CSS
)) {
1885 $preprocess = FALSE
;
1889 // A dummy query-string is added to filenames, to gain control over
1890 // browser-caching. The string changes on every update or full cache
1891 // flush, forcing browsers to load a new copy of the files, as the
1893 $query_string = '?' .
substr(variable_get('css_js_query_string', '0'), 0, 1);
1895 return array($preprocess, $public_downloads, $query_string);
1900 * Returns a themed representation of all stylesheets that should be attached to
1903 * @see drupal_get_css()
1905 * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
1906 * This ensures proper cascading of styles so themes can easily override
1907 * module styles through CSS selectors.
1909 * Themes may replace module-defined CSS files by adding a stylesheet with the
1910 * same filename. For example, themes/garland/system-menus.css would replace
1911 * modules/system/system-menus.css. This allows themes to override complete
1912 * CSS files, rather than specific selectors, when necessary.
1914 * If the original CSS file is being overridden by a theme, the theme is
1915 * responsible for supplying an accompanying RTL CSS file to replace the
1919 * (optional) An array of CSS files. If no array is provided, the default
1920 * stylesheets array is used instead.
1922 * (optional) Bool indicating that aggregation should be disabled if TRUE.
1924 * A string of XHTML CSS tags.
1926 function advagg_process_css($css = NULL
, $noagg = FALSE
) {
1928 $original_css = $css;
1930 $css = drupal_add_css();
1937 list($preprocess_css, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg, 'css');
1939 // Invoke hook_advagg_css_pre_alter() to give installed modules a chance to
1940 // modify the data in the $javascript array if necessary.
1941 drupal_alter('advagg_css_pre', $css, $preprocess_css, $public_downloads);
1944 $external_no_preprocess = array();
1945 $module_no_preprocess = array();
1946 $output_no_preprocess = array();
1947 $output_preprocess = array();
1948 $theme_no_preprocess = array();
1949 $inline_no_preprocess = array();
1950 $files_included = array();
1951 $files_aggregates_included = array();
1952 $inline_included = array();
1955 foreach ($css as
$media => $types) {
1956 // Setup some variables
1957 $files_included[$media] = array();
1958 $files_aggregates_included[$media] = array();
1959 $inline_included[$media] = array();
1961 // If CSS preprocessing is off, we still need to output the styles.
1962 // Additionally, go through any remaining styles if CSS preprocessing is on
1963 // and output the non-cached ones.
1964 foreach ($types as
$type => $files) {
1965 if ($type == 'module') {
1966 // Setup theme overrides for module styles.
1967 $theme_styles = array();
1968 foreach (array_keys($css[$media]['theme']) as
$theme_style) {
1969 $theme_styles[] = basename($theme_style);
1972 foreach ($types[$type] as
$file => $preprocess) {
1973 // If the theme supplies its own style using the name of the module
1974 // style, skip its inclusion. This includes any RTL styles associated
1975 // with its main LTR counterpart.
1976 if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) {
1977 // Unset the file to prevent its inclusion when CSS aggregation is enabled.
1978 unset($types[$type][$file]);
1981 // If a CSS file is not to be preprocessed and it's an inline CSS blob
1982 // it needs to *always* appear at the *very bottom*.
1983 if ($type == 'inline') {
1984 if (is_array($preprocess)) {
1985 foreach ($preprocess as
$suffix => $blob) {
1986 $blob = advagg_drupal_load_stylesheet_content($blob, $preprocess);
1987 // Invoke hook_advagg_css_inline_alter() to give installed modules
1988 // a chance to modify the contents of $blob if necessary.
1989 drupal_alter('advagg_css_inline', $blob);
1991 $inline_no_preprocess[] = array(
1995 'suffix' => $suffix,
1997 $inline_included[$media][] = $blob;
2000 // Unset to prevent its inclusion.
2001 unset($types[$type][$file]);
2007 // Invoke hook_advagg_css_extra_alter() to
2008 // give installed modules a chance to modify the prefix or suffix for a
2010 $values = array($file, NULL
, $prefix, $suffix);
2011 drupal_alter('advagg_css_extra', $values);
2012 list($file, $null, $prefix, $suffix) = $values;
2014 if ($type == 'inline') {
2015 $file = advagg_drupal_load_stylesheet_content($file, $preprocess);
2016 // Invoke hook_advagg_css_inline_alter() to give installed modules a
2017 // chance to modify the contents of $file if necessary.
2018 drupal_alter('advagg_css_inline', $file);
2020 $inline_no_preprocess[] = array(
2023 'prefix' => $prefix,
2024 'suffix' => $suffix,
2026 $inline_included[$media][] = $file;
2027 // Unset to prevent its inclusion.
2028 unset($types[$type][$file]);
2032 // If a CSS file is not to be preprocessed and it's an external
2033 // CSS file, it needs to *always* appear at the *very top*,
2034 // regardless of whether preprocessing is on or off.
2035 if ($type == 'external') {
2036 $external_no_preprocess[] = array(
2039 'prefix' => $prefix,
2040 'suffix' => $suffix,
2042 $files_included[$media][$file] = TRUE
;
2043 // Unset the file to prevent its inclusion.
2044 unset($types[$type][$file]);
2048 // Only include the stylesheet if it exists.
2049 if (advagg_file_exists($file)) {
2050 if (!$preprocess || !($public_downloads && $preprocess_css)) {
2053 // Create URI for file.
2054 $file_uri = advagg_build_uri($file) .
$query_string;
2055 $files_included[$media][$file] = $preprocess;
2056 // If a CSS file is not to be preprocessed and it's a module CSS
2057 // file, it needs to *always* appear at the *top*, regardless of
2058 // whether preprocessing is on or off.
2059 if (!$preprocess && $type == 'module') {
2060 $module_no_preprocess[] = array(
2062 'href' => $file_uri,
2063 'prefix' => $prefix,
2064 'suffix' => $suffix,
2067 // If a CSS file is not to be preprocessed and it's a theme CSS
2068 // file, it needs to *always* appear at the *bottom*, regardless of
2069 // whether preprocessing is on or off.
2070 elseif (!$preprocess && $type == 'theme') {
2071 $theme_no_preprocess[] = array(
2073 'href' => $file_uri,
2074 'prefix' => $prefix,
2075 'suffix' => $suffix,
2080 $output_no_preprocess[] = array(
2082 'href' => $file_uri,
2083 'prefix' => $prefix,
2084 'suffix' => $suffix,
2092 if ($public_downloads && $preprocess_css) {
2093 $files_aggregates_included[$media] = $files_included[$media];
2095 foreach ($types as
$type) {
2096 foreach ($type as
$file => $cache) {
2097 if ($cache && advagg_file_exists($file)) {
2099 $files_included[$media][$file] = TRUE
;
2100 unset($files_aggregates_included[$file]);
2104 if (!empty($files)) {
2105 $preprocess_files = advagg_css_js_file_builder('css', $files, $query_string);
2106 if (!empty($preprocess_files)) {
2108 foreach ($preprocess_files as
$preprocess_file => $extra) {
2109 // Empty aggregate, skip
2110 if (empty($preprocess_file)) {
2114 if ($extra !== FALSE
&& is_array($extra)) {
2115 $prefix = $extra['prefix'];
2116 $suffix = $extra['suffix'];
2117 $output_preprocess[] = array(
2119 'href' => advagg_build_uri($preprocess_file),
2120 'prefix' => $prefix,
2121 'suffix' => $suffix,
2123 $files_aggregates_included[$media][$preprocess_file] = $extra;
2132 // Redo with aggregation turned off and return the new value.
2133 watchdog('advagg', 'CSS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR
);
2134 $data = advagg_process_css($original_css, TRUE
);
2141 // Default function called: advagg_unlimited_css_builder
2142 $function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION
);
2143 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);
2147 * Logic to figure out what kind of css tags to use.
2149 * @param $external_no_preprocess
2150 * array of css files ($media, $href, $prefix, $suffix)
2151 * @param $module_no_preprocess
2152 * array of css files ($media, $href, $prefix, $suffix)
2153 * @param $output_no_preprocess
2154 * array of css files ($media, $href, $prefix, $suffix)
2155 * @param $output_preprocess
2156 * array of css files ($media, $href, $prefix, $suffix)
2157 * @param $theme_no_preprocess
2158 * array of css files ($media, $href, $prefix, $suffix)
2159 * @param $inline_no_preprocess
2160 * array of css data to inline ($media, $data)
2161 * @param $inline_included
2162 * array of inline css included. $a[$media][] = $datablob;
2163 * @param $files_included
2164 * array of css files included. $a[$media][] = $filename
2165 * @param $files_aggregates_included
2166 * array of css files & aggregates included. $a[$media][] = $filename
2168 * html for loading the css. html for the head.
2170 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) {
2173 $files = array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess);
2175 // Select method for css html output
2176 if (count($files) < variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD
)) {
2177 advagg_unlimited_css_traditional($files, $styles);
2179 elseif (variable_get('advagg_css_logged_in_ie_detect', ADVAGG_CSS_LOGGED_IN_IE_DETECT
) && $user->uid
!= 0) {
2180 // Detect IE browsers here
2182 if (isset($_SERVER['HTTP_USER_AGENT'])) {
2183 // Strings for testing found via
2184 // http://chrisschuld.com/projects/browser-php-detecting-a-users-browser-from-php/
2185 // Test for v1 - v1.5 IE
2186 // Test for versions > 1.5
2187 // Test for Pocket IE
2188 if ( stristr($_SERVER['HTTP_USER_AGENT'], 'microsoft internet explorer')
2189 || stristr($_SERVER['HTTP_USER_AGENT'], 'msie')
2190 || stristr($_SERVER['HTTP_USER_AGENT'], 'mspie')
2195 // Play Safe and treat as IE if user agent is not set
2201 advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles);
2202 advagg_unlimited_css_import($output_preprocess, $styles);
2203 advagg_unlimited_css_import($theme_no_preprocess, $styles);
2204 advagg_unlimited_css_traditional($inline_no_preprocess, $styles);
2207 advagg_unlimited_css_traditional($files, $styles);
2211 advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles);
2212 advagg_unlimited_css_import($output_preprocess, $styles);
2213 advagg_unlimited_css_import($theme_no_preprocess, $styles);
2214 advagg_unlimited_css_traditional($inline_no_preprocess, $styles);
2221 * Use link tags for CSS
2224 * array of css files ($media, $href, $prefix, $suffix)
2228 function advagg_unlimited_css_traditional($files, &$styles) {
2231 foreach ($files as
$css_file) {
2232 $media = $css_file['media'];
2233 $prefix = empty($css_file['prefix']) ?
'' : $css_file['prefix'] .
"\n";
2234 $suffix = empty($css_file['suffix']) ?
'' : $css_file['suffix'];
2236 // Group prefixes and suffixes.
2237 if (isset($css_file['href'])) {
2238 $href = $css_file['href'];
2239 if ($prefix != $last_prefix) {
2240 $styles .
= $last_suffix .
"\n" .
$prefix .
'<link type="text/css" rel="stylesheet" media="' .
$media .
'" href="' .
$href .
'" />' .
"\n";
2243 $styles .
= '<link type="text/css" rel="stylesheet" media="' .
$media .
'" href="' .
$href .
'" />' .
"\n";
2247 $data = $css_file['data'];
2248 if ($prefix != $last_prefix) {
2249 $styles .
= $last_suffix .
"\n" .
$prefix .
'<style type="text/css" media="' .
$media .
'">' .
"\n" .
$data .
"\n" .
'</style>' .
"\n";
2252 $styles .
= '<style type="text/css" media="' .
$media .
'">' .
"\n" .
$data .
"\n" .
'</style>' .
"\n";
2255 $last_prefix = $prefix;
2256 $last_suffix = $suffix;
2258 $styles .
= empty($last_suffix) ?
'' : $last_suffix .
"\n";
2262 * Use import tags for CSS
2265 * array of css files ($media, $href)
2269 function advagg_unlimited_css_import($files, &$styles) {
2273 foreach ($files as
$css_file) {
2274 $media_new = $css_file['media'];
2275 $href = $css_file['href'];
2276 if ($media_new != $media || $counter > variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD
)) {
2277 if ($media && !empty($import)) {
2278 $styles .
= "\n" .
'<style type="text/css" media="' .
$media .
'">' .
"\n" .
$import .
'</style>';
2282 $media = $media_new;
2284 $import .
= '@import "' .
$href .
'";' .
"\n";
2287 if ($media && !empty($import)) {
2288 $styles .
= "\n" .
'<style type="text/css" media="' .
$media .
'">' .
"\n" .
$import .
'</style>';
2293 * Returns a themed presentation of all JavaScript code for the current page.
2295 * @see drupal_get_js()
2297 * References to JavaScript files are placed in a certain order: first, all
2298 * 'core' files, then all 'module' and finally all 'theme' JavaScript files
2299 * are added to the page. Then, all settings are output, followed by 'inline'
2300 * JavaScript code. If running update.php, all preprocessing is disabled.
2303 * An array with all JavaScript code. Key it the region
2305 * (optional) Bool indicating that aggregation should be disabled if TRUE.
2307 * All JavaScript code segments and includes for the scope as HTML tags.
2309 function advagg_process_js($master_set, $noagg = FALSE
) {
2311 if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE
!= 'update') && function_exists('locale_update_js_files')) {
2312 locale_update_js_files();
2316 list($preprocess_js, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg, 'js');
2317 $advagg_json_encode_function = variable_get('advagg_json_encode_function', 'advagg_drupal_to_js');
2320 foreach ($master_set as
$scope => $javascript) {
2321 if ($scope != 'header' && $scope != 'footer' && empty($javascript)) {
2325 // Invoke hook_advagg_js_pre_alter() to give installed modules a chance to
2326 // modify the data in the $javascript array if necessary.
2327 drupal_alter('advagg_js_pre', $javascript, $preprocess_js, $public_downloads, $scope);
2328 $master_set[$scope] = $javascript;
2331 // Invoke hook_advagg_js_header_footer_alter() to give installed modules a chance to
2332 // modify the data in the header and footer JS if necessary.
2333 drupal_alter('advagg_js_header_footer', $master_set, $preprocess_js, $public_downloads);
2335 foreach ($master_set as
$scope => $javascript) {
2336 if (empty($javascript)) {
2341 $setting_no_preprocess = array();
2342 $inline_no_preprocess = array();
2343 $external_no_preprocess = array();
2344 $output_no_preprocess = array(
2346 'module' => array(),
2349 $output_preprocess = array();
2350 $preprocess_list = array();
2351 $js_settings_array = array();
2352 $inline_included = array();
2353 $files_included = array();
2354 $files_aggregates_included = array();
2357 foreach ($javascript as
$type => $data) {
2362 // Add the prefix and suffix to the info array if not there.
2363 if ($type != 'setting') {
2364 foreach ($data as
&$info) {
2365 $info['prefix'] = isset($info['prefix']) ?
$info['prefix'] : '';
2366 $info['suffix'] = isset($info['suffix']) ?
$info['suffix'] : '';
2368 // $info needs to be unset, otherwise foreach loops below will break.
2374 $data = call_user_func_array('array_merge_recursive', $data);
2375 $js_settings_array[] = $data;
2376 $js_settings = $advagg_json_encode_function($data);
2377 $js_settings = preg_replace(array('/"DRUPAL_JS_RAW\:/', '/\:DRUPAL_JS_RAW"/'), array('', ''), $js_settings);
2378 $setting_no_preprocess[] = 'jQuery.extend(Drupal.settings, ' .
$js_settings .
");";
2382 foreach ($data as
$info) {
2383 // Invoke hook_advagg_js_inline_alter() to give installed modules a
2384 // chance to modify the contents of $info['code'] if necessary.
2385 drupal_alter('advagg_js_inline', $info['code']);
2387 $inline_no_preprocess[] = array($info['code'], $info['defer'], $info['prefix'], $info['suffix']);
2388 $inline_included[] = $info['code'];
2393 foreach ($data as
$path => $info) {
2394 $external_no_preprocess[] = array($path, $info['defer'], $info['prefix'], $info['suffix']);
2395 $files_included[$path] = TRUE
;
2400 // If JS preprocessing is off, we still need to output the scripts.
2401 // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
2402 foreach ($data as
$path => $info) {
2403 if (!$info['preprocess'] || !$public_downloads || !$preprocess_js) {
2404 $output_no_preprocess[$type][] = array(advagg_build_uri($path) .
($info['cache'] ?
$query_string : '?' .
time()), $info['defer'], $info['prefix'], $info['suffix']);
2405 $files_included[$path] = $info['preprocess'];
2408 $preprocess_list[$path] = $info;
2416 // Aggregate any remaining JS files that haven't already been output.
2417 if ($public_downloads && $preprocess_js && count($preprocess_list) > 0) {
2418 $files_aggregates_included = $files_included;
2420 foreach ($preprocess_list as
$path => $info) {
2421 if ($info['preprocess']) {
2423 $files_included[$path] = TRUE
;
2426 if (!empty($files)) {
2427 $preprocess_files = advagg_css_js_file_builder('js', $files, $query_string);
2428 if (!empty($preprocess_files)) {
2430 foreach ($preprocess_files as
$preprocess_file => $extra) {
2431 // Empty aggregate, skip
2432 if (empty($preprocess_file)) {
2436 if ($extra !== FALSE
&& is_array($extra)) {
2437 $prefix = $extra['prefix'];
2438 $suffix = $extra['suffix'];
2439 $output_preprocess[] = array(advagg_build_uri($preprocess_file), $prefix, $suffix);
2440 $files_aggregates_included[$preprocess_file] = $extra;
2449 // Redo with aggregation turned off and return the new value.
2450 watchdog('advagg', 'JS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR
);
2451 $data = advagg_process_js($master_set, TRUE
);
2457 // Default function called: advagg_js_builder
2458 $function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION
);
2459 $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);
2465 * Build and theme JS output for header.
2467 * @param $external_no_preprocess
2468 * array(array($src, $defer, $prefix, $suffix))
2469 * @param $output_preprocess
2470 * array(array($src, $prefix, $suffix))
2471 * @param $output_no_preprocess
2472 * array(array(array($src, $defer, $prefix, $suffix)))
2473 * @param $setting_no_preprocess
2474 * array(array($code))
2475 * @param $inline_no_preprocess
2476 * array(array($code, $defer, $prefix, $suffix))
2479 * @param $js_settings_array
2480 * array of settings used.
2481 * @param $inline_included
2482 * array of inline scripts used.
2483 * @param $files_included
2484 * array of files used.
2485 * @param $files_aggregates_included
2486 * array of files and aggregates used.
2488 * String of themed JavaScript.
2490 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) {
2493 // For inline Javascript to validate as XHTML, all Javascript containing
2494 // XHTML needs to be wrapped in CDATA. To make that backwards compatible
2495 // with HTML 4, we need to comment out the CDATA-tag.
2496 $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
2497 $embed_suffix = "\n//--><!]]>\n";
2499 // Keep the order of JS files consistent as some are preprocessed and others are not.
2500 // Make sure any inline or JS setting variables appear last after libraries have loaded.
2502 if (!empty($external_no_preprocess)) {
2503 foreach ($external_no_preprocess as
$values) {
2504 list($src, $defer, $prefix, $suffix) = $values;
2505 $output .
= $prefix .
'<script type="text/javascript"' .
($defer ?
' defer="defer"' : '') .
' src="' .
$src .
'"></script>' .
$suffix .
"\n";
2509 if (!empty($output_preprocess)) {
2510 foreach ($output_preprocess as
$values) {
2511 list($src, $prefix, $suffix) = $values;
2512 $output .
= $prefix .
'<script type="text/javascript" src="' .
$src .
'"></script>' .
$suffix .
"\n";
2516 foreach ($output_no_preprocess as
$type => $list) {
2517 if (!empty($list)) {
2518 foreach ($list as
$values) {
2519 list($src, $defer, $prefix, $suffix) = $values;
2520 $output .
= $prefix .
'<script type="text/javascript"' .
($defer ?
' defer="defer"' : '') .
' src="' .
$src .
'"></script>' .
$suffix .
"\n";
2525 if (!empty($setting_no_preprocess)) {
2526 foreach ($setting_no_preprocess as
$code) {
2527 $output .
= '<script type="text/javascript">' .
$embed_prefix .
$code .
$embed_suffix .
"</script>\n";
2531 if (!empty($inline_no_preprocess)) {
2532 foreach ($inline_no_preprocess as
$values) {
2533 list($code, $defer, $prefix, $suffix) = $values;
2534 $output .
= $prefix .
'<script type="text/javascript"' .
($defer ?
' defer="defer"' : '') .
'>' .
$embed_prefix .
$code .
$embed_suffix .
'</script>' .
$suffix .
"\n";
2542 * Always return TRUE, used for array_map in advagg_css_js_file_builder().
2544 function advagg_return_true() {
2549 * Disable the page cache if the aggregate is not in the bundle.
2551 function advagg_disable_page_cache() {
2553 $conf['advagg_use_full_cache'] = FALSE
;
2554 if (variable_get('advagg_page_cache_mode', ADVAGG_PAGE_CACHE_MODE
)) {
2555 $conf['cache'] = CACHE_DISABLED
;
2557 // Invoke hook_advagg_disable_page_cache(). Allows 3rd party page cache
2558 // plugins like boost or varnish to not cache this page.
2559 module_invoke_all('advagg_disable_page_cache');
2564 * Aggregate CSS/JS files, putting them in the files directory.
2566 * @see drupal_build_js_cache()
2567 * @see drupal_build_css_cache()
2572 * An array of JS files to aggregate and compress into one file.
2573 * @param $query_string
2574 * (optional) Query string to add on to the file if bundle isn't ready.
2576 * (optional) Counter value.
2578 * (optional) Rebuild even if file already exists.
2580 * (optional) Bundle's machine name.
2582 * array with the filepath as the key and prefix and suffix in another array.
2584 function advagg_css_js_file_builder($type, $files, $query_string = '', $counter = FALSE
, $force = FALSE
, $md5 = '') {
2585 global $_advagg, $base_path;
2588 // Try cache first. When ever the counter changes this cache gets reset.
2589 $schema = advagg_get_server_schema();
2590 $cached_data_key = 'advagg_file_builder_' .
md5($schema .
implode('', array_filter(array_unique($files))));
2592 // Try cache first; cache table is cache_advagg_bundle_reuse.
2593 $cached_data = advagg_cached_bundle_get($cached_data_key, 'file_builder_cache_object');
2594 if (!empty($cached_data)) {
2595 foreach ($cached_data as
$filepath => $values) {
2597 advagg_bundle_built($filepath);
2599 return $cached_data;
2603 list($css_path, $js_path) = advagg_get_root_files_dir();
2604 if ($type == 'js') {
2605 $file_type_path = $js_path;
2607 if ($type == 'css') {
2608 $file_type_path = $css_path;
2611 // Send $files, get filename back
2612 $filenames = advagg_get_filename($files, $type, $counter, $md5);
2613 if (empty($filenames)) {
2618 if (variable_get('advagg_debug', ADVAGG_DEBUG
)) {
2619 $_advagg['debug']['file_builder_get_filenames'][] = array(
2620 'key' => $cached_data_key,
2621 'filenames' => $filenames,
2627 $files_used = array();
2628 foreach ($filenames as
$info) {
2629 $filename = $info['filename'];
2630 $files = $info['files'];
2631 $bundle_md5 = $info['bundle_md5'];
2634 $filepath = $file_type_path .
'/' .
$filename;
2636 // Invoke hook_advagg_js_extra_alter() or hook_advagg_css_extra_alter to
2637 // give installed modules a chance to modify the prefix or suffix for a
2639 $values = array($filename, $bundle_md5, $prefix, $suffix);
2640 drupal_alter('advagg_' .
$type .
'_extra', $values);
2641 list($filename, $bundle_md5, $prefix, $suffix) = $values;
2643 // Check that the file exists & filesize is not zero
2644 $built = advagg_bundle_built($filepath);
2646 if (!$built || $force) {
2647 // Generate on request?
2648 if (variable_get('advagg_async_generation', ADVAGG_ASYNC_GENERATION
) && !$force && empty($_GET['generator'])) {
2650 $redirect_counter = isset($_GET['redirect_counter']) ?
intval($_GET['redirect_counter']) : 0;
2651 $url = _advagg_build_url($filepath .
'?generator=1&redirect_counter=' .
$redirect_counter);
2653 'Host' => $_SERVER['HTTP_HOST'],
2654 'Connection' => 'close',
2658 if (function_exists('stream_socket_client') && function_exists('stream_select')) {
2659 advagg_async_connect_http_request($url, array('headers' => $headers));
2663 $socket_timeout = ini_set('default_socket_timeout', variable_get('advagg_socket_timeout', ADVAGG_SOCKET_TIMEOUT
));
2664 drupal_http_request($url, $headers, 'GET');
2665 ini_set('default_socket_timeout', $socket_timeout);
2668 // Return filepath if we are going to wait for the bundle to be
2669 // generated or if the bundle already exists.
2670 if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE
) < 2 || advagg_bundle_built($filepath)) {
2671 $output[$filepath] = array(
2672 'prefix' => $prefix,
2673 'suffix' => $suffix,
2674 'files' => array_map('advagg_return_true', array_flip($files)),
2678 // Aggregate isn't built yet, send back the files that where going to
2680 foreach ($files as
$file) {
2681 $output[$file .
$query_string] = array(
2684 'files' => array($file .
$query_string => TRUE
),
2688 advagg_disable_page_cache();
2693 // Only generate once.
2694 $lock_name = 'advagg_' .
$filename;
2695 if (!lock_acquire($lock_name) && !$force) {
2696 if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE
) == 0 ) {
2697 $locks[$lock_name] = $filepath;
2698 $output[$filepath] = array(
2699 'prefix' => $prefix,
2700 'suffix' => $suffix,
2701 'files' => array_map('advagg_return_true', array_flip($files)),
2705 // Aggregate isn't built yet, send back the files that where going
2707 foreach ($files as
$file) {
2708 $output[$file .
$query_string] = array(
2711 'files' => array($file .
$query_string => TRUE
),
2715 advagg_disable_page_cache();
2720 if ($type == 'css') {
2721 $data = advagg_build_css_bundle($files);
2723 elseif ($type == 'js') {
2724 $data = advagg_build_js_bundle($files);
2727 // Invoke hook_advagg_js_alter() or hook_advagg_css_alter to give
2728 // installed modules a chance to modify the data in the bundle if
2730 drupal_alter('advagg_' .
$type, $data, $files, $bundle_md5);
2731 $files_used = array_merge($files_used, $files);
2733 // If data is empty then do not include this bundle in the final output.
2734 if (empty($data) && !$force) {
2735 lock_release($lock_name);
2739 // Create the advagg_$type/ within the files folder.
2740 file_check_directory($file_type_path, FILE_CREATE_DIRECTORY
);
2742 // Write file. default function called: advagg_file_saver
2743 $function = variable_get('advagg_file_save_function', ADVAGG_FILE_SAVE_FUNCTION
);
2744 $good = $function($data, $filepath, $force, $type);
2747 lock_release($lock_name);
2749 // If file save was not good then downgrade to non aggregated mode.
2751 $output[$filepath] = FALSE
;
2757 $files_used = array_merge($files_used, $files);
2759 $output[$filepath] = array(
2760 'prefix' => $prefix,
2761 'suffix' => $suffix,
2762 'files' => array_map('advagg_return_true', array_flip($files)),
2766 // Wait for all locks before returning.
2767 if (!empty($locks)) {
2768 foreach ($locks as
$lock_name => $filepath) {
2769 lock_wait($lock_name);
2770 if (!advagg_bundle_built($filepath)) {
2771 $output[$filepath] = FALSE
;
2776 if (empty($output)) {
2782 if (!$force && $cacheable) {
2783 $new_cached_data_key = 'advagg_file_builder_' .
md5($schema .
implode('', array_filter(array_unique($files_used))));
2784 // Verify the files in equals the files out.
2785 if ($new_cached_data_key == $cached_data_key) {
2786 cache_set($cached_data_key, $output, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY
);
2794 * Given a list of files, grab their contents and glue it into one big string.
2797 * array of filenames.
2799 * string containing all the files.
2801 function advagg_build_css_bundle($files) {
2802 // Check if CSS compression is enabled.
2803 if (module_exists('advagg_css') && (variable_get('advagg_css_compress_agg_files', ADVAGG_CSS_COMPRESS_AGG_FILES
) || variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE
))) {
2810 // Build aggregate CSS file.
2812 foreach ($files as
$file) {
2813 $contents = advagg_drupal_load_stylesheet($file, $optimize);
2815 // Build the base URL of this CSS file: start with the full URL.
2816 $css_base_url = file_create_url($file);
2817 // Move to the parent.
2818 $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
2819 // Simplify to a relative URL if the stylesheet URL starts with the
2820 // base URL of the website.
2821 if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
2822 $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
2825 _drupal_build_css_path(NULL
, $css_base_url .
'/');
2826 // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
2827 $data .
= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
2830 // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
2831 // @import rules must proceed any other style, so we move those to the top.
2832 $regexp = '/@import[^;]+;/i';
2833 preg_match_all($regexp, $data, $matches);
2834 $data = preg_replace($regexp, '', $data);
2835 $data = implode('', $matches[0]) .
$data;
2840 * Given a list of files, grab their contents and glue it into one big string.
2843 * array of filenames.
2845 * string containing all the files.
2847 function advagg_build_js_bundle($files) {
2848 if (empty($files)) {
2852 // Build aggregate JS file.
2853 foreach ($files as
$file) {
2854 // Append a ';' and a newline after each JS file to prevent them from running together.
2855 if (advagg_file_exists($file)) {
2856 $data .
= file_get_contents($file) .
";\n";
2863 * Use a cache table to see if a file exists.
2870 function advagg_file_exists($filename) {
2871 static
$files = array();
2872 if (empty($files)) {
2873 $data = cache_get('advagg_file_checksum', 'cache');
2874 if (empty($data->data
)) {
2875 $result = db_query("SELECT filename, checksum FROM {advagg_files}");
2876 while ($row = db_fetch_array($result)) {
2877 $files[$row['filename']] = $row['checksum'];
2879 cache_set('advagg_file_checksum', $files, 'cache', CACHE_TEMPORARY
);
2882 $files = $data->data
;
2885 if (!empty($files[$filename]) && $files[$filename] != -1) {
2889 advagg_clearstatcache(TRUE
, $filename);
2890 return file_exists($filename);
2895 * Send out a fast 404 and exit.
2897 function advagg_missing_fast404($msg = '') {
2899 if (!headers_sent()) {
2900 header($_SERVER['SERVER_PROTOCOL'] .
' 404 Not Found');
2901 header('X-AdvAgg: Failed Validation. ' .
$msg);
2904 print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' .
"\n";
2906 print '<head><title>404 Not Found</title></head>';
2907 print '<body><h1>Not Found</h1>';
2908 print '<p>The requested URL was not found on this server.</p>';
2909 print '<p><a href="' .
$base_path .
'">Home</a></p>';
2910 print '<!-- advagg_missing_fast404 -->';
2911 print '</body></html>';
2916 * Generate .htaccess rules and place them in advagg dir
2919 * destination of the file that just got saved.
2921 * force recreate the .htaccess file.
2923 function advagg_htaccess_check_generate($dest, $force = FALSE
) {
2925 if (!$force && !variable_get('advagg_dir_htaccess', ADVAGG_DIR_HTACCESS
)) {
2929 $dir = dirname($dest);
2930 $htaccess_file = $dir .
'/.htaccess';
2931 advagg_clearstatcache(TRUE
, $htaccess_file);
2932 if (!$force && file_exists($htaccess_file)) {
2936 list($css_path, $js_path) = advagg_get_root_files_dir();
2939 if ($dir == $js_path) {
2942 $type = 'text/javascript';
2944 elseif ($dir == $css_path) {
2954 if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION
)) {
2955 $data .
= "<IfModule mod_rewrite.c>\n";
2956 $data .
= " RewriteEngine on\n";
2957 $data .
= " RewriteBase ${base_path}${path}\n";
2959 $data .
= " # Send 404's back to index.php\n";
2960 $data .
= " RewriteCond %{REQUEST_FILENAME} !-s\n";
2961 $data .
= " RewriteRule ^(.*)$ ${base_path}index.php?q=$path/$1 [L]\n";
2963 $data .
= " # Rules to correctly serve gzip compressed $ext files.\n";
2964 $data .
= " # Requires both mod_rewrite and mod_headers to be enabled.\n";
2965 $data .
= " <IfModule mod_headers.c>\n";
2966 $data .
= " # Serve gzip compressed $ext files if they exist and client accepts gzip.\n";
2967 $data .
= " RewriteCond %{HTTP:Accept-encoding} gzip\n";
2968 $data .
= " RewriteCond %{REQUEST_FILENAME}\.gz -s\n";
2969 $data .
= " RewriteRule ^(.*)\.$ext$ $1\.$ext\.gz [QSA]\n";
2971 $data .
= " # Serve correct content types, and prevent mod_deflate double gzip.\n";
2972 $data .
= " RewriteRule \.$ext\.gz$ - [T=$type,E=no-gzip:1]\n";
2974 $data .
= " <FilesMatch \"\.$ext\.gz$\">\n";
2975 $data .
= " # Serve correct encoding type.\n";
2976 $data .
= " Header set Content-Encoding gzip\n";
2977 $data .
= " # Force proxies to cache gzipped & non-gzipped $ext files separately.\n";
2978 $data .
= " Header append Vary Accept-Encoding\n";
2979 $data .
= " </FilesMatch>\n";
2980 $data .
= " </IfModule>\n";
2981 $data .
= "</IfModule>\n";
2984 $data .
= "<FilesMatch \"^${ext}_[0-9a-f]{32}_.+\.$ext(\.gz)?\">\n";
2985 $data .
= " # No mod_headers\n";
2986 $data .
= " <IfModule !mod_headers.c>\n";
2987 $data .
= " # No mod_expires\n";
2988 $data .
= " <IfModule !mod_expires.c>\n";
2989 $data .
= " # Use ETags.\n";
2990 $data .
= " FileETag MTime Size\n";
2991 $data .
= " </IfModule>\n";
2993 $data .
= " # Use Expires Directive.\n";
2994 $data .
= " <IfModule mod_expires.c>\n";
2995 $data .
= " # Do not use ETags.\n";
2996 $data .
= " FileETag None\n";
2997 $data .
= " # Enable expirations.\n";
2998 $data .
= " ExpiresActive On\n";
2999 $data .
= " # Cache all aggregated $ext files for 480 weeks after access (A).\n";
3000 $data .
= " ExpiresDefault A290304000\n";
3001 $data .
= " </IfModule>\n";
3002 $data .
= " </IfModule>\n";
3004 $data .
= " <IfModule mod_headers.c>\n";
3005 $data .
= " # Set a far future Cache-Control header to 480 weeks.\n";
3006 $data .
= " Header set Cache-Control \"max-age=290304000, no-transform, public\"\n";
3007 $data .
= " # Set a far future Expires header.\n";
3008 $data .
= " Header set Expires \"Tue, 20 Jan 2037 04:20:42 GMT\"\n";
3009 $data .
= " # Pretend the file was last modified a long time ago in the past.\n";
3010 $data .
= " Header set Last-Modified \"Wed, 20 Jan 1988 04:20:42 GMT\"\n";
3011 $data .
= " # Do not use etags for cache validation.\n";
3012 $data .
= " Header unset ETag\n";
3013 $data .
= " </IfModule>\n";
3014 $data .
= "</FilesMatch>\n";
3016 if (!advagg_file_save_data($data, $htaccess_file, FILE_EXISTS_REPLACE
)) {
3023 * Adds a CSS file to the stylesheet queue.
3026 * (optional) The CSS data that will be set. If not set then the inline CSS
3027 * array will be passed back.
3029 * (optional) The media type for the stylesheet, e.g., all, print, screen.
3031 * (optional) prefix to add before the inlined css.
3033 * (optional) suffix to add after the inlined css.
3035 * An array of CSS files.
3037 function advagg_add_css_inline($data = NULL
, $media = 'all', $prefix = NULL
, $suffix = NULL
) {
3038 static
$css = array();
3040 // Store inline data in a static.
3042 if (!isset($css[$media]['inline'][$prefix][$suffix])) {
3043 $css[$media]['inline'][$prefix][$suffix] = $data;
3046 $css[$media]['inline'][$prefix][$suffix] .
= "\n" .
$data;
3056 * Converts a PHP variable into its Javascript equivalent.
3058 * We use HTML-safe strings, i.e. with <, > and & escaped.
3060 function advagg_drupal_to_js($var) {
3062 if (!isset($php530)) {
3063 $php530 = version_compare(PHP_VERSION
, '5.3.0', '>=');
3066 // json_encode on PHP prior to PHP 5.3.0 doesn't support options.
3068 return json_encode($var, JSON_HEX_QUOT
| JSON_HEX_TAG
| JSON_HEX_AMP
| JSON_HEX_APOS
);
3071 // if json_encode exists, use it.
3072 if (function_exists('json_encode')) {
3073 return str_replace(array("<", ">", "&"), array('\u003c', '\u003e', '\u0026'), json_encode($var));
3076 switch (gettype($var)) {
3078 return $var ?
'true' : 'false'; // Lowercase necessary!
3084 // Always use Unicode escape sequences (\u0022) over JSON escape
3085 // sequences (\") to prevent browsers interpreting these as
3086 // special characters.
3087 $replace_pairs = array(
3088 // ", \ and U+0000 - U+001F must be escaped according to RFC 4627.
3123 // Prevent browsers from interpreting these as as special.
3128 // Prevent browsers from interpreting the solidus as special and
3129 // non-compliant JSON parsers from interpreting // as a comment.
3131 // While these are allowed unescaped according to ECMA-262, section
3132 // 15.12.2, they cause problems in some JSON parsers.
3133 "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator.
3134 "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator.
3137 return '"' .
strtr($var, $replace_pairs) .
'"';
3139 // Arrays in JSON can't be associative. If the array is empty or if it
3140 // has sequential whole number keys starting with 0, it's not associative
3141 // so we can go ahead and convert it as an array.
3142 if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
3144 foreach ($var as
$v) {
3145 $output[] = advagg_drupal_to_js($v);
3147 return '[ ' .
implode(', ', $output) .
' ]';
3149 // Otherwise, fall through to convert the array as an object.
3152 foreach ($var as
$k => $v) {
3153 $output[] = advagg_drupal_to_js(strval($k)) .
': ' .
advagg_drupal_to_js($v);
3155 return '{ ' .
implode(', ', $output) .
' }';
3162 * Process the contents of a stylesheet for aggregation.
3165 * The contents of the stylesheet.
3167 * (optional) Boolean whether CSS contents should be minified. Defaults to
3170 * Contents of the stylesheet including the imported stylesheets.
3172 function advagg_drupal_load_stylesheet_content($contents, $optimize = FALSE
) {
3173 // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
3174 $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
3177 // Perform some safe CSS optimizations.
3178 // Regexp to match comment blocks.
3179 $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
3180 // Regexp to match double quoted strings.
3181 $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
3182 // Regexp to match single quoted strings.
3183 $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
3184 // Strip all comment blocks, but keep double/single quoted strings.
3185 $contents = preg_replace(
3186 "<($double_quot|$single_quot)|$comment>Ss",
3190 // Remove certain whitespace.
3191 // There are different conditions for removing leading and trailing
3193 // @see http://php.net/manual/en/regexp.reference.subpatterns.php
3194 $contents = preg_replace('<
3195 # Strip leading and trailing whitespace.
3197 # Strip only leading whitespace from:
3198 # - Closing parenthesis: Retain "@media (bar) and foo".
3200 # Strip only trailing whitespace from:
3201 # - Opening parenthesis: Retain "@media (bar) and foo".
3202 # - Colon: Retain :pseudo-selectors.
3205 // Only one of the three capturing groups will match, so its reference
3206 // will contain the wanted value and the references for the
3207 // two non-matching groups will be replaced with empty strings.
3211 // End the file with a new line.
3212 $contents = trim($contents);
3216 // Replaces @import commands with the actual stylesheet content.
3217 // This happens recursively but omits external files.
3218 $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_advagg_drupal_load_stylesheet', $contents);
3223 * Loads stylesheets recursively and returns contents with corrected paths.
3225 * This function is used for recursive loading of stylesheets and
3226 * returns the stylesheet content with all url() paths corrected.
3228 function _advagg_drupal_load_stylesheet($matches) {
3229 $filename = $matches[1];
3230 // Load the imported stylesheet and replace @import commands in there as well.
3231 $file = advagg_build_css_bundle(array($filename));
3233 // Determine the file's directory.
3234 $directory = dirname($filename);
3235 // If the file is in the current directory, make sure '.' doesn't appear in
3237 $directory = $directory == '.' ?
'' : $directory .
'/';
3239 // Alter all internal url() paths. Leave external paths alone. We don't need
3240 // to normalize absolute paths here (i.e. remove folder/... segments) because
3241 // that will be done later.
3242 return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1' .
$directory, $file);
3246 * Loads the stylesheet and resolves all @import commands.
3248 * Loads a stylesheet and replaces @import commands with the contents of the
3249 * imported file. Use this instead of file_get_contents when processing
3252 * The returned contents are compressed removing white space and comments only
3253 * when CSS aggregation is enabled. This optimization will not apply for
3254 * color.module enabled themes with CSS aggregation turned off.
3257 * Name of the stylesheet to be processed.
3259 * Defines if CSS contents should be compressed or not.
3260 * @param $reset_basepath
3261 * Used internally to facilitate recursive resolution of @import commands.
3264 * Contents of the stylesheet, including any resolved @import commands.
3266 function advagg_drupal_load_stylesheet($file, $optimize = NULL
, $reset_basepath = TRUE
) {
3267 // These statics are not cache variables, so we don't use drupal_static().
3268 static
$_optimize, $basepath;
3269 if ($reset_basepath) {
3272 // Store the value of $optimize for preg_replace_callback with nested
3274 if (isset($optimize)) {
3275 $_optimize = $optimize;
3277 // Stylesheets are relative one to each other. Start by adding a base path
3278 // prefix provided by the parent stylesheet (if necessary).
3279 if ($basepath && !file_uri_scheme($file)) {
3280 $file = $basepath .
'/' .
$file;
3282 $basepath = dirname($file);
3283 // Load the CSS stylesheet. We suppress errors because themes may specify
3284 // stylesheets in their .info file that don't exist in the theme's path,
3285 // but are merely there to disable certain module CSS files.
3286 if ($contents = @
file_get_contents($file)) {
3287 // Return the processed stylesheet.
3288 return advagg_drupal_load_stylesheet_content($contents, $_optimize);
3294 * Perform an HTTP request; does not wait for reply & you will never get it
3297 * @see drupal_http_request()
3299 * This is a flexible and powerful HTTP client implementation. Correctly
3300 * handles GET, POST, PUT or any other HTTP requests.
3303 * A string containing a fully qualified URI.
3304 * @param array $options
3305 * (optional) An array that can have one or more of the following elements:
3306 * - headers: An array containing request headers to send as name/value pairs.
3307 * - method: A string containing the request method. Defaults to 'GET'.
3308 * - data: A string containing the request body, formatted as
3309 * 'param=value¶m=value&...'. Defaults to NULL.
3310 * - max_redirects: An integer representing how many times a redirect
3311 * may be followed. Defaults to 3.
3312 * - timeout: A float representing the maximum number of seconds the function
3313 * call may take. The default is 30 seconds. If a timeout occurs, the error
3314 * code is set to the HTTP_REQUEST_TIMEOUT constant.
3315 * - context: A context resource created with stream_context_create().
3317 * return value from advagg_async_send_http_request().
3319 function advagg_async_connect_http_request($url, array $options = array()) {
3320 $result = new
stdClass();
3322 // Parse the URL and make sure we can handle the schema.
3323 $uri = @
parse_url($url);
3326 $result->error
= 'unable to parse URL';
3327 $result->code
= -1001;
3331 if (!isset($uri['scheme'])) {
3332 $result->error
= 'missing schema';
3333 $result->code
= -1002;
3337 // Merge the default options.
3339 'headers' => array(),
3342 'max_redirects' => 3,
3346 // stream_socket_client() requires timeout to be a float.
3347 $options['timeout'] = (float) $options['timeout'];
3349 switch ($uri['scheme']) {
3352 $port = isset($uri['port']) ?
$uri['port'] : 80;
3353 $socket = 'tcp://' .
$uri['host'] .
':' .
$port;
3354 // RFC 2616: "non-standard ports MUST, default ports MAY be included".
3355 // We don't add the standard port to prevent from breaking rewrite rules
3356 // checking the host that do not take into account the port number.
3357 if (empty($options['headers']['Host'])) {
3358 $options['headers']['Host'] = $uri['host'];
3361 $options['headers']['Host'] .
= ':' .
$port;
3365 // Note: Only works when PHP is compiled with OpenSSL support.
3366 $port = isset($uri['port']) ?
$uri['port'] : 443;
3367 $socket = 'ssl://' .
$uri['host'] .
':' .
$port;
3368 if (empty($options['headers']['Host'])) {
3369 $options['headers']['Host'] = $uri['host'];
3372 $options['headers']['Host'] .
= ':' .
$port;
3376 $result->error
= 'invalid schema ' .
$uri['scheme'];
3377 $result->code
= -1003;
3381 $flags = STREAM_CLIENT_CONNECT
;
3382 if (variable_get('advagg_async_socket_connect', ADVAGG_ASYNC_SOCKET_CONNECT
)) {
3383 $flags = STREAM_CLIENT_ASYNC_CONNECT
| STREAM_CLIENT_CONNECT
;
3385 if (empty($options['context'])) {
3386 $fp = @
stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags);
3389 // Create a stream with context. Allows verification of a SSL certificate.
3390 $fp = @
stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags, $options['context']);
3393 // Make sure the socket opened properly.
3395 // When a network error occurs, we use a negative number so it does not
3396 // clash with the HTTP status codes.
3397 $result->code
= -$errno;
3398 $result->error
= trim($errstr) ?
trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
3403 // Non blocking stream.
3404 stream_set_blocking($fp, 0);
3406 // Construct the path to act on.
3407 $path = isset($uri['path']) ?
$uri['path'] : '/';
3408 if (isset($uri['query'])) {
3409 $path .
= '?' .
$uri['query'];
3412 // Merge the default headers.
3413 $options['headers'] += array(
3414 'User-Agent' => 'Drupal (+http://drupal.org/)',
3417 // Only add Content-Length if we actually have any content or if it is a POST
3418 // or PUT request. Some non-standard servers get confused by Content-Length in
3419 // at least HEAD/GET requests, and Squid always requires Content-Length in
3420 // POST/PUT requests.
3421 $content_length = strlen($options['data']);
3422 if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
3423 $options['headers']['Content-Length'] = $content_length;
3426 // If the server URL has a user then attempt to use basic authentication.
3427 if (isset($uri['user'])) {
3428 $options['headers']['Authorization'] = 'Basic ' .
base64_encode($uri['user'] .
(!empty($uri['pass']) ?
":" .
$uri['pass'] : ''));
3431 // If the database prefix is being used by SimpleTest to run the tests in a copied
3432 // database then set the user-agent header to the database prefix so that any
3433 // calls to other Drupal pages will run the SimpleTest prefixed database. The
3434 // user-agent is used to ensure that multiple testing sessions running at the
3435 // same time won't interfere with each other as they would if the database
3436 // prefix were stored statically in a file or database variable.
3437 $test_info = &$GLOBALS['drupal_test_info'];
3438 if (!empty($test_info['test_run_id'])) {
3439 $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
3442 $request = $options['method'] .
' ' .
$path .
" HTTP/1.0\r\n";
3443 foreach ($options['headers'] as
$name => $value) {
3444 $request .
= $name .
': ' .
trim($value) .
"\r\n";
3446 $request .
= "\r\n" .
$options['data'];
3447 $result->request
= $request;
3449 return advagg_async_send_http_request($fp, $request, $options['timeout']);
3453 * Perform an HTTP request; does not wait for reply & you never will get it
3456 * @see drupal_http_request()
3458 * This is a flexible and powerful HTTP client implementation. Correctly
3459 * handles GET, POST, PUT or any other HTTP requests.
3462 * (optional) A file pointer.
3464 * (optional) A string containing the request headers to send to the server.
3466 * (optional) An integer holding the stream timeout value.
3468 * TRUE if function worked as planed.
3470 function advagg_async_send_http_request($fp = NULL
, $request = '', $timeout = 30) {
3471 static
$requests = array();
3472 static
$registered = FALSE
;
3474 // Store data in a static, and register a shutdown function.
3475 $args = array($fp, $request, $timeout);
3477 $requests[] = $args;
3479 register_shutdown_function(__FUNCTION__
);
3485 // Shutdown function run.
3486 if (empty($requests)) {
3491 foreach ($requests as
$id => $values) {
3492 list($fp, $request, $timeout) = $values;
3493 $streams[$id] = $fp;
3497 // Run the loop as long as we have a stream to write to.
3498 while (!empty($streams)) {
3499 // Set the read and write vars to the streams var.
3500 $read = $write = $streams;
3503 // Do some voodoo and open all streams at once.
3504 $n = @
stream_select($read, $write, $except, $timeout);
3506 // We have some streams to write to.
3508 // Write to each stream if it is available.
3509 foreach ($write as
$id => $w) {
3510 fwrite($w, $requests[$id][1]);
3512 unset($streams[$id]);
3515 // Timed out waiting or all $streams are closed at this point.
3516 elseif (!empty($retry_count)) {
3524 $requests = array();
3525 if ($n !== FALSE
&& empty($streams)) {
3534 * Implement hook_advagg_js_header_footer_alter.
3536 function advagg_advagg_js_header_footer_alter(&$master_set, $preprocess_js, $public_downloads) {
3537 // Don't run the code below if ctools ajax is not loaded.
3538 if (!defined('CTOOLS_AJAX_INCLUDED')) {
3542 // Get all JS files set to be loaded.
3543 $js_files = array();
3544 foreach ($master_set as
$scope => $scripts) {
3545 if (empty($scripts)) {
3548 advagg_ctools_process_js_files($js_files, $scope, $scripts);
3551 // Add list of CSS & JS files loaded to the settings in the footer.
3552 $loaded = array('CToolsAJAX' => array('scripts' => $js_files));
3553 // Save to the js settings array even though we do not reload it in advagg.
3554 drupal_add_js($loaded, 'setting', 'footer');
3556 // Add it to the settings array in the footer.
3557 if (!isset($master_set['footer']['setting']) || !is_array($master_set['footer']['setting'])) {
3558 $master_set['footer']['setting'] = array();
3560 $master_set['footer']['setting'][] = $loaded;
3564 * Implement hook_advagg_css_pre_alter.
3566 function advagg_advagg_css_pre_alter(&$css, $preprocess_css, $public_downloads) {
3567 // Don't run the code below if ctools ajax is not loaded.
3568 if (!defined('CTOOLS_AJAX_INCLUDED')) {
3572 // Get all CSS files set to be loaded.
3573 $css_files = array();
3574 ctools_process_css_files($css_files, $css);
3576 // Save to the js settings array.
3577 drupal_add_js(array('CToolsAJAX' => array('css' => $css_files)), 'setting', 'footer');
3581 * Create a list of javascript files that are on the page.
3584 * Array of js files that are loaded on this page.
3586 * String usually containing header or footer.
3588 * (Optional) array returned from drupal_add_js(). If NULL then it will load
3589 * the array from drupal_add_js for the given scope.
3590 * @return array $settings
3591 * The JS 'setting' array for the given scope.
3593 function advagg_ctools_process_js_files(&$js_files, $scope, $scripts = NULL
) {
3594 // Automatically extract any 'settings' added via drupal_add_js() and make
3595 // them the first command.
3596 $scripts = drupal_add_js(NULL
, NULL
, $scope);
3597 if (empty($scripts)) {
3598 $scripts = drupal_add_js(NULL
, NULL
, $scope);
3601 // Get replacements that are going to be made by contrib modules and take
3602 // them into account so we don't double-load scripts.
3603 static
$replacements = NULL
;
3604 if (!isset($replacements)) {
3605 $replacements = module_invoke_all('js_replacements');
3608 $settings = array();
3609 foreach ($scripts as
$type => $data) {
3616 // Presently we ignore inline javascript.
3617 // Theme JS is already added and because of admin themes, this could add
3618 // improper JS to the page.
3621 // If JS preprocessing is off, we still need to output the scripts.
3622 // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
3623 foreach ($data as
$path => $info) {
3624 // If the script is being replaced, take that replacement into account.
3625 $final_path = isset($replacements[$type][$path]) ?
$replacements[$type][$path] : $path;
3626 $js_files[base_path() .
$final_path] = TRUE
;
3634 * Wrapper around clearstatcache so it can use php 5.3's new features.
3636 * @param $clear_realpath_cache
3641 * value from clearstatcache().
3643 function advagg_clearstatcache($clear_realpath_cache = FALSE
, $filename = NULL
) {
3645 if (!isset($php530)) {
3646 $php530 = version_compare(PHP_VERSION
, '5.3.0', '>=');
3650 return clearstatcache($clear_realpath_cache, $filename);
3653 return clearstatcache();
3658 * Select records in the database matching where IN(...).
3660 * NOTE Be aware of the servers max_packet_size variable.
3663 * The name of the table.
3665 * field name to be compared to
3666 * @param $placeholder
3667 * db_query placeholders; like %d or '%s'
3669 * array of values you wish to compare to
3671 * array of db fields you return
3673 * returns db_query() result.
3675 function advagg_db_multi_select_in($table, $field, $placeholder, $data, $returns = array(), $groupby = '') {
3676 // Set returns if empty
3677 if (empty($returns)) {
3680 // Get the number of rows that will be inserted
3681 $rows = count($data);
3682 // Create what goes in the IN ()
3684 // Add the rest of the place holders
3685 for ($i = 1; $i < $rows; $i++) {
3686 $in .
= ', ' .
$placeholder;
3689 $query = "SELECT " .
implode(', ', $returns) .
" FROM {" .
$table .
"} WHERE $field IN ($in) $groupby";