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 * Implementation of hook_perm().
143 function advagg_perm() {
144 return array('bypass advanced aggregation');
148 * Implementation of hook_menu().
150 function advagg_menu() {
151 list($css_path, $js_path) = advagg_get_root_files_dir();
152 $file_path = drupal_get_path('module', 'advagg');
155 $items[$css_path .
'/%'] = array(
156 'page callback' => 'advagg_missing_css',
157 'type' => MENU_CALLBACK
,
158 'access callback' => TRUE
,
159 'file path' => $file_path,
160 'file' => 'advagg.missing.inc',
162 $items[$js_path .
'/%'] = array(
163 'page callback' => 'advagg_missing_js',
164 'type' => MENU_CALLBACK
,
165 'access callback' => TRUE
,
166 'file path' => $file_path,
167 'file' => 'advagg.missing.inc',
169 $items['admin/settings/advagg'] = array(
170 'title' => 'Advanced CSS/JS Aggregation',
171 'description' => 'Configuration for Advanced CSS/JS Aggregation.',
172 'page callback' => 'advagg_admin_page',
173 'type' => MENU_NORMAL_ITEM
,
174 'access arguments' => array('administer site configuration'),
175 'file path' => $file_path,
176 'file' => 'advagg.admin.inc',
178 $items['admin/settings/advagg/config'] = array(
179 'title' => 'Configuration',
180 'type' => MENU_DEFAULT_LOCAL_TASK
,
183 $items['admin/settings/advagg/info'] = array(
184 'title' => 'Information',
185 'description' => 'More detailed information about advagg.',
186 'page callback' => 'advagg_admin_info_page',
187 'type' => MENU_LOCAL_TASK
,
188 'access arguments' => array('administer site configuration'),
189 'file path' => $file_path,
190 'file' => 'advagg.admin.inc',
192 $items['admin_menu/flush-cache/advagg'] = array(
193 'page callback' => 'advagg_admin_flush_cache',
194 'type' => MENU_CALLBACK
,
195 'access arguments' => array('administer site configuration'),
196 'file path' => $file_path,
197 'file' => 'advagg.admin.inc',
203 * Implementation of hook_admin_menu().
205 * Add in a cache flush for advagg.
207 function advagg_admin_menu(&$deleted) {
211 'title' => 'Adv CSS/JS Agg',
212 'path' => 'admin_menu/flush-cache/advagg',
213 'query' => 'destination',
214 'parent_path' => 'admin_menu/flush-cache',
221 * Implementation of hook_admin_menu_output_alter().
223 * Add in a cache flush for advagg.
225 function advagg_admin_menu_output_alter(&$content) {
226 if (!empty($content['icon']['icon']['flush-cache']['#access']) && !empty($content['icon']['icon']['flush-cache']['requisites']) && empty($content['icon']['icon']['flush-cache']['advagg'])) {
227 $content['icon']['icon']['flush-cache']['advagg'] = $content['icon']['icon']['flush-cache']['requisites'];
228 $content['icon']['icon']['flush-cache']['advagg']['#title'] = t('Adv CSS/JS Agg');
229 $content['icon']['icon']['flush-cache']['advagg']['#href'] = 'admin_menu/flush-cache/advagg';
234 * Implementation of hook_cron().
236 function advagg_cron() {
237 if (!variable_get('advagg_prune_on_cron', ADVAGG_PRUNE_ON_CRON
)) {
241 // Set the oldest file/bundle to keep at 2 weeks.
242 $max_time = module_exists('advagg_bundler') ?
variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED
) : 1209600;
243 $max_file_time = time() - $max_time;
244 $max_bundle_time = time() - ($max_time*3);
245 $bundles_removed = 0;
246 $files_removed = array();
249 $results = db_query("SELECT filename, filename_md5 FROM {advagg_files}");
250 while ($row = db_fetch_array($results)) {
251 // If the file exists, do nothing
252 if (file_exists($row['filename'])) {
256 // Remove bundles referencing missing files, if they are older than 2 weeks.
257 $bundles = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s' AND timestamp > %d", $row['filename_md5'], $max_file_time);
258 while ($bundle_md5 = db_result($bundles)) {
260 db_query("DELETE FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5);
262 $count = db_result(db_query("SELECT COUNT(*) FROM {advagg_bundles} WHERE filename_md5 = '%s'", $row['filename_md5']));
264 // If no more bundles reference the missing file then remove the file.
266 db_query("DELETE FROM {advagg_files} WHERE filename_md5 = '%s'", $row['filename_md5']);
267 $files_removed[] = $row['filename'];
272 $bundles_removed += db_result(db_query("
276 FROM {advagg_bundles}
279 ) AS advagg_count", $max_bundle_time));
280 $results = db_query("DELETE FROM {advagg_bundles} WHERE timestamp < %d", $max_bundle_time);
282 // Report to watchdog if anything was done.
283 if (!empty($bundles_removed) || !empty($files_removed)) {
284 watchdog('advagg', 'Cron ran and the following files where removed from the database: %files <br /> %count old bundles where also removed from the database.', array(
285 '%files' => implode(', ', $files_removed),
286 '%count' => $bundles_removed,
292 * Implementation of hook_init().
294 function advagg_init() {
295 global $base_path, $conf, $_advagg;
297 // Disable advagg if requested.
298 if (isset($_GET['advagg']) && $_GET['advagg'] == -1 && user_access('bypass advanced aggregation')) {
299 $conf['advagg_enabled'] = FALSE
;
301 // Enable debugging if requested.
302 if (isset($_GET['advagg-debug']) && $_GET['advagg-debug'] == 1 && user_access('bypass advanced aggregation')) {
303 $conf['advagg_debug'] = TRUE
;
305 // Enable core preprocessing if requested.
306 if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 && user_access('bypass advanced aggregation')) {
307 $conf['preprocess_css'] = TRUE
;
308 $conf['preprocess_js'] = TRUE
;
311 // Disable ctools_ajax_page_preprocess() if this functionality is available.
312 if (variable_get('advagg_enabled', ADVAGG_ENABLED
) && function_exists('ctools_ajax_run_page_preprocess')) {
313 ctools_ajax_run_page_preprocess(FALSE
);
314 $_advagg['ctools_patched'] = TRUE
;
317 // Create a closure function that does not add JavaScript.
318 if (variable_get('advagg_closure', ADVAGG_CLOSURE
)) {
319 if (!function_exists('phptemplate_closure')) {
320 $_advagg['closure'] = TRUE
;
323 * Execute hook_footer() which is run at the end of the page right before
324 * the close of the body tag.
326 * @param $main (optional)
327 * Whether the current page is the front page of the site.
329 * A string containing the results of the hook_footer() calls.
331 function phptemplate_closure($main = 0) {
332 $footer = implode("\n", module_invoke_all('footer', $main));
333 // If advagg is disabled, then include footer JS here.
334 if (!variable_get('advagg_enabled', ADVAGG_ENABLED
)) {
335 $footer .
= drupal_get_js('footer');
342 $_advagg['closure'] = FALSE
;
348 * Implementation of hook_theme_registry_alter().
350 * Make sure our preprocess function runs last for page.
352 * @param $theme_registry
353 * The existing theme registry data structure.
355 function advagg_theme_registry_alter(&$theme_registry) {
357 if (isset($theme_registry['page'])) {
358 // If jquery_update's preprocess function is there already, remove it.
359 if (module_exists('jquery_update') && $key = array_search('jquery_update_preprocess_page', $theme_registry['page']['preprocess functions'])) {
360 unset($theme_registry['page']['preprocess functions'][$key]);
363 // If ctools hasn't been patched remove it from getting pre-processed.
364 if ( !empty($_advagg['ctools_patched'])
365 && module_exists('ctools')
366 && $key = array_search('ctools_ajax_page_preprocess', $theme_registry['page']['preprocess functions'])
368 unset($theme_registry['page']['preprocess functions'][$key]);
371 // Add our own preprocessing function to the end of the array.
372 $theme_registry['page']['preprocess functions'][] = 'advagg_processor';
374 // If labjs's is enabled, move it to the bottom.
375 if (module_exists('labjs') && $key = array_search('labjs_preprocess_page', $theme_registry['page']['preprocess functions'])) {
376 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
377 unset($theme_registry['page']['preprocess functions'][$key]);
380 // If designkit is enabled, move it to the bottom.
381 if (module_exists('designkit') && $key = array_search('designkit_preprocess_page', $theme_registry['page']['preprocess functions'])) {
382 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
383 unset($theme_registry['page']['preprocess functions'][$key]);
386 // If conditional styles is enabled, move it to the bottom.
387 if (module_exists('conditional_styles') && $key = array_search('conditional_styles_preprocess_page', $theme_registry['page']['preprocess functions'])) {
388 $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key];
389 unset($theme_registry['page']['preprocess functions'][$key]);
395 * Get the CSS & JS path for advagg.
398 * reset the static variables.
400 * array($css_path, $js_path)
402 function advagg_get_root_files_dir($reset = FALSE
) {
403 static
$css_path = '';
404 static
$js_path = '';
410 if (!empty($css_path) && !empty($js_path)) {
411 return array($css_path, $js_path);
414 $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC
) == FILE_DOWNLOADS_PUBLIC
);
415 if (!$public_downloads) {
416 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR
);
418 if (empty($custom_path)) {
419 $css_path = file_create_path('advagg_css');
420 $js_path = file_create_path('advagg_js');
421 return array($css_path, $js_path);
423 file_check_directory($custom_path, FILE_CREATE_DIRECTORY
);
426 $conf_path = conf_path();
427 if (is_link($conf_path)) {
428 $path = readlink($conf_path);
430 $conf_path = str_replace("\\", '/', $conf_path);
431 $conf_path = explode('/', $conf_path);
432 $conf_path = array_pop($conf_path);
433 $custom_path = $custom_path .
'/' .
$conf_path;
434 file_check_directory($custom_path, FILE_CREATE_DIRECTORY
);
436 $css_path = $custom_path .
'/advagg_css';
437 $js_path = $custom_path .
'/advagg_js';
438 file_check_directory($css_path, FILE_CREATE_DIRECTORY
);
439 file_check_directory($js_path, FILE_CREATE_DIRECTORY
);
440 return array($css_path, $js_path);
444 * Merge 2 css arrays together.
453 function advagg_merge_css($array1, $array2) {
454 foreach ($array2 as
$media => $types) {
455 foreach ($types as
$type => $files) {
456 foreach ($files as
$file => $preprocess) {
457 $array1[$media][$type][$file] = $preprocess;
465 * Merge 2 css arrays together.
474 function advagg_merge_inline_css($array1, $array2) {
475 foreach ($array2 as
$media => $types) {
476 foreach ($types as
$type => $blobs) {
477 foreach ($blobs as
$prefix => $data) {
478 foreach ($data as
$suffix => $blob) {
479 $array1[$media][$type][$prefix][$suffix] = $blob;
488 * Remove .less files from the array.
493 function advagg_css_array_fixer(&$css_func) {
494 if (!module_exists('less')) {
498 // Remove '.css.less' files from the stack.
499 foreach ($css_func as
$k => $v) {
500 foreach ($v as
$ke => $va) {
501 foreach ($va as
$file => $preprocess) {
502 if (advagg_string_ends_with($file, '.css.less')) {
503 unset($css_func[$k][$ke][$file]);
511 * See if a string ends with a substring.
514 * The main string being compared.
516 * The secondary string being compared.
520 function advagg_string_ends_with($haystack, $needle) {
521 // Define substr_compare if it doesn't exist (PHP 4 fix).
522 if (!function_exists('substr_compare')) {
524 * Binary safe comparison of two strings from an offset, up to length
527 * Compares main_str from position offset with str up to length characters.
528 * @see http://php.net/substr-compare#53084
531 * The main string being compared.
533 * The secondary string being compared.
535 * The start position for the comparison. If negative, it starts counting
536 * from the end of the string.
538 * The length of the comparison. The default value is the largest of the
539 * length of the str compared to the length of main_str less the offset.
540 * @param $case_insensitivity
541 * If TRUE, comparison is case insensitive.
543 * Returns < 0 if main_str from position offset is less than str, > 0 if
544 * it is greater than str, and 0 if they are equal. If offset is equal to
545 * or greater than the length of main_str or length is set and is less than
546 * 1, substr_compare() prints a warning and returns FALSE.
548 function substr_compare($main_str, $str, $offset, $length = NULL
, $case_insensitivity = FALSE
) {
549 $offset = (int) $offset;
551 // Throw a warning because the offset is invalid
552 if ($offset >= strlen($main_str)) {
553 trigger_error('The start position cannot exceed initial string length.', E_USER_WARNING
);
557 // We are comparing the first n-characters of each string, so let's use the PHP function to do it
558 if ($offset == 0 && is_int($length) && $case_insensitivity === TRUE
) {
559 return strncasecmp($main_str, $str, $length);
562 // Get the substring that we are comparing
563 if (is_int($length)) {
564 $main_substr = substr($main_str, $offset, $length);
565 $str_substr = substr($str, 0, $length);
568 $main_substr = substr($main_str, $offset);
572 // Return a case-insensitive comparison of the two strings
573 if ($case_insensitivity === TRUE
) {
574 return strcasecmp($main_substr, $str_substr);
577 // Return a case-sensitive comparison of the two strings
578 return strcmp($main_substr, $str_substr);
582 $haystack_len = strlen($haystack);
583 $needle_len = strlen($needle);
584 if ($needle_len > $haystack_len) {
587 return substr_compare($haystack, $needle, $haystack_len-$needle_len, $needle_len, TRUE
) === 0;
591 * Implementation of hook_advagg_disable_processor().
593 function advagg_advagg_disable_processor() {
594 // Disable advagg on the configuration page; in case something bad happened.
595 if (isset($_GET['q']) &&
596 ( $_GET['q'] == 'admin/settings/advagg'
597 || $_GET['q'] == 'admin/settings/advagg/config'
598 || $_GET['q'] == 'batch'
606 * Process variables for page.tpl.php
609 * The existing theme data structure.
611 function advagg_processor(&$variables) {
614 // Invoke hook_advagg_disable_processor
615 $disabled = module_invoke_all('advagg_disable_processor');
618 if (!variable_get('advagg_enabled', ADVAGG_ENABLED
) || in_array(TRUE
, $disabled, TRUE
)) {
619 if (module_exists('jquery_update')) {
620 return jquery_update_preprocess_page($variables);
628 $css_var = $variables['css'];
629 $css_orig = $css_var;
630 $css_func = drupal_add_css();
631 advagg_css_array_fixer($css_func);
632 $css = advagg_merge_css($css_func, $css_var);
633 $css_func_inline = advagg_add_css_inline();
634 if (!empty($css_func_inline)) {
635 $css = advagg_merge_inline_css($css, $css_func_inline);
637 $css_conditional_styles = !empty($variables['conditional_styles']) ?
$variables['conditional_styles'] : '';
638 $css_styles = $variables['styles'];
640 $processed_css = advagg_process_css($css);
641 if (!empty($processed_css)) {
642 $variables['styles'] = $processed_css;
644 $variables['styles'] .
= "\n".
$css_conditional_styles;
648 $js_code['header'] = drupal_add_js(NULL
, NULL
, 'header');
649 if (variable_get('advagg_closure', ADVAGG_CLOSURE
) && !empty($_advagg['closure'])) {
650 $js_code['footer'] = drupal_add_js(NULL
, NULL
, 'footer');
652 $skip_keys = variable_get('advagg_region_skip_keys', array('styles', 'scripts', 'zebra', 'id', 'directory', 'layout', 'head_title', 'base_path', 'front_page', 'head', 'body_classes', 'header', 'footer', 'closure'));
653 foreach ($variables as
$key => $value) {
654 if (!in_array($key, $skip_keys) && is_string($value) && !empty($value) && !isset($js_code[$key])) {
655 $js_code[$key] = drupal_add_js(NULL
, NULL
, $key);
658 $js_code_orig = $js_code;
660 advagg_jquery_updater($js_code['header']);
661 $js_code = advagg_process_js($js_code);
662 foreach ($js_code as
$key => $value) {
663 if ($key == 'header') {
664 $variables['scripts'] = $value;
666 elseif ($key == 'footer' && variable_get('advagg_closure', ADVAGG_CLOSURE
) && !empty($_advagg['closure'])) {
667 $variables['closure'] .
= $value;
670 $variables[$key] .
= $value;
674 // Send requests to server if async enabled.
675 advagg_async_send_http_request();
677 // Write debug info to watchdog if debugging enabled.
678 if (variable_get('advagg_debug', ADVAGG_DEBUG
)) {
680 'css_before_vars' => $css_orig,
681 'css_before_function' => $css_func,
682 'css_before_styles' => $css_styles,
683 'css_before_inline' => $css_func_inline,
684 'css_before_conditional_styles' => $css_conditional_styles,
685 'css_merged' => $css,
686 'css_after' => $processed_css,
687 'js_before' => $js_code_orig,
688 'js_after' => $js_code,
690 $data['runtime'] = isset($_advagg['debug']) ?
$_advagg['debug'] : FALSE
;
691 $data = str_replace(' ', ' ', nl2br(htmlentities(iconv('utf-8', 'utf-8//IGNORE', print_r($data, TRUE
)), ENT_QUOTES
, 'UTF-8')));
692 watchdog('advagg', 'Debug info: !data', array('!data' => $data), WATCHDOG_DEBUG
);
697 * Special handling for jquery update.
700 * List of files in the header
702 function advagg_jquery_updater(&$js) {
703 if (!module_exists('jquery_update') || !variable_get('jquery_update_replace', TRUE
) || empty($js)) {
707 // Replace jquery.js first.
708 $new_jquery = array(jquery_update_jquery_path() => $js['core']['misc/jquery.js']);
709 $js['core'] = array_merge($new_jquery, $js['core']);
710 unset($js['core']['misc/jquery.js']);
712 // Loop through each of the required replacements.
713 foreach (jquery_update_get_replacements() as
$type => $replacements) {
714 foreach ($replacements as
$find => $replace) {
715 // If the file to replace is loaded on this page...
716 if (isset($js[$type][$find])) {
717 // Create a new entry for the replacement file, and unset the original one.
718 $replace = JQUERY_UPDATE_REPLACE_PATH .
'/'.
$replace;
719 $js[$type][$replace] = $js[$type][$find];
720 unset($js[$type][$find]);
727 * Given a list of files; return back the aggregated filename.
730 * List of files in the proposed bundle.
734 * (optional) Counter value.
736 * (optional) Bundle's machine name.
738 * Aggregated filename.
740 function advagg_get_filename($files, $filetype, $counter = FALSE
, $bundle_md5 = '') {
741 if (empty($files) || empty($filetype)) {
746 $filenames = array();
749 if (empty($bundle_md5)) {
751 $bundle_md5 = md5(implode('', $files));
754 // Record root request in db.
755 // Get counter if there.
756 if (empty($counter)) {
757 $counter = db_result(db_query("SELECT counter FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5));
759 // If this is a brand new bundle then insert file/bundle info into database.
760 if ($counter === FALSE
) {
762 advagg_insert_bundle_db($files, $filetype, $bundle_md5, TRUE
);
764 // If bundle should be root and is not, then make it root.
765 // Refresh timestamp if older then 12 hours.
766 $row = db_fetch_array(db_query("SELECT root, timestamp FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5));
767 if ($row['root'] === 0 || time() - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL
)) {
768 db_query("UPDATE {advagg_bundles} SET root = '1', timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5);
772 // Set original array.
773 $filenames[] = array(
774 'filetype' => $filetype,
776 'counter' => $counter,
777 'bundle_md5' => $bundle_md5,
780 // Invoke hook_advagg_filenames_alter() to give installed modules a chance to
781 // alter filenames. One to many relationships come to mind.
782 // Do not run alter if MD5 was given, we want to generate that file only in
783 // this special case.
785 // Force counter to be looked up later.
786 $filenames[0]['counter'] = FALSE
;
787 drupal_alter('advagg_filenames', $filenames);
790 // Write to DB if needed and create filenames.
793 if (variable_get('advagg_debug', ADVAGG_DEBUG
)) {
794 $_advagg['debug']['get_filename_post_alter'][] = array(
795 'key' => $bundle_md5,
796 'filenames' => $filenames,
800 // Get all counters at once
802 foreach ($filenames as
$key => $values) {
803 if (empty($values['counter'])) {
804 $counters[$key] = $values['bundle_md5'];
807 $result = advagg_db_multi_select_in('advagg_bundles', 'bundle_md5', "'%s'", $counters, array('counter', 'bundle_md5'), 'GROUP BY bundle_md5');
808 while ($row = db_fetch_array($result)) {
809 $key = array_search($row['bundle_md5'], $counters);
810 if (empty($filenames[$key]['counter']) && $filenames[$key]['counter'] !== 0) {
811 $filenames[$key]['counter'] = intval($row['counter']);
815 foreach ($filenames as
$values) {
816 // Get info from array.
817 $filetype = $values['filetype'];
818 $files = $values['files'];
819 $counter = $values['counter'];
820 $bundle_md5 = $values['bundle_md5'];
822 // See if a JS bundle exists that already has the same files in it, just in a
824 // if ($filetype == 'js' && $run_alter) {
825 // advagg_find_existing_bundle($files, $bundle_md5);
828 // Do not add the same bundle twice.
829 if (isset($used_md5[$bundle_md5])) {
832 $used_md5[$bundle_md5] = TRUE
;
834 // If this is a brand new bundle then insert file/bundle info into database.
835 if (empty($counter) && $counter !== 0) {
837 advagg_insert_bundle_db($files, $filetype, $bundle_md5, FALSE
);
840 // Prefix filename to prevent blocking by firewalls which reject files
841 // starting with "ad*".
843 'filename' => advagg_build_filename($filetype, $bundle_md5, $counter),
845 'bundle_md5' => $bundle_md5,
852 * Get a bundle from the cache & verify it is good.
854 * @param $cached_data_key
855 * cache key for the cache_advagg_bundle_reuse table.
857 * Name to output in the array if debugging is enabled.
859 * data from the cache.
861 function advagg_cached_bundle_get($cached_data_key, $debug_name) {
863 $data = cache_get($cached_data_key, 'cache_advagg_bundle_reuse');
864 if (!empty($data->data
)) {
866 $bundle_contents = array();
868 // Verify cached data is good.
869 foreach ($data as
$filename => $extra) {
870 if (is_numeric($filename)) {
873 // Get md5 from aggregated filename.
874 $b_md5 = explode('/', $filename);
875 $b_md5 = explode('_', array_pop($b_md5));
878 // Lookup bundle and make sure it is valid.
879 if (!empty($b_md5)) {
880 list($b_filetype, $b_files) = advagg_get_files_in_bundle($b_md5);
881 $bundle_contents[$filename] = $b_files;
882 if (empty($b_files)) {
888 if (variable_get('advagg_debug', ADVAGG_DEBUG
)) {
889 $_advagg['debug'][$debug_name][] = array(
890 'key' => $cached_data_key,
893 'bundle_contents' => $bundle_contents,
904 * Given a list of files, see if a bundle already exists containing all of those
905 * files. If in strict mode then the file count has to be the same.
908 * List of files in the proposed bundle.
910 * Bundle's machine name.
912 function advagg_find_existing_bundle(&$files, &$bundle_md5) {
913 // Sort files for better cache hits.
914 $temp_files = $files;
916 $cached_data_key = 'advagg_existing_' .
md5(implode('', $temp_files));
918 // Try cache first; cache table is cache_advagg_bundle_reuse. string is debug name.
919 $cached_data = advagg_cached_bundle_get($cached_data_key, 'advagg_find_existing_bundle');
920 if (!empty($cached_data)) {
921 $files = $cached_data[0]['files'];
922 $bundle_md5 = $cached_data[0]['bundle_md5'];
926 // Build union query.
927 $query = 'SELECT root.bundle_md5 FROM {advagg_bundles} AS root';
932 foreach ($files as
$filename) {
933 // Use alpha for table aliases; numerics do not work.
934 $key = strtr($counter, '01234567890', 'abcdefghij');
936 $joins[$key] = "\nINNER JOIN {advagg_bundles} AS $key USING(bundle_md5)\n";
938 $wheres[$key] = "WHERE $key.filename_md5 = '%s'";
941 $wheres[$key] = "AND $key.filename_md5 = '%s'";
943 $args[$key] = md5($filename);
946 $query .
= implode("\n", $joins);
947 $query .
= implode("\n", $wheres);
948 $query .
= ' GROUP BY bundle_md5';
950 // Find matching bundles and select first good one.
951 $files_count = count($files);
952 $results = db_query($query, $args);
953 while ($new_md5 = db_result($results)) {
954 $count = db_result(db_query("SELECT count(*) FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $new_md5));
955 // Make sure bundle has the same number of files if using strict matching.
956 if (!empty($count) && $count == $files_count) {
957 $bundle_md5 = $new_md5;
958 $data = array(array('files' => $files, 'bundle_md5' => $bundle_md5));
959 cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY
);
966 * Build the filename.
973 * Bundle's machine name.
975 function advagg_build_filename($filetype, $bundle_md5, $counter) {
976 return $filetype .
'_' .
$bundle_md5 .
'_' .
$counter .
'.' .
$filetype;
980 * Insert info into the advagg_files and advagg_bundles database.
983 * List of files in the proposed bundle.
987 * Bundle's machine name.
989 * Is this a root bundle.
991 function advagg_insert_bundle_db($files, $filetype, $bundle_md5, $root) {
992 $lock_name = 'advagg_insert_bundle_db' .
$bundle_md5;
993 if (!lock_acquire($lock_name)) {
994 lock_wait($lock_name);
998 foreach ($files as
$order => $filename) {
999 $filename_md5 = md5($filename);
1001 // Insert file into the advagg_files table if it doesn't exist.
1002 $checksum = db_result(db_query("SELECT checksum FROM {advagg_files} WHERE filename_md5 = '%s'", $filename_md5));
1003 if (empty($checksum)) {
1004 $checksum = advagg_checksum($filename);
1005 db_query("INSERT INTO {advagg_files} (filename, filename_md5, checksum, filetype, filesize) VALUES ('%s', '%s', '%s', '%s', %d)", $filename, $filename_md5, $checksum, $filetype, @
filesize($filename));
1008 // Create the entries in the advagg_bundles table.
1009 db_query("INSERT INTO {advagg_bundles} (bundle_md5, filename_md5, counter, porder, root) VALUES ('%s', '%s', '%d', '%d', '%d')", $bundle_md5, $filename_md5, 0, $order, $root, time());
1012 lock_release($lock_name);
1016 * Save a string to the specified destination. Verify that file size is not zero.
1019 * A string containing the contents of the file.
1021 * A string containing the destination location.
1023 * Boolean indicating if the file save was successful.
1025 function advagg_file_saver($data, $dest, $force, $type) {
1026 // Create the JS file.
1027 $file_save_data = 'file_save_data';
1028 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR
);
1029 if (!empty($custom_path)) {
1030 $file_save_data = 'advagg_file_save_data';
1033 if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE
)) {
1037 // Make sure filesize is not zero.
1038 advagg_clearstatcache(TRUE
, $dest);
1039 if (@
filesize($dest) == 0 && !empty($data)) {
1040 if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE
)) {
1043 advagg_clearstatcache(TRUE
, $dest);
1044 if (@
filesize($dest) == 0 && !empty($data)) {
1045 // Filename is bad, create a new one next time.
1051 if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION
) && extension_loaded('zlib')) {
1052 $gzip_dest = $dest .
'.gz';
1053 advagg_clearstatcache(TRUE
, $gzip_dest);
1054 if (!file_exists($gzip_dest) || $force) {
1055 $gzip_data = gzencode($data, 9, FORCE_GZIP
);
1056 if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE
)) {
1060 // Make sure filesize is not zero.
1061 advagg_clearstatcache(TRUE
, $gzip_dest);
1062 if (@
filesize($gzip_dest) == 0 && !empty($gzip_data)) {
1063 if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE
)) {
1066 advagg_clearstatcache(TRUE
, $gzip_dest);
1067 if (@
filesize($gzip_dest) == 0 && !empty($gzip_data)) {
1068 // Filename is bad, create a new one next time.
1069 file_delete($gzip_dest);
1076 // Make sure .htaccess file exists.
1077 advagg_htaccess_check_generate($dest);
1079 cache_set($dest, time(), 'cache_advagg', CACHE_PERMANENT
);
1086 * ***MODIFIED CORE FUNCTIONS BELOW***
1088 * @see file_save_data()
1094 * Save a string to the specified destination.
1096 * @see file_save_data()
1098 * @param $data A string containing the contents of the file.
1099 * @param $dest A string containing the destination location.
1100 * @param $replace Replace behavior when the destination file already exists.
1101 * - FILE_EXISTS_REPLACE - Replace the existing file
1102 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
1103 * - FILE_EXISTS_ERROR - Do nothing and return FALSE.
1105 * @return A string containing the resulting filename or 0 on error
1107 function advagg_file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME
) {
1108 $temp = file_directory_temp();
1109 // On Windows, tempnam() requires an absolute path, so we use realpath().
1110 $file = tempnam(realpath($temp), 'file');
1111 if (!$fp = fopen($file, 'wb')) {
1112 drupal_set_message(t('The file could not be created.'), 'error');
1118 if (!advagg_file_move($file, $dest, $replace)) {
1126 * Moves a file to a new location.
1130 * - Checks if $source and $dest are valid and readable/writable.
1131 * - Performs a file move if $source is not equal to $dest.
1132 * - If file already exists in $dest either the call will error out, replace the
1133 * file or rename the file based on the $replace parameter.
1136 * Either a string specifying the file location of the original file or an
1137 * object containing a 'filepath' property. This parameter is passed by
1138 * reference and will contain the resulting destination filename in case of
1141 * A string containing the directory $source should be copied to. If this
1142 * value is omitted, Drupal's 'files' directory will be used.
1144 * Replace behavior when the destination file already exists.
1145 * - FILE_EXISTS_REPLACE: Replace the existing file.
1146 * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
1148 * - FILE_EXISTS_ERROR: Do nothing and return FALSE.
1150 * TRUE for success, FALSE for failure.
1152 function advagg_file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME
) {
1153 $path_original = is_object($source) ?
$source->filepath
: $source;
1155 if (advagg_file_copy($source, $dest, $replace)) {
1156 $path_current = is_object($source) ?
$source->filepath
: $source;
1158 if ($path_original == $path_current || file_delete($path_original)) {
1161 drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error');
1167 * Copies a file to a new location.
1171 * This is a powerful function that in many ways performs like an advanced
1172 * version of copy().
1173 * - Checks if $source and $dest are valid and readable/writable.
1174 * - Performs a file copy if $source is not equal to $dest.
1175 * - If file already exists in $dest either the call will error out, replace the
1176 * file or rename the file based on the $replace parameter.
1179 * Either a string specifying the file location of the original file or an
1180 * object containing a 'filepath' property. This parameter is passed by
1181 * reference and will contain the resulting destination filename in case of
1184 * A string containing the directory $source should be copied to. If this
1185 * value is omitted, Drupal's 'files' directory will be used.
1187 * Replace behavior when the destination file already exists.
1188 * - FILE_EXISTS_REPLACE: Replace the existing file.
1189 * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
1191 * - FILE_EXISTS_ERROR: Do nothing and return FALSE.
1193 * TRUE for success, FALSE for failure.
1195 function advagg_file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME
) {
1196 $directory = dirname($dest);
1198 // Process a file upload object.
1199 if (is_object($source)) {
1201 $source = $file->filepath
;
1203 $basename = $file->filename
;
1207 $source = realpath($source);
1208 advagg_clearstatcache(TRUE
, $source);
1209 if (!file_exists($source)) {
1210 drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error');
1214 // If the destination file is not specified then use the filename of the source file.
1215 $basename = basename($dest);
1216 $basename = $basename ?
$basename : basename($source);
1217 $dest = $directory .
'/' .
$basename;
1219 // Make sure source and destination filenames are not the same, makes no sense
1220 // to copy it if they are. In fact copying the file will most likely result in
1221 // a 0 byte file. Which is bad. Real bad.
1222 if ($source != realpath($dest)) {
1223 if (!$dest = file_destination($dest, $replace)) {
1224 drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $source)), 'error');
1228 if (!@
copy($source, $dest)) {
1229 drupal_set_message(t('The selected file %file could not be copied. ' .
$dest, array('%file' => $source)), 'error');
1233 // Give everyone read access so that FTP'd users or
1234 // non-webserver users can see/read these files,
1235 // and give group write permissions so group members
1236 // can alter files uploaded by the webserver.
1237 @
chmod($dest, 0664);
1240 if (isset($file) && is_object($file)) {
1241 $file->filename
= $basename;
1242 $file->filepath
= $dest;
1249 return 1; // Everything went ok.
1253 * Generate a checksum for a given filename.
1260 function advagg_checksum($filename) {
1261 advagg_clearstatcache(TRUE
, $filename);
1262 if (file_exists($filename)) {
1263 $mode = variable_get('advagg_checksum_mode', ADVAGG_CHECKSUM_MODE
);
1264 if ($mode == 'mtime') {
1265 $checksum = @
filemtime($filename);
1266 if ($checksum === FALSE
) {
1268 advagg_clearstatcache(TRUE
, $filename);
1269 $checksum = @
filemtime($filename);
1270 // Use md5 as a last option.
1271 if ($checksum === FALSE
) {
1272 $checksum = md5(file_get_contents($filename));
1276 elseif ($mode = 'md5') {
1277 $checksum = md5(file_get_contents($filename));
1287 * See if this bundle has been built.
1292 * Boolean indicating if the bundle already exists.
1294 function advagg_bundle_built($filepath) {
1295 // Don't use the cache if not selected.
1296 if (!variable_get('advagg_bundle_built_mode', ADVAGG_BUNDLE_BUILT_MODE
)) {
1297 advagg_clearstatcache(TRUE
, $filepath);
1298 return file_exists($filepath);
1301 $data = advagg_get_bundle_from_filename(basename($filepath));
1302 if (is_array($data)) {
1303 list($type, $md5, $counter) = $data;
1309 $data = cache_get($filepath, 'cache_advagg');
1310 if (isset($data->data
)) {
1311 // Refresh timestamp if older then 12 hours.
1312 if (time() - $data->data
> variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL
)) {
1313 cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT
);
1314 db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5);
1319 // If not in cache check disk.
1320 advagg_clearstatcache(TRUE
, $filepath);
1321 if (file_exists($filepath)) {
1322 if (@
filesize($filepath) == 0) {
1329 // File existed on disk; place in cache.
1330 cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT
);
1331 db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5);
1335 function advagg_get_bundle_from_filename($filename) {
1336 // Verify requested filename has the correct pattern.
1337 if (!preg_match('/^(j|cs)s_[0-9a-f]{32}_\d+\.(j|cs)s$/', $filename)) {
1338 return t('Wrong Pattern.');
1342 $type = substr($filename, 0, strpos($filename, '_'));
1345 $ext = substr($filename, strpos($filename, '.', 37)+1);
1347 // Make sure extension is the same as the type.
1348 if ($ext != $type) {
1349 return t('Type does not match extension.');
1352 // Extract info from wanted filename.
1353 if ($type == 'css') {
1354 $md5 = substr($filename, 4, 32);
1355 $counter = substr($filename, 37, strpos($filename, '.', 38)-37);
1357 elseif ($type == 'js') {
1358 $md5 = substr($filename, 3, 32);
1359 $counter = substr($filename, 36, strpos($filename, '.', 37)-36);
1362 return t('Wrong file type.');
1365 return array($type, $md5, $counter);
1369 * Implementation of hook_flush_caches().
1371 function advagg_flush_caches() {
1372 // Try to allocate enough time to flush the cache
1373 if (function_exists('set_time_limit')) {
1374 @
set_time_limit(240);
1378 // Only one advagg cache flusher can run at a time.
1379 if (!lock_acquire('advagg_flush_caches')) {
1383 // Only run code below if the advagg db tables exist.
1384 if (!db_table_exists('advagg_files')) {
1385 return array('cache_advagg_bundle_reuse');
1388 // Find files that have changed.
1389 $needs_refreshing = array();
1390 $results = db_query("SELECT * FROM {advagg_files}");
1391 while ($row = db_fetch_array($results)) {
1392 $checksum = advagg_checksum($row['filename']);
1393 // Let other modules see if the bundles needs to be rebuilt.
1394 // hook_advagg_files_table
1395 // Return TRUE in order to increment the counter.
1396 $hook_results = module_invoke_all('advagg_files_table', $row, $checksum);
1398 // Check each return value; see if an update is needed.
1400 if (!empty($hook_results)) {
1401 foreach ($hook_results as
$update) {
1402 if ($update === TRUE
) {
1408 // Increment the counter if needed and mark file for bundle refreshment.
1409 if ($checksum != $row['checksum'] || $update == TRUE
) {
1410 $needs_refreshing[$row['filename_md5']] = $row['filename'];
1411 // Update checksum; increment counter.
1412 db_query("UPDATE {advagg_files} SET checksum = '%s', counter = counter + 1 WHERE filename_md5 = '%s'", $checksum, $row['filename_md5']);
1418 foreach ($needs_refreshing as
$filename_md5 => $filename) {
1419 $results = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s'", $filename_md5);
1420 while ($row = db_fetch_array($results)) {
1421 $bundles[$row['bundle_md5']] = $row['bundle_md5'];
1425 foreach ($bundles as
$bundle_md5) {
1426 // Increment Counter
1427 db_query("UPDATE {advagg_bundles} SET counter = counter + 1, timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5);
1429 if (variable_get('advagg_rebuild_on_flush', ADVAGG_REBUILD_ON_FLUSH
)) {
1430 // Rebuild bundles on shutdown in the background. This is needed so that
1431 // the cache_advagg_bundle_reuse table has been cleared.
1432 register_shutdown_function('advagg_rebuild_bundle', $bundle_md5, '', TRUE
);
1435 $_advagg['bundles'] = $bundles;
1436 $_advagg['files'] = $needs_refreshing;
1438 // Garbage collection
1439 list($css_path, $js_path) = advagg_get_root_files_dir();
1440 file_scan_directory($css_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE
);
1441 file_scan_directory($js_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE
);
1443 lock_release('advagg_flush_caches');
1444 return array('cache_advagg_bundle_reuse');
1450 * @param $bundle_md5
1451 * Bundle's machine name.
1455 * Rebuild even if file already exists.
1457 function advagg_rebuild_bundle($bundle_md5, $counter = '', $force = FALSE
) {
1458 global $conf, $_advagg;
1459 list($filetype, $files) = advagg_get_files_in_bundle($bundle_md5);
1461 $conf['advagg_async_generation'] = FALSE
;
1462 $good = advagg_css_js_file_builder($filetype, $files, '', $counter, $force, $bundle_md5);
1464 watchdog('advagg', 'This bundle could not be generated correctly. Bundle MD5: %md5', array('%md5' => $bundle_md5));
1467 $_advagg['rebuilt'][] = $bundle_md5;
1473 * Get list of files and the filetype given a bundle md5.
1475 * @param $bundle_md5
1476 * Bundle's machine name.
1478 * array ($filetype, $files)
1480 function advagg_get_files_in_bundle($bundle_md5) {
1483 $results = db_query("SELECT filename, filetype FROM {advagg_files} AS af INNER JOIN {advagg_bundles} AS ab USING ( filename_md5 ) WHERE bundle_md5 = '%s' ORDER BY porder ASC", $bundle_md5);
1484 while ($row = db_fetch_array($results)) {
1485 $files[] = $row['filename'];
1486 $filetype = $row['filetype'];
1489 return array($filetype, $files);
1493 * Callback to delete files modified more than a set time ago.
1496 * name of a file to check how old it is.
1498 function advagg_delete_file_if_stale($filename) {
1499 // Do not process .gz files
1500 if (strpos($filename, '.gz') !== FALSE
) {
1504 $file_last_mod = variable_get('advagg_stale_file_threshold', ADVAGG_STALE_FILE_THRESHOLD
);
1505 $file_last_used = variable_get('advagg_stale_file_last_used_threshold', ADVAGG_STALE_FILE_LAST_USED_THRESHOLD
);
1507 // Default stale file threshold is 30 days.
1508 advagg_clearstatcache(TRUE
, $filename);
1509 if ($now - filemtime($filename) <= $file_last_mod) {
1513 // Check to see if this file is still in use.
1514 $data = cache_get($filename, 'cache_advagg');
1515 if (!empty($data->data
)) {
1516 advagg_clearstatcache(TRUE
, $filename);
1517 $file_last_a = @
fileatime($filename);
1518 $file_last_agz = @
fileatime($filename .
'.gz');
1519 $file_last_a = max($file_last_a, $file_last_agz);
1520 if ($now - $data->data
> $file_last_used && $now - $file_last_a > $file_last_used) {
1521 // Delete file if it hasn't been used in the last 15 days.
1522 file_delete($filename);
1523 file_delete($filename .
'.gz');
1526 // Touch file so we don't check again for another 30 days
1531 // Delete file if it is not in the cache.
1532 file_delete($filename);
1533 file_delete($filename .
'.gz');
1538 * Get data about a file.
1540 * @param $filename_md5
1543 * data array from database.
1545 function advagg_get_file_data($filename_md5) {
1546 $data = cache_get($filename_md5, 'cache_advagg_files_data');
1547 if (empty($data->data
)) {
1554 * Set data about a file.
1556 * @param $filename_md5
1561 function advagg_set_file_data($filename_md5, $data) {
1562 cache_set($filename_md5, $data, 'cache_advagg_files_data', CACHE_PERMANENT
);
1566 * Given path output uri to that file
1568 * @param $filename_md5
1573 function advagg_build_uri($path) {
1574 $original_path = $path;
1576 if (module_exists('cdn')) {
1577 $status = variable_get(CDN_STATUS_VARIABLE
, CDN_DISABLED
);
1578 if ($status == CDN_ENABLED
|| ($status == CDN_TESTING
&& user_access(CDN_PERM_ACCESS_TESTING
))) {
1579 // Alter URL when the file_create_url() patch is not there.
1580 if (variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE
, FALSE
)) {
1581 cdn_file_url_alter($path);
1583 // Use the patched version of file_create_url().
1585 $path = file_create_url($path);
1587 if (strcmp($original_path, $path) != 0) {
1592 return base_path() .
$path;
1596 * ***MODIFIED CORE FUNCTIONS BELOW***
1598 * @see drupal_get_css()
1599 * @see drupal_build_css_cache()
1600 * @see drupal_get_js()
1601 * @see drupal_build_js_cache()
1605 * Returns an array of values needed for aggregation
1608 * (optional) Bool indicating that aggregation should be disabled if TRUE.
1610 * array of values to be imported via list() function.
1612 function advagg_process_css_js_prep($noagg = FALSE
) {
1613 $preprocess = (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE
!= 'update');
1614 if ($noagg || (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation'))) {
1615 $preprocess = FALSE
;
1618 $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC
) == FILE_DOWNLOADS_PUBLIC
);
1619 if (!$public_downloads) {
1620 $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR
);
1621 if (!empty($custom_path)) {
1622 $public_downloads = TRUE
;
1626 // A dummy query-string is added to filenames, to gain control over
1627 // browser-caching. The string changes on every update or full cache
1628 // flush, forcing browsers to load a new copy of the files, as the
1630 $query_string = '?'.
substr(variable_get('css_js_query_string', '0'), 0, 1);
1632 return array($preprocess, $public_downloads, $query_string);
1637 * Returns a themed representation of all stylesheets that should be attached to
1640 * @see drupal_get_css()
1642 * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
1643 * This ensures proper cascading of styles so themes can easily override
1644 * module styles through CSS selectors.
1646 * Themes may replace module-defined CSS files by adding a stylesheet with the
1647 * same filename. For example, themes/garland/system-menus.css would replace
1648 * modules/system/system-menus.css. This allows themes to override complete
1649 * CSS files, rather than specific selectors, when necessary.
1651 * If the original CSS file is being overridden by a theme, the theme is
1652 * responsible for supplying an accompanying RTL CSS file to replace the
1656 * (optional) An array of CSS files. If no array is provided, the default
1657 * stylesheets array is used instead.
1659 * (optional) Bool indicating that aggregation should be disabled if TRUE.
1661 * A string of XHTML CSS tags.
1663 function advagg_process_css($css = NULL
, $noagg = FALSE
) {
1665 $original_css = $css;
1667 $css = drupal_add_css();
1674 list($preprocess_css, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg);
1676 // Invoke hook_advagg_css_pre_alter() to give installed modules a chance to
1677 // modify the data in the $javascript array if necessary.
1678 drupal_alter('advagg_css_pre', $css, $preprocess_css, $public_downloads);
1681 $external_no_preprocess = array();
1682 $module_no_preprocess = array();
1683 $output_no_preprocess = array();
1684 $output_preprocess = array();
1685 $theme_no_preprocess = array();
1686 $inline_no_preprocess = array();
1687 $files_included = array();
1688 $files_aggregates_included = array();
1689 $inline_included = array();
1692 foreach ($css as
$media => $types) {
1693 // Setup some variables
1694 $files_included[$media] = array();
1695 $files_aggregates_included[$media] = array();
1696 $inline_included[$media] = array();
1698 // If CSS preprocessing is off, we still need to output the styles.
1699 // Additionally, go through any remaining styles if CSS preprocessing is on
1700 // and output the non-cached ones.
1701 foreach ($types as
$type => $files) {
1702 if ($type == 'module') {
1703 // Setup theme overrides for module styles.
1704 $theme_styles = array();
1705 foreach (array_keys($css[$media]['theme']) as
$theme_style) {
1706 $theme_styles[] = basename($theme_style);
1709 foreach ($types[$type] as
$file => $preprocess) {
1710 // If the theme supplies its own style using the name of the module
1711 // style, skip its inclusion. This includes any RTL styles associated
1712 // with its main LTR counterpart.
1713 if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) {
1714 // Unset the file to prevent its inclusion when CSS aggregation is enabled.
1715 unset($types[$type][$file]);
1718 // If a CSS file is not to be preprocessed and it's an external
1719 // CSS file, it needs to *always* appear at the *very top*,
1720 // regardless of whether preprocessing is on or off.
1721 if ($type == 'external') {
1722 $external_no_preprocess[] = array(
1728 $files_included[$media][$file] = TRUE
;
1729 // Unset the file to prevent its inclusion.
1730 unset($types[$type][$file]);
1733 // If a CSS file is not to be preprocessed and it's an inline CSS blob
1734 // it needs to *always* appear at the *very bottom*.
1735 if ($type == 'inline') {
1736 if (is_array($preprocess)) {
1737 foreach ($preprocess as
$suffix => $blob) {
1738 $blob = advagg_drupal_load_stylesheet_content($blob, $preprocess);
1739 // Invoke hook_advagg_css_inline_alter() to give installed modules
1740 // a chance to modify the contents of $blob if necessary.
1741 drupal_alter('advagg_css_inline', $blob);
1743 $inline_no_preprocess[] = array(
1747 'suffix' => $suffix,
1749 $inline_included[$media][] = $blob;
1753 $file = advagg_drupal_load_stylesheet_content($file, $preprocess);
1754 // Invoke hook_advagg_css_inline_alter() to give installed modules a
1755 // chance to modify the contents of $file if necessary.
1756 drupal_alter('advagg_css_inline', $file);
1758 $inline_no_preprocess[] = array(
1764 $inline_included[$media][] = $file;
1766 // Unset to prevent its inclusion.
1767 unset($types[$type][$file]);
1771 // Only include the stylesheet if it exists.
1772 if (advagg_file_exists($file)) {
1773 if (!$preprocess || !($public_downloads && $preprocess_css)) {
1774 // Create URI for file.
1775 $file_uri = advagg_build_uri($file) .
$query_string;
1776 $files_included[$media][$file] = $preprocess;
1777 // If a CSS file is not to be preprocessed and it's a module CSS
1778 // file, it needs to *always* appear at the *top*, regardless of
1779 // whether preprocessing is on or off.
1780 if (!$preprocess && $type == 'module') {
1781 $module_no_preprocess[] = array(
1783 'href' => $file_uri,
1788 // If a CSS file is not to be preprocessed and it's a theme CSS
1789 // file, it needs to *always* appear at the *bottom*, regardless of
1790 // whether preprocessing is on or off.
1791 elseif (!$preprocess && $type == 'theme') {
1792 $theme_no_preprocess[] = array(
1794 'href' => $file_uri,
1801 $output_no_preprocess[] = array(
1803 'href' => $file_uri,
1813 if ($public_downloads && $preprocess_css) {
1814 $files_aggregates_included[$media] = $files_included[$media];
1816 foreach ($types as
$type) {
1817 foreach ($type as
$file => $cache) {
1820 $files_included[$media][$file] = TRUE
;
1821 unset($files_aggregates_included[$file]);
1825 $preprocess_files = advagg_css_js_file_builder('css', $files, $query_string);
1828 foreach ($preprocess_files as
$preprocess_file => $extra) {
1829 // Empty aggregate, skip
1830 if (empty($preprocess_file)) {
1834 if ($extra !== FALSE
&& is_array($extra)) {
1835 $prefix = $extra['prefix'];
1836 $suffix = $extra['suffix'];
1837 $output_preprocess[] = array(
1839 'href' => advagg_build_uri($preprocess_file),
1840 'prefix' => $prefix,
1841 'suffix' => $suffix,
1843 $files_aggregates_included[$media][$preprocess_file] = $extra;
1851 // Redo with aggregation turned off and return the new value.
1852 watchdog('advagg', 'CSS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR
);
1853 $data = advagg_process_css($original_css, TRUE
);
1859 // Default function called: advagg_unlimited_css_builder
1860 $function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION
);
1861 return $function($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included);
1865 * Logic to figure out what kind of css tags to use.
1867 * @param $external_no_preprocess
1868 * array of css files ($media, $href)
1869 * @param $module_no_preprocess
1870 * array of css files ($media, $href)
1871 * @param $output_no_preprocess
1872 * array of css files ($media, $href)
1873 * @param $output_preprocess
1874 * array of css files ($media, $href, $prefix, $suffix)
1875 * @param $theme_no_preprocess
1876 * array of css files ($media, $href)
1877 * @param $inline_no_preprocess
1878 * array of css data to inline ($media, $data)
1879 * @param $inline_included
1880 * array of inline css included. $a[$media][] = $datablob;
1881 * @param $files_included
1882 * array of css files included. $a[$media][] = $filename
1883 * @param $files_aggregates_included
1884 * array of css files & aggregates included. $a[$media][] = $filename
1886 * html for loading the css. html for the head.
1888 function advagg_unlimited_css_builder($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $files_included, $files_aggregates_included, $inline_included) {
1891 $files = array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess);
1893 // Select method for css html output
1894 if (count($files) < variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD
)) {
1895 advagg_unlimited_css_traditional($files, $styles);
1897 elseif (variable_get('advagg_css_logged_in_ie_detect', ADVAGG_CSS_LOGGED_IN_IE_DETECT
) && $user->uid
!= 0) {
1898 // Detect IE browsers here
1900 if (isset($_SERVER['HTTP_USER_AGENT'])) {
1901 // Strings for testing found via
1902 // http://chrisschuld.com/projects/browser-php-detecting-a-users-browser-from-php/
1903 // Test for v1 - v1.5 IE
1904 // Test for versions > 1.5
1905 // Test for Pocket IE
1906 if ( stristr($_SERVER['HTTP_USER_AGENT'], 'microsoft internet explorer')
1907 || stristr($_SERVER['HTTP_USER_AGENT'], 'msie')
1908 || stristr($_SERVER['HTTP_USER_AGENT'], 'mspie')
1913 // Play Safe and treat as IE if user agent is not set
1919 advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles);
1920 advagg_unlimited_css_import($output_preprocess, $styles);
1921 advagg_unlimited_css_import($theme_no_preprocess, $styles);
1922 advagg_unlimited_css_traditional($inline_no_preprocess, $styles);
1925 advagg_unlimited_css_traditional($files, $styles);
1929 advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles);
1930 advagg_unlimited_css_import($output_preprocess, $styles);
1931 advagg_unlimited_css_import($theme_no_preprocess, $styles);
1932 advagg_unlimited_css_traditional($inline_no_preprocess, $styles);
1939 * Use link tags for CSS
1942 * array of css files ($media, $href, $prefix, $suffix)
1946 function advagg_unlimited_css_traditional($files, &$styles) {
1949 foreach ($files as
$css_file) {
1950 $media = $css_file['media'];
1951 $prefix = empty($css_file['prefix']) ?
'' : $css_file['prefix'] .
"\n";
1952 $suffix = empty($css_file['suffix']) ?
'' : $css_file['suffix'];
1954 // Group prefixes and suffixes.
1955 if (isset($css_file['href'])) {
1956 $href = $css_file['href'];
1957 if ($prefix != $last_prefix) {
1958 $styles .
= $last_suffix .
"\n" .
$prefix .
'<link type="text/css" rel="stylesheet" media="'.
$media .
'" href="'.
$href .
'" />' .
"\n";
1961 $styles .
= '<link type="text/css" rel="stylesheet" media="'.
$media .
'" href="'.
$href .
'" />' .
"\n";
1965 $data = $css_file['data'];
1966 if ($prefix != $last_prefix) {
1967 $styles .
= $last_suffix .
"\n" .
$prefix .
'<style type="text/css" media="'.
$media .
'">' .
"\n" .
$data .
"\n" .
'</style>' .
"\n";
1970 $styles .
= '<style type="text/css" media="'.
$media .
'">' .
"\n" .
$data .
"\n" .
'</style>' .
"\n";
1973 $last_prefix = $prefix;
1974 $last_suffix = $suffix;
1976 $styles .
= $last_suffix .
"\n";
1980 * Use import tags for CSS
1983 * array of css files ($media, $href)
1987 function advagg_unlimited_css_import($files, &$styles) {
1991 foreach ($files as
$css_file) {
1992 $media_new = $css_file['media'];
1993 $href = $css_file['href'];
1994 if ($media_new != $media || $counter > variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD
)) {
1995 if ($media && !empty($import)) {
1996 $styles .
= "\n" .
'<style type="text/css" media="'.
$media .
'">' .
"\n".
$import .
'</style>';
2000 $media = $media_new;
2002 $import .
= '@import "'.
$href .
'";'.
"\n";
2005 if ($media && !empty($import)) {
2006 $styles .
= "\n".
'<style type="text/css" media="'.
$media .
'">'.
"\n".
$import .
'</style>';
2011 * Returns a themed presentation of all JavaScript code for the current page.
2013 * @see drupal_get_js()
2015 * References to JavaScript files are placed in a certain order: first, all
2016 * 'core' files, then all 'module' and finally all 'theme' JavaScript files
2017 * are added to the page. Then, all settings are output, followed by 'inline'
2018 * JavaScript code. If running update.php, all preprocessing is disabled.
2021 * An array with all JavaScript code. Key it the region
2023 * (optional) Bool indicating that aggregation should be disabled if TRUE.
2025 * All JavaScript code segments and includes for the scope as HTML tags.
2027 function advagg_process_js($master_set, $noagg = FALSE
) {
2029 if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE
!= 'update') && function_exists('locale_update_js_files')) {
2030 locale_update_js_files();
2034 list($preprocess_js, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg);
2037 foreach ($master_set as
$scope => $javascript) {
2038 if ($scope != 'header' && $scope != 'footer' && empty($javascript)) {
2042 // Invoke hook_advagg_js_pre_alter() to give installed modules a chance to
2043 // modify the data in the $javascript array if necessary.
2044 drupal_alter('advagg_js_pre', $javascript, $preprocess_js, $public_downloads, $scope);
2045 $master_set[$scope] = $javascript;
2048 // Invoke hook_advagg_js_header_footer_alter() to give installed modules a chance to
2049 // modify the data in the header and footer JS if necessary.
2050 drupal_alter('advagg_js_header_footer', $master_set, $preprocess_js, $public_downloads);
2052 foreach ($master_set as
$scope => $javascript) {
2053 if (empty($javascript)) {
2058 $setting_no_preprocess = array();
2059 $inline_no_preprocess = array();
2060 $external_no_preprocess = array();
2061 $output_no_preprocess = array('core' => array(), 'module' => array(), 'theme' => array());
2062 $output_preprocess = array();
2063 $preprocess_list = array();
2064 $js_settings_array = array();
2065 $inline_included = array();
2066 $files_included = array();
2067 $files_aggregates_included = array();
2070 foreach ($javascript as
$type => $data) {
2077 $data = call_user_func_array('array_merge_recursive', $data);
2078 $js_settings_array[] = $data;
2079 $js_settings = advagg_drupal_to_js($data);
2080 $js_settings = preg_replace(array('/"DRUPAL_JS_RAW\:/', '/\:DRUPAL_JS_RAW"/'), array('', ''), $js_settings);
2081 $setting_no_preprocess[] = 'jQuery.extend(Drupal.settings, ' .
$js_settings .
");";
2085 foreach ($data as
$info) {
2086 // Invoke hook_advagg_js_inline_alter() to give installed modules a
2087 // chance to modify the contents of $info['code'] if necessary.
2088 drupal_alter('advagg_js_inline', $info['code']);
2089 $inline_no_preprocess[] = array($info['code'], $info['defer']);
2090 $inline_included[] = $info['code'];
2095 foreach ($data as
$path => $info) {
2096 $external_no_preprocess[] = array($path, $info['defer']);
2097 $files_included[$path] = TRUE
;
2102 // If JS preprocessing is off, we still need to output the scripts.
2103 // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
2104 foreach ($data as
$path => $info) {
2105 if (!$info['preprocess'] || !$public_downloads || !$preprocess_js) {
2106 $output_no_preprocess[$type][] = array(advagg_build_uri($path) .
($info['cache'] ?
$query_string : '?'.
time()), $info['defer']);
2107 $files_included[$path] = $info['preprocess'];
2110 $preprocess_list[$path] = $info;
2116 // Aggregate any remaining JS files that haven't already been output.
2117 if ($public_downloads && $preprocess_js && count($preprocess_list) > 0) {
2118 $files_aggregates_included = $files_included;
2120 foreach ($preprocess_list as
$path => $info) {
2121 if ($info['preprocess']) {
2123 $files_included[$path] = TRUE
;
2126 $preprocess_files = advagg_css_js_file_builder('js', $files, $query_string);
2128 foreach ($preprocess_files as
$preprocess_file => $extra) {
2129 // Empty aggregate, skip
2130 if (empty($preprocess_file)) {
2134 if ($extra !== FALSE
&& is_array($extra)) {
2135 $prefix = $extra['prefix'];
2136 $suffix = $extra['suffix'];
2137 $output_preprocess[] = array(advagg_build_uri($preprocess_file), $prefix, $suffix);
2138 $files_aggregates_included[$preprocess_file] = $extra;
2146 // Redo with aggregation turned off and return the new value.
2147 watchdog('advagg', 'JS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR
);
2148 $data = advagg_process_js($master_set, TRUE
);
2153 // Default function called: advagg_js_builder
2154 $function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION
);
2155 $output[$scope] = $function($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included);
2161 * Build and theme JS output for header.
2163 * @param $external_no_preprocess
2164 * array(array($src, $defer))
2165 * @param $output_preprocess
2166 * array(array($src, $prefix, $suffix))
2167 * @param $output_no_preprocess
2168 * array(array(array($src, $defer)))
2169 * @param $setting_no_preprocess
2170 * array(array($code))
2171 * @param $inline_no_preprocess
2172 * array(array($code, $defer))
2175 * @param $js_settings_array
2176 * array of settings used.
2177 * @param $inline_included
2178 * array of inline scripts used.
2179 * @param $files_included
2180 * array of files used.
2181 * @param $files_aggregates_included
2182 * array of files and aggregates used.
2184 * String of themed JavaScript.
2186 function advagg_js_builder($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) {
2189 // For inline Javascript to validate as XHTML, all Javascript containing
2190 // XHTML needs to be wrapped in CDATA. To make that backwards compatible
2191 // with HTML 4, we need to comment out the CDATA-tag.
2192 $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
2193 $embed_suffix = "\n//--><!]]>\n";
2195 // Keep the order of JS files consistent as some are preprocessed and others are not.
2196 // Make sure any inline or JS setting variables appear last after libraries have loaded.
2198 if (!empty($external_no_preprocess)) {
2199 foreach ($external_no_preprocess as
$values) {
2200 list ($src, $defer) = $values;
2201 $output .
= '<script type="text/javascript"'.
($defer ?
' defer="defer"' : '') .
' src="'.
$src .
"\"></script>\n";
2205 if (!empty($output_preprocess)) {
2206 foreach ($output_preprocess as
$values) {
2207 list ($src, $prefix, $suffix) = $values;
2208 $output .
= $prefix .
'<script type="text/javascript" src="'.
$src .
'"></script>' .
$suffix .
"\n";
2212 foreach ($output_no_preprocess as
$type => $list) {
2213 if (!empty($list)) {
2214 foreach ($list as
$values) {
2215 list ($src, $defer) = $values;
2216 $output .
= '<script type="text/javascript"' .
($defer ?
' defer="defer"' : '') .
' src="' .
$src .
"\"></script>\n";
2221 if (!empty($setting_no_preprocess)) {
2222 foreach ($setting_no_preprocess as
$code) {
2223 $output .
= '<script type="text/javascript">' .
$embed_prefix .
$code .
$embed_suffix .
"</script>\n";
2227 if (!empty($inline_no_preprocess)) {
2228 foreach ($inline_no_preprocess as
$values) {
2229 list ($code, $defer) = $values;
2230 $output .
= '<script type="text/javascript"'.
($defer ?
' defer="defer"' : '') .
'>' .
$embed_prefix .
$code .
$embed_suffix .
"</script>\n";
2238 * Always return TRUE, used for array_map in advagg_css_js_file_builder().
2240 function advagg_return_true() {
2245 * Disable the page cache if the aggregate is not in the bundle.
2247 function advagg_disable_page_cache() {
2249 if (variable_get('advagg_page_cache_mode', ADVAGG_PAGE_CACHE_MODE
)) {
2250 $conf['cache'] = CACHE_DISABLED
;
2252 // Invoke hook_advagg_disable_page_cache(). Allows 3rd party page cache
2253 // plugins like boost or varnish to not cache this page.
2254 module_invoke_all('advagg_disable_page_cache');
2259 * Aggregate CSS/JS files, putting them in the files directory.
2261 * @see drupal_build_js_cache()
2262 * @see drupal_build_css_cache()
2267 * An array of JS files to aggregate and compress into one file.
2268 * @param $query_string
2269 * (optional) Query string to add on to the file if bundle isn't ready.
2271 * (optional) Counter value.
2273 * (optional) Rebuild even if file already exists.
2275 * (optional) Bundle's machine name.
2277 * array with the filepath as the key and prefix and suffix in another array.
2279 function advagg_css_js_file_builder($type, $files, $query_string = '', $counter = FALSE
, $force = FALSE
, $md5 = '') {
2280 global $_advagg, $base_path;
2283 // Try cache first. When ever the counter changes this cache gets reset.
2284 $cached_data_key = 'advagg_file_builder_' .
md5(implode('', array_filter(array_unique($files))));
2286 // Try cache first; cache table is cache_advagg_bundle_reuse.
2287 $cached_data = advagg_cached_bundle_get($cached_data_key, 'file_builder_cache_object');
2288 if (!empty($cached_data)) {
2289 foreach ($cached_data as
$filepath => $values) {
2291 advagg_bundle_built($filepath);
2293 return $cached_data;
2297 list($css_path, $js_path) = advagg_get_root_files_dir();
2298 if ($type == 'js') {
2299 $file_type_path = $js_path;
2301 if ($type == 'css') {
2302 $file_type_path = $css_path;
2305 // Send $files, get filename back
2306 $filenames = advagg_get_filename($files, $type, $counter, $md5);
2309 if (variable_get('advagg_debug', ADVAGG_DEBUG
)) {
2310 $_advagg['debug']['file_builder_get_filenames'][] = array(
2311 'key' => $cached_data_key,
2312 'filenames' => $filenames,
2318 $files_used = array();
2319 foreach ($filenames as
$info) {
2320 $filename = $info['filename'];
2321 $files = $info['files'];
2322 $bundle_md5 = $info['bundle_md5'];
2325 $filepath = $file_type_path .
'/'.
$filename;
2327 // Invoke hook_advagg_js_extra_alter() or hook_advagg_css_extra_alter to
2328 // give installed modules a chance to modify the prefix or suffix for a
2330 $values = array($filename, $bundle_md5, $prefix, $suffix);
2331 drupal_alter('advagg_' .
$type .
'_extra', $values);
2332 list($filename, $bundle_md5, $prefix, $suffix) = $values;
2334 // Check that the file exists & filesize is not zero
2335 $built = advagg_bundle_built($filepath);
2337 if (!$built || $force) {
2338 // Generate on request?
2339 if (variable_get('advagg_async_generation', ADVAGG_ASYNC_GENERATION
) && !$force) {
2341 $url = _advagg_build_url($filepath .
'?generator=1');
2343 'Host' => $_SERVER['HTTP_HOST'],
2347 if (function_exists('stream_socket_client') && function_exists('stream_select')) {
2348 advagg_async_connect_http_request($url, array('headers' => $headers));
2352 $socket_timeout = ini_set('default_socket_timeout', variable_get('advagg_socket_timeout', ADVAGG_SOCKET_TIMEOUT
));
2353 drupal_http_request($url, $headers, 'GET');
2354 ini_set('default_socket_timeout', $socket_timeout);
2357 // Return filepath if we are going to wait for the bundle to be
2358 // generated or if the bundle already exists.
2359 if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE
) < 2 || advagg_bundle_built($filepath)) {
2360 $output[$filepath] = array('prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)));
2363 // Aggregate isn't built yet, send back the files that where going to
2365 foreach ($files as
$file) {
2366 $output[$file .
$query_string] = array('prefix' => '', 'suffix' => '', 'files' => array($file .
$query_string => TRUE
));
2369 advagg_disable_page_cache();
2374 // Only generate once.
2375 $lock_name = 'advagg_' .
$filename;
2376 if (!lock_acquire($lock_name)) {
2377 if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE
) == 0 ) {
2378 $locks[] = array($lock_name => $filepath);
2379 $output[$filepath] = array('prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)));
2382 // Aggregate isn't built yet, send back the files that where going
2384 foreach ($files as
$file) {
2385 $output[$file .
$query_string] = array('prefix' => '', 'suffix' => '', 'files' => array($file .
$query_string => TRUE
));
2388 advagg_disable_page_cache();
2393 if ($type == 'css') {
2394 $data = advagg_build_css_bundle($files);
2396 elseif ($type == 'js') {
2397 $data = advagg_build_js_bundle($files);
2400 // Invoke hook_advagg_js_alter() or hook_advagg_css_alter to give
2401 // installed modules a chance to modify the data in the bundle if
2403 drupal_alter('advagg_' .
$type, $data, $files, $bundle_md5);
2404 $files_used = array_merge($files_used, $files);
2406 // If data is empty then do not include this bundle in the final output.
2407 if (empty($data) && !$force) {
2408 lock_release($lock_name);
2412 // Create the advagg_$type/ within the files folder.
2413 file_check_directory($file_type_path, FILE_CREATE_DIRECTORY
);
2415 // Write file. default function called: advagg_file_saver
2416 $function = variable_get('advagg_file_save_function', ADVAGG_FILE_SAVE_FUNCTION
);
2417 $good = $function($data, $filepath, $force, $type);
2420 lock_release($lock_name);
2422 // If file save was not good then downgrade to non aggregated mode.
2424 $output[$filepath] = FALSE
;
2430 $files_used = array_merge($files_used, $files);
2432 $output[$filepath] = array('prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)));
2435 // Wait for all locks before returning.
2436 if (!empty($locks)) {
2437 foreach ($locks as
$lock_name => $filepath) {
2438 lock_wait($lock_name);
2439 if (!advagg_bundle_built($filepath)) {
2440 $output[$filepath] = FALSE
;
2445 if (empty($output)) {
2451 if (!$force && $cacheable) {
2452 $new_cached_data_key = 'advagg_file_builder_' .
md5(implode('', array_filter(array_unique($files_used))));
2453 // Verify the files in equals the files out.
2454 if ($new_cached_data_key == $cached_data_key) {
2455 cache_set($cached_data_key, $output, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY
);
2463 * Given a list of files, grab their contents and glue it into one big string.
2466 * array of filenames.
2468 * string containing all the files.
2470 function advagg_build_css_bundle($files) {
2472 // Build aggregate CSS file.
2473 foreach ($files as
$file) {
2474 $contents = drupal_load_stylesheet($file, TRUE
);
2475 // Return the path to where this CSS file originated from.
2476 $base = base_path() .
dirname($file) .
'/';
2477 _drupal_build_css_path(NULL
, $base);
2478 // Prefix all paths within this CSS file, ignoring external and absolute paths.
2479 $data .
= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents);
2482 // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
2483 // @import rules must proceed any other style, so we move those to the top.
2484 $regexp = '/@import[^;]+;/i';
2485 preg_match_all($regexp, $data, $matches);
2486 $data = preg_replace($regexp, '', $data);
2487 $data = implode('', $matches[0]) .
$data;
2492 * Given a list of files, grab their contents and glue it into one big string.
2495 * array of filenames.
2497 * string containing all the files.
2499 function advagg_build_js_bundle($files) {
2500 if (empty($files)) {
2504 // Build aggregate JS file.
2505 foreach ($files as
$file) {
2506 // Append a ';' and a newline after each JS file to prevent them from running together.
2507 if (advagg_file_exists($file)) {
2508 $data .
= file_get_contents($file) .
";\n";
2515 * Use a cache table to see if a file exists.
2522 function advagg_file_exists($filename) {
2523 static
$files = array();
2524 if (empty($files)) {
2525 $data = cache_get('advagg_file_checksum', 'cache');
2526 if (empty($data->data
)) {
2527 $result = db_query("SELECT filename, checksum FROM {advagg_files}");
2528 while ($row = db_fetch_array($result)) {
2529 $files[$row['filename']] = $row['checksum'];
2531 cache_set('advagg_file_checksum', $files, 'cache', CACHE_TEMPORARY
);
2534 $files = $data->data
;
2537 if (!empty($files[$filename]) && $files[$filename] != -1) {
2541 advagg_clearstatcache(TRUE
, $filename);
2542 return file_exists($filename);
2547 * Send out a fast 404 and exit.
2549 function advagg_missing_fast404($msg = '') {
2551 if (!headers_sent()) {
2552 header($_SERVER['SERVER_PROTOCOL'] .
' 404 Not Found');
2553 header('X-AdvAgg: Failed Validation. ' .
$msg);
2556 print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' .
"\n";
2558 print '<head><title>404 Not Found</title></head>';
2559 print '<body><h1>Not Found</h1>';
2560 print '<p>The requested URL was not found on this server.</p>';
2561 print '<p><a href="' .
$base_path .
'">Home</a></p>';
2562 print '<!-- advagg_missing_fast404 -->';
2563 print '</body></html>';
2568 * Generate .htaccess rules and place them in advagg dir
2571 * destination of the file that just got saved.
2573 * force recreate the .htaccess file.
2575 function advagg_htaccess_check_generate($dest, $force = FALSE
) {
2577 if (!$force && !variable_get('advagg_dir_htaccess', ADVAGG_DIR_HTACCESS
)) {
2581 $dir = dirname($dest);
2582 $htaccess_file = $dir .
'/.htaccess';
2583 advagg_clearstatcache(TRUE
, $htaccess_file);
2584 if (!$force && file_exists($htaccess_file)) {
2588 list($css_path, $js_path) = advagg_get_root_files_dir();
2591 if ($dir == $js_path) {
2594 $type = 'text/javascript';
2596 elseif ($dir == $css_path) {
2606 if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION
)) {
2607 $data .
= "<IfModule mod_rewrite.c>\n";
2608 $data .
= " RewriteEngine on\n";
2609 $data .
= " RewriteBase ${base_path}${path}\n";
2611 $data .
= " # Send 404's back to index.php\n";
2612 $data .
= " RewriteCond %{REQUEST_FILENAME} !-s\n";
2613 $data .
= " RewriteRule ^(.*)$ ${base_path}index.php?q=$path/$1 [L]\n";
2615 $data .
= " # Rules to correctly serve gzip compressed $ext files.\n";
2616 $data .
= " # Requires both mod_rewrite and mod_headers to be enabled.\n";
2617 $data .
= " <IfModule mod_headers.c>\n";
2618 $data .
= " # Serve gzip compressed $ext files if they exist and client accepts gzip.\n";
2619 $data .
= " RewriteCond %{HTTP:Accept-encoding} gzip\n";
2620 $data .
= " RewriteCond %{REQUEST_FILENAME}\.gz -s\n";
2621 $data .
= " RewriteRule ^(.*)\.$ext$ $1\.$ext\.gz [QSA]\n";
2623 $data .
= " # Serve correct content types, and prevent mod_deflate double gzip.\n";
2624 $data .
= " RewriteRule \.$ext\.gz$ - [T=$type,E=no-gzip:1]\n";
2626 $data .
= " <FilesMatch \"\.$ext\.gz$\">\n";
2627 $data .
= " # Serve correct encoding type.\n";
2628 $data .
= " Header set Content-Encoding gzip\n";
2629 $data .
= " # Force proxies to cache gzipped & non-gzipped $ext files separately.\n";
2630 $data .
= " Header append Vary Accept-Encoding\n";
2631 $data .
= " </FilesMatch>\n";
2632 $data .
= " </IfModule>\n";
2633 $data .
= "</IfModule>\n";
2636 $data .
= "<FilesMatch \"^${ext}_[0-9a-f]{32}_.+\.$ext(\.gz)?\">\n";
2637 $data .
= " <IfModule mod_expires.c>\n";
2638 $data .
= " # Enable expirations.\n";
2639 $data .
= " ExpiresActive On\n";
2641 $data .
= " # Cache all aggregated $ext files for 1 year after access (A).\n";
2642 $data .
= " ExpiresDefault A31556926\n";
2643 $data .
= " </IfModule>\n";
2644 $data .
= " <IfModule mod_headers.c>\n";
2645 $data .
= " # Unset unnecessary headers.\n";
2646 $data .
= " Header unset Last-Modified\n";
2647 $data .
= " Header unset Pragma\n";
2649 $data .
= " # Make these files publicly cacheable.\n";
2650 $data .
= " Header append Cache-Control \"public\"\n";
2651 $data .
= " </IfModule>\n";
2652 $data .
= " FileETag MTime Size\n";
2653 $data .
= "</FilesMatch>\n";
2656 if (!advagg_file_save_data($data, $htaccess_file, FILE_EXISTS_REPLACE
)) {
2663 * Adds a CSS file to the stylesheet queue.
2666 * (optional) The CSS data that will be set. If not set then the inline CSS
2667 * array will be passed back.
2669 * (optional) The media type for the stylesheet, e.g., all, print, screen.
2671 * (optional) prefix to add before the inlined css.
2673 * (optional) suffix to add after the inlined css.
2675 * An array of CSS files.
2677 function advagg_add_css_inline($data = NULL
, $media = 'all', $prefix = NULL
, $suffix = NULL
) {
2678 static
$css = array();
2680 // Store inline data in a static.
2682 if (!isset($css[$media]['inline'][$prefix][$suffix])) {
2683 $css[$media]['inline'][$prefix][$suffix] = $data;
2686 $css[$media]['inline'][$prefix][$suffix] .
= "\n" .
$data;
2696 * Converts a PHP variable into its Javascript equivalent.
2698 * We use HTML-safe strings, i.e. with <, > and & escaped.
2700 function advagg_drupal_to_js($var) {
2702 if (!isset($php530)) {
2703 $php530 = version_compare(PHP_VERSION
, '5.3.0', '>=');
2706 // json_encode on PHP prior to PHP 5.3.0 doesn't support options.
2708 return json_encode($var, JSON_HEX_QUOT
| JSON_HEX_TAG
| JSON_HEX_AMP
| JSON_HEX_APOS
);
2711 // if json_encode exists, use it.
2712 if (function_exists('json_encode')) {
2713 return str_replace(array("<", ">", "&"), array('\u003c', '\u003e', '\u0026'), json_encode($var));
2716 switch (gettype($var)) {
2718 return $var ?
'true' : 'false'; // Lowercase necessary!
2724 // Always use Unicode escape sequences (\u0022) over JSON escape
2725 // sequences (\") to prevent browsers interpreting these as
2726 // special characters.
2727 $replace_pairs = array(
2728 // ", \ and U+0000 - U+001F must be escaped according to RFC 4627.
2763 // Prevent browsers from interpreting these as as special.
2768 // Prevent browsers from interpreting the solidus as special and
2769 // non-compliant JSON parsers from interpreting // as a comment.
2771 // While these are allowed unescaped according to ECMA-262, section
2772 // 15.12.2, they cause problems in some JSON parsers.
2773 "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator.
2774 "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator.
2777 return '"' .
strtr($var, $replace_pairs) .
'"';
2779 // Arrays in JSON can't be associative. If the array is empty or if it
2780 // has sequential whole number keys starting with 0, it's not associative
2781 // so we can go ahead and convert it as an array.
2782 if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
2784 foreach ($var as
$v) {
2785 $output[] = advagg_drupal_to_js($v);
2787 return '[ ' .
implode(', ', $output) .
' ]';
2789 // Otherwise, fall through to convert the array as an object.
2792 foreach ($var as
$k => $v) {
2793 $output[] = advagg_drupal_to_js(strval($k)) .
': ' .
advagg_drupal_to_js($v);
2795 return '{ ' .
implode(', ', $output) .
' }';
2802 * Process the contents of a stylesheet for aggregation.
2805 * The contents of the stylesheet.
2807 * (optional) Boolean whether CSS contents should be minified. Defaults to
2810 * Contents of the stylesheet including the imported stylesheets.
2812 function advagg_drupal_load_stylesheet_content($contents, $optimize = FALSE
) {
2813 // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
2814 $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
2817 // Perform some safe CSS optimizations.
2818 // Regexp to match comment blocks.
2819 $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
2820 // Regexp to match double quoted strings.
2821 $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
2822 // Regexp to match single quoted strings.
2823 $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
2824 // Strip all comment blocks, but keep double/single quoted strings.
2825 $contents = preg_replace(
2826 "<($double_quot|$single_quot)|$comment>Ss",
2830 // Remove certain whitespace.
2831 // There are different conditions for removing leading and trailing
2833 // @see http://php.net/manual/en/regexp.reference.subpatterns.php
2834 $contents = preg_replace('<
2835 # Strip leading and trailing whitespace.
2837 # Strip only leading whitespace from:
2838 # - Closing parenthesis: Retain "@media (bar) and foo".
2840 # Strip only trailing whitespace from:
2841 # - Opening parenthesis: Retain "@media (bar) and foo".
2842 # - Colon: Retain :pseudo-selectors.
2845 // Only one of the three capturing groups will match, so its reference
2846 // will contain the wanted value and the references for the
2847 // two non-matching groups will be replaced with empty strings.
2851 // End the file with a new line.
2852 $contents = trim($contents);
2856 // Replaces @import commands with the actual stylesheet content.
2857 // This happens recursively but omits external files.
2858 $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_advagg_drupal_load_stylesheet', $contents);
2863 * Loads stylesheets recursively and returns contents with corrected paths.
2865 * This function is used for recursive loading of stylesheets and
2866 * returns the stylesheet content with all url() paths corrected.
2868 function _advagg_drupal_load_stylesheet($matches) {
2869 $filename = $matches[1];
2870 // Load the imported stylesheet and replace @import commands in there as well.
2871 $file = advagg_build_css_bundle(array($filename));
2873 // Determine the file's directory.
2874 $directory = dirname($filename);
2875 // If the file is in the current directory, make sure '.' doesn't appear in
2877 $directory = $directory == '.' ?
'' : $directory .
'/';
2879 // Alter all internal url() paths. Leave external paths alone. We don't need
2880 // to normalize absolute paths here (i.e. remove folder/... segments) because
2881 // that will be done later.
2882 return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'.
$directory, $file);
2886 * Perform an HTTP request; does not wait for reply & you will never get it
2889 * @see drupal_http_request()
2891 * This is a flexible and powerful HTTP client implementation. Correctly
2892 * handles GET, POST, PUT or any other HTTP requests.
2895 * A string containing a fully qualified URI.
2896 * @param array $options
2897 * (optional) An array that can have one or more of the following elements:
2898 * - headers: An array containing request headers to send as name/value pairs.
2899 * - method: A string containing the request method. Defaults to 'GET'.
2900 * - data: A string containing the request body, formatted as
2901 * 'param=value¶m=value&...'. Defaults to NULL.
2902 * - max_redirects: An integer representing how many times a redirect
2903 * may be followed. Defaults to 3.
2904 * - timeout: A float representing the maximum number of seconds the function
2905 * call may take. The default is 30 seconds. If a timeout occurs, the error
2906 * code is set to the HTTP_REQUEST_TIMEOUT constant.
2907 * - context: A context resource created with stream_context_create().
2909 * return value from advagg_async_send_http_request().
2911 function advagg_async_connect_http_request($url, array $options = array()) {
2912 $result = new
stdClass();
2914 // Parse the URL and make sure we can handle the schema.
2915 $uri = @
parse_url($url);
2918 $result->error
= 'unable to parse URL';
2919 $result->code
= -1001;
2923 if (!isset($uri['scheme'])) {
2924 $result->error
= 'missing schema';
2925 $result->code
= -1002;
2929 // Merge the default options.
2931 'headers' => array(),
2934 'max_redirects' => 3,
2938 // stream_socket_client() requires timeout to be a float.
2939 $options['timeout'] = (float) $options['timeout'];
2941 switch ($uri['scheme']) {
2944 $port = isset($uri['port']) ?
$uri['port'] : 80;
2945 $socket = 'tcp://' .
$uri['host'] .
':' .
$port;
2946 // RFC 2616: "non-standard ports MUST, default ports MAY be included".
2947 // We don't add the standard port to prevent from breaking rewrite rules
2948 // checking the host that do not take into account the port number.
2949 if (empty($options['headers']['Host'])) {
2950 $options['headers']['Host'] = $uri['host'];
2953 $options['headers']['Host'] .
= ':' .
$port;
2957 // Note: Only works when PHP is compiled with OpenSSL support.
2958 $port = isset($uri['port']) ?
$uri['port'] : 443;
2959 $socket = 'ssl://' .
$uri['host'] .
':' .
$port;
2960 if (empty($options['headers']['Host'])) {
2961 $options['headers']['Host'] = $uri['host'];
2964 $options['headers']['Host'] .
= ':' .
$port;
2968 $result->error
= 'invalid schema ' .
$uri['scheme'];
2969 $result->code
= -1003;
2973 $flags = STREAM_CLIENT_CONNECT
;
2974 if (variable_get('advagg_async_socket_connect', ADVAGG_ASYNC_SOCKET_CONNECT
)) {
2975 $flags = STREAM_CLIENT_ASYNC_CONNECT
|STREAM_CLIENT_CONNECT
;
2977 if (empty($options['context'])) {
2978 $fp = @
stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags);
2981 // Create a stream with context. Allows verification of a SSL certificate.
2982 $fp = @
stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags, $options['context']);
2985 // Make sure the socket opened properly.
2987 // When a network error occurs, we use a negative number so it does not
2988 // clash with the HTTP status codes.
2989 $result->code
= -$errno;
2990 $result->error
= trim($errstr) ?
trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
2995 // Non blocking stream.
2996 stream_set_blocking($fp, 0);
2998 // Construct the path to act on.
2999 $path = isset($uri['path']) ?
$uri['path'] : '/';
3000 if (isset($uri['query'])) {
3001 $path .
= '?' .
$uri['query'];
3004 // Merge the default headers.
3005 $options['headers'] += array(
3006 'User-Agent' => 'Drupal (+http://drupal.org/)',
3009 // Only add Content-Length if we actually have any content or if it is a POST
3010 // or PUT request. Some non-standard servers get confused by Content-Length in
3011 // at least HEAD/GET requests, and Squid always requires Content-Length in
3012 // POST/PUT requests.
3013 $content_length = strlen($options['data']);
3014 if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
3015 $options['headers']['Content-Length'] = $content_length;
3018 // If the server URL has a user then attempt to use basic authentication.
3019 if (isset($uri['user'])) {
3020 $options['headers']['Authorization'] = 'Basic ' .
base64_encode($uri['user'] .
(!empty($uri['pass']) ?
":" .
$uri['pass'] : ''));
3023 // If the database prefix is being used by SimpleTest to run the tests in a copied
3024 // database then set the user-agent header to the database prefix so that any
3025 // calls to other Drupal pages will run the SimpleTest prefixed database. The
3026 // user-agent is used to ensure that multiple testing sessions running at the
3027 // same time won't interfere with each other as they would if the database
3028 // prefix were stored statically in a file or database variable.
3029 $test_info = &$GLOBALS['drupal_test_info'];
3030 if (!empty($test_info['test_run_id'])) {
3031 $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
3034 $request = $options['method'] .
' ' .
$path .
" HTTP/1.0\r\n";
3035 foreach ($options['headers'] as
$name => $value) {
3036 $request .
= $name .
': ' .
trim($value) .
"\r\n";
3038 $request .
= "\r\n" .
$options['data'];
3039 $result->request
= $request;
3041 return advagg_async_send_http_request($fp, $request, $options['timeout']);
3045 * Perform an HTTP request; does not wait for reply & you never will get it
3048 * @see drupal_http_request()
3050 * This is a flexible and powerful HTTP client implementation. Correctly
3051 * handles GET, POST, PUT or any other HTTP requests.
3054 * (optional) A file pointer.
3056 * (optional) A string containing the request headers to send to the server.
3058 * (optional) An integer holding the stream timeout value.
3060 * TRUE if function worked as planed.
3062 function advagg_async_send_http_request($fp = NULL
, $request = '', $timeout = 30) {
3063 static
$requests = array();
3064 static
$registered = FALSE
;
3066 // Store data in a static, and register a shutdown function.
3067 $args = array($fp, $request, $timeout);
3069 $requests[] = $args;
3071 register_shutdown_function(__FUNCTION__
);
3077 // Shutdown function run.
3078 if (empty($requests)) {
3083 foreach ($requests as
$id => $values) {
3084 list($fp, $request, $timeout) = $values;
3085 $streams[$id] = $fp;
3089 // Run the loop as long as we have a stream to write to.
3090 while (!empty($streams)) {
3091 // Set the read and write vars to the streams var.
3092 $read = $write = $streams;
3095 // Do some voodoo and open all streams at once.
3096 $n = @
stream_select($read, $write, $except, $timeout);
3098 // We have some streams to write to.
3100 // Write to each stream if it is available.
3101 foreach ($write as
$id => $w) {
3102 fwrite($w, $requests[$id][1]);
3104 unset($streams[$id]);
3107 // Timed out waiting or all $streams are closed at this point.
3108 elseif (!empty($retry_count)) {
3116 $requests = array();
3117 if ($n !== FALSE
&& empty($streams)) {
3126 * Implement hook_advagg_js_header_footer_alter.
3128 function advagg_advagg_js_header_footer_alter(&$master_set, $preprocess_js, $public_downloads) {
3129 // Don't run the code below if ctools ajax is not loaded.
3130 if (!defined('CTOOLS_AJAX_INCLUDED')) {
3134 // Get all JS files set to be loaded.
3135 $js_files = array();
3136 foreach ($master_set as
$scope => $scripts) {
3137 if (empty($scripts)) {
3140 advagg_ctools_process_js_files($js_files, $scope, $scripts);
3143 // Add list of CSS & JS files loaded to the settings in the footer.
3144 $loaded = array('CToolsAJAX' => array('scripts' => $js_files));
3145 // Save to the js settings array even though we do not reload it in advagg.
3146 drupal_add_js($loaded, 'setting', 'footer');
3148 // Add it to the settings array in the footer.
3149 if (!isset($master_set['footer']['setting']) || !is_array($master_set['footer']['setting'])) {
3150 $master_set['footer']['setting'] = array();
3152 $master_set['footer']['setting'][] = $loaded;
3156 * Implement hook_advagg_css_pre_alter.
3158 function advagg_advagg_css_pre_alter(&$css, $preprocess_css, $public_downloads) {
3159 // Don't run the code below if ctools ajax is not loaded.
3160 if (!defined('CTOOLS_AJAX_INCLUDED')) {
3164 // Get all CSS files set to be loaded.
3165 $css_files = array();
3166 ctools_process_css_files($css_files, $css);
3168 // Save to the js settings array.
3169 drupal_add_js(array('CToolsAJAX' => array('css' => $css_files)), 'setting', 'footer');
3173 * Create a list of javascript files that are on the page.
3176 * Array of js files that are loaded on this page.
3178 * String usually containing header or footer.
3180 * (Optional) array returned from drupal_add_js(). If NULL then it will load
3181 * the array from drupal_add_js for the given scope.
3182 * @return array $settings
3183 * The JS 'setting' array for the given scope.
3185 function advagg_ctools_process_js_files(&$js_files, $scope, $scripts = NULL
) {
3186 // Automatically extract any 'settings' added via drupal_add_js() and make
3187 // them the first command.
3188 $scripts = drupal_add_js(NULL
, NULL
, $scope);
3189 if (empty($scripts)) {
3190 $scripts = drupal_add_js(NULL
, NULL
, $scope);
3193 // Get replacements that are going to be made by contrib modules and take
3194 // them into account so we don't double-load scripts.
3195 static
$replacements = NULL
;
3196 if (!isset($replacements)) {
3197 $replacements = module_invoke_all('js_replacements');
3200 $settings = array();
3201 foreach ($scripts as
$type => $data) {
3208 // Presently we ignore inline javascript.
3209 // Theme JS is already added and because of admin themes, this could add
3210 // improper JS to the page.
3213 // If JS preprocessing is off, we still need to output the scripts.
3214 // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
3215 foreach ($data as
$path => $info) {
3216 // If the script is being replaced, take that replacement into account.
3217 $final_path = isset($replacements[$type][$path]) ?
$replacements[$type][$path] : $path;
3218 $js_files[base_path() .
$final_path] = TRUE
;
3226 * Wrapper around clearstatcache so it can use php 5.3's new features.
3228 * @param $clear_realpath_cache
3233 * value from clearstatcache().
3235 function advagg_clearstatcache($clear_realpath_cache = FALSE
, $filename = NULL
) {
3237 if (!isset($php530)) {
3238 $php530 = version_compare(PHP_VERSION
, '5.3.0', '>=');
3242 return clearstatcache($clear_realpath_cache, $filename);
3245 return clearstatcache();
3250 * Select records in the database matching where IN(...).
3252 * NOTE Be aware of the servers max_packet_size variable.
3255 * The name of the table.
3257 * field name to be compared to
3258 * @param $placeholder
3259 * db_query placeholders; like %d or '%s'
3261 * array of values you wish to compare to
3263 * array of db fields you return
3265 * returns db_query() result.
3267 function advagg_db_multi_select_in($table, $field, $placeholder, $data, $returns = array(), $groupby = '') {
3268 // Set returns if empty
3269 if (empty($returns)) {
3272 // Get the number of rows that will be inserted
3273 $rows = count($data);
3274 // Create what goes in the IN ()
3276 // Add the rest of the place holders
3277 for ($i = 1; $i < $rows; $i++) {
3278 $in .
= ', ' .
$placeholder;
3281 $query = "SELECT " .
implode(', ', $returns) .
" FROM {" .
$table .
"} WHERE $field IN ($in) $groupby";
3283 return db_query($query, $data);
3287 * Return a large array of the CSS & JS files loaded on this page.
3289 * @param $js_files_excluded
3290 * array of js files to not include in the output array.
3291 * @param $css_files_excluded
3292 * array of css files to not include in the output array.
3296 function advagg_get_js_css_get_array($js_files_excluded = array(), $css_files_excluded = array()) {
3297 global $conf, $_advagg;
3304 // Setup render functions.
3305 $css_function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION
);
3306 $js_function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION
);
3307 $conf['advagg_css_render_function'] = 'advagg_css_array';
3308 $conf['advagg_js_render_function'] = 'advagg_js_array';
3311 $css_array = array();
3312 $variables['css'] = drupal_add_css();
3313 if (module_exists('less')) {
3314 less_preprocess_page($variables, NULL
);
3316 $css_func_inline = advagg_add_css_inline();
3317 if (!empty($css_func_inline)) {
3318 $variables['css'] = advagg_merge_inline_css($variables['css'], $css_func_inline);
3320 // Remove excluded CSS files.
3321 foreach ($variables['css'] as
$media => $types) {
3322 foreach ($types as
$type => $values) {
3323 foreach ($values as
$filename => $preprocess) {
3324 if (in_array($filename, $css_files_excluded)) {
3325 unset($variables['css'][$media][$type][$filename]);
3330 $css_array = advagg_process_css($variables['css']);
3333 $js_array = array();
3334 $variables['js']['header'] = drupal_add_js(NULL
, NULL
, 'header');
3335 if (variable_get('advagg_closure', ADVAGG_CLOSURE
) && !empty($_advagg['closure'])) {
3336 $variables['js']['footer'] = drupal_add_js(NULL
, NULL
, 'footer');
3338 advagg_jquery_updater($variables['js']['header']);
3339 // Remove excluded JS files.
3340 foreach ($variables['js'] as
$scope => $values) {
3341 foreach ($values as
$type => $data) {
3342 foreach ($data as
$filename => $info) {
3343 if (in_array($filename, $js_files_excluded)) {
3344 unset($variables['js'][$scope][$type][$filename]);
3349 $js_array = advagg_process_js($variables['js']);
3351 // Set render functions back to defaults.
3352 $conf['advagg_css_render_function'] = $css_function;
3353 $conf['advagg_js_render_function'] = $js_function;
3358 'css' => $css_array,
3363 * Logic to figure out what kind of css tags to use.
3365 * @param $external_no_preprocess
3366 * array of css files ($media, $href)
3367 * @param $module_no_preprocess
3368 * array of css files ($media, $href)
3369 * @param $output_no_preprocess
3370 * array of css files ($media, $href)
3371 * @param $output_preprocess
3372 * array of css files ($media, $href, $prefix, $suffix)
3373 * @param $theme_no_preprocess
3374 * array of css files ($media, $href)
3375 * @param $inline_no_preprocess
3376 * array of css data to inline ($media, $data)
3377 * @param $files_included
3378 * array of css files included. $a[$media][] = $filename
3379 * @param $files_aggregates_included
3380 * array of css files & aggregates included. $a[$media][] = $filename
3381 * @param $inline_included
3382 * array of inline css included. $a[$media][] = $datablob;
3384 * html for loading the css. html for the head.
3386 function advagg_css_array($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included) {
3388 'inline' => $inline_included,
3389 'files' => $files_included,
3390 'files_aggregates' => $files_aggregates_included,
3395 * Build and theme JS output for header.
3397 * @param $external_no_preprocess
3398 * array(array($src, $defer))
3399 * @param $output_preprocess
3400 * array(array($src, $prefix, $suffix))
3401 * @param $output_no_preprocess
3402 * array(array(array($src, $defer)))
3403 * @param $setting_no_preprocess
3404 * array(array($code))
3405 * @param $inline_no_preprocess
3406 * array(array($code, $defer))
3409 * @param $js_settings_array
3410 * array of settings used.
3411 * @param $inline_included
3412 * array of inline scripts used.
3413 * @param $files_included
3414 * array of files used.
3415 * @param $files_aggregates_included
3416 * array of files and aggregates used.
3418 * String of themed JavaScript.
3420 function advagg_js_array($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) {
3422 'settings' => $js_settings_array,
3423 'inline' => $inline_included,
3424 'files' => $files_included,
3425 'files_aggregates' => $files_aggregates_included,
3430 * Implementation of hook_file_download().
3432 * Return the correct headers for advagg bundles.
3434 function advagg_file_download($file, $type = '') {