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

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',
                "\x00" => '\\u0000',
                "\x01" => '\\u0001',
                "\x02" => '\\u0002',
                "\x03" => '\\u0003',
                "\x04" => '\\u0004',
                "\x05" => '\\u0005',
                "\x06" => '\\u0006',
                "\x07" => '\\u0007',
                "\x08" => '\\u0008',
                "\t" => '\\u0009',
                "\n" => '\\u000A',
                "\v" => '\\u000B',
                "\f" => '\\u000C',
                "\r" => '\\u000D',
                "\x0e" => '\\u000E',
                "\x0f" => '\\u000F',
                "\x10" => '\\u0010',
                "\x11" => '\\u0011',
                "\x12" => '\\u0012',
                "\x13" => '\\u0013',
                "\x14" => '\\u0014',
                "\x15" => '\\u0015',
                "\x16" => '\\u0016',
                "\x17" => '\\u0017',
                "\x18" => '\\u0018',
                "\x19" => '\\u0019',
                "\x1a" => '\\u001A',
                "\x1b" => '\\u001B',
                "\x1c" => '\\u001C',
                "\x1d" => '\\u001D',
                "\x1e" => '\\u001E',
                "\x1f" => '\\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

Title Deprecated Summary
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.