advagg.module

  1. cis7 sites/all/modules/ulmus/advagg/advagg.module
  2. cle7 sites/all/modules/ulmus/advagg/advagg.module
  3. ecd7 sites/all/modules/ulmus/advagg/advagg.module
  4. elmsmedia7 sites/all/modules/ulmus/advagg/advagg.module
  5. harmony7 sites/all/modules/ulmus/advagg/advagg.module
  6. icor7 sites/all/modules/ulmus/advagg/advagg.module
  7. meedjum_blog7 sites/all/modules/ulmus/advagg/advagg.module
  8. mooc7 sites/all/modules/ulmus/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_ajax_render_alter Implements hook_ajax_render_alter().
advagg_build_aggregates Builds the requested CSS/JS aggregates.
advagg_build_ajax_js_css Gets the core CSS/JS included in this ajax request.
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_drupal_sort_css_js_stable Stable sort for CSS and JS items.
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_file_url_alter Implements hook_file_url_alter().
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 Callback for pre_render so elements can be modified before they are rendered.
advagg_modify_js_pre_render Callback for pre_render 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 Callback for pre_render to add elements needed for JavaScript to be rendered.
advagg_remove_short_keys Callback for array_filter. Will return FALSE if strlen < 3.
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_url_inbound_alter Inbound URL rewrite helper.
_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_CLEAR_SCRIPTS Empty the scripts key inside of template_process_html replacement function.
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_IE_CSS_SELECTOR_LIMITER_VALUE The value the IE css selector should use
ADVAGG_JS_FIX_TYPE Scan and fix any JS that was added with the wrong type.
ADVAGG_NEEDS_UPDATE TRUE when db table 'advagg_aggregates_versions' is missing in advagg_enable.
ADVAGG_SCRIPTS_SCOPE_ANYWHERE Allow JavaScript insertion into any scope even if theme does not support it.
ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE Display a message that the bypass cookie is set.
ADVAGG_SPACE Default space characters.
ADVAGG_URL_INBOUND_ALTER Run advagg_url_inbound_alter().
ADVAGG_USE_HTTPRL Send non blocking requests in order to generate aggregated files via HTTPRL.

File

sites/all/modules/ulmus/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', FALSE);
  43. /**
  44. * The value the IE css selector should use
  45. */
  46. define('ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE', 4095);
  47. /**
  48. * Default location of AdvAgg configuration items.
  49. */
  50. define('ADVAGG_ADMIN_CONFIG_ROOT_PATH', 'admin/config/development/performance');
  51. /**
  52. * Default value for debugging info to watchdog.
  53. */
  54. define('ADVAGG_DEBUG', FALSE);
  55. /**
  56. * Scan and fix any JS that was added with the wrong type.
  57. */
  58. define('ADVAGG_JS_FIX_TYPE', TRUE);
  59. /**
  60. * Scan and fix any CSS that was added with the wrong type.
  61. */
  62. define('ADVAGG_CSS_FIX_TYPE', TRUE);
  63. /**
  64. * Generate a .htaccess file in the AdvAgg dirs.
  65. */
  66. define('ADVAGG_HTACCESS_CHECK_GENERATE', TRUE);
  67. /**
  68. * Display a message that the bypass cookie is set.
  69. */
  70. define('ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE', TRUE);
  71. /**
  72. * Run advagg_url_inbound_alter().
  73. */
  74. define('ADVAGG_URL_INBOUND_ALTER', TRUE);
  75. /**
  76. * Allow JavaScript insertion into any scope even if theme does not support it.
  77. */
  78. define('ADVAGG_SCRIPTS_SCOPE_ANYWHERE', FALSE);
  79. /**
  80. * Empty the scripts key inside of template_process_html replacement function.
  81. */
  82. define('ADVAGG_CLEAR_SCRIPTS', TRUE);
  83. /**
  84. * TRUE when db table 'advagg_aggregates_versions' is missing in advagg_enable.
  85. */
  86. define('ADVAGG_NEEDS_UPDATE', FALSE);
  87. // Core hook implementations.
  88. /**
  89. * Inbound URL rewrite helper.
  90. *
  91. * If host includes subdomain, rewrite URI and internal path if necessary.
  92. */
  93. function advagg_url_inbound_alter(&$path, $original_path, $path_language) {
  94. // Do nothing if this has been disabled.
  95. if (!variable_get('advagg_url_inbound_alter', ADVAGG_URL_INBOUND_ALTER)) {
  96. return;
  97. }
  98. // Setup static so we only need to run the logic once.
  99. $already_ran = &drupal_static(__FUNCTION__);
  100. if (!isset($already_ran)) {
  101. $already_ran = array();
  102. }
  103. $request_path = request_path();
  104. // Set the path again if we already did this alter.
  105. if (array_key_exists($request_path, $already_ran)) {
  106. $path = $already_ran[$request_path];
  107. return;
  108. }
  109. // If requested path was for an advagg file but now it is something else
  110. // switch is back to the advagg file.
  111. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:2
  112. if ( !empty($path)
  113. && $path != $request_path
  114. && preg_match('/^.*\/advagg_(j|cs)s\/(j|cs)s' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}\.(j|cs)s.*$/', $request_path)
  115. ) {
  116. // Get the advagg paths.
  117. $advagg_path = advagg_get_root_files_dir();
  118. // Get the top level path.
  119. $top_level = substr($advagg_path[0][1], 0, strpos($advagg_path[0][1], 'advagg_css'));
  120. // Only change if it's an exact match.
  121. $start = strpos($request_path, $top_level . 'advagg_');
  122. if ($start === 0) {
  123. // Set path to correct advagg path.
  124. $path = substr($request_path, $start);
  125. $already_ran[$request_path] = $path;
  126. }
  127. else {
  128. // Put all languages prefixes into an array.
  129. $language_list = language_list();
  130. $prefixes = array();
  131. foreach ($language_list as $lang) {
  132. if ($lang->enabled && strpos($request_path, $lang->prefix) !== FALSE) {
  133. $prefixes[$lang->prefix] = $lang->prefix;
  134. }
  135. }
  136. if (!empty($prefixes)) {
  137. // Remove all enabled languages prefixes from the beginning of the path.
  138. $substr_to_shrink = substr($request_path, 0, $start);
  139. foreach ($prefixes as $prefix) {
  140. $substr_to_shrink = str_replace($prefix . '/', '', $substr_to_shrink);
  141. }
  142. // Set path to correct advagg path.
  143. $path = $substr_to_shrink . substr($request_path, $start);
  144. $already_ran[$request_path] = $path;
  145. }
  146. }
  147. }
  148. }
  149. /**
  150. * Implements hook_hook_info().
  151. */
  152. function advagg_hook_info() {
  153. // List of hooks that can be inside of *.advagg.inc files.
  154. $advagg_hooks = array(
  155. 'advagg_get_css_file_contents_alter',
  156. 'advagg_get_css_aggregate_contents_alter',
  157. 'advagg_get_js_file_contents_alter',
  158. 'advagg_get_js_aggregate_contents_alter',
  159. 'advagg_save_aggregate_alter',
  160. 'advagg_build_aggregate_plans_alter',
  161. 'advagg_changed_files',
  162. 'advagg_scan_for_changes',
  163. 'advagg_context_alter',
  164. );
  165. $hooks = array();
  166. foreach ($advagg_hooks as $hook) {
  167. $hooks[$hook] = array('group' => 'advagg');
  168. }
  169. return $hooks;
  170. }
  171. /**
  172. * Implements hook_module_implements_alter().
  173. */
  174. function advagg_module_implements_alter(&$implementations, $hook) {
  175. // Move advagg to the top.
  176. if ($hook === 'theme_registry_alter' && array_key_exists('advagg', $implementations)) {
  177. $item = array('advagg' => $implementations['advagg']);
  178. unset($implementations['advagg']);
  179. $implementations = array_merge($item, $implementations);
  180. }
  181. // Move advagg to the bottom.
  182. if ($hook === 'element_info_alter' && array_key_exists('advagg', $implementations)) {
  183. $item = $implementations['advagg'];
  184. unset($implementations['advagg']);
  185. $implementations['advagg'] = $item;
  186. }
  187. // Move advagg & advagg_mod to the bottom, but advagg is above advagg_mod.
  188. if ($hook === 'js_alter' && array_key_exists('advagg', $implementations)) {
  189. $item = $implementations['advagg'];
  190. unset($implementations['advagg']);
  191. $implementations['advagg'] = $item;
  192. if (array_key_exists('advagg_mod', $implementations)) {
  193. $item = $implementations['advagg_mod'];
  194. unset($implementations['advagg_mod']);
  195. $implementations['advagg_mod'] = $item;
  196. }
  197. }
  198. // Move advagg to the bottom.
  199. if ($hook === 'css_alter' && array_key_exists('advagg', $implementations)) {
  200. $item = $implementations['advagg'];
  201. unset($implementations['advagg']);
  202. $implementations['advagg'] = $item;
  203. }
  204. // Move advagg to the bottom.
  205. if ($hook === 'file_url_alter' && array_key_exists('advagg', $implementations)) {
  206. $item = $implementations['advagg'];
  207. unset($implementations['advagg']);
  208. $implementations['advagg'] = $item;
  209. }
  210. }
  211. /**
  212. * Implements hook_system_info_alter().
  213. */
  214. function advagg_system_info_alter(&$info, $file, $type) {
  215. $config_path = &drupal_static(__FUNCTION__);
  216. // Get advagg config path.
  217. if (empty($config_path)) {
  218. $config_path = advagg_admin_config_root_path();
  219. }
  220. // Replace advagg path.
  221. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:2
  222. // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:8
  223. if ( !empty($info['configure'])
  224. && strpos($info['configure'], '/advagg') !== FALSE
  225. && ((
  226. !empty($info['dependencies'])
  227. && is_array($info['dependencies'])
  228. && in_array('advagg', $info['dependencies'])
  229. ) || $file->name == 'advagg' )
  230. ) {
  231. $pos = strpos($info['configure'], '/advagg') + 7;
  232. $substr = substr($info['configure'], 0, $pos);
  233. $info['configure'] = str_replace($substr, $config_path . '/advagg', $info['configure']);
  234. }
  235. }
  236. /**
  237. * Implements hook_permission().
  238. */
  239. function advagg_permission() {
  240. return array(
  241. 'bypass advanced aggregation' => array(
  242. 'title' => t('bypass advanced aggregation'),
  243. 'description' => t('User can use URL query strings to bypass AdvAgg.'),
  244. ),
  245. );
  246. }
  247. /**
  248. * Implements hook_file_url_alter().
  249. */
  250. function advagg_file_url_alter(&$original_uri) {
  251. // Ignore coder warnings.
  252. // @ignore sniffer_commenting_inlinecomment_spacingbefore:10
  253. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:11
  254. // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:19
  255. // @ignore sniffer_commenting_inlinecomment_spacingafter
  256. // Do nothing if
  257. // in maintenance_mode
  258. // CDN module does not exist
  259. // CDN far future is disabled
  260. // CDN mode is not basic
  261. // URI does not contain /advagg_
  262. // URI does not contain cdn/farfuture/
  263. if ( variable_get('maintenance_mode', FALSE)
  264. || !module_exists('cdn')
  265. || !variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT)
  266. || variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC) != CDN_MODE_BASIC
  267. || strpos($original_uri, '/advagg_') === FALSE
  268. || strpos($original_uri, 'cdn/farfuture/') === FALSE
  269. ) {
  270. return;
  271. }
  272. // Remove cdn/farfuture/BASE64/prefix:value/ from the URI.
  273. $original_uri = preg_replace('/cdn\/farfuture\/[A-Za-z0-9-_]{43}\/[A-Za-z]+\:[A-Za-z0-9-_]+\//', '', $original_uri);
  274. }
  275. /**
  276. * Implements hook_menu().
  277. */
  278. function advagg_menu() {
  279. list($css_path, $js_path) = advagg_get_root_files_dir();
  280. $file_path = drupal_get_path('module', 'advagg');
  281. $config_path = advagg_admin_config_root_path();
  282. $items[$css_path[1] . '/%'] = array(
  283. 'title' => "Generate CSS Aggregate",
  284. 'page callback' => 'advagg_missing_aggregate',
  285. 'type' => MENU_CALLBACK,
  286. 'access callback' => TRUE,
  287. 'file path' => $file_path,
  288. 'file' => 'advagg.missing.inc',
  289. );
  290. $items[$js_path[1] . '/%'] = array(
  291. 'title' => "Generate JS Aggregate",
  292. 'page callback' => 'advagg_missing_aggregate',
  293. 'type' => MENU_CALLBACK,
  294. 'access callback' => TRUE,
  295. 'file path' => $file_path,
  296. 'file' => 'advagg.missing.inc',
  297. );
  298. $items[$config_path . '/default'] = array(
  299. 'title' => 'Performance',
  300. 'type' => MENU_DEFAULT_LOCAL_TASK,
  301. 'file path' => drupal_get_path('module', 'system'),
  302. 'weight' => -10,
  303. );
  304. $items[$config_path . '/advagg'] = array(
  305. 'title' => 'Advanced CSS/JS Aggregation',
  306. 'description' => 'Configuration for Advanced CSS/JS Aggregation.',
  307. 'page callback' => 'drupal_get_form',
  308. 'page arguments' => array('advagg_admin_settings_form'),
  309. 'type' => MENU_LOCAL_TASK,
  310. 'access arguments' => array('administer site configuration'),
  311. 'file path' => $file_path,
  312. 'file' => 'advagg.admin.inc',
  313. 'weight' => 1,
  314. );
  315. $items[$config_path . '/advagg/config'] = array(
  316. 'title' => 'Configuration',
  317. 'type' => MENU_DEFAULT_LOCAL_TASK,
  318. 'weight' => -10,
  319. );
  320. $items[$config_path . '/advagg/info'] = array(
  321. 'title' => 'Information',
  322. 'description' => 'More detailed information about advagg.',
  323. 'page callback' => 'drupal_get_form',
  324. 'page arguments' => array('advagg_admin_info_form'),
  325. 'type' => MENU_LOCAL_TASK,
  326. 'access arguments' => array('administer site configuration'),
  327. 'file path' => $file_path,
  328. 'file' => 'advagg.admin.inc',
  329. 'weight' => 18,
  330. );
  331. $items[$config_path . '/advagg/operations'] = array(
  332. 'title' => 'Operations',
  333. 'description' => 'Flush caches, set the bypass cookie, take drastic actions.',
  334. 'page callback' => 'drupal_get_form',
  335. 'page arguments' => array('advagg_admin_operations_form'),
  336. 'type' => MENU_LOCAL_TASK,
  337. 'access arguments' => array('administer site configuration'),
  338. 'file path' => $file_path,
  339. 'file' => 'advagg.admin.inc',
  340. 'weight' => 20,
  341. );
  342. return $items;
  343. }
  344. // @ignore sniffer_commenting_functioncomment_hookparamdoc:7
  345. /**
  346. * Implements hook_cron().
  347. *
  348. * This will be ran once a day at most.
  349. *
  350. * @param bool $bypass_time_check
  351. * Set to TRUE to skip the 24 hour check.
  352. */
  353. function advagg_cron($bypass_time_check = FALSE) {
  354. // Execute once a day (24 hours).
  355. if (!$bypass_time_check && variable_get('advagg_cron_timestamp', 0) > (REQUEST_TIME - 86400)) {
  356. return array();
  357. }
  358. variable_set('advagg_cron_timestamp', REQUEST_TIME);
  359. $return = array();
  360. // Clear out all stale advagg aggregated files.
  361. module_load_include('inc', 'advagg', 'advagg.cache');
  362. $return[] = advagg_delete_stale_aggregates();
  363. // Remove aggregates that include missing files.
  364. $return[] = advagg_remove_missing_files_from_db();
  365. // Remove unused aggregates.
  366. $return[] = advagg_remove_old_unused_aggregates();
  367. // Remove expired locks from the semaphore database table.
  368. $return[] = advagg_cleanup_semaphore_table();
  369. return $return;
  370. }
  371. // @ignore sniffer_commenting_functioncomment_hookparamdoc:5
  372. /**
  373. * Implements hook_flush_caches().
  374. *
  375. * @param bool $all_bins
  376. * TRUE: Get all advagg cache bins
  377. */
  378. function advagg_flush_caches($all_bins = FALSE) {
  379. // Send back a blank array if aav table doesn't exist.
  380. if (!db_table_exists('advagg_aggregates_versions')) {
  381. return array();
  382. }
  383. // Scan for new changes.
  384. module_load_include('inc', 'advagg', 'advagg.cache');
  385. $flushed = advagg_push_new_changes();
  386. // Get list of cache bins to clear.
  387. $bins = array(
  388. 'cache_advagg_aggregates',
  389. 'cache_advagg_info',
  390. );
  391. return $bins;
  392. }
  393. /**
  394. * Implements hook_element_info_alter().
  395. */
  396. function advagg_element_info_alter(&$type) {
  397. // Swap in our own aggregation callback.
  398. if (isset($type['styles']['#aggregate_callback'])) {
  399. $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css';
  400. $type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render';
  401. }
  402. // Swap in our own aggregation callback.
  403. if (isset($type['scripts']['#aggregate_callback'])) {
  404. $type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js';
  405. }
  406. else {
  407. $type['scripts'] = array(
  408. '#items' => array(),
  409. '#pre_render' => array('advagg_pre_render_scripts'),
  410. '#group_callback' => 'advagg_group_js',
  411. '#aggregate_callback' => '_advagg_aggregate_js',
  412. '#type' => 'scripts',
  413. );
  414. }
  415. $type['scripts']['#pre_render'][] = 'advagg_modify_js_pre_render';
  416. }
  417. /**
  418. * Implements hook_theme_registry_alter().
  419. *
  420. * Replace template_process_html with _advagg_process_html
  421. */
  422. function advagg_theme_registry_alter(&$theme_registry) {
  423. if (!isset($theme_registry['html'])) {
  424. return;
  425. }
  426. // Replace core's process function with our own.
  427. $index = array_search('template_process_html', $theme_registry['html']['process functions']);
  428. if ($index !== FALSE) {
  429. $theme_registry['html']['process functions'][$index] = '_advagg_process_html';
  430. }
  431. }
  432. /**
  433. * Implements hook_ajax_render_alter().
  434. */
  435. function advagg_ajax_render_alter(&$commands) {
  436. // Do not run hook if AdvAgg is disabled.
  437. if (!advagg_enabled()) {
  438. return;
  439. }
  440. // Conditionally adds the default Drupal/jQuery libraries to the page.
  441. // @see http://drupal.org/node/1279226
  442. if (function_exists('drupal_add_js_page_defaults')) {
  443. drupal_add_js_page_defaults();
  444. }
  445. // Get Core JS.
  446. list($styles, $core_scripts_header, $core_scripts_footer, $items, $settings) = advagg_build_ajax_js_css();
  447. // Get AdvAgg JS.
  448. $scripts_footer = advagg_get_js('footer', $items['js'], TRUE, TRUE);
  449. $scripts_header = advagg_get_js('header', $items['js'], TRUE, TRUE);
  450. // Remove core JS.
  451. foreach ($commands as $key => $values) {
  452. // Skip if not an array or not a command.
  453. if (!is_array($values) || empty($values['command'])) {
  454. continue;
  455. }
  456. // Ignore coder warnings.
  457. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:22
  458. // @ignore sniffer_whitespace_closebracketspacing_closingwhitespace:25
  459. // @ignore sniffer_commenting_inlinecomment_spacingafter
  460. if ( $values['command'] == 'settings'
  461. && is_array($values['settings'])
  462. && !empty($values['merge'])
  463. ) {
  464. // Remove JS settings.
  465. unset($commands[$key]);
  466. continue;
  467. }
  468. if ( $values['command'] == 'insert'
  469. && is_null($values['settings'])
  470. && $values['method'] == 'prepend'
  471. && $values['data'] == $core_scripts_header
  472. ) {
  473. // Remove JS header.
  474. unset($commands[$key]);
  475. continue;
  476. }
  477. if ( $values['command'] == 'insert'
  478. && is_null($values['settings'])
  479. && $values['method'] == 'append'
  480. && $values['data'] == $core_scripts_footer
  481. ) {
  482. // Remove JS footer.
  483. unset($commands[$key]);
  484. continue;
  485. }
  486. }
  487. // Add in AdvAgg JS.
  488. $extra_commands = array();
  489. if (!empty($scripts_header)) {
  490. $extra_commands[] = ajax_command_prepend('head', $scripts_header);
  491. }
  492. if (!empty($scripts_footer)) {
  493. $extra_commands[] = ajax_command_append('body', $scripts_footer);
  494. }
  495. if (!empty($extra_commands)) {
  496. $commands = array_merge($extra_commands, $commands);
  497. }
  498. if (!empty($settings)) {
  499. array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
  500. }
  501. }
  502. /**
  503. * Implements hook_js_alter().
  504. */
  505. function advagg_js_alter(&$js) {
  506. if (!advagg_enabled() || !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) {
  507. return;
  508. }
  509. // Fix type if it was incorrectly set.
  510. foreach ($js as $key => &$value) {
  511. if (empty($value['data']) || !is_string($value['data'])) {
  512. continue;
  513. }
  514. // If type is external but doesn't start with http or // change it to file.
  515. if ($value['type'] == 'external' && stripos($value['data'], 'http://') !== 0 && stripos($value['data'], 'https://') !== 0 && stripos($value['data'], '//') !== 0) {
  516. $value['type'] = 'file';
  517. }
  518. // If type is file but it starts with http or // change it to external.
  519. if ((stripos($value['data'], 'http://') === 0 || stripos($value['data'], 'https://') === 0 || stripos($value['data'], '//') === 0) && $value['type'] == 'file') {
  520. $value['type'] = 'external';
  521. }
  522. }
  523. }
  524. /**
  525. * Implements hook_css_alter().
  526. */
  527. function advagg_css_alter(&$css) {
  528. if (!advagg_enabled() || !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) {
  529. return;
  530. }
  531. // Fix type if it was incorrectly set.
  532. foreach ($css as $key => &$value) {
  533. if (empty($value['data']) || !is_string($value['data'])) {
  534. continue;
  535. }
  536. // If type is external but doesn't start with http or // change it to file.
  537. if ($value['type'] == 'external' && stripos($value['data'], 'http://') !== 0 && stripos($value['data'], 'https://') !== 0 && stripos($value['data'], '//') !== 0) {
  538. $value['type'] = 'file';
  539. }
  540. // If type is file but it starts with http or // change it to external.
  541. if ((stripos($value['data'], 'http://') === 0 || stripos($value['data'], 'https://') === 0 || stripos($value['data'], '//') === 0) && $value['type'] == 'file') {
  542. $value['type'] = 'external';
  543. }
  544. }
  545. }
  546. /**
  547. * Implements hook_admin_menu_cache_info().
  548. *
  549. * Add in a cache flush for advagg.
  550. */
  551. function advagg_admin_menu_cache_info() {
  552. if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
  553. $caches['advagg'] = array(
  554. 'title' => t('Adv CSS/JS Agg'),
  555. 'callback' => 'advagg_admin_flush_cache',
  556. );
  557. return $caches;
  558. }
  559. }
  560. /**
  561. * Implements hook_admin_menu_output_alter().
  562. *
  563. * Add in a cache flush for advagg.
  564. */
  565. function advagg_admin_menu_output_alter(&$content) {
  566. if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
  567. // Remove default core aggregation link.
  568. unset($content['icon']['icon']['flush-cache']['assets']);
  569. }
  570. }
  571. /**
  572. * Implements hook_preprocess_html().
  573. *
  574. * Add in rendering IE meta tag if "combine CSS" is enabled.
  575. */
  576. function advagg_preprocess_html() {
  577. // Do not force IE rendering mode if "combine CSS" is disabled.
  578. if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) {
  579. return;
  580. }
  581. // Setup IE meta tag to force IE rendering mode.
  582. $meta_ie_render_engine = array(
  583. '#type' => 'html_tag',
  584. '#tag' => 'meta',
  585. '#attributes' => array(
  586. 'http-equiv' => 'X-UA-Compatible',
  587. 'content' => 'IE=edge,chrome=1',
  588. ),
  589. '#weight' => '-99999',
  590. '#prefix' => '<!--[if IE]>',
  591. '#suffix' => '<![endif]-->',
  592. );
  593. // Add header meta tag for IE to head.
  594. drupal_add_html_head($meta_ie_render_engine, 'meta_ie_render_engine');
  595. }
  596. // Core CSS/JS override functions.
  597. /**
  598. * Callback for pre_render so elements can be modified before they are rendered.
  599. *
  600. * @param array $elements
  601. * A render array containing:
  602. * - #items: The JavaScript items as returned by drupal_add_js() and
  603. * altered by drupal_get_js().
  604. * - #group_callback: A function to call to group #items. Following
  605. * this function, #aggregate_callback is called to aggregate items within
  606. * the same group into a single file.
  607. * - #aggregate_callback: A function to call to aggregate the items within
  608. * the groups arranged by the #group_callback function.
  609. *
  610. * @return array
  611. * A render array that will render to a string of JavaScript tags.
  612. */
  613. function advagg_modify_js_pre_render($elements) {
  614. // Put children elements into a reference array.
  615. $children = array();
  616. foreach ($elements as $key => &$value) {
  617. if ($key !== '' && $key[0] === '#') {
  618. continue;
  619. }
  620. $children[$key] = &$value;
  621. }
  622. // Allow other modules to modify $children & $elements before they are
  623. // rendered.
  624. // Call hook_advagg_modify_js_pre_render_alter()
  625. drupal_alter('advagg_modify_js_pre_render', $children, $elements);
  626. return $elements;
  627. }
  628. /**
  629. * Callback for pre_render so elements can be modified before they are rendered.
  630. *
  631. * @param array $elements
  632. * A render array containing:
  633. * - #items: The CSS items as returned by drupal_add_css() and
  634. * altered by drupal_get_css().
  635. * - #group_callback: A function to call to group #items. Following
  636. * this function, #aggregate_callback is called to aggregate items within
  637. * the same group into a single file.
  638. * - #aggregate_callback: A function to call to aggregate the items within
  639. * the groups arranged by the #group_callback function.
  640. *
  641. * @return array
  642. * A render array that will render to a string of JavaScript tags.
  643. */
  644. function advagg_modify_css_pre_render($elements) {
  645. // Put children elements into a reference array.
  646. $children = array();
  647. foreach ($elements as $key => &$value) {
  648. if ($key !== '' && $key[0] === '#') {
  649. continue;
  650. }
  651. $children[$key] = &$value;
  652. }
  653. // Allow other modules to modify $children & $elements before they are
  654. // rendered.
  655. // Call hook_advagg_modify_css_pre_render_alter()
  656. drupal_alter('advagg_modify_css_pre_render', $children, $elements);
  657. // Cache attached JS if Aggressive caching is being used.
  658. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3 && isset($elements['#attached']['js'])) {
  659. $hooks_hash = advagg_get_current_hooks_hash();
  660. $css = drupal_add_css();
  661. $css_hash = drupal_hash_base64(serialize($css));
  662. $css_cache_id = 'advagg:css:attached-js:' . $hooks_hash . ':' . $css_hash;
  663. cache_set($css_cache_id, $elements['#attached']['js'], 'cache_advagg_aggregates', CACHE_TEMPORARY);
  664. }
  665. return $elements;
  666. }
  667. /**
  668. * Default callback to aggregate CSS files and inline content.
  669. *
  670. * Having the browser load fewer CSS files results in much faster page loads
  671. * than when it loads many small files. This function aggregates files within
  672. * the same group into a single file unless the site-wide setting to do so is
  673. * disabled (commonly the case during site development). To optimize download,
  674. * it also compresses the aggregate files by removing comments, whitespace, and
  675. * other unnecessary content. Additionally, this functions aggregates inline
  676. * content together, regardless of the site-wide aggregation setting.
  677. *
  678. * @param array $css_groups
  679. * An array of CSS groups as returned by drupal_group_css(). This function
  680. * modifies the group's 'data' property for each group that is aggregated.
  681. *
  682. * @see drupal_aggregate_css()
  683. * @see drupal_group_css()
  684. * @see drupal_pre_render_styles()
  685. * @see system_element_info()
  686. */
  687. function _advagg_aggregate_css(&$css_groups) {
  688. if (!advagg_enabled()) {
  689. return drupal_aggregate_css($css_groups);
  690. }
  691. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  692. $GLOBALS['_advagg']['debug']['css_groups_before'][] = $css_groups;
  693. }
  694. $preprocess_css = advagg_file_aggregation_enabled('css');
  695. // Allow other modules to modify $css_groups right before it is processed.
  696. // Call hook_advagg_css_groups_alter().
  697. drupal_alter('advagg_css_groups', $css_groups, $preprocess_css);
  698. // For each group that needs aggregation, aggregate its items.
  699. $files_to_aggregate = array();
  700. // Allow for inline CSS to be between aggregated files.
  701. $gap_counter = 0;
  702. foreach ($css_groups as $key => $group) {
  703. switch ($group['type']) {
  704. // If a file group can be aggregated into a single file, do so, and set
  705. // the group's data property to the file path of the aggregate file.
  706. case 'file':
  707. if ($group['preprocess'] && $preprocess_css) {
  708. $files_to_aggregate[$gap_counter][$key] = $group;
  709. }
  710. else {
  711. ++$gap_counter;
  712. }
  713. break;
  714. // Aggregate all inline CSS content into the group's data property.
  715. case 'inline':
  716. ++$gap_counter;
  717. $css_groups[$key]['data'] = '';
  718. foreach ($group['items'] as $item) {
  719. $css_groups[$key]['data'] .= drupal_load_stylesheet_content($item['data'], $item['preprocess']);
  720. }
  721. break;
  722. // Create a gap for external CSS.
  723. case 'external':
  724. ++$gap_counter;
  725. break;
  726. }
  727. }
  728. if (!empty($files_to_aggregate)) {
  729. $hooks_hash = advagg_get_current_hooks_hash();
  730. $css_hash = drupal_hash_base64(serialize($files_to_aggregate));
  731. $cache_id = 'advagg:css:' . $hooks_hash . ':' . $css_hash;
  732. // @ignore druplart_andor_assignment
  733. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) {
  734. $plans = $cache->data;
  735. }
  736. else {
  737. module_load_include('inc', 'advagg', 'advagg');
  738. $plans = advagg_build_aggregate_plans($files_to_aggregate, 'css');
  739. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) {
  740. cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY);
  741. }
  742. }
  743. $css_groups = advagg_merge_plans($css_groups, $plans);
  744. }
  745. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  746. $GLOBALS['_advagg']['debug']['css_groups_after'][] = $css_groups;
  747. }
  748. }
  749. /**
  750. * Default callback to aggregate JavaScript files.
  751. *
  752. * Having the browser load fewer JavaScript files results in much faster page
  753. * loads than when it loads many small files. This function aggregates files
  754. * within the same group into a single file unless the site-wide setting to do
  755. * so is disabled (commonly the case during site development). To optimize
  756. * download, it also compresses the aggregate files by removing comments,
  757. * whitespace, and other unnecessary content.
  758. *
  759. * @param array $js_groups
  760. * An array of JavaScript groups as returned by drupal_group_js(). For each
  761. * group that is aggregated, this function sets the value of the group's
  762. * 'data' key to the URI of the aggregate file.
  763. *
  764. * @see drupal_group_js()
  765. * @see drupal_pre_render_scripts()
  766. */
  767. function _advagg_aggregate_js(&$js_groups) {
  768. if (!advagg_enabled()) {
  769. if (function_exists('drupal_aggregate_js')) {
  770. return drupal_aggregate_js($js_groups);
  771. }
  772. else {
  773. return;
  774. }
  775. }
  776. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  777. $GLOBALS['_advagg']['debug']['js_groups_before'][] = $js_groups;
  778. }
  779. $preprocess_js = advagg_file_aggregation_enabled('js');
  780. // Allow other modules to modify $js_groups right before it is processed.
  781. // Call hook_advagg_js_groups_alter().
  782. drupal_alter('advagg_js_groups', $js_groups, $preprocess_js);
  783. // For each group that needs aggregation, aggregate its items.
  784. $files_to_aggregate = array();
  785. // Only aggregate when the site is configured to do so, and not during an
  786. // update.
  787. $gap_counter = 0;
  788. if ($preprocess_js) {
  789. foreach ($js_groups as $key => &$group) {
  790. switch ($group['type']) {
  791. // If a file group can be aggregated into a single file, do so, and set
  792. // the group's data property to the file path of the aggregate file.
  793. case 'file':
  794. if ($group['preprocess']) {
  795. $files_to_aggregate[$gap_counter][$key] = $group;
  796. }
  797. else {
  798. ++$gap_counter;
  799. }
  800. break;
  801. // Create a gap for inline JS.
  802. case 'inline':
  803. ++$gap_counter;
  804. break;
  805. // Create a gap for external JS.
  806. case 'external':
  807. ++$gap_counter;
  808. break;
  809. }
  810. }
  811. }
  812. if (!empty($files_to_aggregate)) {
  813. $hooks_hash = advagg_get_current_hooks_hash();
  814. $js_hash = drupal_hash_base64(serialize($files_to_aggregate));
  815. $cache_id = 'advagg:js:' . $hooks_hash . ':' . $js_hash;
  816. // @ignore druplart_andor_assignment
  817. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) {
  818. $plans = $cache->data;
  819. }
  820. else {
  821. module_load_include('inc', 'advagg', 'advagg');
  822. $plans = advagg_build_aggregate_plans($files_to_aggregate, 'js');
  823. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) {
  824. cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY);
  825. }
  826. }
  827. $js_groups = advagg_merge_plans($js_groups, $plans);
  828. }
  829. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  830. $GLOBALS['_advagg']['debug']['js_groups_after'][] = $js_groups;
  831. }
  832. }
  833. /**
  834. * Replacement for template_process_html().
  835. */
  836. function _advagg_process_html(&$variables) {
  837. if (!advagg_enabled()) {
  838. return template_process_html($variables);
  839. }
  840. // Scan for changes to any CSS/JS files.
  841. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
  842. module_load_include('inc', 'advagg', 'advagg.cache');
  843. $flushed = advagg_push_new_changes();
  844. // Report back the results.
  845. if (!empty($flushed)) {
  846. foreach ($flushed as $filename => $data) {
  847. $ext = pathinfo($filename, PATHINFO_EXTENSION);
  848. 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(
  849. '%filename' => $filename,
  850. '%db_usage' => count($data[0]),
  851. '%db_count' => count($data[1]),
  852. '%type' => $ext,
  853. )));
  854. }
  855. }
  856. }
  857. // Get default javascript.
  858. // @see http://drupal.org/node/1279226
  859. if (function_exists('drupal_add_js_page_defaults')) {
  860. drupal_add_js_page_defaults();
  861. }
  862. // Render page_top and page_bottom into top level variables.
  863. if (isset($variables['page']['page_top'])) {
  864. $variables['page_top'] = drupal_render($variables['page']['page_top']);
  865. }
  866. elseif (!isset($variables['page_top'])) {
  867. $variables['page_top'] = '';
  868. }
  869. if (isset($variables['page']['page_bottom'])) {
  870. $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']);
  871. }
  872. elseif (!isset($variables['page_bottom'])) {
  873. $variables['page_bottom'] = '';
  874. }
  875. // Place the rendered HTML for the page body into a top level variable.
  876. if (isset($variables['page']['#children'])) {
  877. $variables['page'] = $variables['page']['#children'];
  878. }
  879. $variables['head'] = drupal_get_html_head();
  880. // Get advagg hash.
  881. $hooks_hash = advagg_get_current_hooks_hash();
  882. // Get the raw CSS variable.
  883. $variables['css'] = drupal_add_css();
  884. // Try the CSS cache first; only if not debugging.
  885. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {
  886. $css_hash = drupal_hash_base64(serialize($variables['css']));
  887. $css_cache_id = 'advagg:css:full:' . $hooks_hash . ':' . $css_hash;
  888. $css_cache = cache_get($css_cache_id, 'cache_advagg_aggregates');
  889. }
  890. // CSS has nice hooks so we don't need to work around it.
  891. if (!empty($css_cache->data) && !variable_get('advagg_debug', ADVAGG_DEBUG)) {
  892. $variables['styles'] = $css_cache->data;
  893. // Load in the attached js from the cache.
  894. $css_cache_id = 'advagg:css:attached-js:' . $hooks_hash . ':' . $css_hash;
  895. $css_cache = cache_get($css_cache_id, 'cache_advagg_aggregates');
  896. if (!empty($css_cache->data)) {
  897. // Code from drupal_process_attached().
  898. $group = JS_DEFAULT;
  899. $type = 'js';
  900. foreach ($css_cache->data as $data => $options) {
  901. // If the value is not an array, it's a filename and passed as first
  902. // (and only) argument.
  903. if (!is_array($options)) {
  904. $data = $options;
  905. $options = NULL;
  906. }
  907. // In some cases, the first parameter ($data) is an array. Arrays can't
  908. // be passed as keys in PHP, so we have to get $data from the value
  909. // array.
  910. if (is_numeric($data)) {
  911. $data = $options['data'];
  912. unset($options['data']);
  913. }
  914. // Apply the default group if it isn't explicitly given.
  915. if (!isset($options['group'])) {
  916. $options['group'] = $group;
  917. }
  918. // Set the every_page flag if one was passed.
  919. if (isset($every_page)) {
  920. $options['every_page'] = $every_page;
  921. }
  922. call_user_func('drupal_add_' . $type, $data, $options);
  923. }
  924. }
  925. }
  926. else {
  927. $variables['styles'] = drupal_get_css();
  928. if (!empty($css_cache_id)) {
  929. cache_set($css_cache_id, $variables['styles'], 'cache_advagg_aggregates', CACHE_TEMPORARY);
  930. }
  931. }
  932. // Get the raw JS variable.
  933. $javascript = drupal_add_js();
  934. // Try the JS cache first; only if not debugging.
  935. if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {
  936. $js_hash = drupal_hash_base64(serialize($javascript));
  937. $js_cache_id = 'advagg:js:full:' . $hooks_hash . ':' . $js_hash;
  938. $js_cache = cache_get($js_cache_id, 'cache_advagg_aggregates');
  939. }
  940. // JS needs hacks.
  941. // Clear out all old scripts.
  942. if (variable_get('advagg_clear_scripts', ADVAGG_CLEAR_SCRIPTS)) {
  943. $variables['scripts'] = '';
  944. }
  945. if (!isset($variables['scripts'])) {
  946. $variables['scripts'] = '';
  947. }
  948. if (!isset($variables['page_bottom']) || !is_string($variables['page_bottom'])) {
  949. $variables['page_bottom'] = '';
  950. }
  951. if (!empty($js_cache->data) && !variable_get('advagg_debug', ADVAGG_DEBUG)) {
  952. foreach ($js_cache->data as $key => $value) {
  953. if (!isset($variables[$key]) || !is_string($variables[$key])) {
  954. $variables[$key] = '';
  955. }
  956. $variables[$key] .= $value;
  957. }
  958. }
  959. else {
  960. $js_cache = array();
  961. $js_cache['scripts'] = '';
  962. $javascript = advagg_get_full_js($javascript);
  963. if (!empty($javascript)) {
  964. $scopes = advagg_get_js_scopes($javascript);
  965. // Add JS to the header and footer of the page.
  966. foreach ($scopes as $scope => $use) {
  967. if (!$use) {
  968. continue;
  969. }
  970. // advagg_get_js() will sort the JavaScript so that it appears in the
  971. // correct order.
  972. $scripts = advagg_get_js($scope, $javascript);
  973. // Header scripts.
  974. if ($scope == 'header') {
  975. $variables['scripts'] = $scripts . $variables['scripts'];
  976. $js_cache['scripts'] = $scripts . $js_cache['scripts'];
  977. }
  978. // Footer scripts.
  979. elseif ($scope == 'footer') {
  980. $variables['page_bottom'] .= $scripts;
  981. $js_cache['page_bottom'] = $scripts;
  982. }
  983. elseif (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
  984. // Scripts in other places.
  985. if (isset($variables[$scope]) && is_string($variables[$scope]) && array_key_exists($scope, $GLOBALS['theme_info']->info['regions'])) {
  986. $variables[$scope] .= $scripts;
  987. $js_cache[$scope] = $scripts;
  988. }
  989. // Add javascript to scripts if we can't find the region in the theme.
  990. else {
  991. $variables['scripts'] .= $scripts;
  992. $js_cache['scripts'] .= $scripts;
  993. }
  994. }
  995. }
  996. if (!empty($js_cache_id) && !empty($js_cache)) {
  997. cache_set($js_cache_id, $js_cache, 'cache_advagg_aggregates', CACHE_TEMPORARY);
  998. }
  999. }
  1000. }
  1001. // Output debug info.
  1002. if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
  1003. $debug = $GLOBALS['_advagg']['debug'];
  1004. if (module_exists('httprl')) {
  1005. $output = ' ' . httprl_pr($debug);
  1006. }
  1007. else {
  1008. $output = '<pre>' . str_replace(array('<', '>'), array('&lt;', '&gt;'), print_r($debug, TRUE)) . '</pre>';
  1009. }
  1010. watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG);
  1011. }
  1012. }
  1013. /**
  1014. * Get full JS array.
  1015. *
  1016. * Note that hook_js_alter(&$javascript) is called during this function call
  1017. * to allow alterations of the JavaScript during its presentation. Calls to
  1018. * drupal_add_js() from hook_js_alter() will not be added to the output
  1019. * presentation. The correct way to add JavaScript during hook_js_alter()
  1020. * is to add another element to the $javascript array, deriving from
  1021. * drupal_js_defaults(). See locale_js_alter() for an example of this.
  1022. *
  1023. * @param array $javascript
  1024. * (optional) An array with all JavaScript code. Defaults to the default
  1025. * JavaScript array for the given scope.
  1026. * @param bool $skip_alter
  1027. * (optional) If set to TRUE, this function skips calling drupal_alter() on
  1028. * $javascript, useful when the calling function passes a $javascript array
  1029. * that has already been altered.
  1030. *
  1031. * @return array
  1032. * The raw JavaScript array.
  1033. *
  1034. * @see drupal_add_js()
  1035. * @see locale_js_alter()
  1036. * @see drupal_js_defaults()
  1037. */
  1038. function advagg_get_full_js($javascript = NULL, $skip_alter = FALSE) {
  1039. if (!isset($javascript)) {
  1040. $javascript = drupal_add_js();
  1041. }
  1042. if (empty($javascript)) {
  1043. return FALSE;
  1044. }
  1045. // Allow modules to alter the JavaScript.
  1046. if (!$skip_alter) {
  1047. // Call hook_js_alter().
  1048. drupal_alter('js', $javascript);
  1049. }
  1050. return $javascript;
  1051. }
  1052. /**
  1053. * Returns a themed presentation of all JavaScript code for the current page.
  1054. *
  1055. * References to JavaScript files are placed in a certain order: first, all
  1056. * 'core' files, then all 'module' and finally all 'theme' JavaScript files
  1057. * are added to the page. Then, all settings are output, followed by 'inline'
  1058. * JavaScript code. If running update.php, all preprocessing is disabled.
  1059. *
  1060. * Note that hook_js_alter(&$javascript) is called during this function call
  1061. * to allow alterations of the JavaScript during its presentation. Calls to
  1062. * drupal_add_js() from hook_js_alter() will not be added to the output
  1063. * presentation. The correct way to add JavaScript during hook_js_alter()
  1064. * is to add another element to the $javascript array, deriving from
  1065. * drupal_js_defaults(). See locale_js_alter() for an example of this.
  1066. *
  1067. * @param string $scope
  1068. * (optional) The scope for which the JavaScript rules should be returned.
  1069. * Defaults to 'header'.
  1070. * @param array $javascript
  1071. * (optional) An array with all JavaScript code. Defaults to the default
  1072. * JavaScript array for the given scope.
  1073. * @param bool $skip_alter
  1074. * (optional) If set to TRUE, this function skips calling drupal_alter() on
  1075. * $javascript, useful when the calling function passes a $javascript array
  1076. * that has already been altered.
  1077. *
  1078. * @return string
  1079. * All JavaScript code segments and includes for the scope as HTML tags.
  1080. *
  1081. * @see drupal_add_js()
  1082. * @see locale_js_alter()
  1083. * @see drupal_js_defaults()
  1084. */
  1085. function advagg_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE, $ajax = FALSE) {
  1086. $javascript_settings_data = &drupal_static(__FUNCTION__, array());
  1087. if (empty($javascript)) {
  1088. return '';
  1089. }
  1090. // Filter out elements of the given scope.
  1091. $items = array();
  1092. foreach ($javascript as $key => $item) {
  1093. if ($item['scope'] == $scope) {
  1094. $items[$key] = $item;
  1095. }
  1096. }
  1097. // Sort the JavaScript so that it appears in the correct order.
  1098. if (is_callable('drupal_sort_css_js')) {
  1099. uasort($items, 'drupal_sort_css_js');
  1100. }
  1101. else {
  1102. advagg_drupal_sort_css_js_stable($items);
  1103. }
  1104. // In Drupal 8, there's a JS_SETTING group for making setting variables
  1105. // appear last after libraries have loaded. In Drupal 7, this is forced
  1106. // without that group. We do not use the $key => $item type of iteration,
  1107. // because PHP uses an internal array pointer for that, and we're modifying
  1108. // the array order inside the loop.
  1109. if ($scope == 'footer' && module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2) {
  1110. // Get all settings from the items array.
  1111. $settings_js = array();
  1112. foreach (array_keys($items) as $key) {
  1113. if ($items[$key]['type'] == 'setting') {
  1114. $settings_js[] = $items[$key];
  1115. unset($items[$key]);
  1116. }
  1117. }
  1118. // Add settings array back in right before the footer shift happened.
  1119. if (!empty($settings_js)) {
  1120. $counter = 0;
  1121. foreach ($items as $key => $item) {
  1122. // Move $settings_js to the bottom of the js that was added to the
  1123. // header, but has now been moved to the footer via advagg_mod.
  1124. if ($item['group'] > 9000) {
  1125. array_splice($items, $counter, 0, $settings_js);
  1126. unset($settings_js);
  1127. break;
  1128. }
  1129. ++$counter;
  1130. }
  1131. }
  1132. if (!empty($settings_js)) {
  1133. $items = array_merge($items, $settings_js);
  1134. }
  1135. }
  1136. else {
  1137. foreach (array_keys($items) as $key) {
  1138. if ($items[$key]['type'] == 'setting') {
  1139. $item = $items[$key];
  1140. unset($items[$key]);
  1141. $items[$key] = $item;
  1142. }
  1143. }
  1144. }
  1145. // Provide the page with information about the individual JavaScript files
  1146. // used, information not otherwise available when aggregation is enabled.
  1147. // Also filter out empty items due to numeric array keys.
  1148. $setting['ajaxPageState']['js'] = array_fill_keys(array_filter(array_keys($items), 'advagg_remove_short_keys'), 1);
  1149. unset($setting['ajaxPageState']['js']['settings']);
  1150. drupal_add_js($setting, 'setting');
  1151. // Move 'settings' javascript to the header region.
  1152. if ($scope == 'header' || ($scope == 'footer' && module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2) && !empty($javascript_settings_data)) {
  1153. foreach ($javascript_settings_data as $js_data) {
  1154. $items['settings']['data'][] = $js_data;
  1155. }
  1156. $javascript_settings_data = array();
  1157. }
  1158. else {
  1159. $javascript_settings_data[] = $setting;
  1160. }
  1161. // If we're outputting the header scope, then this should be the final time
  1162. // that drupal_get_js() is running, so add the setting to this output as well
  1163. // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
  1164. // because drupal_get_js() was intentionally passed a $javascript argument
  1165. // stripped of settings, potentially in order to override how settings get
  1166. // output, so in this case, do not add the setting to this output.
  1167. // Also output the settings if we have pushed all javascript to the footer.
  1168. if (($scope == 'header' || ($scope == 'footer' && module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2)) && isset($items['settings'])) {
  1169. $items['settings']['data'][] = $setting;
  1170. }
  1171. // Do not include jQuery.extend(Drupal.settings) if the output is ajax.
  1172. if ($ajax) {
  1173. unset($items['settings']['data']);
  1174. }
  1175. // Render the HTML needed to load the JavaScript.
  1176. $elements = array(
  1177. '#type' => 'scripts',
  1178. '#items' => $items,
  1179. );
  1180. // Aurora & Omega themes uses alter without checking previous value.
  1181. if (variable_get('advagg_enforce_scripts_callback', TRUE)) {
  1182. $element_info = &drupal_static('element_info');
  1183. if (empty($element_info['scripts']) || $element_info['scripts']['#aggregate_callback'] != '_advagg_aggregate_js') {
  1184. advagg_element_info_alter($element_info);
  1185. }
  1186. }
  1187. return drupal_render($elements);
  1188. }
  1189. /**
  1190. * Callback for array_filter. Will return FALSE if strlen < 3.
  1191. *
  1192. * @param string $array_value
  1193. * A value from an array.
  1194. *
  1195. * @return bool
  1196. * TRUE or FALSE.
  1197. */
  1198. function advagg_remove_short_keys($array_value) {
  1199. if (strlen($array_value) < 3) {
  1200. return FALSE;
  1201. }
  1202. else {
  1203. return TRUE;
  1204. }
  1205. }
  1206. /**
  1207. * Get all javascript scopes set in the $javascript array.
  1208. *
  1209. * @param array $javascript
  1210. * An array with all JavaScript code.
  1211. *
  1212. * @return array
  1213. * Array of scopes that are currently being used.
  1214. */
  1215. function advagg_get_js_scopes($javascript) {
  1216. // Return if nothing given to us.
  1217. if (empty($javascript) || !is_array($javascript)) {
  1218. return FALSE;
  1219. }
  1220. // Filter out elements of the given scope.
  1221. $scopes = array();
  1222. foreach ($javascript as $key => $item) {
  1223. // Skip if the scope is not set.
  1224. if (!is_array($item) || empty($item['scope'])) {
  1225. continue;
  1226. }
  1227. if (!isset($scopes[$item['scope']])) {
  1228. $scopes[$item['scope']] = TRUE;
  1229. }
  1230. }
  1231. // Default to header if nothing found.
  1232. if (empty($scopes)) {
  1233. $scopes['header'] = TRUE;
  1234. }
  1235. // Process header last.
  1236. if (isset($scopes['header']) && count($scopes) > 1) {
  1237. $temp = $scopes['header'];
  1238. unset($scopes['header']);
  1239. $scopes['header'] = $temp;
  1240. }
  1241. // Process footer last if everything has been moved to the footer.
  1242. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  1243. if ( isset($scopes['footer'])
  1244. && count($scopes) > 1
  1245. && module_exists('advagg_mod')
  1246. && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 2
  1247. ) {
  1248. $temp = $scopes['footer'];
  1249. unset($scopes['footer']);
  1250. $scopes['footer'] = $temp;
  1251. }
  1252. // Return the scopes.
  1253. return $scopes;
  1254. }
  1255. /**
  1256. * Apply the advagg changes to the $css_js_groups array.
  1257. *
  1258. * @param array $css_js_groups
  1259. * An array of CSS or JS groups as returned by drupal_group_css/js().
  1260. * @param array $plans
  1261. * An array of changes to do to the $css_js_groups array.
  1262. *
  1263. * @return array
  1264. * New version of $css_js_groups.
  1265. */
  1266. function advagg_merge_plans($css_js_groups, $plans) {
  1267. $used_keys = array();
  1268. foreach ($plans as $plan) {
  1269. $plan_added = FALSE;
  1270. foreach ($css_js_groups as $key => $group) {
  1271. // Remove files from the old css/js array.
  1272. $file_removed = FALSE;
  1273. foreach ($css_js_groups[$key]['items'] as $k => $values) {
  1274. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  1275. if ( is_array($values)
  1276. && array_key_exists('data', $values)
  1277. && is_array($plan['items']['files'])
  1278. && is_string($values['data'])
  1279. ) {
  1280. // If the CSS is a split file, the first file is very meaningful, and
  1281. // is probably the only file.
  1282. $first_file = reset($plan['items']['files']);
  1283. if (array_key_exists($values['data'], $plan['items']['files'])) {
  1284. unset($css_js_groups[$key]['items'][$k]);
  1285. $file_removed = TRUE;
  1286. }
  1287. // This part will try to add each split part matching the original CSS
  1288. // path and only remove the original group if the current part is the
  1289. // last part.
  1290. elseif (!empty($first_file['split'])) {
  1291. if ($values['data'] == $first_file['split_original']) {
  1292. if (!empty($first_file['split_last_part'])) {
  1293. unset($css_js_groups[$key]['items'][$k]);
  1294. }
  1295. $file_removed = TRUE;
  1296. }
  1297. }
  1298. }
  1299. }
  1300. // Replace first file of the old css/js array with one from advagg.
  1301. if ($file_removed && !$plan_added) {
  1302. $step = 0;
  1303. do {
  1304. ++$step;
  1305. $insert_key = '' . floatval($key) . '.' . $step;
  1306. } while (array_key_exists($insert_key, $css_js_groups));
  1307. $css_js_groups[(string) $insert_key] = $plan;
  1308. $plan_added = TRUE;
  1309. }
  1310. }
  1311. // Remove old css/js grouping if no files are left in it.
  1312. foreach ($css_js_groups as $key => $group) {
  1313. if (empty($css_js_groups[$key]['items'])) {
  1314. unset($css_js_groups[$key]);
  1315. }
  1316. }
  1317. if (!$plan_added) {
  1318. foreach ($css_js_groups as $key => $group) {
  1319. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  1320. if ( empty($group['items']['aggregate_filenames_hash'])
  1321. || $group['items']['aggregate_filenames_hash'] != $plan['items']['aggregate_filenames_hash']
  1322. || empty($group['items']['aggregate_contents_hash'])
  1323. || $group['items']['aggregate_contents_hash'] != $plan['items']['aggregate_contents_hash']
  1324. ) {
  1325. continue;
  1326. }
  1327. // Insert a unique key.
  1328. do {
  1329. $key = '' . floatval($key) + 0.01;
  1330. } while (array_key_exists((string) $key, $css_js_groups) || array_key_exists((string) $key, $used_keys));
  1331. $used_keys[(string) $key] = TRUE;
  1332. $css_js_groups[(string) $key] = $plan;
  1333. $plan_added = TRUE;
  1334. break;
  1335. }
  1336. }
  1337. }
  1338. // Key sort and normalize the array before returning it.
  1339. ksort($css_js_groups);
  1340. $css_js_groups = array_values($css_js_groups);
  1341. return $css_js_groups;
  1342. }
  1343. // Helper functions.
  1344. /**
  1345. * Function used to see if aggregation is enabled.
  1346. *
  1347. * @return bool
  1348. * The value of the advagg_enabled variable.
  1349. */
  1350. function advagg_enabled() {
  1351. $init = &drupal_static(__FUNCTION__);
  1352. if (!empty($init)) {
  1353. return variable_get('advagg_enabled', ADVAGG_ENABLED);
  1354. }
  1355. $init = TRUE;
  1356. // Disable AdvAgg if module needs to be upgraded from 1.x to 2.x.
  1357. if (variable_get('advagg_needs_update', ADVAGG_NEEDS_UPDATE)) {
  1358. if (!db_table_exists('advagg_aggregates_versions')) {
  1359. $GLOBALS['conf']['advagg_enabled'] = FALSE;
  1360. if (user_access('administer site configuration')) {
  1361. drupal_set_message(t('Please run <a href="@link">database updates</a>. AdvAgg will remain disabled until done.', array('@link' => url('update.php'))), 'error');
  1362. }
  1363. }
  1364. else {
  1365. variable_del('advagg_needs_update');
  1366. }
  1367. }
  1368. else {
  1369. // Allow for AdvAgg to be enabled per request.
  1370. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  1371. if ( isset($_GET['advagg'])
  1372. && $_GET['advagg'] == 1
  1373. && !defined('MAINTENANCE_MODE')
  1374. && user_access('bypass advanced aggregation')
  1375. ) {
  1376. $GLOBALS['conf']['advagg_enabled'] = TRUE;
  1377. $GLOBALS['conf']['preprocess_css'] = TRUE;
  1378. $GLOBALS['conf']['preprocess_js'] = TRUE;
  1379. }
  1380. // Disable AdvAgg if maintenance mode is defined.
  1381. if (defined('MAINTENANCE_MODE')) {
  1382. $GLOBALS['conf']['advagg_enabled'] = FALSE;
  1383. }
  1384. // Only run code below if advagg is enabled.
  1385. if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
  1386. // Do not use AdvAgg or preprocessing functions if the disable cookie is
  1387. // set.
  1388. $cookie_name = 'AdvAggDisabled';
  1389. $key = drupal_hash_base64(drupal_get_private_key());
  1390. if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
  1391. $GLOBALS['conf']['advagg_enabled'] = FALSE;
  1392. $GLOBALS['conf']['preprocess_css'] = FALSE;
  1393. $GLOBALS['conf']['preprocess_js'] = FALSE;
  1394. // Let the user know that the AdvAgg bypass cookie is currently set.
  1395. static $msg_set;
  1396. if (!isset($msg_set) && variable_get('advagg_show_bypass_cookie_message', ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE)) {
  1397. $msg_set = TRUE;
  1398. if (user_access('administer site configuration')) {
  1399. drupal_set_message(t('The AdvAgg bypass cookie is currently enabled. Turn it off by going to the <a href="@advagg_operations">AdvAgg Operations</a> page and clicking the <em>Toggle the "aggregation bypass cookie" for this browser</em> button.', array(
  1400. '@advagg_operations' => url(advagg_admin_config_root_path() . '/advagg/operations', array('fragment' => 'edit-bypass')),
  1401. )));
  1402. }
  1403. else {
  1404. drupal_set_message(t('The AdvAgg bypass cookie is currently enabled. Turn it off by <a href="@login">logging in</a> with a user with the "administer site configuration" permissions and going to the AdvAgg Operations page (located at @advagg_operations) and clicking the <em>Toggle the "aggregation bypass cookie" for this browser</em> button.', array(
  1405. '@login' => 'user/login',
  1406. '@advagg_operations' => advagg_admin_config_root_path() . '/advagg/operations',
  1407. )));
  1408. }
  1409. }
  1410. }
  1411. // Disable advagg if requested.
  1412. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  1413. if ( isset($_GET['advagg'])
  1414. && $_GET['advagg'] == -1
  1415. && user_access('bypass advanced aggregation')
  1416. ) {
  1417. $GLOBALS['conf']['advagg_enabled'] = FALSE;
  1418. $GLOBALS['conf']['preprocess_css'] = FALSE;
  1419. $GLOBALS['conf']['preprocess_js'] = FALSE;
  1420. }
  1421. // Disable core preprocessing if requested.
  1422. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  1423. if ( isset($_GET['advagg-core'])
  1424. && $_GET['advagg-core'] == 0
  1425. && user_access('bypass advanced aggregation')
  1426. ) {
  1427. $GLOBALS['conf']['preprocess_css'] = FALSE;
  1428. $GLOBALS['conf']['preprocess_js'] = FALSE;
  1429. }
  1430. // Enable core preprocessing if requested.
  1431. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  1432. if ( isset($_GET['advagg-core'])
  1433. && $_GET['advagg-core'] == 1
  1434. && user_access('bypass advanced aggregation')
  1435. ) {
  1436. $GLOBALS['conf']['preprocess_css'] = TRUE;
  1437. $GLOBALS['conf']['preprocess_js'] = TRUE;
  1438. }
  1439. // Enable debugging if requested.
  1440. // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  1441. if ( isset($_GET['advagg-debug'])
  1442. && $_GET['advagg-debug'] == 1
  1443. && user_access('bypass advanced aggregation')
  1444. ) {
  1445. $GLOBALS['conf']['advagg_debug'] = TRUE;
  1446. }
  1447. }
  1448. }
  1449. return variable_get('advagg_enabled', ADVAGG_ENABLED);
  1450. }
  1451. /**
  1452. * Get the current path used for advagg admin configuration.
  1453. *
  1454. * @return string
  1455. * Path to root advagg config.
  1456. */
  1457. function advagg_admin_config_root_path() {
  1458. return variable_get('advagg_admin_config_root_path', ADVAGG_ADMIN_CONFIG_ROOT_PATH);
  1459. }
  1460. /**
  1461. * Get an array of all hooks and settings that affect aggregated files contents.
  1462. *
  1463. * @return array
  1464. * array('variables' => array(...), 'hooks' => array(...))
  1465. */
  1466. function advagg_current_hooks_hash_array() {
  1467. $aggregate_settings = &drupal_static(__FUNCTION__);
  1468. if (isset($aggregate_settings)) {
  1469. return $aggregate_settings;
  1470. }
  1471. // Put all enabled hooks and settings into a big array.
  1472. $aggregate_settings = array(
  1473. 'variables' => array(
  1474. 'advagg_gzip' => variable_get('advagg_gzip', ADVAGG_GZIP),
  1475. 'is_https' => $GLOBALS['is_https'],
  1476. 'advagg_global_counter' => advagg_get_global_counter(),
  1477. 'base_path' => $GLOBALS['base_path'],
  1478. 'advagg_ie_css_selector_limiter' => variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER),
  1479. 'advagg_ie_css_selector_limiter_value' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE),
  1480. 'advagg_scripts_scope_anywhere' => variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE),
  1481. ),
  1482. 'hooks' => advagg_hooks_implemented(FALSE),
  1483. );
  1484. // Add in language if locale is enabled.
  1485. if (module_exists('locale')) {
  1486. $aggregate_settings['variables']['language'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : '';
  1487. }
  1488. // Allow other modules to add in their own settings and hooks.
  1489. // Call hook_advagg_current_hooks_hash_array_alter().
  1490. drupal_alter('advagg_current_hooks_hash_array', $aggregate_settings);
  1491. return $aggregate_settings;
  1492. }
  1493. /**
  1494. * Get the hash of all hooks & settings that affect aggregated files contents.
  1495. *
  1496. * @return string
  1497. * hash value.
  1498. */
  1499. function advagg_get_current_hooks_hash() {
  1500. $current_hash = &drupal_static(__FUNCTION__);
  1501. if (!isset($current_hash)) {
  1502. // Get all advagg hooks and variables in use.
  1503. $aggregate_settings = advagg_current_hooks_hash_array();
  1504. // Generate the hash.
  1505. $current_hash = drupal_hash_base64(serialize($aggregate_settings));
  1506. // Save into variables for verification purposes later on if not found.
  1507. $settings = advagg_get_hash_settings($current_hash);
  1508. if (empty($settings) && lock_acquire(__FUNCTION__, 5)) {
  1509. // Save new hash into.
  1510. advagg_set_hash_settings($current_hash, $aggregate_settings);
  1511. // Release lock.
  1512. lock_release(__FUNCTION__);
  1513. }
  1514. }
  1515. return $current_hash;
  1516. }
  1517. /**
  1518. * Store settings associated with hash.
  1519. *
  1520. * @return MergeQuery
  1521. * value from db_merge
  1522. */
  1523. function advagg_set_hash_settings($hash, $settings) {
  1524. return db_merge('advagg_aggregates_hashes')
  1525. ->key(array('hash' => $hash))
  1526. ->fields(array('settings' => serialize($settings)))
  1527. ->execute();
  1528. }
  1529. /**
  1530. * Get back what hooks are implemented.
  1531. *
  1532. * @param bool $all
  1533. * If TRUE get all hooks related to css/js files.
  1534. * if FALSE get only the subset of hooks that alter the filename/contents.
  1535. *
  1536. * @return array
  1537. * List of hooks and what modules have implemented them.
  1538. */
  1539. function advagg_hooks_implemented($all = TRUE) {
  1540. // Get hooks in use.
  1541. $hooks = array(
  1542. 'advagg_get_css_file_contents_alter' => array(),
  1543. 'advagg_get_css_aggregate_contents_alter' => array(),
  1544. 'advagg_get_js_file_contents_alter' => array(),
  1545. 'advagg_get_js_aggregate_contents_alter' => array(),
  1546. 'advagg_save_aggregate_alter' => array(),
  1547. 'advagg_current_hooks_hash_array_alter' => array(),
  1548. 'advagg_get_root_files_dir_alter' => array(),
  1549. 'advagg_context_alter' => array(),
  1550. );
  1551. if ($all) {
  1552. $hooks += array(
  1553. 'advagg_build_aggregate_plans_alter' => array(),
  1554. 'advagg_changed_files' => array(),
  1555. 'advagg_css_groups_alter' => array(),
  1556. 'advagg_js_groups_alter' => array(),
  1557. 'advagg_modify_css_pre_render_alter' => array(),
  1558. 'advagg_modify_js_pre_render_alter' => array(),
  1559. 'js_alter' => array(),
  1560. 'css_alter' => array(),
  1561. );
  1562. }
  1563. // Call hook_advagg_hooks_implemented_alter().
  1564. drupal_alter('advagg_hooks_implemented', $hooks, $all);
  1565. // Cache module_implements as this will load up .inc files.
  1566. $cid = 'advagg_hooks_implemented:' . (int) $all . ':' . drupal_hash_base64(serialize($hooks));
  1567. $cache = cache_get($cid, 'cache_bootstrap');
  1568. if (!empty($cache->data)) {
  1569. $hooks = $cache->data;
  1570. }
  1571. else {
  1572. foreach ($hooks as $hook => $values) {
  1573. $hooks[$hook] = module_implements($hook);
  1574. }
  1575. cache_set($cid, $hooks, 'cache_bootstrap', CACHE_TEMPORARY);
  1576. }
  1577. return $hooks;
  1578. }
  1579. /**
  1580. * Returns the hashes settings.
  1581. *
  1582. * @param string $hash
  1583. * The name of the variable to return.
  1584. *
  1585. * @return array
  1586. * The settings array or an empty array if not found.
  1587. */
  1588. function advagg_get_hash_settings($hash) {
  1589. $settings = db_select('advagg_aggregates_hashes', 'aah')
  1590. ->fields('aah', array('settings'))
  1591. ->condition('hash', $hash)
  1592. ->execute()
  1593. ->fetchField();
  1594. return !empty($settings) ? unserialize($settings) : array();
  1595. }
  1596. // @ignore production_code:20
  1597. /**
  1598. * Get the CSS & JS path for advagg.
  1599. *
  1600. * @param bool $reset
  1601. * Set to TRUE to reset the static variables.
  1602. *
  1603. * @return array
  1604. * Example return below:
  1605. * @code
  1606. * array(
  1607. * array(
  1608. * public://advagg_css,
  1609. * sites/default/files/advagg_css,
  1610. * ),
  1611. * array(
  1612. * public://advagg_js,
  1613. * sites/default/files/advagg_js,
  1614. * ),
  1615. * )
  1616. * @endcode
  1617. */
  1618. function advagg_get_root_files_dir($reset = FALSE) {
  1619. $css_paths = &drupal_static(__FUNCTION__ . '_css');
  1620. $js_paths = &drupal_static(__FUNCTION__ . '_js');
  1621. // Make sure directories are available and writable.
  1622. if (empty($css_paths) || empty($js_paths) || $reset) {
  1623. $css_paths[0] = 'public://advagg_css';
  1624. $js_paths[0] = 'public://advagg_js';
  1625. file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY);
  1626. file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY);
  1627. // Set the URI of the directory.
  1628. $css_paths[1] = parse_url(file_create_url($css_paths[0]), PHP_URL_PATH);
  1629. if (substr($css_paths[1], 0, strlen($GLOBALS['base_path'])) == $GLOBALS['base_path']) {
  1630. $css_paths[1] = substr($css_paths[1], strlen($GLOBALS['base_path']));
  1631. }
  1632. $js_paths[1] = parse_url(file_create_url($js_paths[0]), PHP_URL_PATH);
  1633. if (substr($js_paths[1], 0, strlen($GLOBALS['base_path'])) == $GLOBALS['base_path']) {
  1634. $js_paths[1] = substr($js_paths[1], strlen($GLOBALS['base_path']));
  1635. }
  1636. // Allow other modules to alter css and js paths.
  1637. // Call hook_advagg_get_root_files_dir_alter()
  1638. drupal_alter('advagg_get_root_files_dir', $css_paths, $js_paths);
  1639. }
  1640. return array($css_paths, $js_paths);
  1641. }
  1642. /**
  1643. * Builds the requested CSS/JS aggregates.
  1644. *
  1645. * @param array $filenames
  1646. * Array of AdvAgg filenames to generate.
  1647. *
  1648. * @return array
  1649. * Array keyed by filename, value is result from advagg_missing_create_file().
  1650. */
  1651. function advagg_build_aggregates($filenames) {
  1652. // Otherwise call the file generation function directly.
  1653. module_load_include('inc', 'advagg', 'advagg.missing');
  1654. $return = array();
  1655. foreach ($filenames as $filename) {
  1656. // Skip if the file exists.
  1657. if (file_exists($filename)) {
  1658. continue;
  1659. }
  1660. // Only create the file if we have a lock.
  1661. $lock_name = 'advagg_' . $filename;
  1662. if (lock_acquire($lock_name, 10)) {
  1663. $return[$filename] = advagg_missing_create_file($filename);
  1664. lock_release($lock_name);
  1665. }
  1666. }
  1667. return $return;
  1668. }
  1669. /**
  1670. * Gets the core CSS/JS included in this ajax request.
  1671. *
  1672. * Used so core JS can be rendered through the AdvAgg pipeline.
  1673. * @see ajax_render()
  1674. *
  1675. * @return array
  1676. * Returns an array containing $styles, $scripts_header, $scripts_footer,
  1677. * $items, and $settings.
  1678. */
  1679. function advagg_build_ajax_js_css() {
  1680. $settings = array();
  1681. // Ajax responses aren't rendered with html.tpl.php, so we have to call
  1682. // drupal_get_css() and drupal_get_js() here, in order to have new files added
  1683. // during this request to be loaded by the page. We only want to send back
  1684. // files that the page hasn't already loaded, so we implement simple diffing
  1685. // logic using array_diff_key().
  1686. foreach (array('css', 'js') as $type) {
  1687. // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty,
  1688. // since the base page ought to have at least one JS file and one CSS file
  1689. // loaded. It probably indicates an error, and rather than making the page
  1690. // reload all of the files, instead we return no new files.
  1691. if (empty($_POST['ajax_page_state'][$type])) {
  1692. $items[$type] = array();
  1693. $scripts = drupal_add_js();
  1694. if (!empty($scripts['settings'])) {
  1695. $settings = $scripts['settings'];
  1696. }
  1697. }
  1698. else {
  1699. $function = 'drupal_add_' . $type;
  1700. $items[$type] = $function();
  1701. drupal_alter($type, $items[$type]);
  1702. // @todo Inline CSS and JS items are indexed numerically. These can't be
  1703. // reliably diffed with array_diff_key(), since the number can change
  1704. // due to factors unrelated to the inline content, so for now, we strip
  1705. // the inline items from Ajax responses, and can add support for them
  1706. // when drupal_add_css() and drupal_add_js() are changed to use a hash
  1707. // of the inline content as the array key.
  1708. foreach ($items[$type] as $key => $item) {
  1709. if (is_numeric($key)) {
  1710. unset($items[$type][$key]);
  1711. }
  1712. }
  1713. // Ensure that the page doesn't reload what it already has.
  1714. // @ignore security_17
  1715. $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]);
  1716. }
  1717. }
  1718. // Render the HTML to load these files, and add AJAX commands to insert this
  1719. // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
  1720. // data from being altered again, as we already altered it above. Settings are
  1721. // handled separately, afterwards.
  1722. if (isset($items['js']['settings'])) {
  1723. $settings = $items['js']['settings'];
  1724. unset($items['js']['settings']);
  1725. }
  1726. $styles = drupal_get_css($items['css'], TRUE);
  1727. $scripts_footer = drupal_get_js('footer', $items['js'], TRUE);
  1728. $scripts_header = drupal_get_js('header', $items['js'], TRUE);
  1729. return array($styles, $scripts_header, $scripts_footer, $items, $settings);
  1730. }
  1731. /**
  1732. * Given the type lets us know if advagg is enabled or disabled.
  1733. *
  1734. * @param string $type
  1735. * css or js.
  1736. *
  1737. * @return bool
  1738. * TRUE or FALSE
  1739. */
  1740. function advagg_file_aggregation_enabled($type) {
  1741. if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
  1742. return FALSE;
  1743. }
  1744. if (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation')) {
  1745. return FALSE;
  1746. }
  1747. if ($type == 'css') {
  1748. return variable_get('preprocess_css', FALSE);
  1749. }
  1750. if ($type == 'js') {
  1751. return variable_get('preprocess_js', FALSE);
  1752. }
  1753. }
  1754. /**
  1755. * Update the atime value in the advagg_aggregates_versions table.
  1756. *
  1757. * @param string $aggregate_filenames_hash
  1758. * Hash of the groupings of files.
  1759. * @param string $aggregate_contents_hash
  1760. * Hash of the files contents.
  1761. *
  1762. * @return bool
  1763. * TRUE if a write to the DB was done.
  1764. */
  1765. function advagg_update_atime($aggregate_filenames_hash, $aggregate_contents_hash) {
  1766. $write_done = FALSE;
  1767. // Set the cache id.
  1768. $cache_id = 'advagg:db:' . $aggregate_filenames_hash . ADVAGG_SPACE . $aggregate_contents_hash;
  1769. // Set db record.
  1770. $record = array(
  1771. 'aggregate_filenames_hash' => $aggregate_filenames_hash,
  1772. 'aggregate_contents_hash' => $aggregate_contents_hash,
  1773. 'atime' => REQUEST_TIME,
  1774. );
  1775. // Use the cache to avoid hitting the database.
  1776. $cache = cache_get($cache_id, 'cache_advagg_info');
  1777. if ($cache) {
  1778. // See if the atime value needs to be updated;
  1779. if (!empty($cache->data['atime']) && $cache->data['atime'] > REQUEST_TIME - (12 * 60 * 60)) {
  1780. // If atime is less than 12 hours old, do nothing.
  1781. return $write_done;
  1782. }
  1783. }
  1784. // If lock is already acquired, return here.
  1785. if (!lock_acquire($cache_id, 5)) {
  1786. return $write_done;
  1787. }
  1788. // Update atime in DB.
  1789. if (drupal_write_record('advagg_aggregates_versions', $record, array('aggregate_filenames_hash', 'aggregate_contents_hash'))) {
  1790. $write_done = TRUE;
  1791. }
  1792. // Update the atime in the cache.
  1793. // Get fresh copy of the cache now that we are in a lock.
  1794. $cache = cache_get($cache_id, 'cache_advagg_info');
  1795. // Set the atime.
  1796. if (empty($cache->data)) {
  1797. $cache = new stdClass();
  1798. }
  1799. $cache->data['atime'] = REQUEST_TIME;
  1800. // Write to the cache.
  1801. cache_set($cache_id, $cache->data, 'cache_advagg_info', CACHE_PERMANENT);
  1802. // Release Lock.
  1803. lock_release($cache_id);
  1804. // Return if a write was done.
  1805. return $write_done;
  1806. }
  1807. /**
  1808. * Return the advagg_global_counter variable.
  1809. *
  1810. * @return int
  1811. * Int value.
  1812. */
  1813. function advagg_get_global_counter() {
  1814. $global_counter = variable_get('advagg_global_counter', ADVAGG_GLOBAL_COUNTER);
  1815. return $global_counter;
  1816. }
  1817. /**
  1818. * Cache clear callback for admin_menu/flush-cache/advagg.
  1819. */
  1820. function advagg_admin_flush_cache() {
  1821. module_load_include('inc', 'advagg', 'advagg.admin');
  1822. advagg_admin_flush_cache_button();
  1823. }
  1824. // Exact Copies of core functions from patches.
  1825. /**
  1826. * Callback for pre_render to add elements needed for JavaScript to be rendered.
  1827. *
  1828. * This function evaluates the aggregation enabled/disabled condition on a group
  1829. * by group basis by testing whether an aggregate file has been made for the
  1830. * group rather than by testing the site-wide aggregation setting. This allows
  1831. * this function to work correctly even if modules have implemented custom
  1832. * logic for grouping and aggregating files.
  1833. *
  1834. * @param array $elements
  1835. * A render array containing:
  1836. * - #items: The JavaScript items as returned by drupal_add_js() and
  1837. * altered by drupal_get_js().
  1838. * - #group_callback: A function to call to group #items. Following
  1839. * this function, #aggregate_callback is called to aggregate items within
  1840. * the same group into a single file.
  1841. * - #aggregate_callback: A function to call to aggregate the items within
  1842. * the groups arranged by the #group_callback function.
  1843. *
  1844. * @return array
  1845. * A render array that will render to a string of JavaScript tags.
  1846. *
  1847. * @see drupal_get_js()
  1848. */
  1849. function advagg_pre_render_scripts($elements) {
  1850. // Group and aggregate the items.
  1851. if (isset($elements['#group_callback'])) {
  1852. $elements['#groups'] = $elements['#group_callback']($elements['#items']);
  1853. }
  1854. if (isset($elements['#aggregate_callback'])) {
  1855. $elements['#aggregate_callback']($elements['#groups']);
  1856. }
  1857. // A dummy query-string is added to filenames, to gain control over
  1858. // browser-caching. The string changes on every update or full cache
  1859. // flush, forcing browsers to load a new copy of the files, as the
  1860. // URL changed. Files that should not be cached (see drupal_add_js())
  1861. // get REQUEST_TIME as query-string instead, to enforce reload on every
  1862. // page request.
  1863. $default_query_string = variable_get('css_js_query_string', '0');
  1864. // For inline JavaScript to validate as XHTML, all JavaScript containing
  1865. // XHTML needs to be wrapped in CDATA. To make that backwards compatible
  1866. // with HTML 4, we need to comment out the CDATA-tag.
  1867. $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
  1868. $embed_suffix = "\n//--><!]]>\n";
  1869. // Since JavaScript may look for arguments in the URL and act on them, some
  1870. // third-party code might require the use of a different query string.
  1871. $js_version_string = variable_get('drupal_js_version_query_string', 'v=');
  1872. // Defaults for each SCRIPT element.
  1873. $element_defaults = array(
  1874. '#type' => 'html_tag',
  1875. '#tag' => 'script',
  1876. '#value' => '',
  1877. '#attributes' => array(
  1878. 'type' => 'text/javascript',
  1879. ),
  1880. );
  1881. // Loop through each group.
  1882. foreach ($elements['#groups'] as $group) {
  1883. // If a group of files has been aggregated into a single file,
  1884. // $group['data'] contains the URI of the aggregate file. Add a single
  1885. // script element for this file.
  1886. if (isset($group['type']) && $group['type'] == 'file' && isset($group['data'])) {
  1887. $element = $element_defaults;
  1888. $element['#attributes']['src'] = file_create_url($group['data']);
  1889. $element['#browsers'] = $group['browsers'];
  1890. if (!empty($group['defer'])) {
  1891. $element['#attributes']['defer'] = 'defer';
  1892. }
  1893. if (!empty($group['async'])) {
  1894. $element['#attributes']['async'] = 'async';
  1895. }
  1896. if (!empty($group['onload'])) {
  1897. $element['#attributes']['onload'] = $group['onload'];
  1898. }
  1899. $elements[] = $element;
  1900. }
  1901. // For non-file types, and non-aggregated files, add a script element per
  1902. // item.
  1903. else {
  1904. foreach ($group['items'] as $item) {
  1905. // Skip if data is empty.
  1906. if (empty($item['data'])) {
  1907. continue;
  1908. }
  1909. // Element properties that do not depend on item type.
  1910. $element = $element_defaults;
  1911. if (!empty($item['defer'])) {
  1912. $element['#attributes']['defer'] = 'defer';
  1913. }
  1914. if (!empty($item['async'])) {
  1915. $element['#attributes']['async'] = 'async';
  1916. }
  1917. if (!empty($item['onload'])) {
  1918. $element['#attributes']['onload'] = $item['onload'];
  1919. }
  1920. $element['#browsers'] = isset($item['browsers']) ? $item['browsers'] : array();
  1921. // Crude type detection if needed.
  1922. if (empty($item['type'])) {
  1923. if (is_array($item['data'])) {
  1924. $item['type'] = 'setting';
  1925. }
  1926. elseif (strpos($item['data'], 'http://') === 0 || strpos($item['data'], 'https://') === 0 || strpos($item['data'], '//') === 0) {
  1927. $item['type'] = 'external';
  1928. }
  1929. elseif (file_exists(trim($item['data']))) {
  1930. $item['type'] = 'file';
  1931. }
  1932. else {
  1933. $item['type'] = 'inline';
  1934. }
  1935. }
  1936. // Element properties that depend on item type.
  1937. switch ($item['type']) {
  1938. case 'setting':
  1939. // If in development mode and PHP >= 5.4.0 pretty print JSON.
  1940. static $php540;
  1941. if (!isset($php540)) {
  1942. $php540 = version_compare(PHP_VERSION, '5.4.0', '>=');
  1943. }
  1944. $json_data = drupal_array_merge_deep_array($item['data']);
  1945. if ($php540 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) == -1) {
  1946. $json_data = @json_encode($json_data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_PRETTY_PRINT);
  1947. }
  1948. else {
  1949. $json_data = @drupal_json_encode($json_data);
  1950. }
  1951. $element['#value_prefix'] = $embed_prefix;
  1952. $element['#value'] = 'jQuery.extend(Drupal.settings, ' . $json_data . ");";
  1953. $element['#value_suffix'] = $embed_suffix;
  1954. break;
  1955. case 'inline':
  1956. $element['#value_prefix'] = $embed_prefix;
  1957. $element['#value'] = $item['data'];
  1958. $element['#value_suffix'] = $embed_suffix;
  1959. break;
  1960. case 'file':
  1961. $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
  1962. $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
  1963. $element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
  1964. break;
  1965. case 'external':
  1966. $element['#attributes']['src'] = $item['data'];
  1967. break;
  1968. }
  1969. $elements[] = $element;
  1970. }
  1971. }
  1972. }
  1973. return $elements;
  1974. }
  1975. /**
  1976. * Default callback to group JavaScript items.
  1977. *
  1978. * This function arranges the JavaScript items that are in the #items property
  1979. * of the scripts element into groups. When aggregation is enabled, files within
  1980. * a group are aggregated into a single file, significantly improving page
  1981. * loading performance by minimizing network traffic overhead.
  1982. *
  1983. * This function puts multiple items into the same group if they are groupable
  1984. * and if they are for the same browsers. Items of the 'file' type are groupable
  1985. * if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or
  1986. * 'external' type are not groupable.
  1987. *
  1988. * This function also ensures that the process of grouping items does not change
  1989. * their relative order. This requirement may result in multiple groups for the
  1990. * same type and browsers, if needed to accommodate other items in
  1991. * between.
  1992. *
  1993. * @param array $javascript
  1994. * An array of JavaScript items, as returned by drupal_add_js(), but after
  1995. * alteration performed by drupal_get_js().
  1996. *
  1997. * @return array
  1998. * An array of JavaScript groups. Each group contains the same keys (e.g.,
  1999. * 'data', etc.) as a JavaScript item from the $javascript parameter, with the
  2000. * value of each key applying to the group as a whole. Each group also
  2001. * contains an 'items' key, which is the subset of items from $javascript that
  2002. * are in the group.
  2003. *
  2004. * @see drupal_pre_render_scripts()
  2005. */
  2006. function advagg_group_js($javascript) {
  2007. $groups = array();
  2008. // If a group can contain multiple items, we track the information that must
  2009. // be the same for each item in the group, so that when we iterate the next
  2010. // item, we can determine if it can be put into the current group, or if a
  2011. // new group needs to be made for it.
  2012. $current_group_keys = NULL;
  2013. $index = -1;
  2014. foreach ($javascript as $key => $item) {
  2015. // The browsers for which the JavaScript item needs to be loaded is part of
  2016. // the information that determines when a new group is needed, but the order
  2017. // of keys in the array doesn't matter, and we don't want a new group if all
  2018. // that's different is that order.
  2019. if (isset($item['browsers'])) {
  2020. ksort($item['browsers']);
  2021. }
  2022. else {
  2023. $item['browsers'] = array();
  2024. }
  2025. if (empty($item['type']) && $key == 'settings') {
  2026. $item['type'] = 'setting';
  2027. }
  2028. switch ($item['type']) {
  2029. case 'file':
  2030. // Group file items if their 'preprocess' flag is TRUE.
  2031. // Help ensure maximum reuse of aggregate files by only grouping
  2032. // together items that share the same 'group' value and 'every_page'
  2033. // flag. See drupal_add_js() for details about that.
  2034. $group_keys = $item['preprocess'] ? array(
  2035. $item['type'],
  2036. $item['group'],
  2037. $item['every_page'],
  2038. $item['browsers'],
  2039. ) : FALSE;
  2040. break;
  2041. case 'external':
  2042. case 'setting':
  2043. case 'inline':
  2044. // Do not group external, settings, and inline items.
  2045. $group_keys = FALSE;
  2046. break;
  2047. default:
  2048. // Define this here so we don't get undefined alerts down below.
  2049. $group_keys = NULL;
  2050. break;
  2051. }
  2052. // If the group keys don't match the most recent group we're working with,
  2053. // then a new group must be made.
  2054. if ($group_keys !== $current_group_keys) {
  2055. ++$index;
  2056. // Initialize the new group with the same properties as the first item
  2057. // being placed into it. The item's 'data' and 'weight' properties are
  2058. // unique to the item and should not be carried over to the group.
  2059. $groups[$index] = $item;
  2060. unset($groups[$index]['data'], $groups[$index]['weight']);
  2061. $groups[$index]['items'] = array();
  2062. $current_group_keys = $group_keys ? $group_keys : NULL;
  2063. }
  2064. // Add the item to the current group.
  2065. $groups[$index]['items'][] = $item;
  2066. }
  2067. return $groups;
  2068. }
  2069. /**
  2070. * Stable sort for CSS and JS items.
  2071. *
  2072. * Preserves the order of items with equal sort criteria.
  2073. *
  2074. * The function will sort by:
  2075. * - $item['group'], integer, ascending
  2076. * - $item['every_page'], boolean, first TRUE then FALSE
  2077. * - $item['weight'], integer, ascending
  2078. *
  2079. * @param array &$items
  2080. * Array of JS or CSS items, as in drupal_add_css() and drupal_add_js().
  2081. * The array keys can be integers or strings. The items themselves are arrays.
  2082. *
  2083. * @see drupal_get_css()
  2084. * @see drupal_get_js()
  2085. * @see drupal_add_css()
  2086. * @see drupal_add_js()
  2087. * @see https://drupal.org/node/1388546
  2088. */
  2089. function advagg_drupal_sort_css_js_stable(&$items) {
  2090. // Within a group, order all infrequently needed, page-specific files after
  2091. // common files needed throughout the website. Separating this way allows for
  2092. // the aggregate file generated for all of the common files to be reused
  2093. // across a site visit without being cut by a page using a less common file.
  2094. $nested = array();
  2095. foreach ($items as $key => $item) {
  2096. $nested[$item['group']][$item['every_page'] ? 1 : 0][$item['weight']][$key] = $item;
  2097. }
  2098. // First order by group, so that, for example, all items in the CSS_SYSTEM
  2099. // group appear before items in the CSS_DEFAULT group, which appear before
  2100. // all items in the CSS_THEME group. Modules may create additional groups by
  2101. // defining their own constants.
  2102. $sorted = array();
  2103. ksort($nested);
  2104. foreach ($nested as $group => $group_items) {
  2105. krsort($group_items);
  2106. foreach ($group_items as $ep => $ep_items) {
  2107. ksort($ep_items);
  2108. // Finally, order by weight.
  2109. foreach ($ep_items as $weight => $weight_items) {
  2110. foreach ($weight_items as $key => $item) {
  2111. $sorted[$key] = $item;
  2112. }
  2113. }
  2114. }
  2115. }
  2116. $items = $sorted;
  2117. }
Error | ELMSLN API

Error

×

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/elmsln_community/api.elmsln.org/includes/common.inc:2791) in drupal_send_headers() (line 1499 of /var/www/html/elmsln_community/api.elmsln.org/includes/bootstrap.inc).
  • Error: Call to undefined function apc_delete() in DrupalAPCCache->clear() (line 289 of /var/www/html/elmsln_community/api.elmsln.org/sites/all/modules/apc/drupal_apc_cache.inc).
The website encountered an unexpected error. Please try again later.