<?php

/**
 * @file
 * Advanced aggregation modifier module.
 */

// Define default variables.
/**
 * Default value to move all JS to the footer.
 */
define('ADVAGG_MOD_JS_FOOTER', 0);

/**
 * Default value to turn on preprocessing for all JavaScript files.
 */
define('ADVAGG_MOD_JS_PREPROCESS', FALSE);

/**
 * Default value to add the defer tag to all script tags.
 */
define('ADVAGG_MOD_JS_DEFER', FALSE);

/**
 * Default value to add use the async script shim for script tags.
 */
define('ADVAGG_MOD_JS_ASYNC_SHIM', FALSE);

/**
 * Default value to remove JavaScript if none was added on the page.
 */
define('ADVAGG_MOD_JS_REMOVE_UNUSED', FALSE);

/**
 * Default value to turn on preprocessing for all CSS files.
 */
define('ADVAGG_MOD_CSS_PREPROCESS', FALSE);

/**
 * Default value to translate the content attributes of CSS files.
 */
define('ADVAGG_MOD_CSS_TRANSLATE', FALSE);

/**
 * Default value to adjust the sorting of external JavaScript.
 */
define('ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL', FALSE);

/**
 * Default value to adjust the sorting of inline JavaScript.
 */
define('ADVAGG_MOD_JS_ADJUST_SORT_INLINE', FALSE);

/**
 * Default value to adjust the sorting of browser conditional JavaScript.
 */
define('ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS', FALSE);

/**
 * Default value to adjust the sorting of external CSS.
 */
define('ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL', FALSE);

/**
 * Default value to adjust the sorting of inline CSS.
 */
define('ADVAGG_MOD_CSS_ADJUST_SORT_INLINE', FALSE);

/**
 * Default value to adjust the sorting of browser conditional CSS.
 */
define('ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS', FALSE);

/**
 * Default value to use JavaScript to defer CSS loading.
 */
define('ADVAGG_MOD_CSS_DEFER', FALSE);

/**
 * Default value to move CSS into drupal_add_css().
 */
define('ADVAGG_MOD_CSS_HEAD_EXTRACT', FALSE);

/**
 * Default value to move JavaScript into drupal_add_js().
 */
define('ADVAGG_MOD_JS_HEAD_EXTRACT', FALSE);

/**
 * Default value to have async on all JS script tags.
 */
define('ADVAGG_MOD_JS_ASYNC', FALSE);

/**
 * Default value to wrap inline content javascript so it runs when it is ready.
 */
define('ADVAGG_MOD_JS_FOOTER_INLINE_ALTER', TRUE);

/**
 * Turns on functionality on every page except the listed pages.
 */
define('ADVAGG_MOD_VISIBILITY_NOTLISTED', 0);

/**
 * Turns on functionality only on the listed pages.
 */
define('ADVAGG_MOD_VISIBILITY_LISTED', 1);

/**
 * Turns on functionality if the associated PHP code returns TRUE.
 */
define('ADVAGG_MOD_VISIBILITY_PHP', 2);

/**
 * Default value of the inclusion method for the loadCSS code.
 */
define('ADVAGG_MOD_CSS_DEFER_JS_CODE', 0);

/**
 * Default value to convert inline GA code into file.
 */
define('ADVAGG_MOD_GA_INLINE_TO_FILE', FALSE);

/**
 * Default value for inline scripts that should not be altered.
 */
define('ADVAGG_MOD_WRAP_INLINE_JS_SKIP_LIST', '');

/**
 * Default value for detection of inline scripts.
 */
define('ADVAGG_MOD_WRAP_INLINE_JS_XPATH', FALSE);

// Core hook implementations.
/**
 * Implements hook_init().
 */
function advagg_mod_init() {
  // Return if unified_multisite_dir is not set.
  $dir = rtrim(variable_get('advagg_mod_unified_multisite_dir', ''), '/');
  if (empty($dir) || !file_exists($dir) || !is_dir($dir)) {
    return;
  }

  $counter_filename = $dir . '/' . ADVAGG_SPACE . 'advagg_global_counter';
  $local_counter = advagg_get_global_counter();
  if (!file_exists($counter_filename)) {
    module_load_include('inc', 'advagg', 'advagg.missing');
    advagg_save_data($counter_filename, $local_counter);
  }
  else {
    $shared_counter = (int) file_get_contents($counter_filename);

    if ($shared_counter == $local_counter) {
      // Counters are the same, return.
      return;
    }
    elseif ($shared_counter < $local_counter) {
      // Local counter is higher, update saved file and return.
      module_load_include('inc', 'advagg', 'advagg.missing');
      advagg_save_data($counter_filename, $local_counter, TRUE);
      return;
    }
    elseif ($shared_counter > $local_counter) {
      // Shared counter is higher, update local copy and return.
      variable_set('advagg_global_counter', $shared_counter);
      return;
    }
  }
}

/**
 * Implements hook_menu().
 */
function advagg_mod_menu() {
  $file_path = drupal_get_path('module', 'advagg_mod');
  $config_path = advagg_admin_config_root_path();

  $items[$config_path . '/advagg/mod'] = array(
    'title' => 'Modifications',
    'description' => 'Turn on or off various mods for CSS/JS.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('advagg_mod_admin_settings_form'),
    'type' => MENU_LOCAL_TASK,
    'access arguments' => array('administer site configuration'),
    'file path' => $file_path,
    'file' => 'advagg_mod.admin.inc',
    'weight' => 10,
  );

  return $items;
}

/**
 * Implements hook_js_alter().
 */
function advagg_mod_js_alter(&$js) {
  if (module_exists('advagg') && !advagg_enabled()) {
    return;
  }

  // Change google analytics inline loader to be inside of an aggregrated file.
  if (variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE)) {
    advagg_mod_ga_inline_to_file($js);
  }

  // Only add JS if it's actually needed.
  if (variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED)) {
    advagg_remove_js_if_not_used($js);
  }

  // Change sort order so aggregates do not get split up.
  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  if ( variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL)
    || variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE)
    || variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS)
  ) {
    advagg_mod_sort_css_js($js, 'js');
  }

  // Move all JS to the footer.
  $move_js_to_footer = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER);
  if (!empty($move_js_to_footer)) {
    foreach ($js as $name => &$values) {
      if ($move_js_to_footer == 1 && $values['group'] <= JS_LIBRARY) {
        continue;
      }

      if (!empty($values['scope_lock'])) {
        continue;
      }

      // Do not move modernizr js to the footer.
      if ( $values['type'] !== 'inline'
        && $values['type'] !== 'setting'
        && stripos($values['data'], '/modernizr.') !== FALSE
      ) {
        continue;
      }

      // Do not move html5shiv or html5shiv-printshiv js to the footer.
      if ( $values['type'] !== 'inline'
        && $values['type'] !== 'setting'
        && ( stripos($values['data'], '/html5shiv.') !== FALSE
          || stripos($values['data'], '/html5shiv-printshiv.') !== FALSE
          )
      ) {
        continue;
      }

      // If JS is not in the header increase group by 10000.
      if ($values['scope'] !== 'header') {
        $values['group'] += 10000;
      }
      // If JS is already in the footer increase group by 10000.
      if ($values['scope'] === 'footer') {
        $values['group'] += 10000;
      }
      $values['scope'] = 'footer';
    }
    unset($values);
  }

  // Do not use preprocessing if JS is inlined.
  // Do not use defer if JS is inlined.
  if (advagg_mod_inline_page()) {
    advagg_mod_inline_js($js);
    return;
  }

  // Force all JS to be preprocessed.
  if (variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS)) {
    foreach ($js as $name => &$values) {
      $values['preprocess'] = TRUE;
      $values['cache'] = TRUE;
    }
    unset($values);
  }

  // Add the defer or the async tag to all JS.
  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  if ( variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)
    || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)
  ) {

    if (module_exists('openlayers')) {
      $has_openlayers = FALSE;
      foreach ($js as &$values) {
        if ($values['type'] === 'inline' || !is_string($values['data'])) {
          continue;
        }
        if (stripos($values['data'], 'openlayers')) {
          $has_openlayers = TRUE;
          break;
        }
      }
      unset($values);
      if ($has_openlayers) {
        // Openlayers fix; external scripts can not be loaded out of order.
        $openlayers = array();
        // Cloudmade.
        $path = variable_get('openlayers_layers_cloudmade_js', '');
        if (valid_url($path, TRUE)) {
          $openlayers['openlayers_layers_cloudmade'] = $path;
        }
        // Google.
        $mapdomain = variable_get('openlayers_layers_google_mapdomain', 'maps.google.com');
        $openlayers['openlayers_layers_google'] = $mapdomain . '/maps';
        // VirtualEarth.
        $openlayers['openlayers_layers_virtualearth'] = 'dev.virtualearth.net/mapcontrol';
        // Yahoo.
        $openlayers['openlayers_layers_yahoo'] = 'api.maps.yahoo.com/ajaxymap';
      }
    }

    $use_on_error = FALSE;
    // If everything is async safe then we can use on error.
    // Only needed if the jquery_update javascript is loaded via async/defer.
    if ($use_on_error) {
      $jquery_update_fallback = '';
      $jquery_update_ui_fallback = '';
      $inline_array = array();
      if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') {
        foreach ($js as $name => &$values) {
          if ($values['type'] !== 'inline') {
            continue;
          }
          if (strpos($values['data'], 'window.jQuery') !== FALSE) {
            $path = drupal_get_path('module', 'jquery_update');
            $version = variable_get('jquery_update_jquery_version', '1.10');
            $min = variable_get('jquery_update_compression_type', 'min') == 'none' ? '' : '.min';
            $jquery_update_fallback = base_path() . $path . '/replace/jquery/' . $version . '/jquery' . $min . '.js';
            $inline_array = $values;
            unset($js[$name]);
            continue;
          }
          if (strpos($values['data'], 'window.jQuery.ui') !== FALSE) {
            $path = drupal_get_path('module', 'jquery_update');
            $min = variable_get('jquery_update_compression_type', 'min') == 'none' ? '' : '.min';
            $js_path = ($min == '.min') ? '/replace/ui/ui/minified/jquery-ui.min.js' : '/replace/ui/ui/jquery-ui.js';
            $jquery_update_ui_fallback = base_path() . $path . $js_path;
            if (empty($inline_array)) {
              $inline_array = $values;
            }
            unset($js[$name]);
            continue;
          }
        }
        unset($values);
      }
      if (!empty($jquery_update_fallback) || !empty($jquery_update_ui_fallback)) {
        $inline_array['group'] = '-150';
        $inline_array['weight'] += -10;
        $inline_array['data'] = 'function advagg_fallback(file){var head = document.getElementsByTagName("head")[0];var script = document.createElement("script");script.src = file;script.type = "text/javascript";head.appendChild(script);};';
        $js[] = $inline_array;
      }
    }

    foreach ($js as $name => &$values) {
      if ($values['type'] !== 'file' && $values['type'] !== 'external') {
        continue;
      }
      if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) {
        // Everything is defer.
        $values['defer'] = TRUE;
      }
      if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) {
        // Everything is async.
        $values['async'] = TRUE;
      }

      if (strpos($name, 'jquery.js') !== FALSE || strpos($name, 'jquery.min.js') !== FALSE) {
        // jquery_update fallback.
        if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') {
          if ($use_on_error) {
            if (!isset($values['onerror'])) {
              $values['onerror'] = '';
            }
            $values['onerror'] .= 'advagg_fallback(\'' . $jquery_update_fallback . '\');';
          }
          // Do not defer/async the loading of jquery.js
          if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) {
            $values['defer'] = FALSE;
          }
          if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) {
            $values['async'] = FALSE;
          }
        }
      }
      if (strpos($name, 'jquery-ui.js') !== FALSE || strpos($name, 'jquery-ui.min.js') !== FALSE) {
        // jquery_update ui fallback.
        if (module_exists('jquery_update') && variable_get('jquery_update_jquery_cdn', 'none') !== 'none') {
          if ($use_on_error) {
            if (!isset($values['onerror'])) {
              $values['onerror'] = '';
            }
            $values['onerror'] .= 'advagg_fallback(' . $jquery_update_ui_fallback . ');';
          }
          // Do not defer/async the loading of jquery-ui.js
          if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) {
            $values['defer'] = FALSE;
          }
          if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) {
            $values['async'] = FALSE;
          }
        }
      }

      // Drupal settings.
      if ($name === 'misc/drupal.js') {
        // Initialize the Drupal.settings JavaScript object after this has
        // loaded.
        if (!isset($values['onload'])) {
          $values['onload'] = '';
        }
        $matches[0] = $matches[2] = 'init_drupal_core_settings();';
        $values['onload'] .= advagg_mod_wrap_inline_js($matches, "window.init_drupal_core_settings && window.init_drupal_core_settings && window.jQuery && window.Drupal", 20);
      }

      // Openlayers.
      if (!empty($openlayers)) {
        foreach ($openlayers as $search_string) {
          if (strpos($name, $search_string) !== FALSE) {
            // Do not defer/async the loading of OpenLayers.js
            if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) {
              $values['defer'] = FALSE;
            }
            if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) {
              $values['async'] = FALSE;
            }
          }
        }
      }

      // Wistia.
      if (strpos($name, '//fast.wistia.') !== FALSE) {
        // Do not defer/async the loading of any wistia js.
        if (variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)) {
          $values['defer'] = FALSE;
        }
        if (variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)) {
          $values['async'] = FALSE;
        }
      }

    }
    unset($values);
  }
}

/**
 * Implements hook_css_alter().
 */
function advagg_mod_css_alter(&$css) {
  if (module_exists('advagg') && !advagg_enabled()) {
    return;
  }

  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:5
  // Change sort order so aggregates do not get split up.
  if ( variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL)
    || variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE)
    || variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS)
  ) {
    advagg_mod_sort_css_js($css, 'css');
  }

  // Do not use preprocessing if CSS is inlined.
  if (advagg_mod_inline_page()) {
    advagg_mod_inline_css($css);
    return;
  }

  // Force all CSS to be preprocessed.
  if (variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS)) {
    foreach ($css as &$values) {
      $values['preprocess'] = TRUE;
    }
    unset($values);
  }
}

/**
 * Implements hook_html_head_alter().
 */
function advagg_mod_html_head_alter(&$head_elements) {
  foreach ($head_elements as $key => $element) {
    // CSS
    // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
    if ( variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT)
      && !empty($element['#tag'])
      && $element['#tag'] === 'link'
      && !empty($element['#attributes']['type'])
      && $element['#attributes']['type'] === 'text/css'
      && !empty($element['#attributes']['href'])
    ) {
      $type = 'file';
      // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
      if ( strpos($element['#attributes']['href'], 'http://') === 0
        || strpos($element['#attributes']['href'], 'https://') === 0
        || strpos($element['#attributes']['href'], '//') === 0
      ) {
        $type = 'external';
      }
      drupal_add_css($element['#attributes']['href'], array(
        'type' => $type,
        'group' => CSS_SYSTEM,
        'every_page' => TRUE,
        'weight' => -50000,
      ));
      unset($head_elements[$key]);
    }
    // JS
    // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
    if ( variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT)
      && !empty($element['#tag'])
      && $element['#tag'] === 'script'
      && !empty($element['#attributes']['type'])
      && $element['#attributes']['type'] === 'text/javascript'
      && !empty($element['#attributes']['src'])
    ) {
      $type = 'file';
      // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
      if ( strpos($element['#attributes']['src'], 'http://') === 0
        || strpos($element['#attributes']['src'], 'https://') === 0
        || strpos($element['#attributes']['src'], '//') === 0
      ) {
        $type = 'external';
      }
      drupal_add_js($element['#attributes']['src'], array(
        'type' => $type,
        'scope' => 'header',
        'group' => JS_LIBRARY,
        'every_page' => TRUE,
        'weight' => -50000,
      ));
      unset($head_elements[$key]);
    }
  }
}

/**
 * Implements hook_theme_registry_alter().
 *
 * Insert advagg_mod_process_move_js before _advagg_process_html.
 */
function advagg_mod_theme_registry_alter(&$theme_registry) {
  if (!isset($theme_registry['html'])) {
    return;
  }

  // Find template_process_html/_advagg_process_html.
  $index = array_search('_advagg_process_html', $theme_registry['html']['process functions']);
  if ($index === FALSE) {
    $index = array_search('template_process_html', $theme_registry['html']['process functions']);
    if ($index === FALSE) {
      return;
    }
  }

  // Insert advagg_mod_process_move_js before _advagg_process_html.
  array_splice($theme_registry['html']['process functions'], $index, 0, 'advagg_mod_process_move_js');
}

/**
 * Implements hook_process().
 *
 * Used to wrap inline JS in a function in order to prevent js errors when JS is
 * moved to the footer.
 */
function advagg_mod_process_move_js(&$variables) {
  // Only run if.
  // $variables['page'] is not empty.
  // Setting is enabled.
  if (empty($variables['page']) || !variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER)) {
    return;
  }

  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  if ( variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) != 2
    && !variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)
    && !variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)
  ) {
    return;
  }

  $pattern = '/<script((?:(?!src=).)*?)>(.*?)<\/script>/smix';
  $callback = 'advagg_mod_wrap_inline_js';
  // Wrap inline JS with a check so that it only runs once Drupal.settings &
  // jQuery are not undefined.
  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  if ( !empty($variables['page']['#children'])
    && is_string($variables['page']['#children'])
    && stripos($variables['page']['#children'], '<script') !== FALSE
  ) {
    if (variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH)) {
      $variables['page']['#children'] = advagg_mod_xpath_script_wrapper($variables['page']['#children']);
    }
    else {
      $variables['page']['#children'] = preg_replace_callback($pattern, $callback, $variables['page']['#children']);
    }
    return;
  }
  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  if ( is_string($variables['page'])
    && stripos($variables['page'], '<script') !== FALSE
  ) {
    if (variable_get('advagg_mod_wrap_inline_js_xpath', ADVAGG_MOD_WRAP_INLINE_JS_XPATH)) {
      $variables['page'] = advagg_mod_xpath_script_wrapper($variables['page']);
    }
    else {
      $variables['page'] = preg_replace_callback($pattern, $callback, $variables['page']);
    }
    return;
  }
}

/**
 * Use DOMDocument's loadHTML along with DOMXPath's query to find script tags.
 *
 * Once found, it will also wrap them in a javascript loader function.
 *
 * @param string $html
 *   HTML fragments.
 *
 * @return string
 *   The HTML fragment with less markup errors and script tags wrapped.
 */
function advagg_mod_xpath_script_wrapper($html) {
  // Do not throw errors when parsing the html.
  libxml_use_internal_errors(TRUE);

  $dom = new DOMDocument();
  // Load html with full tags all around.
  $dom->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
  <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>' . $html . '</body></html>');
  $xpath = new DOMXPath($dom);
  // Get all script tags that are not inside of a textarea and do not contain a
  // src attribute.
  $nodes = $xpath->query('//script[not(@src)][not(ancestor::textarea)]');

  foreach ($nodes as $node) {
    $matches[2] = $node->nodeValue;
    // $matches[0] = $dom->saveHTML($node);
    $matches[0] = $node->nodeValue;
    $new_html = advagg_mod_wrap_inline_js($matches);
    $advagg = $dom->createElement('script');
    $advagg->appendchild($dom->createTextNode($new_html));
    $node->parentNode->replaceChild($advagg, $node);
  }
  // Render to HTML.
  $output = $dom->saveHTML();

  // Remove the tags we added.
  $output = str_replace(array('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
  <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>', '</body></html>'), array('', ''), $output);

  // Clear any errors.
  libxml_clear_errors();
  return $output;
}

// AdvAgg hook implementations.
/**
 * Implements hook_advagg_modify_js_pre_render_alter().
 */
function advagg_mod_advagg_modify_js_pre_render_alter(&$children, &$elements) {
  if (module_exists('advagg') && !advagg_enabled()) {
    return;
  }

  // Do not use defer/async shim if JS is inlined.
  if (advagg_mod_inline_page()) {
    return;
  }

  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
  if ( variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER)
    || variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC)
  ) {
    // Capture all onload code.
    $onload_code = array();
    foreach ($children as $values) {
      if (isset($values['#attributes']['onload'])) {
        $onload_code[$values['#attributes']['src']] = $values['#attributes']['onload'];
      }
    }

    foreach ($children as &$values) {
      // Core's Drupal.settings. Put inside wrapper if there is an onload call
      // for init_drupal_core_settings. Have to do this here because the
      // settings needed to be rendered.
      if (!empty($values['#value']) && strpos($values['#value'], 'jQuery.extend(Drupal.settings') !== FALSE) {
        $found = FALSE;
        foreach ($onload_code as $src => $code) {
          if (strpos($code, 'init_drupal_core_settings(') !== FALSE) {
            $found = TRUE;
            unset($onload_code[$src]);
            break;
          }
        }
        if ($found) {
          $values['#value'] = 'function init_drupal_core_settings() {' . $values['#value'] . '}';
        }
      }
    }
    unset($values);
  }

  if (variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM)) {
    foreach ($children as &$values) {
      if (isset($values['#attributes']) && isset($values['#attributes']['async']) && $values['#attributes']['async'] === 'async' && !empty($values['#attributes']['src'])) {
        $source = $values['#attributes']['src'];
        // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace
        if ( strpos($source, 'http://') !== 0
          && strpos($source, 'https://') !== 0
          && strpos($source, '//') !== 0
        ) {
          $source = url($values['#attributes']['src']);
        }
        $values['#value'] = "(function() {
  var s = document.createElement('script');
  s.type = 'text/javascript';
  s.async = true;
  s.src = '$source';
  var d = document.getElementsByTagName('script')[0];
  d.parentNode.insertBefore(s, d);
})();";
        unset($values['#attributes']['async']);
        unset($values['#attributes']['src']);
      }
    }
    unset($values);
  }
}

/**
 * Implements hook_advagg_modify_css_pre_render_alter().
 */
function advagg_mod_advagg_modify_css_pre_render_alter(&$children, &$elements) {
  if (module_exists('advagg') && !advagg_enabled()) {
    return;
  }

  // Return early if this setting is disabled.
  $css_defer = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER);
  if (empty($css_defer)) {
    return;
  }
  $css_defer_js_code = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE);

  // Make advagg_mod_loadStyleSheet() available.
  $type = 'external';
  $data = '//rawgit.com/filamentgroup/loadCSS/master/loadCSS.js';
  $min = '';
  if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) {
    $min = '.min';
  }
  if ($css_defer_js_code == 2) {
    $type = 'file';
    $data = drupal_get_path('module', 'advagg_mod') . "/loadCSS$min.js";
  }
  if ($css_defer_js_code == 0) {
    $type = 'inline';
    $data = '//[c]2014 @scottjehl, Filament Group, Inc. Licensed MIT.
function loadCSS(a,b,c,d){"use strict";var e=window.document.createElement("link"),f=b||window.document.getElementsByTagName("script")[0],g=window.document.styleSheets;return e.rel="stylesheet",e.href=a,e.media="only x",d&&(e.onload=d),f.parentNode.insertBefore(e,f),e.onloadcssdefined=function(b){for(var c,d=0;d<g.length;d++)g[d].href&&g[d].href.indexOf(a)>-1&&(c=!0);c?b():setTimeout(function(){e.onloadcssdefined(b)})},e.onloadcssdefined(function(){e.media=c||"all"}),e}';
  }
  $options = array(
    'type' => $type,
    'scope' => $css_defer >= 7 ? 'footer' : 'header',
    'scope_lock' => TRUE,
    'every_page' => TRUE,
    'group' => $css_defer == 1 ? JS_LIBRARY - 1 : JS_LIBRARY,
    'weight' => $css_defer == 1 ? -50000 : 0,
    'movable' => $css_defer == 1 ? FALSE : TRUE,
  );
  if ($type !== 'inline') {
    $options['async'] = TRUE;
  }
  drupal_add_js($data, $options);

  // Wrap CSS in noscript tags.
  $options = array(
    'type' => 'inline',
    'scope' => $css_defer >= 5 ? 'footer' : 'header',
    'scope_lock' => TRUE,
    'group' => $css_defer == 1 ? JS_LIBRARY - 1 : JS_DEFAULT,
    'weight' => $css_defer == 1 ? -50000 : 0,
    'movable' => $css_defer == 1 ? FALSE : TRUE,
  );
  foreach ($children as &$values) {
    // Do not defer inline scripts.
    if ($values['#tag'] === 'style') {
      continue;
    }
    if (!empty($values['#attributes']['href'])) {
      // Wrap current css in noscript tags.
      $values['#prefix'] = '<noscript>' . "\n";
      $values['#suffix'] = '</noscript>';

      // Add browsers to the js options.
      if (isset($values['#browsers'])) {
        $options['browsers'] = $values['#browsers'];
      }
      $inline = 'loadCSS("' . $values['#attributes']['href'] . '")';
      // Make async work if it's being used.
      if ($type !== 'inline') {
        $matches[2] = $matches[0] = $inline;
        $inline = advagg_mod_wrap_inline_js($matches, "window.loadCSS", 40);
      }
      // Add in script tags to load css via js.
      drupal_add_js($inline, $options);
      // Reset for next item in loop.
      if (isset($options['browsers'])) {
        unset($options['browsers']);
      }
    }
  }
  unset($values);
}

/**
 * Implements hook_advagg_get_root_files_dir_alter().
 */
function advagg_mod_advagg_get_root_files_dir_alter(&$css_paths, &$js_paths) {
  $dir = rtrim(variable_get('advagg_mod_unified_multisite_dir', ''), '/');
  if (empty($dir) || !file_exists($dir) || !is_dir($dir)) {
    return;
  }
  // Change directory.
  $css_paths[0] = $dir . '/advagg_css';
  $js_paths[0] = $dir . '/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]);
  $js_paths[1] = advagg_get_relative_path($js_paths[0]);
}

/**
 * Implements hook_advagg_current_hooks_hash_array_alter().
 */
function advagg_mod_advagg_current_hooks_hash_array_alter(&$aggregate_settings) {
  // JS Settings.
  $aggregate_settings['variables']['advagg_mod_js_async_shim'] = variable_get('advagg_mod_js_async_shim', ADVAGG_MOD_JS_ASYNC_SHIM);

  // Make safe if using the aggressive cache.
  if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) {
    $aggregate_settings['variables']['advagg_mod_js_preprocess'] = variable_get('advagg_mod_js_preprocess', ADVAGG_MOD_JS_PREPROCESS);
    $aggregate_settings['variables']['advagg_mod_js_remove_unused'] = variable_get('advagg_mod_js_remove_unused', ADVAGG_MOD_JS_REMOVE_UNUSED);
    $aggregate_settings['variables']['advagg_mod_js_head_extract'] = variable_get('advagg_mod_js_head_extract', ADVAGG_MOD_JS_HEAD_EXTRACT);
    $aggregate_settings['variables']['advagg_mod_js_adjust_sort_external'] = variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL);
    $aggregate_settings['variables']['advagg_mod_js_adjust_sort_inline'] = variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE);
    $aggregate_settings['variables']['advagg_mod_js_adjust_sort_browsers'] = variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS);
    $aggregate_settings['variables']['advagg_mod_ga_inline_to_file'] = variable_get('advagg_mod_ga_inline_to_file', ADVAGG_MOD_GA_INLINE_TO_FILE);
    $aggregate_settings['variables']['advagg_mod_js_footer'] = variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER);
    $aggregate_settings['variables']['advagg_mod_js_defer'] = variable_get('advagg_mod_js_defer', ADVAGG_MOD_JS_DEFER);
    $aggregate_settings['variables']['advagg_mod_js_footer_inline_alter'] = variable_get('advagg_mod_js_footer_inline_alter', ADVAGG_MOD_JS_FOOTER_INLINE_ALTER);
    $aggregate_settings['variables']['advagg_mod_js_async'] = variable_get('advagg_mod_js_async', ADVAGG_MOD_JS_ASYNC);
  }

  // CSS Settings.
  $aggregate_settings['variables']['advagg_mod_css_translate'] = variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE);
  if (variable_get('advagg_mod_css_translate', ADVAGG_MOD_CSS_TRANSLATE)) {
    $aggregate_settings['variables']['advagg_mod_css_translate_lang'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : 'en';
  }

  // Make safe if using the aggressive cache.
  if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) {
    $aggregate_settings['variables']['advagg_mod_css_preprocess'] = variable_get('advagg_mod_css_preprocess', ADVAGG_MOD_CSS_PREPROCESS);
    $aggregate_settings['variables']['advagg_mod_css_head_extract'] = variable_get('advagg_mod_css_head_extract', ADVAGG_MOD_CSS_HEAD_EXTRACT);
    $aggregate_settings['variables']['advagg_mod_css_adjust_sort_external'] = variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL);
    $aggregate_settings['variables']['advagg_mod_css_adjust_sort_inline'] = variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE);
    $aggregate_settings['variables']['advagg_mod_css_adjust_sort_browsers'] = variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS);
    $aggregate_settings['variables']['advagg_mod_css_defer'] = variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER);
    $aggregate_settings['variables']['advagg_mod_css_defer_js_code'] = variable_get('advagg_mod_css_defer_js_code', ADVAGG_MOD_CSS_DEFER_JS_CODE);
    $aggregate_settings['variables']['advagg_mod_inline_visibility'] = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED);
    $aggregate_settings['variables']['advagg_mod_inline_pages'] = variable_get('advagg_mod_inline_pages', '');
  }
}

// Helper Functions.
/**
 * Callback for preg_replace_callback.
 *
 * Used to wrap inline JS in a function in order to prevent js errors when JS is
 * moved to the footer, or when js is loaded async.
 *
 * @param array $matches
 *   $matches[0] is the full string; $matches[2] is just the JavaScript.
 * @param string $check_string
 *   JavaScript if statement; when true, run the inline code.
 * @param int $ms_wait
 *   Default amount of time to wait until the required code is available.
 *
 * @return string
 *   Inline javascript code wrapped up in a loader to prevent errors.
 */
function advagg_mod_wrap_inline_js(array $matches, $check_string = NULL, $ms_wait = 250) {
  if (empty($check_string)) {
    $check_string = 'window.jQuery && window.Drupal && window.Drupal.settings';
  }

  // Get inline js skip list string and convert it to an array.
  $inline_js_skip_list = array_filter(array_map('trim', explode("\n", variable_get('advagg_mod_wrap_inline_js_skip_list', ''))));
  if (!empty($inline_js_skip_list)) {
    // If the line is on the skip list then do not inline the script.
    foreach ($inline_js_skip_list as $string_to_check) {
      if (stripos($matches[2], $string_to_check) !== FALSE) {
        return $matches[0];
      }
    }
  }

  // Use a counter in order to create unique function names.
  static $counter;
  ++$counter;

  // JS wrapper code.
  $new = "
function advagg_mod_$counter() {
  // Count how many times this function is called.
  advagg_mod_$counter.count = ++advagg_mod_$counter.count || 1;
  try {
    if (advagg_mod_$counter.count <= 40) {
      $matches[2]

      // Set this to 100 so that this function only runs once.
      advagg_mod_$counter.count = 100;
    }
  }
  catch(e) {
    if (advagg_mod_$counter.count >= 40) {
      // Throw the exception if this still fails after running 40 times.
      throw e;
    }
    else {
      // Try again in $ms_wait ms.
      window.setTimeout(advagg_mod_$counter, $ms_wait);
    }
  }
}
function advagg_mod_${counter}_check() {
  if ($check_string) {
    advagg_mod_$counter();
  }
  else {
    window.setTimeout(advagg_mod_${counter}_check, $ms_wait);
  }
}
advagg_mod_${counter}_check();";

  $return = str_replace($matches[2], $new, $matches[0]);
  return $return;
}

/**
 * Rearrange CSS/JS so that aggregates are better grouped.
 *
 * This can move all external assets to the top, thus in one group.
 * This can move all inline assets to the bottom, thus in one group.
 * This can move all browser conditional assets together.
 *
 * @param array $array
 *   The CSS or JS array.
 * @param string $type
 *   String: css or js.
 */
function advagg_mod_sort_css_js(array &$array, $type) {
  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3
  if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_external', ADVAGG_MOD_JS_ADJUST_SORT_EXTERNAL))
    || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_external', ADVAGG_MOD_CSS_ADJUST_SORT_EXTERNAL))
  ) {
    // Find all external items.
    $external = array();
    $group = NULL;
    $every_page = NULL;
    $weight = NULL;
    foreach ($array as $key => $value) {
      // Set values if not set.
      if (is_null($group)) {
        $group = $value['group'];
      }
      if (is_null($every_page)) {
        $every_page = $value['every_page'];
      }
      if (is_null($weight)) {
        $weight = $value['weight'];
      }

      // Find "lightest" item.
      if ($value['group'] < $group) {
        $group = $value['group'];
      }
      if ($value['every_page'] && !$every_page) {
        $every_page = $value['every_page'];
      }
      if ($value['weight'] < $weight) {
        $weight = $value['weight'];
      }

      if (!empty($value['type']) && $value['type'] === 'external') {
        $external[$key] = $value;
        unset($array[$key]);
      }

      if (!empty($value['type']) && $value['type'] === 'inline') {
        // Move jQuery fallback as well.
        if (strpos($value['data'], 'window.jQuery') === 0) {
          $external[$key] = $value;
          unset($array[$key]);
        }
        // Move jQuery ui fallback as well.
        if (strpos($value['data'], 'window.jQuery.ui') === 0) {
          $external[$key] = $value;
          unset($array[$key]);
        }
      }
    }
    // Sort the array so that it appears in the correct order.
    advagg_drupal_sort_css_js_stable($external);

    // Group all external together.
    $offset = 0.0001;
    $weight += -1;
    $found_jquery = FALSE;
    foreach ($external as $key => $value) {
      if (isset($value['movable']) && empty($value['movable'])) {
        $array[$key] = $value;
        continue;
      }
      // If bootstrap is used, it must be loaded after jquery. Don't move
      // bootstrap if jquery is not above it.
      if ( strpos($value['data'], 'jquery.min.js') !== FALSE
        || strpos($value['data'], 'jquery.js') !== FALSE
      ) {
        $found_jquery = TRUE;
      }
      if (!$found_jquery && (strpos($value['data'], 'bootstrap.min.js') !== FALSE || strpos($value['data'], 'bootstrap.js') !== FALSE)) {
        $array[$key] = $value;
        continue;
      }
      $value['group'] = $group;
      $value['every_page'] = $every_page;
      $value['weight'] = $weight;
      $weight += $offset;
      $array[$key] = $value;
    }
  }

  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3
  if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_inline', ADVAGG_MOD_JS_ADJUST_SORT_INLINE))
    || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_inline', ADVAGG_MOD_CSS_ADJUST_SORT_INLINE))
  ) {
    // Find all inline items.
    $inline = array();
    $group = NULL;
    $every_page = NULL;
    $weight = NULL;
    foreach ($array as $key => $value) {
      // Set values if not set.
      if (is_null($group)) {
        $group = $value['group'];
      }
      if (is_null($every_page)) {
        $every_page = $value['every_page'];
      }
      if (is_null($weight)) {
        $weight = $value['weight'];
      }

      // Find "heaviest" item.
      if ($value['group'] > $group) {
        $group = $value['group'];
      }
      if (!$value['every_page'] && $every_page) {
        $every_page = $value['every_page'];
      }
      if ($value['weight'] > $weight) {
        $weight = $value['weight'];
      }

      if (!empty($value['type']) && $value['type'] === 'inline') {
        // Do not move jQuery fallback.
        if (strpos($value['data'], 'window.jQuery') === 0) {
          continue;
        }
        // Do not move jQuery.ui fallback.
        if (strpos($value['data'], 'window.jQuery.ui') === 0) {
          continue;
        }
        $inline[$key] = $value;
        unset($array[$key]);
      }
    }
    // Sort the array so that it appears in the correct order.
    advagg_drupal_sort_css_js_stable($inline);

    // Group all inline together.
    $offset = 0.0001;
    $weight += 1;
    foreach ($inline as $key => $value) {
      if (isset($value['movable']) && empty($value['movable'])) {
        $array[$key] = $value;
        continue;
      }
      $value['group'] = $group;
      $value['every_page'] = $every_page;
      $value['weight'] = $weight;
      $weight += $offset;
      $array[$key] = $value;
    }
  }

  // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:3
  if ( ($type === 'js' && variable_get('advagg_mod_js_adjust_sort_browsers', ADVAGG_MOD_JS_ADJUST_SORT_BROWSERS))
    || ($type === 'css' && variable_get('advagg_mod_css_adjust_sort_browsers', ADVAGG_MOD_CSS_ADJUST_SORT_BROWSERS))
  ) {
    // Get a list of browsers.
    $browsers_list = array();
    foreach ($array as $key => $value) {
      if (isset($value['browsers']['IE']) && $value['browsers']['IE'] !== TRUE) {
        $browsers_list['IE'][] = $value['browsers']['IE'];
      }
    }

    // Group browsers CSS together.
    if (isset($browsers_list['IE'])) {
      $browsers_list['IE'] = array_values(array_unique($browsers_list['IE']));
      foreach ($browsers_list['IE'] as $browser) {
        $browsers = array();
        $group = NULL;
        $every_page = NULL;
        $weight = NULL;
        foreach ($array as $key => $value) {
          if (isset($value['browsers']['IE']) && $browser === $value['browsers']['IE']) {
            // Set values if not set.
            if (is_null($group)) {
              $group = $value['group'];
            }
            if (is_null($every_page)) {
              $every_page = $value['every_page'];
            }
            if (is_null($weight)) {
              $weight = $value['weight'];
            }

            // Find "heaviest" item.
            if ($value['group'] > $group) {
              $group = $value['group'];
            }
            if (!$value['every_page'] && $every_page) {
              $every_page = $value['every_page'];
            }
            if ($value['weight'] > $weight) {
              $weight = $value['weight'];
            }

            $browsers[$key] = $value;
            unset($array[$key]);
          }
        }

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

        // Group all browsers together.
        $offset = 0.0001;
        foreach ($browsers as $key => $value) {
          if (isset($value['movable']) && empty($value['movable'])) {
            $array[$key] = $value;
            continue;
          }
          $value['group'] = $group;
          $value['every_page'] = $every_page;
          $value['weight'] = $weight;
          $weight += $offset;
          $array[$key] = $value;
        }
      }
    }
  }
}

/**
 * Returns TRUE if this page should have inline CSS/JS.
 *
 * @return bool
 *   TRUE or FALSE.
 */
function advagg_mod_inline_page() {
  $visibility = variable_get('advagg_mod_inline_visibility', ADVAGG_MOD_VISIBILITY_LISTED);
  $pages = variable_get('advagg_mod_inline_pages', '');
  return advagg_mod_match_path($pages, $visibility);
}

/**
 * Transforms all JS files into inline JS.
 *
 * @param array $js
 *   JS array.
 */
function advagg_mod_inline_js(array &$js) {
  $aggregate_settings = advagg_current_hooks_hash_array();

  foreach ($js as &$values) {
    // Only process files.
    if ($values['type'] !== 'file') {
      continue;
    }
    $filename = $values['data'];
    if (file_exists($filename)) {
      $contents = file_get_contents($filename);
    }
    // Allow other modules to modify this files contents.
    // Call hook_advagg_get_js_file_contents_alter().
    drupal_alter('advagg_get_js_file_contents', $contents, $filename, $aggregate_settings);

    $values['data'] = $contents;
    $values['type'] = 'inline';
  }
  unset($values);
}

/**
 * Transforms all CSS files into inline CSS.
 *
 * @param array $css
 *   CSS array.
 *
 * @see advagg_get_css_aggregate_contents()
 * @see drupal_build_css_cache()
 */
function advagg_mod_inline_css(array &$css) {
  $aggregate_settings = advagg_current_hooks_hash_array();
  $optimize = TRUE;
  module_load_include('inc', 'advagg', 'advagg');

  foreach ($css as &$values) {
    // Only process files.
    if ($values['type'] !== 'file') {
      continue;
    }

    $file = $values['data'];
    if (file_exists($file)) {
      $contents = advagg_load_css_stylesheet($file, $optimize, $aggregate_settings);

      // Allow other modules to modify this files contents.
      // Call hook_advagg_get_css_file_contents_alter().
      drupal_alter('advagg_get_css_file_contents', $contents, $file, $aggregate_settings);

      // Per the W3C specification at
      // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import rules
      // must proceed any other style, so we move those to the top.
      $regexp = '/@import[^;]+;/i';
      preg_match_all($regexp, $contents, $matches);
      $contents = preg_replace($regexp, '', $contents);
      $contents = implode('', $matches[0]) . $contents;

      $values['data'] = $contents;
      $values['type'] = 'inline';
    }
  }
  unset($values);
}

/**
 * Transforms all CSS files into inline CSS.
 *
 * @param string $pages
 *   String from the advagg_mod_inline_pages variable.
 * @param int $visibility
 *   Visibility setting from the advagg_mod_inline_visibility variable.
 *
 * @return bool
 *   TRUE if the current path matches the given pages.
 *
 * @see block_block_list_alter()
 */
function advagg_mod_match_path($pages, $visibility) {
  // Limited visibility blocks must list at least one page.
  if ($visibility == ADVAGG_MOD_VISIBILITY_LISTED && empty($pages)) {
    $page_match = FALSE;
  }
  elseif ($pages) {
    // Match path if necessary.
    // Convert path to lowercase. This allows comparison of the same path
    // with different case. Ex: /Page, /page, /PAGE.
    $pages = drupal_strtolower($pages);
    if ($visibility < ADVAGG_MOD_VISIBILITY_PHP) {
      // Convert the Drupal path to lowercase.
      $path = drupal_strtolower(drupal_get_path_alias($_GET['q']));
      // Compare the lowercase internal and lowercase path alias (if any).
      $page_match = drupal_match_path($path, $pages);
      if ($path != $_GET['q']) {
        $page_match = $page_match || drupal_match_path($_GET['q'], $pages);
      }
      // When $visibility has a value of 0 (ADVAGG_MOD_VISIBILITY_NOTLISTED),
      // the block is displayed on all pages except those listed in $pages.
      // When set to 1 (ADVAGG_MOD_VISIBILITY_LISTED), it is displayed only on
      // those pages listed in $block->pages.
      $page_match = !($visibility xor $page_match);
    }
    elseif (module_exists('php')) {
      $page_match = php_eval($pages);
    }
    else {
      $page_match = FALSE;
    }
  }
  else {
    $page_match = TRUE;
  }

  return $page_match;
}

/**
 * See if JavaScript file contains drupal and/or jquery.
 *
 * @param string $filename
 *   Inline css, full URL, or filename.
 * @param string $type
 *   (Optional) inline, external, or file.
 *
 * @return array
 *   Returns an array stating if this JS file contains drupal or jquery.
 */
function advagg_mod_js_contains_jquery_drupal($filename, $type = '') {
  if (is_string($filename)) {
    if ($type === 'inline') {
      $contents = $filename;
    }
    // @ignore sniffer_whitespace_openbracketspacing_openingwhitespace:1
    elseif ( $type === 'external'
          || strpos($filename, 'http://') === 0
          || strpos($filename, 'https://') === 0
          || strpos($filename, '//') === 0
    ) {
      $result = drupal_http_request($filename);
      if (($result->code == 200 || (isset($result->redirect_code) && $result->redirect_code == 200)) && !empty($result->data)) {
        $contents = $result->data;
      }
    }
    elseif (file_exists($filename)) {
      $contents = file_get_contents($filename);
    }
  }

  $results = array();
  if (!empty($contents) && stripos($contents, 'drupal.') !== FALSE) {
    $results['contents']['drupal'] = TRUE;
    if (stripos($contents, 'drupal.settings.') !== FALSE) {
      $results['contents']['drupal.settings'] = TRUE;
    }
    else {
      $results['contents']['drupal.settings'] = FALSE;
    }
    if (stripos($contents, 'drupal.behaviors.') !== FALSE) {
      $results['contents']['drupal.behaviors'] = TRUE;
    }
    else {
      $results['contents']['drupal.behaviors'] = FALSE;
    }
  }
  else {
    $results['contents']['drupal'] = FALSE;
    $results['contents']['drupal.settings'] = FALSE;
    $results['contents']['drupal.behaviors'] = FALSE;
  }
  if (!empty($contents) && stripos($contents, 'jquery') !== FALSE) {
    $results['contents']['jquery'] = TRUE;
  }
  else {
    $results['contents']['jquery'] = FALSE;
  }
  return $results;
}

/**
 * Move analytics.js to be a file instead of inline.
 *
 * @param array $js
 *   JS array.
 */
function advagg_mod_ga_inline_to_file(array &$js) {
  // Do nothing if the googleanalytics module is not enabled.
  if (!module_exists('googleanalytics')) {
    return;
  }

  // Get inline GA js and put it inside of an aggregrate.
  $ga_script = '';
  $debug = variable_get('googleanalytics_debug', 0);
  $library_tracker_url = '//www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js');
  $library_cache_url = 'http:' . $library_tracker_url;
  $ga_script = _googleanalytics_cache($library_cache_url);
  if (variable_get('googleanalytics_cache', 0) && $ga_script) {
    // 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.
    $ga_script_len = strlen('"' . $ga_script . '?' . variable_get('css_js_query_string', '0') . '"');

    $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);
    $ga_script = substr($ga_script, stripos($ga_script, $mod_base_url) + $mod_base_url_len);
  }

  if (!empty($ga_script)) {
    foreach ($js as $key => $value) {
      // Skip if not inline.
      if ($value['type'] !== 'inline') {
        continue;
      }
      // Skip if it doesn't start with the GoogleAnalytics inline loader string.
      if (strpos($value['data'], '(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,"script",') !== 0) {
        continue;
      }
      // Strip loader string.
      $matches[2] = $matches[0] = substr($value['data'], 261 + $ga_script_len + 7);
      $js[$key]['data'] = advagg_mod_wrap_inline_js($matches, "window.ga");

      // Add GA analytics.js file to the $js array.
      $js[$ga_script] = array(
        'data' => $ga_script,
        'type' => 'file',
        // 'async' => TRUE,
      );
      $js[$ga_script] += $value;
      break;
    }
  }
}

/**
 * Remove JS if not in use on current page.
 *
 * @param array $js
 *   JS array.
 */
function advagg_remove_js_if_not_used(array &$js) {
  // Do not run the following code if drupal_add_js_page_defaults exists.
  if (function_exists('drupal_add_js_page_defaults')) {
    return;
  }

  $files_skiplist = array(
    'drupal.js',
    'jquery.js',
    'jquery.min.js',
    'jquery.once.js',
  );
  $inline_skiplist = array();
  if (module_exists('jquery_update')) {
    $inline_skiplist[] = 'document.write("<script src=\'' . $GLOBALS['base_path'] . drupal_get_path('module', 'jquery_update') . '/replace/jquery/' . variable_get('jquery_update_jquery_version', '1.10') . '/jquery' . (variable_get('jquery_update_compression_type', 'min') === 'none' ? '' : '.min') . ".js'>";
  }
  if (module_exists('labjs')) {
    $inline_skiplist[] = 'var $L = $LAB.setGlobalDefaults';
  }

  $include_jquery = FALSE;
  $include_drupal = FALSE;
  module_load_include('inc', 'advagg', 'advagg');

  // Look at each JavaScript entry & get the info on it.
  $files_info_filenames = array();
  foreach ($js as &$values) {
    if ($values['type'] === 'file' || $values['type'] === 'external') {
      foreach ($files_skiplist as $skip_name) {
        if (substr_compare($values['data'], $skip_name, -strlen($skip_name), strlen($skip_name)) === 0) {
          continue 2;
        }
      }
      $files_info_filenames[] = $values['data'];

    }
  }
  unset($values);
  $files_info = advagg_get_info_on_files($files_info_filenames);

  // Look at each JavaScript entry & see if it uses jquery or drupal.
  foreach ($js as $name => &$values) {
    if ($values['type'] === 'file' || $values['type'] === 'external') {
      foreach ($files_skiplist as $skip_name) {
        if (substr_compare($values['data'], $skip_name, -strlen($skip_name), strlen($skip_name)) === 0) {
          continue 2;
        }
      }
    }
    if ($values['type'] === 'inline' && !empty($inline_skiplist)) {
      foreach ($inline_skiplist as $skip_string) {
        if (stripos($values['data'], $skip_string) !== FALSE) {
          continue 2;
        }
      }
    }
    // Get advagg_mod info if not set.
    if (!isset($files_info[$name]['advagg_mod'])) {
      $files_info[$name]['advagg_mod'] = advagg_mod_js_contains_jquery_drupal($values['data'], $values['type']);
    }
    if ($files_info[$name]['advagg_mod']['contents']['drupal']) {
      $include_jquery = TRUE;
      $include_drupal = TRUE;
    }
    elseif ($files_info[$name]['advagg_mod']['contents']['jquery']) {
      $include_jquery = TRUE;
    }
  }
  unset($values);

  // Kill only drupal JavaScript.
  if (!$include_drupal) {
    unset($js['settings']);
    foreach ($js as $name => &$values) {
      $drupal = 'drupal.js';
      if (substr_compare($name, $drupal, -strlen($drupal), strlen($drupal)) === 0) {
        unset($js[$name]);
      }
    }
    unset($values);

    // Kill all default JavaScript.
    if (!$include_jquery) {
      foreach ($js as $name => &$values) {
        if ($values['type'] === 'file' || $values['type'] === 'external') {
          foreach ($files_skiplist as $skip_name) {
            if (substr_compare($name, $skip_name, -strlen($skip_name), strlen($skip_name)) === 0) {
              unset($js[$name]);
            }
          }
        }
        elseif ($values['type'] === 'inline') {
          foreach ($inline_skiplist as $skip_string) {
            if (stripos($values['data'], $skip_string) !== FALSE) {
              unset($js[$name]);
            }
          }
        }
      }
      unset($values);
    }
  }
}

// @ignore sniffer_commenting_functioncomment_hookreturndoc:12
// @ignore sniffer_commenting_functioncomment_hookparamdoc:8
/**
 * Implements hook_magic().
 *
 * @param array $magic_settings
 *   The renderable form array of the magic module theme settings. READ ONLY.
 * @param string $theme
 *   The theme that the settings will be editing.
 *
 * @return array
 *   The array of settings within the magic module theme page. Must not contain
 *   anything from the $magic_settings array.
 */
function advagg_mod_magic(array $magic_settings, $theme) {
  $settings = array();

  // If possible disable access and set default to false.
  if (!isset($magic_settings['css']['magic_embedded_mqs']['#access'])) {
    $settings['css']['magic_embedded_mqs']['#access'] = FALSE;
  }
  if (!isset($magic_settings['css']['magic_embedded_mqs']['#default_value'])) {
    $settings['css']['magic_embedded_mqs']['#default_value'] = FALSE;
  }
  if (!isset($magic_settings['js']['magic_footer_js']['#access'])) {
    $settings['js']['magic_footer_js']['#access'] = FALSE;
  }
  if (!isset($magic_settings['js']['magic_footer_js']['#default_value'])) {
    $settings['js']['magic_footer_js']['#default_value'] = FALSE;
  }
  if (!isset($magic_settings['js']['magic_library_head']['#access'])) {
    $settings['js']['magic_library_head']['#access'] = FALSE;
  }
  if (!isset($magic_settings['js']['magic_library_head']['#default_value'])) {
    $settings['js']['magic_library_head']['#default_value'] = FALSE;
  }
  if (!isset($magic_settings['js']['magic_experimental_js']['#access'])) {
    $settings['js']['magic_experimental_js']['#access'] = FALSE;
  }
  if (!isset($magic_settings['js']['magic_experimental_js']['#default_value'])) {
    $settings['js']['magic_experimental_js']['#default_value'] = FALSE;
  }

  // Add in our own validate function so we can preprocess variables before
  // they are saved.
  $settings['#validate'] = array('advagg_mod_magic_form_validate');
  return $settings;
}

/**
 * Form validation handler. Disable certain magic settings before being saved.
 */
function advagg_mod_magic_form_validate($form, &$form_state) {
  // Disable magic functionality if it is a duplicate of AdvAgg.
  $form_state['values']['magic_embedded_mqs'] = 0;
  $form_state['values']['magic_footer_js'] = 0;
  $form_state['values']['magic_library_head'] = 0;
  $form_state['values']['magic_experimental_js'] = 0;
}
