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