Same filename and directory in other branches
  1. 8.x-2.x advagg.advagg.inc 1 comment

Advanced CSS/JS aggregation module.

File used to store hook_advagg_* hooks.

File

./advagg.advagg.inc

View source
<?php


/**
 * @file
 * Advanced CSS/JS aggregation module.
 *
 * File used to store hook_advagg_* hooks.
 */

/**
 * @addtogroup advagg_hooks
 * @{
 */

/**
 * Implements hook_advagg_save_aggregate_alter().
 *
 * Used to add in a .gz file if none exits.
 */
function advagg_advagg_save_aggregate_alter(array &$files_to_save, array $aggregate_settings, array $other_parameters) {
    // * @param array $files_to_save
    // *   Array($uri => $contents).
    // * @param array $aggregate_settings
    // *   Array of settings.
    // * @param array $other_parameters
    // *   Array of containing $files and $type.
    $file_types = array();
    // Handle gzip.
    if (!empty($aggregate_settings['variables']['advagg_gzip'])) {
        // Use zopfli_encode if it exists.
        // See https://github.com/kjdev/php-ext-zopfli
        if (function_exists('zopfli_encode') && defined('ZOPFLI_GZIP') && empty($aggregate_settings['variables']['advagg_no_zopfli'])) {
            $file_types['.gz'] = array(
                'zopfli_encode',
                15,
                constant('ZOPFLI_GZIP'),
            );
        }
        else {
            $file_types['.gz'] = array(
                'gzencode',
                9,
                FORCE_GZIP,
            );
        }
    }
    // Handle brotli.
    // See https://github.com/kjdev/php-ext-brotli
    if (!empty($aggregate_settings['variables']['advagg_brotli']) && defined('BROTLI_TEXT') && function_exists('brotli_compress')) {
        $file_types['.br'] = array(
            'brotli_compress',
            11,
            constant('BROTLI_TEXT'),
        );
    }
    if (empty($file_types)) {
        return;
    }
    // Special S3 handling.
    $s3fs = FALSE;
    $files_to_save_keys = array_keys($files_to_save);
    foreach ($files_to_save_keys as $uri) {
        $wrapper = file_stream_wrapper_get_instance_by_uri($uri);
        if ($wrapper && get_class($wrapper) === 'S3fsStreamWrapper') {
            $s3fs = TRUE;
            break;
        }
    }
    foreach ($file_types as $ext => $settings) {
        // See if a file already exists with this extension.
        $ext_exists = FALSE;
        foreach ($files_to_save as $uri => $contents) {
            // See if this uri contains $ext near the end of it.
            if (strlen($uri) > 91 + strlen(ADVAGG_SPACE) * 3) {
                $pos = strripos($uri, $ext, 91 + strlen(ADVAGG_SPACE) * 3);
            }
            else {
                $pos = strripos($uri, $ext);
            }
            if (!empty($pos)) {
                $len = strlen($uri);
                // $ext file exists, exit loop.
                if ($pos == $len - 3) {
                    $ext_exists = TRUE;
                    break;
                }
            }
        }
        // If a $ext file does not exist, create one.
        if (!$ext_exists) {
            // Use the first file in the array.
            $data = reset($files_to_save);
            $uri = key($files_to_save);
            // Compress it and add it to the $files_to_save array.
            $callback = $settings[0];
            $settings[0] = $data;
            $compressed = call_user_func_array($callback, $settings);
            if (!empty($compressed)) {
                if ($s3fs && $ext === '.gz') {
                    // Only serve gzip files from S3.
                    $files_to_save[$uri] = $compressed;
                }
                elseif ($s3fs && $ext === '.br' && !isset($file_types['.gz'])) {
                    // Only serve br files from S3.
                    $files_to_save[$uri] = $compressed;
                }
                else {
                    $files_to_save[$uri . $ext] = $compressed;
                }
            }
        }
    }
}

/**
 * Implements hook_advagg_build_aggregate_plans_alter().
 *
 * Used to alter the plan so it has the same grouping as cores.
 */
function advagg_advagg_build_aggregate_plans_alter(array &$files, &$modified, $type) {
    // * @param array $files
    // *   List of files in the aggregate as well as the aggregate name.
    // * @param bool $modified
    // *   Change this to TRUE if $files has been changed.
    // * @param string $type
    // *   String containing css or js.
    //
    // Do nothing if core grouping is disabled.
    if (!variable_get('advagg_core_groups', ADVAGG_CORE_GROUPS)) {
        return;
    }
    $temp_new_files = array();
    $counter = 0;
    foreach ($files as $data) {
        $group = NULL;
        $every_page = NULL;
        foreach ($data['files'] as $fileinfo) {
            // Grouped by group and every_page variables.
            if (is_null($group)) {
                $group = $fileinfo['group'];
            }
            if (is_null($every_page)) {
                $every_page = $fileinfo['every_page'];
            }
            // Bump Counter if group/every_page has changed from the last one.
            if ($group != $fileinfo['group'] || $every_page != $fileinfo['every_page']) {
                ++$counter;
                $group = $fileinfo['group'];
                $every_page = $fileinfo['every_page'];
                $modified = TRUE;
            }
            $temp_new_files[$counter][] = $fileinfo;
        }
        ++$counter;
    }
    // Replace $files array with new aggregate filenames.
    $files = advagg_generate_filenames(array(
        $temp_new_files,
    ), $type);
}

/**
 * Implements hook_advagg_context_alter().
 */
function advagg_advagg_context_alter(&$original, $aggregate_settings, $mode) {
    if ($mode == 0) {
        // Change context.
        $original['base_root'] = $GLOBALS['base_root'];
        $original['base_url'] = $GLOBALS['base_url'];
        $original['base_path'] = $GLOBALS['base_path'];
        $original['is_https'] = $GLOBALS['is_https'];
        $original['language'] = isset($GLOBALS['language']) ? $GLOBALS['language'] : NULL;
        $GLOBALS['is_https'] = $aggregate_settings['variables']['is_https'];
        if ($aggregate_settings['variables']['is_https']) {
            $GLOBALS['base_root'] = str_replace('http://', 'https://', $GLOBALS['base_root']);
        }
        else {
            $GLOBALS['base_root'] = str_replace('https://', 'http://', $GLOBALS['base_root']);
        }
        $GLOBALS['base_path'] = $aggregate_settings['variables']['base_path'];
        $GLOBALS['base_url'] = rtrim($GLOBALS['base_root'] . $GLOBALS['base_path'], '/');
        if (isset($aggregate_settings['variables']['language'])) {
            $languages = language_list();
            if (isset($languages[$aggregate_settings['variables']['language']])) {
                $GLOBALS['language'] = $languages[$aggregate_settings['variables']['language']];
            }
        }
    }
    elseif ($mode == 1) {
        // Change context back.
        if (isset($original['base_root'])) {
            $GLOBALS['base_root'] = $original['base_root'];
        }
        if (isset($original['base_url'])) {
            $GLOBALS['base_url'] = $original['base_url'];
        }
        if (isset($original['base_path'])) {
            $GLOBALS['base_path'] = $original['base_path'];
        }
        if (isset($original['is_https'])) {
            $GLOBALS['is_https'] = $original['is_https'];
        }
        if (isset($original['language'])) {
            $GLOBALS['language'] = $original['language'];
        }
    }
    // Moved this in here due to incomplete bug reports from
    // https://www.drupal.org/node/1942230. I can not reproduce the reported
    // issues with the patch for the CDN module so I've moved the code into
    // advagg.
    if (!function_exists('cdn_advagg_context_alter') && module_exists('cdn')) {
        if ($mode == 0) {
            // Save original context.
            $original[CDN_MODE_VARIABLE] = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
            $original[CDN_BASIC_FARFUTURE_VARIABLE] = variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT);
            $original[CDN_HTTPS_SUPPORT_VARIABLE] = variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE);
            $original[CDN_STATUS_VARIABLE] = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
            // Set context for file_create_url()/cdn_file_url_alter().
            $GLOBALS['conf'][CDN_MODE_VARIABLE] = isset($aggregate_settings['variables'][CDN_MODE_VARIABLE]) ? $aggregate_settings['variables'][CDN_MODE_VARIABLE] : variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
            $GLOBALS['conf'][CDN_BASIC_FARFUTURE_VARIABLE] = isset($aggregate_settings['variables'][CDN_BASIC_FARFUTURE_VARIABLE]) ? $aggregate_settings['variables'][CDN_BASIC_FARFUTURE_VARIABLE] : variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT);
            $GLOBALS['conf'][CDN_HTTPS_SUPPORT_VARIABLE] = isset($aggregate_settings['variables'][CDN_HTTPS_SUPPORT_VARIABLE]) ? $aggregate_settings['variables'][CDN_HTTPS_SUPPORT_VARIABLE] : variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE);
            $GLOBALS['conf'][CDN_STATUS_VARIABLE] = isset($aggregate_settings['variables'][CDN_STATUS_VARIABLE]) ? $aggregate_settings['variables'][CDN_STATUS_VARIABLE] : variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
            // Disable CDN if cdn_check_drupal_path is FALSE.
            if (empty($aggregate_settings['variables']['cdn_check_drupal_path'])) {
                $original[CDN_STATUS_VARIABLE] = CDN_DISABLED;
            }
            // Handle HTTPS.
            if (!empty($aggregate_settings['variables']['cdn_request_is_https']) && !cdn_request_is_https()) {
                if (isset($_SERVER['HTTPS'])) {
                    $original['HTTPS'] = $_SERVER['HTTPS'];
                }
                else {
                    $original['HTTPS'] = FALSE;
                }
                $_SERVER['HTTPS'] = 'on';
            }
        }
        elseif ($mode == 1) {
            // Set context back.
            $GLOBALS['conf'][CDN_MODE_VARIABLE] = $original[CDN_MODE_VARIABLE];
            $GLOBALS['conf'][CDN_BASIC_FARFUTURE_VARIABLE] = $original[CDN_BASIC_FARFUTURE_VARIABLE];
            $GLOBALS['conf'][CDN_HTTPS_SUPPORT_VARIABLE] = $original[CDN_HTTPS_SUPPORT_VARIABLE];
            $GLOBALS['conf'][CDN_STATUS_VARIABLE] = $original[CDN_STATUS_VARIABLE];
            // Handle HTTPS.
            if (isset($original['HTTPS']) && !is_null($original['HTTPS'])) {
                $_SERVER['HTTPS'] = $original['HTTPS'];
            }
        }
    }
}

/**
 * Implements hook_advagg_get_info_on_files_alter().
 *
 * Used to make sure the info is up to date in the cache.
 */
function advagg_advagg_get_info_on_files_alter(&$return, $cached_data, $bypass_cache) {
    // Check for the ie_css_selector_limiter.
    if (variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER)) {
        $limit_value = variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE);
        // Get the css path.
        list($css_path) = advagg_get_root_files_dir();
        $css_parts_path = advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE) ? $css_path[0] : $css_path[1];
        $parts_path = $css_parts_path . '/parts/';
        foreach ($return as &$info) {
            // Skip if not a css file.
            if (empty($info['fileext']) || $info['fileext'] !== 'css') {
                continue;
            }
            // Check if this is a split css file.
            if (strpos($info['data'], $parts_path) !== FALSE) {
                $info['split'] = TRUE;
            }
            elseif ($info['linecount'] > $limit_value) {
                advagg_split_css_file($info);
            }
        }
        unset($info);
    }
    // Capture resource_hints.
    if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT) || variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {
        $aggregate_settings = advagg_current_hooks_hash_array();
        foreach ($return as &$info) {
            // Skip if not a css file.
            if (empty($info['fileext']) || $info['fileext'] !== 'css') {
                continue;
            }
            // Get the file contents.
            $file_contents = (string) @advagg_file_get_contents($info['data']);
            $file_contents = advagg_load_css_stylesheet($info['data'], FALSE, $aggregate_settings, $file_contents);
            // Get domain names and external assets in this css file.
            $hosts = array();
            $urls = array();
            $matches = array();
            $pattern = '%url\\(\\s*+[\'"]?+(http:\\/\\/|https:\\/\\/|\\/\\/)([^\'"()\\s]++)[\'"]?+\\s*+\\)%i';
            preg_match_all($pattern, $file_contents, $matches);
            if (!empty($matches[1])) {
                foreach ($matches[1] as $key => $match) {
                    $url = $match . $matches[2][$key];
                    $parse = @parse_url($url);
                    if (!empty($parse['host'])) {
                        $extra = '';
                        $ext = strtolower(pathinfo($url, PATHINFO_EXTENSION));
                        $supported = array(
                            'eot',
                            'woff2',
                            'woff',
                            'ttf',
                            'otf',
                        );
                        if (in_array($ext, $supported)) {
                            $extra .= '#crossorigin';
                        }
                        $hosts[$parse['host'] . $extra] = $match . $parse['host'] . '/' . $extra;
                        $urls[$url] = $url;
                    }
                }
            }
            if (!empty($hosts) && (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH) || variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT))) {
                $info['dns_prefetch'] = array_values($hosts);
            }
            // Get local files to preload in this css file.
            if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {
                $matches = array();
                $pattern = '/url\\(\\s*+[\'"]?(.*?)[\'"\\s]?+\\)/i';
                preg_match_all($pattern, $file_contents, $matches);
                if (!empty($matches[1])) {
                    foreach ($matches[1] as $key => $match) {
                        $url = advagg_convert_abs_to_rel($match);
                        $parse = @parse_url($url);
                        if (empty($parse['host'])) {
                            $urls[$url] = $url;
                        }
                    }
                }
                if (!empty($urls)) {
                    $info['preload'] = array_values($urls);
                }
            }
        }
        unset($info);
    }
}

/**
 * Implements hook_advagg_changed_files().
 *
 * If the color module is enabled regenerate color module css when it changes.
 * If a responsive file inside an adaptive theme has changed, regenerate it.
 */
function advagg_advagg_changed_files(array $files, array $types) {
    // * @param array $files
    // *   List of files that have changed.
    // * @param array $types
    // *   Array with the css and or the js key.
    if (module_exists('locale')) {
        _advagg_locale_changed_files($files, $types);
    }
    // Keep track of what themes have been done.
    static $themes_done;
    if (!isset($themes_done)) {
        $themes_done = array();
    }
    // Skip if no css changed.
    if (empty($types['css'])) {
        return;
    }
    foreach ($files as $filename => $meta_data) {
        // Only care about css files.
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        if ($ext !== 'css') {
            continue;
        }
        advagg_advagg_scan_for_changes($filename, TRUE);
    }
    // Save error states and clear them.
    $errors_before = drupal_static('form_set_error', array());
    form_clear_error();
    // See if the css file is used the theme.
    $themes = list_themes();
    $changed_files = array_keys($files);
    $submit_ran = FALSE;
    foreach ($themes as $theme_name => $theme_values) {
        $files_in_theme = array();
        foreach ($changed_files as $css_file) {
            // Skip if we already did a form submit for this theme.
            if (!empty($themes_done[$theme_name])) {
                continue;
            }
            // Skip if the file that was changed is not in this themes directory.
            $theme_path = drupal_get_path('theme', $theme_name);
            if (!empty($theme_path) && strpos($css_file, $theme_path) !== 0) {
                continue;
            }
            $files_in_theme[] = $css_file;
        }
        // Skip the theme if none of the changed files live in here.
        if (empty($files_in_theme)) {
            continue;
        }
        // Get the form for this theme.
        $router_item = menu_get_item('admin/appearance/settings/' . $theme_name);
        if ($router_item['include_file']) {
            require_once DRUPAL_ROOT . '/' . $router_item['include_file'];
        }
        $form = drupal_get_form('system_theme_settings', $theme_name);
        // Get the form defaults.
        $defaults = array();
        advagg_get_defaults_from_form($defaults, $form);
        $rebuild = FALSE;
        if (isset($defaults['atcore_version_test'])) {
            // Rebuild if the theme is an adaptive theme.
            $rebuild = TRUE;
        }
        if (!$rebuild && module_exists('color')) {
            foreach ($files_in_theme as $css_file) {
                if (isset($form['color'])) {
                    // Rebuild if the file that was changed is a color module file.
                    foreach ($defaults['info']['css'] as $theme_file) {
                        if ($theme_path . '/' . $theme_file === $css_file) {
                            $rebuild = TRUE;
                            break 2;
                        }
                    }
                }
            }
        }
        // Skip if themes css does not need to be generated dynamically.
        if (empty($rebuild)) {
            continue;
        }
        // Build the palette value.
        $palette = array();
        if (isset($form['color'])) {
            foreach (element_children($form['color']['palette']) as $key) {
                $palette[$key] = $form['color']['palette'][$key]['#value'];
            }
        }
        // Build the form state array.
        $form_state = array(
            'values' => array(
                'palette' => $palette,
            ),
            'build_info' => array(
                'args' => array(
                    0 => $theme_name,
                ),
            ),
        );
        $form_state['values'] += $defaults;
        if (isset($defaults['atcore_version_test'])) {
            // Validate form.
            at_core_settings_validate($form, $form_state);
            $errors = form_set_error();
            if (empty($errors)) {
                // Only submit if no errors.
                at_core_settings_submit($form, $form_state);
                $themes_done[$theme_name] = TRUE;
                $submit_ran = TRUE;
            }
        }
        elseif (isset($form['color'])) {
            // Validate form.
            color_scheme_form_validate($form, $form_state);
            $errors = form_set_error();
            if (empty($errors)) {
                // Only submit if no errors.
                color_scheme_form_submit($form, $form_state);
                $themes_done[$theme_name] = TRUE;
                $submit_ran = TRUE;
            }
        }
        // Reset form errors.
        form_clear_error();
    }
    // Save error states back.
    $form_set_error =& drupal_static('form_set_error', array());
    $form_set_error = $errors_before;
    // Rescan again as the submit will generate new files in the css dir.
    if ($submit_ran) {
        advagg_push_new_changes();
    }
}

/**
 * Implements hook_advagg_scan_for_changes().
 *
 * Used to see if the responsive files inside an adaptive theme has changed.
 */
function advagg_advagg_scan_for_changes($filename, $save_changes = FALSE) {
    // Skip if this file is not a css file.
    $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    if ($ext !== 'css') {
        return FALSE;
    }
    // Skip if the file is not in an adaptive theme.
    $adaptivethemes = array();
    $themes = list_themes();
    foreach ($themes as $theme_name => $theme_values) {
        $path = variable_get('theme_' . $theme_name . '_files_directory', '');
        if (!empty($path) && strpos($filename, $path) !== FALSE) {
            $adaptivethemes[$theme_name] = $path;
        }
    }
    if (empty($adaptivethemes)) {
        return;
    }
    $file_changed = array();
    foreach ($adaptivethemes as $theme_name => $path) {
        // Set up some paths we use to get and save files.
        $path_to_responsive_css = drupal_get_path('theme', $theme_name) . '/css/';
        $path_to_panels_css = drupal_get_path('theme', 'adaptivetheme') . '/layouts/css/';
        // Map files to generated file names.
        $file_map = array(
            "{$path}/{$theme_name}.responsive.styles.css" => array(
                $path_to_responsive_css . 'responsive.custom.css',
                $path_to_responsive_css . 'responsive.smalltouch.portrait.css',
                $path_to_responsive_css . 'responsive.smartphone.portrait.css',
                $path_to_responsive_css . 'responsive.smalltouch.landscape.css',
                $path_to_responsive_css . 'responsive.smartphone.landscape.css',
                $path_to_responsive_css . 'responsive.tablet.portrait.css',
                $path_to_responsive_css . 'responsive.tablet.landscape.css',
                $path_to_responsive_css . 'responsive.desktop.css',
            ),
            "{$path}/{$theme_name}.lt-ie8.layout.css" => array(
                $path_to_panels_css . 'ie_defaults.css',
            ),
        );
        if (!isset($file_map[$filename])) {
            continue;
        }
        // See if anything has changed.
        $changes = advagg_detect_subfile_changes($filename, $file_map[$filename], 'adaptivetheme', $save_changes);
        if (!empty($changes)) {
            $file_changed[$path] = $changes;
        }
    }
    return $file_changed;
}

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

/**
 * If the locale module is enabled regenerate locale translations.
 *
 * @param array $files
 *   List of files that have changed.
 * @param array $types
 *   Array with the css and or the js key.
 */
function _advagg_locale_changed_files(array $files, array $types) {
    // Skip if no js changed.
    if (empty($types['js'])) {
        return;
    }
    $javascript = array();
    foreach ($files as $filename => $meta_data) {
        // Only care about js files.
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
        if ($ext !== 'js') {
            continue;
        }
        $javascript[] = array(
            'type' => 'file',
            'data' => $filename,
        );
    }
    if (!empty($javascript)) {
        $javascript_before = $javascript;
        $language_before = $GLOBALS['language'];
        $language_list = language_list();
        foreach ($language_list as $lang) {
            if ($lang->enabled) {
                $GLOBALS['language'] = $lang;
                $javascript = $javascript_before;
                _advagg_locale_js_alter($javascript);
            }
        }
        $GLOBALS['language'] = $language_before;
    }
}

/**
 * Given a form get the default values from it.
 *
 * @param array $defaults
 *   An empty array used to populate the default values.
 * @param array $form
 *   The form returned from drupal_get_form().
 * @param string $parent_key
 *   The key name of the parent.
 */
function advagg_get_defaults_from_form(array &$defaults, array $form, $parent_key = '') {
    foreach (element_children($form) as $key) {
        $values = $form[$key];
        if (isset($values['#value'])) {
            // Grab defaults at this level.
            if (!isset($defaults[$key])) {
                $defaults[$key] = $values['#value'];
            }
            else {
                $defaults[$parent_key . '-' . $key] = $values['#value'];
            }
        }
        elseif (isset($values['#default_value'])) {
            // Grab defaults at this level.
            if (!isset($defaults[$key])) {
                $defaults[$key] = $values['#default_value'];
            }
            else {
                $defaults[$parent_key . '-' . $key] = $values['#default_value'];
            }
        }
        elseif (is_array($values)) {
            // Go deeper if needed.
            advagg_get_defaults_from_form($defaults, $values, $key);
        }
    }
}

Functions

Title Deprecated Summary
advagg_advagg_build_aggregate_plans_alter Implements hook_advagg_build_aggregate_plans_alter().
advagg_advagg_changed_files Implements hook_advagg_changed_files().
advagg_advagg_context_alter Implements hook_advagg_context_alter().
advagg_advagg_get_info_on_files_alter Implements hook_advagg_get_info_on_files_alter().
advagg_advagg_save_aggregate_alter Implements hook_advagg_save_aggregate_alter().
advagg_advagg_scan_for_changes Implements hook_advagg_scan_for_changes().
advagg_get_defaults_from_form Given a form get the default values from it.
_advagg_locale_changed_files If the locale module is enabled regenerate locale translations.