advagg.module

  1. nittany7 modules/contrib/advagg/advagg.module
  2. cis7 modules/contrib/advagg/advagg.module
  3. mooc7 modules/contrib/advagg/advagg.module

Advanced CSS/JS aggregation module.

Functions

Namesort descending Description
advagg_admin_config_root_path Get the current path used for advagg admin configuration.
advagg_admin_flush_cache Cache clear callback for admin_menu/flush-cache/advagg.
advagg_admin_menu_cache_info Implements hook_admin_menu_cache_info().
advagg_admin_menu_output_alter Implements hook_admin_menu_output_alter().
advagg_cron Implements hook_cron().
advagg_css_alter Implements hook_css_alter().
advagg_current_hooks_hash_array Get an array of all hooks and settings that affect aggregated files contents.
advagg_element_info_alter Implements hook_element_info_alter().
advagg_enabled Function used to see if aggregation is enabled.
advagg_file_aggregation_enabled Given the type lets us know if advagg is enabled or disabled.
advagg_flush_caches Implements hook_flush_caches().
advagg_get_current_hooks_hash Get the hash of all hooks & settings that affect aggregated files contents.
advagg_get_full_js Get full JS array.
advagg_get_global_counter Return the advagg_global_counter variable.
advagg_get_hash_settings Returns the hashes settings.
advagg_get_js Returns a themed presentation of all JavaScript code for the current page.
advagg_get_js_scopes Get all javascript scopes set in the $javascript array.
advagg_get_root_files_dir Get the CSS & JS path for advagg.
advagg_group_js Default callback to group JavaScript items.
advagg_hooks_implemented Get back what hooks are implemented.
advagg_hook_info Implements hook_hook_info().
advagg_js_alter Implements hook_js_alter().
advagg_menu Implements hook_menu().
advagg_merge_plans Apply the advagg changes to the $css_js_groups array.
advagg_modify_css_pre_render #pre_render callback so elements can be modified before they are rendered.
advagg_modify_js_pre_render #pre_render callback so elements can be modified before they are rendered.
advagg_module_implements_alter Implements hook_module_implements_alter().
advagg_permission Implements hook_permission().
advagg_preprocess_html Implements hook_preprocess_html().
advagg_pre_render_scripts #pre_render callback to add elements needed for JavaScript to be rendered.
advagg_set_hash_settings Store settings associated with hash.
advagg_system_info_alter Implements hook_system_info_alter().
advagg_theme_registry_alter Implements hook_theme_registry_alter().
advagg_update_atime Update the atime value in the advagg_aggregates_versions table.
_advagg_aggregate_css Default callback to aggregate CSS files and inline content.
_advagg_aggregate_js Default callback to aggregate JavaScript files.
_advagg_process_html Replacement for template_process_html().

Constants

Namesort descending Description
ADVAGG_ADMIN_CONFIG_ROOT_PATH Default location of AdvAgg configuration items.
ADVAGG_CACHE_LEVEL Default value to see if we cache the full CSS/JS structure.
ADVAGG_COMBINE_CSS_MEDIA Combine css files by using media queries instead of media attributes.
ADVAGG_CORE_GROUPS Default value to see we use core's default grouping of CSS/JS files.
ADVAGG_CSS_FIX_TYPE Scan and fix any CSS that was added with the wrong type.
ADVAGG_DEBUG Default value for debugging info to watchdog.
ADVAGG_ENABLED Default value to see if advanced CSS/JS aggregation is enabled.
ADVAGG_GLOBAL_COUNTER Default value of counter.
ADVAGG_GZIP Default value to see if .gz files should be created as well.
ADVAGG_HTACCESS_CHECK_GENERATE Generate a .htaccess file in the AdvAgg dirs.
ADVAGG_IE_CSS_SELECTOR_LIMITER Prevent more than 4095 css selector rules inside of a CSS aggregate.
ADVAGG_JS_FIX_TYPE Scan and fix any JS that was added with the wrong type.
ADVAGG_SPACE Default space characters.
ADVAGG_USE_HTTPRL Send non blocking requests in order to generate aggregated files via HTTPRL.

File

modules/contrib/advagg/advagg.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Advanced CSS/JS aggregation module.
  5. */
  6. // Define default variables.
  7. /**
  8. * Default space characters.
  9. */
  10. define('ADVAGG_SPACE', '__');
  11. /**
  12. * Default value to see if advanced CSS/JS aggregation is enabled.
  13. */
  14. define('ADVAGG_ENABLED', TRUE);
  15. /**
  16. * Default value to see if .gz files should be created as well.
  17. */
  18. define('ADVAGG_GZIP', TRUE);
  19. /**
  20. * Default value to see we use core's default grouping of CSS/JS files.
  21. */
  22. define('ADVAGG_CORE_GROUPS', TRUE);
  23. /**
  24. * Default value to see if we cache the full CSS/JS structure.
  25. */
  26. define('ADVAGG_CACHE_LEVEL', 1);
  27. /**
  28. * Default value of counter.
  29. */
  30. define('ADVAGG_GLOBAL_COUNTER', 0);
  31. /**
  32. * Send non blocking requests in order to generate aggregated files via HTTPRL.
  33. */
  34. define('ADVAGG_USE_HTTPRL', TRUE);
  35. /**
  36. * Combine css files by using media queries instead of media attributes.
  37. */
  38. define('ADVAGG_COMBINE_CSS_MEDIA', FALSE);
  39. /**
  40. * Prevent more than 4095 css selector rules inside of a CSS aggregate.
  41. */
  42. define('ADVAGG_IE_CSS_SELECTOR_LIMITER', TRUE);
  43. /**
  44. * Default location of AdvAgg configuration items.
  45. */
  46. define('ADVAGG_ADMIN_CONFIG_ROOT_PATH', 'admin/config/development/performance');
  47. /**
  48. * Default value for debugging info to watchdog.
  49. */
  50. define('ADVAGG_DEBUG', FALSE);
  51. /**
  52. * Scan and fix any JS that was added with the wrong type.
  53. */
  54. define('ADVAGG_JS_FIX_TYPE', TRUE);
  55. /**
  56. * Scan and fix any CSS that was added with the wrong type.
  57. */
  58. define('ADVAGG_CSS_FIX_TYPE', TRUE);
  59. /**
  60. * Generate a .htaccess file in the AdvAgg dirs.
  61. */
  62. define('ADVAGG_HTACCESS_CHECK_GENERATE', TRUE);
  63. // Core hook implementations.
  64. /**
  65. * Implements hook_hook_info().
  66. */
  67. function advagg_hook_info() {
  68. // List of hooks that can be inside of *.advagg.inc files.
  69. $advagg_hooks = array(
  70. 'advagg_get_css_file_contents_alter',
  71. 'advagg_get_css_aggregate_contents_alter',
  72. 'advagg_get_js_file_contents_alter',
  73. 'advagg_get_js_aggregate_contents_alter',
  74. 'advagg_save_aggregate_alter',
  75. 'advagg_build_aggregate_plans_alter',
  76. 'advagg_changed_files',
  77. 'advagg_scan_for_changes',
  78. );
  79. $hooks = array();
  80. foreach ($advagg_hooks as $hook) {
  81. $hooks[$hook] = array('group' => 'advagg');
  82. }
  83. return $hooks;
  84. }
  85. /**
  86. * Implements hook_module_implements_alter().
  87. */
  88. function advagg_module_implements_alter(&$implementations, $hook) {
  89. // Move advagg to the top.
  90. if ($hook === 'theme_registry_alter' && array_key_exists('advagg', $implementations)) {
  91. $item = array('advagg' => $implementations['advagg']);
  92. unset($implementations['advagg']);
  93. $implementations = array_merge($item, $implementations);
  94. }
  95. // Move advagg to the bottom.
  96. if ($hook === 'element_info_alter' && array_key_exists('advagg', $implementations)) {
  97. $item = $implementations['advagg'];
  98. unset($implementations['advagg']);
  99. $implementations['advagg'] = $item;
  100. }
  101. // Move advagg to the bottom.
  102. if ($hook === 'js_alter' && array_key_exists('advagg', $implementations)) {
  103. $item = $implementations['advagg'];
  104. unset($implementations['advagg']);
  105. $implementations['advagg'] = $item;
  106. }
  107. // Move advagg to the bottom.
  108. if ($hook === 'css_alter' && array_key_exists('advagg', $implementations)) {
  109. $item = $implementations['advagg'];
  110. unset($implementations['advagg']);
  111. $implementations['advagg'] = $item;
  112. }
  113. }
  114. /**
  115. * Implements hook_system_info_alter().
  116. */
  117. function advagg_system_info_alter(&$info, $file, $type) {
  118. static $config_path;
  119. // Get advagg config path
  120. if (!isset($config_path)) {
  121. $config_path = advagg_admin_config_root_path();
  122. }
  123. // Replace advagg path.
  124. if ( !empty($info['configure'])
  125. && strpos($info['configure'], '/advagg') !== FALSE
  126. && ((
  127. !empty($info['dependencies'])
  128. && is_array($info['dependencies'])
  129. && in_array('advagg', $info['dependencies'])
  130. ) || $file->name == 'advagg' )
  131. ) {
  132. $pos = strpos($info['configure'], '/advagg') + 7;
  133. $substr = substr($info['configure'], 0, $pos);
  134. $info['configure'] = str_replace($substr, $config_path . '/advagg', $info['configure']);
  135. }
  136. }
  137. /**
  138. * Implements hook_permission().
  139. */
  140. function advagg_permission() {
  141. return array(
  142. 'bypass advanced aggregation' => array(
  143. 'title' => t('bypass advanced aggregation'),
  144. 'description' => t('User can use URL query strings to bypass AdvAgg.'),
  145. ),
  146. );
  147. }
  148. /**
  149. * Implements hook_menu().
  150. */
  151. function advagg_menu() {
  152. list($css_path, $js_path) = advagg_get_root_files_dir();
  153. $file_path = drupal_get_path('module', 'advagg');
  154. $config_path = advagg_admin_config_root_path();
  155. $items = array();
  156. $items[$css_path[1] . '/%'] = array(
  157. 'title' => "Generate css aggregate",
  158. 'page callback' => 'advagg_missing_aggregate',
  159. 'type' => MENU_CALLBACK,
  160. 'access callback' => TRUE,
  161. 'file path' => $file_path,
  162. 'file' => 'advagg.missing.inc',
  163. );
  164. $items[$js_path[1] . '/%'] = array(
  165. 'title' => "Generate js aggregate",
  166. 'page callback' => 'advagg_missing_aggregate',
  167. 'type' => MENU_CALLBACK,
  168. 'access callback' => TRUE,
  169. 'file path' => $file_path,
  170. 'file' => 'advagg.missing.inc',
  171. );
  172. $items[$config_path . '/default'] = array(
  173. 'title' => 'Performance',
  174. 'type' => MENU_DEFAULT_LOCAL_TASK,
  175. 'file path' => drupal_get_path('module', 'system'),
  176. 'weight' => -10,
  177. );
  178. $items[$config_path . '/advagg'] = array(
  179. 'title' => 'Advanced CSS/JS Aggregation',
  180. 'description' => 'Configuration for Advanced CSS/JS Aggregation.',
  181. 'page callback' => 'drupal_get_form',
  182. 'page arguments' => array('advagg_admin_settings_form'),
  183. 'type' => MENU_LOCAL_TASK,
  184. 'access arguments' => array('administer site configuration'),
  185. 'file path' => $file_path,
  186. 'file' => 'advagg.admin.inc',
  187. 'weight' => 1,
  188. );
  189. $items[$config_path . '/advagg/config'] = array(
  190. 'title' => 'Configuration',
  191. 'type' => MENU_DEFAULT_LOCAL_TASK,
  192. 'weight' => -10,
  193. );
  194. $items[$config_path . '/advagg/operations'] = array(
  195. 'title' => 'Perform An Operation',
  196. 'description' => 'Flush caches, set the bypass cookie, take drastic actions.',
  197. 'page callback' => 'drupal_get_form',
  198. 'page arguments' => array('advagg_admin_operations_form'),
  199. 'type' => MENU_LOCAL_TASK,
  200. 'access arguments' => array('administer site configuration'),
  201. 'file path' => $file_path,
  202. 'file' => 'advagg.admin.inc',
  203. 'weight' => -9,
  204. );
  205. $items[$config_path . '/advagg/info'] = array(
  206. 'title' => 'Information',
  207. 'description' => 'More detailed information about advagg.',
  208. 'page callback' => 'drupal_get_form',
  209. 'page arguments' => array('advagg_admin_info_form'),
  210. 'type' => MENU_LOCAL_TASK,
  211. 'access arguments' => array('administer site configuration'),
  212. 'file path' => $file_path,
  213. 'file' => 'advagg.admin.inc',
  214. 'weight' => -8,
  215. );
  216. return $items;
  217. }
  218. /**
  219. * Implements hook_cron().
  220. *
  221. * This should be ran about once a day.
  222. */
  223. function advagg_cron() {
  224. $return = array();
  225. // Clear out all stale advagg aggregated files.
  226. module_load_include('inc', 'advagg', 'advagg.cache');
  227. $return[] = advagg_delete_stale_aggregates();
  228. // Remove aggregates that include missing files.
  229. $return[] = advagg_remove_missing_files_from_db();
  230. // Remove unused aggregates.
  231. $return[] = advagg_remove_old_unused_aggregates();
  232. return $return;
  233. }
  234. /**
  235. * Implements hook_flush_caches().
  236. *
  237. * @param $all_bins
  238. * TRUE: Get all advagg cache bins
  239. */
  240. function advagg_flush_caches($all_bins = FALSE) {
  241. // Send back a blank array if aav table doesn't exist.
  242. if (!db_table_exists('advagg_aggregates_versions')) {
  243. return array();
  244. }
  245. // Get list of cache bins to clear.
  246. $bins = array();
  247. $bins[] = 'cache_advagg_aggregates';
  248. if ($all_bins) {
  249. $bins[] = 'cache_advagg_info';
  250. }
  251. return $bins;
  252. }
  253. /**
  254. * Implements hook_element_info_alter().
  255. *
  256. * @param $type
  257. * Configuration array.
  258. */
  259. function advagg_element_info_alter(&$type) {
  260. // Swap in our own aggregation callback.
  261. if (isset($type['styles']['#aggregate_callback'])) {
  262. $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css';
  263. $type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render';
  264. }
  265. // Swap in our own aggregation callback.
  266. if (isset($type['scripts']['#aggregate_callback'])) {
  267. $type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js';
  268. }
  269. else {
  270. $type['scripts'] = array(
  271. '#items' => array(),
  272. '#pre_render' => array('advagg_pre_render_scripts'),
  273. '#group_callback' => 'advagg_group_js',
  274. '#aggregate_callback' => '_advagg_aggregate_js',
  275. '#type' => 'scripts',
  276. );
  277. }
  278. $type['scripts']['#pre_render'][] = 'advagg_modify_js_pre_render';
  279. }
  280. /**
  281. * Implements hook_theme_registry_alter().
  282. *
  283. * Replace template_process_html with _advagg_process_html
  284. *
  285. * @param $theme_registry
  286. * The existing theme registry data structure.
  287. */
  288. function advagg_theme_registry_alter(&$theme_registry) {
  289. if (!isset($theme_registry['html'])) {
  290. return;
  291. }
  292. // Replace core's process function with our own.
  293. $index = array_search('template_process_html', $theme_registry['html']['process functions']);
  294. if ($index !== FALSE) {
  295. $theme_registry['html']['process functions'][$index] = '_advagg_process_html';
  296. }
  297. }
  298. /**
  299. * Implements hook_js_alter().
  300. */
  301. function advagg_js_alter(&$js) {
  302. if (!advagg_enabled() || !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) {
  303. return;
  304. }
  305. // Fix type if it was incorrectly set.
  306. foreach ($js as $key => &$value) {
  307. if (empty($value['data']) || !is_string($value['data'])) {
  308. continue;
  309. }
  310. // If type is external but doesn't start with http or //, change it to file.
  311. if ($value['type'] == 'external' && stripos($value['data'], 'http://') !== 0 && stripos($value['data'], 'https://') !== 0 && stripos($value['data'], '//') !== 0) {
  312. $value['type'] = 'file';
  313. }
  314. // If type is file but it starts with http or //, change it to external.
  315. if ((stripos($value['data'], 'http://') === 0 || stripos($value['data'], 'https://') === 0 || stripos($value['data'], '//') === 0) && $value['type'] == 'file') {
  316. $value['type'] = 'external';
  317. }
  318. }
  319. }
  320. /**
  321. * Implements hook_css_alter().
  322. */
  323. function advagg_css_alter(&$css) {
  324. if (!advagg_enabled() || !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) {
  325. return;
  326. }
  327. // Fix type if it was incorrectly set.
  328. foreach ($css as $key => &$value) {
  329. if (empty($value['data']) || !is_string($value['data'])) {
  330. continue;
  331. }
  332. // If type is external but doesn't start with http, change it to file.
  333. if ($value['type'] == 'external' && stripos($value['data'], 'http://') !== 0 && stripos($value['data'], 'https://') !== 0) {
  334. $value['type'] = 'file';
  335. }
  336. // If type is file but it starts with http, change it to external.
  337. if ((stripos($value['data'], 'http://') === 0 || stripos($value['data'], 'https://') === 0) && $value['type'] == 'file') {
  338. $value['type'] = 'external';
  339. }
  340. }
  341. }
  342. /**
  343. * Implements hook_admin_menu_cache_info().
  344. *
  345. * Add in a cache flush for advagg.
  346. */
  347. function advagg_admin_menu_cache_info() {
  348. if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
  349. $caches['advagg'] = array(
  350. 'title' => t('Adv CSS/JS Agg'),
  351. 'callback' => 'advagg_admin_flush_cache',
  352. );
  353. return $caches;
  354. }
  355. }
  356. /**
  357. * Implements hook_admin_menu_output_alter().
  358. *
  359. * Add in a cache flush for advagg.
  360. */
  361. function advagg_admin_menu_output_alter(&$content) {
  362. if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
  363. // Remove default core aggregation link.
  364. unset($content['icon']['icon']['flush-cache']['assets']);
  365. }
  366. }
  367. /**
  368. * Implements hook_preprocess_html().
  369. *
  370. * Add in rendering IE meta tag if "combine CSS" is enabled.
  371. */
  372. function advagg_preprocess_html() {
  373. // Do not force IE rendering mode if "combine CSS" is disabled.
  374. if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) {
  375. return;
  376. }
  377. // Setup IE meta tag to force IE rendering mode.
  378. $meta_ie_render_engine = array(
  379. '#type' => 'html_tag',
  380. '#tag' => 'meta',
  381. '#attributes' => array(
  382. 'http-equiv' => 'X-UA-Compatible',
  383. 'content' => 'IE=edge,chrome=1',
  384. ),
  385. '#weight' => '-99999',
  386. );
  387. // Add header meta tag for IE to head.
  388. drupal_add_html_head($meta_ie_render_engine, 'meta_ie_render_engine');
  389. }
  390. // Core CSS/JS override functions.
  391. /**
  392. * #pre_render callback so elements can be modified before they are rendered.
  393. *
  394. * @param $elements
  395. * A render array containing:
  396. * - #items: The JavaScript items as returned by drupal_add_js() and
  397. * altered by drupal_get_js().
  398. * - #group_callback: A function to call to group #items. Following
  399. * this function, #aggregate_callback is called to aggregate items within
  400. * the same group into a single file.
  401. * - #aggregate_callback: A function to call to aggregate the items within
  402. * the groups arranged by the #group_callback function.
  403. *
  404. * @return
  405. * A render array that will render to a string of JavaScript tags.
  406. */
  407. function advagg_modify_js_pre_render($elements) {
  408. // Put children elements into a reference array.
  409. $children = array();
  410. foreach ($elements as $key => &$value) {
  411. if ($key !== '' && $key[0] === '#') {
  412. continue;
  413. }
  414. $children[$key] = &$value;
  415. }
  416. // Allow other modules to modify $children & $elements before they are
  417. // rendered.
  418. // Call hook_advagg_modify_js_pre_render_alter()
  419. drupal_alter('advagg_modify_js_pre_render', $children, $elements);
  420. return $elements;
  421. }
  422. /**
  423. * #pre_render callback so elements can be modified before they are rendered.
  424. *
  425. * @param $elements
  426. * A render array containing:
  427. * - #items: The CSS items as returned by drupal_add_css() and
  428. * altered by drupal_get_css().
  429. * - #group_callback: A function to call to group #items. Following
  430. * this function, #aggregate_callback is called to aggregate items within
  431. * the same group into a single file.
  432. * - #aggregate_callback: A function to call to aggregate the items within
  433. * the groups arranged by the #group_callback function.
  434. *
  435. * @return
  436. * A render array that will render to a string of JavaScript tags.
  437. */
  438. function advagg_modify_css_pre_render($elements) {
  439. // Put children elements into a reference array.
  440. $children = array();
  441. foreach ($elements as $key => &$value) {
  442. if ($key !== '' && $key[0] === '#') {
  443. continue;
  444. }
  445. $children[$key] = &$value;
  446. }
  447. // Allow other modules to modify $children & $elements before they are
  448. // rendered.
  449. // Call hook_advagg_modify_css_pre_render_alter()
  450. drupal_alter('advagg_modify_css_pre_render', $children, $elements);
  451. return $elements;
  452. }
  453. /**
  454. * Default callback to aggregate CSS files and inline content.
  455. *
  456. * Having the browser load fewer CSS files results in much faster page loads
  457. * than when it loads many small files. This function aggregates files within
  458. * the same group into a single file unless the site-wide setting to do so is
  459. * disabled (commonly the case during site development). To optimize download,
  460. * it also compresses the aggregate files by removing comments, whitespace, and
  461. * other unnecessary content. Additionally, this functions aggregates inline
  462. * content together, regardless of the site-wide aggregation setting.
  463. *
  464. * @param $css_groups
  465. * An array of CSS groups as returned by drupal_group_css(). This function
  466. * modifies the group's 'data' property for each group that is aggregated.
  467. *
  468. * @see drupal_aggregate_css()
  469. * @see drupal_group_css()
  470. * @see drupal_pre_render_styles()
  471. * @see system_element_info()
  472. */
  473. function _advagg_aggregate_css(&$css_groups) {
  474. if (!advagg_enabled()) {
  475. return drupal_aggregate_css($css_groups);
  476. }
  477. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  478. $GLOBALS['_advagg']['debug']['css_groups_before'][] = $css_groups;
  479. }
  480. $preprocess_css = advagg_file_aggregation_enabled('css');
  481. // Allow other modules to modify $css_groups right before it is processed.
  482. // Call hook_advagg_css_groups_alter().
  483. drupal_alter('advagg_css_groups', $css_groups, $preprocess_css);
  484. // For each group that needs aggregation, aggregate its items.
  485. $files_to_aggregate = array();
  486. // Allow for inline CSS to be between aggregated files.
  487. $gap_counter = 0;
  488. foreach ($css_groups as $key => $group) {
  489. switch ($group['type']) {
  490. // If a file group can be aggregated into a single file, do so, and set
  491. // the group's data property to the file path of the aggregate file.
  492. case 'file':
  493. if ($group['preprocess'] && $preprocess_css) {
  494. $files_to_aggregate[$gap_counter][$key] = $group;
  495. }
  496. else {
  497. $gap_counter++;
  498. }
  499. break;
  500. // Aggregate all inline CSS content into the group's data property.
  501. case 'inline':
  502. $gap_counter++;
  503. $css_groups[$key]['data'] = '';
  504. foreach ($group['items'] as $item) {
  505. $css_groups[$key]['data'] .= drupal_load_stylesheet_content($item['data'], $item['preprocess']);
  506. }
  507. break;
  508. // Create a gap for external CSS.
  509. case 'external':
  510. $gap_counter++;
  511. break;
  512. }
  513. }
  514. if (!empty($files_to_aggregate)) {
  515. $hooks_hash = advagg_get_current_hooks_hash();
  516. $css_hash = drupal_hash_base64(serialize($files_to_aggregate));
  517. $cache_id = 'advagg:css:' . $hooks_hash . ':' . $css_hash;
  518. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) {
  519. $plans = $cache->data;
  520. }
  521. else {
  522. module_load_include('inc', 'advagg', 'advagg');
  523. $plans = advagg_build_aggregate_plans($files_to_aggregate, 'css');
  524. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) {
  525. cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY);
  526. }
  527. }
  528. $css_groups = advagg_merge_plans($css_groups, $plans);
  529. }
  530. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  531. $GLOBALS['_advagg']['debug']['css_groups_after'][] = $css_groups;
  532. }
  533. }
  534. /**
  535. * Default callback to aggregate JavaScript files.
  536. *
  537. * Having the browser load fewer JavaScript files results in much faster page
  538. * loads than when it loads many small files. This function aggregates files
  539. * within the same group into a single file unless the site-wide setting to do
  540. * so is disabled (commonly the case during site development). To optimize
  541. * download, it also compresses the aggregate files by removing comments,
  542. * whitespace, and other unnecessary content.
  543. *
  544. * @param $js_groups
  545. * An array of JavaScript groups as returned by drupal_group_js(). For each
  546. * group that is aggregated, this function sets the value of the group's
  547. * 'data' key to the URI of the aggregate file.
  548. *
  549. * @see drupal_group_js()
  550. * @see drupal_pre_render_scripts()
  551. */
  552. function _advagg_aggregate_js(&$js_groups) {
  553. if (!advagg_enabled()) {
  554. if (function_exists('drupal_aggregate_js')) {
  555. return drupal_aggregate_js($js_groups);
  556. }
  557. else {
  558. return;
  559. }
  560. }
  561. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  562. $GLOBALS['_advagg']['debug']['js_groups_before'][] = $js_groups;
  563. }
  564. $preprocess_js = advagg_file_aggregation_enabled('js');
  565. // Allow other modules to modify $js_groups right before it is processed.
  566. // Call hook_advagg_js_groups_alter().
  567. drupal_alter('advagg_js_groups', $js_groups, $preprocess_js);
  568. // For each group that needs aggregation, aggregate its items.
  569. $files_to_aggregate = array();
  570. // Only aggregate when the site is configured to do so, and not during an
  571. // update.
  572. $gap_counter = 0;
  573. if ($preprocess_js) {
  574. foreach ($js_groups as $key => &$group) {
  575. switch ($group['type']) {
  576. // If a file group can be aggregated into a single file, do so, and set
  577. // the group's data property to the file path of the aggregate file.
  578. case 'file':
  579. if ($group['preprocess']) {
  580. $files_to_aggregate[$gap_counter][$key] = $group;
  581. }
  582. else {
  583. $gap_counter++;
  584. }
  585. break;
  586. // Create a gap for inline JS.
  587. case 'inline':
  588. $gap_counter++;
  589. break;
  590. // Create a gap for external JS.
  591. case 'external':
  592. $gap_counter++;
  593. break;
  594. }
  595. }
  596. }
  597. if (!empty($files_to_aggregate)) {
  598. $hooks_hash = advagg_get_current_hooks_hash();
  599. $js_hash = drupal_hash_base64(serialize($files_to_aggregate));
  600. $cache_id = 'advagg:js:' . $hooks_hash . ':' . $js_hash;
  601. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) {
  602. $plans = $cache->data;
  603. }
  604. else {
  605. module_load_include('inc', 'advagg', 'advagg');
  606. $plans = advagg_build_aggregate_plans($files_to_aggregate, 'js');
  607. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) {
  608. cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY);
  609. }
  610. }
  611. $js_groups = advagg_merge_plans($js_groups, $plans);
  612. }
  613. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  614. $GLOBALS['_advagg']['debug']['js_groups_after'][] = $js_groups;
  615. }
  616. }
  617. /**
  618. * Replacement for template_process_html().
  619. */
  620. function _advagg_process_html(&$variables) {
  621. if (!advagg_enabled()) {
  622. return template_process_html($variables);
  623. }
  624. // Scan for changes to any CSS/JS files.
  625. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
  626. module_load_include('inc', 'advagg', 'advagg.cache');
  627. $flushed = advagg_push_new_changes();
  628. // Report back the results.
  629. if (!empty($flushed)) {
  630. foreach ($flushed as $filename => $data) {
  631. $ext = pathinfo($filename, PATHINFO_EXTENSION);
  632. drupal_set_message(t('The file %filename has changed. %db_usage aggregates are using this file. %db_count db cache entries and all %type full cache entries have been flushed from the cache bins.', array(
  633. '%filename' => $filename,
  634. '%db_usage' => count($data[0]),
  635. '%db_count' => count($data[1]),
  636. '%type' => $ext,
  637. )));
  638. }
  639. }
  640. }
  641. // Get default javascript.
  642. // @see http://drupal.org/node/1279226
  643. if (function_exists('drupal_add_js_page_defaults')) {
  644. drupal_add_js_page_defaults();
  645. }
  646. // Render page_top and page_bottom into top level variables.
  647. if (isset($variables['page']['page_top'])) {
  648. $variables['page_top'] = drupal_render($variables['page']['page_top']);
  649. }
  650. elseif (!isset($variables['page_top'])) {
  651. $variables['page_top'] = '';
  652. }
  653. if (isset($variables['page']['page_bottom'])) {
  654. $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']);
  655. }
  656. elseif (!isset($variables['page_bottom'])) {
  657. $variables['page_bottom'] = '';
  658. }
  659. // Place the rendered HTML for the page body into a top level variable.
  660. if (isset($variables['page']['#children'])) {
  661. $variables['page'] = $variables['page']['#children'];
  662. }
  663. $variables['head'] = drupal_get_html_head();
  664. // Get the raw CSS/JS variables.
  665. $hooks_hash = advagg_get_current_hooks_hash();
  666. $variables['css'] = drupal_add_css();
  667. $javascript = drupal_add_js();
  668. // Try the cache first; only if not debugging.
  669. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {
  670. $css_hash = drupal_hash_base64(serialize($variables['css']));
  671. $css_cache_id = 'advagg:css:full:' . $hooks_hash . ':' . $css_hash;
  672. $css_cache = cache_get($css_cache_id, 'cache_advagg_aggregates');
  673. $js_hash = drupal_hash_base64(serialize($javascript));
  674. $js_cache_id = 'advagg:js:full:' . $hooks_hash . ':' . $js_hash;
  675. $js_cache = cache_get($js_cache_id, 'cache_advagg_aggregates');
  676. }
  677. // CSS has nice hooks so we don't need to work around it.
  678. if (!empty($css_cache->data) && !variable_get('advagg_debug', ADVAGG_DEBUG)) {
  679. $variables['styles'] = $css_cache->data;
  680. }
  681. else {
  682. $variables['styles'] = drupal_get_css();
  683. if (!empty($css_cache_id)) {
  684. cache_set($css_cache_id, $variables['styles'], 'cache_advagg_aggregates', CACHE_TEMPORARY);
  685. }
  686. }
  687. // JS needs hacks.
  688. if (!isset($variables['scripts'])) {
  689. $variables['scripts'] = '';
  690. }
  691. if (!isset($variables['page_bottom']) || !is_string($variables['page_bottom'])) {
  692. $variables['page_bottom'] = '';
  693. }
  694. if (!empty($js_cache->data) && !variable_get('advagg_debug', ADVAGG_DEBUG)) {
  695. foreach ($js_cache->data as $key => $value) {
  696. if (!isset($variables[$key]) || !is_string($variables[$key])) {
  697. $variables[$key] = '';
  698. }
  699. $variables[$key] .= $value;
  700. }
  701. }
  702. else {
  703. $js_cache = array();
  704. $javascript = advagg_get_full_js($javascript);
  705. if (!empty($javascript)) {
  706. $scopes = advagg_get_js_scopes($javascript);
  707. // Add JS to the header and footer of the page.
  708. foreach ($scopes as $scope => $use) {
  709. if (!$use) {
  710. continue;
  711. }
  712. $scripts = advagg_get_js($scope, $javascript);
  713. // Header scripts.
  714. if ($scope == 'header') {
  715. $variables['scripts'] = $scripts;
  716. $js_cache['scripts'] = $scripts;
  717. }
  718. // Footer scripts.
  719. elseif ($scope == 'footer') {
  720. $variables['page_bottom'] .= $scripts;
  721. $js_cache['page_bottom'] = $scripts;
  722. }
  723. // Scripts in other places.
  724. elseif (isset($variables[$scope]) && is_string($variables[$scope])) {
  725. $variables[$scope] .= $scripts;
  726. $js_cache[$scope] = $scripts;
  727. }
  728. }
  729. if (!empty($js_cache_id) && !empty($js_cache)) {
  730. cache_set($js_cache_id, $js_cache, 'cache_advagg_aggregates', CACHE_TEMPORARY);
  731. }
  732. }
  733. }
  734. // Output debug info.
  735. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  736. $debug = $GLOBALS['_advagg']['debug'];
  737. if (module_exists('httprl')) {
  738. $output = ' ' . httprl_pr($debug);
  739. }
  740. else {
  741. $output = '<pre>' . str_replace(array('<', '>'), array('&lt;', '&gt;'), print_r($debug, TRUE)) . '</pre>';
  742. }
  743. watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG);
  744. }
  745. }
  746. /**
  747. * Get full JS array.
  748. *
  749. * Note that hook_js_alter(&$javascript) is called during this function call
  750. * to allow alterations of the JavaScript during its presentation. Calls to
  751. * drupal_add_js() from hook_js_alter() will not be added to the output
  752. * presentation. The correct way to add JavaScript during hook_js_alter()
  753. * is to add another element to the $javascript array, deriving from
  754. * drupal_js_defaults(). See locale_js_alter() for an example of this.
  755. *
  756. * @param $javascript
  757. * (optional) An array with all JavaScript code. Defaults to the default
  758. * JavaScript array for the given scope.
  759. * @param $skip_alter
  760. * (optional) If set to TRUE, this function skips calling drupal_alter() on
  761. * $javascript, useful when the calling function passes a $javascript array
  762. * that has already been altered.
  763. *
  764. * @return
  765. * The raw JavaScript array.
  766. *
  767. * @see drupal_add_js()
  768. * @see locale_js_alter()
  769. * @see drupal_js_defaults()
  770. */
  771. function advagg_get_full_js($javascript = NULL, $skip_alter = FALSE) {
  772. if (!isset($javascript)) {
  773. $javascript = drupal_add_js();
  774. }
  775. if (empty($javascript)) {
  776. return FALSE;
  777. }
  778. // Allow modules to alter the JavaScript.
  779. if (!$skip_alter) {
  780. // Call hook_js_alter().
  781. drupal_alter('js', $javascript);
  782. }
  783. return $javascript;
  784. }
  785. /**
  786. * Returns a themed presentation of all JavaScript code for the current page.
  787. *
  788. * References to JavaScript files are placed in a certain order: first, all
  789. * 'core' files, then all 'module' and finally all 'theme' JavaScript files
  790. * are added to the page. Then, all settings are output, followed by 'inline'
  791. * JavaScript code. If running update.php, all preprocessing is disabled.
  792. *
  793. * Note that hook_js_alter(&$javascript) is called during this function call
  794. * to allow alterations of the JavaScript during its presentation. Calls to
  795. * drupal_add_js() from hook_js_alter() will not be added to the output
  796. * presentation. The correct way to add JavaScript during hook_js_alter()
  797. * is to add another element to the $javascript array, deriving from
  798. * drupal_js_defaults(). See locale_js_alter() for an example of this.
  799. *
  800. * @param $scope
  801. * (optional) The scope for which the JavaScript rules should be returned.
  802. * Defaults to 'header'.
  803. * @param $javascript
  804. * (optional) An array with all JavaScript code. Defaults to the default
  805. * JavaScript array for the given scope.
  806. * @param $skip_alter
  807. * (optional) If set to TRUE, this function skips calling drupal_alter() on
  808. * $javascript, useful when the calling function passes a $javascript array
  809. * that has already been altered.
  810. *
  811. * @return
  812. * All JavaScript code segments and includes for the scope as HTML tags.
  813. *
  814. * @see drupal_add_js()
  815. * @see locale_js_alter()
  816. * @see drupal_js_defaults()
  817. */
  818. function advagg_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE) {
  819. if (empty($javascript)) {
  820. return '';
  821. }
  822. // Filter out elements of the given scope.
  823. $items = array();
  824. foreach ($javascript as $key => $item) {
  825. if ($item['scope'] == $scope) {
  826. $items[$key] = $item;
  827. }
  828. }
  829. // Sort the JavaScript so that it appears in the correct order.
  830. uasort($items, 'drupal_sort_css_js');
  831. // In Drupal 8, there's a JS_SETTING group for making setting variables
  832. // appear last after libraries have loaded. In Drupal 7, this is forced
  833. // without that group. We do not use the $key => $item type of iteration,
  834. // because PHP uses an internal array pointer for that, and we're modifying
  835. // the array order inside the loop.
  836. foreach (array_keys($items) as $key) {
  837. if ($items[$key]['type'] == 'setting') {
  838. $item = $items[$key];
  839. unset($items[$key]);
  840. $items[$key] = $item;
  841. }
  842. }
  843. // Provide the page with information about the individual JavaScript files
  844. // used, information not otherwise available when aggregation is enabled.
  845. $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
  846. unset($setting['ajaxPageState']['js']['settings']);
  847. drupal_add_js($setting, 'setting');
  848. // If we're outputting the header scope, then this might be the final time
  849. // that drupal_get_js() is running, so add the setting to this output as well
  850. // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
  851. // because drupal_get_js() was intentionally passed a $javascript argument
  852. // stripped of settings, potentially in order to override how settings get
  853. // output, so in this case, do not add the setting to this output.
  854. if (($scope == 'header' || ($scope == 'footer' && module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2)) && isset($items['settings'])) {
  855. $items['settings']['data'][] = $setting;
  856. }
  857. // Render the HTML needed to load the JavaScript.
  858. $elements = array(
  859. '#type' => 'scripts',
  860. '#items' => $items,
  861. );
  862. // Aurora & Omega themes uses alter without checking previous value.
  863. if (variable_get('advagg_enforce_scripts_callback', TRUE)) {
  864. $element_info = &drupal_static('element_info');
  865. if ($element_info['scripts']['#aggregate_callback'] != '_advagg_aggregate_js') {
  866. advagg_element_info_alter($element_info);
  867. }
  868. }
  869. return drupal_render($elements);
  870. }
  871. /**
  872. * Get all javascript scopes set in the $javascript array.
  873. *
  874. * @param $javascript
  875. * An array with all JavaScript code.
  876. *
  877. * @return
  878. * Array of scopes that are currently being used.
  879. */
  880. function advagg_get_js_scopes($javascript) {
  881. // Return if nothing given to us.
  882. if (empty($javascript) || !is_array($javascript)) {
  883. return FALSE;
  884. }
  885. // Filter out elements of the given scope.
  886. $scopes = array();
  887. foreach ($javascript as $key => $item) {
  888. // skip if the scope is not set.
  889. if (!is_array($item) || empty($item['scope'])) {
  890. continue;
  891. }
  892. if (!isset($scopes[$item['scope']])) {
  893. $scopes[$item['scope']] = TRUE;
  894. }
  895. }
  896. // Default to header if nothing found.
  897. if (empty($scopes)) {
  898. $scopes['header'] = TRUE;
  899. }
  900. // Return the scopes.
  901. return $scopes;
  902. }
  903. /**
  904. * Apply the advagg changes to the $css_js_groups array.
  905. *
  906. * @param $css_js_groups
  907. * An array of CSS or JS groups as returned by drupal_group_css/js().
  908. * @param $plans
  909. * An array of changes to do to the $css_js_groups array.
  910. * @return array
  911. * New version of $css_js_groups.
  912. */
  913. function advagg_merge_plans($css_js_groups, $plans) {
  914. $used_keys = array();
  915. foreach ($plans as $plan) {
  916. $plan_added = FALSE;
  917. foreach ($css_js_groups as $key => $group) {
  918. // Remove files from the old css/js array.
  919. $file_removed = FALSE;
  920. foreach ($css_js_groups[$key]['items'] as $k => $values) {
  921. if ( is_array($values)
  922. && array_key_exists('data', $values)
  923. && is_array($plan['items']['files'])
  924. && is_string($values['data'])
  925. && array_key_exists($values['data'], $plan['items']['files'])
  926. ) {
  927. unset($css_js_groups[$key]['items'][$k]);
  928. $file_removed = TRUE;
  929. }
  930. }
  931. // Replace first file of the old css/js array with one from advagg.
  932. if ($file_removed && !$plan_added) {
  933. $step = 0;
  934. do {
  935. $step++;
  936. $insert_key = '' . floatval($key) . '.' . $step;
  937. } while (array_key_exists($insert_key, $css_js_groups));
  938. $css_js_groups[(string) $insert_key] = $plan;
  939. $plan_added = TRUE;
  940. }
  941. }
  942. // Remove old css/js grouping if no files are left in it.
  943. foreach ($css_js_groups as $key => $group) {
  944. if (empty($css_js_groups[$key]['items'])) {
  945. unset($css_js_groups[$key]);
  946. }
  947. }
  948. if (!$plan_added) {
  949. foreach ($css_js_groups as $key => $group) {
  950. if ( empty($group['items']['aggregate_filenames_hash'])
  951. || $group['items']['aggregate_filenames_hash'] != $plan['items']['aggregate_filenames_hash']
  952. || empty($group['items']['aggregate_contents_hash'])
  953. || $group['items']['aggregate_contents_hash'] != $plan['items']['aggregate_contents_hash']
  954. ) {
  955. continue;
  956. }
  957. // Insert a unique key
  958. do {
  959. $key = '' . floatval($key) + 0.01;
  960. } while (array_key_exists((string) $key, $css_js_groups) || array_key_exists((string) $key, $used_keys));
  961. $used_keys[(string) $key] = TRUE;
  962. $css_js_groups[(string) $key] = $plan;
  963. $plan_added = TRUE;
  964. break;
  965. }
  966. }
  967. }
  968. // Key sort and normalize the array before returning it.
  969. ksort($css_js_groups);
  970. $css_js_groups = array_values($css_js_groups);
  971. return $css_js_groups;
  972. }
  973. // Helper functions.
  974. /**
  975. * Function used to see if aggregation is enabled.
  976. *
  977. * @return bool
  978. * The value of the advagg_enabled variable.
  979. */
  980. function advagg_enabled() {
  981. // Disable AdvAgg if module needs to be upgraded from 1.x to 2.x.
  982. if (!db_table_exists('advagg_aggregates_versions')) {
  983. $GLOBALS['conf']['advagg_enabled'] = FALSE;
  984. if (user_access(array('administer site configuration'))) {
  985. drupal_set_message(t('Please run <a href="@link">database updates</a>. AdvAgg will remain disabled until done.', array('@link' => url('update.php'))), 'error');
  986. }
  987. }
  988. // Only run code below if advagg is enabled.
  989. if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
  990. // Do not use the cache if the disable cookie is set.
  991. $cookie_name = 'AdvAggDisabled';
  992. $key = drupal_hash_base64(drupal_get_private_key());
  993. if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
  994. $GLOBALS['conf']['advagg_enabled'] = FALSE;
  995. $GLOBALS['conf']['preprocess_css'] = FALSE;
  996. $GLOBALS['conf']['preprocess_js'] = FALSE;
  997. }
  998. // Disable advagg if requested.
  999. if (isset($_GET['advagg']) && $_GET['advagg'] == -1 && user_access('bypass advanced aggregation')) {
  1000. $GLOBALS['conf']['advagg_enabled'] = FALSE;
  1001. $GLOBALS['conf']['preprocess_css'] = FALSE;
  1002. $GLOBALS['conf']['preprocess_js'] = FALSE;
  1003. }
  1004. // Enable core preprocessing if requested.
  1005. if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 && user_access('bypass advanced aggregation')) {
  1006. $GLOBALS['conf']['preprocess_css'] = TRUE;
  1007. $GLOBALS['conf']['preprocess_js'] = TRUE;
  1008. }
  1009. // Enable debugging if requested.
  1010. if (isset($_GET['advagg-debug']) && $_GET['advagg-debug'] == 1 && user_access('bypass advanced aggregation')) {
  1011. $GLOBALS['conf']['advagg_debug'] = TRUE;
  1012. }
  1013. }
  1014. return variable_get('advagg_enabled', ADVAGG_ENABLED);
  1015. }
  1016. /**
  1017. * Get the current path used for advagg admin configuration.
  1018. *
  1019. * @return string
  1020. * Path to root advagg config.
  1021. */
  1022. function advagg_admin_config_root_path() {
  1023. return variable_get('advagg_admin_config_root_path', ADVAGG_ADMIN_CONFIG_ROOT_PATH);
  1024. }
  1025. /**
  1026. * Get an array of all hooks and settings that affect aggregated files contents.
  1027. *
  1028. * @return
  1029. * array('variables' => array(...), 'hooks' => array(...))
  1030. */
  1031. function advagg_current_hooks_hash_array() {
  1032. $aggregate_settings = &drupal_static(__FUNCTION__);
  1033. if (isset($aggregate_settings)) {
  1034. return $aggregate_settings;
  1035. }
  1036. // Put all enabled hooks and settings into a big array.
  1037. $aggregate_settings = array(
  1038. 'variables' => array(
  1039. 'advagg_gzip' => variable_get('advagg_gzip', ADVAGG_GZIP),
  1040. 'is_https' => $GLOBALS['is_https'],
  1041. 'advagg_global_counter' => advagg_get_global_counter(),
  1042. 'base_path' => $GLOBALS['base_path'],
  1043. 'advagg_ie_css_selector_limiter' => variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER),
  1044. ),
  1045. 'hooks' => advagg_hooks_implemented(FALSE),
  1046. );
  1047. // Allow other modules to add in their own settings and hooks.
  1048. // Call hook_advagg_current_hooks_hash_array_alter().
  1049. drupal_alter('advagg_current_hooks_hash_array', $aggregate_settings);
  1050. return $aggregate_settings;
  1051. }
  1052. /**
  1053. * Get the hash of all hooks & settings that affect aggregated files contents.
  1054. *
  1055. * @return string
  1056. * hash value.
  1057. */
  1058. function advagg_get_current_hooks_hash() {
  1059. $current_hash = &drupal_static(__FUNCTION__);
  1060. if (!isset($current_hash)) {
  1061. // Get all advagg hooks and variables in use.
  1062. $aggregate_settings = advagg_current_hooks_hash_array();
  1063. // Generate the hash.
  1064. $current_hash = drupal_hash_base64(serialize($aggregate_settings));
  1065. // Save into variables for verification purposes later on if not found.
  1066. $settings = advagg_get_hash_settings($current_hash);
  1067. if (empty($settings) && lock_acquire(__FUNCTION__, 5)) {
  1068. // Save new hash into
  1069. advagg_set_hash_settings($current_hash, $aggregate_settings);
  1070. // Release lock
  1071. lock_release(__FUNCTION__);
  1072. }
  1073. }
  1074. return $current_hash;
  1075. }
  1076. /**
  1077. * Store settings associated with hash.
  1078. *
  1079. * @return
  1080. * value from db_merge
  1081. */
  1082. function advagg_set_hash_settings($hash, $settings) {
  1083. return db_merge('advagg_aggregates_hashes')
  1084. ->key(array('hash' => $hash))
  1085. ->fields(array('settings' => serialize($settings)))
  1086. ->execute();
  1087. }
  1088. /**
  1089. * Get back what hooks are implemented.
  1090. *
  1091. * @param $all
  1092. * If TRUE get all hooks related to css/js files.
  1093. * if FALSE get only the subset of hooks that alter the filename/contents.
  1094. * @return array
  1095. * List of hooks and what modules have implemented them.
  1096. */
  1097. function advagg_hooks_implemented($all = TRUE) {
  1098. // Get hooks in use.
  1099. $hooks = array(
  1100. 'advagg_get_css_file_contents_alter' => array(),
  1101. 'advagg_get_css_aggregate_contents_alter' => array(),
  1102. 'advagg_get_js_file_contents_alter' => array(),
  1103. 'advagg_get_js_aggregate_contents_alter' => array(),
  1104. 'advagg_save_aggregate_alter' => array(),
  1105. 'advagg_current_hooks_hash_array_alter' => array(),
  1106. 'advagg_get_root_files_dir_alter' => array(),
  1107. );
  1108. if ($all) {
  1109. $hooks += array(
  1110. 'advagg_build_aggregate_plans_alter' => array(),
  1111. 'advagg_changed_files' => array(),
  1112. 'advagg_css_groups_alter' => array(),
  1113. 'advagg_js_groups_alter' => array(),
  1114. 'advagg_modify_css_pre_render_alter' => array(),
  1115. 'advagg_modify_js_pre_render_alter' => array(),
  1116. 'js_alter' => array(),
  1117. 'css_alter' => array(),
  1118. );
  1119. }
  1120. // Call hook_advagg_hooks_implemented_alter().
  1121. drupal_alter('advagg_hooks_implemented', $hooks, $all);
  1122. // Cache module_implements as this will load up .inc files.
  1123. $cid = 'advagg_hooks_implemented:' . (int)$all . ':' . drupal_hash_base64(serialize($hooks));
  1124. $cache = cache_get($cid, 'cache_bootstrap');
  1125. if (!empty($cache->data)) {
  1126. $hooks = $cache->data;
  1127. }
  1128. else {
  1129. foreach ($hooks as $hook => $values) {
  1130. $hooks[$hook] = module_implements($hook);
  1131. }
  1132. cache_set($cid, $hooks, 'cache_bootstrap', CACHE_TEMPORARY);
  1133. }
  1134. return $hooks;
  1135. }
  1136. /**
  1137. * Returns the hashes settings.
  1138. *
  1139. * @param $name
  1140. * The name of the variable to return.
  1141. * @return
  1142. * The settings array or FALSE if not found.
  1143. */
  1144. function advagg_get_hash_settings($hash) {
  1145. $settings = db_select('advagg_aggregates_hashes', 'aah')
  1146. ->fields('aah', array('settings'))
  1147. ->condition('hash', $hash)
  1148. ->execute()
  1149. ->fetchField();
  1150. return !empty($settings) ? unserialize($settings) : FALSE;
  1151. }
  1152. /**
  1153. * Get the CSS & JS path for advagg.
  1154. *
  1155. * @return
  1156. * Example below:
  1157. * array(
  1158. * array(
  1159. * public://advagg_css,
  1160. * sites/default/files/advagg_css,
  1161. * ),
  1162. * array(
  1163. * public://advagg_js,
  1164. * sites/default/files/advagg_js,
  1165. * ),
  1166. * )
  1167. */
  1168. function advagg_get_root_files_dir() {
  1169. $css_paths = &drupal_static(__FUNCTION__ . '_css');
  1170. $js_paths = &drupal_static(__FUNCTION__ . '_js');
  1171. // Make sure directories are available and writable.
  1172. if (empty($css_paths) || empty($js_paths)) {
  1173. $css_paths[0] = 'public://advagg_css';
  1174. $js_paths[0] = 'public://advagg_js';
  1175. file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY);
  1176. file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY);
  1177. // Set the URI of the directory.
  1178. $css_paths[1] = parse_url(file_create_url($css_paths[0]));
  1179. $css_paths[1] = ltrim($css_paths[1]['path'], $GLOBALS['base_path']);
  1180. $js_paths[1] = parse_url(file_create_url($js_paths[0]));
  1181. $js_paths[1] = ltrim($js_paths[1]['path'], $GLOBALS['base_path']);
  1182. // Allow other modules to alter css and js paths.
  1183. // Call hook_advagg_get_root_files_dir_alter()
  1184. drupal_alter('advagg_get_root_files_dir', $css_paths, $js_paths);
  1185. }
  1186. return array($css_paths, $js_paths);
  1187. }
  1188. /**
  1189. * Given the type lets us know if advagg is enabled or disabled.
  1190. *
  1191. * @param $type
  1192. * css or js.
  1193. *
  1194. * @return
  1195. * TRUE or FALSE
  1196. */
  1197. function advagg_file_aggregation_enabled($type) {
  1198. if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
  1199. return FALSE;
  1200. }
  1201. if (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation')) {
  1202. return FALSE;
  1203. }
  1204. if ($type == 'css') {
  1205. return variable_get('preprocess_css', FALSE);
  1206. }
  1207. if ($type == 'js') {
  1208. return variable_get('preprocess_js', FALSE);
  1209. }
  1210. }
  1211. /**
  1212. * Update the atime value in the advagg_aggregates_versions table.
  1213. *
  1214. * @param $aggregate_filenames_hash
  1215. * Hash of the groupings of files.
  1216. * @param $aggregate_contents_hash
  1217. * Hash of the files contents.
  1218. *
  1219. * @return
  1220. * TRUE if a write to the DB was done.
  1221. */
  1222. function advagg_update_atime($aggregate_filenames_hash, $aggregate_contents_hash) {
  1223. $write_done = FALSE;
  1224. // Set the cache id.
  1225. $cache_id = 'advagg:db:' . $aggregate_filenames_hash . ADVAGG_SPACE . $aggregate_contents_hash;
  1226. // Set db record.
  1227. $record = array(
  1228. 'aggregate_filenames_hash' => $aggregate_filenames_hash,
  1229. 'aggregate_contents_hash' => $aggregate_contents_hash,
  1230. 'atime' => REQUEST_TIME,
  1231. );
  1232. // Use the cache to avoid hitting the database.
  1233. if ($cache = cache_get($cache_id, 'cache_advagg_info')) {
  1234. // See if the atime value needs to be updated;
  1235. if (!empty($cache->data['atime']) && $cache->data['atime'] > REQUEST_TIME - (12*60*60)) {
  1236. // If atime is less than 12 hours old, do nothing.
  1237. return $write_done;
  1238. }
  1239. }
  1240. // If lock is already acquired, return here.
  1241. if (!lock_acquire($cache_id, 5)) {
  1242. return $write_done;
  1243. }
  1244. // Update atime in DB.
  1245. if (drupal_write_record('advagg_aggregates_versions', $record, array('aggregate_filenames_hash', 'aggregate_contents_hash'))) {
  1246. $write_done = TRUE;
  1247. }
  1248. // Update the atime in the cache.
  1249. // Get fresh copy of the cache now that we are in a lock.
  1250. $cache = cache_get($cache_id, 'cache_advagg_info');
  1251. // Set the atime.
  1252. if (empty($cache->data)) {
  1253. $cache = new stdClass();
  1254. }
  1255. $cache->data['atime'] = REQUEST_TIME;
  1256. // Write to the cache.
  1257. cache_set($cache_id, $cache->data, 'cache_advagg_info', CACHE_PERMANENT);
  1258. // Release Lock.
  1259. lock_release($cache_id);
  1260. // Return if a write was done.
  1261. return $write_done;
  1262. }
  1263. /**
  1264. * Return the advagg_global_counter variable.
  1265. *
  1266. * @todo Allow this value to be kept in sync across a multisite.
  1267. *
  1268. * @return
  1269. * Int value.
  1270. */
  1271. function advagg_get_global_counter() {
  1272. $global_counter = variable_get('advagg_global_counter', ADVAGG_GLOBAL_COUNTER);
  1273. return $global_counter;
  1274. }
  1275. /**
  1276. * Cache clear callback for admin_menu/flush-cache/advagg.
  1277. */
  1278. function advagg_admin_flush_cache() {
  1279. module_load_include('inc', 'advagg', 'advagg.admin');
  1280. advagg_admin_flush_cache_button();
  1281. }
  1282. // Exact Copies of core functions from patches.
  1283. /**
  1284. * #pre_render callback to add elements needed for JavaScript to be rendered.
  1285. *
  1286. * This function evaluates the aggregation enabled/disabled condition on a group
  1287. * by group basis by testing whether an aggregate file has been made for the
  1288. * group rather than by testing the site-wide aggregation setting. This allows
  1289. * this function to work correctly even if modules have implemented custom
  1290. * logic for grouping and aggregating files.
  1291. *
  1292. * @param $element
  1293. * A render array containing:
  1294. * - #items: The JavaScript items as returned by drupal_add_js() and
  1295. * altered by drupal_get_js().
  1296. * - #group_callback: A function to call to group #items. Following
  1297. * this function, #aggregate_callback is called to aggregate items within
  1298. * the same group into a single file.
  1299. * - #aggregate_callback: A function to call to aggregate the items within
  1300. * the groups arranged by the #group_callback function.
  1301. *
  1302. * @return
  1303. * A render array that will render to a string of JavaScript tags.
  1304. *
  1305. * @see drupal_get_js()
  1306. */
  1307. function advagg_pre_render_scripts($elements) {
  1308. // Group and aggregate the items.
  1309. if (isset($elements['#group_callback'])) {
  1310. $elements['#groups'] = $elements['#group_callback']($elements['#items']);
  1311. }
  1312. if (isset($elements['#aggregate_callback'])) {
  1313. $elements['#aggregate_callback']($elements['#groups']);
  1314. }
  1315. // A dummy query-string is added to filenames, to gain control over
  1316. // browser-caching. The string changes on every update or full cache
  1317. // flush, forcing browsers to load a new copy of the files, as the
  1318. // URL changed. Files that should not be cached (see drupal_add_js())
  1319. // get REQUEST_TIME as query-string instead, to enforce reload on every
  1320. // page request.
  1321. $default_query_string = variable_get('css_js_query_string', '0');
  1322. // For inline JavaScript to validate as XHTML, all JavaScript containing
  1323. // XHTML needs to be wrapped in CDATA. To make that backwards compatible
  1324. // with HTML 4, we need to comment out the CDATA-tag.
  1325. $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
  1326. $embed_suffix = "\n//--><!]]>\n";
  1327. // Since JavaScript may look for arguments in the URL and act on them, some
  1328. // third-party code might require the use of a different query string.
  1329. $js_version_string = variable_get('drupal_js_version_query_string', 'v=');
  1330. // Defaults for each SCRIPT element.
  1331. $element_defaults = array(
  1332. '#type' => 'html_tag',
  1333. '#tag' => 'script',
  1334. '#value' => '',
  1335. '#attributes' => array(
  1336. 'type' => 'text/javascript',
  1337. ),
  1338. );
  1339. // Loop through each group.
  1340. foreach ($elements['#groups'] as $group) {
  1341. // If a group of files has been aggregated into a single file,
  1342. // $group['data'] contains the URI of the aggregate file. Add a single
  1343. // script element for this file.
  1344. if ($group['type'] == 'file' && isset($group['data'])) {
  1345. $element = $element_defaults;
  1346. $element['#attributes']['src'] = file_create_url($group['data']);
  1347. $element['#browsers'] = $group['browsers'];
  1348. if (!empty($group['defer'])) {
  1349. $element['#attributes']['defer'] = 'defer';
  1350. }
  1351. if (!empty($group['async'])) {
  1352. $element['#attributes']['async'] = 'async';
  1353. }
  1354. if (!empty($group['onload'])) {
  1355. $element['#attributes']['onload'] = $group['onload'];
  1356. }
  1357. $elements[] = $element;
  1358. }
  1359. // For non-file types, and non-aggregated files, add a script element per
  1360. // item.
  1361. else {
  1362. foreach ($group['items'] as $item) {
  1363. // Skip if data is empty.
  1364. if (empty($item['data'])) {
  1365. continue;
  1366. }
  1367. // Element properties that do not depend on item type.
  1368. $element = $element_defaults;
  1369. if (!empty($item['defer'])) {
  1370. $element['#attributes']['defer'] = 'defer';
  1371. }
  1372. if (!empty($item['async'])) {
  1373. $element['#attributes']['async'] = 'async';
  1374. }
  1375. if (!empty($item['onload'])) {
  1376. $element['#attributes']['onload'] = $item['onload'];
  1377. }
  1378. $element['#browsers'] = isset($item['browsers']) ? $item['browsers'] : array();
  1379. // Crude type detection if needed.
  1380. if (empty($item['type'])) {
  1381. if (is_array($item['data'])) {
  1382. $item['type'] = 'setting';
  1383. }
  1384. elseif (strpos($item['data'], 'http://') === 0 || strpos($item['data'], 'https://') === 0 || strpos($item['data'], '//') === 0) {
  1385. $item['type'] = 'external';
  1386. }
  1387. elseif (file_exists(trim($item['data']))) {
  1388. $item['type'] = 'file';
  1389. }
  1390. else {
  1391. $item['type'] = 'inline';
  1392. }
  1393. }
  1394. // Element properties that depend on item type.
  1395. switch ($item['type']) {
  1396. case 'setting':
  1397. $element['#value_prefix'] = $embed_prefix;
  1398. $element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
  1399. $element['#value_suffix'] = $embed_suffix;
  1400. break;
  1401. case 'inline':
  1402. $element['#value_prefix'] = $embed_prefix;
  1403. $element['#value'] = $item['data'];
  1404. $element['#value_suffix'] = $embed_suffix;
  1405. break;
  1406. case 'file':
  1407. $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
  1408. $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
  1409. $element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
  1410. break;
  1411. case 'external':
  1412. $element['#attributes']['src'] = $item['data'];
  1413. break;
  1414. }
  1415. $elements[] = $element;
  1416. }
  1417. }
  1418. }
  1419. return $elements;
  1420. }
  1421. /**
  1422. * Default callback to group JavaScript items.
  1423. *
  1424. * This function arranges the JavaScript items that are in the #items property
  1425. * of the scripts element into groups. When aggregation is enabled, files within
  1426. * a group are aggregated into a single file, significantly improving page
  1427. * loading performance by minimizing network traffic overhead.
  1428. *
  1429. * This function puts multiple items into the same group if they are groupable
  1430. * and if they are for the same browsers. Items of the 'file' type are groupable
  1431. * if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or
  1432. * 'external' type are not groupable.
  1433. *
  1434. * This function also ensures that the process of grouping items does not change
  1435. * their relative order. This requirement may result in multiple groups for the
  1436. * same type and browsers, if needed to accommodate other items in
  1437. * between.
  1438. *
  1439. * @param $javascript
  1440. * An array of JavaScript items, as returned by drupal_add_js(), but after
  1441. * alteration performed by drupal_get_js().
  1442. *
  1443. * @return
  1444. * An array of JavaScript groups. Each group contains the same keys (e.g.,
  1445. * 'data', etc.) as a JavaScript item from the $javascript parameter, with the
  1446. * value of each key applying to the group as a whole. Each group also
  1447. * contains an 'items' key, which is the subset of items from $javascript that
  1448. * are in the group.
  1449. *
  1450. * @see drupal_pre_render_scripts()
  1451. */
  1452. function advagg_group_js($javascript) {
  1453. $groups = array();
  1454. // If a group can contain multiple items, we track the information that must
  1455. // be the same for each item in the group, so that when we iterate the next
  1456. // item, we can determine if it can be put into the current group, or if a
  1457. // new group needs to be made for it.
  1458. $current_group_keys = NULL;
  1459. $index = -1;
  1460. foreach ($javascript as $item) {
  1461. // The browsers for which the JavaScript item needs to be loaded is part of
  1462. // the information that determines when a new group is needed, but the order
  1463. // of keys in the array doesn't matter, and we don't want a new group if all
  1464. // that's different is that order.
  1465. if (isset($item['browsers'])) {
  1466. ksort($item['browsers']);
  1467. }
  1468. else {
  1469. $item['browsers'] = array();
  1470. }
  1471. switch ($item['type']) {
  1472. case 'file':
  1473. // Group file items if their 'preprocess' flag is TRUE.
  1474. // Help ensure maximum reuse of aggregate files by only grouping
  1475. // together items that share the same 'group' value and 'every_page'
  1476. // flag. See drupal_add_js() for details about that.
  1477. $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['browsers']) : FALSE;
  1478. break;
  1479. case 'external':
  1480. case 'setting':
  1481. case 'inline':
  1482. // Do not group external, settings, and inline items.
  1483. $group_keys = FALSE;
  1484. break;
  1485. }
  1486. // If the group keys don't match the most recent group we're working with,
  1487. // then a new group must be made.
  1488. if ($group_keys !== $current_group_keys) {
  1489. $index++;
  1490. // Initialize the new group with the same properties as the first item
  1491. // being placed into it. The item's 'data' and 'weight' properties are
  1492. // unique to the item and should not be carried over to the group.
  1493. $groups[$index] = $item;
  1494. unset($groups[$index]['data'], $groups[$index]['weight']);
  1495. $groups[$index]['items'] = array();
  1496. $current_group_keys = $group_keys ? $group_keys : NULL;
  1497. }
  1498. // Add the item to the current group.
  1499. $groups[$index]['items'][] = $item;
  1500. }
  1501. return $groups;
  1502. }