You are here

advagg.module in Advanced CSS/JS Aggregation 7.2

Advanced CSS/JS aggregation module.

File

advagg.module
View source
<?php

/**
 * @file
 * Advanced CSS/JS aggregation module.
 */

/**
 * @defgroup default_variables default values for variables
 * @{
 * Default values for various variables are defined here.
 */

/**
 * Default space characters.
 */
define('ADVAGG_SPACE', '__');

/**
 * Default value to see if advanced CSS/JS aggregation is enabled.
 */
define('ADVAGG_ENABLED', TRUE);

/**
 * Default value to see if .gz files should be created as well.
 */
define('ADVAGG_GZIP', TRUE);

/**
 * Default value to see we use core's default grouping of CSS/JS files.
 */
if (module_exists('advagg_bundler') && variable_get('advagg_bundler_active', TRUE)) {
  define('ADVAGG_CORE_GROUPS', FALSE);
}
else {
  define('ADVAGG_CORE_GROUPS', TRUE);
}

/**
 * Default value to see if we cache the full CSS/JS structure.
 */
define('ADVAGG_CACHE_LEVEL', 1);

/**
 * Default value of counter.
 */
define('ADVAGG_GLOBAL_COUNTER', 0);

/**
 * Send non blocking requests in order to generate aggregated files via HTTPRL.
 */
define('ADVAGG_USE_HTTPRL', FALSE);

/**
 * Combine css files by using media queries instead of media attributes.
 */
define('ADVAGG_COMBINE_CSS_MEDIA', FALSE);

/**
 * Prevent more than 4095 css selector rules inside of a CSS aggregate.
 */
define('ADVAGG_IE_CSS_SELECTOR_LIMITER', FALSE);

/**
 * The value the IE css selector should use.
 */
define('ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE', 4095);

/**
 * Default location of AdvAgg configuration items.
 */
define('ADVAGG_ADMIN_CONFIG_ROOT_PATH', 'admin/config/development/performance');

/**
 * Default value for debugging info to watchdog.
 */
define('ADVAGG_DEBUG', FALSE);

/**
 * Scan and fix any JS that was added with the wrong type.
 */
define('ADVAGG_JS_FIX_TYPE', TRUE);

/**
 * Scan and fix any CSS that was added with the wrong type.
 */
define('ADVAGG_CSS_FIX_TYPE', TRUE);

/**
 * Generate a .htaccess file in the AdvAgg dirs.
 */
define('ADVAGG_HTACCESS_CHECK_GENERATE', TRUE);

/**
 * Display a message that the bypass cookie is set.
 */
define('ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE', TRUE);

/**
 * Run advagg_url_inbound_alter().
 */
define('ADVAGG_URL_INBOUND_ALTER', TRUE);

/**
 * Allow JavaScript insertion into any scope even if theme does not support it.
 */
define('ADVAGG_SCRIPTS_SCOPE_ANYWHERE', FALSE);

/**
 * Empty the scripts key inside of template_process_html replacement function.
 */
define('ADVAGG_CLEAR_SCRIPTS', TRUE);

/**
 * TRUE when db table 'advagg_aggregates_versions' is missing in advagg_enable.
 */
define('ADVAGG_NEEDS_UPDATE', FALSE);

/**
 * How long to wait until advagg cron will run again. Default is 23 hours.
 */
define('ADVAGG_CRON_FREQUENCY', 82800);

/**
 * How long to wait until unaccessed aggregates are removed from the database.
 */
define('ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME', 1209600);

/**
 * How long to wait until unaccessed aggregates are removed from the database.
 */
define('ADVAGG_REMOVE_OLD_UNUSED_AGGREGATES_TIME', 3888000);

/**
 * Run advagg* js_alter and css_alter after all others including theme hooks.
 */
define('ADVAGG_RUN_ALTER_AFTER_THEME', TRUE);

/**
 * Do more file operations in main thread if the file system is fast.
 *
 * If AdvAgg's directories are mounted on something like S3, you might want to
 * set this to FALSE.
 */
define('ADVAGG_FAST_FILESYSTEM', TRUE);

/**
 * Pregenerate aggregate files.
 *
 * If disable the browser requesting the file will cause the generation to
 * happen. If advagg 404 handling is broken then setting this to false will
 * break your site in bad ways.
 */
define('ADVAGG_PREGENERATE_AGGREGATE_FILES', TRUE);

/**
 * Include the base_url global as part of the hooks hash array.
 */
define('ADVAGG_INCLUDE_BASE_URL', FALSE);

/**
 * Convert absolute path CSS/JS src/url() to be relative if self referencing.
 */
define('ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH', TRUE);

/**
 * Convert absolute path CSS/JS src/url() to be protocol relative.
 */
define('ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH', TRUE);

/**
 * Convert absolute path CSS/JS src/url() to be https.
 */
define('ADVAGG_FORCE_HTTPS_PATH', FALSE);

/**
 * Convert relative path CSS inside src() to be absolute.
 */
define('ADVAGG_CSS_ABSOLUTE_PATH', FALSE);

/**
 * If TRUE then the css is being rendered via javascript.
 */
define('ADVAGG_CSS_IN_JS', FALSE);

/**
 * Set to FALSE to not alter the CSS/JS pushed out from AdvAgg.
 */
define('ADVAGG_AJAX_RENDER_ALTER', TRUE);

/**
 * Workaround for 401 errors.
 */
define('ADVAGG_AUTH_BASIC_USER', '');

/**
 * Workaround for 401 errors.
 */
define('ADVAGG_AUTH_BASIC_PASS', '');

/**
 * Skip far future check on status page.
 */
define('ADVAGG_SKIP_FAR_FUTURE_CHECK', FALSE);

/**
 * Skip preprocess check on status page.
 */
define('ADVAGG_SKIP_ENABLED_PREPROCESS_CHECK', FALSE);

/**
 * Prefetch External domains for CSS/JS.
 */
define('ADVAGG_RESOURCE_HINTS_DNS_PREFETCH', FALSE);

/**
 * Preconnect External domains for CSS/JS.
 */
define('ADVAGG_RESOURCE_HINTS_PRECONNECT', FALSE);

/**
 * Preload CSS/JS and sub requests.
 */
define('ADVAGG_RESOURCE_HINTS_PRELOAD', FALSE);

/**
 * Location of CSS/JS and sub requests resource hints.
 */
define('ADVAGG_RESOURCE_HINTS_LOCATION', 1);

/**
 * Function to use when converting a non scalar to a string.
 */
define('ADVAGG_SERIALIZE', 'json_encode');

/**
 * Default root dir for the advagg files; controls advagg_get_root_files_dir().
 */
define('ADVAGG_ROOT_DIR_PREFIX', 'public://');

/**
 * Skip gzip check on status page.
 */
define('ADVAGG_SKIP_GZIP_CHECK', FALSE);

/**
 * If true do not call file_create_url() for url() inside css files.
 */
if (module_exists('cdn')) {
  define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', FALSE);
}
else {
  define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', TRUE);
}

/**
 * Display a message that the a CSS/JS file has changed.
 */
define('ADVAGG_SHOW_FILE_CHANGED_MESSAGE', TRUE);

/**
 * How long to wait until an aggregate with a missing file is written to disk.
 */
define('ADVAGG_FILE_READ_FAILURE_TIMEOUT', 1800);

/**
 * See if the mtime != if TRUE < if FALSE.
 */
define('ADVAGG_STRICT_MTIME_CHECK', TRUE);

/**
 * Default value to see if .br files should be created as well.
 */
if (function_exists('brotli_compress')) {
  define('ADVAGG_BROTLI', TRUE);
}
else {
  define('ADVAGG_BROTLI', FALSE);
}

/**
 * Default value to see zopfli_encode should not be used.
 */
define('ADVAGG_NO_ZOPFLI', FALSE);

/**
 * If true do test for 304 files on the status report page.
 */
define('ADVAGG_SKIP_304_CHECK', FALSE);

/**
 * If false do not set the immutable header.
 */
define('ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE', TRUE);

/**
 * If true chrome=1 will be added to the X-UA-Compatible header.
 */
define('ADVAGG_CHROME_HEADER_ENABLED', FALSE);

/**
 * If true advagg htaccess Options uses SymLinksIfOwnerMatch vs FollowSymLinks.
 */
define('ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH', FALSE);

/**
 * How far down a JS file to look for use strict.
 */
define('ADVAGG_JS_HEADER_LENGTH', 24576);

/**
 * If not empty advagg htaccess will include the given rewritebase.
 */
define('ADVAGG_HTACCESS_REWRITEBASE', '');

/**
 * Preload CSS/JS header limit 3kb.
 */
define('ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE', 3072);

/**
 * If TRUE advagg will search for and remove empty CSS files from aggregates.
 */
define('ADVAGG_CSS_REMOVE_EMPTY_FILES', FALSE);

/**
 * If TRUE advagg will search for and remove empty JS files from aggregates.
 */
define('ADVAGG_JS_REMOVE_EMPTY_FILES', FALSE);

/**
 * If TRUE advagg will be disabled on admin pages.
 */
define('ADVAGG_DISABLE_ON_ADMIN', FALSE);

/**
 * Default is 200; 203 has been requested in the past.
 */
define('ADVAGG_HTTP_200_CODE', 200);

/**
 * Verify all 3 hashes from the filename, if TRUE only verify 1st hash.
 */
define('ADVAGG_WEAK_FILE_VERIFICATION', FALSE);

/**
 * If FALSE lock_acquire is used before writing a file.
 */
define('ADVAGG_NO_LOCKS', FALSE);

/**
 * If TRUE drupal_get_html_head will be rendered at the top of the css section.
 */
define('ADVAGG_HTML_HEAD_IN_CSS_LOCATION', FALSE);

/**
 * If 4 the admin section gets unlocked.
 */
define('ADVAGG_ADMIN_MODE', 4);

/**
 * If TRUE farfuture headers will go out if the file is delivered by php.
 */
define('ADVAGG_FARFUTURE_PHP', FALSE);

/**
 * Internal urls set here will have advagg disabled on those pages.
 */
define('ADVAGG_DISABLE_ON_LISTED_PAGES', '');

/**
 * Check library versions on admin pages.
 */
define('ADVAGG_REMOTE_VERSION_CHECK', TRUE);

/**
 * @} End of "defgroup default_variables".
 */

/**
 * @addtogroup hooks
 * @{
 */

/**
 * Implements hook_help().
 */
function advagg_help($path, $arg) {
  switch ($path) {
    case 'admin/help#advagg':
      $filepath = dirname(__FILE__) . '/README.txt';
      if (file_exists($filepath)) {
        $readme = file_get_contents($filepath);
      }
      if (!isset($readme)) {
        return NULL;
      }
      if (module_exists('markdown')) {
        $filters = module_invoke('markdown', 'filter_info');
        $info = $filters['filter_markdown'];
        if (function_exists($info['process callback'])) {
          $output = $info['process callback']($readme, NULL);
        }
        else {
          $output = '<pre>' . $readme . '</pre>';
        }
      }
      else {
        $output = '<pre>' . $readme . '</pre>';
      }
      return $output;
  }
}

/**
 * Implements hook_block_view_alter().
 */
function advagg_block_view_alter(&$data, $block) {

  // Do not run hook if AdvAgg is disabled.
  if (!advagg_enabled()) {
    return;
  }

  // Do not run hook if setting is disabled.
  if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
    return;
  }
  if (empty($data) || empty($data['content'])) {
    return;
  }
  $block_info = $block->module . ':' . $block->delta;
  $prefix = "<!-- AdvAgg block:prefix:{$block_info} tag -->";
  $suffix = "<!-- AdvAgg block:suffix:{$block_info} tag -->";
  if (is_string($data['content'])) {
    $data['content'] = $prefix . $data['content'] . $suffix;
  }
  else {
    if (!isset($data['content']['#prefix'])) {
      $data['content']['#prefix'] = '';
    }
    $data['content']['#prefix'] .= $prefix;
    if (!isset($data['content']['#suffix'])) {
      $data['content']['#suffix'] = '';
    }
    $data['content']['#suffix'] .= $suffix;
  }
}

/**
 * Implements hook_views_pre_render().
 */
function advagg_views_pre_render(&$view) {

  // Do not run hook if AdvAgg is disabled.
  if (!advagg_enabled()) {
    return;
  }

  // Do not run hook if setting is disabled.
  if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
    return;
  }
  $info = "{$view->name}:{$view->current_display}";
  $prefix = "<!-- AdvAgg view:prefix:{$info} tag -->";
  $suffix = "<!-- AdvAgg view:suffix:{$info} tag -->";
  if (!isset($view->attachment_before)) {
    $view->attachment_before = '';
  }
  $view->attachment_before .= $prefix;
  if (!isset($view->attachment_after)) {
    $view->attachment_after = '';
  }
  $view->attachment_after .= $suffix;
}

/**
 * Implements hook_panels_pre_render().
 */
function advagg_panels_pre_render($panels_display, &$renderer) {

  // Do not run hook if AdvAgg is disabled.
  if (!advagg_enabled()) {
    return;
  }

  // Do not run hook if setting is disabled.
  if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
    return;
  }
  $info = "{$panels_display->layout}:{$panels_display->css_id}";
  $prefix = "<!-- AdvAgg panels:prefix:{$info} tag -->";
  $suffix = "<!-- AdvAgg panels:suffix:{$info} tag -->";
  if (!isset($renderer->prefix)) {
    $renderer->prefix = '';
  }
  $renderer->prefix .= $prefix;
  if (!isset($renderer->suffix)) {
    $renderer->suffix = '';
  }
  $renderer->suffix .= $suffix;
}

/**
 * Implements hook_url_inbound_alter().
 *
 * Inbound URL rewrite helper. If host includes subdomain, rewrite URI and
 * internal path if necessary.
 */
function advagg_url_inbound_alter(&$path, $original_path, $path_language) {

  // Do nothing if this has been disabled.
  if (!variable_get('advagg_url_inbound_alter', ADVAGG_URL_INBOUND_ALTER)) {
    return;
  }

  // Setup static so we only need to run the logic once.
  $already_ran =& drupal_static(__FUNCTION__);
  if (!isset($already_ran)) {
    $already_ran = array();
  }
  $request_path = request_path();

  // Set the path again if we already did this alter.
  if (array_key_exists($request_path, $already_ran)) {
    $path = $already_ran[$request_path];
    return;
  }

  // If requested path was for an advagg file but now it is something else
  // switch is back to the advagg file.
  if (!empty($path) && $path != $request_path && advagg_match_file_pattern($request_path)) {

    // Get the advagg paths.
    $advagg_path = advagg_get_root_files_dir();

    // Get the top level path.
    $top_level = substr($advagg_path[0][1], 0, strpos($advagg_path[0][1], 'advagg_css'));

    // Only change if it's an exact match.
    $start = strpos($request_path, $top_level . 'advagg_');
    if ($start === 0) {

      // Set path to correct advagg path.
      $path = substr($request_path, $start);
      $already_ran[$request_path] = $path;
    }
    else {

      // Put all languages prefixes into an array.
      $language_list = language_list();
      $prefixes = array();
      foreach ($language_list as $lang) {
        if ($lang->enabled && !empty($lang->prefix) && strpos($request_path, $lang->prefix) !== FALSE) {
          $prefixes[$lang->prefix] = $lang->prefix;
        }
      }
      if (!empty($prefixes)) {

        // Remove all enabled languages prefixes from the beginning of the path.
        $substr_to_shrink = substr($request_path, 0, $start);
        foreach ($prefixes as $prefix) {
          $substr_to_shrink = str_replace($prefix . '/', '', $substr_to_shrink);
        }

        // Set path to correct advagg path.
        $path = $substr_to_shrink . substr($request_path, $start);
        $already_ran[$request_path] = $path;
      }
    }
  }
}

/**
 * Implements hook_hook_info().
 */
function advagg_hook_info() {

  // List of hooks that can be inside of *.advagg.inc files.
  // All advagg hooks except for:
  // advagg_current_hooks_hash_array_alter
  // advagg_hooks_implemented_alter
  // advagg_get_root_files_dir_alter
  // because these 3 hooks are used on most requests.
  $advagg_hooks = array(
    'advagg_get_css_file_contents_pre_alter',
    'advagg_get_css_file_contents_alter',
    'advagg_get_js_file_contents_alter',
    'advagg_get_css_aggregate_contents_alter',
    'advagg_get_js_aggregate_contents_alter',
    'advagg_save_aggregate_pre_alter',
    'advagg_save_aggregate_alter',
    'advagg_build_aggregate_plans_alter',
    'advagg_build_aggregate_plans_post_alter',
    'advagg_css_groups_alter',
    'advagg_js_groups_alter',
    'advagg_modify_css_pre_render_alter',
    'advagg_modify_js_pre_render_alter',
    'advagg_changed_files',
    'advagg_removed_aggregates',
    'advagg_scan_for_changes',
    'advagg_get_info_on_files_alter',
    'advagg_context_alter',
    'advagg_missing_root_file',
  );
  $hooks = array();
  foreach ($advagg_hooks as $hook) {
    $hooks[$hook] = array(
      'group' => 'advagg',
    );
  }
  return $hooks;
}

/**
 * Implements hook_module_implements_alter().
 */
function advagg_module_implements_alter(&$implementations, $hook) {

  // Move advagg_theme_registry_alter to the top.
  if ($hook === 'theme_registry_alter' && array_key_exists('advagg', $implementations)) {
    $item = array(
      'advagg' => $implementations['advagg'],
    );
    unset($implementations['advagg']);
    $implementations = array_merge($item, $implementations);
  }

  // Move advagg_ajax_render_alter to the top.
  if ($hook === 'ajax_render_alter' && array_key_exists('advagg', $implementations)) {
    $item = array(
      'advagg' => $implementations['advagg'],
    );
    unset($implementations['advagg']);
    $implementations = array_merge($item, $implementations);
  }

  // Move advagg_element_info_alter to the bottom.
  if ($hook === 'element_info_alter' && array_key_exists('advagg', $implementations)) {
    $item = $implementations['advagg'];
    unset($implementations['advagg']);
    $implementations['advagg'] = $item;
  }

  // Replace locale_js_alter with _advagg_locale_js_alter.
  if ($hook === 'js_alter' && array_key_exists('locale', $implementations)) {
    unset($implementations['locale']);
    $implementations['_advagg_locale'] = FALSE;
  }

  // Move advagg_file_url_alter to the bottom.
  if ($hook === 'file_url_alter' && array_key_exists('advagg', $implementations)) {
    $item = $implementations['advagg'];
    unset($implementations['advagg']);
    $implementations['advagg'] = $item;
  }
  if ($hook === 'requirements') {

    // Move advagg_requirements to the bottom.
    if (array_key_exists('advagg', $implementations)) {
      $item = $implementations['advagg'];
      unset($implementations['advagg']);
      $implementations['advagg'] = $item;
    }

    // Move advagg_css_cdn to the bottom.
    if (array_key_exists('advagg_css_cdn', $implementations)) {
      $item = $implementations['advagg_css_cdn'];
      unset($implementations['advagg_css_cdn']);
      $implementations['advagg_css_cdn'] = $item;
    }

    // Move advagg_css_compress to the bottom.
    if (array_key_exists('advagg_css_compress', $implementations)) {
      $item = $implementations['advagg_css_compress'];
      unset($implementations['advagg_css_compress']);
      $implementations['advagg_css_compress'] = $item;
    }

    // Move advagg_js_cdn to the bottom.
    if (array_key_exists('advagg_js_cdn', $implementations)) {
      $item = $implementations['advagg_js_cdn'];
      unset($implementations['advagg_js_cdn']);
      $implementations['advagg_js_cdn'] = $item;
    }

    // Move advagg_js_compress to the bottom.
    if (array_key_exists('advagg_js_compress', $implementations)) {
      $item = $implementations['advagg_js_compress'];
      unset($implementations['advagg_js_compress']);
      $implementations['advagg_js_compress'] = $item;
    }
  }

  // Move advagg_cron to the bottom.
  if ($hook === 'cron' && array_key_exists('advagg', $implementations)) {
    $item = $implementations['advagg'];
    unset($implementations['advagg']);
    $implementations['advagg'] = $item;
  }
}

/**
 * Implements hook_js_alter().
 *
 * This is a locking wrapper for locale_js_alter().
 */
function _advagg_locale_js_alter(&$js) {

  // If the variable is empty then get the latest variable from the database.
  $name = 'javascript_parsed';
  $parsed = variable_get($name, array());
  if (empty($parsed)) {
    $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable} WHERE name = :name', array(
      ':name' => $name,
    ))
      ->fetchAllKeyed());
    if (!empty($variables[$name])) {
      $GLOBALS['conf'][$name] = $variables[$name];
    }
  }

  // See if locale_js_alter() needs to do anything.
  $dir = 'public://' . variable_get('locale_js_directory', 'languages');
  $new_files = FALSE;

  // See if a rebuild of the translation file for the current language is
  // needed.
  if (!empty($parsed['refresh:' . $GLOBALS['language']->language])) {
    $new_files = TRUE;
  }

  // Check for new js source files.
  if (empty($new_files)) {
    foreach ($js as $item) {
      if ($item['type'] === 'file' && !in_array($item['data'], $parsed) && substr($item['data'], 0, strlen($dir)) != $dir) {
        $new_files = TRUE;
        break;
      }
    }
  }
  if (empty($new_files)) {

    // No new files to manage, just add in available i18n files.
    advagg_locale_js_add_translations($js, $dir);

    // Exit function.
    return;
  }
  $count = 0;
  while (!lock_acquire('locale_js_alter', 10)) {
    ++$count;

    // If we've waited over 3 times then skip.
    if ($count > 3) {
      lock_release('locale_js_alter');

      // Add in available i18n files.
      advagg_locale_js_add_translations($js, $dir);

      // Disable saving to the cache as translations might be missing.
      drupal_page_is_cacheable(FALSE);
      if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) > 1) {
        $GLOBALS['conf']['advagg_cache_level'] = 0;
      }
      return;
    }

    // Wait for the lock to be available.
    lock_wait('locale_js_alter');
  }
  try {

    // Run the alter.
    locale_js_alter($js);
  } catch (PDOException $e) {

    // If it fails we don't care, javascript_parsed is either already written or
    // it will happen again on the next request.
    // Still log it if in development mode.
    if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
      watchdog('advagg', 'Development Mode - Caught PDO Exception: <code>@info</code>', array(
        '@info' => $e,
      ));
    }
  }
  lock_release('locale_js_alter');
}

/**
 * Implements hook_system_info_alter().
 */
function advagg_system_info_alter(&$info, $file, $type) {
  $config_path =& drupal_static(__FUNCTION__);

  // Get advagg config path.
  if (empty($config_path)) {
    $config_path = advagg_admin_config_root_path();
  }

  // Replace advagg path.
  if (!empty($info['configure']) && strpos($info['configure'], '/advagg') !== FALSE && (!empty($info['dependencies']) && is_array($info['dependencies']) && in_array('advagg', $info['dependencies']) || $file->name === 'advagg')) {
    $pos = strpos($info['configure'], '/advagg') + 7;
    $substr = substr($info['configure'], 0, $pos);
    $info['configure'] = str_replace($substr, $config_path . '/advagg', $info['configure']);
  }
}

/**
 * Implements hook_permission().
 */
function advagg_permission() {
  return array(
    'bypass advanced aggregation' => array(
      'title' => t('bypass advanced aggregation'),
      'description' => t('User can use URL query strings to bypass AdvAgg.'),
    ),
  );
}

/**
 * Implements hook_file_url_alter().
 */
function advagg_file_url_alter(&$original_uri) {

  // Do nothing if URI does not contain /advagg_
  // OR file does not have the correct pattern.
  if (strpos($original_uri, '/advagg_') === FALSE || !advagg_match_file_pattern($original_uri)) {
    return;
  }

  // CDN fix.
  // Do nothing if
  // in maintenance_mode
  // CDN module does not exist
  // CDN far future is disabled
  // CDN mode is not basic
  // URI does not contain cdn/farfuture/.
  if (variable_get('maintenance_mode', FALSE) || !module_exists('cdn') || !variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT) || variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC) != CDN_MODE_BASIC || strpos($original_uri, 'cdn/farfuture/') === FALSE) {
    return;
  }

  // Remove cdn/farfuture/BASE64/prefix:value/ from the URI.
  $original_uri = preg_replace('/cdn\\/farfuture\\/[A-Za-z0-9-_]{43}\\/[A-Za-z]+\\:[A-Za-z0-9-_]+\\//', '', $original_uri);
}

/**
 * Implements hook_menu().
 */
function advagg_menu() {
  list($css_path, $js_path) = advagg_get_root_files_dir();
  $file_path = drupal_get_path('module', 'advagg');
  $config_path = advagg_admin_config_root_path();
  $path_defined = FALSE;
  if (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) {
    $external_css = trim(parse_url(str_replace('/test.css', '/%', file_create_url($css_path[0] . '/test.css')), PHP_URL_PATH));
    if (strpos($external_css, $GLOBALS['base_path']) === 0) {
      $external_css = substr($external_css, strlen($GLOBALS['base_path']));
    }
    $external_js = trim(parse_url(str_replace('/test.js', '/%', file_create_url($js_path[0] . '/test.js')), PHP_URL_PATH));
    if (strpos($external_js, $GLOBALS['base_path']) === 0) {
      $external_js = substr($external_js, strlen($GLOBALS['base_path']));
    }
    $items[$external_css] = array(
      'title' => "Generate CSS Aggregate",
      'page callback' => 'advagg_missing_aggregate',
      'type' => MENU_CALLBACK,
      // Allow anyone to access these public css files.
      'access callback' => TRUE,
      'file path' => $file_path,
      'file' => 'advagg.missing.inc',
    );
    $items[$external_js] = array(
      'title' => "Generate JS Aggregate",
      'page callback' => 'advagg_missing_aggregate',
      'type' => MENU_CALLBACK,
      // Allow anyone to access these public js files.
      'access callback' => TRUE,
      'file path' => $file_path,
      'file' => 'advagg.missing.inc',
    );
    $path_defined = TRUE;
  }
  if (!$path_defined) {
    $items[$css_path[1] . '/%'] = array(
      'title' => "Generate CSS Aggregate",
      'page callback' => 'advagg_missing_aggregate',
      'type' => MENU_CALLBACK,
      // Allow anyone to access these public css files.
      'access callback' => TRUE,
      'file path' => $file_path,
      'file' => 'advagg.missing.inc',
    );
    $items[$js_path[1] . '/%'] = array(
      'title' => "Generate JS Aggregate",
      'page callback' => 'advagg_missing_aggregate',
      'type' => MENU_CALLBACK,
      // Allow anyone to access these public js files.
      'access callback' => TRUE,
      'file path' => $file_path,
      'file' => 'advagg.missing.inc',
    );
  }

  // If mutiple paths are symlinked to the same location; allow advagg to handle
  // those addtional locations.
  $advagg_additional_generate_paths = variable_get('advagg_additional_generate_paths', array());
  if (!empty($advagg_additional_generate_paths)) {
    foreach ($advagg_additional_generate_paths as $path) {
      $items[$path] = array(
        'title' => "Generate CSS/JS Aggregate",
        'page callback' => 'advagg_missing_aggregate',
        'type' => MENU_CALLBACK,
        // Allow anyone to access these public css files.
        'access callback' => TRUE,
        'file path' => $file_path,
        'file' => 'advagg.missing.inc',
      );
    }
  }
  $items[$config_path . '/default'] = array(
    'title' => 'Performance',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'file path' => drupal_get_path('module', 'system'),
    'weight' => -10,
  );
  $items[$config_path . '/advagg'] = array(
    'title' => 'Advanced CSS/JS Aggregation',
    'description' => 'Configuration for Advanced CSS/JS Aggregation.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'advagg_admin_settings_form',
    ),
    'type' => MENU_LOCAL_TASK,
    'access arguments' => array(
      'administer site configuration',
    ),
    'file path' => $file_path,
    'file' => 'advagg.admin.inc',
    'weight' => 1,
  );
  $items[$config_path . '/advagg/config'] = array(
    'title' => 'Configuration',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items[$config_path . '/advagg/info'] = array(
    'title' => 'Information',
    'description' => 'More detailed information about advagg.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'advagg_admin_info_form',
    ),
    'type' => MENU_LOCAL_TASK,
    'access arguments' => array(
      'administer site configuration',
    ),
    'file path' => $file_path,
    'file' => 'advagg.admin.inc',
    'weight' => 18,
  );
  $items[$config_path . '/advagg/operations'] = array(
    'title' => 'Operations',
    'description' => 'Flush caches, set the bypass cookie, take drastic actions.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'advagg_admin_operations_form',
    ),
    'type' => MENU_LOCAL_TASK,
    'access arguments' => array(
      'administer site configuration',
    ),
    'file path' => $file_path,
    'file' => 'advagg.admin.inc',
    'weight' => 20,
  );
  return $items;
}

/**
 * Implements hook_cron().
 *
 * This will be ran once a day at most.
 */
function advagg_cron($bypass_time_check = FALSE) {

  // @param bool $bypass_time_check
  // Set to TRUE to skip the 24 hour check.
  //
  // Execute once a day (24 hours).
  if (!$bypass_time_check && variable_get('advagg_cron_timestamp', 0) > REQUEST_TIME - variable_get('advagg_cron_frequency', ADVAGG_CRON_FREQUENCY)) {
    return array();
  }
  variable_set('advagg_cron_timestamp', REQUEST_TIME);

  // Flush the cache_advagg_info cache bin.
  cache_clear_all(NULL, 'cache_advagg_info');
  $return = array();

  // Clear out all stale advagg aggregated files.
  module_load_include('inc', 'advagg', 'advagg.cache');
  $return[] = advagg_delete_stale_aggregates();

  // Delete all empty aggregated files.
  $return[] = advagg_delete_empty_aggregates();

  // Delete orphaned aggregates.
  $return[] = advagg_delete_orphaned_aggregates();

  // Remove aggregates that include missing files.
  $return[] = advagg_remove_missing_files_from_db();

  // Remove unused aggregates.
  $return[] = advagg_remove_old_unused_aggregates();

  // Remove expired locks from the semaphore database table.
  $return[] = advagg_cleanup_semaphore_table();

  // Remove old temp files.
  $return[] = advagg_remove_temp_files();

  // Refresh all locale files.
  $return[] = advagg_refresh_all_locale_files();

  // Update libraries data.
  advagg_get_remote_libraries_versions(TRUE);
  return $return;
}

/**
 * Implements hook_flush_caches().
 */
function advagg_flush_caches($all_bins = FALSE, $push_new_changes = TRUE) {

  // * @param bool $all_bins
  // *   TRUE: Get all advagg cache bins.
  // * @param bool $push_new_changes
  // *   FALSE: Do not scan for changes.
  //
  // Send back a blank array if aav table doesn't exist.
  if (!db_table_exists('advagg_aggregates_versions')) {
    return array();
  }

  // Scan for and push new changes.
  module_load_include('inc', 'advagg', 'advagg.cache');
  if ($push_new_changes) {
    advagg_push_new_changes();
  }

  // Get list of cache bins to clear.
  $bins = array(
    'cache_advagg_aggregates',
  );
  if ($all_bins) {
    $bins[] = 'cache_advagg_info';
  }
  return $bins;
}

/**
 * Implements hook_element_info_alter().
 */
function advagg_element_info_alter(&$type) {

  // Replace drupal_pre_render_styles with advagg_pre_render_styles.
  $type['styles']['#items'] = array();
  if (!isset($type['styles']['#pre_render'])) {
    $type['styles']['#pre_render'] = array();
  }
  $key = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']);
  if ($key !== FALSE) {
    $type['styles']['#pre_render'][$key] = 'advagg_pre_render_styles';
  }
  else {
    $type['styles']['#pre_render'][] = 'advagg_pre_render_styles';
  }

  // Allow for other code to easily change the render with alter hooks.
  $type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render';
  $type['styles']['#group_callback'] = 'drupal_group_css';

  // Swap in our own aggregation callback.
  $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css';
  $type['styles']['#type'] = 'styles';

  // Replace drupal_pre_render_scripts with advagg_pre_render_scripts.
  $type['scripts']['#items'] = array();
  if (!isset($type['scripts']['#pre_render'])) {
    $type['scripts']['#pre_render'] = array();
  }
  $key_drupal = array_search('drupal_pre_render_scripts', $type['scripts']['#pre_render']);
  $key_omega = array_search('omega_pre_render_scripts', $type['scripts']['#pre_render']);
  $key_aurora = array_search('aurora_pre_render_scripts', $type['scripts']['#pre_render']);
  if ($key_drupal !== FALSE) {
    $type['scripts']['#pre_render'][$key_drupal] = 'advagg_pre_render_scripts';
  }
  elseif ($key_omega !== FALSE) {
    $type['scripts']['#pre_render'][$key_omega] = 'advagg_pre_render_scripts';
  }
  elseif ($key_aurora !== FALSE) {
    $type['scripts']['#pre_render'][$key_aurora] = 'advagg_pre_render_scripts';
  }
  else {
    $type['scripts']['#pre_render'][] = 'advagg_pre_render_scripts';
  }

  // Allow for other code to easily change the render with alter hooks.
  $type['scripts']['#pre_render'][] = 'advagg_modify_js_pre_render';
  $type['scripts']['#group_callback'] = 'advagg_group_js';

  // Swap in our own aggregation callback.
  $type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js';
  $type['scripts']['#type'] = 'scripts';

  // Copy html_tag to html_script_tag.
  $type['html_script_tag'] = $type['html_tag'];
  $type['html_script_tag']['#theme'] = 'html_script_tag';
  $type['html_script_tag']['#type'] = 'html_script_tag';
}

/**
 * Implements hook_theme_registry_alter().
 *
 * Replace template_process_html with _advagg_process_html.
 */
function advagg_theme_registry_alter(&$theme_registry) {
  if (!isset($theme_registry['html'])) {
    return;
  }

  // Replace core's process function with our own.
  $index = array_search('template_process_html', $theme_registry['html']['process functions']);
  if ($index !== FALSE) {
    $theme_registry['html']['process functions'][$index] = '_advagg_process_html';
  }
  else {

    // Put AdvAgg at the bottom if we can't find the replacement.
    $theme_registry['html']['process functions'][] = '_advagg_process_html';
  }

  // Copy html_tag to html_script_tag.
  $theme_registry['html_script_tag'] = $theme_registry['html_tag'];
  $theme_registry['html_script_tag']['function'] = 'theme_html_script_tag';

  // Fix imce_page.
  if (isset($theme_registry['imce_page'])) {
    $advagg_path = drupal_get_path('module', 'advagg');
    $imce_path = drupal_get_path('module', 'imce');
    if (strpos($theme_registry['imce_page']['path'], $imce_path) !== FALSE) {
      $theme_registry['imce_page']['path'] = $advagg_path . '/tpl';
    }
  }
}

/**
 * Implements hook_ajax_render_alter().
 */
function advagg_ajax_render_alter(&$commands) {

  // Do not run hook if AdvAgg is disabled.
  if (!advagg_enabled()) {
    return;
  }

  // Do not run hook if advagg_ajax_render_alter is FALSE.
  if (!variable_get('advagg_ajax_render_alter', ADVAGG_AJAX_RENDER_ALTER)) {
    return;
  }

  // Conditionally adds the default Drupal/jQuery libraries to the page.
  // @see http://drupal.org/node/1279226
  if (function_exists('drupal_add_js_page_defaults')) {
    drupal_add_js_page_defaults();
  }

  // Get Core JS.
  list(, $core_scripts_header, $core_scripts_footer, $items, $settings) = advagg_build_ajax_js_css();

  // Get AdvAgg JS.
  $scripts_header = $scripts_footer = '';
  if (!empty($items['js'])) {
    $scripts_footer_array = advagg_get_js('footer', $items['js'], TRUE);

    // Function advagg_pre_render_scripts() gets called here.
    $scripts_footer = drupal_render($scripts_footer_array);
    $scripts_header_array = advagg_get_js('header', $items['js'], TRUE);

    // Function advagg_pre_render_scripts() gets called here.
    $scripts_header = drupal_render($scripts_header_array);
  }

  // Remove core JS.
  foreach ($commands as $key => $values) {

    // Skip if not an array or not a command.
    if (!is_array($values) || empty($values['command'])) {
      continue;
    }
    if ($values['command'] === 'settings' && is_array($values['settings']) && !empty($values['merge'])) {

      // Remove JS settings.
      unset($commands[$key]);
      continue;
    }
    if ($values['command'] === 'insert' && is_null($values['settings']) && $values['method'] === 'prepend' && $values['data'] == $core_scripts_header) {

      // Remove JS header.
      unset($commands[$key]);
      continue;
    }
    if ($values['command'] === 'insert' && is_null($values['settings']) && $values['method'] === 'append' && $values['data'] == $core_scripts_footer) {

      // Remove JS footer.
      unset($commands[$key]);
      continue;
    }
  }

  // Add in AdvAgg JS.
  $extra_commands = array();
  if (!empty($scripts_header)) {
    $extra_commands[] = ajax_command_prepend('head', $scripts_header);
  }
  if (!empty($scripts_footer)) {
    $extra_commands[] = ajax_command_append('body', $scripts_footer);
  }
  if (!empty($extra_commands)) {
    $commands = array_merge($extra_commands, $commands);
  }
  if (!empty($settings)) {
    array_unshift($commands, ajax_command_settings(advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($settings['data'], 'is_array'))), TRUE));
  }
}

/**
 * Implements hook_preprocess_page().
 */
function advagg_preprocess_page() {

  // Scan for changes to any CSS/JS files if in development mode.
  advagg_scan_filesystem_for_changes_live();
}

/**
 * Implements hook_preprocess_html().
 *
 * Add in rendering IE meta tag if "combine CSS" is enabled.
 */
function advagg_preprocess_html() {

  // http://www.phpied.com/conditional-comments-block-downloads/#update
  // Prevent conditional comments from stalling css downloads.
  $fix_blocking_css_ie = array(
    '#weight' => '-999999',
    '#type' => 'markup',
    '#markup' => "<!--[if IE]><![endif]-->\n",
  );

  // Add markup for IE conditional comments to head.
  drupal_add_html_head($fix_blocking_css_ie, 'fix_blocking_css_ie');

  // Do not force IE rendering mode if "combine CSS" is disabled.
  if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) {
    return;
  }

  // Send IE meta tag to force IE rendering mode header.
  $x_ua_compatible = 'IE=edge';
  if (variable_get('advagg_chrome_header_enabled', ADVAGG_CHROME_HEADER_ENABLED)) {
    $x_ua_compatible .= ',chrome=1';
  }
  drupal_add_http_header('X-UA-Compatible', $x_ua_compatible);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Give advice on how to temporarily disable css/js aggregation.
 */
function advagg_form_system_performance_settings_alter(&$form, &$form_state) {
  module_load_include('admin.inc', 'advagg');
  advagg_admin_system_performance_settings_form($form, $form_state);
}

/**
 * Implements hook_js_alter().
 */
function advagg_js_alter(&$js) {
  if (module_exists('admin_menu')) {

    // Fix for admin menu; put JS in footer.
    $path = drupal_get_path('module', 'admin_menu');
    $filename = $path . '/admin_menu.js';
    if (isset($js[$filename])) {
      $js[$filename]['scope'] = 'footer';
    }
  }
}

/**
 * @} End of "addtogroup hooks".
 */

/**
 * @defgroup 3rd_party_hooks 3rd party hook implementations
 * @{
 * Hooks that are not apart of core or AdvAgg.
 */

/**
 * Implements hook_cron_alter().
 */
function advagg_cron_alter(&$data) {

  // Run this cron job every 2 minutes.
  if (isset($data['advagg_js_compress_cron'])) {
    $data['advagg_js_compress_cron']['rule'] = '*/2 * * * *';
  }

  // Run this cron job every 5 minutes.
  if (isset($data['advagg_relocate_cron'])) {
    $data['advagg_relocate_cron']['rule'] = '*/5 * * * *';
  }

  // Run this cron job every day.
  if (isset($data['advagg_cron'])) {
    $data['advagg_cron']['rule'] = '0 0 * * *';
  }
}

/**
 * Implements hook_password_policy_force_change_allowed_paths_alter().
 */
function advagg_password_policy_force_change_allowed_paths_alter(&$allowed_paths) {
  $advagg_items = advagg_menu();
  foreach ($advagg_items as $path => $attributes) {
    if (!empty($attributes['page callback']) && $attributes['page callback'] === 'advagg_missing_aggregate') {
      $allowed_paths[] = str_replace('/%', '/*', $path);
    }
  }
}

/**
 * Implements hook_s3fs_upload_params_alter().
 *
 * Set headers for advagg files.
 */
function advagg_s3fs_upload_params_alter(&$upload_params) {

  // Get advagg dir.
  list($css_path, $js_path) = advagg_get_root_files_dir();
  $scheme_css = file_uri_scheme($css_path[1]);
  if ($scheme_css) {
    $css_path_dir = str_replace("{$scheme_css}://", '', $css_path[1]);
  }
  else {
    $css_path_dir = ltrim($css_path[1], '/');
  }
  $scheme_js = file_uri_scheme($js_path[1]);
  if ($scheme_js) {
    $js_path_dir = str_replace("{$scheme_js}://", '', $js_path[1]);
  }
  else {
    $js_path_dir = ltrim($js_path[1], '/');
  }

  // Get file type in advagg dir, css or js.
  $type = '';
  if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $css_path_dir) !== FALSE) {
    $type = 'css';
  }
  if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $js_path_dir) !== FALSE) {
    $type = 'js';
  }
  if ($js_path_dir === $css_path_dir && !empty($type)) {
    $pathinfo = pathinfo($upload_params['Key']);
    if ($pathinfo['extension'] === 'gz') {
      $pathinfo = pathinfo($pathinfo['filename']);
    }
    $type = $pathinfo['extension'];
  }
  if (empty($type)) {

    // Only change advagg files.
    return;
  }

  // Cache control is 52 weeeks.
  if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) {
    $upload_params['CacheControl'] = 'max-age=31449600, public, immutable';
  }
  else {
    $upload_params['CacheControl'] = 'max-age=31449600, public';
  }

  // Expires in 365 days.
  $upload_params['Expires'] = gmdate('D, d M Y H:i:s \\G\\M\\T', REQUEST_TIME + 365 * 24 * 60 * 60);

  // The extension is .css or .js.
  $pathinfo = pathinfo($upload_params['Key']);
  if ($pathinfo['extension'] === $type) {
    if (variable_get('advagg_gzip', ADVAGG_GZIP)) {

      // Set gzip.
      $upload_params['ContentEncoding'] = 'gzip';
    }
    elseif (variable_get('advagg_brotli', ADVAGG_BROTLI)) {

      // Set br.
      $upload_params['ContentEncoding'] = 'br';
    }
  }
}

/**
 * Return s3fs configuration settings and values.
 *
 * @param string $key
 *   A specific key available in the s3fs configuration. NULL by default.
 *
 * @return array|string|null
 *   The full s3fs configuration settings, value of a specific key,
 *   or NULL if s3fs is not enabled and the function do not exist, or the s3fs
 *   value is not defined.
 */
function advagg_get_s3fs_config($key = NULL) {
  if (module_exists('s3fs') && is_callable('_s3fs_get_config')) {
    $s3fs_config = _s3fs_get_config();
    if (empty($key)) {
      return $s3fs_config;
    }
    elseif (isset($s3fs_config[$key])) {
      return $s3fs_config[$key];
    }
  }
  return NULL;
}

/**
 * Shortcut to evaluate if s3fs no_rewrite_cssjs is set or empty.
 *
 * If this needs to be accessed in a loop, it is more efficient to call
 * advagg_get_s3fs_config() once from outside of the loop. An example
 * can be seen in the advagg_install_check_via_http function.
 *
 * @param bool $is_set
 *   Check if no_write_cssjs field is set (TRUE) or empty (FALSE).
 *
 * @return bool
 *   TRUE or FALSE is returned based on evaluating the field. If
 *   s3fs_config returns a NULL, evaluate the function to FALSE.
 *
 * @see advagg_get_s3fs_config()
 */
function advagg_s3fs_evaluate_no_rewrite_cssjs($is_set = TRUE) {
  $s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs');
  if (!is_null($s3fs_no_rewrite_cssjs)) {
    return $is_set ? !empty($s3fs_no_rewrite_cssjs) : empty($s3fs_no_rewrite_cssjs);
  }
  else {
    return FALSE;
  }
}

/**
 * Implements hook_admin_menu_cache_info().
 *
 * Add in a cache flush for advagg.
 */
function advagg_admin_menu_cache_info() {
  if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
    $caches['advagg'] = array(
      'title' => t('Adv CSS/JS Agg'),
      'callback' => 'advagg_admin_flush_cache',
    );
    return $caches;
  }
}

/**
 * Implements hook_admin_menu_output_alter().
 *
 * Add in a cache flush for advagg.
 */
function advagg_admin_menu_output_alter(array &$content) {
  if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {

    // Remove default core aggregation link.
    unset($content['icon']['icon']['flush-cache']['assets']);
  }
}

/**
 * Implements hook_anonymous_login_paths_alter().
 */
function advagg_anonymous_login_paths_alter(&$paths) {

  // Exclude advagg css/js paths.
  list($css_path, $js_path) = advagg_get_root_files_dir();
  $paths['exclude'][] = $css_path[1] . '/*';
  $paths['exclude'][] = $js_path[1] . '/*';
}

/**
 * Implements hook_pre_flush_all_caches().
 */
function advagg_pre_flush_all_caches() {
  static $run_once;
  if (!isset($run_once)) {
    $run_once = TRUE;

    // Only invoked by registry_rebuild.
    module_load_include('admin.inc', 'advagg');

    // Truncate the advagg_files table.
    advagg_admin_truncate_advagg_files();
  }
}

/**
 * @} End of "defgroup 3rd_party_hooks".
 */

/**
 * Only the alter part of locale_js_alter(), not the parsing part.
 *
 * @param array $javascript
 *   An array with all JavaScript code. Defaults to the default
 *   JavaScript array for the given scope.
 * @param string $dir
 *   String pointing to the public locale_js_directory.
 */
function advagg_locale_js_add_translations(array &$javascript, $dir) {

  // Add the translation JavaScript file to the page.
  if (!empty($GLOBALS['language']->javascript)) {

    // Add the translation JavaScript file to the page.
    $file = $dir . '/' . $GLOBALS['language']->language . '_' . $GLOBALS['language']->javascript . '.js';
    $javascript[$file] = drupal_js_defaults($file);
  }
}

/**
 * Callback for pre_render so elements can be modified before they are rendered.
 *
 * @param array $elements
 *   A render array containing:
 *   - #items: The JavaScript items as returned by drupal_add_js() and
 *     altered by drupal_get_js().
 *   - #group_callback: A function to call to group #items. Following
 *     this function, #aggregate_callback is called to aggregate items within
 *     the same group into a single file.
 *   - #aggregate_callback: A function to call to aggregate the items within
 *     the groups arranged by the #group_callback function.
 *
 * @return array
 *   A render array that will render to a string of JavaScript tags.
 */
function advagg_modify_js_pre_render(array $elements) {

  // Get the children elements.
  $children = array_intersect_key($elements, array_flip(element_children($elements)));

  // Allow other modules to modify $children and $elements before they are
  // rendered.
  // Call hook_advagg_modify_js_pre_render_alter()
  drupal_alter('advagg_modify_js_pre_render', $children, $elements);

  // Remove old children elements.
  foreach ($children as $key => $value) {
    if (isset($elements[$key])) {
      unset($elements[$key]);
    }
  }

  // Add in new children elements.
  $elements += $children;
  return $elements;
}

/**
 * Callback for pre_render so elements can be modified before they are rendered.
 *
 * @param array $elements
 *   A render array containing:
 *   - #items: The CSS items as returned by drupal_add_css() and
 *     altered by drupal_get_css().
 *   - #group_callback: A function to call to group #items. Following
 *     this function, #aggregate_callback is called to aggregate items within
 *     the same group into a single file.
 *   - #aggregate_callback: A function to call to aggregate the items within
 *     the groups arranged by the #group_callback function.
 *
 * @return array
 *   A render array that will render to a string of JavaScript tags.
 */
function advagg_modify_css_pre_render(array $elements) {
  if (!advagg_enabled()) {
    return $elements;
  }

  // Put children elements into a reference array.
  $children = array();
  foreach ($elements as $key => &$value) {
    if ($key !== '' && is_string($key) && 0 === strpos($key, '#')) {
      continue;
    }
    $children[$key] =& $value;
  }
  unset($value);

  // Allow other modules to modify $children and $elements before they are
  // rendered.
  // Call hook_advagg_modify_css_pre_render_alter()
  drupal_alter('advagg_modify_css_pre_render', $children, $elements);
  return $elements;
}

/**
 * Default callback to aggregate CSS files and inline content.
 *
 * Having the browser load fewer CSS files results in much faster page loads
 * than when it loads many small files. This function aggregates files within
 * the same group into a single file unless the site-wide setting to do so is
 * disabled (commonly the case during site development). To optimize download,
 * it also compresses the aggregate files by removing comments, whitespace, and
 * other unnecessary content. Additionally, this functions aggregates inline
 * content together, regardless of the site-wide aggregation setting.
 *
 * @param array $css_groups
 *   An array of CSS groups as returned by drupal_group_css(). This function
 *   modifies the group's 'data' property for each group that is aggregated.
 *
 * @see drupal_aggregate_css()
 * @see drupal_group_css()
 * @see drupal_pre_render_styles()
 * @see system_element_info()
 */
function _advagg_aggregate_css(array &$css_groups) {
  if (!advagg_enabled()) {
    return drupal_aggregate_css($css_groups);
  }
  if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
    $GLOBALS['_advagg']['debug']['css_groups_before'][] = $css_groups;
  }
  $preprocess_css = advagg_file_aggregation_enabled('css');

  // Allow other modules to modify $css_groups right before it is processed.
  // Call hook_advagg_css_groups_alter().
  drupal_alter('advagg_css_groups', $css_groups, $preprocess_css);

  // For each group that needs aggregation, aggregate its items.
  $files_to_aggregate = array();

  // Allow for inline CSS to be between aggregated files.
  $gap_counter = 0;
  foreach ($css_groups as $key => $group) {
    switch ($group['type']) {

      // If a file group can be aggregated into a single file, do so, and set
      // the group's data property to the file path of the aggregate file.
      case 'file':
        if ($group['preprocess'] && $preprocess_css) {
          $files_to_aggregate[$gap_counter][$key] = $group;
        }
        else {
          ++$gap_counter;
        }
        break;

      // Aggregate all inline CSS content into the group's data property.
      case 'inline':
        ++$gap_counter;
        $css_groups[$key]['data'] = '';
        foreach ($group['items'] as $item) {
          $css_groups[$key]['data'] .= advagg_load_stylesheet_content($item['data'], $item['preprocess']);
        }
        break;

      // Create a gap for external CSS.
      case 'external':
        ++$gap_counter;
        break;
    }
  }
  if (!empty($files_to_aggregate)) {
    $hooks_hash = advagg_get_current_hooks_hash();
    $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
    $css_hash = drupal_hash_base64($serialize_function($files_to_aggregate));
    $cache_id = 'advagg:css:' . $hooks_hash . ':' . $css_hash;
    if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && ($cache = cache_get($cache_id, 'cache_advagg_aggregates'))) {
      $plans = $cache->data;
    }
    else {
      module_load_include('inc', 'advagg', 'advagg');
      $plans = advagg_build_aggregate_plans($files_to_aggregate, 'css');
      if (!empty($plans) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) {
        cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY);
      }
    }
    $css_groups = advagg_merge_plans($css_groups, $plans);
  }
  if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
    $GLOBALS['_advagg']['debug']['css_groups_after'][] = $css_groups;
  }
}

/**
 * Default callback to aggregate JavaScript files.
 *
 * Having the browser load fewer JavaScript files results in much faster page
 * loads than when it loads many small files. This function aggregates files
 * within the same group into a single file unless the site-wide setting to do
 * so is disabled (commonly the case during site development). To optimize
 * download, it also compresses the aggregate files by removing comments,
 * whitespace, and other unnecessary content.
 *
 * @param array $js_groups
 *   An array of JavaScript groups as returned by drupal_group_js(). For each
 *   group that is aggregated, this function sets the value of the group's
 *   'data' key to the URI of the aggregate file.
 *
 * @see drupal_group_js()
 * @see drupal_pre_render_scripts()
 */
function _advagg_aggregate_js(array &$js_groups) {
  if (!advagg_enabled()) {
    if (function_exists('drupal_aggregate_js')) {
      return drupal_aggregate_js($js_groups);
    }
    else {
      return;
    }
  }
  if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
    $GLOBALS['_advagg']['debug']['js_groups_before'][] = $js_groups;
  }
  $preprocess_js = advagg_file_aggregation_enabled('js');

  // Allow other modules to modify $js_groups right before it is processed.
  // Call hook_advagg_js_groups_alter().
  drupal_alter('advagg_js_groups', $js_groups, $preprocess_js);

  // For each group that needs aggregation, aggregate its items.
  $files_to_aggregate = array();

  // Only aggregate when the site is configured to do so, and not during an
  // update.
  $gap_counter = 0;
  if ($preprocess_js) {

    // Set boolean to TRUE if all JS in footer.
    $all_in_footer = FALSE;
    if (module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) >= 2) {
      $all_in_footer = TRUE;
    }
    foreach ($js_groups as $key => &$group) {
      switch ($group['type']) {

        // If a file group can be aggregated into a single file, do so, and set
        // the group's data property to the file path of the aggregate file.
        case 'file':
          if (!empty($group['preprocess'])) {

            // Special handing for when all JS is in the footer.
            if ($all_in_footer && $group['scope'] === 'footer' && $group['group'] > 9000) {
              ++$gap_counter;
              $all_in_footer = FALSE;
            }
            $files_to_aggregate[$gap_counter][$key] = $group;
          }
          else {
            ++$gap_counter;
          }
          break;

        // Create a gap for inline JS.
        case 'inline':
          ++$gap_counter;
          break;

        // Create a gap for external JS.
        case 'external':
          ++$gap_counter;
          break;
      }
    }
    unset($group);
  }
  if (!empty($files_to_aggregate)) {
    $hooks_hash = advagg_get_current_hooks_hash();
    $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
    $js_hash = drupal_hash_base64($serialize_function($files_to_aggregate));
    $cache_id = 'advagg:js:' . $hooks_hash . ':' . $js_hash;
    if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && ($cache = cache_get($cache_id, 'cache_advagg_aggregates'))) {
      $plans = $cache->data;
    }
    else {
      module_load_include('inc', 'advagg', 'advagg');
      $plans = advagg_build_aggregate_plans($files_to_aggregate, 'js');
      if (!empty($plans) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) {
        cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY);
      }
    }
    $js_groups = advagg_merge_plans($js_groups, $plans);
  }
  if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
    $GLOBALS['_advagg']['debug']['js_groups_after'][] = $js_groups;
  }
}

/**
 * Builds the arrays needed for css rendering and caching.
 *
 * @param bool $skip_alter
 *   (Optional) If set to TRUE, this function skips calling drupal_alter() on
 *   css, useful for the aggressive cache.
 *
 * @return array
 *   Array contains the 2 arrays used for css.
 */
function _advagg_build_css_arrays_for_rendering($skip_alter = FALSE) {

  // Get the raw CSS variable.
  $raw_css = drupal_add_css();

  // Process and Sort css.
  $full_css = advagg_get_css($raw_css, $skip_alter);

  // Add attached js to drupal_add_js() function.
  if (!empty($full_css['#attached'])) {
    drupal_process_attached($full_css);

    // Remove #attached since it's been added to the javascript array now.
    unset($full_css['#attached']);
  }
  return array(
    $raw_css,
    $full_css,
  );
}

/**
 * Builds the arrays needed for js rendering and caching.
 *
 * @param bool $skip_alter
 *   (Optional) If set to TRUE, this function skips calling drupal_alter() on
 *   js, useful for the aggressive cache.
 *
 * @return array
 *   Array contains the 3 arrays used for javascript.
 */
function _advagg_build_js_arrays_for_rendering($skip_alter = FALSE) {

  // Get the raw JS variable.
  $javascript = drupal_add_js();

  // Process and Sort JS.
  $full_javascript = advagg_get_full_js($javascript, $skip_alter);

  // Get scopes used in the js.
  $scopes = advagg_get_js_scopes($full_javascript);

  // Add JS to the header and footer of the page.
  $js_scope_array = array();
  $js_scope_settings_array = array();
  foreach ($scopes as $scope => $use) {
    if (!$use) {

      // If the scope is not being used, skip it.
      continue;
    }

    // advagg_get_js() will sort the JavaScript so that it appears in the
    // correct order.
    $scripts = advagg_get_js($scope, $full_javascript);
    if (isset($scripts['#items']['settings'])) {

      // Get the js settings.
      $js_scope_settings_array[$scope]['settings'] = $scripts['#items']['settings'];

      // Exclude JS Settings from the array; we'll add it back later.
      $scripts['#items']['settings'] = array();
    }
    $js_scope_array[$scope] = $scripts;
  }

  // Fix settings; if more than 1 is set, use the largest one.
  if (count($js_scope_settings_array) > 1) {
    $max = -1;
    $max_scope = '';
    foreach ($js_scope_settings_array as $scope => $settings) {
      $count = count($settings);
      $max = max($max, $count);
      if ($max == $count) {
        $max_scope = $scope;
      }
    }
    foreach ($js_scope_settings_array as $scope => $settings) {
      if ($scope !== $max_scope) {
        unset($js_scope_settings_array[$scope]);
      }
    }
  }
  return array(
    $javascript,
    $js_scope_settings_array,
    $js_scope_array,
  );
}

/**
 * Returns TRUE if the CSS is being loaded via JavaScript.
 *
 * @param object $css_cache
 *   Cache object from cache_get().
 *
 * @return bool
 *   TRUE if CSS loaded via JS. FALSE if not.
 */
function advagg_css_in_js($css_cache = NULL) {
  if (module_exists('advagg_mod') && variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER)) {
    return TRUE;
  }
  if (module_exists('css_delivery') && css_delivery_enabled()) {
    return TRUE;
  }

  // Critical css added by another means.
  if (!empty($css_cache->data[1]['#items'])) {
    foreach ($css_cache->data[1]['#items'] as $values) {
      if (!empty($values['critical-css'])) {
        return TRUE;
      }
    }
  }
  return variable_get('advagg_css_in_js', ADVAGG_CSS_IN_JS);
}

/**
 * Given the full css and js scope array return back the render cache.
 *
 * @param array $full_css
 *   Array from advagg_get_css() with #attached removed because it was built by
 *   _advagg_build_css_arrays_for_rendering().
 * @param array $js_scope_array
 *   Array built from iterations of advagg_get_js() inside of
 *   _advagg_build_js_arrays_for_rendering().
 *
 * @return array
 *   Array containing the $css_cache, $js_cache, $css_cache_id, $js_cache_id.
 */
function advagg_get_render_cache(array $full_css, array $js_scope_array) {
  $cids = array();
  $css_cache_id = '';
  $js_cache_id = '';

  // Get advagg hash.
  $hooks_hash = advagg_get_current_hooks_hash();
  $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
  if (advagg_file_aggregation_enabled('css')) {

    // Generate css cache id.
    $cids[] = $css_cache_id = 'advagg:css:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($full_css));
  }
  if (advagg_file_aggregation_enabled('js')) {

    // Generate js cache id.
    $cids[] = $js_cache_id = 'advagg:js:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($js_scope_array));
  }
  if (!empty($cids)) {

    // Get the cached data.
    $cached_data = cache_get_multiple($cids, 'cache_advagg_aggregates');

    // Set variables from the cache.
    if (isset($cached_data[$css_cache_id])) {
      $css_cache = $cached_data[$css_cache_id];
    }
    if (isset($cached_data[$js_cache_id])) {
      $js_cache = $cached_data[$js_cache_id];
    }
  }

  // Special handling if the css is loaded via JS.
  if (!empty($css_cache) && empty($js_cache) && advagg_css_in_js($css_cache)) {

    // If CSS is being loaded via JavaScript and the css cache is set but the
    // js cache is not set; then unset the css cache as well.
    unset($css_cache);
  }

  // Set to empty arrays on a cache miss.
  if (!isset($css_cache)) {
    $css_cache = new stdClass();
  }
  if (!isset($js_cache)) {
    $js_cache = new stdClass();
  }
  return array(
    $css_cache,
    $js_cache,
    $css_cache_id,
    $js_cache_id,
  );
}

/**
 * Replacement for template_process_html().
 */
function _advagg_process_html(&$variables) {

  // Don't fail even if the menu router failed.
  if (drupal_get_http_header('status') === '404 Not Found') {

    // See if the URI contains advagg.
    $uri = request_uri();
    if (stripos($uri, '/advagg_') !== FALSE) {
      $advagg_items = advagg_menu();

      // Check css.
      $css = reset($advagg_items);
      $css_path = key($advagg_items);
      $css_path = substr($css_path, 0, strlen($css_path) - 1);
      $css_start = strpos($uri, $css_path);
      if ($css_start !== FALSE) {
        $filename = substr($uri, $css_start + strlen($css_path));
      }
      else {
        if (variable_get('advagg_weak_file_verification', ADVAGG_WEAK_FILE_VERIFICATION)) {
          $css_start = strpos($uri, '/' . basename($css_path) . '/');
          if ($css_start !== FALSE) {
            $filename = substr($uri, $css_start + strlen('/' . basename($css_path) . '/'));
          }
        }
      }

      // Check js.
      if (empty($filename)) {
        $js = next($advagg_items);
        $js_path = key($advagg_items);
        $js_path = substr($js_path, 0, strlen($js_path) - 1);
        $js_start = strpos($uri, $js_path);
        if ($js_start !== FALSE) {
          $filename = substr($uri, $js_start + strlen($js_path));
        }
        else {
          if (variable_get('advagg_weak_file_verification', ADVAGG_WEAK_FILE_VERIFICATION)) {
            $js_start = strpos($uri, '/' . basename($js_path) . '/');
            if ($js_start !== FALSE) {
              $filename = substr($uri, $js_start + strlen('/' . basename($js_path) . '/'));
            }
          }
        }
      }

      // If we have a filename call the page callback.
      if (!empty($filename)) {
        $router_item = $css;
        if (isset($js)) {
          $router_item = $js;
        }

        // Include the file if needed.
        if ($router_item['file']) {
          $included = module_load_include($router_item['file'], 'advagg');
          if (!$included && !function_exists($router_item['page callback'])) {
            $file = DRUPAL_ROOT . '/' . drupal_get_path('module', 'advagg') . '/' . $router_item['file'];
            if (is_file($file)) {
              require_once $file;
            }
          }
        }

        // Call the function.
        if (function_exists($router_item['page callback'])) {

          // Strip query and fragment form the filename.
          if ($pos = strpos($filename, '?')) {
            $filename = substr($filename, 0, $pos);
          }
          if ($pos = strpos($filename, '#')) {
            $filename = substr($filename, 0, $pos);
          }

          // Generate the file.
          call_user_func_array($router_item['page callback'], array(
            $filename,
          ));
        }
        else {

          // Report the bigger issue to watchdog.
          watchdog('advagg', 'You need to flush your menu cache. This can be done at the top of the <a href="@performance">performance page</a>. The advagg callback failed while trying to generate this file: @uri', array(
            '@performance' => url('admin/config/development/performance'),
            '@uri' => $uri,
          ), WATCHDOG_CRITICAL);
        }
      }
    }
  }
  if (!advagg_enabled()) {
    template_process_html($variables);
    return;
  }

  // Render page_top and page_bottom into top level variables.
  if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_top'])) {
    $variables['page_top'] = drupal_render($variables['page']['page_top']);
  }
  elseif (!isset($variables['page_top'])) {
    $variables['page_top'] = '';
  }
  if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_bottom'])) {
    $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']);
  }
  elseif (!isset($variables['page_bottom'])) {
    $variables['page_bottom'] = '';
  }

  // Place the rendered HTML for the page body into a top level variable.
  if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['#children'])) {
    $variables['page'] = $variables['page']['#children'];
  }
  $advagg_script_alt_scope_scripts = array();
  if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
    $prefix = "<!-- AdvAgg page:prefix tag -->";
    $suffix = "<!-- AdvAgg page:suffix tag -->";
    $variables['page'] = $prefix . $variables['page'] . $suffix;
    $prefix = "<!-- AdvAgg page_top:prefix tag -->";
    $suffix = "<!-- AdvAgg page_top:suffix tag -->";
    $variables['page_top'] = $prefix . $variables['page_top'] . $suffix;
    $prefix = "<!-- AdvAgg page_bottom:prefix tag -->";
    $suffix = "<!-- AdvAgg page_bottom:suffix tag -->";
    $variables['page_bottom'] = $prefix . $variables['page_bottom'] . $suffix;
    $matches = array();
    preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page_top'], $matches);
    $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts);
    preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page'], $matches);
    $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts);
    preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page_bottom'], $matches);
    $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts);
  }

  // Parts of drupal_get_html_head().
  $elements = drupal_add_html_head();
  if (is_callable('advagg_mod_html_head_post_alter')) {
    advagg_mod_html_head_post_alter($elements);
  }

  // Get default javascript.
  // @see http://drupal.org/node/1279226
  if (function_exists('drupal_add_js_page_defaults')) {
    drupal_add_js_page_defaults();
  }
  $javascript = array();

  // Try the render cache.
  if (!variable_get('advagg_debug', ADVAGG_DEBUG)) {

    // No Alter.
    if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5 && !module_exists('advagg_relocate')) {

      // Get all CSS and JS variables needed; running no alters.
      list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(TRUE);
      list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(TRUE);

      // Get the render cache.
      list($css_cache, $js_cache, $css_cache_id_no_alter, $js_cache_id_no_alter) = advagg_get_render_cache($full_css, $js_scope_array);
    }

    // With Alter.
    if ((empty($css_cache->data) || empty($js_cache->data)) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {

      // Get all CSS and JS variables needed; running alters.
      list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering();
      list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering();

      // Get the render cache.
      list($css_cache, $js_cache, $css_cache_id, $js_cache_id) = advagg_get_render_cache($full_css, $js_scope_array);
    }
  }

  // CSS has nice hooks so we don't need to work around it.
  if (!empty($css_cache->data)) {

    // Use render cache.
    list($variables['styles'], $full_css) = $css_cache->data;
  }
  else {

    // Get the css if we have not done so.
    if (empty($full_css)) {
      list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering();
    }

    // Render the CSS; advagg_pre_render_styles() gets called here.
    $variables['styles'] = drupal_render($full_css);
    if (!empty($css_cache_id) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {

      // Save to the cache.
      cache_set($css_cache_id, array(
        $variables['styles'],
        $full_css,
      ), 'cache_advagg_aggregates', CACHE_TEMPORARY);
    }
    if (!empty($css_cache_id_no_alter) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) {

      // Save to the cache.
      cache_set($css_cache_id_no_alter, array(
        $variables['styles'],
        $full_css,
      ), 'cache_advagg_aggregates', CACHE_TEMPORARY);
    }
  }
  if (module_exists('advagg_font') && variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER)) {
    $fonts = array();
    foreach ($full_css['#groups'] as $groups) {
      if (isset($groups['items']['files'])) {
        foreach ($groups['items']['files'] as $file) {
          if (isset($file['advagg_font'])) {
            foreach ($file['advagg_font'] as $class => $name) {
              $fonts[$class] = $name;
            }
          }
        }
      }
    }
    if (!empty($fonts)) {
      if (isset($js_scope_settings_array)) {
        $key = key($js_scope_settings_array);
        $js_scope_settings_array[$key]['settings']['data'][] = array(
          'advagg_font' => $fonts,
        );
      }
      drupal_add_js(array(
        'advagg_font' => $fonts,
      ), array(
        'type' => 'setting',
      ));
    }
  }
  if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {
    foreach ($full_css['#groups'] as $groups) {
      if (empty($groups['data']) || $groups['type'] === 'inline') {
        continue;
      }
      advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'style');
    }
  }

  // JS needs hacks.
  // Clear out all old scripts.
  if (variable_get('advagg_clear_scripts', ADVAGG_CLEAR_SCRIPTS)) {
    $variables['scripts'] = '';
  }
  if (!isset($variables['scripts'])) {
    $variables['scripts'] = '';
  }
  if (!isset($variables['page_bottom']) || !is_string($variables['page_bottom'])) {
    $variables['page_bottom'] = '';
  }
  $use_cache = FALSE;
  if (!empty($js_cache->data) && !variable_get('advagg_debug', ADVAGG_DEBUG)) {

    // Use render cache.
    $use_cache = TRUE;
    $add_to_variables = array();

    // Replace cached settings with current ones.
    $js_settings_used = array();
    $js_scope_settings_array_copy = $js_scope_settings_array;
    if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) {
      if (!empty($js_scope_settings_array_copy['header']) && empty($js_scope_settings_array_copy['footer'])) {

        // Copy header settings into the footer.
        $js_scope_settings_array_copy['footer'] = $js_scope_settings_array_copy['header'];
      }
    }
    list($js_cache_data, $js_scope_array) = $js_cache->data;
    foreach ($js_cache_data as $scope => $value) {
      $scope_settings = $scope;
      if ($scope_settings === 'scripts') {
        $scope_settings = 'header';
      }
      if ($scope === 'page_bottom') {
        $scope_settings = 'footer';
      }

      // Search $value for Drupal.settings.
      $start = strpos($value, 'jQuery.extend(Drupal.settings,');
      if ($start !== FALSE) {

        // If the cache and current settings scope's do not match; do not use
        // the cached version.
        if (!isset($js_scope_settings_array_copy[$scope_settings]['settings'])) {
          $use_cache = FALSE;
          break;
        }

        // Replace cached Drupal.settings with current Drupal.settings for this
        // page.
        $merged = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($js_scope_settings_array_copy[$scope_settings]['settings']['data'], 'is_array')));
        $json_data = advagg_json_encode($merged);
        if (!empty($json_data)) {

          // Record that this is being used.
          $js_settings_used[$scope_settings] = TRUE;

          // Replace the drupal settings string.
          $value = advagg_replace_drupal_settings_string($value, $json_data);
        }
      }
      $add_to_variables[$scope] = $value;
    }
    if ($use_cache) {
      $all_used = array_diff(array_keys($js_scope_settings_array_copy), array_keys($js_settings_used));

      // Ignore this check if the cache level is less than 5.
      if (!empty($all_used) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 5 && !empty($js_settings_used)) {

        // Some js settings did not make it into the output. Skip cache.
        $use_cache = FALSE;
      }
    }
    if ($use_cache) {

      // Using the cache; write to the $variables array.
      foreach ($add_to_variables as $scope => $value) {

        // Set the scope variable if not set.
        if (!isset($variables[$scope]) || !is_string($variables[$scope])) {
          $variables[$scope] = '';
        }

        // Append the js to the scope.
        $variables[$scope] .= $value;
      }
    }
  }

  // If the cache isn't used.
  if (!$use_cache) {
    if (!empty($js_cache->data) && !empty($css_cache->data) && advagg_css_in_js($css_cache)) {

      // Render the css so it will be added to the js array;
      // advagg_pre_render_styles() gets called here.
      $variables['styles'] = drupal_render($full_css);
    }

    // Check if the js has changed.
    $new_js = drupal_add_js();
    $diff = array_diff(array_keys($new_js), array_keys($javascript));
    if (!empty($diff) || empty($javascript)) {

      // Get all JS variables needed again because js changed; or because we
      // never got them in the first place.
      list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering();
    }
    $js_cache = array();
    $js_cache['scripts'] = '';
    if (!empty($js_scope_array)) {

      // Add JS to the header and footer of the page.
      foreach ($js_scope_array as $scope => &$scripts_array) {

        // Add js settings.
        if (!empty($js_scope_settings_array[$scope]['settings'])) {
          $scripts_array['#items']['settings'] = $js_scope_settings_array[$scope]['settings'];
        }

        // Render js; advagg_pre_render_scripts() gets called here.
        $scripts = drupal_render($scripts_array);
        if ($scope === 'header') {

          // Add to the top of this section.
          $variables['scripts'] = $scripts . $variables['scripts'];
          $js_cache['scripts'] = $scripts . $js_cache['scripts'];
        }
        elseif ($scope === 'footer') {

          // Add to the bottom of this section.
          $variables['page_bottom'] .= $scripts;
          $js_cache['page_bottom'] = $scripts;
        }
        elseif ($scope === 'above_css') {

          // Put in this new section.
          $variables['above_css'] = $scripts;
          $js_cache['above_css'] = $scripts;
        }
        elseif (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {

          // Scripts in other places.
          if (isset($variables[$scope]) && is_string($variables[$scope]) && array_key_exists($scope, $GLOBALS['theme_info']->info['regions'])) {

            // Add to the bottom of this section.
            $variables[$scope] .= $scripts;
            $js_cache[$scope] = $scripts;
          }
          elseif (array_search($scope, $advagg_script_alt_scope_scripts, TRUE) !== FALSE) {

            // Add to the inline html.
            $pos_page_top = strpos($variables['page_top'], "<!-- AdvAgg {$scope} tag -->");
            $pos_page = strpos($variables['page'], "<!-- AdvAgg {$scope} tag -->");
            $pos_page_bottom = strpos($variables['page_bottom'], "<!-- AdvAgg {$scope} tag -->");
            if ($pos_page_top !== FALSE) {
              $pos_page_top += strlen("<!-- AdvAgg {$scope} tag -->");
              $variables['page_top'] = substr_replace($variables['page_top'], "\n{$scripts}", $pos_page_top, 0);
              $js_cache[$scope] = $scripts;
            }
            elseif ($pos_page !== FALSE) {
              $pos_page += strlen("<!-- AdvAgg {$scope} tag -->");
              $variables['page'] = substr_replace($variables['page'], "\n{$scripts}", $pos_page, 0);
              $js_cache[$scope] = $scripts;
            }
            elseif ($pos_page_bottom !== FALSE) {
              $pos_page_bottom += strlen("<!-- AdvAgg {$scope} tag -->");
              $variables['page_bottom'] = substr_replace($variables['page_bottom'], "\n{$scripts}", $pos_page_bottom, 0);
              $js_cache[$scope] = $scripts;
            }
          }
          elseif (strpos($scope, ':') === FALSE) {

            // Add to the bottom of this section.
            $variables['scripts'] .= $scripts;
            $js_cache['scripts'] .= $scripts;
          }
        }
      }
      unset($scripts_array);

      // Clear drupal settings so cache is smaller.
      foreach ($js_cache as &$string) {
        $string = advagg_replace_drupal_settings_string($string, '{}');
      }
      unset($string);

      // Clear drupal settings and not needed items from render cache.
      $js_scope_array = array_intersect_key($js_scope_array, array_flip(element_children($js_scope_array)));
      foreach ($js_scope_array as $scope => &$scripts_array) {

        // Clear element children.
        $scripts_array = array_diff_key($scripts_array, array_flip(element_children($scripts_array)));
        if (isset($scripts_array['#children'])) {
          unset($scripts_array['#children']);
        }

        // Clear drupal settings.
        if (isset($scripts_array['#items']['settings']['data']) && is_array($scripts_array['#items']['settings']['data'])) {
          $scripts_array['#items']['settings']['data'] = array();
        }

        // Clear printed keys.
        if (isset($scripts_array['#printed'])) {
          unset($scripts_array['#printed']);
        }

        // Clear not used groups.
        foreach ($scripts_array['#groups'] as $key => $groups) {
          if (!isset($groups['items']['files'])) {
            unset($scripts_array['#groups'][$key]);
          }
        }
      }
      unset($scripts_array);
      if (!empty($js_cache_id) && !empty($js_cache) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {
        cache_set($js_cache_id, array(
          $js_cache,
          $js_scope_array,
        ), 'cache_advagg_aggregates', CACHE_TEMPORARY);
      }
      if (!empty($js_cache_id_no_alter) && !empty($js_cache) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) {
        cache_set($js_cache_id_no_alter, array(
          $js_cache,
          $js_scope_array,
        ), 'cache_advagg_aggregates', CACHE_TEMPORARY);
      }
    }
  }
  if (!empty($variables['above_css'])) {
    $variables['styles'] = $variables['above_css'] . $variables['styles'];
  }
  if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {
    foreach ($js_scope_array as $scope => &$scripts_array) {
      if ($scope !== 'header' && $scope !== 'footer' && $scope !== 'above_css' && !variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
        continue;
      }
      foreach ($scripts_array['#groups'] as $groups) {
        if (empty($groups['data']) || $groups['type'] === 'inline') {
          continue;
        }
        advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'script');
      }
    }
  }
  $head_elements_before = drupal_add_html_head();
  if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {

    // Prefetch css domains.
    foreach ($full_css['#items'] as $file) {
      advagg_add_resource_hints_array($file);
    }
    foreach ($full_css['#groups'] as $groups) {
      if (isset($groups['items']['files'])) {
        foreach ($groups['items']['files'] as $file) {
          advagg_add_resource_hints_array($file);
        }
      }
    }

    // Prefetch js domains.
    foreach ($js_scope_array as $scope_js) {
      foreach ($scope_js['#items'] as $file) {
        advagg_add_resource_hints_array($file);
      }
      if (isset($scope_js['#groups'])) {
        foreach ($scope_js['#groups'] as $groups) {
          if (isset($groups['items']['files'])) {
            foreach ($groups['items']['files'] as $file) {
              advagg_add_resource_hints_array($file);
            }
          }
        }
      }
    }
  }

  // Add in preload link headers.
  advagg_add_preload_header();

  // Add in the headers added by advagg.
  $head_elements_after = drupal_add_html_head();
  $elements += array_diff_key($head_elements_after, $head_elements_before);

  // Parts of drupal_get_html_head().
  drupal_alter('html_head', $elements);
  $head = drupal_render($elements);
  if (variable_get('advagg_html_head_in_css_location', ADVAGG_HTML_HEAD_IN_CSS_LOCATION)) {
    $variables['styles'] = $head . $variables['styles'];
    $variables['head'] = '';
  }
  else {
    $variables['head'] = $head;
  }

  // Remove AdvAgg comments.
  if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE) && !empty($advagg_script_alt_scope_scripts) && !variable_get('theme_debug', FALSE)) {
    $variables['page_top'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page_top']);
    $variables['page'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page']);
    $variables['page_bottom'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page_bottom']);
  }

  // Output debug info.
  if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
    $debug = $GLOBALS['_advagg']['debug'];
    if (is_callable('httprl_pr')) {
      $output = ' ' . httprl_pr($debug);
    }
    else {
      $output = '<pre>' . str_replace(array(
        '<',
        '>',
      ), array(
        '&lt;',
        '&gt;',
      ), print_r($debug, TRUE)) . '</pre>';
    }
    watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG);
  }
}

/**
 * Replace inline drupal settings script.
 *
 * @param string $subject
 *   Inline js.
 * @param string $replace
 *   JS settings replacement.
 *
 * @return string
 *   Returns the subject with the replacement in place if this is a drupal
 *   settings json blob.
 */
function advagg_replace_drupal_settings_string($subject, $replace) {
  $start = strpos($subject, 'jQuery.extend(Drupal.settings,');
  if ($start === FALSE) {
    return $subject;
  }

  // Find the end of the Drupal.settings.
  $script_end = stripos($subject, '</script>', $start);
  $settings_substring = substr($subject, $start, $script_end - $start);
  $json_end = strripos($settings_substring, '});');

  // Check if LABjs has added an additional wrapper around Drupal settings.
  $script_tag_start = strripos(substr($subject, 0, $start), '<script');
  if (strpos(substr($subject, $script_tag_start, $start), '$L.wait(') !== FALSE) {

    // Refine JSON end position.
    $_json_end = strripos(substr($settings_substring, 0, $json_end), '});');
    if ($_json_end !== FALSE) {
      $json_end = $_json_end;
    }
  }

  // Replace Drupal.settings json.
  $subject = substr($subject, 0, $start + 30) . $replace . substr($subject, $json_end + $start + 1);
  return $subject;
}

/**
 * Shrink the ajaxPageState data.
 *
 * @param array $data
 *   Settings for javascript.
 */
function advagg_cleanup_settings_array(array $data) {

  // Remove inline js from the ajaxPageState data.
  if (isset($data['ajaxPageState']['js'])) {
    foreach ((array) $data['ajaxPageState']['js'] as $key => $value) {
      if (advagg_remove_short_keys($key)) {
        if (is_array($data['ajaxPageState']['js']) && isset($data['ajaxPageState']['js'][$key])) {
          unset($data['ajaxPageState']['js'][$key]);
        }
        elseif (is_object($data['ajaxPageState']['js']) && isset($data['ajaxPageState']['js']->{$key})) {
          unset($data['ajaxPageState']['js']->{$key});
        }
      }
    }
  }

  // Remove inline css from the ajaxPageState data.
  if (isset($data['ajaxPageState']['css'])) {
    foreach ((array) $data['ajaxPageState']['css'] as $key => $value) {
      if (advagg_remove_short_keys($key, 6)) {
        if (is_object($data['ajaxPageState']['css']) && isset($data['ajaxPageState']['css']->{$key})) {
          unset($data['ajaxPageState']['css']->{$key});
        }
        elseif (is_array($data['ajaxPageState']['css']) && isset($data['ajaxPageState']['css'][$key])) {
          unset($data['ajaxPageState']['css'][$key]);
        }
      }
    }
  }

  // Remove settings from the js ajaxPageState data.
  if (isset($data['ajaxPageState']['js']['settings'])) {
    unset($data['ajaxPageState']['js']['settings']);
  }
  if (isset($data['ajaxPageState']['js']->settings)) {
    unset($data['ajaxPageState']['js']->settings);
  }
  return $data;
}

/**
 * Find dns_prefetch and call advagg_add_dns_prefetch().
 *
 * @param array $values
 *   Attributes added via code for the file.
 */
function advagg_add_resource_hints_array(array $values) {
  if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) {
    if (!empty($values['type']) && ($values['type'] === 'external' || $values['type'] === 'file')) {

      // Get external domains.
      advagg_add_dns_prefetch($values['data']);
    }
    if (!empty($values['dns_prefetch'])) {

      // Grab domains that will be access when this file is loaded.
      if (is_array($values['dns_prefetch'])) {
        foreach ($values['dns_prefetch'] as $url) {
          advagg_add_dns_prefetch($url);
        }
      }
      else {
        advagg_add_dns_prefetch($values['dns_prefetch']);
      }
    }
  }
  if (!empty($values['preload']) && variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {
    if (is_array($values['preload'])) {
      foreach ($values['preload'] as $url) {
        advagg_add_preload_header($url);
      }
    }
    else {
      advagg_add_preload_header($values['preload']);
    }
  }
}

/**
 * Add in the dns-prefetch header for CSS and JS external files.
 *
 * @param string $url
 *   The url of the external host.
 *
 * @return bool
 *   TRUE if it was added to the head.
 */
function advagg_add_dns_prefetch($url) {

  // Keep the order.
  $advagg_resource_hints_location = variable_get('advagg_resource_hints_location', ADVAGG_RESOURCE_HINTS_LOCATION);
  static $weight = -1001;
  if ($advagg_resource_hints_location == 3) {
    $weight = -999.9;
  }
  $weight += 0.0001;

  // Get the host.
  $parse = @parse_url($url);
  if (empty($parse['host'])) {

    // If just the hostname was given, build proper url.
    if (strpos($url, '.') && strpos($url, '/') === FALSE) {
      $parse['scheme'] = '//';
      $parse['host'] = $url;

      // Check for fragment.
      $pos = strpos($url, '#');
      if ($pos !== FALSE) {
        $parse['fragment'] = substr($url, $pos + 1);
        $parse['host'] = substr($url, 0, $pos);
      }

      // Put it back together and parse again.
      $url = advagg_glue_url($parse);
      $parse = @parse_url($url);
    }
    if (empty($parse['host'])) {
      return FALSE;
    }
  }

  // Filter out wrong schemes.
  if (!empty($parse['scheme']) && $parse['scheme'] !== 'http' && $parse['scheme'] !== 'https') {
    return FALSE;
  }

  // Filter out local host.
  $host = @parse_url($GLOBALS['base_root'], PHP_URL_HOST);
  if ($parse['host'] === $host) {
    return FALSE;
  }

  // Add DNS information for more domains.
  if (strpos($parse['host'], 'fonts.googleapis.com') !== FALSE) {

    // Add fonts.gstatic.com when fonts.googleapis.com is added.
    advagg_add_dns_prefetch('https://fonts.gstatic.com/#crossorigin');
  }

  // Build render array.
  if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH)) {
    $element = array(
      '#type' => 'html_tag',
      '#tag' => 'link',
      '#attributes' => array(
        'rel' => 'dns-prefetch',
        'href' => '//' . $parse['host'],
      ),
      '#weight' => $weight,
    );

    // Add markup for dns-prefetch to html_head.
    drupal_add_html_head($element, 'advagg_resource_hints_dns_prefetch:' . $parse['host']);
  }
  if (variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) {

    // HTTPS use Protocol Relative; HTTP and scheme defined use given scheme.
    $href = '//' . $parse['host'];
    if (!$GLOBALS['is_https'] && isset($parse['scheme'])) {
      $href = "{$parse['scheme']}://{$parse['host']}";
    }
    $element = array(
      '#type' => 'html_tag',
      '#tag' => 'link',
      '#attributes' => array(
        'rel' => 'preconnect',
        'href' => $href,
      ),
      '#weight' => $weight,
    );
    if (!empty($parse['fragment']) && $parse['fragment'] === 'crossorigin') {
      $element['#attributes']['crossorigin'] = '';
    }

    // Add markup for dns-prefetch to html_head.
    drupal_add_html_head($element, 'advagg_resource_hints_preconnect:' . $parse['host']);
  }

  // Build render array. Goes after charset tag.
  if (!empty($parse['fragment']) && $parse['fragment'] === 'prefetch') {

    // Hacky way to open up a connection to the remote host.
    $element = array(
      '#type' => 'html_tag',
      '#tag' => 'link',
      '#attributes' => array(
        'rel' => 'prefetch',
        'href' => '//' . $parse['host'] . '/robots.txt',
      ),
      '#weight' => $weight,
    );
    drupal_add_html_head($element, 'advagg_prefetch:' . $parse['host']);
  }
  return TRUE;
}

/**
 * Returns a themed representation of all stylesheets to attach to the page.
 *
 * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
 * This ensures proper cascading of styles so themes can easily override
 * module styles through CSS selectors.
 *
 * Themes may replace module-defined CSS files by adding a stylesheet with the
 * same filename. For example, themes/bartik/system-menus.css would replace
 * modules/system/system-menus.css. This allows themes to override complete
 * CSS files, rather than specific selectors, when necessary.
 *
 * If the original CSS file is being overridden by a theme, the theme is
 * responsible for supplying an accompanying RTL CSS file to replace the
 * module's.
 *
 * @param array $css
 *   (Optional) An array of CSS files. If no array is provided, the default
 *   stylesheets array is used instead.
 * @param bool $skip_alter
 *   (Optional) If set to TRUE, this function skips calling drupal_alter() on
 *   $css, useful when the calling function passes a $css array that has already
 *   been altered.
 *
 * @return array
 *   An array ready to be passed into drupal_render().
 *
 * @see drupal_add_css()
 */
function advagg_get_css(array $css = array(), $skip_alter = FALSE) {
  if (empty($css)) {
    $css = drupal_add_css();
  }

  // Allow modules and themes to alter the CSS items.
  if (!$skip_alter) {
    advagg_add_default_dns_lookups($css, 'css');

    // Call hook_css_alter().
    drupal_alter('css', $css);

    // Call hook_css_post_alter().
    drupal_alter('css_post', $css);

    // Call these advagg functions after the hook_css_alter was called.
    advagg_fix_type($css, 'css');
  }

  // Sort CSS items, so that they appear in the correct order.
  advagg_drupal_sort_css_js_stable($css);

  // Provide the page with information about the individual CSS files used,
  // information not otherwise available when CSS aggregation is enabled. The
  // setting is attached later in this function, but is set here, so that CSS
  // files removed below are still considered "used" and prevented from being
  // added in a later AJAX request.
  // Skip if no files were added to the page or jQuery.extend() will overwrite
  // the Drupal.settings.ajaxPageState.css object with an empty array.
  if (!empty($css)) {

    // Cast the array to an object to be on the safe side even if not empty.
    $setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1);
  }

  // Remove the overridden CSS files. Later CSS files override former ones.
  $previous_item = array();
  foreach ($css as $key => $item) {
    if ($item['type'] == 'file') {

      // If defined, force a unique basename for this file.
      $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']);
      if (isset($previous_item[$basename])) {

        // Remove the previous item that shared the same base name.
        unset($css[$previous_item[$basename]]);
      }
      $previous_item[$basename] = $key;
    }
  }

  // Remove empty files.
  advagg_remove_empty_files($css);

  // Render the HTML needed to load the CSS.
  $styles = array(
    '#type' => 'styles',
    '#items' => $css,
  );
  if (!empty($setting)) {
    $styles['#attached']['js'][] = array(
      'type' => 'setting',
      'data' => $setting,
    );
  }
  return $styles;
}

/**
 * Get full JS array.
 *
 * Note that hook_js_alter(&$javascript) is called during this function call
 * to allow alterations of the JavaScript during its presentation. Calls to
 * drupal_add_js() from hook_js_alter() will not be added to the output
 * presentation. The correct way to add JavaScript during hook_js_alter()
 * is to add another element to the $javascript array, deriving from
 * drupal_js_defaults(). See locale_js_alter() for an example of this.
 *
 * @param array $javascript
 *   (optional) An array with all JavaScript code. Defaults to the default
 *   JavaScript array for the given scope.
 * @param bool $skip_alter
 *   (optional) If set to TRUE, this function skips calling drupal_alter() on
 *   $javascript, useful when the calling function passes a $javascript array
 *   that has already been altered.
 *
 * @return array
 *   The raw JavaScript array.
 *
 * @see drupal_add_js()
 * @see locale_js_alter()
 * @see drupal_js_defaults()
 */
function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) {
  if (empty($javascript)) {
    $javascript = drupal_add_js();
  }

  // Return an empty array if
  // no javascript is used,
  // only the settings array is used and scope is header.
  if (empty($javascript) || isset($javascript['settings']) && count($javascript) == 1) {
    return array();
  }

  // Allow modules to alter the JavaScript.
  if (!$skip_alter) {
    advagg_add_default_dns_lookups($javascript, 'js');
    if (is_callable('advagg_mod_js_pre_alter')) {
      advagg_mod_js_pre_alter($javascript);
    }

    // Call hook_js_alter().
    drupal_alter('js', $javascript);

    // Call hook_js_post_alter().
    drupal_alter('js_post', $javascript);

    // Call these advagg functions after the hook_js_alter was called.
    advagg_fix_type($javascript, 'js');
  }
  elseif (is_callable('advagg_mod_js_move_to_footer')) {
    if (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 3) {
      advagg_mod_js_move_to_footer($javascript);
    }
  }

  // If in development mode make sure the ajaxPageState css is there.
  if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
    $have_css = FALSE;
    foreach ($javascript['settings']['data'] as $setting) {
      if (!empty($setting['ajaxPageState']['css'])) {
        $have_css = TRUE;
        break;
      }
    }
    if (!$have_css) {
      $css = drupal_add_css();
      if (!empty($css)) {

        // Cast the array to an object to be on the safe side even if not empty.
        $javascript['settings']['data'][]['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1);
      }
    }
  }

  // Remove empty files.
  advagg_remove_empty_files($javascript);
  return $javascript;
}

/**
 * Returns a themed presentation of all JavaScript code for the current page.
 *
 * References to JavaScript files are placed in a certain order: first, all
 * 'core' files, then all 'module' and finally all 'theme' JavaScript files
 * are added to the page. Then, all settings are output, followed by 'inline'
 * JavaScript code. If running update.php, all preprocessing is disabled.
 *
 * Note that hook_js_alter(&$javascript) is called during this function call
 * to allow alterations of the JavaScript during its presentation. Calls to
 * drupal_add_js() from hook_js_alter() will not be added to the output
 * presentation. The correct way to add JavaScript during hook_js_alter()
 * is to add another element to the $javascript array, deriving from
 * drupal_js_defaults(). See locale_js_alter() for an example of this.
 *
 * @param string $scope
 *   (optional) The scope for which the JavaScript rules should be returned.
 *   Defaults to 'header'.
 * @param array $javascript
 *   (optional) An array with all JavaScript code. Defaults to the default
 *   JavaScript array for the given scope.
 * @param bool $ajax
 *   (optional) If set to TRUE, this function will not output Drupal.settings.
 *
 * @return array
 *   An array ready to be passed into drupal_render() containing all JavaScript
 *   code segments and includes for the scope as HTML tags.
 *
 * @see drupal_add_js()
 * @see locale_js_alter()
 * @see drupal_js_defaults()
 */
function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = FALSE) {

  // Keep track of js added for ajaxPageState.
  $page_state =& drupal_static(__FUNCTION__, array());

  // Add in javascript if none was passed in.
  if (empty($javascript) && !$ajax) {
    $javascript = advagg_get_full_js();
  }

  // Return an empty array if no javascript is used.
  if (empty($javascript)) {
    return array();
  }

  // Filter out elements of the given scope.
  $items = array();
  foreach ($javascript as $key => $item) {
    if (!empty($item['scope']) && $item['scope'] === $scope) {
      $items[$key] = $item;
    }
  }

  // Sort the JavaScript so that it appears in the correct order.
  advagg_drupal_sort_css_js_stable($items);

  // In Drupal 8, there's a JS_SETTING group for making setting variables
  // appear last after libraries have loaded. In Drupal 7, this is forced
  // without that group. We do not use the $key => $item type of iteration,
  // because PHP uses an internal array pointer for that, and we're modifying
  // the array order inside the loop.
  if ($scope === 'footer' && !empty($items['settings'])) {

    // Remove settings array from items.
    $settings_js['settings'] = $items['settings'];
    unset($items['settings']);

    // Move $settings_js to the bottom of the js that was added to the
    // header, but has now been moved to the footer via advagg_mod.
    $counter = 0;
    foreach ($items as $key => $item) {
      if ($item['group'] > 9000) {
        advagg_array_splice_assoc($items, $counter, 0, $settings_js);
        unset($settings_js);
        break;
      }
      ++$counter;
    }

    // Nothing in the footer, add settings to the bottom of the array.
    if (isset($settings_js)) {
      $items = array_merge($items, $settings_js);
    }
  }
  else {
    foreach (array_keys($items) as $key) {
      if ($items[$key]['type'] === 'setting') {
        $item = $items[$key];
        unset($items[$key]);
        $items[$key] = $item;
      }
    }
  }

  // Provide the page with information about the individual JavaScript files
  // used, information not otherwise available when aggregation is enabled.
  $page_state = array_merge($page_state, array_fill_keys(array_keys($items), 1));

  // If we're outputting the header scope, then this should be the final time
  // that drupal_get_js() is running, so add the setting to this output as well
  // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
  // because drupal_get_js() was intentionally passed a $javascript argument
  // stripped of settings, potentially in order to override how settings get
  // output, so in this case, do not add the setting to this output.
  // Also output the settings if we have pushed all javascript to the footer.
  if (isset($items['settings'])) {
    $items['settings']['data'][] = array(
      'ajaxPageState' => array(
        'js' => $page_state,
      ),
    );
  }

  // Do not include jQuery.extend(Drupal.settings) if the output is ajax.
  if ($ajax) {
    unset($items['settings']['data']);
  }

  // Semi support of the attributes array.
  foreach ($items as $key => $item) {
    if (!isset($item['attributes'])) {
      continue;
    }
    if (isset($item['attributes']['defer'])) {
      $items[$key]['defer'] = $item['attributes']['defer'];
    }
    if (isset($item['attributes']['async'])) {
      $items[$key]['async'] = $item['attributes']['async'];
    }
    if (isset($item['attributes']['onload'])) {
      $items[$key]['onload'] = $item['attributes']['onload'];
    }
    if (isset($item['attributes']['onerror'])) {
      $items[$key]['onerror'] = $item['attributes']['onerror'];
    }
  }

  // Render the HTML needed to load the JavaScript.
  $elements = array(
    '#type' => 'scripts',
    '#items' => $items,
  );

  // Aurora and Omega themes uses alter without checking previous value.
  if (variable_get('advagg_enforce_scripts_callback', TRUE)) {

    // Get the element_info for scripts.
    $scripts = element_info('scripts');
    if (empty($scripts) || $scripts['#aggregate_callback'] !== '_advagg_aggregate_js') {

      // Directly alter the static.
      $element_info =& drupal_static('element_info');
      advagg_element_info_alter($element_info);
      if (function_exists('advagg_mod_element_info_alter')) {
        advagg_mod_element_info_alter($element_info);
      }
    }
  }

  // Remove ajaxPageState CSS/JS from Drupal.settings if ajax.js is not used.
  if (function_exists('advagg_mod_js_no_ajaxpagestate')) {
    if (variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE)) {
      advagg_mod_js_no_ajaxpagestate($elements);
    }
  }
  return $elements;
}

/**
 * Remove a portion of the array and replace it with something else.
 *
 * @param array $input
 *   The input array.
 * @param int $offset
 *   If offset is positive then the start of removed portion is at that offset
 *   from the beginning of the input array. If offset is negative then it starts
 *   that far from the end of the input array.
 * @param int $length
 *   If length is omitted, removes everything from offset to the end of the
 *   array. If length is specified and is positive, then that many elements will
 *   be removed. If length is specified and is negative then the end of the
 *   removed portion will be that many elements from the end of the array. Tip:
 *   to remove everything from offset to the end of the array when replacement
 *   is also specified, use count($input) for length.
 * @param mixed $replacement
 *   If replacement array is specified, then the removed elements are replaced
 *   with elements from this array.
 *   If offset and length are such that nothing is removed, then the elements
 *   from the replacement array are inserted in the place specified by the
 *   offset. Note that keys in replacement array are preserved.
 *   If replacement is just one element it is not necessary to put array()
 *   around it, unless the element is an array itself, an object or NULL.
 *
 * @see http://php.net/array-splice#111204
 */
function advagg_array_splice_assoc(array &$input, $offset, $length, $replacement) {
  $replacement = (array) $replacement;
  $key_indices = array_flip(array_keys($input));
  if (isset($input[$offset]) && is_string($offset)) {
    $offset = $key_indices[$offset];
  }
  if (isset($input[$length]) && is_string($length)) {
    $length = $key_indices[$length] - $offset;
  }
  $input = array_slice($input, 0, $offset, TRUE) + $replacement + array_slice($input, $offset + $length, NULL, TRUE);
}

/**
 * Callback for array_filter. Will return FALSE if strlen < 3.
 *
 * @param string $value
 *   A value from an array/object.
 * @param int $min_len
 *   The strlen check length.
 *
 * @return bool
 *   TRUE or FALSE.
 */
function advagg_remove_short_keys($value, $min_len = 3) {
  if (strlen($value) < $min_len) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Get all javascript scopes set in the $javascript array.
 *
 * @param array $javascript
 *   An array with all JavaScript code.
 *
 * @return array
 *   Array of scopes that are currently being used.
 */
function advagg_get_js_scopes(array $javascript) {

  // Return if nothing given to us.
  if (empty($javascript)) {
    return array();
  }

  // Filter out elements of the given scope.
  $scopes = array();
  $js_settings_in_footer = FALSE;
  foreach ($javascript as $name => $item) {

    // Skip if the scope is not set.
    if (!is_array($item) || empty($item['scope'])) {
      continue;
    }
    if (!isset($scopes[$item['scope']])) {
      $scopes[$item['scope']] = TRUE;
    }
    if ($name === 'settings' && $item['scope'] === 'footer') {
      $js_settings_in_footer = TRUE;
    }
  }

  // Default to header if nothing found.
  if (empty($scopes)) {
    $scopes['header'] = TRUE;
  }

  // Process header last.
  if (isset($scopes['header']) && count($scopes) > 1) {
    $temp = $scopes['header'];
    unset($scopes['header']);
    $scopes['header'] = $temp;
  }

  // Process footer last if everything has been moved to the footer.
  if (isset($scopes['footer']) && count($scopes) > 1 && $js_settings_in_footer) {
    $temp = $scopes['footer'];
    unset($scopes['footer']);
    $scopes['footer'] = $temp;
  }

  // Return the scopes.
  return $scopes;
}

/**
 * Apply the advagg changes to the $css_js_groups array.
 *
 * @param array $css_js_groups
 *   An array of CSS or JS groups as returned by drupal_group_css/js().
 * @param array $plans
 *   An array of changes to do to the $css_js_groups array.
 *
 * @return array
 *   New version of $css_js_groups.
 */
function advagg_merge_plans(array $css_js_groups, array $plans) {
  $used_keys = array();
  foreach ($plans as $plan) {
    $plan_added = FALSE;
    foreach ($css_js_groups as $key => $group) {

      // Remove files from the old css/js array.
      $file_removed = FALSE;
      foreach ($css_js_groups[$key]['items'] as $k => $values) {
        if (is_array($values) && array_key_exists('data', $values) && is_array($plan['items']['files']) && is_string($values['data'])) {

          // If the CSS is a split file, the first file is very meaningful, and
          // is probably the only file.
          $first_file = reset($plan['items']['files']);
          if (array_key_exists($values['data'], $plan['items']['files'])) {
            unset($css_js_groups[$key]['items'][$k]);
            $file_removed = TRUE;
          }
          elseif (!empty($first_file['split'])) {
            if ($values['data'] == $first_file['split_original']) {
              if (!empty($first_file['split_last_part'])) {
                unset($css_js_groups[$key]['items'][$k]);
              }
              $file_removed = TRUE;
            }
          }
        }
      }

      // Replace first file of the old css/js array with one from advagg.
      if ($file_removed && !$plan_added) {
        $step = 0;
        do {
          ++$step;
          $insert_key = '' . floatval($key) . '.' . sprintf('%03d', $step);
        } while (array_key_exists($insert_key, $css_js_groups));
        $css_js_groups[(string) $insert_key] = $plan;
        $plan_added = TRUE;
      }
    }

    // Remove old css/js grouping if no files are left in it.
    foreach ($css_js_groups as $key => $group) {
      if (empty($css_js_groups[$key]['items'])) {
        unset($css_js_groups[$key]);
      }
    }
    if (!$plan_added) {
      foreach ($css_js_groups as $key => $group) {
        if (empty($group['items']['aggregate_filenames_hash']) || $group['items']['aggregate_filenames_hash'] != $plan['items']['aggregate_filenames_hash'] || empty($group['items']['aggregate_contents_hash']) || $group['items']['aggregate_contents_hash'] != $plan['items']['aggregate_contents_hash']) {
          continue;
        }

        // Insert a unique key.
        do {
          $key = '' . (floatval($key) + 0.01);
        } while (array_key_exists((string) $key, $css_js_groups) || array_key_exists((string) $key, $used_keys));
        $used_keys[(string) $key] = TRUE;
        $css_js_groups[(string) $key] = $plan;
        $plan_added = TRUE;
        break;
      }
    }
  }

  // Key sort and normalize the array before returning it.
  ksort($css_js_groups);
  $css_js_groups = array_values($css_js_groups);
  return $css_js_groups;
}

/**
 * Function used to see if aggregation is enabled.
 *
 * @return bool
 *   The value of the advagg_enabled variable.
 */
function advagg_enabled() {

  // If current_path() doesn't exist then bootstrap Drupal to make it available.
  if (!function_exists('current_path')) {
    drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  }
  $init =& drupal_static(__FUNCTION__);
  if (!empty($init)) {
    return variable_get('advagg_enabled', ADVAGG_ENABLED);
  }

  // Set base_path if not set.
  if (empty($GLOBALS['base_path'])) {
    $GLOBALS['base_path'] = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\\/') . '/';
  }
  $init = TRUE;

  // Disable AdvAgg if module needs to be upgraded from 1.x to 2.x.
  if (variable_get('advagg_needs_update', ADVAGG_NEEDS_UPDATE)) {
    if (!db_table_exists('advagg_aggregates_versions')) {
      $GLOBALS['conf']['advagg_enabled'] = FALSE;
      if (user_access('administer site configuration')) {
        drupal_set_message(t('Please run <a href="@link">database updates</a>. AdvAgg will remain disabled until done.', array(
          '@link' => url('update.php'),
        )), 'error');
      }
    }
    else {
      variable_del('advagg_needs_update');
    }
  }
  else {

    // Get values and fill in defaults if needed.
    $config_path = advagg_admin_config_root_path();
    $current_path = current_path();
    $arg = arg();
    $arg += array(
      1 => '',
      2 => '',
      3 => '',
      4 => '',
      5 => '',
    );
    $admin_theme = variable_get('admin_theme');

    // List of all the pages which will not have Advanced Aggregator enabled.
    $list_of_pages = variable_get('advagg_disable_on_listed_pages');
    $pages = trim(drupal_strtolower($list_of_pages));

    // Convert the Drupal path to lowercase.
    $path = drupal_strtolower(drupal_get_path_alias(current_path()));

    // Compare the lowercase internal and lowercase path alias (if any).
    $page_match = drupal_match_path($path, $pages);
    if ($page_match) {
      $GLOBALS['conf']['advagg_enabled'] = FALSE;
      $GLOBALS['conf']['preprocess_css'] = FALSE;
      $GLOBALS['conf']['preprocess_js'] = FALSE;
    }

    // Disable advagg if on admin page and configured to do so.
    // AND theme is admin theme
    // AND NOT /admin/reports/status
    // AND NOT /admin/config/development/performance/.
    // AND NOT /admin/appearance/settings/*.
    // AND NOT /admin/config/development/performance/advagg/*.
    if (variable_get('advagg_disable_on_admin', ADVAGG_DISABLE_ON_ADMIN) && $GLOBALS['theme'] === $admin_theme && path_is_admin($current_path) && !($arg[1] === 'reports' && $arg[2] === 'status') && !($arg[2] === 'development' && $arg[3] === 'performance' && empty($arg[4])) && !($arg[1] === 'appearance' && $arg[2] === 'settings' && !empty($arg[3])) && stripos($current_path, $config_path . '/advagg') !== 0) {
      $GLOBALS['conf']['advagg_enabled'] = FALSE;
      $GLOBALS['conf']['preprocess_css'] = FALSE;
      $GLOBALS['conf']['preprocess_js'] = FALSE;
    }

    // Check if the advagg cookie is set.
    $cookie_name = 'AdvAggDisabled';
    $bypass_cookie = FALSE;
    $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal'));
    if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
      $bypass_cookie = TRUE;
    }

    // Allow for AdvAgg to be enabled per request.
    if (isset($_GET['advagg']) && $_GET['advagg'] == 1 && !defined('MAINTENANCE_MODE') && (user_access('bypass advanced aggregation') || $bypass_cookie)) {
      $GLOBALS['conf']['advagg_enabled'] = TRUE;
      $GLOBALS['conf']['preprocess_css'] = TRUE;
      $GLOBALS['conf']['preprocess_js'] = TRUE;
    }

    // Disable AdvAgg if maintenance mode is defined.
    if (defined('MAINTENANCE_MODE')) {
      $GLOBALS['conf']['advagg_enabled'] = FALSE;
    }

    // Only run code below if advagg is enabled.
    if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {

      // Do not use AdvAgg or preprocessing functions if the disable cookie is
      // set.
      if ($bypass_cookie && !isset($_GET['advagg'])) {
        $GLOBALS['conf']['advagg_enabled'] = FALSE;
        $GLOBALS['conf']['preprocess_css'] = FALSE;
        $GLOBALS['conf']['preprocess_js'] = FALSE;
        $bypass_cookie = TRUE;

        // Let the user know that the AdvAgg bypass cookie is currently set.
        static $msg_set;
        if (!isset($msg_set) && variable_get('advagg_show_bypass_cookie_message', ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE)) {
          $msg_set = TRUE;
          if (user_access('administer site configuration')) {
            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(
              '@advagg_operations' => url(advagg_admin_config_root_path() . '/advagg/operations', array(
                'fragment' => 'edit-bypass',
              )),
            )));
          }
          else {
            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(
              '@login' => 'user/login',
              '@advagg_operations' => advagg_admin_config_root_path() . '/advagg/operations',
            )));
          }
        }
      }

      // Disable advagg if requested.
      if (isset($_GET['advagg']) && $_GET['advagg'] == -1 && (user_access('bypass advanced aggregation') || $bypass_cookie)) {
        $GLOBALS['conf']['advagg_enabled'] = FALSE;
        $GLOBALS['conf']['preprocess_css'] = FALSE;
        $GLOBALS['conf']['preprocess_js'] = FALSE;
      }

      // Disable core preprocessing if requested.
      if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 0 && (user_access('bypass advanced aggregation') || $bypass_cookie)) {
        $GLOBALS['conf']['preprocess_css'] = FALSE;
        $GLOBALS['conf']['preprocess_js'] = FALSE;
      }

      // Enable core preprocessing if requested.
      if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 && (user_access('bypass advanced aggregation') || $bypass_cookie)) {
        $GLOBALS['conf']['preprocess_css'] = TRUE;
        $GLOBALS['conf']['preprocess_js'] = TRUE;
      }

      // Enable debugging if requested.
      if (isset($_GET['advagg-debug']) && (user_access('bypass advanced aggregation') || $bypass_cookie)) {

        // Cast to an int.
        $GLOBALS['conf']['advagg_debug'] = (int) $_GET['advagg-debug'];
      }
    }
  }
  return variable_get('advagg_enabled', ADVAGG_ENABLED);
}

/**
 * Get the current path used for advagg admin configuration.
 *
 * @return string
 *   Path to root advagg config.
 */
function advagg_admin_config_root_path() {
  return variable_get('advagg_admin_config_root_path', ADVAGG_ADMIN_CONFIG_ROOT_PATH);
}

/**
 * Get an array of all hooks and settings that affect aggregated files contents.
 *
 * @return array
 *   array('variables' => array(...), 'hooks' => array(...))
 */
function advagg_current_hooks_hash_array() {
  $aggregate_settings =& drupal_static(__FUNCTION__);
  if (!empty($aggregate_settings)) {
    return $aggregate_settings;
  }
  list($css_path, $js_path) = advagg_get_root_files_dir();

  // Put all enabled hooks and settings into a big array.
  $aggregate_settings = array(
    'variables' => array(
      'advagg_gzip' => variable_get('advagg_gzip', ADVAGG_GZIP),
      'advagg_brotli' => variable_get('advagg_brotli', ADVAGG_BROTLI),
      'advagg_no_zopfli' => variable_get('advagg_no_zopfli', ADVAGG_NO_ZOPFLI),
      'is_https' => $GLOBALS['is_https'],
      'advagg_global_counter' => advagg_get_global_counter(),
      'base_path' => $GLOBALS['base_path'],
      'advagg_ie_css_selector_limiter' => variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER),
      'advagg_ie_css_selector_limiter_value' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE),
      'advagg_scripts_scope_anywhere' => variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE),
      'advagg_devel' => variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0 ? TRUE : FALSE,
      'advagg_convert_absolute_to_relative_path' => variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH),
      'advagg_convert_absolute_to_protocol_relative_path' => variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH),
      'advagg_css_absolute_path' => variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH),
      'advagg_force_https_path' => variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH),
      'advagg_css_dir' => $css_path[0],
      'advagg_js_dir' => $js_path[0],
    ),
    'hooks' => advagg_hooks_implemented(FALSE),
  );

  // Add in language if locale is enabled.
  if (module_exists('locale')) {
    $aggregate_settings['variables']['language'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : '';
  }

  // Add the base url if so desired to.
  if (variable_get('advagg_include_base_url', ADVAGG_INCLUDE_BASE_URL)) {
    $aggregate_settings['variables']['base_url'] = $GLOBALS['base_url'];
  }

  // CDN module settings.
  // Patch in https://www.drupal.org/node/1942230#comment-7927171 is fine
  // on a technical level but I got frustrated with all the reports about it not
  // working with no good reason as to why it doesn't work.
  if (!function_exists('cdn_advagg_current_hooks_hash_array_alter') && module_exists('cdn')) {
    $aggregate_settings['variables'][CDN_MODE_VARIABLE] = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
    $aggregate_settings['variables'][CDN_BASIC_FARFUTURE_VARIABLE] = variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT);
    $aggregate_settings['variables'][CDN_HTTPS_SUPPORT_VARIABLE] = variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE);
    $aggregate_settings['variables'][CDN_STATUS_VARIABLE] = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
    $aggregate_settings['variables']['cdn_request_is_https'] = cdn_request_is_https();
    $aggregate_settings['variables']['cdn_check_drupal_path'] = cdn_check_drupal_path($_GET['q']);
  }

  // Allow other modules to add in their own settings and hooks.
  // Call hook_advagg_current_hooks_hash_array_alter().
  drupal_alter('advagg_current_hooks_hash_array', $aggregate_settings);
  return $aggregate_settings;
}

/**
 * Get the hash of all hooks and settings that affect aggregated files contents.
 *
 * @return string
 *   hash value.
 */
function advagg_get_current_hooks_hash() {
  $current_hash =& drupal_static(__FUNCTION__);
  if (empty($current_hash)) {

    // Get all advagg hooks and variables in use.
    $aggregate_settings = advagg_current_hooks_hash_array();

    // Generate the hash.
    $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
    $current_hash = drupal_hash_base64($serialize_function($aggregate_settings));

    // Save into variables for verification purposes later on if not found.
    $settings = advagg_get_hash_settings($current_hash);
    if (empty($settings)) {

      // Save new hash into.
      advagg_set_hash_settings($current_hash, $aggregate_settings);
    }
  }
  return $current_hash;
}

/**
 * Store settings associated with hash.
 *
 * @param string $hash
 *   The hash.
 * @param array $settings
 *   The settings associated with this hash.
 *
 * @return MergeQuery
 *   value from db_merge
 */
function advagg_set_hash_settings($hash, array $settings = array()) {
  return db_merge('advagg_aggregates_hashes')
    ->key(array(
    'hash' => $hash,
  ))
    ->fields(array(
    'hash' => $hash,
    'settings' => serialize($settings),
  ))
    ->execute();
}

/**
 * Get back what hooks are implemented.
 *
 * @param bool $all
 *   If TRUE get all hooks related to css/js files.
 *   if FALSE get only the subset of hooks that alter the filename/contents.
 *
 * @return array
 *   List of hooks and what modules have implemented them.
 */
function advagg_hooks_implemented($all = TRUE) {

  // Get hooks in use.
  $hooks = array(
    'advagg_get_css_file_contents_pre_alter' => array(),
    'advagg_get_css_file_contents_alter' => array(),
    'advagg_get_css_aggregate_contents_alter' => array(),
    'advagg_get_js_file_contents_alter' => array(),
    'advagg_get_js_aggregate_contents_alter' => array(),
    'advagg_save_aggregate_pre_alter' => array(),
    'advagg_save_aggregate_alter' => array(),
    'advagg_current_hooks_hash_array_alter' => array(),
    'advagg_get_root_files_dir_alter' => array(),
    'advagg_context_alter' => array(),
  );
  if ($all) {
    $hooks += array(
      'advagg_build_aggregate_plans_alter' => array(),
      'advagg_build_aggregate_plans_post_alter' => array(),
      'advagg_changed_files' => array(),
      'advagg_css_groups_alter' => array(),
      'advagg_js_groups_alter' => array(),
      'advagg_modify_css_pre_render_alter' => array(),
      'advagg_modify_js_pre_render_alter' => array(),
      'advagg_get_info_on_files_alter' => array(),
      'advagg_hooks_implemented_alter' => array(),
      'advagg_removed_aggregates' => array(),
      'advagg_scan_for_changes' => array(),
      'advagg_missing_root_file' => array(),
      'js_alter' => array(),
      'css_alter' => array(),
    );
  }

  // Call hook_advagg_hooks_implemented_alter().
  drupal_alter('advagg_hooks_implemented', $hooks, $all);

  // Cache module_implements as this will load up .inc files.
  $serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
  $cid = 'advagg_hooks_implemented:' . (int) $all . ':' . drupal_hash_base64($serialize_function($hooks));
  $cache = cache_get($cid, 'cache_bootstrap');
  if (!empty($cache->data)) {
    $hooks = $cache->data;
  }
  else {
    foreach ($hooks as $hook => $values) {
      $hooks[$hook] = module_implements($hook);

      // Also check themes as drupal_alter() allows for themes to alter things.
      $theme_keys = array_keys(list_themes());
      if (!empty($theme_keys)) {
        foreach ($theme_keys as $theme_key) {
          $function = $theme_key . '_' . $hook;
          if (function_exists($function)) {
            $hooks[$hook][] = $theme_key;
          }
        }
      }
    }
    cache_set($cid, $hooks, 'cache_bootstrap', CACHE_TEMPORARY);
  }
  return $hooks;
}

/**
 * Returns the hashes settings.
 *
 * @param string $hash
 *   The name of the variable to return.
 *
 * @return array
 *   The settings array or an empty array if not found.
 */
function advagg_get_hash_settings($hash) {
  $settings = db_select('advagg_aggregates_hashes', 'aah')
    ->fields('aah', array(
    'settings',
  ))
    ->condition('hash', $hash)
    ->execute()
    ->fetchField();
  return !empty($settings) ? unserialize($settings) : array();
}

/**
 * Get the CSS and JS path for advagg.
 *
 * @param bool $reset
 *   Set to TRUE to reset the static variables.
 *
 * @return array
 *   Example return below:
 *
 * @code
 *   array(
 *     array(
 *       public://advagg_css,
 *       sites/default/files/advagg_css,
 *     ),
 *     array(
 *       public://advagg_js,
 *       sites/default/files/advagg_js,
 *     ),
 *   )
 * @endcode
 */
function advagg_get_root_files_dir($reset = FALSE) {
  $css_paths =& drupal_static(__FUNCTION__ . '_css');
  $js_paths =& drupal_static(__FUNCTION__ . '_js');

  // Make sure directories are available and writable.
  if (empty($css_paths) || empty($js_paths) || $reset) {

    // Default is public://.
    $prefix = variable_get('advagg_root_dir_prefix', ADVAGG_ROOT_DIR_PREFIX);
    $css_paths[0] = $prefix . 'advagg_css';
    $js_paths[0] = $prefix . 'advagg_js';
    file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY);
    file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY);

    // Set the URI of the directory.
    $css_paths[1] = advagg_get_relative_path($css_paths[0], 'css');
    $js_paths[1] = advagg_get_relative_path($js_paths[0], 'js');

    // If the css or js got a path, use it for the other missing one.
    if (empty($css_paths[1]) && !empty($js_paths[1])) {
      $css_paths[1] = str_replace('/advagg_js', '/advagg_css', $js_paths[1]);
    }
    elseif (empty($js_paths[1]) && !empty($css_paths[1])) {
      $js_paths[1] = str_replace('/advagg_css', '/advagg_js', $css_paths[1]);
    }

    // Fix if empty.
    if (empty($css_paths[1])) {
      $css_paths[1] = $css_paths[0];
    }
    if (empty($js_paths[1])) {
      $js_paths[1] = $js_paths[0];
    }

    // Allow other modules to alter css and js paths.
    // Call hook_advagg_get_root_files_dir_alter()
    drupal_alter('advagg_get_root_files_dir', $css_paths, $js_paths);
  }
  return array(
    $css_paths,
    $js_paths,
  );
}

/**
 * Given a uri, get the relative_path.
 *
 * @param string $uri
 *   The uri for the stream wrapper.
 * @param string $type
 *   (Optional) String: css or js.
 *
 * @return string
 *   The relative path of the uri.
 *
 * @see https://www.drupal.org/node/837794#comment-9124435
 */
function advagg_get_relative_path($uri, $type = '') {
  $wrapper = file_stream_wrapper_get_instance_by_uri($uri);
  if ($wrapper instanceof DrupalLocalStreamWrapper) {
    $relative_path = $wrapper
      ->getDirectoryPath() . '/' . file_uri_target($uri);
  }
  else {
    $relative_path = parse_url(file_create_url($uri), PHP_URL_PATH);
    if (empty($relative_path) && !empty($uri)) {
      $filename = advagg_generate_advagg_filename_from_db($type);
      $relative_path = parse_url(file_create_url("{$uri}/{$filename}"), PHP_URL_PATH);
      $end = strpos($relative_path, "/{$filename}");
      if ($end !== FALSE) {
        $relative_path = substr($relative_path, 0, $end);
      }
    }
    if (substr($relative_path, 0, strlen($GLOBALS['base_path'])) == $GLOBALS['base_path']) {
      $relative_path = substr($relative_path, strlen($GLOBALS['base_path']));
    }
    $relative_path = ltrim($relative_path, '/');
  }
  return $relative_path;
}

/**
 * Builds the requested CSS/JS aggregates.
 *
 * @param array $filenames
 *   Array of AdvAgg filenames to generate.
 * @param string $type
 *   String: css or js.
 *
 * @return array
 *   Array keyed by filename, value is result from advagg_missing_create_file().
 */
function advagg_build_aggregates(array $filenames, $type) {

  // Check input values.
  if (empty($filenames)) {
    return array();
  }
  if (empty($type)) {
    $filename = reset($filenames);
    $type = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
  }

  // Call the file generation function directly.
  module_load_include('inc', 'advagg', 'advagg.missing');
  list($css_path, $js_path) = advagg_get_root_files_dir();
  $return = array();
  foreach ($filenames as $filename) {

    // Skip if the file exists.
    if ($type === 'css') {
      $uri = $css_path[0] . '/' . $filename;
    }
    elseif ($type === 'js') {
      $uri = $js_path[0] . '/' . $filename;
    }
    if (file_exists($uri)) {
      continue;
    }

    // Only create the file if we have a lock.
    $lock_name = 'advagg_' . $filename;
    if (variable_get('advagg_no_locks', ADVAGG_NO_LOCKS)) {
      $return[$filename] = advagg_missing_create_file($filename);
    }
    elseif (lock_acquire($lock_name, 10)) {
      $return[$filename] = advagg_missing_create_file($filename);
      lock_release($lock_name);
    }
  }
  return $return;
}

/**
 * Gets the core CSS/JS included in this ajax request.
 *
 * Used so core JS can be rendered through the AdvAgg pipeline.
 *
 * @see ajax_render()
 *
 * @return array
 *   Returns an array containing $styles, $scripts_header, $scripts_footer,
 *   $items, and $settings.
 */
function advagg_build_ajax_js_css() {
  $settings = array();

  // Ajax responses aren't rendered with html.tpl.php, so we have to call
  // drupal_get_css() and drupal_get_js() here, in order to have new files added
  // during this request to be loaded by the page. We only want to send back
  // files that the page hasn't already loaded, so we implement simple diffing
  // logic using array_diff_key().
  foreach (array(
    'css',
    'js',
  ) as $type) {

    // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty,
    // since the base page ought to have at least one JS file and one CSS file
    // loaded. It probably indicates an error, and rather than making the page
    // reload all of the files, instead we return no new files.
    if (empty($_POST['ajax_page_state'][$type])) {
      $core_items[$type] = $items[$type] = array();
      $scripts = drupal_add_js();
      if (!empty($scripts['settings'])) {
        $settings = $scripts['settings'];
      }
    }
    else {
      $function = 'drupal_add_' . $type;

      // Get the current css/js needed for this page.
      $items[$type] = $function();

      // Call hook_js_alter() OR hook_css_alter().
      drupal_alter($type, $items[$type]);

      // Separately track original items that will be used to build the snippets
      // added by core, which will be replaced in advagg_ajax_render_alter().
      $core_items[$type] = $items[$type];
      drupal_alter($type . '_post', $items[$type]);

      // @todo Inline CSS and JS items are indexed numerically. These can't be
      //   reliably diffed with array_diff_key(), since the number can change
      //   due to factors unrelated to the inline content, so for now, we strip
      //   the inline items from Ajax responses, and can add support for them
      //   when drupal_add_css() and drupal_add_js() are changed to use a hash
      //   of the inline content as the array key.
      foreach ($items[$type] as $key => $item) {
        if (is_numeric($key)) {
          unset($items[$type][$key]);
        }
      }
      foreach ($core_items[$type] as $key => $core_item) {
        if (is_numeric($key)) {
          unset($core_items[$type][$key]);
        }
      }

      // Ensure that the page doesn't reload what it already has.
      // @ignore security_17
      $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]);

      // @ignore security_17
      $core_items[$type] = array_diff_key($core_items[$type], $_POST['ajax_page_state'][$type]);
    }
  }

  // Render the HTML to load these files, and add AJAX commands to insert this
  // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
  // data from being altered again, as we already altered it above. Settings are
  // handled separately, afterwards.
  $scripts = drupal_add_js();
  if (isset($scripts['settings'])) {
    $settings = $scripts['settings'];
    unset($items['js']['settings']);
    unset($core_items['js']['settings']);
  }
  $styles = drupal_get_css($core_items['css'], TRUE);
  $scripts_footer = drupal_get_js('footer', $core_items['js'], TRUE);
  $scripts_header = drupal_get_js('header', $core_items['js'], TRUE);
  return array(
    $styles,
    $scripts_header,
    $scripts_footer,
    $items,
    $settings,
  );
}

/**
 * Given the type lets us know if advagg is enabled or disabled.
 *
 * @param string $type
 *   String: css or js.
 *
 * @return bool
 *   TRUE or FALSE.
 */
function advagg_file_aggregation_enabled($type) {
  if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'update') {
    return FALSE;
  }
  if (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation')) {
    return FALSE;
  }
  if ($type === 'css') {
    return variable_get('preprocess_css', FALSE);
  }
  if ($type === 'js') {
    return variable_get('preprocess_js', FALSE);
  }
}

/**
 * Update atime inside advagg_aggregates_versions and cache_advagg_info.
 *
 * @param array $files
 *   List of files in the aggregate as well as the aggregate name.
 *
 * @return bool
 *   Return TRUE if anything was written to the database.
 */
function advagg_multi_update_atime(array $files) {
  $write_done = FALSE;
  $records = array();
  foreach ($files as $values) {

    // Create the cache id.
    $cid = 'advagg:db:' . $values['aggregate_filenames_hash'] . ADVAGG_SPACE . $values['aggregate_contents_hash'];

    // Create the db record.
    $records[$cid] = array(
      'aggregate_filenames_hash' => $values['aggregate_filenames_hash'],
      'aggregate_contents_hash' => $values['aggregate_contents_hash'],
      'atime' => REQUEST_TIME,
    );
  }

  // Use the cache.
  $cids = array_keys($records);
  $caches = cache_get_multiple($cids, 'cache_advagg_info');
  if (!empty($caches)) {
    foreach ($caches as $cache) {

      // See if the atime value needs to be updated.
      if (!empty($cache->data['atime']) && $cache->data['atime'] > REQUEST_TIME - 12 * 60 * 60) {

        // If atime is less than 12 hours old, do nothing.
        unset($records[$cache->cid]);
      }
    }
  }
  if (empty($records)) {
    return $write_done;
  }
  foreach ($records as $cid => $record) {

    // Update atime in DB.
    $result = db_merge('advagg_aggregates_versions')
      ->key(array(
      'aggregate_filenames_hash' => $record['aggregate_filenames_hash'],
      'aggregate_contents_hash' => $record['aggregate_contents_hash'],
    ))
      ->fields(array(
      'atime' => $record['atime'],
    ))
      ->execute();
    if (!$write_done && $result) {
      $write_done = TRUE;
    }

    // Update the atime in the cache.
    // Get fresh copy of the cache.
    $cache = cache_get($cid, 'cache_advagg_info');

    // Set the atime.
    if (empty($cache->data)) {
      $cache = new stdClass();
    }
    $cache->data['atime'] = REQUEST_TIME;

    // Write to the cache.
    // CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days.
    // The random 0 to 45 day addition is to prevent a cache stampede.
    cache_set($cid, $cache->data, 'cache_advagg_info', round(REQUEST_TIME + 1209600 + mt_rand(0, 3888000), -3));
  }
  return $write_done;
}

/**
 * Return the advagg_global_counter variable.
 *
 * @return int
 *   Int value.
 */
function advagg_get_global_counter() {
  $global_counter = variable_get('advagg_global_counter', ADVAGG_GLOBAL_COUNTER);
  return $global_counter;
}

/**
 * Cache clear callback for admin_menu/flush-cache/advagg.
 */
function advagg_admin_flush_cache() {
  module_load_include('inc', 'advagg', 'advagg.admin');
  advagg_admin_flush_cache_button();
}

/**
 * Returns HTML for a generic HTML tag with attributes.
 *
 * @param array $variables
 *   An associative array containing:
 *   - element: An associative array describing the tag:
 *     - #tag: The tag name to output. Typical tags added to the HTML HEAD:
 *       - meta: To provide meta information, such as a page refresh.
 *       - link: To refer to stylesheets and other contextual information.
 *       - script: To load JavaScript.
 *     - #attributes: (optional) An array of HTML attributes to apply to the
 *       tag.
 *     - #value: (optional) A string containing tag content, such as inline
 *       CSS.
 *     - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA
 *       wrapper prefix.
 *     - #value_suffix: (optional) A string to append to #value, e.g. a CDATA
 *       wrapper suffix.
 */
function theme_html_script_tag(array $variables) {
  $element = $variables['element'];
  $attributes = '';
  $onload = '';
  $onerror = '';
  if (isset($element['#attributes'])) {

    // On Load.
    if (!empty($element['#attributes']['onload'])) {
      $onload = $element['#attributes']['onload'];
      unset($element['#attributes']['onload']);
    }

    // On Error.
    if (!empty($element['#attributes']['onerror'])) {
      $onerror = $element['#attributes']['onerror'];
      unset($element['#attributes']['onerror']);
    }
    $attributes = !empty($element['#attributes']) ? drupal_attributes($element['#attributes']) : '';
    if (!empty($onload)) {
      $attributes .= ' onload="' . advagg_jsspecialchars($onload) . '"';
    }
    if (!empty($onerror)) {
      $attributes .= ' onerror="' . advagg_jsspecialchars($onerror) . '"';
    }
  }
  if (!isset($element['#value'])) {
    return '<' . $element['#tag'] . $attributes . " />\n";
  }
  else {
    $output = '<' . $element['#tag'] . $attributes . '>';
    if (isset($element['#value_prefix'])) {
      $output .= $element['#value_prefix'];
    }
    $output .= $element['#value'];
    if (isset($element['#value_suffix'])) {
      $output .= $element['#value_suffix'];
    }
    $output .= '</' . $element['#tag'] . ">\n";
    return $output;
  }
}

/**
 * Replace quotes with the html version of it.
 *
 * @param string $string
 *   Input string. Convert quotes to html chars.
 *
 * @return string
 *   Transformed string.
 */
function advagg_jsspecialchars($string = '') {
  $string = str_replace('"', '&quot;', $string);
  $string = str_replace("'", '&#039;', $string);
  return $string;
}

/**
 * Callback for pre_render to add elements needed for JavaScript to be rendered.
 *
 * This function evaluates the aggregation enabled/disabled condition on a group
 * by group basis by testing whether an aggregate file has been made for the
 * group rather than by testing the site-wide aggregation setting. This allows
 * this function to work correctly even if modules have implemented custom
 * logic for grouping and aggregating files.
 *
 * @param array $elements
 *   A render array containing:
 *   - #items: The JavaScript items as returned by drupal_add_js() and
 *     altered by drupal_get_js().
 *   - #group_callback: A function to call to group #items. Following
 *     this function, #aggregate_callback is called to aggregate items within
 *     the same group into a single file.
 *   - #aggregate_callback: A function to call to aggregate the items within
 *     the groups arranged by the #group_callback function.
 *
 * @return array
 *   A render array that will render to a string of JavaScript tags.
 *
 * @see drupal_get_js()
 */
function advagg_pre_render_scripts(array $elements) {

  // Don't run it twice.
  if (!empty($elements['#groups'])) {
    return $elements;
  }

  // Group and aggregate the items.
  if (isset($elements['#group_callback'])) {

    // Call advagg_group_js().
    $elements['#groups'] = $elements['#group_callback']($elements['#items']);
  }
  if (isset($elements['#aggregate_callback'])) {

    // Call _advagg_aggregate_js().
    $elements['#aggregate_callback']($elements['#groups']);
  }

  // A dummy query-string is added to filenames, to gain control over
  // browser-caching. The string changes on every update or full cache
  // flush, forcing browsers to load a new copy of the files, as the
  // URL changed. Files that should not be cached (see drupal_add_js())
  // get REQUEST_TIME as query-string instead, to enforce reload on every
  // page request.
  $default_query_string = variable_get('css_js_query_string', '0');

  // For inline JavaScript to validate as XHTML, all JavaScript containing
  // XHTML needs to be wrapped in CDATA. To make that backwards compatible
  // with HTML 4, we need to comment out the CDATA-tag.
  $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
  $embed_suffix = "\n//--><!]]>\n";

  // Since JavaScript may look for arguments in the URL and act on them, some
  // third-party code might require the use of a different query string.
  $js_version_string = variable_get('drupal_js_version_query_string', 'v=');

  // Defaults for each SCRIPT element.
  $element_defaults = array(
    '#type' => 'html_script_tag',
    '#tag' => 'script',
    '#value' => '',
    '#attributes' => array(),
  );
  $hooks = theme_get_registry(FALSE);
  if (empty($hooks['html_script_tag'])) {
    $element_defaults['#type'] = 'html_tag';
  }

  // Loop through each group.
  foreach ($elements['#groups'] as $group) {

    // If a group of files has been aggregated into a single file,
    // $group['data'] contains the URI of the aggregate file. Add a single
    // script element for this file.
    if (isset($group['type']) && $group['type'] === 'file' && isset($group['data'])) {
      $element = $element_defaults;
      $element['#attributes']['src'] = advagg_file_create_url($group['data']) . ($group['cache'] ? '' : '?' . REQUEST_TIME);
      $element['#browsers'] = $group['browsers'];
      if (!empty($group['defer'])) {
        $element['#attributes']['defer'] = 'defer';
      }
      if (!empty($group['async'])) {
        $element['#attributes']['async'] = 'async';
      }
      if (!empty($group['onload'])) {
        if (!isset($element['#attributes']['onload'])) {
          $element['#attributes']['onload'] = '';
        }
        $element['#attributes']['onload'] .= $group['onload'];
      }
      if (!empty($group['onerror'])) {
        if (!isset($element['#attributes']['onerror'])) {
          $element['#attributes']['onerror'] = '';
        }
        $element['#attributes']['onerror'] .= $group['onerror'];
      }
      if (!empty($group['attributes'])) {
        $element['#attributes'] += $group['attributes'];
      }
      $elements[] = $element;
    }
    else {
      foreach ($group['items'] as $item) {

        // Skip if data is empty.
        if (empty($item['data'])) {
          continue;
        }

        // Element properties that do not depend on item type.
        $element = $element_defaults;
        if (!empty($item['defer'])) {
          $element['#attributes']['defer'] = 'defer';
        }
        if (!empty($item['async'])) {
          $element['#attributes']['async'] = 'async';
        }
        if (!empty($item['onload'])) {
          if (!isset($element['#attributes']['onload'])) {
            $element['#attributes']['onload'] = '';
          }
          $element['#attributes']['onload'] .= $item['onload'];
        }
        if (!empty($item['onerror'])) {
          if (!isset($element['#attributes']['onerror'])) {
            $element['#attributes']['onerror'] = '';
          }
          $element['#attributes']['onerror'] .= $item['onerror'];
        }
        if (!empty($group['attributes'])) {
          $element['#attributes'] += $group['attributes'];
        }
        $element['#browsers'] = isset($item['browsers']) ? $item['browsers'] : array();

        // Crude type detection if needed.
        if (empty($item['type'])) {
          if (is_array($item['data'])) {
            $item['type'] = 'setting';
          }
          elseif (strpos($item['data'], 'http://') === 0 || strpos($item['data'], 'https://') === 0 || strpos($item['data'], '//') === 0) {
            $item['type'] = 'external';
          }
          elseif (file_exists(trim($item['data']))) {
            $item['type'] = 'file';
          }
          else {
            $item['type'] = 'inline';
          }
        }

        // Element properties that depend on item type.
        switch ($item['type']) {
          case 'setting':
            $data = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($item['data'], 'is_array')));
            $json_data = advagg_json_encode($data);
            $element['#value_prefix'] = $embed_prefix;
            $element['#value'] = 'jQuery.extend(Drupal.settings, ' . $json_data . ");";
            $element['#value_suffix'] = $embed_suffix;
            break;
          case 'inline':

            // If a BOM is found, convert the string to UTF-8.
            $encoding = advagg_get_encoding_from_bom($item['data']);
            if (!empty($encoding)) {
              $item['data'] = advagg_convert_to_utf8($item['data'], $encoding);
            }
            $element['#value_prefix'] = $embed_prefix;
            $element['#value'] = $item['data'];
            $element['#value_suffix'] = $embed_suffix;
            break;
          case 'file':
            $query_string_separator = strpos($item['data'], '?') !== FALSE ? '&' : '?';
            $cache_validator = REQUEST_TIME;
            if (!empty($item['cache'])) {
              $cache_validator = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
            }
            $element['#attributes']['src'] = advagg_file_create_url($item['data']) . $query_string_separator . $cache_validator;
            break;
          case 'external':

            // Convert to protocol relative path.
            $file_uri = $item['data'];
            if (variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH)) {
              $file_uri = advagg_convert_abs_to_protocol($item['data']);
            }
            $element['#attributes']['src'] = $file_uri;
            break;
        }
        $elements[] = $element;
      }
    }
  }
  return $elements;
}

/**
 * Get the prefix and suffix for inline css.
 *
 * @return array
 *   An array where the prefix is key 0 and suffix is key 1.
 */
function advagg_get_css_prefix_suffix() {
  $embed_prefix = "\n/* <![CDATA[ */\n";
  $embed_suffix = "\n/* ]]> */\n";
  return array(
    $embed_prefix,
    $embed_suffix,
  );
}

/**
 * A #pre_render callback to add elements needed for CSS tags to be rendered.
 *
 * For production websites, LINK tags are preferable to STYLE tags with @import
 * statements, because:
 * - They are the standard tag intended for linking to a resource.
 * - On Firefox 2 and perhaps other browsers, CSS files included with @import
 *   statements don't get saved when saving the complete web page for offline
 *   use: http://drupal.org/node/145218.
 * - On IE, if only LINK tags and no @import statements are used, all the CSS
 *   files are downloaded in parallel, resulting in faster page load, but if
 *   the @import statements are used and span across multiple STYLE tags, all
 *   the ones from 1 STYLE tag must be downloaded before downloading begins for
 *   the next STYLE tag. Furthermore, IE7 does not support media declaration on
 *   the @import statement, so multiple STYLE tags must be used when different
 *   files are for different media types. Non-IE browsers always download in
 *   parallel, so this is an IE-specific performance quirk:
 *   http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
 *
 * However, IE has an annoying limit of 31 total CSS inclusion tags
 * (http://drupal.org/node/228818) and LINK tags are limited to one file per
 * tag, whereas STYLE tags can contain multiple @import statements allowing
 * multiple files to be loaded per tag. When CSS aggregation is disabled, a
 * Drupal site can easily have more than 31 CSS files that need to be loaded, so
 * using LINK tags exclusively would result in a site that would display
 * incorrectly in IE. Depending on different needs, different strategies can be
 * employed to decide when to use LINK tags and when to use STYLE tags.
 *
 * The strategy employed by this function is to use LINK tags for all aggregate
 * files and for all files that cannot be aggregated (e.g., if 'preprocess' is
 * set to FALSE or the type is 'external'), and to use STYLE tags for groups
 * of files that could be aggregated together but aren't (e.g., if the site-wide
 * aggregation setting is disabled). This results in all LINK tags when
 * aggregation is enabled, a guarantee that as many or only slightly more tags
 * are used with aggregation disabled than enabled (so that if the limit were to
 * be crossed with aggregation enabled, the site developer would also notice the
 * problem while aggregation is disabled), and an easy way for a developer to
 * view HTML source while aggregation is disabled and know what files will be
 * aggregated together when aggregation becomes enabled.
 *
 * This function evaluates the aggregation enabled/disabled condition on a group
 * by group basis by testing whether an aggregate file has been made for the
 * group rather than by testing the site-wide aggregation setting. This allows
 * this function to work correctly even if modules have implemented custom
 * logic for grouping and aggregating files.
 *
 * @param array $elements
 *   A render array containing:
 *   - '#items': The CSS items as returned by drupal_add_css() and altered by
 *     drupal_get_css().
 *   - '#group_callback': A function to call to group #items to enable the use
 *     of fewer tags by aggregating files and/or using multiple @import
 *     statements within a single tag.
 *   - '#aggregate_callback': A function to call to aggregate the items within
 *     the groups arranged by the #group_callback function.
 *
 * @return array
 *   A render array that will render to a string of XHTML CSS tags.
 *
 * @see drupal_get_css()
 */
function advagg_pre_render_styles(array $elements) {

  // Skip if advagg is disabled.
  if (!advagg_enabled()) {
    return drupal_pre_render_styles($elements);
  }

  // Don't run it twice.
  if (!empty($elements['#groups'])) {
    return $elements;
  }

  // Group and aggregate the items.
  if (isset($elements['#group_callback'])) {

    // Call drupal_group_css().
    $elements['#groups'] = $elements['#group_callback']($elements['#items']);
  }
  if (isset($elements['#aggregate_callback'])) {

    // Call _advagg_aggregate_css().
    $elements['#aggregate_callback']($elements['#groups']);
  }

  // A dummy query-string is added to filenames, to gain control over
  // browser-caching. The string changes on every update or full cache
  // flush, forcing browsers to load a new copy of the files, as the
  // URL changed.
  $query_string = variable_get('css_js_query_string', '0');

  // For inline CSS to validate as XHTML, all CSS containing XHTML needs to be
  // wrapped in CDATA. To make that backwards compatible with HTML 4, we need to
  // comment out the CDATA-tag.
  // @see https://www.drupal.org/node/1021622
  list($embed_prefix, $embed_suffix) = advagg_get_css_prefix_suffix();

  // Defaults for LINK and STYLE elements.
  $link_element_defaults = array(
    '#type' => 'html_tag',
    '#tag' => 'link',
    '#attributes' => array(
      'type' => 'text/css',
      'rel' => 'stylesheet',
    ),
  );
  $style_element_defaults = array(
    '#type' => 'html_tag',
    '#tag' => 'style',
    '#attributes' => array(
      'type' => 'text/css',
    ),
  );

  // Loop through each group.
  foreach ($elements['#groups'] as $group) {
    switch ($group['type']) {

      // For file items, there are three possibilities.
      // - The group has been aggregated: in this case, output a LINK tag for
      //   the aggregate file.
      // - The group can be aggregated but has not been (most likely because
      //   the site administrator disabled the site-wide setting): in this case,
      //   output as few STYLE tags for the group as possible, using @import
      //   statement for each file in the group. This enables us to stay within
      //   IE's limit of 31 total CSS inclusion tags.
      // - The group contains items not eligible for aggregation (their
      //   'preprocess' flag has been set to FALSE): in this case, output a LINK
      //   tag for each file.
      case 'file':

        // The group has been aggregated into a single file: output a LINK tag
        // for the aggregate file.
        if (isset($group['data'])) {
          $element = $link_element_defaults;
          $element['#attributes']['href'] = advagg_file_create_url($group['data']);
          $element['#attributes']['media'] = $group['media'];
          $element['#browsers'] = $group['browsers'];
          if (!empty($group['attributes'])) {
            $element['#attributes'] += $group['attributes'];
          }
          $elements[] = $element;
        }
        elseif ($group['preprocess']) {
          $import = array();
          foreach ($group['items'] as $item) {

            // A theme's .info file may have an entry for a file that doesn't
            // exist as a way of overriding a module or base theme CSS file from
            // being added to the page. Normally, file_exists() calls that need
            // to run for every page request should be minimized, but this one
            // is okay, because it only runs when CSS aggregation is disabled.
            // On a server under heavy enough load that file_exists() calls need
            // to be minimized, CSS aggregation should be enabled, in which case
            // this code is not run. When aggregation is enabled,
            // drupal_load_stylesheet() checks file_exists(), but only when
            // building the aggregate file, which is then reused for many page
            // requests.
            if (file_exists($item['data'])) {

              // The dummy query string needs to be added to the URL to control
              // browser-caching. IE7 does not support a media type on the
              // "@import" statement, so we instead specify the media for the
              // group on the STYLE tag.
              $import[] = '@import url("' . check_plain(advagg_file_create_url($item['data']) . '?' . $query_string) . '");';
            }
          }

          // In addition to IE's limit of 31 total CSS inclusion tags, it also
          // has a limit of 31 @import statements per STYLE tag.
          while (!empty($import)) {
            $import_batch = array_slice($import, 0, 31);
            $import = array_slice($import, 31);
            $element = $style_element_defaults;

            // This simplifies the JavaScript regex, allowing each line
            // (separated by \n) to be treated as a completely different string.
            // This means that we can use ^ and $ on one line at a time, and not
            // worry about style tags since they'll never match the regex.
            $element['#value'] = "\n" . implode("\n", $import_batch) . "\n";
            $element['#attributes']['media'] = $group['media'];
            $element['#browsers'] = $group['browsers'];
            if (!empty($group['attributes'])) {
              $element['#attributes'] += $group['attributes'];
            }
            $elements[] = $element;
          }
        }
        else {
          foreach ($group['items'] as $item) {
            $element = $link_element_defaults;

            // We do not check file_exists() here, because this code runs for
            // files whose 'preprocess' is set to FALSE, and therefore, even
            // when aggregation is enabled, and we want to avoid needlessly
            // taxing a server that may be under heavy load. The file_exists()
            // performed above for files whose 'preprocess' is TRUE is done for
            // the benefit of theme .info files, but code that deals with files
            // whose 'preprocess' is FALSE is responsible for ensuring the file
            // exists.
            // The dummy query string needs to be added to the URL to control
            // browser-caching.
            $query_string_separator = strpos($item['data'], '?') !== FALSE ? '&' : '?';
            $element['#attributes']['href'] = advagg_file_create_url($item['data']) . $query_string_separator . $query_string;
            $element['#attributes']['media'] = $item['media'];
            $element['#browsers'] = $group['browsers'];
            if (!empty($group['attributes'])) {
              $element['#attributes'] += $group['attributes'];
            }
            $elements[] = $element;
          }
        }
        break;

      // For inline content, the 'data' property contains the CSS content. If
      // the group's 'data' property is set, then output it in a single STYLE
      // tag. Otherwise, output a separate STYLE tag for each item.
      case 'inline':
        if (isset($group['data'])) {
          $element = $style_element_defaults;
          $element['#value'] = $group['data'];
          $element['#value_prefix'] = $embed_prefix;
          $element['#value_suffix'] = $embed_suffix;
          $element['#attributes']['media'] = $group['media'];
          $element['#browsers'] = $group['browsers'];
          if (!empty($group['attributes'])) {
            $element['#attributes'] += $group['attributes'];
          }
          $elements[] = $element;
        }
        else {
          foreach ($group['items'] as $item) {
            $element = $style_element_defaults;
            $element['#value'] = $item['data'];
            $element['#value_prefix'] = $embed_prefix;
            $element['#value_suffix'] = $embed_suffix;
            $element['#attributes']['media'] = $item['media'];
            $element['#browsers'] = $group['browsers'];
            if (!empty($group['attributes'])) {
              $element['#attributes'] += $group['attributes'];
            }
            $elements[] = $element;
          }
        }
        break;

      // Output a LINK tag for each external item. The item's 'data' property
      // contains the full URL.
      case 'external':
        foreach ($group['items'] as $item) {
          $element = $link_element_defaults;

          // Convert to protocol relative path.
          $file_uri = $item['data'];
          if (variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH)) {
            $file_uri = advagg_convert_abs_to_protocol($item['data']);
          }
          $element['#attributes']['href'] = $file_uri;
          $element['#attributes']['media'] = $item['media'];
          $element['#browsers'] = $group['browsers'];
          if (!empty($group['attributes'])) {
            $element['#attributes'] += $group['attributes'];
          }
          $elements[] = $element;
        }
        break;
    }
  }
  return $elements;
}

/**
 * Default callback to group JavaScript items.
 *
 * This function arranges the JavaScript items that are in the #items property
 * of the scripts element into groups. When aggregation is enabled, files within
 * a group are aggregated into a single file, significantly improving page
 * loading performance by minimizing network traffic overhead.
 *
 * This function puts multiple items into the same group if they are groupable
 * and if they are for the same browsers. Items of the 'file' type are groupable
 * if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or
 * 'external' type are not groupable.
 *
 * This function also ensures that the process of grouping items does not change
 * their relative order. This requirement may result in multiple groups for the
 * same type and browsers, if needed to accommodate other items in
 * between.
 *
 * @param array $javascript
 *   An array of JavaScript items, as returned by drupal_add_js(), but after
 *   alteration performed by drupal_get_js().
 *
 * @return array
 *   An array of JavaScript groups. Each group contains the same keys (e.g.,
 *   'data', etc.) as a JavaScript item from the $javascript parameter, with the
 *   value of each key applying to the group as a whole. Each group also
 *   contains an 'items' key, which is the subset of items from $javascript that
 *   are in the group.
 *
 * @see drupal_pre_render_scripts()
 */
function advagg_group_js(array $javascript) {
  $groups = array();

  // If a group can contain multiple items, we track the information that must
  // be the same for each item in the group, so that when we iterate the next
  // item, we can determine if it can be put into the current group, or if a
  // new group needs to be made for it.
  $current_group_keys = NULL;
  $index = -1;
  foreach ($javascript as $key => $item) {
    if (empty($item)) {
      continue;
    }

    // The browsers for which the JavaScript item needs to be loaded is part of
    // the information that determines when a new group is needed, but the order
    // of keys in the array doesn't matter, and we don't want a new group if all
    // that's different is that order.
    if (isset($item['browsers'])) {
      ksort($item['browsers']);
    }
    else {
      $item['browsers'] = array();
    }

    // Fix missing types.
    if (empty($item['type'])) {

      // Setting is easy.
      if ($key === 'settings') {
        $item['type'] = 'setting';
      }
      elseif (stripos($item['data'], 'http://') === 0 || stripos($item['data'], 'https://') === 0 || strpos($item['data'], '//') === 0 && strpos($item['data'], '///') === FALSE) {
        $item['type'] = 'external';
      }
      elseif (strpos($item['data'], ';') !== FALSE || strpos($item['data'], "\n") || strpos($item['data'], "\$") || strpos($item['data'], "'") || strpos($item['data'], '"')) {
        $item['type'] = 'inline';
      }
      elseif (stripos(strrev($item['data']), strrev('.js')) === 0) {
        $item['type'] = 'file';
      }
    }
    switch ($item['type']) {
      case 'file':

        // Group file items if their 'preprocess' flag is TRUE.
        // Help ensure maximum reuse of aggregate files by only grouping
        // together items that share the same 'group' value and 'every_page'
        // flag. See drupal_add_js() for details about that.
        $group_keys = !empty($item['preprocess']) ? array(
          $item['type'],
          $item['group'],
          $item['every_page'],
          $item['browsers'],
        ) : FALSE;
        break;
      case 'external':
      case 'setting':
      case 'inline':

        // Do not group external, settings, and inline items.
        $group_keys = FALSE;
        break;
      default:

        // Define this here so we don't get undefined alerts down below.
        $group_keys = NULL;

        // Log the error as well.
        watchdog('advagg', 'Bad javascript was added. Type is unknown. @key - @item', array(
          '@key' => $key,
          '@item' => print_r($item, TRUE),
        ), WATCHDOG_NOTICE);
        break;
    }

    // If the group keys don't match the most recent group we're working with,
    // then a new group must be made.
    if ($group_keys !== $current_group_keys) {
      ++$index;

      // Initialize the new group with the same properties as the first item
      // being placed into it. The item's 'data' and 'weight' properties are
      // unique to the item and should not be carried over to the group.
      $groups[$index] = $item;
      unset($groups[$index]['data'], $groups[$index]['weight']);
      $groups[$index]['items'] = array();
      $current_group_keys = $group_keys ? $group_keys : NULL;
    }

    // Add the item to the current group.
    $groups[$index]['items'][] = $item;
  }
  return $groups;
}

/**
 * Stable sort for CSS and JS items.
 *
 * Preserves the order of items with equal sort criteria.
 *
 * The function will sort by:
 * - $item['group'],      integer, ascending
 * - $item['every_page'], boolean, first TRUE then FALSE
 * - $item['weight'],     integer, ascending
 *
 * @param array &$items
 *   Array of JS or CSS items, as in drupal_add_css() and drupal_add_js().
 *   The array keys can be integers or strings. The items themselves are arrays.
 *
 * @see drupal_get_css()
 * @see drupal_get_js()
 * @see drupal_add_css()
 * @see drupal_add_js()
 * @see https://drupal.org/node/1388546
 */
function advagg_drupal_sort_css_js_stable(array &$items) {

  // Within a group, order all infrequently needed, page-specific files after
  // common files needed throughout the website. Separating this way allows for
  // the aggregate file generated for all of the common files to be reused
  // across a site visit without being cut by a page using a less common file.
  $nested = array();
  foreach ($items as $key => &$item) {

    // If weight is not set, make it 0.
    if (!isset($item['weight'])) {
      $item['weight'] = 0;
    }

    // If every_page is not set, make it FALSE.
    if (!isset($item['every_page'])) {
      $item['every_page'] = FALSE;
    }

    // If group is not set, make it CSS_DEFAULT/JS_DEFAULT (0).
    if (!isset($item['group'])) {
      $item['group'] = 0;
    }

    // If scope is not set, make it header.
    if (!isset($item['scope'])) {
      $item['scope'] = 'header';
    }

    // Weight cast to string to preserve float.
    $weight = (string) $item['weight'];
    $nested[$item['group']][$item['every_page'] ? 1 : 0][$weight][$key] = $item;
  }

  // First order by group, so that, for example, all items in the CSS_SYSTEM
  // group appear before items in the CSS_DEFAULT group, which appear before
  // all items in the CSS_THEME group. Modules may create additional groups by
  // defining their own constants.
  $sorted = array();

  // Sort group; then iterate over it.
  ksort($nested);
  foreach ($nested as &$group_items) {

    // Reverse sort every_page; then iterate over it.
    krsort($group_items);
    foreach ($group_items as &$ep_items) {

      // Sort weight; then iterate over it.
      ksort($ep_items);

      // Finally, order by weight.
      foreach ($ep_items as &$weight_items) {
        foreach ($weight_items as $key => &$item) {
          $sorted[$key] = $item;
        }
        unset($item);
      }
    }
    unset($ep_items);
  }
  unset($group_items);
  $items = $sorted;
}

/**
 * Converts a PHP variable into its JavaScript equivalent.
 *
 * @param mixed $data
 *   Usually an array of data to be converted into a JSON string.
 *
 * @return string
 *   If there are no errors, this will return a JSON string. FALSE will be
 *   returned on failure.
 */
function advagg_json_encode($data) {

  // Different versions of PHP handle json_encode() differently.
  static $php550;
  static $php540;
  static $php530;
  if (!isset($php550)) {
    $php550 = version_compare(PHP_VERSION, '5.5.0', '>=');
  }
  if (!isset($php540)) {
    $php540 = version_compare(PHP_VERSION, '5.4.0', '>=');
  }
  if (!isset($php530)) {
    $php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
  }

  // Use fallback drupal encoder if PHP < 5.3.0.
  if (!$php530) {
    return @drupal_json_encode($data);
  }

  // Default json encode options.
  $options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT;
  if ($php550 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) {

    // Output partial json if not in development mode and PHP >= 5.5.0.
    $options |= JSON_PARTIAL_OUTPUT_ON_ERROR;
  }
  if ($php540 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {

    // Pretty print JSON if in development mode and PHP >= 5.4.0.
    $options |= JSON_PRETTY_PRINT;
  }

  // Encode to JSON.
  $json_data = @json_encode($data, $options);

  // Uses json_last_error() if in development mode.
  if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
    $error_number = json_last_error();
    switch ($error_number) {
      case JSON_ERROR_NONE:
        $error_message = '';
        break;
      case JSON_ERROR_DEPTH:
        $error_message = 'Maximum stack depth exceeded';
        break;
      case JSON_ERROR_STATE_MISMATCH:
        $error_message = 'Underflow or the modes mismatch';
        break;
      case JSON_ERROR_CTRL_CHAR:
        $error_message = 'Unexpected control character found';
        break;
      case JSON_ERROR_SYNTAX:
        $error_message = 'Syntax error, malformed JSON';
        break;
      case JSON_ERROR_UTF8:
        $error_message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
        break;
      default:
        $error_message = 'Unknown error: ' . $error_number;
        break;
    }
    if (!empty($error_message)) {
      if (is_callable('httprl_pr')) {
        $pretty_data = httprl_pr($data);
      }
      elseif (is_callable('kprint_r')) {

        // @codingStandardsIgnoreLine
        $pretty_data = kprint_r($data, TRUE);
      }
      else {
        $pretty_data = '<pre>' . filter_xss(print_r($data, TRUE)) . '</pre>';
      }
      watchdog('advagg_json', 'Error with json encoding the Drupal.settings value. Error Message: %error_message. JSON Data: !data', array(
        '%error_message' => $error_message,
        '!data' => $pretty_data,
      ), WATCHDOG_ERROR);
    }
  }
  return $json_data;
}

/**
 * Will scan, flush, use, and report any changes to css/js files in aggregates.
 */
function advagg_scan_filesystem_for_changes_live() {
  static $function_has_ran;
  if (isset($function_has_ran)) {
    return;
  }
  $function_has_ran = TRUE;
  $bypass_cookie = FALSE;
  $cookie_name = 'AdvAggDisabled';
  $key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal'));
  if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
    $bypass_cookie = TRUE;
  }
  if (!advagg_enabled() && !$bypass_cookie || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) {
    return;
  }

  // Scan for changes to any CSS/JS files.
  module_load_include('inc', 'advagg', 'advagg.cache');
  $flushed = advagg_push_new_changes();

  // Report back the results.
  if (empty($flushed) || !user_is_logged_in()) {
    return;
  }
  list($css_path) = advagg_get_root_files_dir();
  $parts_uri = $css_path[1] . '/parts';
  foreach ($flushed as $filename => $data) {
    if (strpos($filename, $parts_uri) === 0) {

      // Do not report on css files manged in the parts directory.
      continue;
    }
    if (variable_get('advagg_show_file_changed_message', ADVAGG_SHOW_FILE_CHANGED_MESSAGE)) {
      $ext = pathinfo($filename, PATHINFO_EXTENSION);
      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. Trigger: <code>@changes</code>', array(
        '%filename' => $filename,
        '%db_usage' => count($data[0]),
        '%db_count' => count($data[1]),
        '@changes' => print_r($data[2], TRUE),
        '%type' => $ext,
      )));
    }
  }
}

/**
 * Checks if the filename matches the advagg file pattern.
 *
 * @param string $filename
 *   Path to check.
 *
 * @return int
 *   Returns 1 if the pattern matches, 0 if it does not.
 */
function advagg_match_file_pattern($filename) {
  return preg_match('/.*(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$/', $filename);
}

/**
 * Converts absolute paths to be self references.
 *
 * @param string $path
 *   Path to check.
 * @param bool $strip_base_path
 *   Do no add the base path to the given path if TRUE.
 *
 * @return string
 *   The path.
 */
function advagg_convert_abs_to_rel($path, $strip_base_path = FALSE) {
  $base_url = $GLOBALS['base_url'];

  // Add a slash to end if none is found.
  if (strpos(strrev($base_url), '/') !== 0) {
    $base_url .= '/';
  }

  // Set base path.
  $base_path = $GLOBALS['base_path'];
  if ($strip_base_path) {
    $base_path = '';
  }

  // Do conversion of https and http to self references.
  $base_url_https = advagg_force_https_path($base_url);
  $path = str_replace($base_url_https, $base_path, $path);
  $base_url_http = advagg_force_http_path($base_url);
  $path = str_replace($base_url_http, $base_path, $path);
  $base_url = advagg_convert_abs_to_protocol($GLOBALS['base_url']);

  // Add a slash to end if none is found.
  if (strpos(strrev($base_url), '/') !== 0) {
    $base_url .= '/';
  }

  // Do conversion of protocol relative to self references.
  $path = str_replace($base_url, $base_path, $path);
  return $path;
}

/**
 * Converts absolute paths to be protocol relative paths.
 *
 * @param string $path
 *   Path to check.
 *
 * @return string
 *   The path.
 */
function advagg_convert_abs_to_protocol($path) {
  if (strpos($path, 'http://') === 0) {
    $path = substr($path, 5);
  }
  return $path;
}

/**
 * Convert http:// and // to https://.
 *
 * @param string $path
 *   Path to check.
 *
 * @return string
 *   The path.
 */
function advagg_force_https_path($path) {
  if (strpos($path, 'http://') === 0) {
    $path = 'https://' . substr($path, 7);
  }
  elseif (strpos($path, '//') === 0) {
    $path = 'https:' . $path;
  }
  return $path;
}

/**
 * Convert https:// to http://.
 *
 * @param string $path
 *   Path to check.
 *
 * @return string
 *   The path.
 */
function advagg_force_http_path($path) {
  if (strpos($path, 'https://') === 0) {
    $path = 'http://' . substr($path, 8);
  }
  return $path;
}

/**
 * Wrapper around file_create_url() to do post-processing on the created url.
 *
 * @param string $path
 *   Path to check.
 * @param array $aggregate_settings
 *   Array of settings used.
 * @param bool $run_file_create_url
 *   If TRUE then run the given path through file_create_url().
 * @param string $source_type
 *   CSS or JS; if empty url in not embedded in another file.
 *
 * @return string
 *   The file uri.
 */
function advagg_file_create_url($path, array $aggregate_settings = array(), $run_file_create_url = TRUE, $source_type = '') {
  $file_uri = $path;
  if ($run_file_create_url) {

    // This calls hook_file_url_alter().
    $file_uri = file_create_url($path);
  }
  elseif (strpos($path, '/') !== 0 && !advagg_is_external($path)) {
    $file_uri = '/' . $path;
  }

  // Ideally convert to relative path.
  if (isset($aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) && $aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'] || !isset($aggregate_settings['variables']['advagg_convert_absolute_to_relative_path']) && variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH)) {
    $file_uri = advagg_convert_abs_to_rel($file_uri);
  }

  // Next try protocol relative path.
  if (isset($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path']) && $aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'] || !isset($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path']) && variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH)) {
    $file_uri = advagg_convert_abs_to_protocol($file_uri);
  }
  if ($source_type === 'css' && !advagg_is_external($file_uri) && (isset($aggregate_settings['variables']['advagg_css_absolute_path']) && $aggregate_settings['variables']['advagg_css_absolute_path'] || !isset($aggregate_settings['variables']['advagg_css_absolute_path']) && variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH))) {

    // Get public dir.
    list($css_path) = advagg_get_root_files_dir();
    $parsed = parse_url($css_path[0]);
    $new_parsed = array();
    if (!empty($parsed['host'])) {
      $new_parsed['host'] = $parsed['host'];
    }
    if (!empty($parsed['path'])) {
      $new_parsed['path'] = $parsed['path'];
    }
    $css_path_0 = advagg_glue_url($new_parsed);
    $parsed = parse_url($css_path[1]);
    $new_parsed = array();
    if (!empty($parsed['host'])) {
      $new_parsed['host'] = $parsed['host'];
    }
    if (!empty($parsed['path'])) {
      $new_parsed['path'] = $parsed['path'];
    }
    $css_path_1 = advagg_glue_url($new_parsed);
    $pos = strpos($css_path_1, $css_path_0);
    if (!empty($pos)) {
      $public_dir = substr($css_path_1, 0, $pos);

      // If public dir is not in the file uri, use absolute URL.
      if (strpos($file_uri, $public_dir) === FALSE) {
        $file_uri = url($path, array(
          'absolute' => TRUE,
        ));
      }
    }
  }

  // Finally force https.
  if (isset($aggregate_settings['variables']['advagg_force_https_path']) && $aggregate_settings['variables']['advagg_force_https_path'] || !isset($aggregate_settings['variables']['advagg_force_https_path']) && variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH)) {
    $file_uri = advagg_force_https_path($file_uri);
  }
  return $file_uri;
}

/**
 * Loads the stylesheet and resolves all @import commands.
 *
 * Loads a stylesheet and replaces @import commands with the contents of the
 * imported file. Use this instead of file_get_contents when processing
 * stylesheets.
 *
 * The returned contents are compressed removing white space and comments only
 * when CSS aggregation is enabled. This optimization will not apply for
 * color.module enabled themes with CSS aggregation turned off.
 *
 * @param string $file
 *   Name of the stylesheet to be processed.
 * @param bool $optimize
 *   Defines if CSS contents should be compressed or not.
 * @param bool $reset_basepath
 *   Used internally to facilitate recursive resolution of @import commands.
 *
 * @return string
 *   Contents of the stylesheet, including any resolved @import commands.
 *
 * @see drupal_load_stylesheet()
 */
function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE, $contents = '') {

  // These static's are not cache variables, so we don't use drupal_static().
  static $_optimize, $basepath;
  if ($reset_basepath) {
    $basepath = '';
  }

  // Store the value of $optimize for preg_replace_callback with nested @import
  // loops.
  if (isset($optimize)) {
    $_optimize = $optimize;
  }

  // Stylesheets are relative one to each other. Start by adding a base path
  // prefix provided by the parent stylesheet (if necessary).
  if ($basepath && !file_uri_scheme($file)) {
    $file = $basepath . '/' . $file;
  }

  // Store the parent base path to restore it later.
  $parent_base_path = $basepath;

  // Set the current base path to process possible child imports.
  $basepath = dirname($file);

  // Load the CSS stylesheet. We suppress errors because themes may specify
  // stylesheets in their .info file that don't exist in the theme's path,
  // but are merely there to disable certain module CSS files.
  $content = '';
  if (empty($contents) && !empty($file)) {
    $contents = (string) @advagg_file_get_contents($file);
  }
  if ($contents) {

    // Return the processed stylesheet.
    $content = advagg_load_stylesheet_content($contents, $_optimize);
  }

  // Restore the parent base path as the file and its children are processed.
  $basepath = $parent_base_path;
  if ($_optimize) {
    $content = trim($content);
  }
  return $content;
}

/**
 * Decodes UTF byte-order mark (BOM) into the encoding's name.
 *
 * @param string $data
 *   The data possibly containing a BOM. This can be the entire contents of
 *   a file, or just a fragment containing at least the first five bytes.
 *
 * @return string|bool
 *   The name of the encoding, or FALSE if no byte order mark was present.
 *
 * @see https://api.drupal.org/api/drupal/core!lib!Drupal!Component!Utility!Unicode.php/function/Unicode%3A%3AencodingFromBOM/8
 */
function advagg_get_encoding_from_bom($data) {
  static $bom_map = array(
    "" => 'UTF-8',
    "" => 'UTF-16BE',
    "" => 'UTF-16LE',
    "" => 'UTF-32BE',
    "" => 'UTF-32LE',
    "+/v8" => 'UTF-7',
    "+/v9" => 'UTF-7',
    "+/v+" => 'UTF-7',
    "+/v/" => 'UTF-7',
    "+/v8-" => 'UTF-7',
  );
  foreach ($bom_map as $bom => $encoding) {
    if (strpos($data, $bom) === 0) {
      return $encoding;
    }
  }
  return FALSE;
}

/**
 * Converts data to UTF-8.
 *
 * Requires the iconv, GNU recode or mbstring PHP extension.
 *
 * @param string $data
 *   The data to be converted.
 * @param string $encoding
 *   The encoding that the data is in.
 *
 * @return string|bool
 *   Converted data or FALSE.
 */
function advagg_convert_to_utf8($data, $encoding) {
  if (function_exists('iconv')) {
    return @iconv($encoding, 'utf-8', $data);
  }
  elseif (function_exists('mb_convert_encoding')) {
    return @mb_convert_encoding($data, 'utf-8', $encoding);
  }
  elseif (function_exists('recode_string')) {
    return @recode_string($encoding . '..utf-8', $data);
  }

  // Cannot convert.
  return FALSE;
}

/**
 * Processes the contents of a stylesheet for aggregation.
 *
 * @param string $contents
 *   The contents of the stylesheet.
 * @param bool $optimize
 *   (Optional) Boolean whether CSS contents should be minified. Defaults to
 *   FALSE.
 *
 * @return string
 *   Contents of the stylesheet including the imported stylesheets.
 *
 * @see drupal_load_stylesheet_content()
 */
function advagg_load_stylesheet_content($contents, $optimize = FALSE) {

  // If a BOM is found, convert the file to UTF-8. Used for inline CSS here.
  $encoding = advagg_get_encoding_from_bom($contents);
  if (!empty($encoding)) {
    $contents = advagg_convert_to_utf8($contents, $encoding);
  }
  if ($optimize) {

    // Perform some safe CSS optimizations.
    // Regexp to match comment blocks.
    // Regexp to match double quoted strings.
    // Regexp to match single quoted strings.
    $comment = '/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/';
    $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
    $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";

    // Strip all comment blocks, but keep double/single quoted strings.
    $contents = preg_replace("<({$double_quot}|{$single_quot})|{$comment}>Ss", "\$1", $contents);

    // Remove certain whitespace.
    // There are different conditions for removing leading and trailing
    // whitespace.
    // @see http://php.net/manual/regexp.reference.subpatterns.php
    $contents = preg_replace('<
      # Do not strip any space from within single or double quotes
      (' . $double_quot . '|' . $single_quot . ')
      # Strip leading and trailing whitespace.
      | \\s*([@{};,])\\s*
      # Strip only leading whitespace from:
      # - Closing parenthesis: Retain "@media (bar) and foo".
      | \\s+([\\)])
      # Strip only trailing whitespace from:
      # - Opening parenthesis: Retain "@media (bar) and foo".
      # - Colon: Retain :pseudo-selectors.
      | ([\\(:])\\s+
    >xSs', '$1$2$3$4', $contents);

    // End the file with a new line.
    $contents = trim($contents);
    $contents .= "\n";
  }

  // Remove multiple charset declarations for standards compliance (and fixing
  // Safari problems).
  $contents = preg_replace('/^@charset\\s+[\'"](\\S*?)\\b[\'"];/i', '', $contents);

  // Replaces @import commands with the actual stylesheet content.
  // This happens recursively but omits external files.
  $contents = preg_replace_callback('%@import\\s*+(?:url\\(\\s*+)?+[\'"]?+(?![a-z]++:|/)([^\'"()\\s]++)[\'"]?+\\s*+\\)?+\\s*+;%i', '_advagg_load_stylesheet', $contents);
  return $contents;
}

/**
 * Loads stylesheets recursively and returns contents with corrected paths.
 *
 * This function is used for recursive loading of stylesheets and
 * returns the stylesheet content with all url() paths corrected.
 *
 * @param array $matches
 *   The matches from preg_replace_callback().
 *
 * @return array
 *   String with altered internal url() paths.
 *
 * @see _drupal_load_stylesheet()
 */
function _advagg_load_stylesheet(array $matches) {
  $filename = $matches[1];

  // Load the imported stylesheet and replace @import commands in there as well.
  $file = advagg_load_stylesheet($filename, NULL, FALSE);
  if (empty($file)) {
    if (strpos($matches[0], 'http://') === 0 || strpos($matches[0], 'https://') === 0 || strpos($matches[0], '//') === 0) {
      return $matches[0];
    }
    if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) {
      watchdog('advagg-debug', 'Trying to load @file via @import statement but it was not found.', array(
        '@file' => $filename,
      ), WATCHDOG_DEBUG);
    }
    if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) <= 1) {
      return $matches[0];
    }
    else {
      return '';
    }
  }

  // Determine the file's directory.
  $directory = dirname($filename);

  // If the file is in the current directory, make sure '.' doesn't appear in
  // the url() path.
  $directory = $directory == '.' ? '' : $directory . '/';

  // Alter all internal url() paths. Leave external paths alone. We don't need
  // to normalize absolute paths here (i.e. remove folder/... segments) because
  // that will be done later.
  return preg_replace('%url\\(\\s*+([\'"]?+)(?![a-z]++:|/)([^\'")]+)([\'"]?+)\\s*\\)%i', 'url(\\1' . $directory . '\\2\\3)', $file);
}

/**
 * Check and see if the aggressive cache can safely be enabled.
 *
 * @return array
 *   If there are no conflicts, this will return an empty array.
 */
function advagg_aggressive_cache_conflicts() {
  $hooks = array(
    'css_alter' => TRUE,
    'js_alter' => TRUE,
  );
  foreach ($hooks as $hook => $values) {
    $hooks[$hook] = module_implements($hook);

    // Also check themes as drupal_alter() allows for themes to alter things.
    $themes = list_themes();
    $theme_keys = array_keys($themes);
    if (!empty($theme_keys)) {
      foreach ($theme_keys as $theme_key) {
        $function = $theme_key . '_' . $hook;

        // Search loaded themes.
        if (function_exists($function)) {
          $hooks[$hook][] = $theme_key;
          continue;
        }

        // Skip disabled themes.
        if (empty($themes[$theme_key]->status)) {
          continue;
        }

        // Search enabled but not loaded themes.
        $file = dirname($themes[$theme_key]->filename) . '/template.php';
        if (file_exists($file)) {
          $contents = (string) @advagg_file_get_contents($file);
          if (stripos($contents, $function)) {
            $hooks[$hook][] = $theme_key;
          }
        }
      }
    }
  }
  $whitelist = array(
    // Core.
    //
    // locale_js_directory variable; default: languages.
    // javascript_parsed variable; default: array().
    'locale',
    // No control; same every time.
    'simpletest',
    // No control; same every time.
    'seven',
    // Popular contrib.
    //
    // No control; same every time.
    'at_commerce',
    // ais_adaptive_styles variable; Default: array().
    // ais_adaptive_styles_method; Default: 'both-max'.
    // 'ais',
    //
    // No control; same every time.
    'bluecheese',
    // drupal_static('clientside_validation_settings') array.
    // 'clientside_validation',
    //
    // version_compare(VERSION, '7.14', '<').
    'conditional_fields',
    // _css_injector_load_rule() function.
    // Changes the weight of all files added in init so no special handling.
    // 'css_injector',
    //
    // disable_css_ . $theme . _all variable; default: FALSE.
    // disable_css_ . $theme . _modules; default: array().
    // disable_css_ . $theme . _files; default: array().
    // 'disable_css',
    //
    // Empty call; commented code is same every time.
    'elfinder',
    // excluded_css_custom variable; Default: ''.
    // excluded_javascript_custom variable; Default: ''.
    // 'excluded',
    //
    // No control; same every time.
    'fences',
    // jqmulti_jquery_path() function.
    // jqmulti_get_files() function.
    // jqmulti_load_always variable; Default: FALSE.
    // 'jqmulti',
    //
    // No control; same every time.
    'jquery_dollar',
    // labjs_suppress() function.
    'labjs_js',
    // Empty call.
    'panopoly_core',
    // speedy_js_production variable; Default: TRUE.
    'speedy',
    // logintoboggan_validating_id() function.
    'logintoboggan',
  );

  // Allow other modules to modify the $whitelist.
  // Call hook_advagg_aggressive_cache_conflicts_alter()
  drupal_alter('advagg_aggressive_cache_conflicts', $whitelist);
  $questionable_modules = array();
  foreach ($hooks as $hook => $modules) {
    foreach ($modules as $key => $module) {

      // Anything from advagg is ok.
      if (strpos($module, 'advagg') === 0 || strpos($module, '_advagg') === 0) {
        unset($modules[$key]);
        continue;
      }

      // Remove known modules that should work with aggressive caching.
      if (in_array($module, $whitelist)) {
        unset($modules[$key]);
      }
      else {
        $questionable_modules[$module] = $module;
      }
    }
  }
  return $questionable_modules;
}

/**
 * Alt to http_build_url().
 *
 * @param array $parsed
 *   Array from parse_url().
 * @param bool $strip_query_and_fragment
 *   If set to TRUE the query and fragment will be removed from the output.
 *
 * @return string
 *   URI is returned.
 *
 * @see http://php.net/parse-url#85963
 */
function advagg_glue_url(array $parsed, $strip_query_and_fragment = FALSE) {
  $uri = '';
  if (isset($parsed['scheme'])) {
    switch (strtolower($parsed['scheme'])) {

      // Mailto uri.
      case 'mailto':
        $uri .= $parsed['scheme'] . ':';
        break;

      // Protocol relative uri.
      case '//':
        $uri .= $parsed['scheme'];
        break;

      // Standard uri.
      default:
        $uri .= $parsed['scheme'] . '://';
    }
  }
  $uri .= isset($parsed['user']) ? $parsed['user'] . (isset($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : '';
  $uri .= isset($parsed['host']) ? $parsed['host'] : '';
  $uri .= !empty($parsed['port']) ? ':' . $parsed['port'] : '';
  if (isset($parsed['path'])) {
    $uri .= substr($parsed['path'], 0, 1) === '/' ? $parsed['path'] : (!empty($uri) ? '/' : '') . $parsed['path'];
  }
  if (!$strip_query_and_fragment) {
    $uri .= isset($parsed['query']) ? '?' . $parsed['query'] : '';
    $uri .= isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
  }
  return $uri;
}

/**
 * Clear certain caches on form submit.
 */
function advagg_cache_clear_admin_submit() {
  $cache_bins = advagg_flush_caches();
  foreach ($cache_bins as $bin) {
    cache_clear_all('*', $bin, TRUE);
  }
  cache_clear_all('hook_info', 'cache_bootstrap');
  cache_clear_all('advagg_hooks_implemented:', 'cache_bootstrap', TRUE);
}

/**
 * Get the resource hint settings for the preload attribute.
 *
 * @param bool $return_defaults
 *   Default FALSE, TRUE returns the default values.
 *
 * @return array
 *   Ordered 2 dimensional array.
 */
function advagg_get_resource_hints_preload_settings($return_defaults = FALSE) {
  $sub_defaults = array(
    'enabled' => 1,
    'push' => 0,
    'local' => 1,
    'external' => 1,
  );

  // Collect your data.
  $advagg_resource_hints_preload_settings_defaults = array(
    'style' => $sub_defaults + array(
      '#weight' => -10,
      'title' => t('CSS Files'),
    ),
    'font' => $sub_defaults + array(
      '#weight' => -9,
      'title' => t('Font Files'),
    ),
    'script' => $sub_defaults + array(
      '#weight' => -8,
      'title' => t('JS Files'),
    ),
    'svg' => $sub_defaults + array(
      '#weight' => -7,
      'title' => t('SVG Files'),
    ),
    'image' => $sub_defaults + array(
      '#weight' => -6,
      'title' => t('Image Files'),
    ),
    'all_others' => $sub_defaults + array(
      '#weight' => -5,
      'title' => t('All Other Files'),
    ),
  );
  if ($return_defaults) {
    return $advagg_resource_hints_preload_settings_defaults;
  }
  $advagg_resource_hints_preload_settings = variable_get('advagg_resource_hints_preload_settings', $advagg_resource_hints_preload_settings_defaults);

  // Merge in defaults.
  foreach ($advagg_resource_hints_preload_settings as $id => &$entry) {
    if (isset($advagg_resource_hints_preload_settings_defaults[$id])) {
      $entry += $advagg_resource_hints_preload_settings_defaults[$id];
    }
    ksort($entry);
  }
  unset($entry);

  // Sort the rows.
  uasort($advagg_resource_hints_preload_settings, 'element_sort');
  return $advagg_resource_hints_preload_settings;
}

/**
 * See if the .htaccess file uses the RewriteBase directive.
 *
 * @param string $location
 *   The location of the .htaccess file.
 *
 * @return string
 *   The last active RewriteBase entry in htaccess.
 */
function advagg_htaccess_rewritebase($location = DRUPAL_ROOT) {
  if (is_readable($location . '/.htaccess')) {
    $htaccess = advagg_file_get_contents($location . '/.htaccess');
    $matches = array();
    $found = preg_match_all('/\\n\\s*RewriteBase\\s.*/i', $htaccess, $matches);
    if ($found && !empty($matches[0])) {
      $matches[0] = array_map('trim', $matches[0]);
      return array_pop($matches[0]);
    }
  }
  return '';
}

/**
 * Get the latest version number for the remote version.
 *
 * @param array $library
 *   An associative array containing all information about the library.
 * @param array $options
 *   An associative array containing options for the version parser.
 * @param string $url
 *   URL for the remote version to lookup.
 *
 * @return string
 *   The latest version number as a string or 0 on failure.
 */
function advagg_get_github_version_json(array $library, array $options, $url) {
  $http_options = array(
    'timeout' => 2,
  );
  $package = drupal_http_request($url, $http_options);
  if (empty($package->data)) {

    // Try again.
    $package = drupal_http_request($url, array(
      'timeout' => 5,
    ));
  }
  if (empty($package->data)) {

    // Try again but force http.
    $url = advagg_force_http_path($url);
    $package = drupal_http_request($url, $http_options);
  }
  if (!empty($package->data)) {
    $package = json_decode($package->data);
    if (isset($package->version)) {
      return (string) $package->version;
    }
  }
  return 0;
}

/**
 * Get the latest version number for the remote version.
 *
 * @param array $library
 *   An associative array containing all information about the library.
 * @param array $options
 *   An associative array containing options for the version parser.
 * @param string $url
 *   URL for the remote version to lookup.
 *
 * @return string
 *   The latest version number as a string or 0 on failure.
 */
function advagg_get_github_version_txt(array $library, array $options, $url) {
  $http_options = array(
    'timeout' => 2,
  );
  $request = drupal_http_request($url, $http_options);
  if (empty($request->data)) {

    // Try again.
    $request = drupal_http_request($url, array(
      'timeout' => 5,
    ));
  }
  if (empty($request->data)) {

    // Try again but force http.
    $url = advagg_force_http_path($url);
    $request = drupal_http_request($url, $http_options);
  }
  if (!empty($request->data)) {
    $matches = array();
    if (preg_match($options['pattern'], $request->data, $matches)) {
      return $matches[1];
    }
  }
  return '0';
}

/**
 * Update github version numbers to the latest.
 *
 * @param bool $refresh
 *   Set to TRUE to skip the cache and force a refresh of the data.
 *
 * @return mixed
 *   Key Value pair of the project name and remote version number. If $target is
 *   set then that version number is returned.
 */
function advagg_get_remote_libraries_versions($refresh = FALSE) {
  $cid = __FUNCTION__;
  $versions = array();
  if (!$refresh) {
    $versions = advagg_get_remote_libraries_versions_cache($cid);
    if (!empty($versions)) {
      return $versions;
    }
  }
  if (is_callable('libraries_info')) {
    $libraries = libraries_info();
    foreach ($libraries as $key => $library) {

      // Get current version.
      $libraries_detect = libraries_detect($key);
      if (isset($libraries_detect['version'])) {
        $versions[$key]['local'] = $libraries_detect['version'];
      }
      elseif (!empty($libraries_detect['local version'])) {
        $versions[$key]['local'] = $libraries_detect['local version'];
      }

      // Check if callback is live.
      $remote = advagg_get_remote_libraries_version($key, $library, FALSE);
      if (!empty($remote)) {
        $versions[$key]['remote'] = $remote;
      }
    }
  }
  if (!empty($versions)) {
    cache_set($cid, $versions, 'cache_advagg_info');
  }
  return $versions;
}

/**
 * Get the remote and local versions cache of the available libraries.
 *
 * @param string $cid
 *   Cache ID.
 *
 * @return array
 *   Library name => (local, remote).
 */
function advagg_get_remote_libraries_versions_cache($cid = '') {
  if (empty($cid)) {
    $cid = 'advagg_get_remote_libraries_versions';
  }
  $versions =& drupal_static($cid, array());
  if (empty($versions)) {
    $cache = cache_get($cid, 'cache_advagg_info');
    if (!empty($cache) && !empty($cache->data)) {
      $versions = $cache->data;
    }
  }
  return $versions;
}

/**
 * Get the latest version number for the remote version.
 *
 * @param string $name
 *   Name of the library.
 * @param array $library
 *   An associative array containing all information about the library.
 * @param bool $use_cache
 *   TRUE try the cache first.
 *
 * @return string
 *   The latest version number as a string or 0 on failure.
 */
function advagg_get_remote_libraries_version($name, array $library, $use_cache = TRUE) {
  if ($use_cache) {
    $cid = 'advagg_get_remote_libraries_versions';
    $versions = advagg_get_remote_libraries_versions_cache($cid);
    if (isset($versions[$name]['remote'])) {
      return $versions[$name]['remote'];
    }
  }

  // Remote url is not set, see if we can generate it given the current data.
  if (empty($library['remote']['url']) && !empty($library['version arguments'])) {
    if (!isset($library['version arguments']['file']) && isset($library['version arguments']['variants'])) {

      // Use the first variant.
      $file = reset($library['version arguments']['variants']);
      $library['version arguments']['file'] = $file['file'];
      $library['version arguments']['pattern'] = $file['pattern'];
    }
    if (!empty($library['version arguments']['file'])) {
      if (!empty($library['vendor url'])) {

        // Use vendor url if it's a github one.
        if (strpos($library['vendor url'], 'https://github.com/') === 0) {
          $parsed_vendor = @parse_url($library['vendor url']);

          // Previously: https://rawgit.com{$parsed_vendor['path']}/master/{$library['version arguments']['file']} .
          $library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_vendor['path']}@master/{$library['version arguments']['file']}";
        }
      }
      if (empty($library['remote']['url']) && !empty($library['download url'])) {

        // Use download url if it's a github one.
        if (strpos($library['download url'], 'https://github.com/') === 0) {
          $parsed_download = @parse_url($library['download url']);
          $paths = explode('/', $parsed_download['path']);
          $parsed_download['path'] = "/{$paths[1]}/{$paths[2]}";

          // Previously: https://rawgit.com{$parsed_download['path']}/master/{$library['version arguments']['file']} .
          $library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_download['path']}@master/{$library['version arguments']['file']}";
        }
      }
    }
  }

  // Remote callback is not set, try to generate it given the current data.
  if (empty($library['remote']['callback']) && isset($library['version arguments']['file'])) {
    if (!empty($library['version callback'])) {

      // Use defined parser.
      $library['remote']['callback'] = $library['version callback'];
    }
    else {
      if ($library['version arguments']['file'] === 'package.json') {

        // JSON parser.
        $library['remote']['callback'] = 'advagg_get_github_version_json';
      }
      else {

        // Text parser.
        $library['remote']['callback'] = 'advagg_get_github_version_txt';
      }
    }
  }

  // Get remote version.
  $return = 0;
  if (!empty($library['remote']) && !empty($library['remote']['callback']) && !empty($library['remote']['url']) && isset($library['version arguments']) && is_callable($library['remote']['callback']) && variable_get('advagg_remote_version_check', ADVAGG_REMOTE_VERSION_CHECK)) {
    $return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']);

    // Try package.json on failure.
    if (empty($return) && $library['version arguments']['file'] !== 'package.json') {
      $pos = strrpos($library['remote']['url'], $library['version arguments']['file']);
      $library['remote']['url'] = substr($library['remote']['url'], 0, $pos) . 'package.json';
      $library['remote']['callback'] = 'advagg_get_github_version_json';
      $return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']);
    }
  }
  if (empty($return) && !empty($library['version arguments']['default_version'])) {
    $return = $library['version arguments']['default_version'];
  }
  return $return;
}

/**
 * Get the latest version number for the remote version.
 *
 * @param string $name
 *   Name of the library.
 * @param string $module_name
 *   Name of the module where the library is registered.
 *
 * @return array
 *   The library array.
 */
function advagg_get_library($name, $module_name) {
  $library = cache_get($name, 'cache_libraries');
  if ($library) {
    $library = $library->data;
  }
  else {
    if (is_callable('libraries_detect')) {
      $library = libraries_detect($name);
    }
    elseif (is_callable("{$module_name}_libraries_info")) {
      $library = call_user_func("{$module_name}_libraries_info");
      $library = $library[$name];
    }
  }
  return $library;
}

/**
 * Alter the js array fixing the type key if set incorrectly.
 *
 * @param array $array
 *   CSS or JS array.
 * @param string $type
 *   CSS or JS.
 */
function advagg_fix_type(array &$array, $type) {

  // Skip if advagg is disabled.
  if (!advagg_enabled()) {
    return;
  }

  // Skip if setting is turned off.
  if ($type === 'css' && !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) {
    return;
  }
  if ($type === 'js' && !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) {
    return;
  }

  // Fix type if it was incorrectly set.
  // Get hostname and base path.
  $mod_base_url = substr($GLOBALS['base_root'] . $GLOBALS['base_path'], strpos($GLOBALS['base_root'] . $GLOBALS['base_path'], '//') + 2);
  $mod_base_url_len = strlen($mod_base_url);
  foreach ($array as &$value) {

    // Skip if the data is empty or not a string.
    if (empty($value['data']) || !is_string($value['data'])) {
      continue;
    }

    // Default to file if type is not set.
    if (!isset($value['type'])) {
      $value['type'] = 'file';
    }

    // If inline, be done with processing.
    if ($value['type'] === 'inline') {
      continue;
    }

    // Default to file if not file, inline, external, setting.
    if ($value['type'] !== 'file' && $value['type'] !== 'inline' && $value['type'] !== 'external' && $value['type'] !== 'setting') {
      if ($value['type'] === 'settings') {
        $value['type'] = 'setting';
      }
      else {
        $value['type'] = 'file';
      }
    }
    $lower = strtolower($value['data']);
    $http_pos = strpos($lower, 'http://');
    $https_pos = strpos($lower, 'https://');
    $double_slash_pos = strpos($lower, '//');
    $tripple_slash_pos = strpos($lower, '///');
    $mod_base_url_pos = stripos($value['data'], $mod_base_url);

    // If type is external but doesn't start with http, https, or // change it
    // to file.
    if ($value['type'] === 'external' && $http_pos !== 0 && $https_pos !== 0 && $double_slash_pos !== 0) {
      if (is_readable($value['data'])) {
        $value['type'] = 'file';
      }
      else {

        // Fix for subdir issues.
        $parsed = parse_url($value['data']);
        if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) {
          $path = substr($parsed['path'], strlen($GLOBALS['base_path']));
          if (is_readable($path)) {
            $value['data'] = $path;
            $value['type'] = 'file';
          }
        }
      }
    }

    // If type is file but it starts with http, https, or // change it to
    // external. Skip tripple slash for local files.
    if ($value['type'] === 'file' && ($http_pos === 0 || $https_pos === 0 || $double_slash_pos === 0 && $tripple_slash_pos === FALSE)) {
      $value['type'] = 'external';
    }

    // If type is external and starts with http, https, or // but points to
    // this host change to file, but move it to the top of the aggregation
    // stack.
    if ($value['type'] === 'external' && $mod_base_url_pos - 2 === $double_slash_pos && ($http_pos === 0 || $https_pos === 0 || $double_slash_pos === 0)) {
      $path = substr($value['data'], $mod_base_url_pos + $mod_base_url_len);
      if (is_readable($path)) {
        $value['data'] = $path;
        $value['type'] = 'file';
        $value['group'] = JS_LIBRARY;
        $value['every_page'] = TRUE;
        $value['weight'] = -40000;
      }
      else {

        // Fix for subdir issues.
        $parsed = parse_url($path);
        if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) {
          $path = substr($parsed['path'], strlen($GLOBALS['base_path']));
          if (is_readable($path)) {
            $value['data'] = $path;
            $value['type'] = 'file';
            $value['group'] = JS_LIBRARY;
            $value['every_page'] = TRUE;
            $value['weight'] = -40000;
          }
        }
      }
    }
  }
  unset($value);
}

/**
 * Alter the CSS or JS array removing empty files from the aggregates.
 *
 * @param array $array
 *   CSS or JS array.
 */
function advagg_remove_empty_files(array &$array) {
  if (!variable_get('advagg_js_remove_empty_files', ADVAGG_JS_REMOVE_EMPTY_FILES)) {
    return;
  }
  if (variable_get('advagg_fast_filesystem', ADVAGG_FAST_FILESYSTEM)) {
    foreach ($array as $key => $value) {
      if ($value['type'] !== 'file') {
        continue;
      }

      // Check locally.
      if (!is_readable($value['data']) || filesize($value['data']) == 0) {
        unset($array[$key]);
      }
    }
  }
  else {
    module_load_include('inc', 'advagg', 'advagg');
    $files = array();
    foreach ($array as $key => $value) {
      if ($value['type'] !== 'file') {
        continue;
      }
      $files[$key] = $value['data'];
    }

    // Check cache/db.
    $info = advagg_get_info_on_files($files);
    foreach ($info as $key => $values) {
      if (empty($values['filesize'])) {
        $key = array_search($values['data'], $files);
        if (isset($array[$key])) {
          unset($array[$key]);
        }
      }
    }
  }
}

/**
 * Alter the CSS or JS array adding in DNS domain info.
 *
 * @param array $array
 *   CSS or JS array.
 * @param string $type
 *   CSS or JS.
 */
function advagg_add_default_dns_lookups(array &$array, $type) {

  // Skip if advagg is disabled.
  if (!advagg_enabled()) {
    return;
  }

  // Remove this return once CSS lookups are needed.
  if ($type !== 'js') {
    return;
  }

  // Add DNS information for some of the more popular modules.
  foreach ($array as &$value) {
    if (!is_string($value['data'])) {
      continue;
    }

    // Google Ad Manager.
    if (strpos($value['data'], '/google_service.') !== FALSE) {
      if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) {
        $temp = $value['dns_prefetch'];
        unset($value['dns_prefetch']);
        $value['dns_prefetch'] = array(
          $temp,
        );
      }

      // Domains in the google_service.js file.
      $value['dns_prefetch'][] = 'https://csi.gstatic.com';
      $value['dns_prefetch'][] = 'https://pubads.g.doubleclick.net';
      $value['dns_prefetch'][] = 'https://partner.googleadservices.com';
      $value['dns_prefetch'][] = 'https://securepubads.g.doubleclick.net';

      // Domains in the google_ads.js file.
      $value['dns_prefetch'][] = 'https://pagead2.googlesyndication.com';

      // Other domains that usually get hit.
      $value['dns_prefetch'][] = 'https://cm.g.doubleclick.net';
      $value['dns_prefetch'][] = 'https://tpc.googlesyndication.com';
    }

    // Google Analytics.
    if (strpos($value['data'], 'GoogleAnalyticsObject') !== FALSE || strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE) {
      if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) {
        $temp = $value['dns_prefetch'];
        unset($value['dns_prefetch']);
        $value['dns_prefetch'] = array(
          $temp,
        );
      }
      if ($GLOBALS['is_https'] && strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE) {
        $value['dns_prefetch'][] = 'https://ssl.google-analytics.com';
      }
      else {
        $value['dns_prefetch'][] = 'https://www.google-analytics.com';
      }
      $value['dns_prefetch'][] = 'https://stats.g.doubleclick.net';
    }
  }
}

/**
 * Insert element into an array at a specific position.
 *
 * @param array $input_array
 *   The original array.
 * @param array $new_value
 *   The element that is getting inserted.
 * @param int $location_key
 *   The key location.
 *
 * @return array
 *   The new array with the element merged in.
 */
function advagg_insert_into_array_at_location(array $input_array, array $new_value, $location_key) {
  return array_merge(array_slice($input_array, 0, $location_key), $new_value, array_slice($input_array, $location_key));
}

/**
 * Insert element into an array at a specific key location.
 *
 * @param array $input_array
 *   The original array.
 * @param array $insert
 *   The element that is getting inserted; array(key => value).
 * @param string $target_key
 *   The key name.
 * @param int $location
 *   After is 1 , 0 is replace, -1 is before.
 *
 * @return array
 *   The new array with the element merged in.
 */
function advagg_insert_into_array_at_key(array $input_array, array $insert, $target_key, $location = 1) {
  $output = array();
  $new_value = reset($insert);
  $new_key = key($insert);
  foreach ($input_array as $key => $value) {
    if ($key === $target_key) {

      // Insert before.
      if ($location == -1) {
        $output[$new_key] = $new_value;
        $output[$key] = $value;
      }

      // Replace.
      if ($location == 0) {
        $output[$new_key] = $new_value;
      }

      // After.
      if ($location == 1) {
        $output[$key] = $value;
        $output[$new_key] = $new_value;
      }
    }
    else {

      // Pick next key if there is an number collision.
      if (is_numeric($key)) {
        while (isset($output[$key])) {
          $key++;
        }
      }
      $output[$key] = $value;
    }
  }

  // Add to array if not found.
  if (!isset($output[$new_key])) {

    // Before everything.
    if ($location == -1) {
      $output = $insert + $output;
    }

    // After everything.
    if ($location == 1) {
      $output[$new_key] = $new_value;
    }
  }
  return $output;
}

/**
 * Given a URL output a filename.
 *
 * @param string $url
 *   The url.
 * @param string $strict
 *   If FALSE then slashes will be kept.
 *
 * @return string
 *   The filename.
 */
function advagg_url_to_filename($url, $strict = TRUE) {

  // Keep filtering till there are no more changes.
  $decoded1 = _advagg_url_to_filename_filter($url, $strict);
  $decoded2 = _advagg_url_to_filename_filter($decoded1, $strict);
  while ($decoded1 != $decoded2) {
    $decoded1 = _advagg_url_to_filename_filter($decoded2, $strict);
    $decoded2 = _advagg_url_to_filename_filter($decoded1, $strict);
  }
  $filename = $decoded1;

  // Shorten filename to a max of 250 characters.
  $filename = mb_strcut($filename, 0, 250, mb_detect_encoding($filename));
  return $filename;
}

/**
 * Given a URL output a filtered filename.
 *
 * @param string $url
 *   The url.
 * @param string $strict
 *   If FALSE then slashes will be kept.
 *
 * @return string
 *   The filename.
 */
function _advagg_url_to_filename_filter($url, $strict = TRUE) {

  // URL Decode if needed.
  $decoded1 = $url;
  $decoded2 = rawurldecode($decoded1);
  while ($decoded1 != $decoded2) {
    $decoded1 = rawurldecode($decoded2);
    $decoded2 = rawurldecode($decoded1);
  }
  $url = $decoded1;

  // Replace url spaces with a dash.
  $filename = str_replace(array(
    '%20',
    '+',
  ), '-', $url);

  // Remove file system reserved characters
  // https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
  // Remove control charters
  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
  // Remove non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
  // Remove URI reserved characters
  // https://tools.ietf.org/html/rfc3986#section-2.2
  // Remove URL unsafe characters
  // https://www.ietf.org/rfc/rfc1738.txt
  if ($strict) {
    $filename = preg_replace('~[<>:"/\\|?*]|[\\x00-\\x1F]|[\\x7F\\xA0\\xAD]|[#\\[\\]@!$&\'()+,;=%]|[{}^\\~`]~x', '-', $filename);
  }
  else {
    $filename = preg_replace('~[<>:"\\|?*]|[\\x00-\\x1F]|[\\x7F\\xA0\\xAD]|[#\\[\\]@!$&\'()+,;=%]|[{}^\\~`]~x', '-', $filename);
  }

  // Replce all white spaces with a dash.
  $filename = preg_replace('/[\\r\\n\\t -]+/', '-', $filename);

  // Avoid ".", ".." or ".hiddenFiles".
  $filename = ltrim($filename, '.-');

  // Compress spaces in a file name and replace with a dash.
  // Compress underscores in a file name and replace with a dash.
  // Compress dashes in a file name and replace with a dash.
  $filename = preg_replace(array(
    '/ +/',
    '/_+/',
    '/-+/',
  ), '-', $filename);

  // Compress dashes and dots in a file name and replace with a dot.
  $filename = preg_replace(array(
    '/-*\\.-*/',
    '/\\.{2,}/',
  ), '.', $filename);

  // Lowercase for windows/unix interoperability
  // http://support.microsoft.com/kb/100625.
  $filename = mb_strtolower($filename, 'UTF-8');

  // Remove ? \ ..
  $filename = str_replace(array(
    '?',
    '\\',
    '..',
  ), '', $filename);

  // ".file-name.-" becomes "file-name".
  $filename = trim($filename, '.-');
  return $filename;
}

/**
 * Given a URI return TRUE if it is external.
 *
 * @param string $uri
 *   The uri.
 *
 * @return bool
 *   TRUE if external.
 */
function advagg_is_external($uri) {
  $http_pos = strpos($uri, 'http://');
  $https_pos = strpos($uri, 'https://');
  $double_slash_pos = strpos($uri, '//');
  if ($http_pos !== 0 && $https_pos !== 0 && $double_slash_pos !== 0) {
    return FALSE;
  }
  return TRUE;
}

/**
 * Same as file_get_contents() but will convert string to UTF-8 if needed.
 *
 * @return mixed
 *   The files contents or FALSE on failure.
 */
function advagg_file_get_contents() {

  // Get the file contents.
  $file_contents = call_user_func_array('file_get_contents', func_get_args());
  if ($file_contents === FALSE) {
    return $file_contents;
  }

  // If a BOM is found, convert the file to UTF-8.
  $encoding = advagg_get_encoding_from_bom($file_contents);
  if (!empty($encoding)) {
    $file_contents = advagg_convert_to_utf8($file_contents, $encoding);
  }
  return $file_contents;
}

/**
 * Get the description text based off the library version.
 *
 * @param string $library_name
 *   Name of the library.
 * @param string $module_name
 *   Name of the module that contains hook_libraries_info for this library.
 *
 * @return array
 *   Description text and info array.
 */
function advagg_get_version_description($library_name, $module_name, $only_remote_ok = FALSE) {
  $t = get_t();

  // Get local and external library version numbers.
  $versions =& drupal_static(__FUNCTION__);
  if (!isset($versions)) {
    $versions = advagg_get_remote_libraries_versions(TRUE);
  }
  $description = '';
  if (!empty($versions[$library_name]['remote']) && (empty($versions[$library_name]['local']) || $versions[$library_name]['local'] !== $versions[$library_name]['remote'])) {
    $library = advagg_get_library($library_name, $module_name);
    if (empty($versions[$library_name]['local'])) {
      $versions[$library_name]['local'] = 'NULL';
    }
    if (!empty($library['installed'])) {
      $description = $t('Go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the @lib_path folder. An example valid filename is %version_file. Current Version: %version.', array(
        '@name' => $library['name'],
        '@lib_path' => $library['library path'],
        '@url-page' => $library['vendor url'],
        '@url-zip' => $library['download url'],
        '@remote' => $versions[$library_name]['remote'],
        '%version' => $versions[$library_name]['local'],
        '%version_file' => $library['library path'] . '/' . $library['version arguments']['file'],
      ));
    }
    elseif (!$only_remote_ok && is_callable('libraries_load')) {
      $description = $t('Go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array(
        '@name' => $library['name'],
        '@url-page' => $library['vendor url'],
        '@url-zip' => $library['download url'],
        '@remote' => $versions[$library_name]['remote'],
        '%version_file' => "sites/all/libraries/{$library_name}/{$library['version arguments']['file']}",
      ));
    }
    elseif (!$only_remote_ok) {
      $description = $t('Install the <a href="@url-lib-api">Libraries API</a> module and then go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array(
        '@name' => $library['name'],
        '@url-page' => $library['vendor url'],
        '@url-zip' => $library['download url'],
        '@remote' => $versions[$library_name]['remote'],
        '%version_file' => "sites/all/libraries/{$library_name}/{$library['version arguments']['file']}",
        '@url-lib-api' => 'https://www.drupal.org/project/libraries',
      ));
    }
  }
  $path = drupal_get_path('module', $module_name);
  $info = drupal_parse_info_file("{$path}/{$module_name}.info");

  // Check if library was unzipped with -master.
  if (!empty($description) && is_callable('libraries_get_libraries')) {
    $libraries_paths = libraries_get_libraries();
    if (!empty($libraries_paths["{$library_name}-master"])) {
      $description = $t('Rename @lib_dir_master to @lib_dir at this location: @lib_path_master.', array(
        '@lib_dir_master' => "{$library_name}-master",
        '@lib_path_master' => $libraries_paths["{$library_name}-master"],
        '@lib_dir' => $library_name,
      ));
    }
  }
  return array(
    $description,
    $info,
  );
}

/**
 * Given a advagg type this will return the most recent aggregate from the db.
 *
 * @param string $type
 *   String: css or js.
 *
 * @return string
 *   Returns advagg filename or an empty string on failure.
 */
function advagg_generate_advagg_filename_from_db($type) {

  // Get the most recently accessed file from the database.
  $query = db_select('advagg_aggregates_versions', 'aav');
  $query
    ->join('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash =
    aav.aggregate_filenames_hash');
  $query
    ->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND
    af.filetype = :type', array(
    ':type' => $type,
  ));
  $query = $query
    ->fields('aav', array(
    'aggregate_filenames_hash',
    'aggregate_contents_hash',
  ))
    ->orderBy('atime', 'DESC')
    ->range(0, 1);
  $results = $query
    ->execute();
  if (empty($results)) {
    return '';
  }
  $hooks_hash = advagg_get_current_hooks_hash();
  foreach ($results as $row) {
    return $type . ADVAGG_SPACE . $row->aggregate_filenames_hash . ADVAGG_SPACE . $row->aggregate_contents_hash . ADVAGG_SPACE . $hooks_hash . '.' . $type;
  }
}

/**
 * Display a message if there are requirement issues with AdvAgg.
 *
 * @param array $requirements
 *   Other requirements to list besides the standard ones.
 */
function advagg_display_message_if_requirements_not_met(array $requirements = array()) {
  include_once DRUPAL_ROOT . '/includes/install.inc';
  module_load_include('install', 'advagg');
  $requirements += advagg_install_fast_checks();
  if (!empty($requirements)) {
    module_load_include('admin.inc', 'system');
    usort($requirements, '_system_sort_requirements');
    $report = theme('status_report', array(
      'requirements' => $requirements,
    ));
    drupal_set_message(t('Go to the <a href="@url">status report page</a> and fix the issues that AdvAgg lists there. Sneak peak: !report', array(
      '@url' => url('admin/reports/status'),
      '!report' => $report,
    )));
  }
}

/**
 * Add in the preload header for CSS and JS external files.
 *
 * @param string $url
 *   The url of the external host.
 *
 * @return bool
 *   TRUE if it was added to the head.
 */
function advagg_add_preload_header($url = '', $as = '') {

  // Get defaults and setup static's.
  $list =& drupal_static(__FUNCTION__ . ':list', array());
  $output =& drupal_static(__FUNCTION__ . ':output');
  $header_strlen =& drupal_static(__FUNCTION__ . ':strlen', 0);
  static $resource_hints_preload_order;
  if (!isset($resource_hints_preload_order)) {
    $resource_hints_preload_order = advagg_get_resource_hints_preload_settings();
  }
  if (!isset($output)) {
    $keys = array_keys($resource_hints_preload_order);
    $output = array_fill_keys($keys, array());
  }

  // Output headers.
  if (empty($url)) {

    // Call hook_advagg_preload_header_alter()
    drupal_alter('advagg_preload_header', $output);

    // Build header string.
    $header_value = '';
    foreach ($output as $value) {
      if (!empty($value)) {

        // Remove empty values.
        $value = array_filter($value);
        foreach ($value as $string) {
          $header_strlen += strlen($string) + 2;

          // Don't add if over the limit.
          if ($header_strlen >= variable_get('advagg_resource_hints_preload_max_size', ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE)) {
            continue;
          }

          // Add to $header_value string.
          if (empty($header_value)) {
            $header_value = $string;
          }
          else {
            $header_value .= ',' . $string;
          }
        }
      }
    }
    if (!empty($header_value)) {
      drupal_add_http_header('Link', $header_value, TRUE);
    }
    return FALSE;
  }

  // Check for duplicates.
  if (isset($list[$url])) {
    return FALSE;
  }

  // Fill in missing info.
  $payload = "<{$url}>; rel=preload";
  list($as, $type, $crossorigin, $parse) = advagg_get_preload_info_from_url($url, $as);
  if (!empty($as) && $as === 'php') {
    $list[$url] = FALSE;
    return FALSE;
  }
  $additional_info = array();
  if (!empty($crossorigin)) {
    $additional_info[] = "crossorigin";
  }
  if (!empty($type)) {
    if ($type !== 'text/css' || $type !== 'text/javascript' || $as !== 'image') {

      // Type is not needed for css/js files and images.
      $additional_info[] = "type=\"{$type}\"";
    }
  }
  $additional_info = implode('; ', $additional_info);

  // Get settings.
  if (!empty($as) && !empty($resource_hints_preload_order[$as])) {
    $settings = $resource_hints_preload_order[$as];
  }
  elseif (!empty($resource_hints_preload_order['all_others'])) {
    $settings = $resource_hints_preload_order['all_others'];
  }

  // Apply settings.
  if (!$settings['enabled']) {
    $list[$url] = FALSE;
    return FALSE;
  }
  if (!$settings['local'] && empty($parse['host'])) {
    $list[$url] = FALSE;
    return FALSE;
  }
  if (!$settings['external'] && !empty($parse['host'])) {
    $list[$url] = FALSE;
    return FALSE;
  }

  // Add additional info and queue.
  if (!empty($as)) {
    $payload .= "; as={$as}";
  }
  if (!empty($additional_info)) {
    $payload .= "; {$additional_info}";
  }
  if (!$settings['push']) {
    $payload .= "; nopush";
  }
  $list[$url] = TRUE;
  $output[$as][] = $payload;
}

/**
 * Given a link get the as, type, and crossorigin attributes.
 *
 * @param string $url
 *   Link to the url that will be preloaded.
 * @param string $as
 *   What type of content is this; font, image, video, etc.
 * @param string $type
 *   The mime type of the file.
 * @param string $crossorigin
 *   Preload cross-origin resources; fonts always need to be CORS.
 *
 * @return array
 *   An array containing
 */
function advagg_get_preload_info_from_url($url, $as = '', $type = '', $crossorigin = NULL) {

  // Get extension.
  $parse = @parse_url($url);
  if (empty($parse['path'])) {
    return FALSE;
  }
  $file_ext = strtolower(pathinfo($parse['path'], PATHINFO_EXTENSION));
  if (empty($file_ext)) {
    $file_ext = basename($parse['path']);
  }

  // Detect missing parts.
  $list = advagg_preload_list();
  if (empty($as) && !empty($file_ext)) {
    foreach ($list as $as_key => $list_type) {
      $key = array_search($file_ext, $list_type);
      if ($key !== FALSE) {
        $as = $as_key;

        // Type of font, ext is svg but file doesn't contain string font.
        // This will be treated as an image.
        if ($as === 'font' && $file_ext === 'svg' && stripos($url, 'font') === FALSE) {
          $as = '';
        }
      }
      if (!empty($as)) {
        break;
      }
    }
  }
  if ($file_ext !== 'css' && empty($type) && !empty($as)) {
    $type = "{$as}/{$file_ext}";
    if ($file_ext === 'svg') {
      $type .= '+xml';
    }
    if ($file_ext === 'js') {
      $type = 'text/javascript';
    }
    if ($file_ext === 'css') {
      $type = 'text/css';
    }
  }
  if ($as === 'font' && is_null($crossorigin)) {
    $crossorigin = 'anonymous';
  }
  return array(
    $as,
    $type,
    $crossorigin,
    $parse,
  );
}

/**
 * Add preload link to the top of the html head.
 *
 * @param string $url
 *   Link to the url that will be preloaded.
 * @param string $media
 *   Media types or media queries, allowing for responsive preloading.
 * @param string $as
 *   What type of content is this; font, image, video, etc.
 * @param string $type
 *   The mime type of the file.
 * @param string $crossorigin
 *   Preload cross-origin resources; fonts always need to be CORS.
 */
function advagg_add_preload_link($url, $media = '', $as = '', $type = '', $crossorigin = NULL) {
  static $weight = -2000;
  $weight += 0.0001;
  $href = advagg_file_create_url($url);
  $key = "advagg_preload:{$href}";

  // Return here if url has already been added.
  $stored_head = drupal_static('drupal_add_html_head');
  if (isset($stored_head[$key])) {
    return TRUE;
  }

  // Add basic attributes.
  $attributes = array(
    'rel' => 'preload',
    'href' => $href,
  );

  // Fill in missing info.
  list($as, $type, $crossorigin) = advagg_get_preload_info_from_url($url, $as, $type, $crossorigin);

  // Exit if no as.
  if (empty($as)) {
    return FALSE;
  }

  // Build up attributes array.
  $attributes['as'] = $as;
  if (!empty($type)) {
    $attributes['type'] = $type;
  }
  if (!empty($crossorigin)) {
    $attributes['crossorigin'] = $crossorigin;
  }
  if (!empty($media)) {
    $attributes['media'] = $media;
  }

  // Call hook_advagg_preload_link_attributes_alter()
  drupal_alter('advagg_preload_link_attributes', $attributes);

  // Add to HTML head.
  $element = array(
    '#type' => 'html_tag',
    '#tag' => 'link',
    '#attributes' => $attributes,
    '#weight' => $weight,
  );
  drupal_add_html_head($element, $key);
  return TRUE;
}

/**
 * Generate a list of file types for the as field given the extension.
 *
 * @return array
 *   Returns an array of arrays.
 */
function advagg_preload_list() {
  $list = array(
    'font' => array(
      'woff2',
      'woff',
      'ttf',
      'otf',
      'eot',
      // Need to check if the svg file is in a font folder.
      'svg',
    ),
    'image' => array(
      'gif',
      'jpg',
      'jpeg',
      'jpe',
      'jif',
      'jfif',
      'jfi',
      'png',
      'webp',
      'jp2',
      'jpx',
      'jxr',
      'heif',
      'heic',
      'bpg',
      'svg',
    ),
    'style' => array(
      'css',
    ),
    'script' => array(
      'js',
    ),
    'video' => array(
      'mp4',
      'webm',
      'ogg',
    ),
  );

  // Call hook_advagg_preload_list_alter()
  drupal_alter('advagg_preload_list', $list);
  return $list;
}

/**
 * Save form defaults or recommended values.
 *
 * @param array $element
 *   Form element or child element.
 *
 * @return array
 *   An array of form names and the recommended value for that setting.
 */
function advagg_find_all_recommended_admin_values(array &$element, $key_name = '#recommended_value') {
  $results = array();
  $children = element_children($element);
  foreach ($children as $key) {
    $child = $element[$key];
    if (is_array($child)) {
      if (!empty($child['#type']) && !empty($child['#name']) && isset($child[$key_name])) {
        $results[$child['#name']] = $child[$key_name];
      }
      $results = array_merge($results, advagg_find_all_recommended_admin_values($child, $key_name));
    }
    unset($child);
  }
  return $results;
}

/**
 * Get form values that have changed.
 *
 * @param array $element
 *   Form element or child element.
 *
 * @return array
 *   An array of form names and the recommended value for that setting.
 */
function advagg_find_all_changed_admin_values(array &$element) {
  $results = array();
  $children = element_children($element);
  foreach ($children as $key) {
    $child = $element[$key];
    if (is_array($child)) {
      if (!empty($child['#type']) && !empty($child['#name']) && isset($child['#default_value']) && isset($child['#value'])) {
        if ($child['#type'] === 'checkboxes') {

          // Add in not selected by default values.
          $child['#value'] += array_diff_assoc($child['#default_value'], $child['#value']);
        }
        if ($child['#default_value'] != $child['#value']) {
          $results[$child['#name']] = array(
            $child['#value'],
            $child['#default_value'],
          );
        }
      }
      $results = array_merge($results, advagg_find_all_changed_admin_values($child));
    }
    unset($child);
  }
  return $results;
}

/**
 * Get form title and description.
 *
 * @param array $element
 *   Form element or child element.
 *
 * @return array
 *   An array of form names and the recommended value for that setting.
 */
function advagg_find_title(array &$element) {
  $results = array();
  $children = element_children($element);
  foreach ($children as $key) {
    $child = $element[$key];
    if (is_array($child)) {
      if (!empty($child['#type']) && !empty($child['#name']) && isset($child['#title']) && isset($child['#default_value']) && !isset($results[$child['#name']]) && $child['#type'] !== 'radio') {
        $results[$child['#name']] = $child['#title'];
      }
      $results = array_merge($results, advagg_find_title($child));
    }
    unset($child);
  }
  return $results;
}

/**
 * Save form defaults or recommended values.
 *
 * @param array $form_state
 *   Form state array from drupal form submit.
 * @param string $trigger_key
 *   The key of the setting from the form that controls this.
 */
function advagg_set_admin_form_defaults_recommended(array &$form_state, $trigger_key) {
  $changed = array();
  $recommended_values = array();

  // Set to recommended values.
  if ($form_state['values'][$trigger_key] == 2) {
    $recommended_values = advagg_find_all_recommended_admin_values($form_state['complete form']);
    foreach ($recommended_values as $key => $value) {
      if (!isset($form_state['values'][$key])) {
        $changed[$key] = array(
          $value,
        );
      }
      elseif ($value != $form_state['values'][$key]) {
        $changed[$key] = array(
          $value,
          $form_state['values'][$key],
        );
      }
      $form_state['values'][$key] = $value;
    }
  }

  // Set to defaults.
  if ($form_state['values'][$trigger_key] == 0 || $form_state['values'][$trigger_key] == 2) {

    // Reset to defaults.
    foreach ($form_state['values'] as $key => &$value) {

      // Skip non advagg settings, trigger key, or if a recommended value.
      if (strpos($key, 'advagg_') !== 0 || $key === $trigger_key || isset($changed[$key]) || isset($recommended_values[$key])) {
        continue;
      }

      // Default to FALSE.
      $default = FALSE;

      // Get easy defaults.
      if (defined(strtoupper($key))) {
        $default = constant(strtoupper($key));
      }

      // Get more complex default values.
      if ($key === 'advagg_resource_hints_preload_settings') {
        $default = advagg_get_resource_hints_preload_settings(TRUE);
        foreach ($default as $key => &$values) {
          $default[$key]['weight'] = $values['#weight'];
          unset($default[$key]['#weight'], $values['#weight'], $default[$key]['title'], $values['title']);
          ksort($values);
        }
        ksort($default);
        foreach ($value as $key => &$values) {
          ksort($values);
        }
        ksort($value);
      }
      if ($key === 'advagg_relocate_css_inline_import_browsers') {
        $default = array(
          'woff2' => 'woff2',
          'woff' => 'woff',
          'ttf' => 'ttf',
          'eot' => 0,
          'svg' => 0,
        );
      }

      // See if it changed.
      if ($default != $value) {

        // After, Before.
        $changed[$key] = array(
          $default,
          $value,
        );
        $value = $default;
      }
    }
  }
  if ($form_state['values'][$trigger_key] == 4) {
    $changed = advagg_find_all_changed_admin_values($form_state['complete form']);
    if (isset($changed[$trigger_key])) {
      unset($changed[$trigger_key]);
    }
  }
  $all_titles_descriptions = advagg_find_title($form_state['complete form']);
  foreach ($changed as $key => $values) {

    // Remove things that didn't really change.
    if (isset($values[1])) {
      if ($values[0] == $values[1]) {
        unset($changed[$key]);
        continue;
      }
      if (is_string($values[0]) && is_string($values[1]) && trim($values[0]) == trim($values[1])) {
        unset($changed[$key]);
        continue;
      }
    }

    // Make output nicer.
    if (!isset($values[1])) {
      $values[1] = NULL;
    }
    if (is_bool($values[0]) && !is_bool($values[1]) || !is_bool($values[0]) && is_bool($values[1])) {
      $values[0] = (bool) $values[0];
      $values[1] = (bool) $values[1];
    }
    if (is_int($values[0]) && !is_int($values[1]) || !is_int($values[0]) && is_int($values[1])) {
      $values[0] = (int) $values[0];
      $values[1] = (int) $values[1];
    }

    // Let user know what changed.
    if (empty($all_titles_descriptions[$key])) {
      drupal_set_message(t('%before -> %after for %title', array(
        '%title' => $key,
        '%before' => var_export($values[1], TRUE),
        '%after' => var_export($values[0], TRUE),
      )));
    }
    else {
      drupal_set_message(t('%before -> %after for %title', array(
        '%title' => $all_titles_descriptions[$key],
        '%before' => var_export($values[1], TRUE),
        '%after' => var_export($values[0], TRUE),
      )));
    }
  }
  return $changed;
}

/**
 * Given a list of items see what ones need to be inserted/updated or deleted.
 *
 * @param array $defaults
 *   Array of default values, representing a row in the db.
 * @param mixed $new_values
 *   Array of edited values, representing a row in the db.
 *
 * @return array
 *   Nested array strucutre; only the diff.
 */
function advagg_diff_multi(array $defaults, $new_values) {
  $result = array();
  foreach ($defaults as $key => $val) {
    if (is_array($val) && isset($new_values[$key])) {
      $tmp = advagg_diff_multi($val, $new_values[$key]);
      if ($tmp) {
        $result[$key] = $tmp;
      }
    }
    elseif (!isset($new_values[$key])) {
      $result[$key] = NULL;
    }
    elseif ($val != $new_values[$key]) {
      $result[$key] = $new_values[$key];
    }
    if (isset($new_values[$key])) {
      unset($new_values[$key]);
    }
  }
  $result = $result + $new_values;
  return $result;
}

Functions

Namesort descending Description
advagg_add_default_dns_lookups Alter the CSS or JS array adding in DNS domain info.
advagg_add_dns_prefetch Add in the dns-prefetch header for CSS and JS external files.
advagg_add_preload_header Add in the preload header for CSS and JS external files.
advagg_add_preload_link Add preload link to the top of the html head.
advagg_add_resource_hints_array Find dns_prefetch and call advagg_add_dns_prefetch().
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_aggressive_cache_conflicts Check and see if the aggressive cache can safely be enabled.
advagg_ajax_render_alter Implements hook_ajax_render_alter().
advagg_anonymous_login_paths_alter Implements hook_anonymous_login_paths_alter().
advagg_array_splice_assoc Remove a portion of the array and replace it with something else.
advagg_block_view_alter Implements hook_block_view_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_cache_clear_admin_submit Clear certain caches on form submit.
advagg_cleanup_settings_array Shrink the ajaxPageState data.
advagg_convert_abs_to_protocol Converts absolute paths to be protocol relative paths.
advagg_convert_abs_to_rel Converts absolute paths to be self references.
advagg_convert_to_utf8 Converts data to UTF-8.
advagg_cron Implements hook_cron().
advagg_cron_alter Implements hook_cron_alter().
advagg_css_in_js Returns TRUE if the CSS is being loaded via JavaScript.
advagg_current_hooks_hash_array Get an array of all hooks and settings that affect aggregated files contents.
advagg_diff_multi Given a list of items see what ones need to be inserted/updated or deleted.
advagg_display_message_if_requirements_not_met Display a message if there are requirement issues with AdvAgg.
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_create_url Wrapper around file_create_url() to do post-processing on the created url.
advagg_file_get_contents Same as file_get_contents() but will convert string to UTF-8 if needed.
advagg_file_url_alter Implements hook_file_url_alter().
advagg_find_all_changed_admin_values Get form values that have changed.
advagg_find_all_recommended_admin_values Save form defaults or recommended values.
advagg_find_title Get form title and description.
advagg_fix_type Alter the js array fixing the type key if set incorrectly.
advagg_flush_caches Implements hook_flush_caches().
advagg_force_https_path Convert http:// and // to https://.
advagg_force_http_path Convert https:// to http://.
advagg_form_system_performance_settings_alter Implements hook_form_FORM_ID_alter().
advagg_generate_advagg_filename_from_db Given a advagg type this will return the most recent aggregate from the db.
advagg_get_css Returns a themed representation of all stylesheets to attach to the page.
advagg_get_css_prefix_suffix Get the prefix and suffix for inline css.
advagg_get_current_hooks_hash Get the hash of all hooks and settings that affect aggregated files contents.
advagg_get_encoding_from_bom Decodes UTF byte-order mark (BOM) into the encoding's name.
advagg_get_full_js Get full JS array.
advagg_get_github_version_json Get the latest version number for the remote version.
advagg_get_github_version_txt Get the latest version number for the remote version.
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_library Get the latest version number for the remote version.
advagg_get_preload_info_from_url Given a link get the as, type, and crossorigin attributes.
advagg_get_relative_path Given a uri, get the relative_path.
advagg_get_remote_libraries_version Get the latest version number for the remote version.
advagg_get_remote_libraries_versions Update github version numbers to the latest.
advagg_get_remote_libraries_versions_cache Get the remote and local versions cache of the available libraries.
advagg_get_render_cache Given the full css and js scope array return back the render cache.
advagg_get_resource_hints_preload_settings Get the resource hint settings for the preload attribute.
advagg_get_root_files_dir Get the CSS and JS path for advagg.
advagg_get_s3fs_config Return s3fs configuration settings and values.
advagg_get_version_description Get the description text based off the library version.
advagg_glue_url Alt to http_build_url().
advagg_group_js Default callback to group JavaScript items.
advagg_help Implements hook_help().
advagg_hooks_implemented Get back what hooks are implemented.
advagg_hook_info Implements hook_hook_info().
advagg_htaccess_rewritebase See if the .htaccess file uses the RewriteBase directive.
advagg_insert_into_array_at_key Insert element into an array at a specific key location.
advagg_insert_into_array_at_location Insert element into an array at a specific position.
advagg_is_external Given a URI return TRUE if it is external.
advagg_json_encode Converts a PHP variable into its JavaScript equivalent.
advagg_jsspecialchars Replace quotes with the html version of it.
advagg_js_alter Implements hook_js_alter().
advagg_load_stylesheet Loads the stylesheet and resolves all @import commands.
advagg_load_stylesheet_content Processes the contents of a stylesheet for aggregation.
advagg_locale_js_add_translations Only the alter part of locale_js_alter(), not the parsing part.
advagg_match_file_pattern Checks if the filename matches the advagg file pattern.
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_multi_update_atime Update atime inside advagg_aggregates_versions and cache_advagg_info.
advagg_panels_pre_render Implements hook_panels_pre_render().
advagg_password_policy_force_change_allowed_paths_alter Implements hook_password_policy_force_change_allowed_paths_alter().
advagg_permission Implements hook_permission().
advagg_preload_list Generate a list of file types for the as field given the extension.
advagg_preprocess_html Implements hook_preprocess_html().
advagg_preprocess_page Implements hook_preprocess_page().
advagg_pre_flush_all_caches Implements hook_pre_flush_all_caches().
advagg_pre_render_scripts Callback for pre_render to add elements needed for JavaScript to be rendered.
advagg_pre_render_styles A #pre_render callback to add elements needed for CSS tags to be rendered.
advagg_remove_empty_files Alter the CSS or JS array removing empty files from the aggregates.
advagg_remove_short_keys Callback for array_filter. Will return FALSE if strlen < 3.
advagg_replace_drupal_settings_string Replace inline drupal settings script.
advagg_s3fs_evaluate_no_rewrite_cssjs Shortcut to evaluate if s3fs no_rewrite_cssjs is set or empty.
advagg_s3fs_upload_params_alter Implements hook_s3fs_upload_params_alter().
advagg_scan_filesystem_for_changes_live Will scan, flush, use, and report any changes to css/js files in aggregates.
advagg_set_admin_form_defaults_recommended Save form defaults or recommended values.
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_url_inbound_alter Implements hook_url_inbound_alter().
advagg_url_to_filename Given a URL output a filename.
advagg_views_pre_render Implements hook_views_pre_render().
theme_html_script_tag Returns HTML for a generic HTML tag with attributes.
_advagg_aggregate_css Default callback to aggregate CSS files and inline content.
_advagg_aggregate_js Default callback to aggregate JavaScript files.
_advagg_build_css_arrays_for_rendering Builds the arrays needed for css rendering and caching.
_advagg_build_js_arrays_for_rendering Builds the arrays needed for js rendering and caching.
_advagg_load_stylesheet Loads stylesheets recursively and returns contents with corrected paths.
_advagg_locale_js_alter Implements hook_js_alter().
_advagg_process_html Replacement for template_process_html().
_advagg_url_to_filename_filter Given a URL output a filtered filename.

Constants

Namesort descending Description
ADVAGG_ADMIN_CONFIG_ROOT_PATH Default location of AdvAgg configuration items.
ADVAGG_ADMIN_MODE If 4 the admin section gets unlocked.
ADVAGG_AJAX_RENDER_ALTER Set to FALSE to not alter the CSS/JS pushed out from AdvAgg.
ADVAGG_AUTH_BASIC_PASS Workaround for 401 errors.
ADVAGG_AUTH_BASIC_USER Workaround for 401 errors.
ADVAGG_CACHE_LEVEL Default value to see if we cache the full CSS/JS structure.
ADVAGG_CHROME_HEADER_ENABLED If true chrome=1 will be added to the X-UA-Compatible header.
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_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH Convert absolute path CSS/JS src/url() to be protocol relative.
ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH Convert absolute path CSS/JS src/url() to be relative if self referencing.
ADVAGG_CRON_FREQUENCY How long to wait until advagg cron will run again. Default is 23 hours.
ADVAGG_CSS_ABSOLUTE_PATH Convert relative path CSS inside src() to be absolute.
ADVAGG_CSS_FIX_TYPE Scan and fix any CSS that was added with the wrong type.
ADVAGG_CSS_IN_JS If TRUE then the css is being rendered via javascript.
ADVAGG_CSS_REMOVE_EMPTY_FILES If TRUE advagg will search for and remove empty CSS files from aggregates.
ADVAGG_DEBUG Default value for debugging info to watchdog.
ADVAGG_DISABLE_ON_ADMIN If TRUE advagg will be disabled on admin pages.
ADVAGG_DISABLE_ON_LISTED_PAGES Internal urls set here will have advagg disabled on those pages.
ADVAGG_ENABLED Default value to see if advanced CSS/JS aggregation is enabled.
ADVAGG_FARFUTURE_PHP If TRUE farfuture headers will go out if the file is delivered by php.
ADVAGG_FAST_FILESYSTEM Do more file operations in main thread if the file system is fast.
ADVAGG_FILE_READ_FAILURE_TIMEOUT How long to wait until an aggregate with a missing file is written to disk.
ADVAGG_FORCE_HTTPS_PATH Convert absolute path CSS/JS src/url() to be https.
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_HTACCESS_REWRITEBASE If not empty advagg htaccess will include the given rewritebase.
ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH If true advagg htaccess Options uses SymLinksIfOwnerMatch vs FollowSymLinks.
ADVAGG_HTML_HEAD_IN_CSS_LOCATION If TRUE drupal_get_html_head will be rendered at the top of the css section.
ADVAGG_HTTP_200_CODE Default is 200; 203 has been requested in the past.
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_INCLUDE_BASE_URL Include the base_url global as part of the hooks hash array.
ADVAGG_JS_FIX_TYPE Scan and fix any JS that was added with the wrong type.
ADVAGG_JS_HEADER_LENGTH How far down a JS file to look for use strict.
ADVAGG_JS_REMOVE_EMPTY_FILES If TRUE advagg will search for and remove empty JS files from aggregates.
ADVAGG_NEEDS_UPDATE TRUE when db table 'advagg_aggregates_versions' is missing in advagg_enable.
ADVAGG_NO_LOCKS If FALSE lock_acquire is used before writing a file.
ADVAGG_NO_ZOPFLI Default value to see zopfli_encode should not be used.
ADVAGG_PREGENERATE_AGGREGATE_FILES Pregenerate aggregate files.
ADVAGG_REMOTE_VERSION_CHECK Check library versions on admin pages.
ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME How long to wait until unaccessed aggregates are removed from the database.
ADVAGG_REMOVE_OLD_UNUSED_AGGREGATES_TIME How long to wait until unaccessed aggregates are removed from the database.
ADVAGG_RESOURCE_HINTS_DNS_PREFETCH Prefetch External domains for CSS/JS.
ADVAGG_RESOURCE_HINTS_LOCATION Location of CSS/JS and sub requests resource hints.
ADVAGG_RESOURCE_HINTS_PRECONNECT Preconnect External domains for CSS/JS.
ADVAGG_RESOURCE_HINTS_PRELOAD Preload CSS/JS and sub requests.
ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE Preload CSS/JS header limit 3kb.
ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE If false do not set the immutable header.
ADVAGG_ROOT_DIR_PREFIX Default root dir for the advagg files; controls advagg_get_root_files_dir().
ADVAGG_RUN_ALTER_AFTER_THEME Run advagg* js_alter and css_alter after all others including theme hooks.
ADVAGG_SCRIPTS_SCOPE_ANYWHERE Allow JavaScript insertion into any scope even if theme does not support it.
ADVAGG_SERIALIZE Function to use when converting a non scalar to a string.
ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE Display a message that the bypass cookie is set.
ADVAGG_SHOW_FILE_CHANGED_MESSAGE Display a message that the a CSS/JS file has changed.
ADVAGG_SKIP_304_CHECK If true do test for 304 files on the status report page.
ADVAGG_SKIP_ENABLED_PREPROCESS_CHECK Skip preprocess check on status page.
ADVAGG_SKIP_FAR_FUTURE_CHECK Skip far future check on status page.
ADVAGG_SKIP_GZIP_CHECK Skip gzip check on status page.
ADVAGG_SPACE Default space characters.
ADVAGG_STRICT_MTIME_CHECK See if the mtime != if TRUE < if FALSE.
ADVAGG_URL_INBOUND_ALTER Run advagg_url_inbound_alter().
ADVAGG_USE_HTTPRL Send non blocking requests in order to generate aggregated files via HTTPRL.
ADVAGG_WEAK_FILE_VERIFICATION Verify all 3 hashes from the filename, if TRUE only verify 1st hash.