Same filename and directory in other branches
  1. 5.0.x advagg.module 1 commentaire
  2. 6.0.x advagg.module 1 commentaire
  3. 7.x-1.x advagg.module 1 commentaire
  4. 8.x-2.x advagg.module 1 commentaire
  5. 8.x-3.x advagg.module 1 commentaire
  6. 8.x-4.x advagg.module 1 commentaire

Advanced CSS/JS aggregation module.

Fichier

./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 type is not set, default type in drupal_add_js() is file.
            if (isset($item['type']) && $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',
        "\xfe\xff" => 'UTF-16BE',
        "\xff\xfe" => 'UTF-16LE',
        "\x00\x00\xfe\xff" => 'UTF-32BE',
        "\xff\xfe\x00\x00" => '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')) {
        // phpcs:ignore
        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

Titre Deprecated Résumé
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.

Constants

Titre Deprecated Résumé
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.