You are here

advagg.module in Advanced CSS/JS Aggregation 7

Advanced CSS/JS aggregation module

File

advagg.module
View source
<?php

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

// Load Includes
$includes = array(
  'constants',
  'css',
  'js',
);
foreach ($includes as $file) {
  $file = dirname(__FILE__) . "/includes/{$file}.inc";
  if (file_exists($file)) {
    include_once $file;
  }
}

/**
 * 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_menu().
 */
function advagg_menu() {
  list($css_path, $js_path) = advagg_get_root_files_dir();
  $file_path = drupal_get_path('module', 'advagg') . '/includes';
  $items = array();
  $items[$css_path . '/%'] = array(
    'page callback' => 'advagg_missing_css',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
    'file path' => $file_path,
    'file' => 'missing.inc',
  );
  $items[$js_path . '/%'] = array(
    'page callback' => 'advagg_missing_js',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
    'file path' => $file_path,
    'file' => 'missing.inc',
  );
  return $items;
}

/**
 * Implements hook_admin_menu().
 *
 * Add in a cache flush for advagg.
 */
function advagg_admin_menu(&$deleted) {
  $links = array();
  $links[] = array(
    'title' => 'Adv CSS/JS Agg',
    'path' => 'admin_menu/flush-cache/advagg',
    'query' => 'destination',
    'parent_path' => 'admin_menu/flush-cache',
  );
  return $links;
}
function advagg_admin_menu_cache_info() {
  $caches['advagg'] = array(
    'title' => t('Advanced CSS/JS Aggregation'),
    'callback' => 'advagg_admin_flush_cache',
  );
  return $caches;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function advagg_form_system_performance_settings_alter(&$form, &$form_state) {
  include_once dirname(__FILE__) . "/includes/admin.inc";
  _advagg_form_system_performance_settings_alter($form, $form_state);
}

/**
 * Implements hook_admin_menu_output_alter().
 *
 * Add in a cache flush for advagg.
 */
function advagg_admin_menu_output_alter(&$content) {
  if (!empty($content['icon']['icon']['flush-cache']['#access']) && !empty($content['icon']['icon']['flush-cache']['requisites']) && empty($content['icon']['icon']['flush-cache']['advagg'])) {
    $content['icon']['icon']['flush-cache']['advagg'] = $content['icon']['icon']['flush-cache']['requisites'];
    $content['icon']['icon']['flush-cache']['advagg']['#title'] = t('Adv CSS/JS Agg');
    $content['icon']['icon']['flush-cache']['advagg']['#href'] = 'admin_menu/flush-cache/advagg';
  }
}

/**
 * Implements hook_cron().
 */
function advagg_cron() {
  if (!variable_get('advagg_prune_on_cron', ADVAGG_PRUNE_ON_CRON)) {
    return;
  }

  // Set the oldest file/bundle to keep at 2 weeks.
  $max_time = module_exists('advagg_bundler') ? variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED) : 1209600;
  $max_file_time = REQUEST_TIME - $max_time;
  $max_bundle_time = REQUEST_TIME - $max_time * 3;
  $bundles_removed = 0;
  $files_removed = array();

  // Prune old files
  $results = db_query("SELECT filename, filename_md5 FROM {advagg_files}");
  while ($row = $results
    ->fetchAssoc()) {

    // If the file exists, do nothing
    if (file_exists($row['filename'])) {
      continue;
    }

    // Remove bundles referencing missing files, if they are older than 2 weeks.
    $bundles = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = :filename_md5 AND timestamp < :timestamp", array(
      ':filename_md5' => $row['filename_md5'],
      ':timestamp' => $max_file_time,
    ));
    while ($bundle_md5 = $bundles
      ->fetchField()) {
      $bundles_removed++;

      // TODO Please review the conversion of this statement to the D7 database API syntax.

      /* db_query("DELETE FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5) */
      db_delete('advagg_bundles')
        ->condition('bundle_md5', $bundle_md5)
        ->execute();
    }
    $count = db_query("SELECT COUNT(*) FROM {advagg_bundles} WHERE filename_md5 = :filename_md5", array(
      ':filename_md5' => $row['filename_md5'],
    ))
      ->fetchField();

    // If no more bundles reference the missing file then remove the file.
    if (empty($count)) {

      // TODO Please review the conversion of this statement to the D7 database API syntax.

      /* db_query("DELETE FROM {advagg_files} WHERE filename_md5 = '%s'", $row['filename_md5']) */
      db_delete('advagg_files')
        ->condition('filename_md5', $row['filename_md5'])
        ->execute();
      $files_removed[] = $row['filename'];
    }
  }

  // Prune old bundles
  $bundles_removed += db_query("\n    SELECT COUNT(DISTINCT bundle_md5) AS advagg_count\n    FROM advagg_bundles\n    WHERE timestamp < %d\n  ", array(
    $max_bundle_time,
  ))
    ->fetchField();

  // TODO Please review the conversion of this statement to the D7 database API syntax.

  /* db_query("DELETE FROM {advagg_bundles} WHERE timestamp < %d", $max_bundle_time) */
  $results = db_delete('advagg_bundles')
    ->condition('timestamp', $max_bundle_time, '<')
    ->execute();

  // Report to watchdog if anything was done.
  if (!empty($bundles_removed) || !empty($files_removed)) {
    watchdog('advagg', 'Cron ran and the following files where removed from the database: %files <br /> %count old bundles where also removed from the database.', array(
      '%files' => implode(', ', $files_removed),
      '%count' => $bundles_removed,
    ));
  }
}

/**
 * Implements hook_init().
 */
function advagg_init() {
  global $base_path, $conf;

  // Initially set these values.
  if (!isset($conf['advagg_enabled'])) {
    variable_set('advagg_enabled', ADVAGG_ENABLED);
  }
  if (!isset($conf['advagg_closure'])) {
    variable_set('advagg_closure', ADVAGG_CLOSURE);
  }
  if (!isset($conf['advagg_async_generation'])) {
    variable_set('advagg_async_generation', ADVAGG_ASYNC_GENERATION);
  }
  if (!isset($conf['advagg_gzip_compression'])) {
    variable_set('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION);
  }
  if (!isset($conf['advagg_dir_htaccess'])) {
    variable_set('advagg_dir_htaccess', ADVAGG_DIR_HTACCESS);
  }
  if (!isset($conf['advagg_rebuild_on_flush'])) {
    variable_set('advagg_rebuild_on_flush', ADVAGG_REBUILD_ON_FLUSH);
  }
  if (!isset($conf['advagg_custom_files_dir'])) {
    variable_set('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR);
  }
  if (!isset($conf['advagg_aggregate_mode'])) {
    variable_set('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE);
  }
  if (!isset($conf['advagg_page_cache_mode'])) {
    variable_set('advagg_page_cache_mode', ADVAGG_PAGE_CACHE_MODE);
  }
  if (!isset($conf['advagg_checksum_mode'])) {
    variable_set('advagg_checksum_mode', ADVAGG_CHECKSUM_MODE);
  }
  if (!isset($conf['advagg_server_addr'])) {
    variable_set('advagg_server_addr', FALSE);
  }

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

  // Enable debugging if requested.
  if (isset($_GET['advagg-debug']) && $_GET['advagg-debug'] == 1 && user_access('bypass advanced aggregation')) {
    $conf['advagg_debug'] = TRUE;
    $conf['advagg_use_full_cache'] = FALSE;
  }

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

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

  // Swap in our own aggregation callback.
  if (isset($type['styles']['#aggregate_callback'])) {
    $type['styles']['#aggregate_callback'] = '_advagg_aggregate_css';

    //     $type['styles']['#group_callback'] = '_advagg_group_css';
  }
}

/**
 * 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 $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_group_css()
 * @see drupal_pre_render_styles()
 * @see system_element_info()
 */
function _advagg_aggregate_css(&$css_groups) {
  $preprocess_css = variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update');

  // For each group that needs aggregation, aggregate its items.
  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) {
          $css_groups[$key]['data'] = drupal_build_css_cache($group['items']);
        }
        break;

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

/**
 * Implements hook_process_html().
 */
function advagg_process_html(&$variables) {
  global $_advagg, $conf;

  // Invoke hook_advagg_disable_processor
  $disabled = module_invoke_all('advagg_disable_processor');

  // If disabled, skip
  if (!variable_get('advagg_enabled', ADVAGG_ENABLED) || in_array(TRUE, $disabled, TRUE)) {
    if (module_exists('jquery_update')) {
      return jquery_update_preprocess_page($variables);
    }
    else {
      return;
    }
  }

  // Do not use the cache if advagg is set in the URL.
  if (isset($_GET['advagg']) && user_access('bypass advanced aggregation')) {
    $conf['advagg_use_full_cache'] = FALSE;
  }

  // Do not use the cache if the disable cookie is set.
  $cookie_name = 'AdvAggDisabled';
  $key = md5(drupal_get_private_key());
  if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
    $conf['advagg_use_full_cache'] = FALSE;
  }

  // CSS
  advagg_process_html_css($variables);

  // JS
  advagg_process_html_js($variables);

  // Send requests to server if async enabled.
  advagg_async_send_http_request();

  // Write debug info to watchdog if debugging enabled.
  if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
    $data = array(
      'css_before_vars' => $css_orig,
      'css_before_function' => $css_func,
      'css_before_styles' => $css_styles,
      'css_before_inline' => $css_func_inline,
      'css_before_conditional_styles' => $css_conditional_styles,
      'css_merged' => $css,
      'css_after' => $processed_css,
      'js_before' => $js_code_orig,
      'js_after' => $js_code,
    );
    $data['runtime'] = isset($_advagg['debug']) ? $_advagg['debug'] : FALSE;
    $data = str_replace('    ', '&nbsp;&nbsp;&nbsp;&nbsp;', nl2br(htmlentities(iconv('utf-8', 'utf-8//IGNORE', print_r($data, TRUE)), ENT_QUOTES, 'UTF-8')));
    watchdog('advagg', 'Debug info: !data', array(
      '!data' => $data,
    ), WATCHDOG_DEBUG);
  }
}

/**
 * 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 $javascript
 *   (optional) An array with all JavaScript code. Defaults to the default
 *   JavaScript array for the given scope.
 * @param $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
 *   The raw JavaScript array.
 *
 * @see drupal_add_js()
 * @see locale_js_alter()
 * @see drupal_js_defaults()
 */
function advagg_get_full_js($javascript = NULL, $skip_alter = FALSE) {
  if (!isset($javascript)) {
    $javascript = drupal_add_js();
  }
  if (empty($javascript)) {
    return FALSE;
  }

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

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

  // Return if nothing given to us.
  if (empty($javascript) || !is_array($javascript)) {
    return FALSE;
  }

  // Filter out elements of the given scope.
  $scopes = array();
  foreach ($javascript as $key => $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;
    }
  }
  return $scopes;
}

/**
 * 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.
 *
 * @param $scope
 *   (optional) The scope for which the JavaScript rules should be returned.
 *   Defaults to 'header'.
 * @param $javascript
 *   (optional) An array with all JavaScript code. Defaults to the default
 *   JavaScript array for the given scope.
 *
 * @return
 *   All JavaScript code segments and includes for the scope as HTML tags.
 */
function advagg_get_js($scope, $javascript) {

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

  // The index counter is used to keep aggregated and non-aggregated files in
  // order by weight.
  $index = 1;
  $processed = array();
  $files = array();
  $preprocess_js = variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update');

  // 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=');

  // Sort the JavaScript so that it appears in the correct order.
  uasort($items, 'drupal_sort_css_js');

  // Provide the page with information about the individual JavaScript files
  // used, information not otherwise available when aggregation is enabled.
  $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
  unset($setting['ajaxPageState']['js']['settings']);
  drupal_add_js($setting, 'setting');

  // If we're outputting the header scope, then this might 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 off settings, potentially in order to override how settings get
  // output, so in this case, do not add the setting to this output.
  if ($scope == 'header' && isset($items['settings'])) {
    $items['settings']['data'][] = $setting;
  }

  // Loop through the JavaScript to construct the rendered output.
  $element = array(
    '#tag' => 'script',
    '#value' => '',
    '#attributes' => array(
      'type' => 'text/javascript',
    ),
  );
  foreach ($items as $item) {
    $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
    switch ($item['type']) {
      case 'setting':
        $js_element = $element;
        $js_element['#value_prefix'] = $embed_prefix;
        $js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
        $js_element['#value_suffix'] = $embed_suffix;
        $output .= theme('html_tag', array(
          'element' => $js_element,
        ));
        break;
      case 'inline':
        $js_element = $element;
        if ($item['defer']) {
          $js_element['#attributes']['defer'] = 'defer';
        }
        $js_element['#value_prefix'] = $embed_prefix;
        $js_element['#value'] = $item['data'];
        $js_element['#value_suffix'] = $embed_suffix;
        $processed[$index++] = theme('html_tag', array(
          'element' => $js_element,
        ));
        break;
      case 'file':
        $js_element = $element;
        if (!$item['preprocess'] || !$preprocess_js) {
          if ($item['defer']) {
            $js_element['#attributes']['defer'] = 'defer';
          }
          $query_string_separator = strpos($item['data'], '?') !== FALSE ? '&' : '?';
          $js_element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
          $processed[$index++] = theme('html_tag', array(
            'element' => $js_element,
          ));
        }
        else {

          // By increasing the index for each aggregated file, we maintain
          // the relative ordering of JS by weight. We also set the key such
          // that groups are split by items sharing the same 'group' value and
          // 'every_page' flag. While this potentially results in more aggregate
          // files, it helps make each one more reusable across a site visit,
          // leading to better front-end performance of a website as a whole.
          // See drupal_add_js() for details.
          $key = 'aggregate_' . $item['group'] . '_' . $item['every_page'] . '_' . $index;
          $processed[$key] = '';
          $files[$key][$item['data']] = $item;
        }
        break;
      case 'external':
        $js_element = $element;

        // Preprocessing for external JavaScript files is ignored.
        if ($item['defer']) {
          $js_element['#attributes']['defer'] = 'defer';
        }
        $js_element['#attributes']['src'] = $item['data'];
        $processed[$index++] = theme('html_tag', array(
          'element' => $js_element,
        ));
        break;
    }
  }

  // Aggregate any remaining JS files that haven't already been output.
  if ($preprocess_js && count($files) > 0) {
    foreach ($files as $key => $file_set) {
      $uri = drupal_build_js_cache($file_set);

      // Only include the file if was written successfully. Errors are logged
      // using watchdog.
      if ($uri) {
        $preprocess_file = file_create_url($uri);
        $js_element = $element;
        $js_element['#attributes']['src'] = $preprocess_file;
        $processed[$key] = theme('html_tag', array(
          'element' => $js_element,
        ));
      }
    }
  }

  // Keep the order of JS files consistent as some are preprocessed and others are not.
  // Make sure any inline or JS setting variables appear last after libraries have loaded.
  return implode('', $processed) . $output;
}

/**
 * Get the CSS & JS path for advagg.
 *
 * @return
 *   Example below:
 *   array(
 *     array(
 *       public://advagg_css,
 *       sites/default/files/advagg_css,
 *     ),
 *     array(
 *       public://advagg_js,
 *       sites/default/files/advagg_js,
 *     ),
 *   )
 */
function advagg_get_root_files_dir() {
  static $css_path;
  static $js_path;

  // Make sure directories are available and writable.
  if (empty($css_path) || empty($js_path)) {
    $css_path = 'public://advagg_css';
    $js_path = 'public://advagg_js';
    file_prepare_directory($css_path, FILE_CREATE_DIRECTORY);
    file_prepare_directory($js_path, FILE_CREATE_DIRECTORY);
    $css_path = parse_url(file_create_url($css_path));
    $js_path = parse_url(file_create_url($js_path));
  }
  return array(
    ltrim($css_path['path'], '/'),
    ltrim($js_path['path'], '/'),
  );
}

/**
 * Merge 2 css arrays together.
 *
 * @param $array1
 *   first array
 * @param $array2
 *   second array
 * @return
 *   combined array
 */
function advagg_merge_css($array1, $array2) {
  if (!empty($array2)) {
    foreach ($array2 as $media => $types) {
      foreach ($types as $type => $files) {
        foreach ($files as $file => $preprocess) {
          $array1[$media][$type][$file] = $preprocess;
        }
      }
    }
  }
  return $array1;
}

/**
 * Merge 2 css arrays together.
 *
 * @param $array1
 *   first array
 * @param $array2
 *   second array
 * @return
 *   combined array
 */
function advagg_merge_inline_css($array1, $array2) {
  foreach ($array2 as $media => $types) {
    foreach ($types as $type => $blobs) {
      foreach ($blobs as $prefix => $data) {
        foreach ($data as $suffix => $blob) {
          $array1[$media][$type][$prefix][$suffix] = $blob;
        }
      }
    }
  }
  return $array1;
}

/**
 * Remove .less files from the array.
 *
 * @param $css_func
 *   Drupal CSS array.
 */
function advagg_css_array_fixer(&$css_func) {
  if (!module_exists('less')) {
    return;
  }

  // Remove '.css.less' files from the stack.
  foreach ($css_func as $k => $v) {
    foreach ($v as $ke => $va) {
      foreach ($va as $file => $preprocess) {
        if (substr($file, -5) === '.less') {
          unset($css_func[$k][$ke][$file]);
        }
      }
    }
  }
}

/**
 * See if a string ends with a substring.
 *
 * @param $haystack
 *   The main string being compared.
 * @param $needle
 *   The secondary string being compared.
 * @return
 *   bool
 */
function advagg_string_ends_with($haystack, $needle) {

  // Define substr_compare if it doesn't exist (PHP 4 fix).
  if (!function_exists('substr_compare')) {

    /**
     * Binary safe comparison of two strings from an offset, up to length
     * characters.
     *
     * Compares main_str from position offset with str up to length characters.
     * @see http://php.net/substr-compare#53084
     *
     * @param $main_str
     *   The main string being compared.
     * @param $str
     *   The secondary string being compared.
     * @param $offset
     *   The start position for the comparison. If negative, it starts counting
     *   from the end of the string.
     * @param $length
     *   The length of the comparison. The default value is the largest of the
     *   length of the str compared to the length of main_str less the offset.
     * @param $case_insensitivity
     *   If TRUE, comparison is case insensitive.
     * @return
     *   Returns < 0 if main_str from position offset is less than str, > 0 if
     *   it is greater than str, and 0 if they are equal. If offset is equal to
     *   or greater than the length of main_str or length is set and is less than
     *   1, substr_compare() prints a warning and returns FALSE.
     */
    function substr_compare($main_str, $str, $offset, $length = NULL, $case_insensitivity = FALSE) {
      $offset = (int) $offset;

      // Throw a warning because the offset is invalid
      if ($offset >= strlen($main_str)) {
        trigger_error('The start position cannot exceed initial string length.', E_USER_WARNING);
        return FALSE;
      }

      // We are comparing the first n-characters of each string, so let's use the PHP function to do it
      if ($offset == 0 && is_int($length) && $case_insensitivity === TRUE) {
        return strncasecmp($main_str, $str, $length);
      }

      // Get the substring that we are comparing
      if (is_int($length)) {
        $main_substr = substr($main_str, $offset, $length);
        $str_substr = substr($str, 0, $length);
      }
      else {
        $main_substr = substr($main_str, $offset);
        $str_substr = $str;
      }

      // Return a case-insensitive comparison of the two strings
      if ($case_insensitivity === TRUE) {
        return strcasecmp($main_substr, $str_substr);
      }

      // Return a case-sensitive comparison of the two strings
      return strcmp($main_substr, $str_substr);
    }
  }
  $haystack_len = strlen($haystack);
  $needle_len = strlen($needle);
  if ($needle_len > $haystack_len) {
    return FALSE;
  }
  return substr_compare($haystack, $needle, $haystack_len - $needle_len, $needle_len, TRUE) === 0;
}

/**
 * Implements hook_advagg_disable_processor().
 */
function advagg_advagg_disable_processor() {

  // Disable advagg on the configuration page; in case something bad happened.
  if (isset($_GET['q']) && ($_GET['q'] == 'admin/config/advagg' || $_GET['q'] == 'admin/config/advagg/config' || $_GET['q'] == 'batch')) {
    return TRUE;
  }
}

/**
 * Return the server schema (http or https).
 *
 * @return string
 *   http OR https.
 */
function advagg_get_server_schema() {
  return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] == 'on' ? 'https' : 'http';
}

/**
 * Special handling for jquery update.
 *
 * @param $js
 *   List of files in the header
 */
function advagg_jquery_updater(&$js) {
  if (!module_exists('jquery_update') || !variable_get('jquery_update_replace', TRUE) || empty($js)) {
    return;
  }

  // Replace jquery.js first.
  $new_jquery = array(
    jquery_update_jquery_path() => $js['core']['misc/jquery.js'],
  );
  $js['core'] = array_merge($new_jquery, $js['core']);
  unset($js['core']['misc/jquery.js']);

  // Loop through each of the required replacements.
  $replacement_path = drupal_get_path('module', 'jquery_update') . '/replace/';
  foreach (jquery_update_get_replacements() as $type => $replacements) {
    foreach ($replacements as $find => $replace) {

      // If the file to replace is loaded on this page...
      if (isset($js[$type][$find])) {

        // Create a new entry for the replacement file, and unset the original one.
        $replace = $replacement_path . $replace;
        $js[$type][$replace] = $js[$type][$find];
        unset($js[$type][$find]);
      }
    }
  }
}

/**
 * Given a list of files; return back the aggregated filename.
 *
 * @param $files
 *   List of files in the proposed bundle.
 * @param $filetype
 *   css or js.
 * @param $counter
 *   (optional) Counter value.
 * @param $bundle_md5
 *   (optional) Bundle's machine name.
 * @return
 *   Aggregated filename.
 */
function advagg_get_filename($files, $filetype, $counter = FALSE, $bundle_md5 = '') {
  if (empty($files) || empty($filetype)) {
    return FALSE;
  }
  global $_advagg;
  $filenames = array();
  $run_alter = FALSE;
  if (empty($bundle_md5)) {

    // Create bundle md5
    $schema = advagg_get_server_schema();
    $bundle_md5 = md5($schema . implode('', $files));
    $run_alter = TRUE;

    // Record root request in db.
    // Get counter if there.
    if (empty($counter)) {
      $counter = db_query("SELECT counter FROM {advagg_bundles} WHERE bundle_md5 = :bundle_md5", array(
        ':bundle_md5' => $bundle_md5,
      ))
        ->fetchField();
    }

    // If this is a brand new bundle then insert file/bundle info into database.
    if ($counter === FALSE) {
      $counter = 0;
      advagg_insert_bundle_db($files, $filetype, $bundle_md5, TRUE);
    }

    // If bundle should be root and is not, then make it root.
    // Refresh timestamp if older then 12 hours.
    $row = db_fetch_array(db_query("SELECT root, timestamp FROM {advagg_bundles} WHERE bundle_md5 = :bundle_md5", array(
      ':bundle_md5' => $bundle_md5,
    )));
    if ($row['root'] === 0 || REQUEST_TIME - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) {

      // TODO Please review the conversion of this statement to the D7 database API syntax.

      /* db_query("UPDATE {advagg_bundles} SET root = '1', timestamp = %d WHERE bundle_md5 = '%s'", REQUEST_TIME, $bundle_md5) */
      db_update('advagg_bundles')
        ->fields(array(
        'root' => '1',
        'timestamp' => REQUEST_TIME,
      ))
        ->condition('bundle_md5', $bundle_md5)
        ->execute();
    }
  }

  // Set original array.
  $filenames[] = array(
    'filetype' => $filetype,
    'files' => $files,
    'counter' => $counter,
    'bundle_md5' => $bundle_md5,
  );

  // Invoke hook_advagg_filenames_alter() to give installed modules a chance to
  // alter filenames. One to many relationships come to mind.
  // Do not run alter if MD5 was given, we want to generate that file only in
  // this special case.
  if ($run_alter) {

    // Force counter to be looked up later.
    $filenames[0]['counter'] = FALSE;
    drupal_alter('advagg_filenames', $filenames);
  }

  // Write to DB if needed and create filenames.
  $output = array();
  $used_md5 = array();
  if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
    $_advagg['debug']['get_filename_post_alter'][] = array(
      'key' => $bundle_md5,
      'filenames' => $filenames,
    );
  }

  // Get all counters at once
  $counters = array();
  foreach ($filenames as $key => $values) {
    if (empty($values['counter'])) {
      $counters[$key] = $values['bundle_md5'];
    }
  }
  $result = advagg_db_multi_select_in('advagg_bundles', 'bundle_md5', "'%s'", $counters, array(
    'counter',
    'bundle_md5',
  ), 'GROUP BY bundle_md5');
  while ($row = db_fetch_array($result)) {
    $key = array_search($row['bundle_md5'], $counters);
    if (empty($filenames[$key]['counter']) && $filenames[$key]['counter'] !== 0) {
      $filenames[$key]['counter'] = intval($row['counter']);
    }
  }
  foreach ($filenames as $values) {

    // Get info from array.
    $filetype = $values['filetype'];
    $files = $values['files'];
    $counter = $values['counter'];
    $bundle_md5 = $values['bundle_md5'];

    // See if a JS bundle exists that already has the same files in it, just in a
    // different order.
    //     if ($filetype == 'js' && $run_alter) {
    //       advagg_find_existing_bundle($files, $bundle_md5);
    //     }
    // Do not add the same bundle twice.
    if (isset($used_md5[$bundle_md5])) {
      continue;
    }
    $used_md5[$bundle_md5] = TRUE;

    // If this is a brand new bundle then insert file/bundle info into database.
    if (empty($counter) && $counter !== 0) {
      $counter = 0;
      advagg_insert_bundle_db($files, $filetype, $bundle_md5, FALSE);
    }

    // Prefix filename to prevent blocking by firewalls which reject files
    // starting with "ad*".
    $output[] = array(
      'filename' => advagg_build_filename($filetype, $bundle_md5, $counter),
      'files' => $files,
      'bundle_md5' => $bundle_md5,
    );

    // Refresh timestamp if older then 12 hours.
    $row = db_fetch_array(db_query("SELECT timestamp FROM {advagg_bundles} WHERE bundle_md5 = :bundle_md5", array(
      ':bundle_md5' => $bundle_md5,
    )));
    if (REQUEST_TIME - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) {

      // TODO Please review the conversion of this statement to the D7 database API syntax.

      /* db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", REQUEST_TIME, $bundle_md5) */
      db_update('advagg_bundles')
        ->fields(array(
        'timestamp' => REQUEST_TIME,
      ))
        ->condition('bundle_md5', $bundle_md5)
        ->execute();
    }
  }
  return $output;
}

/**
 * Get a bundle from the cache & verify it is good.
 *
 * @param $cached_data_key
 *   cache key for the cache_advagg_bundle_reuse table.
 * @param $debug_name
 *   Name to output in the array if debugging is enabled.
 * @return
 *   data from the cache.
 */
function advagg_cached_bundle_get($cached_data_key, $debug_name) {
  global $_advagg;
  $data = cache_get($cached_data_key, 'cache_advagg_bundle_reuse');
  if (!empty($data->data)) {
    $data = $data->data;
    $bundle_contents = array();
    $good = TRUE;

    // Debugging.
    if (variable_get('advagg_debug', ADVAGG_DEBUG)) {

      // Verify cached data is good.
      foreach ($data as $filename => $extra) {
        if (is_numeric($filename)) {
          continue;
        }

        // Get md5 from aggregated filename.
        $b_md5 = explode('/', $filename);
        $b_md5 = explode('_', array_pop($b_md5));
        $b_md5 = $b_md5[1];

        // Lookup bundle and make sure it is valid.
        if (!empty($b_md5)) {
          list($b_filetype, $b_files) = advagg_get_files_in_bundle($b_md5);
          $bundle_contents[$filename] = $b_files;
          if (empty($b_files)) {
            $good = FALSE;
          }
        }
      }
      $_advagg['debug'][$debug_name][] = array(
        'key' => $cached_data_key,
        'cache' => $data,
        'bundle_contents' => $bundle_contents,
      );
    }
    if ($good) {
      return $data;
    }
  }
  return FALSE;
}

/**
 * Given a list of files, see if a bundle already exists containing all of those
 * files. If in strict mode then the file count has to be the same.
 *
 * @param $files
 *   List of files in the proposed bundle.
 * @param $bundle_md5
 *   Bundle's machine name.
 */
function advagg_find_existing_bundle(&$files, &$bundle_md5) {

  // Sort files for better cache hits.
  $temp_files = $files;
  sort($temp_files);
  $schema = advagg_get_server_schema();
  $cached_data_key = 'advagg_existing_' . md5($schema . implode('', $temp_files));

  // Try cache first; cache table is cache_advagg_bundle_reuse. string is debug name.
  $cached_data = advagg_cached_bundle_get($cached_data_key, 'advagg_find_existing_bundle');
  if (!empty($cached_data)) {
    $files = $cached_data[0]['files'];
    $bundle_md5 = $cached_data[0]['bundle_md5'];
    return;
  }

  // Build union query.
  $query = 'SELECT root.bundle_md5 FROM {advagg_bundles} AS root';
  $joins = array();
  $wheres = array();
  $args = array();
  $counter = 0;
  foreach ($files as $filename) {

    // Use alpha for table aliases; numerics do not work.
    $key = strtr($counter, '01234567890', 'abcdefghij');
    $joins[$key] = "\nINNER JOIN {advagg_bundles} AS {$key} USING(bundle_md5)\n";
    if ($counter == 0) {
      $wheres[$key] = "WHERE {$key}.filename_md5 = '%s'";
    }
    else {
      $wheres[$key] = "AND {$key}.filename_md5 = '%s'";
    }
    $args[$key] = md5($filename);
    $counter++;
  }
  $query .= implode("\n", $joins);
  $query .= implode("\n", $wheres);
  $query .= ' GROUP BY bundle_md5';

  // Find matching bundles and select first good one.
  $files_count = count($files);
  $results = db_query($query, $args);
  while ($new_md5 = $results
    ->fetchField()) {
    $count = db_query("SELECT count(*) FROM {advagg_bundles} WHERE bundle_md5 = :bundle_md5", array(
      ':bundle_md5' => $new_md5,
    ))
      ->fetchField();

    // Make sure bundle has the same number of files if using strict matching.
    if (!empty($count) && $count == $files_count) {
      $bundle_md5 = $new_md5;
      $data = array(
        array(
          'files' => $files,
          'bundle_md5' => $bundle_md5,
        ),
      );
      cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY);
      return;
    }
  }
}

/**
 * Build the filename.
 *
 * @param $filetype
 *   css or js.
 * @param $counter
 *   Counter value.
 * @param $bundle_md5
 *   Bundle's machine name.
 */
function advagg_build_filename($filetype, $bundle_md5, $counter) {
  return $filetype . '_' . $bundle_md5 . '_' . $counter . '.' . $filetype;
}

/**
 * Insert info into the advagg_files and advagg_bundles database.
 *
 * @param $files
 *   List of files in the proposed bundle.
 * @param $filetype
 *   css or js.
 * @param $bundle_md5
 *   Bundle's machine name.
 * @param $root
 *   Is this a root bundle.
 */
function advagg_insert_bundle_db($files, $filetype, $bundle_md5, $root) {
  $lock_name = 'advagg_insert_bundle_db' . $bundle_md5;
  if (!lock_acquire($lock_name)) {

    // If using async, wait before returning to give the other request time
    // to complete.
    if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) < 2) {
      lock_wait($lock_name);
    }
    return;
  }

  // Double check that the bundle doesn't exist now that we are in a lock.
  $bundle_exists = db_query("SELECT 1 FROM {advagg_bundles} WHERE bundle_md5 = :bundle_md5", array(
    ':bundle_md5' => $bundle_md5,
  ))
    ->fetchField();
  if ($bundle_exists) {
    lock_release($lock_name);
    return;
  }
  foreach ($files as $order => $filename) {
    $filename_md5 = md5($filename);

    // Insert file into the advagg_files table if it doesn't exist.
    $checksum = db_query("SELECT checksum FROM {advagg_files} WHERE filename_md5 = :filename_md5", array(
      ':filename_md5' => $filename_md5,
    ))
      ->fetchField();
    if (empty($checksum)) {
      $checksum = advagg_checksum($filename);

      // TODO Please review the conversion of this statement to the D7 database API syntax.

      /* db_query("INSERT INTO {advagg_files} (filename, filename_md5, checksum, filetype, filesize) VALUES ('%s', '%s', '%s', '%s', %d)", $filename, $filename_md5, $checksum, $filetype, @filesize($filename)) */
      $id = db_insert('advagg_files')
        ->fields(array(
        'filename' => $filename,
        'filename_md5' => $filename_md5,
        'checksum' => $checksum,
        'filetype' => $filetype,
        'filesize' => filesize($filename),
      ))
        ->execute();
    }

    // Create the entries in the advagg_bundles table.
    // TODO Please review the conversion of this statement to the D7 database API syntax.

    /* db_query("INSERT INTO {advagg_bundles} (bundle_md5, filename_md5, counter, porder, root, timestamp) VALUES ('%s', '%s', '%d', '%d', '%d', '%d')", $bundle_md5, $filename_md5, 0, $order, $root, REQUEST_TIME) */
    $id = db_insert('advagg_bundles')
      ->fields(array(
      'bundle_md5' => $bundle_md5,
      'filename_md5' => $filename_md5,
      'counter' => 0,
      'porder' => $order,
      'root' => $root,
      'timestamp' => REQUEST_TIME,
    ))
      ->execute();
  }
  lock_release($lock_name);
}

/**
 * Save a string to the specified destination. Verify that file size is not zero.
 *
 * @param $data
 *   A string containing the contents of the file.
 * @param $dest
 *   A string containing the destination location.
 * @return
 *   Boolean indicating if the file save was successful.
 */
function advagg_file_saver($data, $dest, $force, $type) {

  // Make sure the tmp folder is ready for use
  $tmp = file_directory_temp();
  file_prepare_directory($tmp, FILE_CREATE_DIRECTORY);

  // Create the JS file.
  $file_save_data = 'file_save_data';
  $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR);
  if (!empty($custom_path)) {
    $file_save_data = 'advagg_file_save_data';
  }
  if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) {
    return FALSE;
  }

  // Make sure filesize is not zero.
  advagg_clearstatcache(TRUE, $dest);
  if (@filesize($dest) == 0 && !empty($data)) {
    if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) {
      return FALSE;
    }
    advagg_clearstatcache(TRUE, $dest);
    if (@filesize($dest) == 0 && !empty($data)) {

      // Filename is bad, create a new one next time.
      file_unmanaged_delete($dest);
      return FALSE;
    }
  }
  if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION) && extension_loaded('zlib')) {
    $gzip_dest = $dest . '.gz';
    advagg_clearstatcache(TRUE, $gzip_dest);
    if (!file_exists($gzip_dest) || $force) {
      $gzip_data = gzencode($data, 9, FORCE_GZIP);
      if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) {
        return FALSE;
      }

      // Make sure filesize is not zero.
      advagg_clearstatcache(TRUE, $gzip_dest);
      if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) {
        if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) {
          return FALSE;
        }
        advagg_clearstatcache(TRUE, $gzip_dest);
        if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) {

          // Filename is bad, create a new one next time.
          file_unmanaged_delete($gzip_dest);
          return FALSE;
        }
      }
    }
  }

  // Make sure .htaccess file exists.
  advagg_htaccess_check_generate($dest);
  cache_set($dest, REQUEST_TIME, 'cache_advagg', CACHE_PERMANENT);
  return TRUE;
}

/**
 * ***MODIFIED CORE FUNCTIONS BELOW***
 *
 * @see file_save_data()
 * @see file_move()
 * @see file_copy()
 */

/**
 * Save a string to the specified destination.
 *
 * @see file_save_data()
 *
 * @param $data A string containing the contents of the file.
 * @param $dest A string containing the destination location.
 * @param $replace Replace behavior when the destination file already exists.
 *   - FILE_EXISTS_REPLACE - Replace the existing file
 *   - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique
 *   - FILE_EXISTS_ERROR - Do nothing and return FALSE.
 *
 * @return A string containing the resulting filename or 0 on error
 */
function advagg_file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) {
  $temp = file_directory_temp();

  // On Windows, tempnam() requires an absolute path, so we use realpath().
  $file = tempnam(realpath($temp), 'file');
  if (!($fp = fopen($file, 'wb'))) {
    drupal_set_message(t('The file could not be created.'), 'error');
    return 0;
  }
  fwrite($fp, $data);
  fclose($fp);
  if (!advagg_file_move($file, $dest, $replace)) {
    return 0;
  }
  return $file;
}

/**
 * Moves a file to a new location.
 *
 * @see file_move()
 *
 * - Checks if $source and $dest are valid and readable/writable.
 * - Performs a file move if $source is not equal to $dest.
 * - If file already exists in $dest either the call will error out, replace the
 *   file or rename the file based on the $replace parameter.
 *
 * @param $source
 *   Either a string specifying the file location of the original file or an
 *   object containing a 'filepath' property. This parameter is passed by
 *   reference and will contain the resulting destination filename in case of
 *   success.
 * @param $dest
 *   A string containing the directory $source should be copied to. If this
 *   value is omitted, Drupal's 'files' directory will be used.
 * @param $replace
 *   Replace behavior when the destination file already exists.
 *   - FILE_EXISTS_REPLACE: Replace the existing file.
 *   - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
 *     unique.
 *   - FILE_EXISTS_ERROR: Do nothing and return FALSE.
 * @return
 *   TRUE for success, FALSE for failure.
 */
function advagg_file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
  $path_original = is_object($source) ? $source->filepath : $source;
  if (advagg_file_copy($source, $dest, $replace)) {
    $path_current = is_object($source) ? $source->filepath : $source;
    if ($path_original == $path_current || file_unmanaged_delete($path_original)) {
      return 1;
    }
    drupal_set_message(t('The removal of the original file %file has failed.', array(
      '%file' => $path_original,
    )), 'error');
  }
  return 0;
}

/**
 * Copies a file to a new location.
 *
 * @see file_copy()
 *
 * This is a powerful function that in many ways performs like an advanced
 * version of copy().
 * - Checks if $source and $dest are valid and readable/writable.
 * - Performs a file copy if $source is not equal to $dest.
 * - If file already exists in $dest either the call will error out, replace the
 *   file or rename the file based on the $replace parameter.
 *
 * @param $source
 *   Either a string specifying the file location of the original file or an
 *   object containing a 'filepath' property. This parameter is passed by
 *   reference and will contain the resulting destination filename in case of
 *   success.
 * @param $dest
 *   A string containing the directory $source should be copied to. If this
 *   value is omitted, Drupal's 'files' directory will be used.
 * @param $replace
 *   Replace behavior when the destination file already exists.
 *   - FILE_EXISTS_REPLACE: Replace the existing file.
 *   - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
 *     unique.
 *   - FILE_EXISTS_ERROR: Do nothing and return FALSE.
 * @return
 *   TRUE for success, FALSE for failure.
 */
function advagg_file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
  $directory = dirname($dest);

  // Process a file upload object.
  if (is_object($source)) {
    $file = $source;
    $source = $file->filepath;
    if (!$basename) {
      $basename = $file->filename;
    }
  }
  $source = realpath($source);
  advagg_clearstatcache(TRUE, $source);
  if (!file_exists($source)) {
    drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array(
      '%file' => $source,
    )), 'error');
    return 0;
  }

  // If the destination file is not specified then use the filename of the source file.
  $basename = basename($dest);
  $basename = $basename ? $basename : basename($source);
  $dest = $directory . '/' . $basename;

  // Make sure source and destination filenames are not the same, makes no sense
  // to copy it if they are. In fact copying the file will most likely result in
  // a 0 byte file. Which is bad. Real bad.
  if ($source != realpath($dest)) {
    if (!($dest = file_destination($dest, $replace))) {
      drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array(
        '%file' => $source,
      )), 'error');
      return FALSE;
    }
    if (!@copy($source, $dest)) {
      drupal_set_message(t('The selected file %file could not be copied. ' . $dest, array(
        '%file' => $source,
      )), 'error');
      return 0;
    }

    // Give everyone read access so that FTP'd users or
    // non-webserver users can see/read these files,
    // and give group write permissions so group members
    // can alter files uploaded by the webserver.
    @chmod($dest, 0664);
  }
  if (isset($file) && is_object($file)) {
    $file->filename = $basename;
    $file->filepath = $dest;
    $source = $file;
  }
  else {
    $source = $dest;
  }
  return 1;

  // Everything went ok.
}

/**
 * Generate a checksum for a given filename.
 *
 * @param $filename
 *   filename
 * @return
 *   Checksum value.
 */
function advagg_checksum($filename) {
  advagg_clearstatcache(TRUE, $filename);
  if (file_exists($filename)) {
    $mode = variable_get('advagg_checksum_mode', ADVAGG_CHECKSUM_MODE);
    if ($mode == 'filemtime' && function_exists('filemtime')) {
      $checksum = @filemtime($filename);
      if ($checksum === FALSE) {
        touch($filename);
        advagg_clearstatcache(TRUE, $filename);
        $checksum = @filemtime($filename);

        // Use md5 as a last option.
        if ($checksum === FALSE) {
          $checksum = md5(file_get_contents($filename));
        }
      }
    }
    elseif ($mode = 'md5' && function_exists('md5')) {
      $checksum = md5(file_get_contents($filename));
    }
  }
  else {
    $checksum = '-1';
  }
  return $checksum;
}

/**
 * See if this bundle has been built.
 *
 * @param $filepath
 *   filename
 * @return
 *   Boolean indicating if the bundle already exists.
 */
function advagg_bundle_built($filepath) {

  // Don't use the cache if not selected.
  if (!variable_get('advagg_bundle_built_mode', ADVAGG_BUNDLE_BUILT_MODE)) {
    advagg_clearstatcache(TRUE, $filepath);
    return file_exists($filepath);
  }
  $data = advagg_get_bundle_from_filename(basename($filepath));
  if (is_array($data)) {
    list($type, $md5, $counter) = $data;
  }
  else {
    return FALSE;
  }
  $data = cache_get($filepath, 'cache_advagg');
  if (isset($data->data)) {

    // Refresh timestamp if older then 12 hours.
    if (REQUEST_TIME - $data->data > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) {
      cache_set($filepath, REQUEST_TIME, 'cache_advagg', CACHE_PERMANENT);

      // TODO Please review the conversion of this statement to the D7 database API syntax.

      /* db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", REQUEST_TIME, $md5) */
      db_update('advagg_bundles')
        ->fields(array(
        'timestamp' => REQUEST_TIME,
      ))
        ->condition('bundle_md5', $md5)
        ->execute();
    }
    return TRUE;
  }

  // If not in cache check disk.
  advagg_clearstatcache(TRUE, $filepath);
  if (file_exists($filepath)) {
    if (@filesize($filepath) == 0) {
      return FALSE;
    }
  }
  else {
    return FALSE;
  }

  // File existed on disk; place in cache.
  cache_set($filepath, REQUEST_TIME, 'cache_advagg', CACHE_PERMANENT);

  // TODO Please review the conversion of this statement to the D7 database API syntax.

  /* db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", REQUEST_TIME, $md5) */
  db_update('advagg_bundles')
    ->fields(array(
    'timestamp' => REQUEST_TIME,
  ))
    ->condition('bundle_md5', $md5)
    ->execute();
  return TRUE;
}

/**
 * @todo Please document this function.
 * @see http://drupal.org/node/1354
 */
function advagg_get_bundle_from_filename($filename) {

  // Verify requested filename has the correct pattern.
  if (!preg_match('/^(j|cs)s_[0-9a-f]{32}_\\d+\\.(j|cs)s$/', $filename)) {
    return t('Wrong Pattern.');
  }

  // Get type
  $type = substr($filename, 0, strpos($filename, '_'));

  // Get extension
  $ext = substr($filename, strpos($filename, '.', 37) + 1);

  // Make sure extension is the same as the type.
  if ($ext != $type) {
    return t('Type does not match extension.');
  }

  // Extract info from wanted filename.
  if ($type == 'css') {
    $md5 = substr($filename, 4, 32);
    $counter = substr($filename, 37, strpos($filename, '.', 38) - 37);
  }
  elseif ($type == 'js') {
    $md5 = substr($filename, 3, 32);
    $counter = substr($filename, 36, strpos($filename, '.', 37) - 36);
  }
  else {
    return t('Wrong file type.');
  }
  return array(
    $type,
    $md5,
    $counter,
  );
}

/**
 * Implements hook_flush_caches().
 */
function advagg_flush_caches() {

  // Try to allocate enough time to flush the cache
  if (function_exists('set_time_limit')) {
    @set_time_limit(240);
  }
  global $_advagg;

  // Only one advagg cache flusher can run at a time.
  if (!lock_acquire('advagg_flush_caches')) {
    return;
  }

  // Only run code below if the advagg db tables exist.
  if (!db_table_exists('advagg_files')) {
    return array(
      'cache_advagg_bundle_reuse',
    );
  }

  // Find files that have changed.
  $needs_refreshing = array();
  $results = db_query("SELECT * FROM {advagg_files}");
  while ($row = $results
    ->fetchAssoc()) {
    $checksum = advagg_checksum($row['filename']);

    // Let other modules see if the bundles needs to be rebuilt.
    // hook_advagg_files_table
    // Return TRUE in order to increment the counter.
    $hook_results = module_invoke_all('advagg_files_table', $row, $checksum);

    // Check each return value; see if an update is needed.
    $update = FALSE;
    if (!empty($hook_results)) {
      foreach ($hook_results as $update) {
        if ($update === TRUE) {
          break;
        }
      }
    }

    // Increment the counter if needed and mark file for bundle refreshment.
    if ($checksum != $row['checksum'] || $update == TRUE) {
      $needs_refreshing[$row['filename_md5']] = $row['filename'];

      // Update checksum; increment counter.
      // TODO Please review the conversion of this statement to the D7 database API syntax.

      /* db_query("UPDATE {advagg_files} SET checksum = '%s', counter = counter + 1 WHERE filename_md5 = '%s'", $checksum, $row['filename_md5']) */
      db_update('advagg_files')
        ->fields(array(
        'checksum' => $checksum,
        'counter' => counter + 1,
      ))
        ->condition('filename_md5', $row['filename_md5'])
        ->execute();
    }
  }

  // Get the bundles.
  $bundles = array();
  foreach ($needs_refreshing as $filename_md5 => $filename) {
    $results = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = :filename_md5", array(
      ':filename_md5' => $filename_md5,
    ));
    while ($row = db_fetch_array($results)) {
      $bundles[$row['bundle_md5']] = $row['bundle_md5'];
    }
  }
  foreach ($bundles as $bundle_md5) {

    // Increment Counter
    // TODO Please review the conversion of this statement to the D7 database API syntax.

    /* db_query("UPDATE {advagg_bundles} SET counter = counter + 1, timestamp = %d WHERE bundle_md5 = '%s'", REQUEST_TIME, $bundle_md5) */
    db_update('advagg_bundles')
      ->fields(array(
      'counter' => counter + 1,
      'timestamp' => REQUEST_TIME,
    ))
      ->condition('bundle_md5', $bundle_md5)
      ->execute();
    if (variable_get('advagg_rebuild_on_flush', ADVAGG_REBUILD_ON_FLUSH)) {

      // Rebuild bundles on shutdown in the background. This is needed so that
      // the cache_advagg_bundle_reuse table has been cleared.
      register_shutdown_function('advagg_rebuild_bundle', $bundle_md5, '', TRUE);
    }
  }
  $_advagg['bundles'] = $bundles;
  $_advagg['files'] = $needs_refreshing;

  // Garbage collection
  list($css_path, $js_path) = advagg_get_root_files_dir();
  file_scan_directory($css_path[1], '/.*/', array(
    'callback' => 'advagg_delete_file_if_stale',
  ));
  file_scan_directory($js_path[1], '/.*/', array(
    'callback' => 'advagg_delete_file_if_stale',
  ));
  lock_release('advagg_flush_caches');
  return array(
    'cache_advagg_bundle_reuse',
  );
}

/**
 * Rebuild a bundle.
 *
 * @param $bundle_md5
 *   Bundle's machine name.
 * @param $counter
 *   Counter value.
 * @param $force
 *   Rebuild even if file already exists.
 */
function advagg_rebuild_bundle($bundle_md5, $counter = '', $force = FALSE) {
  global $conf, $_advagg;
  list($filetype, $files) = advagg_get_files_in_bundle($bundle_md5);
  $conf['advagg_async_generation'] = FALSE;
  $good = advagg_css_js_file_builder($filetype, $files, '', $counter, $force, $bundle_md5);
  if (!$good) {
    watchdog('advagg', 'This bundle could not be generated correctly. Bundle MD5: %md5', array(
      '%md5' => $bundle_md5,
    ));
  }
  else {
    $_advagg['rebuilt'][] = $bundle_md5;
  }
  return $good;
}

/**
 * Get list of files and the filetype given a bundle md5.
 *
 * @param $bundle_md5
 *   Bundle's machine name.
 * @return
 *   array ($filetype, $files)
 */
function advagg_get_files_in_bundle($bundle_md5) {
  $files = array();
  $filetype = NULL;
  $results = db_query("SELECT filename, filetype FROM {advagg_files} AS af INNER JOIN {advagg_bundles} AS ab USING ( filename_md5 ) WHERE bundle_md5 = :bundle_md5 ORDER BY porder ASC", array(
    ':bundle_md5' => $bundle_md5,
  ));
  while ($row = db_fetch_array($results)) {
    $files[] = $row['filename'];
    $filetype = $row['filetype'];
  }
  return array(
    $filetype,
    $files,
  );
}

/**
 * Callback to delete files modified more than a set time ago.
 *
 * @param $filename
 *   name of a file to check how old it is.
 */
function advagg_delete_file_if_stale($filename) {

  // Do not process .gz files
  if (strpos($filename, '.gz') !== FALSE) {
    return;
  }
  $now = REQUEST_TIME;
  $file_last_mod = variable_get('advagg_stale_file_threshold', ADVAGG_STALE_FILE_THRESHOLD);
  $file_last_used = variable_get('advagg_stale_file_last_used_threshold', ADVAGG_STALE_FILE_LAST_USED_THRESHOLD);

  // Default mtime stale file threshold is 6 days.
  advagg_clearstatcache(TRUE, $filename);
  if ($now - filemtime($filename) <= $file_last_mod) {
    return;
  }

  // Check to see if this file is still in use.
  $data = cache_get($filename, 'cache_advagg');
  if (!empty($data->data)) {
    advagg_clearstatcache(TRUE, $filename);
    $file_last_a = @fileatime($filename);
    $file_last_agz = @fileatime($filename . '.gz');
    $file_last_a = max($file_last_a, $file_last_agz);
    if ($now - $data->data > $file_last_used && $now - $file_last_a > $file_last_used) {

      // Delete file if it hasn't been used in the last 3 days.
      file_unmanaged_delete($filename);
      file_unmanaged_delete($filename . '.gz');
    }
    else {

      // Touch file so we don't check again for another 6 days.
      touch($filename);
    }
  }
  else {

    // Delete file if it is not in the cache.
    file_unmanaged_delete($filename);
    file_unmanaged_delete($filename . '.gz');
  }
}

/**
 * Get data about a file.
 *
 * @param $filename_md5
 *   md5 of filename.
 * @return
 *   data array from database.
 */
function advagg_get_file_data($filename_md5) {
  $data = cache_get($filename_md5, 'cache_advagg_files_data');
  if (empty($data->data)) {
    return FALSE;
  }
  return $data->data;
}

/**
 * Set data about a file.
 *
 * @param $filename_md5
 *   md5 of filename.
 * @param $data
 *   data to store.
 */
function advagg_set_file_data($filename_md5, $data) {
  cache_set($filename_md5, $data, 'cache_advagg_files_data', CACHE_PERMANENT);
}

/**
 * Given path output uri to that file
 *
 * @param $filename_md5
 *   md5 of filename.
 * @param $data
 *   data to store.
 */
function advagg_build_uri($path) {
  static $hook_file_url_alter = array();

  // If the current path is an absolute path, return immediately.
  $fragments = parse_url($path);
  if (isset($fragments['host'])) {
    return $path;
  }
  $original_path = $path;

  // CDN Support.
  if (module_exists('cdn')) {
    $status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
    if ($status == CDN_ENABLED || $status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING)) {

      // Alter URL when the file_create_url() patch is not there.
      if (variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE, FALSE)) {
        cdn_file_url_alter($path);
      }
      else {
        $path = file_create_url($path);
      }

      // Return here if the path was changed above.
      if (strcmp($original_path, $path) != 0) {
        return $path;
      }
    }
  }

  // Other modules besides CDN might want to use hook_file_url_alter.
  if (empty($hook_file_url_alter)) {
    $hook_file_url_alter = module_implements('file_url_alter');
  }
  if (!empty($hook_file_url_alter)) {
    $path = file_create_url($path);

    // Return here if the path was changed above.
    if (strcmp($original_path, $path) != 0) {
      return $path;
    }
  }

  // If nothing was altered then use the drupal default.
  return base_path() . $path;
}

/**
 * ***MODIFIED CORE FUNCTIONS BELOW***
 *
 * @see drupal_get_css()
 * @see drupal_build_css_cache()
 * @see drupal_get_js()
 * @see drupal_build_js_cache()
 */

/**
 * Returns an array of values needed for aggregation
 *
 * @param $noagg
 *   (optional) Bool indicating that aggregation should be disabled if TRUE.
 * @param $type
 *   (optional) String. js or css.
 * @return
 *   array of values to be imported via list() function.
 */
function advagg_process_css_js_prep($noagg = FALSE, $type = NULL) {
  global $conf;
  $preprocess = !defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update';

  // URL bypass.
  if ($noagg || isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation')) {
    $preprocess = FALSE;
    $conf['advagg_use_full_cache'] = FALSE;
  }

  // Cookie bypass.
  $cookie_name = 'AdvAggDisabled';
  $key = md5(drupal_get_private_key());
  if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
    $preprocess = FALSE;
    $conf['advagg_use_full_cache'] = FALSE;
  }
  $scheme = file_default_scheme();
  if ($scheme !== 'public') {
    $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR);
    if (!empty($custom_path)) {
      $scheme = 'public';
    }
  }
  if ($preprocess) {
    if ($type == 'js' && !variable_get('advagg_preprocess_js', ADVAGG_PREPROCESS_JS)) {
      $preprocess = FALSE;
    }
    if ($type == 'css' && !variable_get('advagg_preprocess_css', ADVAGG_PREPROCESS_CSS)) {
      $preprocess = FALSE;
    }
  }

  // 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 = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1);
  return array(
    $preprocess,
    $scheme,
    $query_string,
  );
}

/**
 * Returns a themed presentation of all JavaScript code for the current page.
 *
 * @see drupal_get_js()
 *
 * 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.
 *
 * @param $js_code
 *   An array with all JavaScript code. Key it the region
 * @param $noagg
 *   (optional) Bool indicating that aggregation should be disabled if TRUE.
 * @return
 *   All JavaScript code segments and includes for the scope as HTML tags.
 */
function advagg_process_js($master_set, $noagg = FALSE) {
  global $conf;
  if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) {
    locale_update_js_files();
  }

  // Get useful info.
  list($preprocess_js, $scheme, $query_string) = advagg_process_css_js_prep($noagg, 'js');
  $advagg_json_encode_function = variable_get('advagg_json_encode_function', 'advagg_drupal_to_js');
  $output = array();
  foreach ($master_set as $scope => $javascript) {
    if ($scope != 'header' && $scope != 'footer' && empty($javascript)) {
      continue;
    }

    // Invoke hook_advagg_js_pre_alter() to give installed modules a chance to
    // modify the data in the $javascript array if necessary.
    drupal_alter('advagg_js_pre', $javascript, $preprocess_js, $scheme, $scope);
    $master_set[$scope] = $javascript;
  }

  // Invoke hook_advagg_js_header_footer_alter() to give installed modules a chance to
  // modify the data in the header and footer JS if necessary.
  drupal_alter('advagg_js_header_footer', $master_set, $preprocess_js, $scheme);
  foreach ($master_set as $scope => $javascript) {
    if (empty($javascript)) {
      continue;
    }

    // Set variables.
    $setting_no_preprocess = array();
    $inline_no_preprocess = array();
    $external_no_preprocess = array();
    $output_no_preprocess = array(
      'core' => array(),
      'module' => array(),
      'theme' => array(),
    );
    $output_preprocess = array();
    $preprocess_list = array();
    $js_settings_array = array();
    $inline_included = array();
    $files_included = array();
    $files_aggregates_included = array();

    // Process input.
    foreach ($javascript as $type => $data) {
      if (empty($data)) {
        continue;
      }

      // Add the prefix and suffix to the info array if not there.
      if ($type != 'setting') {
        foreach ($data as &$info) {
          $info['prefix'] = isset($info['prefix']) ? $info['prefix'] : '';
          $info['suffix'] = isset($info['suffix']) ? $info['suffix'] : '';
        }

        // $info needs to be unset, otherwise foreach loops below will break.
        unset($info);
      }
      switch ($type) {
        case 'setting':
          $data = call_user_func_array('array_merge_recursive', $data);
          $js_settings_array[] = $data;
          $js_settings = $advagg_json_encode_function($data);
          $js_settings = preg_replace(array(
            '/"DRUPAL_JS_RAW\\:/',
            '/\\:DRUPAL_JS_RAW"/',
          ), array(
            '',
            '',
          ), $js_settings);
          $setting_no_preprocess[] = 'jQuery.extend(Drupal.settings, ' . $js_settings . ");";
          break;
        case 'inline':
          foreach ($data as $info) {

            // Invoke hook_advagg_js_inline_alter() to give installed modules a
            // chance to modify the contents of $info['code'] if necessary.
            drupal_alter('advagg_js_inline', $info['code']);
            $inline_no_preprocess[] = array(
              $info['code'],
              $info['defer'],
              $info['prefix'],
              $info['suffix'],
            );
            $inline_included[] = $info['code'];
          }
          break;
        case 'external':
          foreach ($data as $path => $info) {
            $external_no_preprocess[] = array(
              $path,
              $info['defer'],
              $info['prefix'],
              $info['suffix'],
            );
            $files_included[$path] = TRUE;
          }
          break;
        default:

          // If JS preprocessing is off, we still need to output the scripts.
          // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
          foreach ($data as $path => $info) {
            if (!$info['preprocess'] || $scheme !== 'public' || !$preprocess_js) {
              $output_no_preprocess[$type][] = array(
                advagg_build_uri($path) . ($info['cache'] ? $query_string : '?' . REQUEST_TIME),
                $info['defer'],
                $info['prefix'],
                $info['suffix'],
              );
              $files_included[$path] = $info['preprocess'];
            }
            else {
              $preprocess_list[$path] = $info;
            }
          }
          break;
      }
    }

    // Aggregate any remaining JS files that haven't already been output.
    if ($scheme === 'public' && $preprocess_js && count($preprocess_list) > 0) {
      $files_aggregates_included = $files_included;
      $files = array();
      foreach ($preprocess_list as $path => $info) {
        if ($info['preprocess']) {
          $files[] = $path;
          $files_included[$path] = TRUE;
        }
      }
      if (!empty($files)) {
        $preprocess_files = advagg_css_js_file_builder('js', $files, $query_string);
        if (!empty($preprocess_files)) {
          $good = TRUE;
          foreach ($preprocess_files as $preprocess_file => $extra) {

            // Empty aggregate, skip
            if (empty($preprocess_file)) {
              continue;
            }
            if ($extra !== FALSE && is_array($extra)) {
              $prefix = $extra['prefix'];
              $suffix = $extra['suffix'];
              $output_preprocess[] = array(
                advagg_build_uri($preprocess_file),
                $prefix,
                $suffix,
              );
              $files_aggregates_included[$preprocess_file] = $extra;
            }
            else {
              $good = FALSE;
              break;
            }
          }
        }
        if (empty($good)) {

          // Redo with aggregation turned off and return the new value.
          watchdog('advagg', 'JS aggregation failed. %filename could not be saved correctly.', array(
            '%filename' => $preprocess_file,
          ), WATCHDOG_ERROR);
          $data = advagg_process_js($master_set, TRUE);
          return $data;
        }
      }
    }

    // Default function called: advagg_js_builder
    $function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION);
    $output[$scope] = $function($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included);
  }
  return $output;
}

/**
 * Build and theme JS output for header.
 *
 * @param $external_no_preprocess
 *   array(array($src, $defer, $prefix, $suffix))
 * @param $output_preprocess
 *   array(array($src, $prefix, $suffix))
 * @param $output_no_preprocess
 *   array(array(array($src, $defer, $prefix, $suffix)))
 * @param $setting_no_preprocess
 *   array(array($code))
 * @param $inline_no_preprocess
 *   array(array($code, $defer, $prefix, $suffix))
 * @param $scope
 *   header or footer.
 * @param $js_settings_array
 *   array of settings used.
 * @param $inline_included
 *   array of inline scripts used.
 * @param $files_included
 *   array of files used.
 * @param $files_aggregates_included
 *   array of files and aggregates used.
 * @return
 *   String of themed JavaScript.
 */
function advagg_js_builder($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) {
  $output = '';

  // 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";

  // Keep the order of JS files consistent as some are preprocessed and others are not.
  // Make sure any inline or JS setting variables appear last after libraries have loaded.
  if (!empty($external_no_preprocess)) {
    foreach ($external_no_preprocess as $values) {
      list($src, $defer, $prefix, $suffix) = $values;
      $output .= $prefix . '<script type="text/javascript"' . ($defer ? ' defer="defer"' : '') . ' src="' . $src . '"></script>' . $suffix . "\n";
    }
  }
  if (!empty($output_preprocess)) {
    foreach ($output_preprocess as $values) {
      list($src, $prefix, $suffix) = $values;
      $output .= $prefix . '<script type="text/javascript" src="' . $src . '"></script>' . $suffix . "\n";
    }
  }
  foreach ($output_no_preprocess as $type => $list) {
    if (!empty($list)) {
      foreach ($list as $values) {
        list($src, $defer, $prefix, $suffix) = $values;
        $output .= $prefix . '<script type="text/javascript"' . ($defer ? ' defer="defer"' : '') . ' src="' . $src . '"></script>' . $suffix . "\n";
      }
    }
  }
  if (!empty($setting_no_preprocess)) {
    foreach ($setting_no_preprocess as $code) {
      $output .= '<script type="text/javascript">' . $embed_prefix . $code . $embed_suffix . "</script>\n";
    }
  }
  if (!empty($inline_no_preprocess)) {
    foreach ($inline_no_preprocess as $values) {
      list($code, $defer, $prefix, $suffix) = $values;
      $output .= $prefix . '<script type="text/javascript"' . ($defer ? ' defer="defer"' : '') . '>' . $embed_prefix . $code . $embed_suffix . '</script>' . $suffix . "\n";
    }
  }
  return $output;
}

/**
 * Always return TRUE, used for array_map in advagg_css_js_file_builder().
 */
function advagg_return_true() {
  return TRUE;
}

/**
 * Disable the page cache if the aggregate is not in the bundle.
 */
function advagg_disable_page_cache() {
  global $conf;
  $conf['advagg_use_full_cache'] = FALSE;
  if (variable_get('advagg_page_cache_mode', ADVAGG_PAGE_CACHE_MODE)) {
    $conf['cache'] = CACHE_DISABLED;

    // Invoke hook_advagg_disable_page_cache(). Allows 3rd party page cache
    // plugins like boost or varnish to not cache this page.
    module_invoke_all('advagg_disable_page_cache');
  }
}

/**
 * Aggregate CSS/JS files, putting them in the files directory.
 *
 * @see drupal_build_js_cache()
 * @see drupal_build_css_cache()
 *
 * @param $type
 *   js or css
 * @param $files
 *   An array of JS files to aggregate and compress into one file.
 * @param $query_string
 *   (optional) Query string to add on to the file if bundle isn't ready.
 * @param $counter
 *   (optional) Counter value.
 * @param $force
 *   (optional) Rebuild even if file already exists.
 * @param $md5
 *   (optional) Bundle's machine name.
 * @return
 *   array with the filepath as the key and prefix and suffix in another array.
 */
function advagg_css_js_file_builder($type, $files, $query_string = '', $counter = FALSE, $force = FALSE, $md5 = '') {
  global $_advagg, $base_path;
  $data = '';

  // Try cache first. When ever the counter changes this cache gets reset.
  $schema = advagg_get_server_schema();
  $cached_data_key = 'advagg_file_builder_' . md5($schema . implode('', array_filter(array_unique($files))));
  if (!$force) {

    // Try cache first; cache table is cache_advagg_bundle_reuse.
    $cached_data = advagg_cached_bundle_get($cached_data_key, 'file_builder_cache_object');
    if (!empty($cached_data)) {
      foreach ($cached_data as $filepath => $values) {

        // Ping cache.
        advagg_bundle_built($filepath);
      }
      return $cached_data;
    }
  }
  list($css_path, $js_path) = advagg_get_root_files_dir();
  if ($type == 'js') {
    $file_type_path = $js_path[0];
  }
  if ($type == 'css') {
    $file_type_path = $css_path[0];
  }

  // Send $files, get filename back
  $filenames = advagg_get_filename($files, $type, $counter, $md5);
  if (empty($filenames)) {
    return FALSE;
  }

  // Debugging.
  if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
    $_advagg['debug']['file_builder_get_filenames'][] = array(
      'key' => $cached_data_key,
      'filenames' => $filenames,
    );
  }
  $output = array();
  $locks = array();
  $cacheable = TRUE;
  $files_used = array();
  foreach ($filenames as $info) {
    $filename = $info['filename'];
    $files = $info['files'];
    $bundle_md5 = $info['bundle_md5'];
    $prefix = '';
    $suffix = '';
    $filepath = $file_type_path . '/' . $filename;

    // Invoke hook_advagg_js_extra_alter() or hook_advagg_css_extra_alter to
    // give installed modules a chance to modify the prefix or suffix for a
    // given filename.
    $values = array(
      $filename,
      $bundle_md5,
      $prefix,
      $suffix,
    );
    drupal_alter('advagg_' . $type . '_extra', $values);
    list($filename, $bundle_md5, $prefix, $suffix) = $values;

    // Check that the file exists & filesize is not zero
    $built = advagg_bundle_built($filepath);
    if (!$built || $force) {

      // Generate on request?
      if (variable_get('advagg_async_generation', ADVAGG_ASYNC_GENERATION) && !$force && empty($_GET['generator'])) {

        // Build request.
        $redirect_counter = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0;
        $url = _advagg_build_url($filepath . '?generator=1&redirect_counter=' . $redirect_counter);
        $headers = array(
          'Host' => $_SERVER['HTTP_HOST'],
          'Connection' => 'close',
        );

        // Request file.
        if (function_exists('stream_socket_client') && function_exists('stream_select')) {
          advagg_async_connect_http_request($url, array(
            'headers' => $headers,
          ));
        }
        else {

          // Set timeout.
          $socket_timeout = ini_set('default_socket_timeout', variable_get('advagg_socket_timeout', ADVAGG_SOCKET_TIMEOUT));
          drupal_http_request($url, array(
            'headers' => $headers,
          ));
          ini_set('default_socket_timeout', $socket_timeout);
        }

        // Return filepath if we are going to wait for the bundle to be
        // generated or if the bundle already exists.
        if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) < 2 || advagg_bundle_built($filepath)) {
          $output[$filepath] = array(
            'prefix' => $prefix,
            'suffix' => $suffix,
            'files' => array_map('advagg_return_true', array_flip($files)),
          );
        }
        else {

          // Aggregate isn't built yet, send back the files that where going to
          // be in it.
          foreach ($files as $file) {
            $output[$file . $query_string] = array(
              'prefix' => '',
              'suffix' => '',
              'files' => array(
                $file . $query_string => TRUE,
              ),
            );
          }
          $cacheable = FALSE;
          advagg_disable_page_cache();
        }
        continue;
      }

      // Only generate once.
      $lock_name = 'advagg_' . $filename;
      if (!lock_acquire($lock_name)) {
        if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) == 0) {
          $locks[] = array(
            $lock_name => $filepath,
          );
          $output[$filepath] = array(
            'prefix' => $prefix,
            'suffix' => $suffix,
            'files' => array_map('advagg_return_true', array_flip($files)),
          );
        }
        else {

          // Aggregate isn't built yet, send back the files that where going
          // to be in it.
          foreach ($files as $file) {
            $output[$file . $query_string] = array(
              'prefix' => '',
              'suffix' => '',
              'files' => array(
                $file . $query_string => TRUE,
              ),
            );
          }
          $cacheable = FALSE;
          advagg_disable_page_cache();
        }
        continue;
      }
      if ($type == 'css') {
        $data = advagg_build_css_bundle($files);
      }
      elseif ($type == 'js') {
        $data = advagg_build_js_bundle($files);
      }

      // Invoke hook_advagg_js_alter() or hook_advagg_css_alter to give
      // installed modules a chance to modify the data in the bundle if
      // necessary.
      drupal_alter('advagg_' . $type, $data, $files, $bundle_md5);
      $files_used = array_merge($files_used, $files);

      // If data is empty then do not include this bundle in the final output.
      if (empty($data) && !$force) {
        lock_release($lock_name);
        continue;
      }

      // Create the advagg_$type/ within the files folder.
      file_prepare_directory($file_type_path, FILE_CREATE_DIRECTORY);

      // Write file. default function called: advagg_file_saver
      $function = variable_get('advagg_file_save_function', ADVAGG_FILE_SAVE_FUNCTION);
      $good = $function($data, $filepath, $force, $type);

      // Release lock.
      lock_release($lock_name);

      // If file save was not good then downgrade to non aggregated mode.
      if (!$good) {
        $output[$filepath] = FALSE;
        $cacheable = FALSE;
        continue;
      }
    }
    else {
      $files_used = array_merge($files_used, $files);
    }
    $output[$filepath] = array(
      'prefix' => $prefix,
      'suffix' => $suffix,
      'files' => array_map('advagg_return_true', array_flip($files)),
    );
  }

  // Wait for all locks before returning.
  if (!empty($locks)) {
    foreach ($locks as $lock_name => $filepath) {
      lock_wait($lock_name);
      if (!advagg_bundle_built($filepath)) {
        $output[$filepath] = FALSE;
      }
    }
  }
  if (empty($output)) {
    $output[] = FALSE;
    return $output;
  }

  // Cache the output
  if (!$force && $cacheable) {
    $new_cached_data_key = 'advagg_file_builder_' . md5($schema . implode('', array_filter(array_unique($files_used))));

    // Verify the files in equals the files out.
    if ($new_cached_data_key == $cached_data_key) {
      cache_set($cached_data_key, $output, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY);
    }
  }
  return $output;
}

/**
 * Given a list of files, grab their contents and glue it into one big string.
 *
 * @param $files
 *   array of filenames.
 * @return
 *   string containing all the files.
 */
function advagg_build_css_bundle($files) {
  $data = '';

  // Build aggregate CSS file.
  foreach ($files as $file) {
    $contents = drupal_load_stylesheet($file, TRUE);

    // Return the path to where this CSS file originated from.
    $base = base_path() . dirname($file) . '/';
    _drupal_build_css_path(NULL, $base);

    // Prefix all paths within this CSS file, ignoring external and absolute paths.
    $data .= preg_replace_callback('/url\\([\'"]?(?![a-z]+:|\\/+)([^\'")]+)[\'"]?\\)/i', '_drupal_build_css_path', $contents);
  }

  // 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, $data, $matches);
  $data = preg_replace($regexp, '', $data);
  $data = implode('', $matches[0]) . $data;
  return $data;
}

/**
 * Given a list of files, grab their contents and glue it into one big string.
 *
 * @param $files
 *   array of filenames.
 * @return
 *   string containing all the files.
 */
function advagg_build_js_bundle($files) {
  if (empty($files)) {
    return '';
  }
  $data = '';

  // Build aggregate JS file.
  foreach ($files as $file) {

    // Append a ';' and a newline after each JS file to prevent them from running together.
    if (advagg_file_exists($file)) {
      $data .= file_get_contents($file) . ";\n";
    }
  }
  return $data;
}

/**
 * Use a cache table to see if a file exists.
 *
 * @param $filename
 *   name of file
 * @return
 *   TRUE or FALSE
 */
function advagg_file_exists($filename) {
  static $files = array();
  if (empty($files)) {
    $data = cache_get('advagg_file_checksum', 'cache');
    if (empty($data->data)) {
      $result = db_query("SELECT filename, checksum FROM {advagg_files}");
      while ($row = $result
        ->fetchAssoc()) {
        $files[$row['filename']] = $row['checksum'];
      }
      cache_set('advagg_file_checksum', $files, 'cache', CACHE_TEMPORARY);
    }
    else {
      $files = $data->data;
    }
  }
  if (!empty($files[$filename]) && $files[$filename] != -1) {
    return TRUE;
  }
  else {
    advagg_clearstatcache(TRUE, $filename);
    return file_exists($filename);
  }
}

/**
 * Send out a fast 404 and exit.
 */
function advagg_missing_fast404($msg = '') {
  global $base_path;
  if (!headers_sent()) {
    header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
    header('X-AdvAgg: Failed Validation. ' . $msg);
  }
  print '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
  print '<html>';
  print '<head><title>404 Not Found</title></head>';
  print '<body><h1>Not Found</h1>';
  print '<p>The requested URL was not found on this server.</p>';
  print '<p><a href="' . $base_path . '">Home</a></p>';
  print '<!-- advagg_missing_fast404 -->';
  print '</body></html>';
  exit;
}

/**
 * Generate .htaccess rules and place them in advagg dir
 *
 * @param $dest
 *   destination of the file that just got saved.
 * @param $force
 *   force recreate the .htaccess file.
 */
function advagg_htaccess_check_generate($dest, $force = FALSE) {
  global $base_path;
  if (!$force && !variable_get('advagg_dir_htaccess', ADVAGG_DIR_HTACCESS)) {
    return TRUE;
  }
  $dir = dirname($dest);
  $htaccess_file = $dir . '/.htaccess';
  advagg_clearstatcache(TRUE, $htaccess_file);
  if (!$force && file_exists($htaccess_file)) {
    return TRUE;
  }
  list($css_path, $js_path) = advagg_get_root_files_dir();
  $type = '';
  if ($dir == $js_path[1]) {
    $ext = 'js';
    $path = $js_path[1];
    $type = 'text/javascript';
  }
  elseif ($dir == $css_path[1]) {
    $ext = 'css';
    $path = $css_path[1];
    $type = 'text/css';
  }
  else {
    return FALSE;
  }
  $data = "\n";
  if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION)) {
    $data .= "<IfModule mod_rewrite.c>\n";
    $data .= "  RewriteEngine on\n";
    $data .= "  RewriteBase {$base_path}{$path}\n";
    $data .= "\n";
    $data .= "  # Send 404's back to index.php\n";
    $data .= "  RewriteCond %{REQUEST_FILENAME} !-s\n";
    $data .= "  RewriteRule ^(.*)\$ {$base_path}index.php?q={$path}/\$1 [L]\n";
    $data .= "\n";
    $data .= "  # Rules to correctly serve gzip compressed {$ext} files.\n";
    $data .= "  # Requires both mod_rewrite and mod_headers to be enabled.\n";
    $data .= "  <IfModule mod_headers.c>\n";
    $data .= "    # Serve gzip compressed {$ext} files if they exist and client accepts gzip.\n";
    $data .= "    RewriteCond %{HTTP:Accept-encoding} gzip\n";
    $data .= "    RewriteCond %{REQUEST_FILENAME}\\.gz -s\n";
    $data .= "    RewriteRule ^(.*)\\.{$ext}\$ \$1\\.{$ext}\\.gz [QSA]\n";
    $data .= "\n";
    $data .= "    # Serve correct content types, and prevent mod_deflate double gzip.\n";
    $data .= "    RewriteRule \\.{$ext}\\.gz\$ - [T={$type},E=no-gzip:1]\n";
    $data .= "\n";
    $data .= "    <FilesMatch \"\\.{$ext}\\.gz\$\">\n";
    $data .= "      # Serve correct encoding type.\n";
    $data .= "      Header set Content-Encoding gzip\n";
    $data .= "      # Force proxies to cache gzipped & non-gzipped {$ext} files separately.\n";
    $data .= "      Header append Vary Accept-Encoding\n";
    $data .= "    </FilesMatch>\n";
    $data .= "  </IfModule>\n";
    $data .= "</IfModule>\n";
    $data .= "\n";
  }
  $data .= "<FilesMatch \"^{$ext}_[0-9a-f]{32}_.+\\.{$ext}(\\.gz)?\">\n";
  $data .= "  # No mod_headers\n";
  $data .= "  <IfModule !mod_headers.c>\n";
  $data .= "    # No mod_expires\n";
  $data .= "    <IfModule !mod_expires.c>\n";
  $data .= "      # Use ETags.\n";
  $data .= "      FileETag MTime Size\n";
  $data .= "    </IfModule>\n";
  $data .= "\n";
  $data .= "    # Use Expires Directive.\n";
  $data .= "    <IfModule mod_expires.c>\n";
  $data .= "      # Do not use ETags.\n";
  $data .= "      FileETag None\n";
  $data .= "      # Enable expirations.\n";
  $data .= "      ExpiresActive On\n";
  $data .= "      # Cache all aggregated {$ext} files for 480 weeks after access (A).\n";
  $data .= "      ExpiresDefault A290304000\n";
  $data .= "    </IfModule>\n";
  $data .= "  </IfModule>\n";
  $data .= "\n";
  $data .= "  <IfModule mod_headers.c>\n";
  $data .= "    # Set a far future Cache-Control header to 480 weeks.\n";
  $data .= "    Header set Cache-Control \"max-age=290304000, no-transform, public\"\n";
  $data .= "    # Set a far future Expires header.\n";
  $data .= "    Header set Expires \"Tue, 20 Jan 2037 04:20:42 GMT\"\n";
  $data .= "    # Pretend the file was last modified a long time ago in the past.\n";
  $data .= "    Header set Last-Modified \"Wed, 20 Jan 1988 04:20:42 GMT\"\n";
  $data .= "    # Do not use etags for cache validation.\n";
  $data .= "    Header unset ETag\n";
  $data .= "  </IfModule>\n";
  $data .= "</FilesMatch>\n";
  if (!advagg_file_save_data($data, $htaccess_file, FILE_EXISTS_REPLACE)) {
    return FALSE;
  }
  return TRUE;
}

/**
 * Adds a CSS file to the stylesheet queue.
 *
 * @param $data
 *   (optional) The CSS data that will be set. If not set then the inline CSS
 *   array will be passed back.
 * @param $media
 *   (optional) The media type for the stylesheet, e.g., all, print, screen.
 * @param $prefix
 *   (optional) prefix to add before the inlined css.
 * @param $suffix
 *   (optional) suffix to add after the inlined css.
 * @return
 *   An array of CSS files.
 */
function advagg_add_css_inline($data = NULL, $media = 'all', $prefix = NULL, $suffix = NULL) {
  static $css = array();

  // Store inline data in a static.
  if (isset($data)) {
    if (!isset($css[$media]['inline'][$prefix][$suffix])) {
      $css[$media]['inline'][$prefix][$suffix] = $data;
    }
    else {
      $css[$media]['inline'][$prefix][$suffix] .= "\n" . $data;
    }
    return;
  }
  else {
    return $css;
  }
}

/**
 * Converts a PHP variable into its Javascript equivalent.
 *
 * We use HTML-safe strings, i.e. with <, > and & escaped.
 */
function advagg_drupal_to_js($var) {
  static $php530;
  if (!isset($php530)) {
    $php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
  }

  // json_encode on PHP prior to PHP 5.3.0 doesn't support options.
  if ($php530) {
    return json_encode($var, JSON_HEX_QUOT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS);
  }

  // if json_encode exists, use it.
  if (function_exists('json_encode')) {
    return str_replace(array(
      "<",
      ">",
      "&",
    ), array(
      '\\u003c',
      '\\u003e',
      '\\u0026',
    ), json_encode($var));
  }
  switch (gettype($var)) {
    case 'boolean':
      return $var ? 'true' : 'false';

    // Lowercase necessary!
    case 'integer':
    case 'double':
      return $var;
    case 'resource':
    case 'string':

      // Always use Unicode escape sequences (\u0022) over JSON escape
      // sequences (\") to prevent browsers interpreting these as
      // special characters.
      $replace_pairs = array(
        // ", \ and U+0000 - U+001F must be escaped according to RFC 4627.
        '\\' => '\\u005C',
        '"' => '\\u0022',
        "\0" => '\\u0000',
        "\1" => '\\u0001',
        "\2" => '\\u0002',
        "\3" => '\\u0003',
        "\4" => '\\u0004',
        "\5" => '\\u0005',
        "\6" => '\\u0006',
        "\7" => '\\u0007',
        "\10" => '\\u0008',
        "\t" => '\\u0009',
        "\n" => '\\u000A',
        "\v" => '\\u000B',
        "\f" => '\\u000C',
        "\r" => '\\u000D',
        "\16" => '\\u000E',
        "\17" => '\\u000F',
        "\20" => '\\u0010',
        "\21" => '\\u0011',
        "\22" => '\\u0012',
        "\23" => '\\u0013',
        "\24" => '\\u0014',
        "\25" => '\\u0015',
        "\26" => '\\u0016',
        "\27" => '\\u0017',
        "\30" => '\\u0018',
        "\31" => '\\u0019',
        "\32" => '\\u001A',
        "\33" => '\\u001B',
        "\34" => '\\u001C',
        "\35" => '\\u001D',
        "\36" => '\\u001E',
        "\37" => '\\u001F',
        // Prevent browsers from interpreting these as as special.
        "'" => '\\u0027',
        '<' => '\\u003C',
        '>' => '\\u003E',
        '&' => '\\u0026',
        // Prevent browsers from interpreting the solidus as special and
        // non-compliant JSON parsers from interpreting // as a comment.
        '/' => '\\u002F',
        // While these are allowed unescaped according to ECMA-262, section
        // 15.12.2, they cause problems in some JSON parsers.
        "
" => '\\u2028',
        // U+2028, Line Separator.
        "
" => '\\u2029',
      );
      return '"' . strtr($var, $replace_pairs) . '"';
    case 'array':

      // Arrays in JSON can't be associative. If the array is empty or if it
      // has sequential whole number keys starting with 0, it's not associative
      // so we can go ahead and convert it as an array.
      if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
        $output = array();
        foreach ($var as $v) {
          $output[] = advagg_drupal_to_js($v);
        }
        return '[ ' . implode(', ', $output) . ' ]';
      }

    // Otherwise, fall through to convert the array as an object.
    case 'object':
      $output = array();
      foreach ($var as $k => $v) {
        $output[] = advagg_drupal_to_js(strval($k)) . ': ' . advagg_drupal_to_js($v);
      }
      return '{ ' . implode(', ', $output) . ' }';
    default:
      return 'null';
  }
}

/**
 * Process the contents of a stylesheet for aggregation.
 *
 * @param $contents
 *   The contents of the stylesheet.
 * @param $optimize
 *   (optional) Boolean whether CSS contents should be minified. Defaults to
 *   FALSE.
 * @return
 *   Contents of the stylesheet including the imported stylesheets.
 */
function advagg_drupal_load_stylesheet_content($contents, $optimize = FALSE) {

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

    // Perform some safe CSS optimizations.
    // Regexp to match comment blocks.
    $comment = '/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/';

    // Regexp to match double quoted strings.
    $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';

    // Regexp to match single quoted strings.
    $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/en/regexp.reference.subpatterns.php
    $contents = preg_replace('<
      # 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+
    >xS', '$1$2$3', $contents);

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

  // 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*;/', '_advagg_drupal_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.
 */
function _advagg_drupal_load_stylesheet($matches) {
  $filename = $matches[1];

  // Load the imported stylesheet and replace @import commands in there as well.
  $file = advagg_build_css_bundle(array(
    $filename,
  ));

  // 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]+:|\\/+)/i', 'url(\\1' . $directory, $file);
}

/**
 * Perform an HTTP request; does not wait for reply & you will never get it
 * back.
 *
 * @see drupal_http_request()
 *
 * This is a flexible and powerful HTTP client implementation. Correctly
 * handles GET, POST, PUT or any other HTTP requests.
 *
 * @param $url
 *   A string containing a fully qualified URI.
 * @param array $options
 *   (optional) An array that can have one or more of the following elements:
 *   - headers: An array containing request headers to send as name/value pairs.
 *   - method: A string containing the request method. Defaults to 'GET'.
 *   - data: A string containing the request body, formatted as
 *     'param=value&param=value&...'. Defaults to NULL.
 *   - max_redirects: An integer representing how many times a redirect
 *     may be followed. Defaults to 3.
 *   - timeout: A float representing the maximum number of seconds the function
 *     call may take. The default is 30 seconds. If a timeout occurs, the error
 *     code is set to the HTTP_REQUEST_TIMEOUT constant.
 *   - context: A context resource created with stream_context_create().
 * @return bool
 *   return value from advagg_async_send_http_request().
 */
function advagg_async_connect_http_request($url, array $options = array()) {
  $result = new stdClass();

  // Parse the URL and make sure we can handle the schema.
  $uri = @parse_url($url);
  if (empty($uri)) {
    $result->error = 'unable to parse URL';
    $result->code = -1001;
    return $result;
  }
  if (!isset($uri['scheme'])) {
    $result->error = 'missing schema';
    $result->code = -1002;
    return $result;
  }

  // Merge the default options.
  $options += array(
    'headers' => array(),
    'method' => 'GET',
    'data' => NULL,
    'max_redirects' => 3,
    'timeout' => 30.0,
    'context' => NULL,
  );

  // stream_socket_client() requires timeout to be a float.
  $options['timeout'] = (double) $options['timeout'];
  switch ($uri['scheme']) {
    case 'http':
    case 'feed':
      $port = isset($uri['port']) ? $uri['port'] : 80;
      $socket = 'tcp://' . $uri['host'] . ':' . $port;

      // RFC 2616: "non-standard ports MUST, default ports MAY be included".
      // We don't add the standard port to prevent from breaking rewrite rules
      // checking the host that do not take into account the port number.
      if (empty($options['headers']['Host'])) {
        $options['headers']['Host'] = $uri['host'];
      }
      if ($port != 80) {
        $options['headers']['Host'] .= ':' . $port;
      }
      break;
    case 'https':

      // Note: Only works when PHP is compiled with OpenSSL support.
      $port = isset($uri['port']) ? $uri['port'] : 443;
      $socket = 'ssl://' . $uri['host'] . ':' . $port;
      if (empty($options['headers']['Host'])) {
        $options['headers']['Host'] = $uri['host'];
      }
      if ($port != 443) {
        $options['headers']['Host'] .= ':' . $port;
      }
      break;
    default:
      $result->error = 'invalid schema ' . $uri['scheme'];
      $result->code = -1003;
      return $result;
  }
  $flags = STREAM_CLIENT_CONNECT;
  if (variable_get('advagg_async_socket_connect', ADVAGG_ASYNC_SOCKET_CONNECT)) {
    $flags = STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT;
  }
  if (empty($options['context'])) {
    $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags);
  }
  else {

    // Create a stream with context. Allows verification of a SSL certificate.
    $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags, $options['context']);
  }

  // Make sure the socket opened properly.
  if (!$fp) {

    // When a network error occurs, we use a negative number so it does not
    // clash with the HTTP status codes.
    $result->code = -$errno;
    $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array(
      '@socket' => $socket,
    ));
    return $result;
  }

  // Non blocking stream.
  stream_set_blocking($fp, 0);

  // Construct the path to act on.
  $path = isset($uri['path']) ? $uri['path'] : '/';
  if (isset($uri['query'])) {
    $path .= '?' . $uri['query'];
  }

  // Merge the default headers.
  $options['headers'] += array(
    'User-Agent' => 'Drupal (+http://drupal.org/)',
  );

  // Only add Content-Length if we actually have any content or if it is a POST
  // or PUT request. Some non-standard servers get confused by Content-Length in
  // at least HEAD/GET requests, and Squid always requires Content-Length in
  // POST/PUT requests.
  $content_length = strlen($options['data']);
  if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
    $options['headers']['Content-Length'] = $content_length;
  }

  // If the server URL has a user then attempt to use basic authentication.
  if (isset($uri['user'])) {
    $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
  }

  // If the database prefix is being used by SimpleTest to run the tests in a copied
  // database then set the user-agent header to the database prefix so that any
  // calls to other Drupal pages will run the SimpleTest prefixed database. The
  // user-agent is used to ensure that multiple testing sessions running at the
  // same time won't interfere with each other as they would if the database
  // prefix were stored statically in a file or database variable.
  $test_info =& $GLOBALS['drupal_test_info'];
  if (!empty($test_info['test_run_id'])) {
    $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
  }
  $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
  foreach ($options['headers'] as $name => $value) {
    $request .= $name . ': ' . trim($value) . "\r\n";
  }
  $request .= "\r\n" . $options['data'];
  $result->request = $request;
  return advagg_async_send_http_request($fp, $request, $options['timeout']);
}

/**
 * Perform an HTTP request; does not wait for reply & you never will get it
 * back.
 *
 * @see drupal_http_request()
 *
 * This is a flexible and powerful HTTP client implementation. Correctly
 * handles GET, POST, PUT or any other HTTP requests.
 *
 * @param $fp
 *   (optional) A file pointer.
 * @param $request
 *   (optional) A string containing the request headers to send to the server.
 * @param $timeout
 *   (optional) An integer holding the stream timeout value.
 * @return bool
 *   TRUE if function worked as planed.
 */
function advagg_async_send_http_request($fp = NULL, $request = '', $timeout = 30) {
  static $requests = array();
  static $registered = FALSE;

  // Store data in a static, and register a shutdown function.
  $args = array(
    $fp,
    $request,
    $timeout,
  );
  if (!empty($fp)) {
    $requests[] = $args;
    if (!$registered) {
      register_shutdown_function(__FUNCTION__);
      $registered = TRUE;
    }
    return TRUE;
  }

  // Shutdown function run.
  if (empty($requests)) {
    return FALSE;
  }
  $streams = array();
  foreach ($requests as $id => $values) {
    list($fp, $request, $timeout) = $values;
    $streams[$id] = $fp;
  }
  $retry_count = 2;

  // Run the loop as long as we have a stream to write to.
  while (!empty($streams)) {

    // Set the read and write vars to the streams var.
    $read = $write = $streams;
    $except = array();

    // Do some voodoo and open all streams at once.
    $n = @stream_select($read, $write, $except, $timeout);

    // We have some streams to write to.
    if (!empty($n)) {

      // Write to each stream if it is available.
      foreach ($write as $id => $w) {
        fwrite($w, $requests[$id][1]);
        fclose($w);
        unset($streams[$id]);
      }
    }
    elseif (!empty($retry_count)) {
      $retry_count--;
    }
    else {
      break;
    }
  }

  // Free memory.
  $requests = array();
  if ($n !== FALSE && empty($streams)) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Implements hook_advagg_js_header_footer_alter().
 */
function advagg_advagg_js_header_footer_alter(&$master_set, $preprocess_js, $scheme) {

  // Don't run the code below if ctools ajax is not loaded.
  if (!defined('CTOOLS_AJAX_INCLUDED')) {
    return;
  }

  // Get all JS files set to be loaded.
  $js_files = array();
  foreach ($master_set as $scope => $scripts) {
    if (empty($scripts)) {
      continue;
    }
    advagg_ctools_process_js_files($js_files, $scope, $scripts);
  }

  // Add list of CSS & JS files loaded to the settings in the footer.
  $loaded = array(
    'CToolsAJAX' => array(
      'scripts' => $js_files,
    ),
  );

  // Save to the js settings array even though we do not reload it in advagg.
  drupal_add_js($loaded, array(
    'type' => 'setting',
    'scope' => 'footer',
  ));

  // Add it to the settings array in the footer.
  if (!isset($master_set['footer']['setting']) || !is_array($master_set['footer']['setting'])) {
    $master_set['footer']['setting'] = array();
  }
  $master_set['footer']['setting'][] = $loaded;
}

/**
 * Implements hook_advagg_css_pre_alter().
 */
function advagg_advagg_css_pre_alter(&$css, $preprocess_css, $scheme) {

  // Don't run the code below if ctools ajax is not loaded.
  if (!defined('CTOOLS_AJAX_INCLUDED')) {
    return;
  }

  // Get all CSS files set to be loaded.
  $css_files = array();
  ctools_process_css_files($css_files, $css);

  // Save to the js settings array.
  drupal_add_js(array(
    'CToolsAJAX' => array(
      'css' => $css_files,
    ),
  ), array(
    'type' => 'setting',
    'scope' => 'footer',
  ));
}

/**
 * Create a list of javascript files that are on the page.
 *
 * @param $js_files
 *   Array of js files that are loaded on this page.
 * @param $scope
 *   String usually containing header or footer.
 * @param $scripts
 *   (Optional) array returned from drupal_add_js(). If NULL then it will load
 *   the array from drupal_add_js for the given scope.
 * @return array $settings
 *   The JS 'setting' array for the given scope.
 */
function advagg_ctools_process_js_files(&$js_files, $scope, $scripts = NULL) {

  // Automatically extract any 'settings' added via drupal_add_js() and make
  // them the first command.
  $scripts = drupal_add_js(NULL, array(
    'type' => NULL,
    'scope' => $scope,
  ));
  if (empty($scripts)) {
    $scripts = drupal_add_js(NULL, array(
      'type' => NULL,
      'scope' => $scope,
    ));
  }

  // Get replacements that are going to be made by contrib modules and take
  // them into account so we don't double-load scripts.
  static $replacements = NULL;
  if (!isset($replacements)) {
    $replacements = module_invoke_all('js_replacements');
  }
  $settings = array();
  foreach ($scripts as $type => $data) {
    switch ($type) {
      case 'setting':
        $settings = $data;
        break;
      case 'inline':
      case 'theme':

        // Presently we ignore inline javascript.
        // Theme JS is already added and because of admin themes, this could add
        // improper JS to the page.
        break;
      default:

        // If JS preprocessing is off, we still need to output the scripts.
        // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
        foreach ($data as $path => $info) {

          // If the script is being replaced, take that replacement into account.
          $final_path = isset($replacements[$type][$path]) ? $replacements[$type][$path] : $path;
          $js_files[base_path() . $final_path] = TRUE;
        }
    }
  }
  return $settings;
}

/**
 * Wrapper around clearstatcache so it can use php 5.3's new features.
 *
 * @param $clear_realpath_cache
 *   Bool.
 * @param $filename
 *   String.
 * @return
 *   value from clearstatcache().
 */
function advagg_clearstatcache($clear_realpath_cache = FALSE, $filename = NULL) {
  static $php530;
  if (!isset($php530)) {
    $php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
  }
  if ($php530) {
    return clearstatcache($clear_realpath_cache, $filename);
  }
  else {
    return clearstatcache();
  }
}

/**
 * Select records in the database matching where IN(...).
 *
 * NOTE Be aware of the servers max_packet_size variable.
 *
 * @param $table
 *   The name of the table.
 * @param $field
 *  field name to be compared to
 * @param $placeholder
 *   db_query placeholders; like %d or '%s'
 * @param $data
 *   array of values you wish to compare to
 * @param $returns
 *   array of db fields you return
 * @return
 *   returns db_query() result.
 */
function advagg_db_multi_select_in($table, $field, $placeholder, $data, $returns = array(), $groupby = '') {

  // Set returns if empty
  if (empty($returns)) {
    $returns[] = '*';
  }

  // Get the number of rows that will be inserted
  $rows = count($data);

  // Create what goes in the IN ()
  $in = $placeholder;

  // Add the rest of the place holders
  for ($i = 1; $i < $rows; $i++) {
    $in .= ', ' . $placeholder;
  }

  // Build the query
  $query = "SELECT " . implode(', ', $returns) . " FROM {" . $table . "} WHERE {$field} IN ({$in}) {$groupby}";

  // Run the query
  // TODO Please convert this statement to the D7 database API syntax.
  return db_query($query, $data);
}

/**
 * Return a large array of the CSS & JS files loaded on this page.
 *
 * @param $js_files_excluded
 *   array of js files to not include in the output array.
 * @param $css_files_excluded
 *   array of css files to not include in the output array.
 * @return
 *   array.
 */
function advagg_get_js_css_get_array($js_files_excluded = array(), $css_files_excluded = array()) {
  global $conf, $_advagg;

  // Setup variables.
  $variables = array(
    'css' => array(),
    'js' => array(),
  );

  // Setup render functions.
  $css_function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION);
  $js_function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION);
  $conf['advagg_css_render_function'] = 'advagg_css_array';
  $conf['advagg_js_render_function'] = 'advagg_js_array';

  // Run CSS code.
  $css_array = array();
  $variables['css'] = drupal_add_css(CSS_DEFAULT);
  if (module_exists('less')) {
    less_preprocess_page($variables, NULL);
  }
  $css_func_inline = advagg_add_css_inline();
  if (!empty($css_func_inline)) {
    $variables['css'] = advagg_merge_inline_css($variables['css'], $css_func_inline);
  }

  // Remove excluded CSS files.
  foreach ($variables['css'] as $media => $types) {
    foreach ($types as $type => $values) {
      foreach ($values as $filename => $preprocess) {
        if (in_array($filename, $css_files_excluded)) {
          unset($variables['css'][$media][$type][$filename]);
        }
      }
    }
  }
  $css_array = advagg_process_css($variables['css']);

  // Run JS code.
  $js_array = array();
  $variables['js']['header'] = drupal_add_js(NULL, array(
    'type' => NULL,
  ));
  if (variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) {
    $variables['js']['footer'] = drupal_add_js(NULL, array(
      'type' => NULL,
      'scope' => 'footer',
    ));
  }
  advagg_jquery_updater($variables['js']['header']);

  // Remove excluded JS files.
  foreach ($variables['js'] as $scope => $values) {
    foreach ($values as $type => $data) {
      foreach ($data as $filename => $info) {
        if (in_array($filename, $js_files_excluded)) {
          unset($variables['js'][$scope][$type][$filename]);
        }
      }
    }
  }
  $js_array = advagg_process_js($variables['js']);

  // Set render functions back to defaults.
  $conf['advagg_css_render_function'] = $css_function;
  $conf['advagg_js_render_function'] = $js_function;

  // Return arrays.
  return array(
    'js' => $js_array,
    'css' => $css_array,
  );
}

/**
 * Logic to figure out what kind of css tags to use.
 *
 * @param $external_no_preprocess
 *   array of css files ($media, $href)
 * @param $module_no_preprocess
 *   array of css files ($media, $href)
 * @param $output_no_preprocess
 *   array of css files ($media, $href)
 * @param $output_preprocess
 *   array of css files ($media, $href, $prefix, $suffix)
 * @param $theme_no_preprocess
 *   array of css files ($media, $href)
 * @param $inline_no_preprocess
 *   array of css data to inline ($media, $data)
 * @param $files_included
 *   array of css files included. $a[$media][] = $filename
 * @param $files_aggregates_included
 *   array of css files & aggregates included. $a[$media][] = $filename
 * @param $inline_included
 *   array of inline css included. $a[$media][] = $datablob;
 * @return
 *   html for loading the css. html for the head.
 */
function advagg_css_array($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included) {
  return array(
    'inline' => $inline_included,
    'files' => $files_included,
    'files_aggregates' => $files_aggregates_included,
  );
}

/**
 * Build and theme JS output for header.
 *
 * @param $external_no_preprocess
 *   array(array($src, $defer))
 * @param $output_preprocess
 *   array(array($src, $prefix, $suffix))
 * @param $output_no_preprocess
 *   array(array(array($src, $defer)))
 * @param $setting_no_preprocess
 *   array(array($code))
 * @param $inline_no_preprocess
 *   array(array($code, $defer))
 * @param $scope
 *   header or footer.
 * @param $js_settings_array
 *   array of settings used.
 * @param $inline_included
 *   array of inline scripts used.
 * @param $files_included
 *   array of files used.
 * @param $files_aggregates_included
 *   array of files and aggregates used.
 * @return
 *   String of themed JavaScript.
 */
function advagg_js_array($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) {
  return array(
    'settings' => $js_settings_array,
    'inline' => $inline_included,
    'files' => $files_included,
    'files_aggregates' => $files_aggregates_included,
  );
}

/**
 * Implements hook_file_download().
 *
 * Return the correct headers for advagg bundles.
 */
function advagg_file_download($file, $type = '') {

  // Do nothing if not an AdvAgg File.
  if (strpos($file, '/advagg_') === FALSE || empty($type)) {
    return;
  }

  // Set the headers.
  $return = array();
  $return[] = 'Content-Length: ' . filesize($file);

  // Set a far future Cache-Control header (480 weeks), which prevents
  // intermediate caches from transforming the data and allows any
  // intermediate cache to cache it, since it's marked as a public resource.
  $return[] = "Cache-Control: max-age=290304000, no-transform, public";

  // Set a far future Expires header. The maximum UNIX timestamp is somewhere
  // in 2038. Set it to a date in 2037, just to be safe.
  $return[] = 'Expires: Tue, 20 Jan 2037 04:20:42 GMT';

  // Pretend the file was last modified a long time ago in the past, this will
  // prevent browsers that don't support Cache-Control nor Expires headers to
  // still request a new version too soon (these browsers calculate a
  // heuristic to determine when to request a new version, based on the last
  // time the resource has been modified).
  // Also see http://code.google.com/speed/page-speed/docs/caching.html.
  $return[] = 'Last-Modified: Wed, 20 Jan 1988 04:20:42 GMT';
  if ($type == 'css') {
    $return[] = 'Content-Type: text/css';
  }
  if ($type == 'js') {
    $return[] = 'Content-Type: text/javascript';
  }
  return $return;
}

/**
 * Helper function to build an URL for asynchronous requests.
 *
 * @param $filepath
 *   Path to a URI excluding everything to the left and including the base path.
 */
function _advagg_build_url($filepath = '') {
  global $base_path;

  // Server auth.
  $auth = '';
  if (isset($_SERVER['AUTH_TYPE']) && $_SERVER['AUTH_TYPE'] == 'Basic') {
    $auth = $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'] . '@';
  }

  // Host.
  $ip = variable_get('advagg_server_addr', FALSE);
  if ($ip == -1) {
    $ip = $_SERVER['HTTP_HOST'];
  }
  elseif (empty($ip)) {
    $ip = empty($_SERVER['SERVER_ADDR']) ? '127.0.0.1' : $_SERVER['SERVER_ADDR'];
  }

  // Port.
  $port = '';

  //   if (   isset($_SERVER['SERVER_PORT'])
  //       && is_numeric($_SERVER['SERVER_PORT'])
  //       && ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443)
  //         ) {
  //     $port = ':' . $_SERVER['SERVER_PORT'];
  //   }
  return 'http://' . $auth . $ip . $port . $base_path . $filepath;
}

Functions

Namesort descending Description
advagg_add_css_inline Adds a CSS file to the stylesheet queue.
advagg_admin_menu Implements hook_admin_menu().
advagg_admin_menu_cache_info
advagg_admin_menu_output_alter Implements hook_admin_menu_output_alter().
advagg_advagg_css_pre_alter Implements hook_advagg_css_pre_alter().
advagg_advagg_disable_processor Implements hook_advagg_disable_processor().
advagg_advagg_js_header_footer_alter Implements hook_advagg_js_header_footer_alter().
advagg_async_connect_http_request Perform an HTTP request; does not wait for reply & you will never get it back.
advagg_async_send_http_request Perform an HTTP request; does not wait for reply & you never will get it back.
advagg_build_css_bundle Given a list of files, grab their contents and glue it into one big string.
advagg_build_filename Build the filename.
advagg_build_js_bundle Given a list of files, grab their contents and glue it into one big string.
advagg_build_uri Given path output uri to that file
advagg_bundle_built See if this bundle has been built.
advagg_cached_bundle_get Get a bundle from the cache & verify it is good.
advagg_checksum Generate a checksum for a given filename.
advagg_clearstatcache Wrapper around clearstatcache so it can use php 5.3's new features.
advagg_cron Implements hook_cron().
advagg_css_array Logic to figure out what kind of css tags to use.
advagg_css_array_fixer Remove .less files from the array.
advagg_css_js_file_builder Aggregate CSS/JS files, putting them in the files directory.
advagg_ctools_process_js_files Create a list of javascript files that are on the page.
advagg_db_multi_select_in Select records in the database matching where IN(...).
advagg_delete_file_if_stale Callback to delete files modified more than a set time ago.
advagg_disable_page_cache Disable the page cache if the aggregate is not in the bundle.
advagg_drupal_load_stylesheet_content Process the contents of a stylesheet for aggregation.
advagg_drupal_to_js Converts a PHP variable into its Javascript equivalent.
advagg_element_info_alter Implements hook_element_info_alter().
advagg_file_copy Copies a file to a new location.
advagg_file_download Implements hook_file_download().
advagg_file_exists Use a cache table to see if a file exists.
advagg_file_move Moves a file to a new location.
advagg_file_saver Save a string to the specified destination. Verify that file size is not zero.
advagg_file_save_data Save a string to the specified destination.
advagg_find_existing_bundle Given a list of files, see if a bundle already exists containing all of those files. If in strict mode then the file count has to be the same.
advagg_flush_caches Implements hook_flush_caches().
advagg_form_system_performance_settings_alter Implements hook_form_FORM_ID_alter().
advagg_get_bundle_from_filename @todo Please document this function.
advagg_get_filename Given a list of files; return back the aggregated filename.
advagg_get_files_in_bundle Get list of files and the filetype given a bundle md5.
advagg_get_file_data Get data about a file.
advagg_get_full_js Get full JS array.
advagg_get_js Returns a themed presentation of all JavaScript code for the current page.
advagg_get_js_css_get_array Return a large array of the CSS & JS files loaded on this page.
advagg_get_js_scopes Get all javascript scopes set in the $javascript array.
advagg_get_root_files_dir Get the CSS & JS path for advagg.
advagg_get_server_schema Return the server schema (http or https).
advagg_htaccess_check_generate Generate .htaccess rules and place them in advagg dir
advagg_init Implements hook_init().
advagg_insert_bundle_db Insert info into the advagg_files and advagg_bundles database.
advagg_jquery_updater Special handling for jquery update.
advagg_js_array Build and theme JS output for header.
advagg_js_builder Build and theme JS output for header.
advagg_menu Implements hook_menu().
advagg_merge_css Merge 2 css arrays together.
advagg_merge_inline_css Merge 2 css arrays together.
advagg_missing_fast404 Send out a fast 404 and exit.
advagg_permission Implements hook_permission().
advagg_process_css_js_prep Returns an array of values needed for aggregation
advagg_process_html Implements hook_process_html().
advagg_process_js Returns a themed presentation of all JavaScript code for the current page.
advagg_rebuild_bundle Rebuild a bundle.
advagg_return_true Always return TRUE, used for array_map in advagg_css_js_file_builder().
advagg_set_file_data Set data about a file.
advagg_string_ends_with See if a string ends with a substring.
_advagg_aggregate_css Default callback to aggregate CSS files and inline content.
_advagg_build_url Helper function to build an URL for asynchronous requests.
_advagg_drupal_load_stylesheet Loads stylesheets recursively and returns contents with corrected paths.