6978 lines
229 KiB
Plaintext
6978 lines
229 KiB
Plaintext
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Advanced CSS/JS aggregation module.
|
|
*/
|
|
|
|
/**
|
|
* @defgroup default_variables default values for variables
|
|
* @{
|
|
* Default values for various variables are defined here.
|
|
*/
|
|
|
|
/**
|
|
* Default space characters.
|
|
*/
|
|
define('ADVAGG_SPACE', '__');
|
|
|
|
/**
|
|
* Default value to see if advanced CSS/JS aggregation is enabled.
|
|
*/
|
|
define('ADVAGG_ENABLED', TRUE);
|
|
|
|
/**
|
|
* Default value to see if .gz files should be created as well.
|
|
*/
|
|
define('ADVAGG_GZIP', TRUE);
|
|
|
|
/**
|
|
* Default value to see we use core's default grouping of CSS/JS files.
|
|
*/
|
|
if (module_exists('advagg_bundler') && variable_get('advagg_bundler_active', TRUE)) {
|
|
define('ADVAGG_CORE_GROUPS', FALSE);
|
|
}
|
|
else {
|
|
define('ADVAGG_CORE_GROUPS', TRUE);
|
|
}
|
|
|
|
/**
|
|
* Default value to see if we cache the full CSS/JS structure.
|
|
*/
|
|
define('ADVAGG_CACHE_LEVEL', 1);
|
|
|
|
/**
|
|
* Default value of counter.
|
|
*/
|
|
define('ADVAGG_GLOBAL_COUNTER', 0);
|
|
|
|
/**
|
|
* Send non blocking requests in order to generate aggregated files via HTTPRL.
|
|
*/
|
|
define('ADVAGG_USE_HTTPRL', FALSE);
|
|
|
|
/**
|
|
* Combine css files by using media queries instead of media attributes.
|
|
*/
|
|
define('ADVAGG_COMBINE_CSS_MEDIA', FALSE);
|
|
|
|
/**
|
|
* Prevent more than 4095 css selector rules inside of a CSS aggregate.
|
|
*/
|
|
define('ADVAGG_IE_CSS_SELECTOR_LIMITER', FALSE);
|
|
|
|
/**
|
|
* The value the IE css selector should use.
|
|
*/
|
|
define('ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE', 4095);
|
|
|
|
/**
|
|
* Default location of AdvAgg configuration items.
|
|
*/
|
|
define('ADVAGG_ADMIN_CONFIG_ROOT_PATH', 'admin/config/development/performance');
|
|
|
|
/**
|
|
* Default value for debugging info to watchdog.
|
|
*/
|
|
define('ADVAGG_DEBUG', FALSE);
|
|
|
|
/**
|
|
* Scan and fix any JS that was added with the wrong type.
|
|
*/
|
|
define('ADVAGG_JS_FIX_TYPE', TRUE);
|
|
|
|
/**
|
|
* Scan and fix any CSS that was added with the wrong type.
|
|
*/
|
|
define('ADVAGG_CSS_FIX_TYPE', TRUE);
|
|
|
|
/**
|
|
* Generate a .htaccess file in the AdvAgg dirs.
|
|
*/
|
|
define('ADVAGG_HTACCESS_CHECK_GENERATE', TRUE);
|
|
|
|
/**
|
|
* Display a message that the bypass cookie is set.
|
|
*/
|
|
define('ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE', TRUE);
|
|
|
|
/**
|
|
* Run advagg_url_inbound_alter().
|
|
*/
|
|
define('ADVAGG_URL_INBOUND_ALTER', TRUE);
|
|
|
|
/**
|
|
* Allow JavaScript insertion into any scope even if theme does not support it.
|
|
*/
|
|
define('ADVAGG_SCRIPTS_SCOPE_ANYWHERE', FALSE);
|
|
|
|
/**
|
|
* Empty the scripts key inside of template_process_html replacement function.
|
|
*/
|
|
define('ADVAGG_CLEAR_SCRIPTS', TRUE);
|
|
|
|
/**
|
|
* TRUE when db table 'advagg_aggregates_versions' is missing in advagg_enable.
|
|
*/
|
|
define('ADVAGG_NEEDS_UPDATE', FALSE);
|
|
|
|
/**
|
|
* How long to wait until advagg cron will run again. Default is 23 hours.
|
|
*/
|
|
define('ADVAGG_CRON_FREQUENCY', 82800);
|
|
|
|
/**
|
|
* How long to wait until unaccessed aggregates are removed from the database.
|
|
*/
|
|
define('ADVAGG_REMOVE_MISSING_FILES_FROM_DB_TIME', 1209600);
|
|
|
|
/**
|
|
* How long to wait until unaccessed aggregates are removed from the database.
|
|
*/
|
|
define('ADVAGG_REMOVE_OLD_UNUSED_AGGREGATES_TIME', 3888000);
|
|
|
|
/**
|
|
* Run advagg* js_alter and css_alter after all others including theme hooks.
|
|
*/
|
|
define('ADVAGG_RUN_ALTER_AFTER_THEME', TRUE);
|
|
|
|
/**
|
|
* Do more file operations in main thread if the file system is fast.
|
|
*
|
|
* If AdvAgg's directories are mounted on something like S3, you might want to
|
|
* set this to FALSE.
|
|
*/
|
|
define('ADVAGG_FAST_FILESYSTEM', TRUE);
|
|
|
|
/**
|
|
* Pregenerate aggregate files.
|
|
*
|
|
* If disable the browser requesting the file will cause the generation to
|
|
* happen. If advagg 404 handling is broken then setting this to false will
|
|
* break your site in bad ways.
|
|
*/
|
|
define('ADVAGG_PREGENERATE_AGGREGATE_FILES', TRUE);
|
|
|
|
/**
|
|
* Include the base_url global as part of the hooks hash array.
|
|
*/
|
|
define('ADVAGG_INCLUDE_BASE_URL', FALSE);
|
|
|
|
/**
|
|
* Convert absolute path CSS/JS src/url() to be relative if self referencing.
|
|
*/
|
|
define('ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH', TRUE);
|
|
|
|
/**
|
|
* Convert absolute path CSS/JS src/url() to be protocol relative.
|
|
*/
|
|
define('ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH', TRUE);
|
|
|
|
/**
|
|
* Convert absolute path CSS/JS src/url() to be https.
|
|
*/
|
|
define('ADVAGG_FORCE_HTTPS_PATH', FALSE);
|
|
|
|
/**
|
|
* Convert relative path CSS inside src() to be absolute.
|
|
*/
|
|
define('ADVAGG_CSS_ABSOLUTE_PATH', FALSE);
|
|
|
|
/**
|
|
* If TRUE then the css is being rendered via javascript.
|
|
*/
|
|
define('ADVAGG_CSS_IN_JS', FALSE);
|
|
|
|
/**
|
|
* Set to FALSE to not alter the CSS/JS pushed out from AdvAgg.
|
|
*/
|
|
define('ADVAGG_AJAX_RENDER_ALTER', TRUE);
|
|
|
|
/**
|
|
* Workaround for 401 errors.
|
|
*/
|
|
define('ADVAGG_AUTH_BASIC_USER', '');
|
|
|
|
/**
|
|
* Workaround for 401 errors.
|
|
*/
|
|
define('ADVAGG_AUTH_BASIC_PASS', '');
|
|
|
|
/**
|
|
* Skip far future check on status page.
|
|
*/
|
|
define('ADVAGG_SKIP_FAR_FUTURE_CHECK', FALSE);
|
|
|
|
/**
|
|
* Skip preprocess check on status page.
|
|
*/
|
|
define('ADVAGG_SKIP_ENABLED_PREPROCESS_CHECK', FALSE);
|
|
|
|
/**
|
|
* Prefetch External domains for CSS/JS.
|
|
*/
|
|
define('ADVAGG_RESOURCE_HINTS_DNS_PREFETCH', FALSE);
|
|
|
|
/**
|
|
* Preconnect External domains for CSS/JS.
|
|
*/
|
|
define('ADVAGG_RESOURCE_HINTS_PRECONNECT', FALSE);
|
|
|
|
/**
|
|
* Preload CSS/JS and sub requests.
|
|
*/
|
|
define('ADVAGG_RESOURCE_HINTS_PRELOAD', FALSE);
|
|
|
|
/**
|
|
* Location of CSS/JS and sub requests resource hints.
|
|
*/
|
|
define('ADVAGG_RESOURCE_HINTS_LOCATION', 1);
|
|
|
|
/**
|
|
* Function to use when converting a non scalar to a string.
|
|
*/
|
|
define('ADVAGG_SERIALIZE', 'json_encode');
|
|
|
|
/**
|
|
* Default root dir for the advagg files; controls advagg_get_root_files_dir().
|
|
*/
|
|
define('ADVAGG_ROOT_DIR_PREFIX', 'public://');
|
|
|
|
/**
|
|
* Skip gzip check on status page.
|
|
*/
|
|
define('ADVAGG_SKIP_GZIP_CHECK', FALSE);
|
|
|
|
/**
|
|
* If true do not call file_create_url() for url() inside css files.
|
|
*/
|
|
if (module_exists('cdn')) {
|
|
define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', FALSE);
|
|
}
|
|
else {
|
|
define('ADVAGG_SKIP_FILE_CREATE_URL_INSIDE_CSS', TRUE);
|
|
}
|
|
|
|
/**
|
|
* Display a message that the a CSS/JS file has changed.
|
|
*/
|
|
define('ADVAGG_SHOW_FILE_CHANGED_MESSAGE', TRUE);
|
|
|
|
/**
|
|
* How long to wait until an aggregate with a missing file is written to disk.
|
|
*/
|
|
define('ADVAGG_FILE_READ_FAILURE_TIMEOUT', 1800);
|
|
|
|
/**
|
|
* See if the mtime != if TRUE < if FALSE.
|
|
*/
|
|
define('ADVAGG_STRICT_MTIME_CHECK', TRUE);
|
|
|
|
/**
|
|
* Default value to see if .br files should be created as well.
|
|
*/
|
|
if (function_exists('brotli_compress')) {
|
|
define('ADVAGG_BROTLI', TRUE);
|
|
}
|
|
else {
|
|
define('ADVAGG_BROTLI', FALSE);
|
|
}
|
|
|
|
/**
|
|
* Default value to see zopfli_encode should not be used.
|
|
*/
|
|
define('ADVAGG_NO_ZOPFLI', FALSE);
|
|
|
|
/**
|
|
* If true do test for 304 files on the status report page.
|
|
*/
|
|
define('ADVAGG_SKIP_304_CHECK', FALSE);
|
|
|
|
/**
|
|
* If false do not set the immutable header.
|
|
*/
|
|
define('ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE', TRUE);
|
|
|
|
/**
|
|
* If true chrome=1 will be added to the X-UA-Compatible header.
|
|
*/
|
|
define('ADVAGG_CHROME_HEADER_ENABLED', FALSE);
|
|
|
|
/**
|
|
* If true advagg htaccess Options uses SymLinksIfOwnerMatch vs FollowSymLinks.
|
|
*/
|
|
define('ADVAGG_HTACCESS_SYMLINKSIFOWNERMATCH', FALSE);
|
|
|
|
/**
|
|
* How far down a JS file to look for use strict.
|
|
*/
|
|
define('ADVAGG_JS_HEADER_LENGTH', 24576);
|
|
|
|
/**
|
|
* If not empty advagg htaccess will include the given rewritebase.
|
|
*/
|
|
define('ADVAGG_HTACCESS_REWRITEBASE', '');
|
|
|
|
/**
|
|
* Preload CSS/JS header limit 3kb.
|
|
*/
|
|
define('ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE', 3072);
|
|
|
|
/**
|
|
* If TRUE advagg will search for and remove empty CSS files from aggregates.
|
|
*/
|
|
define('ADVAGG_CSS_REMOVE_EMPTY_FILES', FALSE);
|
|
|
|
/**
|
|
* If TRUE advagg will search for and remove empty JS files from aggregates.
|
|
*/
|
|
define('ADVAGG_JS_REMOVE_EMPTY_FILES', FALSE);
|
|
|
|
/**
|
|
* If TRUE advagg will be disabled on admin pages.
|
|
*/
|
|
define('ADVAGG_DISABLE_ON_ADMIN', FALSE);
|
|
|
|
/**
|
|
* Default is 200; 203 has been requested in the past.
|
|
*/
|
|
define('ADVAGG_HTTP_200_CODE', 200);
|
|
|
|
/**
|
|
* Verify all 3 hashes from the filename, if TRUE only verify 1st hash.
|
|
*/
|
|
define('ADVAGG_WEAK_FILE_VERIFICATION', FALSE);
|
|
|
|
/**
|
|
* If FALSE lock_acquire is used before writing a file.
|
|
*/
|
|
define('ADVAGG_NO_LOCKS', FALSE);
|
|
|
|
/**
|
|
* If TRUE drupal_get_html_head will be rendered at the top of the css section.
|
|
*/
|
|
define('ADVAGG_HTML_HEAD_IN_CSS_LOCATION', FALSE);
|
|
|
|
/**
|
|
* If 4 the admin section gets unlocked.
|
|
*/
|
|
define('ADVAGG_ADMIN_MODE', 4);
|
|
|
|
/**
|
|
* If TRUE farfuture headers will go out if the file is delivered by php.
|
|
*/
|
|
define('ADVAGG_FARFUTURE_PHP', FALSE);
|
|
|
|
/**
|
|
* Internal urls set here will have advagg disabled on those pages.
|
|
*/
|
|
define('ADVAGG_DISABLE_ON_LISTED_PAGES', '');
|
|
|
|
/**
|
|
* @} End of "defgroup default_variables".
|
|
*/
|
|
|
|
/**
|
|
* @addtogroup hooks
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* Implements hook_help().
|
|
*/
|
|
function advagg_help($path, $arg) {
|
|
switch ($path) {
|
|
case 'admin/help#advagg':
|
|
$filepath = dirname(__FILE__) . '/README.txt';
|
|
if (file_exists($filepath)) {
|
|
$readme = file_get_contents($filepath);
|
|
}
|
|
if (!isset($readme)) {
|
|
return NULL;
|
|
}
|
|
if (module_exists('markdown')) {
|
|
$filters = module_invoke('markdown', 'filter_info');
|
|
$info = $filters['filter_markdown'];
|
|
|
|
if (function_exists($info['process callback'])) {
|
|
$output = $info['process callback']($readme, NULL);
|
|
}
|
|
else {
|
|
$output = '<pre>' . $readme . '</pre>';
|
|
}
|
|
}
|
|
else {
|
|
$output = '<pre>' . $readme . '</pre>';
|
|
}
|
|
return $output;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_block_view_alter().
|
|
*/
|
|
function advagg_block_view_alter(&$data, $block) {
|
|
// Do not run hook if AdvAgg is disabled.
|
|
if (!advagg_enabled()) {
|
|
return;
|
|
}
|
|
// Do not run hook if setting is disabled.
|
|
if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
|
|
return;
|
|
}
|
|
|
|
if (empty($data) || empty($data['content'])) {
|
|
return;
|
|
}
|
|
|
|
$block_info = $block->module . ':' . $block->delta;
|
|
$prefix = "<!-- AdvAgg block:prefix:$block_info tag -->";
|
|
$suffix = "<!-- AdvAgg block:suffix:$block_info tag -->";
|
|
if (is_string($data['content'])) {
|
|
$data['content'] = $prefix . $data['content'] . $suffix;
|
|
}
|
|
else {
|
|
if (!isset($data['content']['#prefix'])) {
|
|
$data['content']['#prefix'] = '';
|
|
}
|
|
$data['content']['#prefix'] .= $prefix;
|
|
if (!isset($data['content']['#suffix'])) {
|
|
$data['content']['#suffix'] = '';
|
|
}
|
|
$data['content']['#suffix'] .= $suffix;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_views_pre_render().
|
|
*/
|
|
function advagg_views_pre_render(&$view) {
|
|
// Do not run hook if AdvAgg is disabled.
|
|
if (!advagg_enabled()) {
|
|
return;
|
|
}
|
|
// Do not run hook if setting is disabled.
|
|
if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
|
|
return;
|
|
}
|
|
|
|
$info = "{$view->name}:{$view->current_display}";
|
|
$prefix = "<!-- AdvAgg view:prefix:$info tag -->";
|
|
$suffix = "<!-- AdvAgg view:suffix:$info tag -->";
|
|
if (!isset($view->attachment_before)) {
|
|
$view->attachment_before = '';
|
|
}
|
|
$view->attachment_before .= $prefix;
|
|
if (!isset($view->attachment_after)) {
|
|
$view->attachment_after = '';
|
|
}
|
|
$view->attachment_after .= $suffix;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_panels_pre_render().
|
|
*/
|
|
function advagg_panels_pre_render($panels_display, &$renderer) {
|
|
// Do not run hook if AdvAgg is disabled.
|
|
if (!advagg_enabled()) {
|
|
return;
|
|
}
|
|
// Do not run hook if setting is disabled.
|
|
if (!variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
|
|
return;
|
|
}
|
|
|
|
$info = "{$panels_display->layout}:{$panels_display->css_id}";
|
|
$prefix = "<!-- AdvAgg panels:prefix:$info tag -->";
|
|
$suffix = "<!-- AdvAgg panels:suffix:$info tag -->";
|
|
if (!isset($renderer->prefix)) {
|
|
$renderer->prefix = '';
|
|
}
|
|
$renderer->prefix .= $prefix;
|
|
if (!isset($renderer->suffix)) {
|
|
$renderer->suffix = '';
|
|
}
|
|
$renderer->suffix .= $suffix;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_url_inbound_alter().
|
|
*
|
|
* Inbound URL rewrite helper. If host includes subdomain, rewrite URI and
|
|
* internal path if necessary.
|
|
*/
|
|
function advagg_url_inbound_alter(&$path, $original_path, $path_language) {
|
|
// Do nothing if this has been disabled.
|
|
if (!variable_get('advagg_url_inbound_alter', ADVAGG_URL_INBOUND_ALTER)) {
|
|
return;
|
|
}
|
|
|
|
// Setup static so we only need to run the logic once.
|
|
$already_ran = &drupal_static(__FUNCTION__);
|
|
if (!isset($already_ran)) {
|
|
$already_ran = array();
|
|
}
|
|
$request_path = request_path();
|
|
|
|
// Set the path again if we already did this alter.
|
|
if (array_key_exists($request_path, $already_ran)) {
|
|
$path = $already_ran[$request_path];
|
|
return;
|
|
}
|
|
|
|
// If requested path was for an advagg file but now it is something else
|
|
// switch is back to the advagg file.
|
|
if (!empty($path)
|
|
&& $path != $request_path
|
|
&& advagg_match_file_pattern($request_path)
|
|
) {
|
|
// Get the advagg paths.
|
|
$advagg_path = advagg_get_root_files_dir();
|
|
|
|
// Get the top level path.
|
|
$top_level = substr($advagg_path[0][1], 0, strpos($advagg_path[0][1], 'advagg_css'));
|
|
|
|
// Only change if it's an exact match.
|
|
$start = strpos($request_path, $top_level . 'advagg_');
|
|
if ($start === 0) {
|
|
// Set path to correct advagg path.
|
|
$path = substr($request_path, $start);
|
|
$already_ran[$request_path] = $path;
|
|
}
|
|
else {
|
|
// Put all languages prefixes into an array.
|
|
$language_list = language_list();
|
|
$prefixes = array();
|
|
foreach ($language_list as $lang) {
|
|
if ($lang->enabled && !empty($lang->prefix) && strpos($request_path, $lang->prefix) !== FALSE) {
|
|
$prefixes[$lang->prefix] = $lang->prefix;
|
|
}
|
|
}
|
|
if (!empty($prefixes)) {
|
|
// Remove all enabled languages prefixes from the beginning of the path.
|
|
$substr_to_shrink = substr($request_path, 0, $start);
|
|
foreach ($prefixes as $prefix) {
|
|
$substr_to_shrink = str_replace($prefix . '/', '', $substr_to_shrink);
|
|
}
|
|
// Set path to correct advagg path.
|
|
$path = $substr_to_shrink . substr($request_path, $start);
|
|
$already_ran[$request_path] = $path;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_hook_info().
|
|
*/
|
|
function advagg_hook_info() {
|
|
// List of hooks that can be inside of *.advagg.inc files.
|
|
// All advagg hooks except for:
|
|
// advagg_current_hooks_hash_array_alter
|
|
// advagg_hooks_implemented_alter
|
|
// advagg_get_root_files_dir_alter
|
|
// because these 3 hooks are used on most requests.
|
|
$advagg_hooks = array(
|
|
'advagg_get_css_file_contents_pre_alter',
|
|
'advagg_get_css_file_contents_alter',
|
|
'advagg_get_js_file_contents_alter',
|
|
'advagg_get_css_aggregate_contents_alter',
|
|
'advagg_get_js_aggregate_contents_alter',
|
|
'advagg_save_aggregate_pre_alter',
|
|
'advagg_save_aggregate_alter',
|
|
'advagg_build_aggregate_plans_alter',
|
|
'advagg_build_aggregate_plans_post_alter',
|
|
'advagg_css_groups_alter',
|
|
'advagg_js_groups_alter',
|
|
'advagg_modify_css_pre_render_alter',
|
|
'advagg_modify_js_pre_render_alter',
|
|
'advagg_changed_files',
|
|
'advagg_removed_aggregates',
|
|
'advagg_scan_for_changes',
|
|
'advagg_get_info_on_files_alter',
|
|
'advagg_context_alter',
|
|
'advagg_missing_root_file',
|
|
);
|
|
$hooks = array();
|
|
foreach ($advagg_hooks as $hook) {
|
|
$hooks[$hook] = array('group' => 'advagg');
|
|
}
|
|
return $hooks;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_module_implements_alter().
|
|
*/
|
|
function advagg_module_implements_alter(&$implementations, $hook) {
|
|
// Move advagg_theme_registry_alter to the top.
|
|
if ($hook === 'theme_registry_alter' && array_key_exists('advagg', $implementations)) {
|
|
$item = array('advagg' => $implementations['advagg']);
|
|
unset($implementations['advagg']);
|
|
$implementations = array_merge($item, $implementations);
|
|
}
|
|
|
|
// Move advagg_ajax_render_alter to the top.
|
|
if ($hook === 'ajax_render_alter' && array_key_exists('advagg', $implementations)) {
|
|
$item = array('advagg' => $implementations['advagg']);
|
|
unset($implementations['advagg']);
|
|
$implementations = array_merge($item, $implementations);
|
|
}
|
|
|
|
// Move advagg_element_info_alter to the bottom.
|
|
if ($hook === 'element_info_alter' && array_key_exists('advagg', $implementations)) {
|
|
$item = $implementations['advagg'];
|
|
unset($implementations['advagg']);
|
|
$implementations['advagg'] = $item;
|
|
}
|
|
|
|
// Replace locale_js_alter with _advagg_locale_js_alter.
|
|
if ($hook === 'js_alter' && array_key_exists('locale', $implementations)) {
|
|
unset($implementations['locale']);
|
|
$implementations['_advagg_locale'] = FALSE;
|
|
}
|
|
|
|
// Move advagg_file_url_alter to the bottom.
|
|
if ($hook === 'file_url_alter' && array_key_exists('advagg', $implementations)) {
|
|
$item = $implementations['advagg'];
|
|
unset($implementations['advagg']);
|
|
$implementations['advagg'] = $item;
|
|
}
|
|
|
|
if ($hook === 'requirements') {
|
|
// Move advagg_requirements to the bottom.
|
|
if (array_key_exists('advagg', $implementations)) {
|
|
$item = $implementations['advagg'];
|
|
unset($implementations['advagg']);
|
|
$implementations['advagg'] = $item;
|
|
}
|
|
// Move advagg_css_cdn to the bottom.
|
|
if (array_key_exists('advagg_css_cdn', $implementations)) {
|
|
$item = $implementations['advagg_css_cdn'];
|
|
unset($implementations['advagg_css_cdn']);
|
|
$implementations['advagg_css_cdn'] = $item;
|
|
}
|
|
// Move advagg_css_compress to the bottom.
|
|
if (array_key_exists('advagg_css_compress', $implementations)) {
|
|
$item = $implementations['advagg_css_compress'];
|
|
unset($implementations['advagg_css_compress']);
|
|
$implementations['advagg_css_compress'] = $item;
|
|
}
|
|
// Move advagg_js_cdn to the bottom.
|
|
if (array_key_exists('advagg_js_cdn', $implementations)) {
|
|
$item = $implementations['advagg_js_cdn'];
|
|
unset($implementations['advagg_js_cdn']);
|
|
$implementations['advagg_js_cdn'] = $item;
|
|
}
|
|
// Move advagg_js_compress to the bottom.
|
|
if (array_key_exists('advagg_js_compress', $implementations)) {
|
|
$item = $implementations['advagg_js_compress'];
|
|
unset($implementations['advagg_js_compress']);
|
|
$implementations['advagg_js_compress'] = $item;
|
|
}
|
|
}
|
|
|
|
// Move advagg_cron to the bottom.
|
|
if ($hook === 'cron' && array_key_exists('advagg', $implementations)) {
|
|
$item = $implementations['advagg'];
|
|
unset($implementations['advagg']);
|
|
$implementations['advagg'] = $item;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_js_alter().
|
|
*
|
|
* This is a locking wrapper for locale_js_alter().
|
|
*/
|
|
function _advagg_locale_js_alter(&$js) {
|
|
// If the variable is empty then get the latest variable from the database.
|
|
$name = 'javascript_parsed';
|
|
$parsed = variable_get($name, array());
|
|
if (empty($parsed)) {
|
|
$variables = array_map('unserialize', db_query('SELECT name, value FROM {variable} WHERE name = :name', array(':name' => $name))->fetchAllKeyed());
|
|
if (!empty($variables[$name])) {
|
|
$GLOBALS['conf'][$name] = $variables[$name];
|
|
}
|
|
}
|
|
|
|
// See if locale_js_alter() needs to do anything.
|
|
$dir = 'public://' . variable_get('locale_js_directory', 'languages');
|
|
$new_files = FALSE;
|
|
// See if a rebuild of the translation file for the current language is
|
|
// needed.
|
|
if (!empty($parsed['refresh:' . $GLOBALS['language']->language])) {
|
|
$new_files = TRUE;
|
|
}
|
|
// Check for new js source files.
|
|
if (empty($new_files)) {
|
|
foreach ($js as $item) {
|
|
if ($item['type'] === 'file'
|
|
&& !in_array($item['data'], $parsed)
|
|
&& substr($item['data'], 0, strlen($dir)) != $dir
|
|
) {
|
|
$new_files = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (empty($new_files)) {
|
|
// No new files to manage, just add in available i18n files.
|
|
advagg_locale_js_add_translations($js, $dir);
|
|
// Exit function.
|
|
return;
|
|
}
|
|
|
|
$count = 0;
|
|
while (!lock_acquire('locale_js_alter', 10)) {
|
|
++$count;
|
|
// If we've waited over 3 times then skip.
|
|
if ($count > 3) {
|
|
lock_release('locale_js_alter');
|
|
// Add in available i18n files.
|
|
advagg_locale_js_add_translations($js, $dir);
|
|
|
|
// Disable saving to the cache as translations might be missing.
|
|
drupal_page_is_cacheable(FALSE);
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) > 1) {
|
|
$GLOBALS['conf']['advagg_cache_level'] = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Wait for the lock to be available.
|
|
lock_wait('locale_js_alter');
|
|
}
|
|
|
|
try {
|
|
// Run the alter.
|
|
locale_js_alter($js);
|
|
}
|
|
catch (PDOException $e) {
|
|
// If it fails we don't care, javascript_parsed is either already written or
|
|
// it will happen again on the next request.
|
|
// Still log it if in development mode.
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
|
|
watchdog('advagg', 'Development Mode - Caught PDO Exception: <code>@info</code>', array('@info' => $e));
|
|
}
|
|
}
|
|
lock_release('locale_js_alter');
|
|
}
|
|
|
|
/**
|
|
* Implements hook_system_info_alter().
|
|
*/
|
|
function advagg_system_info_alter(&$info, $file, $type) {
|
|
$config_path = &drupal_static(__FUNCTION__);
|
|
// Get advagg config path.
|
|
if (empty($config_path)) {
|
|
$config_path = advagg_admin_config_root_path();
|
|
}
|
|
|
|
// Replace advagg path.
|
|
if (!empty($info['configure'])
|
|
&& strpos($info['configure'], '/advagg') !== FALSE
|
|
&& ((!empty($info['dependencies'])
|
|
&& is_array($info['dependencies'])
|
|
&& in_array('advagg', $info['dependencies'])
|
|
) || $file->name === 'advagg')
|
|
) {
|
|
$pos = strpos($info['configure'], '/advagg') + 7;
|
|
$substr = substr($info['configure'], 0, $pos);
|
|
$info['configure'] = str_replace($substr, $config_path . '/advagg', $info['configure']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_permission().
|
|
*/
|
|
function advagg_permission() {
|
|
return array(
|
|
'bypass advanced aggregation' => array(
|
|
'title' => t('bypass advanced aggregation'),
|
|
'description' => t('User can use URL query strings to bypass AdvAgg.'),
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_file_url_alter().
|
|
*/
|
|
function advagg_file_url_alter(&$original_uri) {
|
|
// Do nothing if URI does not contain /advagg_
|
|
// OR file does not have the correct pattern.
|
|
if (strpos($original_uri, '/advagg_') === FALSE || !advagg_match_file_pattern($original_uri)) {
|
|
return;
|
|
}
|
|
|
|
// CDN fix.
|
|
// Do nothing if
|
|
// in maintenance_mode
|
|
// CDN module does not exist
|
|
// CDN far future is disabled
|
|
// CDN mode is not basic
|
|
// URI does not contain cdn/farfuture/.
|
|
if (variable_get('maintenance_mode', FALSE)
|
|
|| !module_exists('cdn')
|
|
|| !variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT)
|
|
|| variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC) != CDN_MODE_BASIC
|
|
|| strpos($original_uri, 'cdn/farfuture/') === FALSE
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Remove cdn/farfuture/BASE64/prefix:value/ from the URI.
|
|
$original_uri = preg_replace('/cdn\/farfuture\/[A-Za-z0-9-_]{43}\/[A-Za-z]+\:[A-Za-z0-9-_]+\//', '', $original_uri);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_menu().
|
|
*/
|
|
function advagg_menu() {
|
|
list($css_path, $js_path) = advagg_get_root_files_dir();
|
|
$file_path = drupal_get_path('module', 'advagg');
|
|
$config_path = advagg_admin_config_root_path();
|
|
|
|
$path_defined = FALSE;
|
|
if (advagg_s3fs_evaluate_no_rewrite_cssjs(FALSE)) {
|
|
$external_css = trim(parse_url(str_replace('/test.css', '/%', file_create_url($css_path[0] . '/test.css')), PHP_URL_PATH));
|
|
if (strpos($external_css, $GLOBALS['base_path']) === 0) {
|
|
$external_css = substr($external_css, strlen($GLOBALS['base_path']));
|
|
}
|
|
$external_js = trim(parse_url(str_replace('/test.js', '/%', file_create_url($js_path[0] . '/test.js')), PHP_URL_PATH));
|
|
if (strpos($external_js, $GLOBALS['base_path']) === 0) {
|
|
$external_js = substr($external_js, strlen($GLOBALS['base_path']));
|
|
}
|
|
$items[$external_css] = array(
|
|
'title' => "Generate CSS Aggregate",
|
|
'page callback' => 'advagg_missing_aggregate',
|
|
'type' => MENU_CALLBACK,
|
|
// Allow anyone to access these public css files.
|
|
'access callback' => TRUE,
|
|
'file path' => $file_path,
|
|
'file' => 'advagg.missing.inc',
|
|
);
|
|
$items[$external_js] = array(
|
|
'title' => "Generate JS Aggregate",
|
|
'page callback' => 'advagg_missing_aggregate',
|
|
'type' => MENU_CALLBACK,
|
|
// Allow anyone to access these public js files.
|
|
'access callback' => TRUE,
|
|
'file path' => $file_path,
|
|
'file' => 'advagg.missing.inc',
|
|
);
|
|
$path_defined = TRUE;
|
|
}
|
|
|
|
if (!$path_defined) {
|
|
$items[$css_path[1] . '/%'] = array(
|
|
'title' => "Generate CSS Aggregate",
|
|
'page callback' => 'advagg_missing_aggregate',
|
|
'type' => MENU_CALLBACK,
|
|
// Allow anyone to access these public css files.
|
|
'access callback' => TRUE,
|
|
'file path' => $file_path,
|
|
'file' => 'advagg.missing.inc',
|
|
);
|
|
$items[$js_path[1] . '/%'] = array(
|
|
'title' => "Generate JS Aggregate",
|
|
'page callback' => 'advagg_missing_aggregate',
|
|
'type' => MENU_CALLBACK,
|
|
// Allow anyone to access these public js files.
|
|
'access callback' => TRUE,
|
|
'file path' => $file_path,
|
|
'file' => 'advagg.missing.inc',
|
|
);
|
|
}
|
|
|
|
// If mutiple paths are symlinked to the same location; allow advagg to handle
|
|
// those addtional locations.
|
|
$advagg_additional_generate_paths = variable_get('advagg_additional_generate_paths', array());
|
|
if (!empty($advagg_additional_generate_paths)) {
|
|
foreach ($advagg_additional_generate_paths as $path) {
|
|
$items[$path] = array(
|
|
'title' => "Generate CSS/JS Aggregate",
|
|
'page callback' => 'advagg_missing_aggregate',
|
|
'type' => MENU_CALLBACK,
|
|
// Allow anyone to access these public css files.
|
|
'access callback' => TRUE,
|
|
'file path' => $file_path,
|
|
'file' => 'advagg.missing.inc',
|
|
);
|
|
}
|
|
}
|
|
|
|
$items[$config_path . '/default'] = array(
|
|
'title' => 'Performance',
|
|
'type' => MENU_DEFAULT_LOCAL_TASK,
|
|
'file path' => drupal_get_path('module', 'system'),
|
|
'weight' => -10,
|
|
);
|
|
$items[$config_path . '/advagg'] = array(
|
|
'title' => 'Advanced CSS/JS Aggregation',
|
|
'description' => 'Configuration for Advanced CSS/JS Aggregation.',
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('advagg_admin_settings_form'),
|
|
'type' => MENU_LOCAL_TASK,
|
|
'access arguments' => array('administer site configuration'),
|
|
'file path' => $file_path,
|
|
'file' => 'advagg.admin.inc',
|
|
'weight' => 1,
|
|
);
|
|
$items[$config_path . '/advagg/config'] = array(
|
|
'title' => 'Configuration',
|
|
'type' => MENU_DEFAULT_LOCAL_TASK,
|
|
'weight' => -10,
|
|
);
|
|
$items[$config_path . '/advagg/info'] = array(
|
|
'title' => 'Information',
|
|
'description' => 'More detailed information about advagg.',
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('advagg_admin_info_form'),
|
|
'type' => MENU_LOCAL_TASK,
|
|
'access arguments' => array('administer site configuration'),
|
|
'file path' => $file_path,
|
|
'file' => 'advagg.admin.inc',
|
|
'weight' => 18,
|
|
);
|
|
$items[$config_path . '/advagg/operations'] = array(
|
|
'title' => 'Operations',
|
|
'description' => 'Flush caches, set the bypass cookie, take drastic actions.',
|
|
'page callback' => 'drupal_get_form',
|
|
'page arguments' => array('advagg_admin_operations_form'),
|
|
'type' => MENU_LOCAL_TASK,
|
|
'access arguments' => array('administer site configuration'),
|
|
'file path' => $file_path,
|
|
'file' => 'advagg.admin.inc',
|
|
'weight' => 20,
|
|
);
|
|
return $items;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_cron().
|
|
*
|
|
* This will be ran once a day at most.
|
|
*/
|
|
function advagg_cron($bypass_time_check = FALSE) {
|
|
// @param bool $bypass_time_check
|
|
// Set to TRUE to skip the 24 hour check.
|
|
//
|
|
// Execute once a day (24 hours).
|
|
if (!$bypass_time_check && variable_get('advagg_cron_timestamp', 0) > (REQUEST_TIME - variable_get('advagg_cron_frequency', ADVAGG_CRON_FREQUENCY))) {
|
|
return array();
|
|
}
|
|
variable_set('advagg_cron_timestamp', REQUEST_TIME);
|
|
|
|
// Flush the cache_advagg_info cache bin.
|
|
cache_clear_all(NULL, 'cache_advagg_info');
|
|
|
|
$return = array();
|
|
// Clear out all stale advagg aggregated files.
|
|
module_load_include('inc', 'advagg', 'advagg.cache');
|
|
$return[] = advagg_delete_stale_aggregates();
|
|
|
|
// Delete all empty aggregated files.
|
|
$return[] = advagg_delete_empty_aggregates();
|
|
|
|
// Delete orphaned aggregates.
|
|
$return[] = advagg_delete_orphaned_aggregates();
|
|
|
|
// Remove aggregates that include missing files.
|
|
$return[] = advagg_remove_missing_files_from_db();
|
|
|
|
// Remove unused aggregates.
|
|
$return[] = advagg_remove_old_unused_aggregates();
|
|
|
|
// Remove expired locks from the semaphore database table.
|
|
$return[] = advagg_cleanup_semaphore_table();
|
|
|
|
// Remove old temp files.
|
|
$return[] = advagg_remove_temp_files();
|
|
|
|
// Refresh all locale files.
|
|
$return[] = advagg_refresh_all_locale_files();
|
|
|
|
// Update libraries data.
|
|
advagg_get_remote_libraries_versions(TRUE);
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_flush_caches().
|
|
*/
|
|
function advagg_flush_caches($all_bins = FALSE, $push_new_changes = TRUE) {
|
|
// * @param bool $all_bins
|
|
// * TRUE: Get all advagg cache bins.
|
|
// * @param bool $push_new_changes
|
|
// * FALSE: Do not scan for changes.
|
|
//
|
|
// Send back a blank array if aav table doesn't exist.
|
|
if (!db_table_exists('advagg_aggregates_versions')) {
|
|
return array();
|
|
}
|
|
|
|
// Scan for and push new changes.
|
|
module_load_include('inc', 'advagg', 'advagg.cache');
|
|
if ($push_new_changes) {
|
|
advagg_push_new_changes();
|
|
}
|
|
|
|
// Get list of cache bins to clear.
|
|
$bins = array('cache_advagg_aggregates');
|
|
if ($all_bins) {
|
|
$bins[] = 'cache_advagg_info';
|
|
}
|
|
return $bins;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_element_info_alter().
|
|
*/
|
|
function advagg_element_info_alter(&$type) {
|
|
// Replace drupal_pre_render_styles with advagg_pre_render_styles.
|
|
$type['styles']['#items'] = array();
|
|
if (!isset($type['styles']['#pre_render'])) {
|
|
$type['styles']['#pre_render'] = array();
|
|
}
|
|
$key = array_search('drupal_pre_render_styles', $type['styles']['#pre_render']);
|
|
if ($key !== FALSE) {
|
|
$type['styles']['#pre_render'][$key] = 'advagg_pre_render_styles';
|
|
}
|
|
else {
|
|
$type['styles']['#pre_render'][] = 'advagg_pre_render_styles';
|
|
}
|
|
// Allow for other code to easily change the render with alter hooks.
|
|
$type['styles']['#pre_render'][] = 'advagg_modify_css_pre_render';
|
|
$type['styles']['#group_callback'] = 'drupal_group_css';
|
|
// Swap in our own aggregation callback.
|
|
$type['styles']['#aggregate_callback'] = '_advagg_aggregate_css';
|
|
$type['styles']['#type'] = 'styles';
|
|
|
|
// Replace drupal_pre_render_scripts with advagg_pre_render_scripts.
|
|
$type['scripts']['#items'] = array();
|
|
if (!isset($type['scripts']['#pre_render'])) {
|
|
$type['scripts']['#pre_render'] = array();
|
|
}
|
|
$key_drupal = array_search('drupal_pre_render_scripts', $type['scripts']['#pre_render']);
|
|
$key_omega = array_search('omega_pre_render_scripts', $type['scripts']['#pre_render']);
|
|
$key_aurora = array_search('aurora_pre_render_scripts', $type['scripts']['#pre_render']);
|
|
if ($key_drupal !== FALSE) {
|
|
$type['scripts']['#pre_render'][$key_drupal] = 'advagg_pre_render_scripts';
|
|
}
|
|
elseif ($key_omega !== FALSE) {
|
|
$type['scripts']['#pre_render'][$key_omega] = 'advagg_pre_render_scripts';
|
|
}
|
|
elseif ($key_aurora !== FALSE) {
|
|
$type['scripts']['#pre_render'][$key_aurora] = 'advagg_pre_render_scripts';
|
|
}
|
|
else {
|
|
$type['scripts']['#pre_render'][] = 'advagg_pre_render_scripts';
|
|
}
|
|
// Allow for other code to easily change the render with alter hooks.
|
|
$type['scripts']['#pre_render'][] = 'advagg_modify_js_pre_render';
|
|
$type['scripts']['#group_callback'] = 'advagg_group_js';
|
|
// Swap in our own aggregation callback.
|
|
$type['scripts']['#aggregate_callback'] = '_advagg_aggregate_js';
|
|
$type['scripts']['#type'] = 'scripts';
|
|
|
|
// Copy html_tag to html_script_tag.
|
|
$type['html_script_tag'] = $type['html_tag'];
|
|
$type['html_script_tag']['#theme'] = 'html_script_tag';
|
|
$type['html_script_tag']['#type'] = 'html_script_tag';
|
|
}
|
|
|
|
/**
|
|
* Implements hook_theme_registry_alter().
|
|
*
|
|
* Replace template_process_html with _advagg_process_html.
|
|
*/
|
|
function advagg_theme_registry_alter(&$theme_registry) {
|
|
if (!isset($theme_registry['html'])) {
|
|
return;
|
|
}
|
|
|
|
// Replace core's process function with our own.
|
|
$index = array_search('template_process_html', $theme_registry['html']['process functions']);
|
|
if ($index !== FALSE) {
|
|
$theme_registry['html']['process functions'][$index] = '_advagg_process_html';
|
|
}
|
|
else {
|
|
// Put AdvAgg at the bottom if we can't find the replacement.
|
|
$theme_registry['html']['process functions'][] = '_advagg_process_html';
|
|
}
|
|
|
|
// Copy html_tag to html_script_tag.
|
|
$theme_registry['html_script_tag'] = $theme_registry['html_tag'];
|
|
$theme_registry['html_script_tag']['function'] = 'theme_html_script_tag';
|
|
|
|
// Fix imce_page.
|
|
if (isset($theme_registry['imce_page'])) {
|
|
$advagg_path = drupal_get_path('module', 'advagg');
|
|
$imce_path = drupal_get_path('module', 'imce');
|
|
if (strpos($theme_registry['imce_page']['path'], $imce_path) !== FALSE) {
|
|
$theme_registry['imce_page']['path'] = $advagg_path . '/tpl';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_ajax_render_alter().
|
|
*/
|
|
function advagg_ajax_render_alter(&$commands) {
|
|
// Do not run hook if AdvAgg is disabled.
|
|
if (!advagg_enabled()) {
|
|
return;
|
|
}
|
|
|
|
// Do not run hook if advagg_ajax_render_alter is FALSE.
|
|
if (!variable_get('advagg_ajax_render_alter', ADVAGG_AJAX_RENDER_ALTER)) {
|
|
return;
|
|
}
|
|
|
|
// Conditionally adds the default Drupal/jQuery libraries to the page.
|
|
// @see http://drupal.org/node/1279226
|
|
if (function_exists('drupal_add_js_page_defaults')) {
|
|
drupal_add_js_page_defaults();
|
|
}
|
|
|
|
// Get Core JS.
|
|
list(, $core_scripts_header, $core_scripts_footer, $items, $settings) = advagg_build_ajax_js_css();
|
|
|
|
// Get AdvAgg JS.
|
|
$scripts_header = $scripts_footer = '';
|
|
if (!empty($items['js'])) {
|
|
$scripts_footer_array = advagg_get_js('footer', $items['js'], TRUE);
|
|
// Function advagg_pre_render_scripts() gets called here.
|
|
$scripts_footer = drupal_render($scripts_footer_array);
|
|
$scripts_header_array = advagg_get_js('header', $items['js'], TRUE);
|
|
// Function advagg_pre_render_scripts() gets called here.
|
|
$scripts_header = drupal_render($scripts_header_array);
|
|
}
|
|
|
|
// Remove core JS.
|
|
foreach ($commands as $key => $values) {
|
|
// Skip if not an array or not a command.
|
|
if (!is_array($values) || empty($values['command'])) {
|
|
continue;
|
|
}
|
|
|
|
if ($values['command'] === 'settings'
|
|
&& is_array($values['settings'])
|
|
&& !empty($values['merge'])
|
|
) {
|
|
// Remove JS settings.
|
|
unset($commands[$key]);
|
|
continue;
|
|
}
|
|
if ($values['command'] === 'insert'
|
|
&& is_null($values['settings'])
|
|
&& $values['method'] === 'prepend'
|
|
&& $values['data'] == $core_scripts_header
|
|
) {
|
|
// Remove JS header.
|
|
unset($commands[$key]);
|
|
continue;
|
|
}
|
|
if ($values['command'] === 'insert'
|
|
&& is_null($values['settings'])
|
|
&& $values['method'] === 'append'
|
|
&& $values['data'] == $core_scripts_footer
|
|
) {
|
|
// Remove JS footer.
|
|
unset($commands[$key]);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Add in AdvAgg JS.
|
|
$extra_commands = array();
|
|
if (!empty($scripts_header)) {
|
|
$extra_commands[] = ajax_command_prepend('head', $scripts_header);
|
|
}
|
|
if (!empty($scripts_footer)) {
|
|
$extra_commands[] = ajax_command_append('body', $scripts_footer);
|
|
}
|
|
if (!empty($extra_commands)) {
|
|
$commands = array_merge($extra_commands, $commands);
|
|
}
|
|
if (!empty($settings)) {
|
|
array_unshift($commands, ajax_command_settings(advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($settings['data'], 'is_array'))), TRUE));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_preprocess_page().
|
|
*/
|
|
function advagg_preprocess_page() {
|
|
// Scan for changes to any CSS/JS files if in development mode.
|
|
advagg_scan_filesystem_for_changes_live();
|
|
}
|
|
|
|
/**
|
|
* Implements hook_preprocess_html().
|
|
*
|
|
* Add in rendering IE meta tag if "combine CSS" is enabled.
|
|
*/
|
|
function advagg_preprocess_html() {
|
|
// http://www.phpied.com/conditional-comments-block-downloads/#update
|
|
// Prevent conditional comments from stalling css downloads.
|
|
$fix_blocking_css_ie = array(
|
|
'#weight' => '-999999',
|
|
'#type' => 'markup',
|
|
'#markup' => "<!--[if IE]><![endif]-->\n",
|
|
);
|
|
// Add markup for IE conditional comments to head.
|
|
drupal_add_html_head($fix_blocking_css_ie, 'fix_blocking_css_ie');
|
|
|
|
// Do not force IE rendering mode if "combine CSS" is disabled.
|
|
if (!variable_get('advagg_combine_css_media', ADVAGG_COMBINE_CSS_MEDIA)) {
|
|
return;
|
|
}
|
|
|
|
// Send IE meta tag to force IE rendering mode header.
|
|
$x_ua_compatible = 'IE=edge';
|
|
if (variable_get('advagg_chrome_header_enabled', ADVAGG_CHROME_HEADER_ENABLED)) {
|
|
$x_ua_compatible .= ',chrome=1';
|
|
}
|
|
drupal_add_http_header('X-UA-Compatible', $x_ua_compatible);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter().
|
|
*
|
|
* Give advice on how to temporarily disable css/js aggregation.
|
|
*/
|
|
function advagg_form_system_performance_settings_alter(&$form, &$form_state) {
|
|
module_load_include('admin.inc', 'advagg');
|
|
advagg_admin_system_performance_settings_form($form, $form_state);
|
|
}
|
|
|
|
/**
|
|
* Implements hook_js_alter().
|
|
*/
|
|
function advagg_js_alter(&$js) {
|
|
if (module_exists('admin_menu')) {
|
|
// Fix for admin menu; put JS in footer.
|
|
$path = drupal_get_path('module', 'admin_menu');
|
|
$filename = $path . '/admin_menu.js';
|
|
if (isset($js[$filename])) {
|
|
$js[$filename]['scope'] = 'footer';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @} End of "addtogroup hooks".
|
|
*/
|
|
|
|
/**
|
|
* @defgroup 3rd_party_hooks 3rd party hook implementations
|
|
* @{
|
|
* Hooks that are not apart of core or AdvAgg.
|
|
*/
|
|
|
|
/**
|
|
* Implements hook_cron_alter().
|
|
*/
|
|
function advagg_cron_alter(&$data) {
|
|
// Run this cron job every 2 minutes.
|
|
if (isset($data['advagg_js_compress_cron'])) {
|
|
$data['advagg_js_compress_cron']['rule'] = '*/2 * * * *';
|
|
}
|
|
// Run this cron job every 5 minutes.
|
|
if (isset($data['advagg_relocate_cron'])) {
|
|
$data['advagg_relocate_cron']['rule'] = '*/5 * * * *';
|
|
}
|
|
// Run this cron job every day.
|
|
if (isset($data['advagg_cron'])) {
|
|
$data['advagg_cron']['rule'] = '0 0 * * *';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_password_policy_force_change_allowed_paths_alter().
|
|
*/
|
|
function advagg_password_policy_force_change_allowed_paths_alter(&$allowed_paths) {
|
|
$advagg_items = advagg_menu();
|
|
foreach ($advagg_items as $path => $attributes) {
|
|
if (!empty($attributes['page callback']) && $attributes['page callback'] === 'advagg_missing_aggregate') {
|
|
$allowed_paths[] = str_replace('/%', '/*', $path);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_s3fs_upload_params_alter().
|
|
*
|
|
* Set headers for advagg files.
|
|
*/
|
|
function advagg_s3fs_upload_params_alter(&$upload_params) {
|
|
// Get advagg dir.
|
|
list($css_path, $js_path) = advagg_get_root_files_dir();
|
|
$scheme = file_uri_scheme($css_path[1]);
|
|
if ($scheme) {
|
|
$css_path_dir = parse_url($css_path[1]);
|
|
$css_path_dir = str_replace("$scheme://", '', $css_path[1]);
|
|
}
|
|
else {
|
|
$css_path_dir = ltrim($css_path[1], '/');
|
|
}
|
|
$scheme = file_uri_scheme($js_path[1]);
|
|
if ($scheme) {
|
|
$js_path_dir = parse_url($js_path[1]);
|
|
$js_path_dir = str_replace("$scheme://", '', $js_path_dir[1]);
|
|
}
|
|
else {
|
|
$js_path_dir = ltrim($js_path[1], '/');
|
|
}
|
|
|
|
// Get file type in advagg dir, css or js.
|
|
$type = '';
|
|
if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $css_path_dir) !== FALSE) {
|
|
$type = 'css';
|
|
}
|
|
if (strpos($upload_params['Bucket'] . '/' . $upload_params['Key'], $js_path_dir) !== FALSE) {
|
|
$type = 'js';
|
|
}
|
|
if ($js_path_dir === $css_path_dir && !empty($type)) {
|
|
$pathinfo = pathinfo($upload_params['Key']);
|
|
if ($pathinfo['extension'] === 'gz') {
|
|
$pathinfo = pathinfo($pathinfo['filename']);
|
|
}
|
|
$type = $pathinfo['extension'];
|
|
}
|
|
if (empty($type)) {
|
|
// Only change advagg files.
|
|
return;
|
|
}
|
|
|
|
// Cache control is 52 weeeks.
|
|
if (variable_get('advagg_resource_hints_use_immutable', ADVAGG_RESOURCE_HINTS_USE_IMMUTABLE)) {
|
|
$upload_params['CacheControl'] = 'max-age=31449600, no-transform, public, immutable';
|
|
}
|
|
else {
|
|
$upload_params['CacheControl'] = 'max-age=31449600, no-transform, public';
|
|
}
|
|
// Expires in 365 days.
|
|
$upload_params['Expires'] = gmdate('D, d M Y H:i:s \G\M\T', REQUEST_TIME + 365 * 24 * 60 * 60);
|
|
|
|
// The extension is .css or .js.
|
|
$pathinfo = pathinfo($upload_params['Key']);
|
|
if ($pathinfo['extension'] === $type) {
|
|
if (variable_get('advagg_gzip', ADVAGG_GZIP)) {
|
|
// Set gzip.
|
|
$upload_params['ContentEncoding'] = 'gzip';
|
|
}
|
|
elseif (variable_get('advagg_brotli', ADVAGG_BROTLI)) {
|
|
// Set br.
|
|
$upload_params['ContentEncoding'] = 'br';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return s3fs configuration settings and values.
|
|
*
|
|
* @param string $key
|
|
* A specific key available in the s3fs configuration. NULL by default.
|
|
*
|
|
* @return array|string|null
|
|
* The full s3fs configuration settings, value of a specific key,
|
|
* or NULL if s3fs and the function do not exist.
|
|
*/
|
|
function advagg_get_s3fs_config($key = NULL) {
|
|
if (module_exists('s3fs') && is_callable('_s3fs_get_config')) {
|
|
$s3fs_config = _s3fs_get_config();
|
|
return (empty($key)) ? $s3fs_config : $s3fs_config[$key];
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shortcut to evaluate if s3fs no_rewrite_cssjs is set or empty.
|
|
*
|
|
* If this needs to be accessed in a loop, it is more efficient to call
|
|
* advagg_get_s3fs_config() once from outside of the loop. An example
|
|
* can be seen in the advagg_install_check_via_http function.
|
|
*
|
|
* @param bool $is_set
|
|
* Check if no_write_cssjs field is set (TRUE) or empty (FALSE).
|
|
*
|
|
* @return bool
|
|
* TRUE or FALSE is returned based on evaluating the field. If
|
|
* s3fs_config returns a NULL, evaluate the function to FALSE.
|
|
*
|
|
* @see advagg_get_s3fs_config()
|
|
*/
|
|
function advagg_s3fs_evaluate_no_rewrite_cssjs($is_set = TRUE) {
|
|
$s3fs_no_rewrite_cssjs = advagg_get_s3fs_config('no_rewrite_cssjs');
|
|
if (!is_null($s3fs_no_rewrite_cssjs)) {
|
|
return ($is_set) ? !empty($s3fs_no_rewrite_cssjs) : empty($s3fs_no_rewrite_cssjs);
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_admin_menu_cache_info().
|
|
*
|
|
* Add in a cache flush for advagg.
|
|
*/
|
|
function advagg_admin_menu_cache_info() {
|
|
if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
|
|
$caches['advagg'] = array(
|
|
'title' => t('Adv CSS/JS Agg'),
|
|
'callback' => 'advagg_admin_flush_cache',
|
|
);
|
|
return $caches;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_admin_menu_output_alter().
|
|
*
|
|
* Add in a cache flush for advagg.
|
|
*/
|
|
function advagg_admin_menu_output_alter(array &$content) {
|
|
if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
|
|
// Remove default core aggregation link.
|
|
unset($content['icon']['icon']['flush-cache']['assets']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements hook_anonymous_login_paths_alter().
|
|
*/
|
|
function advagg_anonymous_login_paths_alter(&$paths) {
|
|
// Exclude advagg css/js paths.
|
|
list($css_path, $js_path) = advagg_get_root_files_dir();
|
|
$paths['exclude'][] = $css_path[1] . '/*';
|
|
$paths['exclude'][] = $js_path[1] . '/*';
|
|
}
|
|
|
|
/**
|
|
* Implements hook_pre_flush_all_caches().
|
|
*/
|
|
function advagg_pre_flush_all_caches() {
|
|
static $run_once;
|
|
|
|
if (!isset($run_once)) {
|
|
$run_once = TRUE;
|
|
// Only invoked by registry_rebuild.
|
|
module_load_include('admin.inc', 'advagg');
|
|
// Truncate the advagg_files table.
|
|
advagg_admin_truncate_advagg_files();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @} End of "defgroup 3rd_party_hooks".
|
|
*/
|
|
|
|
/**
|
|
* Only the alter part of locale_js_alter(), not the parsing part.
|
|
*
|
|
* @param array $javascript
|
|
* An array with all JavaScript code. Defaults to the default
|
|
* JavaScript array for the given scope.
|
|
* @param string $dir
|
|
* String pointing to the public locale_js_directory.
|
|
*/
|
|
function advagg_locale_js_add_translations(array &$javascript, $dir) {
|
|
// Add the translation JavaScript file to the page.
|
|
if (!empty($GLOBALS['language']->javascript)) {
|
|
// Add the translation JavaScript file to the page.
|
|
$file = $dir . '/' . $GLOBALS['language']->language . '_' . $GLOBALS['language']->javascript . '.js';
|
|
$javascript[$file] = drupal_js_defaults($file);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback for pre_render so elements can be modified before they are rendered.
|
|
*
|
|
* @param array $elements
|
|
* A render array containing:
|
|
* - #items: The JavaScript items as returned by drupal_add_js() and
|
|
* altered by drupal_get_js().
|
|
* - #group_callback: A function to call to group #items. Following
|
|
* this function, #aggregate_callback is called to aggregate items within
|
|
* the same group into a single file.
|
|
* - #aggregate_callback: A function to call to aggregate the items within
|
|
* the groups arranged by the #group_callback function.
|
|
*
|
|
* @return array
|
|
* A render array that will render to a string of JavaScript tags.
|
|
*/
|
|
function advagg_modify_js_pre_render(array $elements) {
|
|
// Get the children elements.
|
|
$children = array_intersect_key($elements, array_flip(element_children($elements)));
|
|
|
|
// Allow other modules to modify $children and $elements before they are
|
|
// rendered.
|
|
// Call hook_advagg_modify_js_pre_render_alter()
|
|
drupal_alter('advagg_modify_js_pre_render', $children, $elements);
|
|
|
|
// Remove old children elements.
|
|
foreach ($children as $key => $value) {
|
|
if (isset($elements[$key])) {
|
|
unset($elements[$key]);
|
|
}
|
|
}
|
|
// Add in new children elements.
|
|
$elements += $children;
|
|
return $elements;
|
|
}
|
|
|
|
/**
|
|
* Callback for pre_render so elements can be modified before they are rendered.
|
|
*
|
|
* @param array $elements
|
|
* A render array containing:
|
|
* - #items: The CSS items as returned by drupal_add_css() and
|
|
* altered by drupal_get_css().
|
|
* - #group_callback: A function to call to group #items. Following
|
|
* this function, #aggregate_callback is called to aggregate items within
|
|
* the same group into a single file.
|
|
* - #aggregate_callback: A function to call to aggregate the items within
|
|
* the groups arranged by the #group_callback function.
|
|
*
|
|
* @return array
|
|
* A render array that will render to a string of JavaScript tags.
|
|
*/
|
|
function advagg_modify_css_pre_render(array $elements) {
|
|
if (!advagg_enabled()) {
|
|
return $elements;
|
|
}
|
|
|
|
// Put children elements into a reference array.
|
|
$children = array();
|
|
foreach ($elements as $key => &$value) {
|
|
if ($key !== '' && is_string($key) && (0 === strpos($key, '#'))) {
|
|
continue;
|
|
}
|
|
$children[$key] = &$value;
|
|
}
|
|
unset($value);
|
|
|
|
// Allow other modules to modify $children and $elements before they are
|
|
// rendered.
|
|
// Call hook_advagg_modify_css_pre_render_alter()
|
|
drupal_alter('advagg_modify_css_pre_render', $children, $elements);
|
|
return $elements;
|
|
}
|
|
|
|
/**
|
|
* Default callback to aggregate CSS files and inline content.
|
|
*
|
|
* Having the browser load fewer CSS files results in much faster page loads
|
|
* than when it loads many small files. This function aggregates files within
|
|
* the same group into a single file unless the site-wide setting to do so is
|
|
* disabled (commonly the case during site development). To optimize download,
|
|
* it also compresses the aggregate files by removing comments, whitespace, and
|
|
* other unnecessary content. Additionally, this functions aggregates inline
|
|
* content together, regardless of the site-wide aggregation setting.
|
|
*
|
|
* @param array $css_groups
|
|
* An array of CSS groups as returned by drupal_group_css(). This function
|
|
* modifies the group's 'data' property for each group that is aggregated.
|
|
*
|
|
* @see drupal_aggregate_css()
|
|
* @see drupal_group_css()
|
|
* @see drupal_pre_render_styles()
|
|
* @see system_element_info()
|
|
*/
|
|
function _advagg_aggregate_css(array &$css_groups) {
|
|
if (!advagg_enabled()) {
|
|
return drupal_aggregate_css($css_groups);
|
|
}
|
|
if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
|
|
$GLOBALS['_advagg']['debug']['css_groups_before'][] = $css_groups;
|
|
}
|
|
|
|
$preprocess_css = advagg_file_aggregation_enabled('css');
|
|
|
|
// Allow other modules to modify $css_groups right before it is processed.
|
|
// Call hook_advagg_css_groups_alter().
|
|
drupal_alter('advagg_css_groups', $css_groups, $preprocess_css);
|
|
|
|
// For each group that needs aggregation, aggregate its items.
|
|
$files_to_aggregate = array();
|
|
// Allow for inline CSS to be between aggregated files.
|
|
$gap_counter = 0;
|
|
foreach ($css_groups as $key => $group) {
|
|
switch ($group['type']) {
|
|
// If a file group can be aggregated into a single file, do so, and set
|
|
// the group's data property to the file path of the aggregate file.
|
|
case 'file':
|
|
if ($group['preprocess'] && $preprocess_css) {
|
|
$files_to_aggregate[$gap_counter][$key] = $group;
|
|
}
|
|
else {
|
|
++$gap_counter;
|
|
}
|
|
break;
|
|
|
|
// Aggregate all inline CSS content into the group's data property.
|
|
case 'inline':
|
|
++$gap_counter;
|
|
$css_groups[$key]['data'] = '';
|
|
foreach ($group['items'] as $item) {
|
|
$css_groups[$key]['data'] .= advagg_load_stylesheet_content($item['data'], $item['preprocess']);
|
|
}
|
|
break;
|
|
|
|
// Create a gap for external CSS.
|
|
case 'external':
|
|
++$gap_counter;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!empty($files_to_aggregate)) {
|
|
$hooks_hash = advagg_get_current_hooks_hash();
|
|
$serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
|
|
$css_hash = drupal_hash_base64($serialize_function($files_to_aggregate));
|
|
$cache_id = 'advagg:css:' . $hooks_hash . ':' . $css_hash;
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) {
|
|
$plans = $cache->data;
|
|
}
|
|
else {
|
|
module_load_include('inc', 'advagg', 'advagg');
|
|
$plans = advagg_build_aggregate_plans($files_to_aggregate, 'css');
|
|
if (!empty($plans) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) {
|
|
cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY);
|
|
}
|
|
}
|
|
$css_groups = advagg_merge_plans($css_groups, $plans);
|
|
}
|
|
if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
|
|
$GLOBALS['_advagg']['debug']['css_groups_after'][] = $css_groups;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default callback to aggregate JavaScript files.
|
|
*
|
|
* Having the browser load fewer JavaScript files results in much faster page
|
|
* loads than when it loads many small files. This function aggregates files
|
|
* within the same group into a single file unless the site-wide setting to do
|
|
* so is disabled (commonly the case during site development). To optimize
|
|
* download, it also compresses the aggregate files by removing comments,
|
|
* whitespace, and other unnecessary content.
|
|
*
|
|
* @param array $js_groups
|
|
* An array of JavaScript groups as returned by drupal_group_js(). For each
|
|
* group that is aggregated, this function sets the value of the group's
|
|
* 'data' key to the URI of the aggregate file.
|
|
*
|
|
* @see drupal_group_js()
|
|
* @see drupal_pre_render_scripts()
|
|
*/
|
|
function _advagg_aggregate_js(array &$js_groups) {
|
|
if (!advagg_enabled()) {
|
|
if (function_exists('drupal_aggregate_js')) {
|
|
return drupal_aggregate_js($js_groups);
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
|
|
$GLOBALS['_advagg']['debug']['js_groups_before'][] = $js_groups;
|
|
}
|
|
|
|
$preprocess_js = advagg_file_aggregation_enabled('js');
|
|
|
|
// Allow other modules to modify $js_groups right before it is processed.
|
|
// Call hook_advagg_js_groups_alter().
|
|
drupal_alter('advagg_js_groups', $js_groups, $preprocess_js);
|
|
|
|
// For each group that needs aggregation, aggregate its items.
|
|
$files_to_aggregate = array();
|
|
// Only aggregate when the site is configured to do so, and not during an
|
|
// update.
|
|
$gap_counter = 0;
|
|
if ($preprocess_js) {
|
|
// Set boolean to TRUE if all JS in footer.
|
|
$all_in_footer = FALSE;
|
|
if (module_exists('advagg_mod') && variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) >= 2) {
|
|
$all_in_footer = TRUE;
|
|
}
|
|
foreach ($js_groups as $key => &$group) {
|
|
switch ($group['type']) {
|
|
// If a file group can be aggregated into a single file, do so, and set
|
|
// the group's data property to the file path of the aggregate file.
|
|
case 'file':
|
|
if (!empty($group['preprocess'])) {
|
|
// Special handing for when all JS is in the footer.
|
|
if ($all_in_footer && $group['scope'] === 'footer' && $group['group'] > 9000) {
|
|
++$gap_counter;
|
|
$all_in_footer = FALSE;
|
|
}
|
|
$files_to_aggregate[$gap_counter][$key] = $group;
|
|
}
|
|
else {
|
|
++$gap_counter;
|
|
}
|
|
break;
|
|
|
|
// Create a gap for inline JS.
|
|
case 'inline':
|
|
++$gap_counter;
|
|
break;
|
|
|
|
// Create a gap for external JS.
|
|
case 'external':
|
|
++$gap_counter;
|
|
break;
|
|
}
|
|
}
|
|
unset($group);
|
|
}
|
|
|
|
if (!empty($files_to_aggregate)) {
|
|
$hooks_hash = advagg_get_current_hooks_hash();
|
|
$serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
|
|
$js_hash = drupal_hash_base64($serialize_function($files_to_aggregate));
|
|
$cache_id = 'advagg:js:' . $hooks_hash . ':' . $js_hash;
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1 && $cache = cache_get($cache_id, 'cache_advagg_aggregates')) {
|
|
$plans = $cache->data;
|
|
}
|
|
else {
|
|
module_load_include('inc', 'advagg', 'advagg');
|
|
$plans = advagg_build_aggregate_plans($files_to_aggregate, 'js');
|
|
if (!empty($plans) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 1) {
|
|
cache_set($cache_id, $plans, 'cache_advagg_aggregates', CACHE_TEMPORARY);
|
|
}
|
|
}
|
|
$js_groups = advagg_merge_plans($js_groups, $plans);
|
|
}
|
|
if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
|
|
$GLOBALS['_advagg']['debug']['js_groups_after'][] = $js_groups;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds the arrays needed for css rendering and caching.
|
|
*
|
|
* @param bool $skip_alter
|
|
* (Optional) If set to TRUE, this function skips calling drupal_alter() on
|
|
* css, useful for the aggressive cache.
|
|
*
|
|
* @return array
|
|
* Array contains the 2 arrays used for css.
|
|
*/
|
|
function _advagg_build_css_arrays_for_rendering($skip_alter = FALSE) {
|
|
// Get the raw CSS variable.
|
|
$raw_css = drupal_add_css();
|
|
// Process and Sort css.
|
|
$full_css = advagg_get_css($raw_css, $skip_alter);
|
|
// Add attached js to drupal_add_js() function.
|
|
if (!empty($full_css['#attached'])) {
|
|
drupal_process_attached($full_css);
|
|
// Remove #attached since it's been added to the javascript array now.
|
|
unset($full_css['#attached']);
|
|
}
|
|
return array($raw_css, $full_css);
|
|
}
|
|
|
|
/**
|
|
* Builds the arrays needed for js rendering and caching.
|
|
*
|
|
* @param bool $skip_alter
|
|
* (Optional) If set to TRUE, this function skips calling drupal_alter() on
|
|
* js, useful for the aggressive cache.
|
|
*
|
|
* @return array
|
|
* Array contains the 3 arrays used for javascript.
|
|
*/
|
|
function _advagg_build_js_arrays_for_rendering($skip_alter = FALSE) {
|
|
// Get the raw JS variable.
|
|
$javascript = drupal_add_js();
|
|
// Process and Sort JS.
|
|
$full_javascript = advagg_get_full_js($javascript, $skip_alter);
|
|
// Get scopes used in the js.
|
|
$scopes = advagg_get_js_scopes($full_javascript);
|
|
// Add JS to the header and footer of the page.
|
|
$js_scope_array = array();
|
|
$js_scope_settings_array = array();
|
|
foreach ($scopes as $scope => $use) {
|
|
if (!$use) {
|
|
// If the scope is not being used, skip it.
|
|
continue;
|
|
}
|
|
// advagg_get_js() will sort the JavaScript so that it appears in the
|
|
// correct order.
|
|
$scripts = advagg_get_js($scope, $full_javascript);
|
|
if (isset($scripts['#items']['settings'])) {
|
|
// Get the js settings.
|
|
$js_scope_settings_array[$scope]['settings'] = $scripts['#items']['settings'];
|
|
// Exclude JS Settings from the array; we'll add it back later.
|
|
$scripts['#items']['settings'] = array();
|
|
}
|
|
$js_scope_array[$scope] = $scripts;
|
|
}
|
|
|
|
// Fix settings; if more than 1 is set, use the largest one.
|
|
if (count($js_scope_settings_array) > 1) {
|
|
$max = -1;
|
|
$max_scope = '';
|
|
foreach ($js_scope_settings_array as $scope => $settings) {
|
|
$count = count($settings);
|
|
$max = max($max, $count);
|
|
if ($max == $count) {
|
|
$max_scope = $scope;
|
|
}
|
|
}
|
|
|
|
foreach ($js_scope_settings_array as $scope => $settings) {
|
|
if ($scope !== $max_scope) {
|
|
unset($js_scope_settings_array[$scope]);
|
|
}
|
|
}
|
|
}
|
|
return array($javascript, $js_scope_settings_array, $js_scope_array);
|
|
}
|
|
|
|
/**
|
|
* Returns TRUE if the CSS is being loaded via JavaScript.
|
|
*
|
|
* @param object $css_cache
|
|
* Cache object from cache_get().
|
|
*
|
|
* @return bool
|
|
* TRUE if CSS loaded via JS. FALSE if not.
|
|
*/
|
|
function advagg_css_in_js($css_cache = NULL) {
|
|
if (module_exists('advagg_mod')
|
|
&& variable_get('advagg_mod_css_defer', ADVAGG_MOD_CSS_DEFER)
|
|
) {
|
|
return TRUE;
|
|
}
|
|
if (module_exists('css_delivery')
|
|
&& css_delivery_enabled()
|
|
) {
|
|
return TRUE;
|
|
}
|
|
// Critical css added by another means.
|
|
if (!empty($css_cache->data[1]['#items'])) {
|
|
foreach ($css_cache->data[1]['#items'] as $values) {
|
|
if (!empty($values['critical-css'])) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return variable_get('advagg_css_in_js', ADVAGG_CSS_IN_JS);
|
|
}
|
|
|
|
/**
|
|
* Given the full css and js scope array return back the render cache.
|
|
*
|
|
* @param array $full_css
|
|
* Array from advagg_get_css() with #attached removed because it was built by
|
|
* _advagg_build_css_arrays_for_rendering().
|
|
* @param array $js_scope_array
|
|
* Array built from iterations of advagg_get_js() inside of
|
|
* _advagg_build_js_arrays_for_rendering().
|
|
*
|
|
* @return array
|
|
* Array containing the $css_cache, $js_cache, $css_cache_id, $js_cache_id.
|
|
*/
|
|
function advagg_get_render_cache(array $full_css, array $js_scope_array) {
|
|
$cids = array();
|
|
$css_cache_id = '';
|
|
$js_cache_id = '';
|
|
|
|
// Get advagg hash.
|
|
$hooks_hash = advagg_get_current_hooks_hash();
|
|
$serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
|
|
if (advagg_file_aggregation_enabled('css')) {
|
|
// Generate css cache id.
|
|
$cids[] = $css_cache_id = 'advagg:css:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($full_css));
|
|
}
|
|
if (advagg_file_aggregation_enabled('js')) {
|
|
// Generate js cache id.
|
|
$cids[] = $js_cache_id = 'advagg:js:full:1.1:' . $hooks_hash . ':' . drupal_hash_base64($serialize_function($js_scope_array));
|
|
}
|
|
|
|
if (!empty($cids)) {
|
|
// Get the cached data.
|
|
$cached_data = cache_get_multiple($cids, 'cache_advagg_aggregates');
|
|
|
|
// Set variables from the cache.
|
|
if (isset($cached_data[$css_cache_id])) {
|
|
$css_cache = $cached_data[$css_cache_id];
|
|
}
|
|
if (isset($cached_data[$js_cache_id])) {
|
|
$js_cache = $cached_data[$js_cache_id];
|
|
}
|
|
}
|
|
|
|
// Special handling if the css is loaded via JS.
|
|
if (!empty($css_cache)
|
|
&& empty($js_cache)
|
|
&& advagg_css_in_js($css_cache)
|
|
) {
|
|
// If CSS is being loaded via JavaScript and the css cache is set but the
|
|
// js cache is not set; then unset the css cache as well.
|
|
unset($css_cache);
|
|
}
|
|
|
|
// Set to empty arrays on a cache miss.
|
|
if (!isset($css_cache)) {
|
|
$css_cache = new stdClass();
|
|
}
|
|
if (!isset($js_cache)) {
|
|
$js_cache = new stdClass();
|
|
}
|
|
return array($css_cache, $js_cache, $css_cache_id, $js_cache_id);
|
|
}
|
|
|
|
/**
|
|
* Replacement for template_process_html().
|
|
*/
|
|
function _advagg_process_html(&$variables) {
|
|
// Don't fail even if the menu router failed.
|
|
if (drupal_get_http_header('status') === '404 Not Found') {
|
|
// See if the URI contains advagg.
|
|
$uri = request_uri();
|
|
if (stripos($uri, '/advagg_') !== FALSE) {
|
|
$advagg_items = advagg_menu();
|
|
|
|
// Check css.
|
|
$css = reset($advagg_items);
|
|
$css_path = key($advagg_items);
|
|
$css_path = substr($css_path, 0, strlen($css_path) - 1);
|
|
$css_start = strpos($uri, $css_path);
|
|
if ($css_start !== FALSE) {
|
|
$filename = substr($uri, $css_start + strlen($css_path));
|
|
}
|
|
|
|
// Check js.
|
|
if (empty($filename)) {
|
|
$js = next($advagg_items);
|
|
$js_path = key($advagg_items);
|
|
$js_path = substr($js_path, 0, strlen($js_path) - 1);
|
|
$js_start = strpos($uri, $js_path);
|
|
if ($js_start !== FALSE) {
|
|
$filename = substr($uri, $js_start + strlen($js_path));
|
|
}
|
|
}
|
|
|
|
// If we have a filename call the page callback.
|
|
if (!empty($filename)) {
|
|
$router_item = $css;
|
|
if (isset($js)) {
|
|
$router_item = $js;
|
|
}
|
|
|
|
// Include the file if needed.
|
|
if ($router_item['file']) {
|
|
$included = module_load_include($router_item['file'], 'advagg');
|
|
if (!$included && !function_exists($router_item['page callback'])) {
|
|
$file = DRUPAL_ROOT . '/' . drupal_get_path('module', 'advagg') . '/' . $router_item['file'];
|
|
if (is_file($file)) {
|
|
require_once $file;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call the function.
|
|
if (function_exists($router_item['page callback'])) {
|
|
// Strip query and fragment form the filename.
|
|
if ($pos = strpos($filename, '?')) {
|
|
$filename = substr($filename, 0, $pos);
|
|
}
|
|
if ($pos = strpos($filename, '#')) {
|
|
$filename = substr($filename, 0, $pos);
|
|
}
|
|
// Generate the file.
|
|
call_user_func_array($router_item['page callback'], array($filename));
|
|
}
|
|
else {
|
|
// Report the bigger issue to watchdog.
|
|
watchdog('advagg', 'You need to flush your menu cache. This can be done at the top of the <a href="@performance">performance page</a>. The advagg callback failed while trying to generate this file: @uri', array(
|
|
'@performance' => url('admin/config/development/performance'),
|
|
'@uri' => $uri,
|
|
), WATCHDOG_CRITICAL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!advagg_enabled()) {
|
|
template_process_html($variables);
|
|
return;
|
|
}
|
|
|
|
// Render page_top and page_bottom into top level variables.
|
|
if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_top'])) {
|
|
$variables['page_top'] = drupal_render($variables['page']['page_top']);
|
|
}
|
|
elseif (!isset($variables['page_top'])) {
|
|
$variables['page_top'] = '';
|
|
}
|
|
if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['page_bottom'])) {
|
|
$variables['page_bottom'] = drupal_render($variables['page']['page_bottom']);
|
|
}
|
|
elseif (!isset($variables['page_bottom'])) {
|
|
$variables['page_bottom'] = '';
|
|
}
|
|
// Place the rendered HTML for the page body into a top level variable.
|
|
if (isset($variables['page']) && is_array($variables['page']) && isset($variables['page']['#children'])) {
|
|
$variables['page'] = $variables['page']['#children'];
|
|
}
|
|
$advagg_script_alt_scope_scripts = array();
|
|
if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
|
|
$prefix = "<!-- AdvAgg page:prefix tag -->";
|
|
$suffix = "<!-- AdvAgg page:suffix tag -->";
|
|
$variables['page'] = $prefix . $variables['page'] . $suffix;
|
|
$prefix = "<!-- AdvAgg page_top:prefix tag -->";
|
|
$suffix = "<!-- AdvAgg page_top:suffix tag -->";
|
|
$variables['page_top'] = $prefix . $variables['page_top'] . $suffix;
|
|
$prefix = "<!-- AdvAgg page_bottom:prefix tag -->";
|
|
$suffix = "<!-- AdvAgg page_bottom:suffix tag -->";
|
|
$variables['page_bottom'] = $prefix . $variables['page_bottom'] . $suffix;
|
|
|
|
$matches = array();
|
|
preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page_top'], $matches);
|
|
$advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts);
|
|
preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page'], $matches);
|
|
$advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts);
|
|
preg_match_all('/<!-- AdvAgg (.*?) tag -->/', $variables['page_bottom'], $matches);
|
|
$advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts);
|
|
}
|
|
|
|
// Parts of drupal_get_html_head().
|
|
$elements = drupal_add_html_head();
|
|
if (is_callable('advagg_mod_html_head_post_alter')) {
|
|
advagg_mod_html_head_post_alter($elements);
|
|
}
|
|
|
|
// Get default javascript.
|
|
// @see http://drupal.org/node/1279226
|
|
if (function_exists('drupal_add_js_page_defaults')) {
|
|
drupal_add_js_page_defaults();
|
|
}
|
|
$javascript = array();
|
|
|
|
// Try the render cache.
|
|
if (!variable_get('advagg_debug', ADVAGG_DEBUG)) {
|
|
// No Alter.
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5 && !module_exists('advagg_relocate')) {
|
|
// Get all CSS and JS variables needed; running no alters.
|
|
list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering(TRUE);
|
|
list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering(TRUE);
|
|
|
|
// Get the render cache.
|
|
list($css_cache, $js_cache, $css_cache_id_no_alter, $js_cache_id_no_alter) = advagg_get_render_cache($full_css, $js_scope_array);
|
|
}
|
|
|
|
// With Alter.
|
|
if ((empty($css_cache->data) || empty($js_cache->data)) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {
|
|
// Get all CSS and JS variables needed; running alters.
|
|
list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering();
|
|
list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering();
|
|
|
|
// Get the render cache.
|
|
list($css_cache, $js_cache, $css_cache_id, $js_cache_id) = advagg_get_render_cache($full_css, $js_scope_array);
|
|
}
|
|
}
|
|
|
|
// CSS has nice hooks so we don't need to work around it.
|
|
if (!empty($css_cache->data)) {
|
|
// Use render cache.
|
|
list($variables['styles'], $full_css) = $css_cache->data;
|
|
}
|
|
else {
|
|
// Get the css if we have not done so.
|
|
if (empty($full_css)) {
|
|
list($variables['css'], $full_css) = _advagg_build_css_arrays_for_rendering();
|
|
}
|
|
// Render the CSS; advagg_pre_render_styles() gets called here.
|
|
$variables['styles'] = drupal_render($full_css);
|
|
if (!empty($css_cache_id) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {
|
|
// Save to the cache.
|
|
cache_set($css_cache_id, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY);
|
|
}
|
|
if (!empty($css_cache_id_no_alter) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) {
|
|
// Save to the cache.
|
|
cache_set($css_cache_id_no_alter, array($variables['styles'], $full_css), 'cache_advagg_aggregates', CACHE_TEMPORARY);
|
|
}
|
|
}
|
|
|
|
if (module_exists('advagg_font') && variable_get('advagg_font_fontfaceobserver', ADVAGG_FONT_FONTFACEOBSERVER)) {
|
|
$fonts = array();
|
|
foreach ($full_css['#groups'] as $groups) {
|
|
if (isset($groups['items']['files'])) {
|
|
foreach ($groups['items']['files'] as $file) {
|
|
if (isset($file['advagg_font'])) {
|
|
foreach ($file['advagg_font'] as $class => $name) {
|
|
$fonts[$class] = $name;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!empty($fonts)) {
|
|
if (isset($js_scope_settings_array)) {
|
|
$key = key($js_scope_settings_array);
|
|
$js_scope_settings_array[$key]['settings']['data'][] = array('advagg_font' => $fonts);
|
|
}
|
|
drupal_add_js(array('advagg_font' => $fonts), array('type' => 'setting'));
|
|
}
|
|
}
|
|
|
|
if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {
|
|
foreach ($full_css['#groups'] as $groups) {
|
|
if (empty($groups['data']) || $groups['type'] === 'inline') {
|
|
continue;
|
|
}
|
|
advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'style');
|
|
}
|
|
}
|
|
|
|
// JS needs hacks.
|
|
// Clear out all old scripts.
|
|
if (variable_get('advagg_clear_scripts', ADVAGG_CLEAR_SCRIPTS)) {
|
|
$variables['scripts'] = '';
|
|
}
|
|
if (!isset($variables['scripts'])) {
|
|
$variables['scripts'] = '';
|
|
}
|
|
if (!isset($variables['page_bottom']) || !is_string($variables['page_bottom'])) {
|
|
$variables['page_bottom'] = '';
|
|
}
|
|
|
|
$use_cache = FALSE;
|
|
if (!empty($js_cache->data) && !variable_get('advagg_debug', ADVAGG_DEBUG)) {
|
|
// Use render cache.
|
|
$use_cache = TRUE;
|
|
$add_to_variables = array();
|
|
// Replace cached settings with current ones.
|
|
$js_settings_used = array();
|
|
|
|
$js_scope_settings_array_copy = $js_scope_settings_array;
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) {
|
|
if (!empty($js_scope_settings_array_copy['header']) && empty($js_scope_settings_array_copy['footer'])) {
|
|
// Copy header settings into the footer.
|
|
$js_scope_settings_array_copy['footer'] = $js_scope_settings_array_copy['header'];
|
|
}
|
|
}
|
|
|
|
list($js_cache_data, $js_scope_array) = $js_cache->data;
|
|
|
|
foreach ($js_cache_data as $scope => $value) {
|
|
$scope_settings = $scope;
|
|
if ($scope_settings === 'scripts') {
|
|
$scope_settings = 'header';
|
|
}
|
|
if ($scope === 'page_bottom') {
|
|
$scope_settings = 'footer';
|
|
}
|
|
// Search $value for Drupal.settings.
|
|
$start = strpos($value, 'jQuery.extend(Drupal.settings,');
|
|
if ($start !== FALSE) {
|
|
// If the cache and current settings scope's do not match; do not use
|
|
// the cached version.
|
|
if (!isset($js_scope_settings_array_copy[$scope_settings]['settings'])) {
|
|
$use_cache = FALSE;
|
|
break;
|
|
}
|
|
|
|
// Replace cached Drupal.settings with current Drupal.settings for this
|
|
// page.
|
|
$merged = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($js_scope_settings_array_copy[$scope_settings]['settings']['data'], 'is_array')));
|
|
$json_data = advagg_json_encode($merged);
|
|
if (!empty($json_data)) {
|
|
// Record that this is being used.
|
|
$js_settings_used[$scope_settings] = TRUE;
|
|
|
|
// Replace the drupal settings string.
|
|
$value = advagg_replace_drupal_settings_string($value, $json_data);
|
|
}
|
|
}
|
|
$add_to_variables[$scope] = $value;
|
|
}
|
|
|
|
if ($use_cache) {
|
|
$all_used = array_diff(array_keys($js_scope_settings_array_copy), array_keys($js_settings_used));
|
|
// Ignore this check if the cache level is less than 5.
|
|
if (!empty($all_used) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 5 && !empty($js_settings_used)) {
|
|
// Some js settings did not make it into the output. Skip cache.
|
|
$use_cache = FALSE;
|
|
}
|
|
}
|
|
|
|
if ($use_cache) {
|
|
// Using the cache; write to the $variables array.
|
|
foreach ($add_to_variables as $scope => $value) {
|
|
// Set the scope variable if not set.
|
|
if (!isset($variables[$scope]) || !is_string($variables[$scope])) {
|
|
$variables[$scope] = '';
|
|
}
|
|
// Append the js to the scope.
|
|
$variables[$scope] .= $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the cache isn't used.
|
|
if (!$use_cache) {
|
|
if (!empty($js_cache->data) && !empty($css_cache->data) && advagg_css_in_js($css_cache)) {
|
|
// Render the css so it will be added to the js array;
|
|
// advagg_pre_render_styles() gets called here.
|
|
$variables['styles'] = drupal_render($full_css);
|
|
}
|
|
|
|
// Check if the js has changed.
|
|
$new_js = drupal_add_js();
|
|
$diff = array_diff(array_keys($new_js), array_keys($javascript));
|
|
if (!empty($diff) || empty($javascript)) {
|
|
// Get all JS variables needed again because js changed; or because we
|
|
// never got them in the first place.
|
|
list($javascript, $js_scope_settings_array, $js_scope_array) = _advagg_build_js_arrays_for_rendering();
|
|
}
|
|
|
|
$js_cache = array();
|
|
$js_cache['scripts'] = '';
|
|
if (!empty($js_scope_array)) {
|
|
// Add JS to the header and footer of the page.
|
|
foreach ($js_scope_array as $scope => &$scripts_array) {
|
|
// Add js settings.
|
|
if (!empty($js_scope_settings_array[$scope]['settings'])) {
|
|
$scripts_array['#items']['settings'] = $js_scope_settings_array[$scope]['settings'];
|
|
}
|
|
// Render js; advagg_pre_render_scripts() gets called here.
|
|
$scripts = drupal_render($scripts_array);
|
|
|
|
if ($scope === 'header') {
|
|
// Add to the top of this section.
|
|
$variables['scripts'] = $scripts . $variables['scripts'];
|
|
$js_cache['scripts'] = $scripts . $js_cache['scripts'];
|
|
}
|
|
// Footer scripts.
|
|
elseif ($scope === 'footer') {
|
|
// Add to the bottom of this section.
|
|
$variables['page_bottom'] .= $scripts;
|
|
$js_cache['page_bottom'] = $scripts;
|
|
}
|
|
// Above css scripts.
|
|
elseif ($scope === 'above_css') {
|
|
// Put in this new section.
|
|
$variables['above_css'] = $scripts;
|
|
$js_cache['above_css'] = $scripts;
|
|
}
|
|
elseif (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)) {
|
|
// Scripts in other places.
|
|
if (isset($variables[$scope])
|
|
&& is_string($variables[$scope])
|
|
&& array_key_exists($scope, $GLOBALS['theme_info']->info['regions'])
|
|
) {
|
|
// Add to the bottom of this section.
|
|
$variables[$scope] .= $scripts;
|
|
$js_cache[$scope] = $scripts;
|
|
}
|
|
elseif (array_search($scope, $advagg_script_alt_scope_scripts, TRUE) !== FALSE) {
|
|
// Add to the inline html.
|
|
$pos_page_top = strpos($variables['page_top'], "<!-- AdvAgg $scope tag -->");
|
|
$pos_page = strpos($variables['page'], "<!-- AdvAgg $scope tag -->");
|
|
$pos_page_bottom = strpos($variables['page_bottom'], "<!-- AdvAgg $scope tag -->");
|
|
if ($pos_page_top !== FALSE) {
|
|
$pos_page_top += strlen("<!-- AdvAgg $scope tag -->");
|
|
$variables['page_top'] = substr_replace($variables['page_top'], "\n$scripts", $pos_page_top, 0);
|
|
$js_cache[$scope] = $scripts;
|
|
}
|
|
elseif ($pos_page !== FALSE) {
|
|
$pos_page += strlen("<!-- AdvAgg $scope tag -->");
|
|
$variables['page'] = substr_replace($variables['page'], "\n$scripts", $pos_page, 0);
|
|
$js_cache[$scope] = $scripts;
|
|
}
|
|
elseif ($pos_page_bottom !== FALSE) {
|
|
$pos_page_bottom += strlen("<!-- AdvAgg $scope tag -->");
|
|
$variables['page_bottom'] = substr_replace($variables['page_bottom'], "\n$scripts", $pos_page_bottom, 0);
|
|
$js_cache[$scope] = $scripts;
|
|
}
|
|
}
|
|
// Add javascript to scripts if we can't find the region in the theme.
|
|
elseif (strpos($scope, ':') === FALSE) {
|
|
// Add to the bottom of this section.
|
|
$variables['scripts'] .= $scripts;
|
|
$js_cache['scripts'] .= $scripts;
|
|
}
|
|
}
|
|
}
|
|
unset($scripts_array);
|
|
|
|
// Clear drupal settings so cache is smaller.
|
|
foreach ($js_cache as &$string) {
|
|
$string = advagg_replace_drupal_settings_string($string, '{}');
|
|
}
|
|
unset($string);
|
|
|
|
// Clear drupal settings and not needed items from render cache.
|
|
$js_scope_array = array_intersect_key($js_scope_array, array_flip(element_children($js_scope_array)));
|
|
foreach ($js_scope_array as $scope => &$scripts_array) {
|
|
// Clear element children.
|
|
$scripts_array = array_diff_key($scripts_array, array_flip(element_children($scripts_array)));
|
|
if (isset($scripts_array['#children'])) {
|
|
unset($scripts_array['#children']);
|
|
}
|
|
// Clear drupal settings.
|
|
if (isset($scripts_array['#items']['settings']['data']) && is_array($scripts_array['#items']['settings']['data'])) {
|
|
$scripts_array['#items']['settings']['data'] = array();
|
|
}
|
|
// Clear printed keys.
|
|
if (isset($scripts_array['#printed'])) {
|
|
unset($scripts_array['#printed']);
|
|
}
|
|
// Clear not used groups.
|
|
foreach ($scripts_array['#groups'] as $key => $groups) {
|
|
if (!isset($groups['items']['files'])) {
|
|
unset($scripts_array['#groups'][$key]);
|
|
}
|
|
}
|
|
}
|
|
unset($scripts_array);
|
|
|
|
if (!empty($js_cache_id) && !empty($js_cache) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 3) {
|
|
cache_set($js_cache_id, array($js_cache, $js_scope_array), 'cache_advagg_aggregates', CACHE_TEMPORARY);
|
|
}
|
|
if (!empty($js_cache_id_no_alter) && !empty($js_cache) && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 5) {
|
|
cache_set($js_cache_id_no_alter, array($js_cache, $js_scope_array), 'cache_advagg_aggregates', CACHE_TEMPORARY);
|
|
}
|
|
}
|
|
}
|
|
if (!empty($variables['above_css'])) {
|
|
$variables['styles'] = $variables['above_css'] . $variables['styles'];
|
|
}
|
|
|
|
if (variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {
|
|
foreach ($js_scope_array as $scope => &$scripts_array) {
|
|
if ($scope !== 'header'
|
|
&& $scope !== 'footer'
|
|
&& $scope !== 'above_css'
|
|
&& !variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($scripts_array['#groups'] as $groups) {
|
|
if (empty($groups['data']) || $groups['type'] === 'inline') {
|
|
continue;
|
|
}
|
|
advagg_add_preload_header(advagg_convert_abs_to_rel(file_create_url($groups['data'])), 'script');
|
|
}
|
|
}
|
|
}
|
|
|
|
$head_elements_before = drupal_add_html_head();
|
|
if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH)
|
|
|| variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)
|
|
|| variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)
|
|
) {
|
|
// Prefetch css domains.
|
|
foreach ($full_css['#items'] as $file) {
|
|
advagg_add_resource_hints_array($file);
|
|
}
|
|
foreach ($full_css['#groups'] as $groups) {
|
|
if (isset($groups['items']['files'])) {
|
|
foreach ($groups['items']['files'] as $file) {
|
|
advagg_add_resource_hints_array($file);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prefetch js domains.
|
|
foreach ($js_scope_array as $scope_js) {
|
|
foreach ($scope_js['#items'] as $file) {
|
|
advagg_add_resource_hints_array($file);
|
|
}
|
|
if (isset($scope_js['#groups'])) {
|
|
foreach ($scope_js['#groups'] as $groups) {
|
|
if (isset($groups['items']['files'])) {
|
|
foreach ($groups['items']['files'] as $file) {
|
|
advagg_add_resource_hints_array($file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add in preload link headers.
|
|
advagg_add_preload_header();
|
|
|
|
// Add in the headers added by advagg.
|
|
$head_elements_after = drupal_add_html_head();
|
|
$elements += array_diff_key($head_elements_after, $head_elements_before);
|
|
|
|
// Parts of drupal_get_html_head().
|
|
drupal_alter('html_head', $elements);
|
|
$head = drupal_render($elements);
|
|
if (variable_get('advagg_html_head_in_css_location', ADVAGG_HTML_HEAD_IN_CSS_LOCATION)) {
|
|
$variables['styles'] = $head . $variables['styles'];
|
|
$variables['head'] = '';
|
|
}
|
|
else {
|
|
$variables['head'] = $head;
|
|
}
|
|
|
|
// Remove AdvAgg comments.
|
|
if (variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE)
|
|
&& !empty($advagg_script_alt_scope_scripts)
|
|
&& !variable_get('theme_debug', FALSE)
|
|
) {
|
|
$variables['page_top'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page_top']);
|
|
$variables['page'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page']);
|
|
$variables['page_bottom'] = preg_replace('/<!-- AdvAgg (.*?) tag -->/', '', $variables['page_bottom']);
|
|
}
|
|
|
|
// Output debug info.
|
|
if (variable_get('advagg_debug', ADVAGG_DEBUG)) {
|
|
$debug = $GLOBALS['_advagg']['debug'];
|
|
if (is_callable('httprl_pr')) {
|
|
$output = ' ' . httprl_pr($debug);
|
|
}
|
|
else {
|
|
$output = '<pre>' . str_replace(array('<', '>'), array('<', '>'), print_r($debug, TRUE)) . '</pre>';
|
|
}
|
|
watchdog('advagg_debug', $output, array(), WATCHDOG_DEBUG);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replace inline drupal settings script.
|
|
*
|
|
* @param string $subject
|
|
* Inline js.
|
|
* @param string $replace
|
|
* JS settings replacement.
|
|
*
|
|
* @return string
|
|
* Returns the subject with the replacement in place if this is a drupal
|
|
* settings json blob.
|
|
*/
|
|
function advagg_replace_drupal_settings_string($subject, $replace) {
|
|
$start = strpos($subject, 'jQuery.extend(Drupal.settings,');
|
|
if ($start === FALSE) {
|
|
return $subject;
|
|
}
|
|
|
|
// Find the end of the Drupal.settings.
|
|
$script_end = stripos($subject, '</script>', $start);
|
|
$settings_substring = substr($subject, $start, $script_end - $start);
|
|
$json_end = strripos($settings_substring, '});');
|
|
|
|
// Check if LABjs has added an additional wrapper around Drupal settings.
|
|
$script_tag_start = strripos(substr($subject, 0, $start), '<script');
|
|
if (strpos(substr($subject, $script_tag_start, $start), '$L.wait(') !== FALSE) {
|
|
// Refine JSON end position.
|
|
$_json_end = strripos(substr($settings_substring, 0, $json_end), '});');
|
|
if ($_json_end !== FALSE) {
|
|
$json_end = $_json_end;
|
|
}
|
|
}
|
|
|
|
// Replace Drupal.settings json.
|
|
$subject = substr($subject, 0, $start + 30) . $replace . substr($subject, $json_end + $start + 1);
|
|
return $subject;
|
|
}
|
|
|
|
/**
|
|
* Shrink the ajaxPageState data.
|
|
*
|
|
* @param array $data
|
|
* Settings for javascript.
|
|
*/
|
|
function advagg_cleanup_settings_array(array $data) {
|
|
// Remove inline js from the ajaxPageState data.
|
|
if (isset($data['ajaxPageState']['js'])) {
|
|
foreach ((array) $data['ajaxPageState']['js'] as $key => $value) {
|
|
if (advagg_remove_short_keys($key)) {
|
|
if (is_array($data['ajaxPageState']['js']) && isset($data['ajaxPageState']['js'][$key])) {
|
|
unset($data['ajaxPageState']['js'][$key]);
|
|
}
|
|
elseif (is_object($data['ajaxPageState']['js']) && isset($data['ajaxPageState']['js']->{$key})) {
|
|
unset($data['ajaxPageState']['js']->{$key});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Remove inline css from the ajaxPageState data.
|
|
if (isset($data['ajaxPageState']['css'])) {
|
|
foreach ((array) $data['ajaxPageState']['css'] as $key => $value) {
|
|
if (advagg_remove_short_keys($key, 6)) {
|
|
if (is_object($data['ajaxPageState']['css']) && isset($data['ajaxPageState']['css']->{$key})) {
|
|
unset($data['ajaxPageState']['css']->{$key});
|
|
}
|
|
elseif (is_array($data['ajaxPageState']['css']) && isset($data['ajaxPageState']['css'][$key])) {
|
|
unset($data['ajaxPageState']['css'][$key]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Remove settings from the js ajaxPageState data.
|
|
if (isset($data['ajaxPageState']['js']['settings'])) {
|
|
unset($data['ajaxPageState']['js']['settings']);
|
|
}
|
|
if (isset($data['ajaxPageState']['js']->settings)) {
|
|
unset($data['ajaxPageState']['js']->settings);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Find dns_prefetch and call advagg_add_dns_prefetch().
|
|
*
|
|
* @param array $values
|
|
* Attributes added via code for the file.
|
|
*/
|
|
function advagg_add_resource_hints_array(array $values) {
|
|
if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH)
|
|
|| variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) {
|
|
if (!empty($values['type'])
|
|
&& ($values['type'] === 'external' || $values['type'] === 'file')
|
|
) {
|
|
// Get external domains.
|
|
advagg_add_dns_prefetch($values['data']);
|
|
}
|
|
if (!empty($values['dns_prefetch'])) {
|
|
// Grab domains that will be access when this file is loaded.
|
|
if (is_array($values['dns_prefetch'])) {
|
|
foreach ($values['dns_prefetch'] as $url) {
|
|
advagg_add_dns_prefetch($url);
|
|
}
|
|
}
|
|
else {
|
|
advagg_add_dns_prefetch($values['dns_prefetch']);
|
|
}
|
|
}
|
|
}
|
|
if (!empty($values['preload']) && variable_get('advagg_resource_hints_preload', ADVAGG_RESOURCE_HINTS_PRELOAD)) {
|
|
if (is_array($values['preload'])) {
|
|
foreach ($values['preload'] as $url) {
|
|
advagg_add_preload_header($url);
|
|
}
|
|
}
|
|
else {
|
|
advagg_add_preload_header($values['preload']);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add in the dns-prefetch header for CSS and JS external files.
|
|
*
|
|
* @param string $url
|
|
* The url of the external host.
|
|
*
|
|
* @return bool
|
|
* TRUE if it was added to the head.
|
|
*/
|
|
function advagg_add_dns_prefetch($url) {
|
|
// Keep the order.
|
|
$advagg_resource_hints_location = variable_get('advagg_resource_hints_location', ADVAGG_RESOURCE_HINTS_LOCATION);
|
|
static $weight = -1001;
|
|
if ($advagg_resource_hints_location == 3) {
|
|
$weight = -999.9;
|
|
}
|
|
$weight += 0.0001;
|
|
|
|
// Get the host.
|
|
$parse = @parse_url($url);
|
|
if (empty($parse['host'])) {
|
|
// If just the hostname was given, build proper url.
|
|
if (strpos($url, '.') && strpos($url, '/') === FALSE) {
|
|
$parse['scheme'] = '//';
|
|
$parse['host'] = $url;
|
|
// Check for fragment.
|
|
$pos = strpos($url, '#');
|
|
if ($pos !== FALSE) {
|
|
$parse['fragment'] = substr($url, $pos + 1);
|
|
$parse['host'] = substr($url, 0, $pos);
|
|
}
|
|
// Put it back together and parse again.
|
|
$url = advagg_glue_url($parse);
|
|
$parse = @parse_url($url);
|
|
}
|
|
if (empty($parse['host'])) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// Filter out wrong schemes.
|
|
if (!empty($parse['scheme'])
|
|
&& $parse['scheme'] !== 'http'
|
|
&& $parse['scheme'] !== 'https'
|
|
) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Filter out local host.
|
|
$host = @parse_url($GLOBALS['base_root'], PHP_URL_HOST);
|
|
if ($parse['host'] === $host) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Add DNS information for more domains.
|
|
if (strpos($parse['host'], 'fonts.googleapis.com') !== FALSE) {
|
|
// Add fonts.gstatic.com when fonts.googleapis.com is added.
|
|
advagg_add_dns_prefetch('https://fonts.gstatic.com/#crossorigin');
|
|
}
|
|
|
|
// Build render array.
|
|
if (variable_get('advagg_resource_hints_dns_prefetch', ADVAGG_RESOURCE_HINTS_DNS_PREFETCH)) {
|
|
$element = array(
|
|
'#type' => 'html_tag',
|
|
'#tag' => 'link',
|
|
'#attributes' => array(
|
|
'rel' => 'dns-prefetch',
|
|
'href' => '//' . $parse['host'],
|
|
),
|
|
'#weight' => $weight,
|
|
);
|
|
// Add markup for dns-prefetch to html_head.
|
|
drupal_add_html_head($element, 'advagg_resource_hints_dns_prefetch:' . $parse['host']);
|
|
}
|
|
if (variable_get('advagg_resource_hints_preconnect', ADVAGG_RESOURCE_HINTS_PRECONNECT)) {
|
|
// HTTPS use Protocol Relative; HTTP and scheme defined use given scheme.
|
|
$href = '//' . $parse['host'];
|
|
if (!$GLOBALS['is_https'] && isset($parse['scheme'])) {
|
|
$href = "{$parse['scheme']}://{$parse['host']}";
|
|
}
|
|
|
|
$element = array(
|
|
'#type' => 'html_tag',
|
|
'#tag' => 'link',
|
|
'#attributes' => array(
|
|
'rel' => 'preconnect',
|
|
'href' => $href,
|
|
),
|
|
'#weight' => $weight,
|
|
);
|
|
if (!empty($parse['fragment']) && $parse['fragment'] === 'crossorigin') {
|
|
$element['#attributes']['crossorigin'] = '';
|
|
}
|
|
// Add markup for dns-prefetch to html_head.
|
|
drupal_add_html_head($element, 'advagg_resource_hints_preconnect:' . $parse['host']);
|
|
}
|
|
|
|
// Build render array. Goes after charset tag.
|
|
if (!empty($parse['fragment']) && $parse['fragment'] === 'prefetch') {
|
|
// Hacky way to open up a connection to the remote host.
|
|
$element = array(
|
|
'#type' => 'html_tag',
|
|
'#tag' => 'link',
|
|
'#attributes' => array(
|
|
'rel' => 'prefetch',
|
|
'href' => '//' . $parse['host'] . '/robots.txt',
|
|
),
|
|
'#weight' => $weight,
|
|
);
|
|
drupal_add_html_head($element, 'advagg_prefetch:' . $parse['host']);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Returns a themed representation of all stylesheets to attach to the page.
|
|
*
|
|
* It loads the CSS in order, with 'module' first, then 'theme' afterwards.
|
|
* This ensures proper cascading of styles so themes can easily override
|
|
* module styles through CSS selectors.
|
|
*
|
|
* Themes may replace module-defined CSS files by adding a stylesheet with the
|
|
* same filename. For example, themes/bartik/system-menus.css would replace
|
|
* modules/system/system-menus.css. This allows themes to override complete
|
|
* CSS files, rather than specific selectors, when necessary.
|
|
*
|
|
* If the original CSS file is being overridden by a theme, the theme is
|
|
* responsible for supplying an accompanying RTL CSS file to replace the
|
|
* module's.
|
|
*
|
|
* @param array $css
|
|
* (Optional) An array of CSS files. If no array is provided, the default
|
|
* stylesheets array is used instead.
|
|
* @param bool $skip_alter
|
|
* (Optional) If set to TRUE, this function skips calling drupal_alter() on
|
|
* $css, useful when the calling function passes a $css array that has already
|
|
* been altered.
|
|
*
|
|
* @return array
|
|
* An array ready to be passed into drupal_render().
|
|
*
|
|
* @see drupal_add_css()
|
|
*/
|
|
function advagg_get_css(array $css = array(), $skip_alter = FALSE) {
|
|
if (empty($css)) {
|
|
$css = drupal_add_css();
|
|
}
|
|
|
|
// Allow modules and themes to alter the CSS items.
|
|
if (!$skip_alter) {
|
|
advagg_add_default_dns_lookups($css, 'css');
|
|
// Call hook_css_alter().
|
|
drupal_alter('css', $css);
|
|
// Call hook_css_post_alter().
|
|
drupal_alter('css_post', $css);
|
|
// Call these advagg functions after the hook_css_alter was called.
|
|
advagg_fix_type($css, 'css');
|
|
}
|
|
|
|
// Sort CSS items, so that they appear in the correct order.
|
|
advagg_drupal_sort_css_js_stable($css);
|
|
|
|
// Provide the page with information about the individual CSS files used,
|
|
// information not otherwise available when CSS aggregation is enabled. The
|
|
// setting is attached later in this function, but is set here, so that CSS
|
|
// files removed below are still considered "used" and prevented from being
|
|
// added in a later AJAX request.
|
|
// Skip if no files were added to the page or jQuery.extend() will overwrite
|
|
// the Drupal.settings.ajaxPageState.css object with an empty array.
|
|
if (!empty($css)) {
|
|
// Cast the array to an object to be on the safe side even if not empty.
|
|
$setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1);
|
|
}
|
|
|
|
// Remove the overridden CSS files. Later CSS files override former ones.
|
|
$previous_item = array();
|
|
foreach ($css as $key => $item) {
|
|
if ($item['type'] == 'file') {
|
|
// If defined, force a unique basename for this file.
|
|
$basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']);
|
|
if (isset($previous_item[$basename])) {
|
|
// Remove the previous item that shared the same base name.
|
|
unset($css[$previous_item[$basename]]);
|
|
}
|
|
$previous_item[$basename] = $key;
|
|
}
|
|
}
|
|
|
|
// Remove empty files.
|
|
advagg_remove_empty_files($css);
|
|
|
|
// Render the HTML needed to load the CSS.
|
|
$styles = array(
|
|
'#type' => 'styles',
|
|
'#items' => $css,
|
|
);
|
|
|
|
if (!empty($setting)) {
|
|
$styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
|
|
}
|
|
|
|
return $styles;
|
|
}
|
|
|
|
/**
|
|
* Get full JS array.
|
|
*
|
|
* Note that hook_js_alter(&$javascript) is called during this function call
|
|
* to allow alterations of the JavaScript during its presentation. Calls to
|
|
* drupal_add_js() from hook_js_alter() will not be added to the output
|
|
* presentation. The correct way to add JavaScript during hook_js_alter()
|
|
* is to add another element to the $javascript array, deriving from
|
|
* drupal_js_defaults(). See locale_js_alter() for an example of this.
|
|
*
|
|
* @param array $javascript
|
|
* (optional) An array with all JavaScript code. Defaults to the default
|
|
* JavaScript array for the given scope.
|
|
* @param bool $skip_alter
|
|
* (optional) If set to TRUE, this function skips calling drupal_alter() on
|
|
* $javascript, useful when the calling function passes a $javascript array
|
|
* that has already been altered.
|
|
*
|
|
* @return array
|
|
* The raw JavaScript array.
|
|
*
|
|
* @see drupal_add_js()
|
|
* @see locale_js_alter()
|
|
* @see drupal_js_defaults()
|
|
*/
|
|
function advagg_get_full_js(array $javascript = array(), $skip_alter = FALSE) {
|
|
if (empty($javascript)) {
|
|
$javascript = drupal_add_js();
|
|
}
|
|
|
|
// Return an empty array if
|
|
// no javascript is used,
|
|
// only the settings array is used and scope is header.
|
|
if (empty($javascript)
|
|
|| (isset($javascript['settings']) && count($javascript) == 1)
|
|
) {
|
|
return array();
|
|
}
|
|
|
|
// Allow modules to alter the JavaScript.
|
|
if (!$skip_alter) {
|
|
advagg_add_default_dns_lookups($javascript, 'js');
|
|
if (is_callable('advagg_mod_js_pre_alter')) {
|
|
advagg_mod_js_pre_alter($javascript);
|
|
}
|
|
// Call hook_js_alter().
|
|
drupal_alter('js', $javascript);
|
|
// Call hook_js_post_alter().
|
|
drupal_alter('js_post', $javascript);
|
|
// Call these advagg functions after the hook_js_alter was called.
|
|
advagg_fix_type($javascript, 'js');
|
|
}
|
|
elseif (is_callable('advagg_mod_js_move_to_footer')) {
|
|
if (variable_get('advagg_mod_js_footer', ADVAGG_MOD_JS_FOOTER) == 3) {
|
|
advagg_mod_js_move_to_footer($javascript);
|
|
}
|
|
}
|
|
|
|
// If in development mode make sure the ajaxPageState css is there.
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
|
|
$have_css = FALSE;
|
|
foreach ($javascript['settings']['data'] as $setting) {
|
|
if (!empty($setting['ajaxPageState']['css'])) {
|
|
$have_css = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if (!$have_css) {
|
|
$css = drupal_add_css();
|
|
if (!empty($css)) {
|
|
// Cast the array to an object to be on the safe side even if not empty.
|
|
$javascript['settings']['data'][]['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove empty files.
|
|
advagg_remove_empty_files($javascript);
|
|
|
|
return $javascript;
|
|
}
|
|
|
|
/**
|
|
* Returns a themed presentation of all JavaScript code for the current page.
|
|
*
|
|
* References to JavaScript files are placed in a certain order: first, all
|
|
* 'core' files, then all 'module' and finally all 'theme' JavaScript files
|
|
* are added to the page. Then, all settings are output, followed by 'inline'
|
|
* JavaScript code. If running update.php, all preprocessing is disabled.
|
|
*
|
|
* Note that hook_js_alter(&$javascript) is called during this function call
|
|
* to allow alterations of the JavaScript during its presentation. Calls to
|
|
* drupal_add_js() from hook_js_alter() will not be added to the output
|
|
* presentation. The correct way to add JavaScript during hook_js_alter()
|
|
* is to add another element to the $javascript array, deriving from
|
|
* drupal_js_defaults(). See locale_js_alter() for an example of this.
|
|
*
|
|
* @param string $scope
|
|
* (optional) The scope for which the JavaScript rules should be returned.
|
|
* Defaults to 'header'.
|
|
* @param array $javascript
|
|
* (optional) An array with all JavaScript code. Defaults to the default
|
|
* JavaScript array for the given scope.
|
|
* @param bool $ajax
|
|
* (optional) If set to TRUE, this function will not output Drupal.settings.
|
|
*
|
|
* @return array
|
|
* An array ready to be passed into drupal_render() containing all JavaScript
|
|
* code segments and includes for the scope as HTML tags.
|
|
*
|
|
* @see drupal_add_js()
|
|
* @see locale_js_alter()
|
|
* @see drupal_js_defaults()
|
|
*/
|
|
function advagg_get_js($scope = 'header', array $javascript = array(), $ajax = FALSE) {
|
|
// Keep track of js added for ajaxPageState.
|
|
$page_state = &drupal_static(__FUNCTION__, array());
|
|
|
|
// Add in javascript if none was passed in.
|
|
if (empty($javascript) && !$ajax) {
|
|
$javascript = advagg_get_full_js();
|
|
}
|
|
|
|
// Return an empty array if no javascript is used.
|
|
if (empty($javascript)) {
|
|
return array();
|
|
}
|
|
|
|
// Filter out elements of the given scope.
|
|
$items = array();
|
|
foreach ($javascript as $key => $item) {
|
|
if (!empty($item['scope']) && $item['scope'] === $scope) {
|
|
$items[$key] = $item;
|
|
}
|
|
}
|
|
|
|
// Sort the JavaScript so that it appears in the correct order.
|
|
advagg_drupal_sort_css_js_stable($items);
|
|
|
|
// In Drupal 8, there's a JS_SETTING group for making setting variables
|
|
// appear last after libraries have loaded. In Drupal 7, this is forced
|
|
// without that group. We do not use the $key => $item type of iteration,
|
|
// because PHP uses an internal array pointer for that, and we're modifying
|
|
// the array order inside the loop.
|
|
if ($scope === 'footer' && !empty($items['settings'])) {
|
|
// Remove settings array from items.
|
|
$settings_js['settings'] = $items['settings'];
|
|
unset($items['settings']);
|
|
|
|
// Move $settings_js to the bottom of the js that was added to the
|
|
// header, but has now been moved to the footer via advagg_mod.
|
|
$counter = 0;
|
|
foreach ($items as $key => $item) {
|
|
if ($item['group'] > 9000) {
|
|
advagg_array_splice_assoc($items, $counter, 0, $settings_js);
|
|
unset($settings_js);
|
|
break;
|
|
}
|
|
++$counter;
|
|
}
|
|
// Nothing in the footer, add settings to the bottom of the array.
|
|
if (isset($settings_js)) {
|
|
$items = array_merge($items, $settings_js);
|
|
}
|
|
}
|
|
else {
|
|
foreach (array_keys($items) as $key) {
|
|
if ($items[$key]['type'] === 'setting') {
|
|
$item = $items[$key];
|
|
unset($items[$key]);
|
|
$items[$key] = $item;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Provide the page with information about the individual JavaScript files
|
|
// used, information not otherwise available when aggregation is enabled.
|
|
$page_state = array_merge($page_state, array_fill_keys(array_keys($items), 1));
|
|
|
|
// If we're outputting the header scope, then this should be the final time
|
|
// that drupal_get_js() is running, so add the setting to this output as well
|
|
// as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
|
|
// because drupal_get_js() was intentionally passed a $javascript argument
|
|
// stripped of settings, potentially in order to override how settings get
|
|
// output, so in this case, do not add the setting to this output.
|
|
// Also output the settings if we have pushed all javascript to the footer.
|
|
if (isset($items['settings'])) {
|
|
$items['settings']['data'][] = array(
|
|
'ajaxPageState' => array(
|
|
'js' => $page_state,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Do not include jQuery.extend(Drupal.settings) if the output is ajax.
|
|
if ($ajax) {
|
|
unset($items['settings']['data']);
|
|
}
|
|
|
|
// Semi support of the attributes array.
|
|
foreach ($items as $key => $item) {
|
|
if (!isset($item['attributes'])) {
|
|
continue;
|
|
}
|
|
if (isset($item['attributes']['defer'])) {
|
|
$items[$key]['defer'] = $item['attributes']['defer'];
|
|
}
|
|
if (isset($item['attributes']['async'])) {
|
|
$items[$key]['async'] = $item['attributes']['async'];
|
|
}
|
|
if (isset($item['attributes']['onload'])) {
|
|
$items[$key]['onload'] = $item['attributes']['onload'];
|
|
}
|
|
if (isset($item['attributes']['onerror'])) {
|
|
$items[$key]['onerror'] = $item['attributes']['onerror'];
|
|
}
|
|
}
|
|
|
|
// Render the HTML needed to load the JavaScript.
|
|
$elements = array(
|
|
'#type' => 'scripts',
|
|
'#items' => $items,
|
|
);
|
|
|
|
// Aurora and Omega themes uses alter without checking previous value.
|
|
if (variable_get('advagg_enforce_scripts_callback', TRUE)) {
|
|
// Get the element_info for scripts.
|
|
$scripts = element_info('scripts');
|
|
if (empty($scripts) || $scripts['#aggregate_callback'] !== '_advagg_aggregate_js') {
|
|
// Directly alter the static.
|
|
$element_info = &drupal_static('element_info');
|
|
advagg_element_info_alter($element_info);
|
|
if (function_exists('advagg_mod_element_info_alter')) {
|
|
advagg_mod_element_info_alter($element_info);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove ajaxPageState CSS/JS from Drupal.settings if ajax.js is not used.
|
|
if (function_exists('advagg_mod_js_no_ajaxpagestate')) {
|
|
if (variable_get('advagg_mod_js_no_ajaxpagestate', ADVAGG_MOD_JS_NO_AJAXPAGESTATE)) {
|
|
advagg_mod_js_no_ajaxpagestate($elements);
|
|
}
|
|
}
|
|
return $elements;
|
|
}
|
|
|
|
/**
|
|
* Remove a portion of the array and replace it with something else.
|
|
*
|
|
* @param array $input
|
|
* The input array.
|
|
* @param int $offset
|
|
* If offset is positive then the start of removed portion is at that offset
|
|
* from the beginning of the input array. If offset is negative then it starts
|
|
* that far from the end of the input array.
|
|
* @param int $length
|
|
* If length is omitted, removes everything from offset to the end of the
|
|
* array. If length is specified and is positive, then that many elements will
|
|
* be removed. If length is specified and is negative then the end of the
|
|
* removed portion will be that many elements from the end of the array. Tip:
|
|
* to remove everything from offset to the end of the array when replacement
|
|
* is also specified, use count($input) for length.
|
|
* @param mixed $replacement
|
|
* If replacement array is specified, then the removed elements are replaced
|
|
* with elements from this array.
|
|
* If offset and length are such that nothing is removed, then the elements
|
|
* from the replacement array are inserted in the place specified by the
|
|
* offset. Note that keys in replacement array are preserved.
|
|
* If replacement is just one element it is not necessary to put array()
|
|
* around it, unless the element is an array itself, an object or NULL.
|
|
*
|
|
* @see http://php.net/array-splice#111204
|
|
*/
|
|
function advagg_array_splice_assoc(array &$input, $offset, $length, $replacement) {
|
|
$replacement = (array) $replacement;
|
|
$key_indices = array_flip(array_keys($input));
|
|
if (isset($input[$offset]) && is_string($offset)) {
|
|
$offset = $key_indices[$offset];
|
|
}
|
|
if (isset($input[$length]) && is_string($length)) {
|
|
$length = $key_indices[$length] - $offset;
|
|
}
|
|
|
|
$input = array_slice($input, 0, $offset, TRUE) + $replacement + array_slice($input, $offset + $length, NULL, TRUE);
|
|
}
|
|
|
|
/**
|
|
* Callback for array_filter. Will return FALSE if strlen < 3.
|
|
*
|
|
* @param string $value
|
|
* A value from an array/object.
|
|
* @param int $min_len
|
|
* The strlen check length.
|
|
*
|
|
* @return bool
|
|
* TRUE or FALSE.
|
|
*/
|
|
function advagg_remove_short_keys($value, $min_len = 3) {
|
|
if (strlen($value) < $min_len) {
|
|
return TRUE;
|
|
}
|
|
else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all javascript scopes set in the $javascript array.
|
|
*
|
|
* @param array $javascript
|
|
* An array with all JavaScript code.
|
|
*
|
|
* @return array
|
|
* Array of scopes that are currently being used.
|
|
*/
|
|
function advagg_get_js_scopes(array $javascript) {
|
|
// Return if nothing given to us.
|
|
if (empty($javascript)) {
|
|
return array();
|
|
}
|
|
|
|
// Filter out elements of the given scope.
|
|
$scopes = array();
|
|
$js_settings_in_footer = FALSE;
|
|
foreach ($javascript as $name => $item) {
|
|
// Skip if the scope is not set.
|
|
if (!is_array($item) || empty($item['scope'])) {
|
|
continue;
|
|
}
|
|
if (!isset($scopes[$item['scope']])) {
|
|
$scopes[$item['scope']] = TRUE;
|
|
}
|
|
if ($name === 'settings' && $item['scope'] === 'footer') {
|
|
$js_settings_in_footer = TRUE;
|
|
}
|
|
}
|
|
|
|
// Default to header if nothing found.
|
|
if (empty($scopes)) {
|
|
$scopes['header'] = TRUE;
|
|
}
|
|
|
|
// Process header last.
|
|
if (isset($scopes['header']) && count($scopes) > 1) {
|
|
$temp = $scopes['header'];
|
|
unset($scopes['header']);
|
|
$scopes['header'] = $temp;
|
|
}
|
|
|
|
// Process footer last if everything has been moved to the footer.
|
|
if (isset($scopes['footer'])
|
|
&& count($scopes) > 1
|
|
&& $js_settings_in_footer
|
|
) {
|
|
$temp = $scopes['footer'];
|
|
unset($scopes['footer']);
|
|
$scopes['footer'] = $temp;
|
|
}
|
|
|
|
// Return the scopes.
|
|
return $scopes;
|
|
}
|
|
|
|
/**
|
|
* Apply the advagg changes to the $css_js_groups array.
|
|
*
|
|
* @param array $css_js_groups
|
|
* An array of CSS or JS groups as returned by drupal_group_css/js().
|
|
* @param array $plans
|
|
* An array of changes to do to the $css_js_groups array.
|
|
*
|
|
* @return array
|
|
* New version of $css_js_groups.
|
|
*/
|
|
function advagg_merge_plans(array $css_js_groups, array $plans) {
|
|
$used_keys = array();
|
|
foreach ($plans as $plan) {
|
|
$plan_added = FALSE;
|
|
foreach ($css_js_groups as $key => $group) {
|
|
// Remove files from the old css/js array.
|
|
$file_removed = FALSE;
|
|
foreach ($css_js_groups[$key]['items'] as $k => $values) {
|
|
if (is_array($values)
|
|
&& array_key_exists('data', $values)
|
|
&& is_array($plan['items']['files'])
|
|
&& is_string($values['data'])
|
|
) {
|
|
// If the CSS is a split file, the first file is very meaningful, and
|
|
// is probably the only file.
|
|
$first_file = reset($plan['items']['files']);
|
|
if (array_key_exists($values['data'], $plan['items']['files'])) {
|
|
unset($css_js_groups[$key]['items'][$k]);
|
|
$file_removed = TRUE;
|
|
}
|
|
// This part will try to add each split part matching the original CSS
|
|
// path and only remove the original group if the current part is the
|
|
// last part.
|
|
elseif (!empty($first_file['split'])) {
|
|
if ($values['data'] == $first_file['split_original']) {
|
|
if (!empty($first_file['split_last_part'])) {
|
|
unset($css_js_groups[$key]['items'][$k]);
|
|
}
|
|
$file_removed = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Replace first file of the old css/js array with one from advagg.
|
|
if ($file_removed && !$plan_added) {
|
|
$step = 0;
|
|
do {
|
|
++$step;
|
|
$insert_key = '' . floatval($key) . '.' . sprintf('%03d', $step);
|
|
} while (array_key_exists($insert_key, $css_js_groups));
|
|
$css_js_groups[(string) $insert_key] = $plan;
|
|
$plan_added = TRUE;
|
|
}
|
|
}
|
|
|
|
// Remove old css/js grouping if no files are left in it.
|
|
foreach ($css_js_groups as $key => $group) {
|
|
if (empty($css_js_groups[$key]['items'])) {
|
|
unset($css_js_groups[$key]);
|
|
}
|
|
}
|
|
|
|
if (!$plan_added) {
|
|
foreach ($css_js_groups as $key => $group) {
|
|
if (empty($group['items']['aggregate_filenames_hash'])
|
|
|| $group['items']['aggregate_filenames_hash'] != $plan['items']['aggregate_filenames_hash']
|
|
|| empty($group['items']['aggregate_contents_hash'])
|
|
|| $group['items']['aggregate_contents_hash'] != $plan['items']['aggregate_contents_hash']
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// Insert a unique key.
|
|
do {
|
|
$key = '' . (floatval($key) + 0.01);
|
|
} while (array_key_exists((string) $key, $css_js_groups) || array_key_exists((string) $key, $used_keys));
|
|
$used_keys[(string) $key] = TRUE;
|
|
$css_js_groups[(string) $key] = $plan;
|
|
$plan_added = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Key sort and normalize the array before returning it.
|
|
ksort($css_js_groups);
|
|
$css_js_groups = array_values($css_js_groups);
|
|
return $css_js_groups;
|
|
}
|
|
|
|
/**
|
|
* Function used to see if aggregation is enabled.
|
|
*
|
|
* @return bool
|
|
* The value of the advagg_enabled variable.
|
|
*/
|
|
function advagg_enabled() {
|
|
$init = &drupal_static(__FUNCTION__);
|
|
|
|
if (!empty($init)) {
|
|
return variable_get('advagg_enabled', ADVAGG_ENABLED);
|
|
}
|
|
|
|
// Set base_path if not set.
|
|
if (empty($GLOBALS['base_path'])) {
|
|
$GLOBALS['base_path'] = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/') . '/';
|
|
}
|
|
|
|
$init = TRUE;
|
|
// Disable AdvAgg if module needs to be upgraded from 1.x to 2.x.
|
|
if (variable_get('advagg_needs_update', ADVAGG_NEEDS_UPDATE)) {
|
|
if (!db_table_exists('advagg_aggregates_versions')) {
|
|
$GLOBALS['conf']['advagg_enabled'] = FALSE;
|
|
if (user_access('administer site configuration')) {
|
|
drupal_set_message(t('Please run <a href="@link">database updates</a>. AdvAgg will remain disabled until done.', array('@link' => url('update.php'))), 'error');
|
|
}
|
|
}
|
|
else {
|
|
variable_del('advagg_needs_update');
|
|
}
|
|
}
|
|
else {
|
|
// Get values and fill in defaults if needed.
|
|
$config_path = advagg_admin_config_root_path();
|
|
$current_path = current_path();
|
|
$arg = arg();
|
|
$arg += array(1 => '', 2 => '', 3 => '', 4 => '', 5 => '');
|
|
$admin_theme = variable_get('admin_theme');
|
|
|
|
// List of all the pages which will not have Advanced Aggregator enabled.
|
|
$list_of_pages = variable_get('advagg_disable_on_listed_pages');
|
|
|
|
$pages = trim(drupal_strtolower($list_of_pages));
|
|
// Convert the Drupal path to lowercase.
|
|
$path = drupal_strtolower(drupal_get_path_alias(current_path()));
|
|
|
|
// Compare the lowercase internal and lowercase path alias (if any).
|
|
$page_match = drupal_match_path($path, $pages);
|
|
if ($page_match) {
|
|
$GLOBALS['conf']['advagg_enabled'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_css'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_js'] = FALSE;
|
|
}
|
|
|
|
// Disable advagg if on admin page and configured to do so.
|
|
// AND theme is admin theme
|
|
// AND NOT /admin/reports/status
|
|
// AND NOT /admin/config/development/performance/.
|
|
// AND NOT /admin/appearance/settings/*.
|
|
// AND NOT /admin/config/development/performance/advagg/*.
|
|
if (variable_get('advagg_disable_on_admin', ADVAGG_DISABLE_ON_ADMIN)
|
|
&& $GLOBALS['theme'] === $admin_theme
|
|
&& path_is_admin($current_path)
|
|
&& !($arg[1] === 'reports' && $arg[2] === 'status')
|
|
&& !($arg[2] === 'development' && $arg[3] === 'performance' && empty($arg[4]))
|
|
&& !($arg[1] === 'appearance' && $arg[2] === 'settings' && !empty($arg[3]))
|
|
&& stripos($current_path, $config_path . '/advagg') !== 0
|
|
) {
|
|
$GLOBALS['conf']['advagg_enabled'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_css'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_js'] = FALSE;
|
|
}
|
|
|
|
// Check if the advagg cookie is set.
|
|
$cookie_name = 'AdvAggDisabled';
|
|
$bypass_cookie = FALSE;
|
|
$key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal'));
|
|
if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
|
|
$bypass_cookie = TRUE;
|
|
}
|
|
|
|
// Allow for AdvAgg to be enabled per request.
|
|
if (isset($_GET['advagg'])
|
|
&& $_GET['advagg'] == 1
|
|
&& !defined('MAINTENANCE_MODE')
|
|
&& (user_access('bypass advanced aggregation') || $bypass_cookie)
|
|
) {
|
|
$GLOBALS['conf']['advagg_enabled'] = TRUE;
|
|
$GLOBALS['conf']['preprocess_css'] = TRUE;
|
|
$GLOBALS['conf']['preprocess_js'] = TRUE;
|
|
}
|
|
|
|
// Disable AdvAgg if maintenance mode is defined.
|
|
if (defined('MAINTENANCE_MODE')) {
|
|
$GLOBALS['conf']['advagg_enabled'] = FALSE;
|
|
}
|
|
// Only run code below if advagg is enabled.
|
|
if (variable_get('advagg_enabled', ADVAGG_ENABLED)) {
|
|
// Do not use AdvAgg or preprocessing functions if the disable cookie is
|
|
// set.
|
|
if ($bypass_cookie && !isset($_GET['advagg'])) {
|
|
$GLOBALS['conf']['advagg_enabled'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_css'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_js'] = FALSE;
|
|
$bypass_cookie = TRUE;
|
|
|
|
// Let the user know that the AdvAgg bypass cookie is currently set.
|
|
static $msg_set;
|
|
if (!isset($msg_set) && variable_get('advagg_show_bypass_cookie_message', ADVAGG_SHOW_BYPASS_COOKIE_MESSAGE)) {
|
|
$msg_set = TRUE;
|
|
if (user_access('administer site configuration')) {
|
|
drupal_set_message(t('The AdvAgg bypass cookie is currently enabled. Turn it off by going to the <a href="@advagg_operations">AdvAgg Operations</a> page and clicking the <em>Toggle the "aggregation bypass cookie" for this browser</em> button.', array(
|
|
'@advagg_operations' => url(advagg_admin_config_root_path() . '/advagg/operations', array('fragment' => 'edit-bypass')),
|
|
)));
|
|
}
|
|
else {
|
|
drupal_set_message(t('The AdvAgg bypass cookie is currently enabled. Turn it off by <a href="@login">logging in</a> with a user with the "administer site configuration" permissions and going to the AdvAgg Operations page (located at @advagg_operations) and clicking the <em>Toggle the "aggregation bypass cookie" for this browser</em> button.', array(
|
|
'@login' => 'user/login',
|
|
'@advagg_operations' => advagg_admin_config_root_path() . '/advagg/operations',
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
// Disable advagg if requested.
|
|
if (isset($_GET['advagg'])
|
|
&& $_GET['advagg'] == -1
|
|
&& (user_access('bypass advanced aggregation') || $bypass_cookie)
|
|
) {
|
|
$GLOBALS['conf']['advagg_enabled'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_css'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_js'] = FALSE;
|
|
}
|
|
// Disable core preprocessing if requested.
|
|
if (isset($_GET['advagg-core'])
|
|
&& $_GET['advagg-core'] == 0
|
|
&& (user_access('bypass advanced aggregation') || $bypass_cookie)
|
|
) {
|
|
$GLOBALS['conf']['preprocess_css'] = FALSE;
|
|
$GLOBALS['conf']['preprocess_js'] = FALSE;
|
|
}
|
|
// Enable core preprocessing if requested.
|
|
if (isset($_GET['advagg-core'])
|
|
&& $_GET['advagg-core'] == 1
|
|
&& (user_access('bypass advanced aggregation') || $bypass_cookie)
|
|
) {
|
|
$GLOBALS['conf']['preprocess_css'] = TRUE;
|
|
$GLOBALS['conf']['preprocess_js'] = TRUE;
|
|
}
|
|
// Enable debugging if requested.
|
|
if (isset($_GET['advagg-debug'])
|
|
&& (user_access('bypass advanced aggregation') || $bypass_cookie)
|
|
) {
|
|
// Cast to an int.
|
|
$GLOBALS['conf']['advagg_debug'] = (int) $_GET['advagg-debug'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return variable_get('advagg_enabled', ADVAGG_ENABLED);
|
|
}
|
|
|
|
/**
|
|
* Get the current path used for advagg admin configuration.
|
|
*
|
|
* @return string
|
|
* Path to root advagg config.
|
|
*/
|
|
function advagg_admin_config_root_path() {
|
|
return variable_get('advagg_admin_config_root_path', ADVAGG_ADMIN_CONFIG_ROOT_PATH);
|
|
}
|
|
|
|
/**
|
|
* Get an array of all hooks and settings that affect aggregated files contents.
|
|
*
|
|
* @return array
|
|
* array('variables' => array(...), 'hooks' => array(...))
|
|
*/
|
|
function advagg_current_hooks_hash_array() {
|
|
$aggregate_settings = &drupal_static(__FUNCTION__);
|
|
if (!empty($aggregate_settings)) {
|
|
return $aggregate_settings;
|
|
}
|
|
list($css_path, $js_path) = advagg_get_root_files_dir();
|
|
|
|
// Put all enabled hooks and settings into a big array.
|
|
$aggregate_settings = array(
|
|
'variables' => array(
|
|
'advagg_gzip' => variable_get('advagg_gzip', ADVAGG_GZIP),
|
|
'advagg_brotli' => variable_get('advagg_brotli', ADVAGG_BROTLI),
|
|
'advagg_no_zopfli' => variable_get('advagg_no_zopfli', ADVAGG_NO_ZOPFLI),
|
|
'is_https' => $GLOBALS['is_https'],
|
|
'advagg_global_counter' => advagg_get_global_counter(),
|
|
'base_path' => $GLOBALS['base_path'],
|
|
'advagg_ie_css_selector_limiter' => variable_get('advagg_ie_css_selector_limiter', ADVAGG_IE_CSS_SELECTOR_LIMITER),
|
|
'advagg_ie_css_selector_limiter_value' => variable_get('advagg_ie_css_selector_limiter_value', ADVAGG_IE_CSS_SELECTOR_LIMITER_VALUE),
|
|
'advagg_scripts_scope_anywhere' => variable_get('advagg_scripts_scope_anywhere', ADVAGG_SCRIPTS_SCOPE_ANYWHERE),
|
|
'advagg_devel' => variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0 ? TRUE : FALSE,
|
|
'advagg_convert_absolute_to_relative_path' => variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH),
|
|
'advagg_convert_absolute_to_protocol_relative_path' => variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH),
|
|
'advagg_css_absolute_path' => variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH),
|
|
'advagg_force_https_path' => variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH),
|
|
'advagg_css_dir' => $css_path[0],
|
|
'advagg_js_dir' => $js_path[0],
|
|
),
|
|
'hooks' => advagg_hooks_implemented(FALSE),
|
|
);
|
|
// Add in language if locale is enabled.
|
|
if (module_exists('locale')) {
|
|
$aggregate_settings['variables']['language'] = isset($GLOBALS['language']->language) ? $GLOBALS['language']->language : '';
|
|
}
|
|
|
|
// Add the base url if so desired to.
|
|
if (variable_get('advagg_include_base_url', ADVAGG_INCLUDE_BASE_URL)) {
|
|
$aggregate_settings['variables']['base_url'] = $GLOBALS['base_url'];
|
|
}
|
|
|
|
// CDN module settings.
|
|
// Patch in https://www.drupal.org/node/1942230#comment-7927171 is fine
|
|
// on a technical level but I got frustrated with all the reports about it not
|
|
// working with no good reason as to why it doesn't work.
|
|
if (!function_exists('cdn_advagg_current_hooks_hash_array_alter') && module_exists('cdn')) {
|
|
$aggregate_settings['variables'][CDN_MODE_VARIABLE] = variable_get(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
|
|
$aggregate_settings['variables'][CDN_BASIC_FARFUTURE_VARIABLE] = variable_get(CDN_BASIC_FARFUTURE_VARIABLE, CDN_BASIC_FARFUTURE_DEFAULT);
|
|
$aggregate_settings['variables'][CDN_HTTPS_SUPPORT_VARIABLE] = variable_get(CDN_HTTPS_SUPPORT_VARIABLE, FALSE);
|
|
$aggregate_settings['variables'][CDN_STATUS_VARIABLE] = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED);
|
|
$aggregate_settings['variables']['cdn_request_is_https'] = cdn_request_is_https();
|
|
$aggregate_settings['variables']['cdn_check_drupal_path'] = cdn_check_drupal_path($_GET['q']);
|
|
}
|
|
|
|
// Allow other modules to add in their own settings and hooks.
|
|
// Call hook_advagg_current_hooks_hash_array_alter().
|
|
drupal_alter('advagg_current_hooks_hash_array', $aggregate_settings);
|
|
|
|
return $aggregate_settings;
|
|
}
|
|
|
|
/**
|
|
* Get the hash of all hooks and settings that affect aggregated files contents.
|
|
*
|
|
* @return string
|
|
* hash value.
|
|
*/
|
|
function advagg_get_current_hooks_hash() {
|
|
$current_hash = &drupal_static(__FUNCTION__);
|
|
|
|
if (empty($current_hash)) {
|
|
// Get all advagg hooks and variables in use.
|
|
$aggregate_settings = advagg_current_hooks_hash_array();
|
|
|
|
// Generate the hash.
|
|
$serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
|
|
$current_hash = drupal_hash_base64($serialize_function($aggregate_settings));
|
|
|
|
// Save into variables for verification purposes later on if not found.
|
|
$settings = advagg_get_hash_settings($current_hash);
|
|
if (empty($settings)) {
|
|
// Save new hash into.
|
|
advagg_set_hash_settings($current_hash, $aggregate_settings);
|
|
}
|
|
}
|
|
|
|
return $current_hash;
|
|
}
|
|
|
|
/**
|
|
* Store settings associated with hash.
|
|
*
|
|
* @param string $hash
|
|
* The hash.
|
|
* @param array $settings
|
|
* The settings associated with this hash.
|
|
*
|
|
* @return MergeQuery
|
|
* value from db_merge
|
|
*/
|
|
function advagg_set_hash_settings($hash, array $settings = array()) {
|
|
return db_merge('advagg_aggregates_hashes')
|
|
->key(array('hash' => $hash))
|
|
->fields(array(
|
|
'hash' => $hash,
|
|
'settings' => serialize($settings),
|
|
))
|
|
->execute();
|
|
}
|
|
|
|
/**
|
|
* Get back what hooks are implemented.
|
|
*
|
|
* @param bool $all
|
|
* If TRUE get all hooks related to css/js files.
|
|
* if FALSE get only the subset of hooks that alter the filename/contents.
|
|
*
|
|
* @return array
|
|
* List of hooks and what modules have implemented them.
|
|
*/
|
|
function advagg_hooks_implemented($all = TRUE) {
|
|
// Get hooks in use.
|
|
$hooks = array(
|
|
'advagg_get_css_file_contents_pre_alter' => array(),
|
|
'advagg_get_css_file_contents_alter' => array(),
|
|
'advagg_get_css_aggregate_contents_alter' => array(),
|
|
'advagg_get_js_file_contents_alter' => array(),
|
|
'advagg_get_js_aggregate_contents_alter' => array(),
|
|
'advagg_save_aggregate_pre_alter' => array(),
|
|
'advagg_save_aggregate_alter' => array(),
|
|
'advagg_current_hooks_hash_array_alter' => array(),
|
|
'advagg_get_root_files_dir_alter' => array(),
|
|
'advagg_context_alter' => array(),
|
|
);
|
|
if ($all) {
|
|
$hooks += array(
|
|
'advagg_build_aggregate_plans_alter' => array(),
|
|
'advagg_build_aggregate_plans_post_alter' => array(),
|
|
'advagg_changed_files' => array(),
|
|
'advagg_css_groups_alter' => array(),
|
|
'advagg_js_groups_alter' => array(),
|
|
'advagg_modify_css_pre_render_alter' => array(),
|
|
'advagg_modify_js_pre_render_alter' => array(),
|
|
'advagg_get_info_on_files_alter' => array(),
|
|
'advagg_hooks_implemented_alter' => array(),
|
|
'advagg_removed_aggregates' => array(),
|
|
'advagg_scan_for_changes' => array(),
|
|
'advagg_missing_root_file' => array(),
|
|
'js_alter' => array(),
|
|
'css_alter' => array(),
|
|
);
|
|
}
|
|
// Call hook_advagg_hooks_implemented_alter().
|
|
drupal_alter('advagg_hooks_implemented', $hooks, $all);
|
|
|
|
// Cache module_implements as this will load up .inc files.
|
|
$serialize_function = variable_get('advagg_serialize', ADVAGG_SERIALIZE);
|
|
$cid = 'advagg_hooks_implemented:' . (int) $all . ':' . drupal_hash_base64($serialize_function($hooks));
|
|
$cache = cache_get($cid, 'cache_bootstrap');
|
|
if (!empty($cache->data)) {
|
|
$hooks = $cache->data;
|
|
}
|
|
else {
|
|
foreach ($hooks as $hook => $values) {
|
|
$hooks[$hook] = module_implements($hook);
|
|
|
|
// Also check themes as drupal_alter() allows for themes to alter things.
|
|
$theme_keys = array_keys(list_themes());
|
|
if (!empty($theme_keys)) {
|
|
foreach ($theme_keys as $theme_key) {
|
|
$function = $theme_key . '_' . $hook;
|
|
if (function_exists($function)) {
|
|
$hooks[$hook][] = $theme_key;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
cache_set($cid, $hooks, 'cache_bootstrap', CACHE_TEMPORARY);
|
|
}
|
|
|
|
return $hooks;
|
|
}
|
|
|
|
/**
|
|
* Returns the hashes settings.
|
|
*
|
|
* @param string $hash
|
|
* The name of the variable to return.
|
|
*
|
|
* @return array
|
|
* The settings array or an empty array if not found.
|
|
*/
|
|
function advagg_get_hash_settings($hash) {
|
|
$settings = db_select('advagg_aggregates_hashes', 'aah')
|
|
->fields('aah', array('settings'))
|
|
->condition('hash', $hash)
|
|
->execute()
|
|
->fetchField();
|
|
|
|
return !empty($settings) ? unserialize($settings) : array();
|
|
}
|
|
|
|
/**
|
|
* Get the CSS and JS path for advagg.
|
|
*
|
|
* @param bool $reset
|
|
* Set to TRUE to reset the static variables.
|
|
*
|
|
* @return array
|
|
* Example return below:
|
|
*
|
|
* @code
|
|
* array(
|
|
* array(
|
|
* public://advagg_css,
|
|
* sites/default/files/advagg_css,
|
|
* ),
|
|
* array(
|
|
* public://advagg_js,
|
|
* sites/default/files/advagg_js,
|
|
* ),
|
|
* )
|
|
* @endcode
|
|
*/
|
|
function advagg_get_root_files_dir($reset = FALSE) {
|
|
$css_paths = &drupal_static(__FUNCTION__ . '_css');
|
|
$js_paths = &drupal_static(__FUNCTION__ . '_js');
|
|
|
|
// Make sure directories are available and writable.
|
|
if (empty($css_paths) || empty($js_paths) || $reset) {
|
|
// Default is public://.
|
|
$prefix = variable_get('advagg_root_dir_prefix', ADVAGG_ROOT_DIR_PREFIX);
|
|
$css_paths[0] = $prefix . 'advagg_css';
|
|
$js_paths[0] = $prefix . 'advagg_js';
|
|
|
|
file_prepare_directory($css_paths[0], FILE_CREATE_DIRECTORY);
|
|
file_prepare_directory($js_paths[0], FILE_CREATE_DIRECTORY);
|
|
|
|
// Set the URI of the directory.
|
|
$css_paths[1] = advagg_get_relative_path($css_paths[0], 'css');
|
|
$js_paths[1] = advagg_get_relative_path($js_paths[0], 'js');
|
|
|
|
// If the css or js got a path, use it for the other missing one.
|
|
if (empty($css_paths[1]) && !empty($js_paths[1])) {
|
|
$css_paths[1] = str_replace('/advagg_js', '/advagg_css', $js_paths[1]);
|
|
}
|
|
elseif (empty($js_paths[1]) && !empty($css_paths[1])) {
|
|
$js_paths[1] = str_replace('/advagg_css', '/advagg_js', $css_paths[1]);
|
|
}
|
|
|
|
// Fix if empty.
|
|
if (empty($css_paths[1])) {
|
|
$css_paths[1] = $css_paths[0];
|
|
}
|
|
if (empty($js_paths[1])) {
|
|
$js_paths[1] = $js_paths[0];
|
|
}
|
|
|
|
// Allow other modules to alter css and js paths.
|
|
// Call hook_advagg_get_root_files_dir_alter()
|
|
drupal_alter('advagg_get_root_files_dir', $css_paths, $js_paths);
|
|
}
|
|
|
|
return array($css_paths, $js_paths);
|
|
}
|
|
|
|
/**
|
|
* Given a uri, get the relative_path.
|
|
*
|
|
* @param string $uri
|
|
* The uri for the stream wrapper.
|
|
* @param string $type
|
|
* (Optional) String: css or js.
|
|
*
|
|
* @return string
|
|
* The relative path of the uri.
|
|
*
|
|
* @see https://www.drupal.org/node/837794#comment-9124435
|
|
*/
|
|
function advagg_get_relative_path($uri, $type = '') {
|
|
$wrapper = file_stream_wrapper_get_instance_by_uri($uri);
|
|
if ($wrapper instanceof DrupalLocalStreamWrapper) {
|
|
$relative_path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
|
|
}
|
|
else {
|
|
$relative_path = parse_url(file_create_url($uri), PHP_URL_PATH);
|
|
if (empty($relative_path) && !empty($uri)) {
|
|
$filename = advagg_generate_advagg_filename_from_db($type);
|
|
$relative_path = parse_url(file_create_url("{$uri}/{$filename}"), PHP_URL_PATH);
|
|
$end = strpos($relative_path, "/{$filename}");
|
|
if ($end !== FALSE) {
|
|
$relative_path = substr($relative_path, 0, $end);
|
|
}
|
|
}
|
|
if (substr($relative_path, 0, strlen($GLOBALS['base_path'])) == $GLOBALS['base_path']) {
|
|
$relative_path = substr($relative_path, strlen($GLOBALS['base_path']));
|
|
}
|
|
$relative_path = ltrim($relative_path, '/');
|
|
}
|
|
return $relative_path;
|
|
}
|
|
|
|
/**
|
|
* Builds the requested CSS/JS aggregates.
|
|
*
|
|
* @param array $filenames
|
|
* Array of AdvAgg filenames to generate.
|
|
* @param string $type
|
|
* String: css or js.
|
|
*
|
|
* @return array
|
|
* Array keyed by filename, value is result from advagg_missing_create_file().
|
|
*/
|
|
function advagg_build_aggregates(array $filenames, $type) {
|
|
// Check input values.
|
|
if (empty($filenames)) {
|
|
return array();
|
|
}
|
|
if (empty($type)) {
|
|
$filename = reset($filenames);
|
|
$type = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
|
}
|
|
|
|
// Call the file generation function directly.
|
|
module_load_include('inc', 'advagg', 'advagg.missing');
|
|
list($css_path, $js_path) = advagg_get_root_files_dir();
|
|
$return = array();
|
|
foreach ($filenames as $filename) {
|
|
// Skip if the file exists.
|
|
if ($type === 'css') {
|
|
$uri = $css_path[0] . '/' . $filename;
|
|
}
|
|
elseif ($type === 'js') {
|
|
$uri = $js_path[0] . '/' . $filename;
|
|
}
|
|
if (file_exists($uri)) {
|
|
continue;
|
|
}
|
|
|
|
// Only create the file if we have a lock.
|
|
$lock_name = 'advagg_' . $filename;
|
|
if (variable_get('advagg_no_locks', ADVAGG_NO_LOCKS)) {
|
|
$return[$filename] = advagg_missing_create_file($filename);
|
|
}
|
|
elseif (lock_acquire($lock_name, 10)) {
|
|
$return[$filename] = advagg_missing_create_file($filename);
|
|
lock_release($lock_name);
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Gets the core CSS/JS included in this ajax request.
|
|
*
|
|
* Used so core JS can be rendered through the AdvAgg pipeline.
|
|
*
|
|
* @see ajax_render()
|
|
*
|
|
* @return array
|
|
* Returns an array containing $styles, $scripts_header, $scripts_footer,
|
|
* $items, and $settings.
|
|
*/
|
|
function advagg_build_ajax_js_css() {
|
|
$settings = array();
|
|
// Ajax responses aren't rendered with html.tpl.php, so we have to call
|
|
// drupal_get_css() and drupal_get_js() here, in order to have new files added
|
|
// during this request to be loaded by the page. We only want to send back
|
|
// files that the page hasn't already loaded, so we implement simple diffing
|
|
// logic using array_diff_key().
|
|
foreach (array('css', 'js') as $type) {
|
|
// It is highly suspicious if $_POST['ajax_page_state'][$type] is empty,
|
|
// since the base page ought to have at least one JS file and one CSS file
|
|
// loaded. It probably indicates an error, and rather than making the page
|
|
// reload all of the files, instead we return no new files.
|
|
if (empty($_POST['ajax_page_state'][$type])) {
|
|
$items[$type] = array();
|
|
$scripts = drupal_add_js();
|
|
if (!empty($scripts['settings'])) {
|
|
$settings = $scripts['settings'];
|
|
}
|
|
}
|
|
else {
|
|
$function = 'drupal_add_' . $type;
|
|
// Get the current css/js needed for this page.
|
|
$items[$type] = $function();
|
|
// Call hook_js_alter() OR hook_css_alter().
|
|
drupal_alter($type, $items[$type]);
|
|
// @todo Inline CSS and JS items are indexed numerically. These can't be
|
|
// reliably diffed with array_diff_key(), since the number can change
|
|
// due to factors unrelated to the inline content, so for now, we strip
|
|
// the inline items from Ajax responses, and can add support for them
|
|
// when drupal_add_css() and drupal_add_js() are changed to use a hash
|
|
// of the inline content as the array key.
|
|
foreach ($items[$type] as $key => $item) {
|
|
if (is_numeric($key)) {
|
|
unset($items[$type][$key]);
|
|
}
|
|
}
|
|
// Ensure that the page doesn't reload what it already has.
|
|
// @ignore security_17
|
|
$items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]);
|
|
}
|
|
}
|
|
|
|
// Render the HTML to load these files, and add AJAX commands to insert this
|
|
// HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
|
|
// data from being altered again, as we already altered it above. Settings are
|
|
// handled separately, afterwards.
|
|
$scripts = drupal_add_js();
|
|
if (isset($scripts['settings'])) {
|
|
$settings = $scripts['settings'];
|
|
unset($items['js']['settings']);
|
|
}
|
|
|
|
$styles = drupal_get_css($items['css'], TRUE);
|
|
$scripts_footer = drupal_get_js('footer', $items['js'], TRUE);
|
|
$scripts_header = drupal_get_js('header', $items['js'], TRUE);
|
|
return array($styles, $scripts_header, $scripts_footer, $items, $settings);
|
|
}
|
|
|
|
/**
|
|
* Given the type lets us know if advagg is enabled or disabled.
|
|
*
|
|
* @param string $type
|
|
* String: css or js.
|
|
*
|
|
* @return bool
|
|
* TRUE or FALSE.
|
|
*/
|
|
function advagg_file_aggregation_enabled($type) {
|
|
if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE === 'update') {
|
|
return FALSE;
|
|
}
|
|
if (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation')) {
|
|
return FALSE;
|
|
}
|
|
if ($type === 'css') {
|
|
return variable_get('preprocess_css', FALSE);
|
|
}
|
|
if ($type === 'js') {
|
|
return variable_get('preprocess_js', FALSE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update atime inside advagg_aggregates_versions and cache_advagg_info.
|
|
*
|
|
* @param array $files
|
|
* List of files in the aggregate as well as the aggregate name.
|
|
*
|
|
* @return bool
|
|
* Return TRUE if anything was written to the database.
|
|
*/
|
|
function advagg_multi_update_atime(array $files) {
|
|
$write_done = FALSE;
|
|
$records = array();
|
|
foreach ($files as $values) {
|
|
// Create the cache id.
|
|
$cid = 'advagg:db:' . $values['aggregate_filenames_hash'] . ADVAGG_SPACE . $values['aggregate_contents_hash'];
|
|
// Create the db record.
|
|
$records[$cid] = array(
|
|
'aggregate_filenames_hash' => $values['aggregate_filenames_hash'],
|
|
'aggregate_contents_hash' => $values['aggregate_contents_hash'],
|
|
'atime' => REQUEST_TIME,
|
|
);
|
|
}
|
|
|
|
// Use the cache.
|
|
$cids = array_keys($records);
|
|
$caches = cache_get_multiple($cids, 'cache_advagg_info');
|
|
if (!empty($caches)) {
|
|
foreach ($caches as $cache) {
|
|
// See if the atime value needs to be updated.
|
|
if (!empty($cache->data['atime']) && $cache->data['atime'] > REQUEST_TIME - (12 * 60 * 60)) {
|
|
// If atime is less than 12 hours old, do nothing.
|
|
unset($records[$cache->cid]);
|
|
}
|
|
}
|
|
}
|
|
if (empty($records)) {
|
|
return $write_done;
|
|
}
|
|
|
|
foreach ($records as $cid => $record) {
|
|
// Update atime in DB.
|
|
$result = db_merge('advagg_aggregates_versions')
|
|
->key(array(
|
|
'aggregate_filenames_hash' => $record['aggregate_filenames_hash'],
|
|
'aggregate_contents_hash' => $record['aggregate_contents_hash'],
|
|
))
|
|
->fields(array('atime' => $record['atime']))
|
|
->execute();
|
|
if (!$write_done && $result) {
|
|
$write_done = TRUE;
|
|
}
|
|
|
|
// Update the atime in the cache.
|
|
// Get fresh copy of the cache.
|
|
$cache = cache_get($cid, 'cache_advagg_info');
|
|
// Set the atime.
|
|
if (empty($cache->data)) {
|
|
$cache = new stdClass();
|
|
}
|
|
$cache->data['atime'] = REQUEST_TIME;
|
|
|
|
// Write to the cache.
|
|
// CACHE_PERMANENT isn't good here. Use 2 weeks from now + 0-45 days.
|
|
// The random 0 to 45 day addition is to prevent a cache stampede.
|
|
cache_set($cid, $cache->data, 'cache_advagg_info', round(REQUEST_TIME + 1209600 + mt_rand(0, 3888000), -3));
|
|
}
|
|
return $write_done;
|
|
}
|
|
|
|
/**
|
|
* Return the advagg_global_counter variable.
|
|
*
|
|
* @return int
|
|
* Int value.
|
|
*/
|
|
function advagg_get_global_counter() {
|
|
$global_counter = variable_get('advagg_global_counter', ADVAGG_GLOBAL_COUNTER);
|
|
|
|
return $global_counter;
|
|
}
|
|
|
|
/**
|
|
* Cache clear callback for admin_menu/flush-cache/advagg.
|
|
*/
|
|
function advagg_admin_flush_cache() {
|
|
module_load_include('inc', 'advagg', 'advagg.admin');
|
|
advagg_admin_flush_cache_button();
|
|
}
|
|
|
|
/**
|
|
* Returns HTML for a generic HTML tag with attributes.
|
|
*
|
|
* @param array $variables
|
|
* An associative array containing:
|
|
* - element: An associative array describing the tag:
|
|
* - #tag: The tag name to output. Typical tags added to the HTML HEAD:
|
|
* - meta: To provide meta information, such as a page refresh.
|
|
* - link: To refer to stylesheets and other contextual information.
|
|
* - script: To load JavaScript.
|
|
* - #attributes: (optional) An array of HTML attributes to apply to the
|
|
* tag.
|
|
* - #value: (optional) A string containing tag content, such as inline
|
|
* CSS.
|
|
* - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA
|
|
* wrapper prefix.
|
|
* - #value_suffix: (optional) A string to append to #value, e.g. a CDATA
|
|
* wrapper suffix.
|
|
*/
|
|
function theme_html_script_tag(array $variables) {
|
|
$element = $variables['element'];
|
|
$attributes = '';
|
|
$onload = '';
|
|
$onerror = '';
|
|
if (isset($element['#attributes'])) {
|
|
// On Load.
|
|
if (!empty($element['#attributes']['onload'])) {
|
|
$onload = $element['#attributes']['onload'];
|
|
unset($element['#attributes']['onload']);
|
|
}
|
|
// On Error.
|
|
if (!empty($element['#attributes']['onerror'])) {
|
|
$onerror = $element['#attributes']['onerror'];
|
|
unset($element['#attributes']['onerror']);
|
|
}
|
|
$attributes = !empty($element['#attributes']) ? drupal_attributes($element['#attributes']) : '';
|
|
if (!empty($onload)) {
|
|
$attributes .= ' onload="' . advagg_jsspecialchars($onload) . '"';
|
|
}
|
|
if (!empty($onerror)) {
|
|
$attributes .= ' onerror="' . advagg_jsspecialchars($onerror) . '"';
|
|
}
|
|
}
|
|
|
|
if (!isset($element['#value'])) {
|
|
return '<' . $element['#tag'] . $attributes . " />\n";
|
|
}
|
|
else {
|
|
$output = '<' . $element['#tag'] . $attributes . '>';
|
|
if (isset($element['#value_prefix'])) {
|
|
$output .= $element['#value_prefix'];
|
|
}
|
|
$output .= $element['#value'];
|
|
if (isset($element['#value_suffix'])) {
|
|
$output .= $element['#value_suffix'];
|
|
}
|
|
$output .= '</' . $element['#tag'] . ">\n";
|
|
return $output;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replace quotes with the html version of it.
|
|
*
|
|
* @param string $string
|
|
* Input string. Convert quotes to html chars.
|
|
*
|
|
* @return string
|
|
* Transformed string.
|
|
*/
|
|
function advagg_jsspecialchars($string = '') {
|
|
$string = str_replace('"', '"', $string);
|
|
$string = str_replace("'", ''', $string);
|
|
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Callback for pre_render to add elements needed for JavaScript to be rendered.
|
|
*
|
|
* This function evaluates the aggregation enabled/disabled condition on a group
|
|
* by group basis by testing whether an aggregate file has been made for the
|
|
* group rather than by testing the site-wide aggregation setting. This allows
|
|
* this function to work correctly even if modules have implemented custom
|
|
* logic for grouping and aggregating files.
|
|
*
|
|
* @param array $elements
|
|
* A render array containing:
|
|
* - #items: The JavaScript items as returned by drupal_add_js() and
|
|
* altered by drupal_get_js().
|
|
* - #group_callback: A function to call to group #items. Following
|
|
* this function, #aggregate_callback is called to aggregate items within
|
|
* the same group into a single file.
|
|
* - #aggregate_callback: A function to call to aggregate the items within
|
|
* the groups arranged by the #group_callback function.
|
|
*
|
|
* @return array
|
|
* A render array that will render to a string of JavaScript tags.
|
|
*
|
|
* @see drupal_get_js()
|
|
*/
|
|
function advagg_pre_render_scripts(array $elements) {
|
|
// Don't run it twice.
|
|
if (!empty($elements['#groups'])) {
|
|
return $elements;
|
|
}
|
|
|
|
// Group and aggregate the items.
|
|
if (isset($elements['#group_callback'])) {
|
|
// Call advagg_group_js().
|
|
$elements['#groups'] = $elements['#group_callback']($elements['#items']);
|
|
}
|
|
if (isset($elements['#aggregate_callback'])) {
|
|
// Call _advagg_aggregate_js().
|
|
$elements['#aggregate_callback']($elements['#groups']);
|
|
}
|
|
|
|
// A dummy query-string is added to filenames, to gain control over
|
|
// browser-caching. The string changes on every update or full cache
|
|
// flush, forcing browsers to load a new copy of the files, as the
|
|
// URL changed. Files that should not be cached (see drupal_add_js())
|
|
// get REQUEST_TIME as query-string instead, to enforce reload on every
|
|
// page request.
|
|
$default_query_string = variable_get('css_js_query_string', '0');
|
|
|
|
// For inline JavaScript to validate as XHTML, all JavaScript containing
|
|
// XHTML needs to be wrapped in CDATA. To make that backwards compatible
|
|
// with HTML 4, we need to comment out the CDATA-tag.
|
|
$embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
|
|
$embed_suffix = "\n//--><!]]>\n";
|
|
|
|
// Since JavaScript may look for arguments in the URL and act on them, some
|
|
// third-party code might require the use of a different query string.
|
|
$js_version_string = variable_get('drupal_js_version_query_string', 'v=');
|
|
|
|
// Defaults for each SCRIPT element.
|
|
$element_defaults = array(
|
|
'#type' => 'html_script_tag',
|
|
'#tag' => 'script',
|
|
'#value' => '',
|
|
'#attributes' => array(
|
|
'type' => 'text/javascript',
|
|
),
|
|
);
|
|
$hooks = theme_get_registry(FALSE);
|
|
if (empty($hooks['html_script_tag'])) {
|
|
$element_defaults['#type'] = 'html_tag';
|
|
}
|
|
|
|
// Loop through each group.
|
|
foreach ($elements['#groups'] as $group) {
|
|
// If a group of files has been aggregated into a single file,
|
|
// $group['data'] contains the URI of the aggregate file. Add a single
|
|
// script element for this file.
|
|
if (isset($group['type']) && $group['type'] === 'file' && isset($group['data'])) {
|
|
$element = $element_defaults;
|
|
$element['#attributes']['src'] = advagg_file_create_url($group['data']) . ($group['cache'] ? '' : '?' . REQUEST_TIME);
|
|
$element['#browsers'] = $group['browsers'];
|
|
if (!empty($group['defer'])) {
|
|
$element['#attributes']['defer'] = 'defer';
|
|
}
|
|
if (!empty($group['async'])) {
|
|
$element['#attributes']['async'] = 'async';
|
|
}
|
|
if (!empty($group['onload'])) {
|
|
if (!isset($element['#attributes']['onload'])) {
|
|
$element['#attributes']['onload'] = '';
|
|
}
|
|
$element['#attributes']['onload'] .= $group['onload'];
|
|
}
|
|
if (!empty($group['onerror'])) {
|
|
if (!isset($element['#attributes']['onerror'])) {
|
|
$element['#attributes']['onerror'] = '';
|
|
}
|
|
$element['#attributes']['onerror'] .= $group['onerror'];
|
|
}
|
|
if (!empty($group['attributes'])) {
|
|
$element['#attributes'] += $group['attributes'];
|
|
}
|
|
$elements[] = $element;
|
|
}
|
|
// For non-file types, and non-aggregated files, add a script element per
|
|
// item.
|
|
else {
|
|
foreach ($group['items'] as $item) {
|
|
// Skip if data is empty.
|
|
if (empty($item['data'])) {
|
|
continue;
|
|
}
|
|
|
|
// Element properties that do not depend on item type.
|
|
$element = $element_defaults;
|
|
if (!empty($item['defer'])) {
|
|
$element['#attributes']['defer'] = 'defer';
|
|
}
|
|
if (!empty($item['async'])) {
|
|
$element['#attributes']['async'] = 'async';
|
|
}
|
|
if (!empty($item['onload'])) {
|
|
if (!isset($element['#attributes']['onload'])) {
|
|
$element['#attributes']['onload'] = '';
|
|
}
|
|
$element['#attributes']['onload'] .= $item['onload'];
|
|
}
|
|
if (!empty($item['onerror'])) {
|
|
if (!isset($element['#attributes']['onerror'])) {
|
|
$element['#attributes']['onerror'] = '';
|
|
}
|
|
$element['#attributes']['onerror'] .= $item['onerror'];
|
|
}
|
|
if (!empty($group['attributes'])) {
|
|
$element['#attributes'] += $group['attributes'];
|
|
}
|
|
$element['#browsers'] = isset($item['browsers']) ? $item['browsers'] : array();
|
|
|
|
// Crude type detection if needed.
|
|
if (empty($item['type'])) {
|
|
if (is_array($item['data'])) {
|
|
$item['type'] = 'setting';
|
|
}
|
|
elseif (strpos($item['data'], 'http://') === 0 || strpos($item['data'], 'https://') === 0 || strpos($item['data'], '//') === 0) {
|
|
$item['type'] = 'external';
|
|
}
|
|
elseif (file_exists(trim($item['data']))) {
|
|
$item['type'] = 'file';
|
|
}
|
|
else {
|
|
$item['type'] = 'inline';
|
|
}
|
|
}
|
|
// Element properties that depend on item type.
|
|
switch ($item['type']) {
|
|
case 'setting':
|
|
$data = advagg_cleanup_settings_array(drupal_array_merge_deep_array(array_filter($item['data'], 'is_array')));
|
|
$json_data = advagg_json_encode($data);
|
|
$element['#value_prefix'] = $embed_prefix;
|
|
$element['#value'] = 'jQuery.extend(Drupal.settings, ' . $json_data . ");";
|
|
$element['#value_suffix'] = $embed_suffix;
|
|
break;
|
|
|
|
case 'inline':
|
|
// If a BOM is found, convert the string to UTF-8.
|
|
$encoding = advagg_get_encoding_from_bom($item['data']);
|
|
if (!empty($encoding)) {
|
|
$item['data'] = advagg_convert_to_utf8($item['data'], $encoding);
|
|
}
|
|
|
|
$element['#value_prefix'] = $embed_prefix;
|
|
$element['#value'] = $item['data'];
|
|
$element['#value_suffix'] = $embed_suffix;
|
|
break;
|
|
|
|
case 'file':
|
|
$query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
|
|
$cache_validator = REQUEST_TIME;
|
|
if (!empty($item['cache'])) {
|
|
$cache_validator = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];;
|
|
}
|
|
|
|
$element['#attributes']['src'] = advagg_file_create_url($item['data']) . $query_string_separator . $cache_validator;
|
|
break;
|
|
|
|
case 'external':
|
|
// Convert to protocol relative path.
|
|
$file_uri = $item['data'];
|
|
if (variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH)) {
|
|
$file_uri = advagg_convert_abs_to_protocol($item['data']);
|
|
}
|
|
$element['#attributes']['src'] = $file_uri;
|
|
break;
|
|
}
|
|
|
|
$elements[] = $element;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $elements;
|
|
}
|
|
|
|
/**
|
|
* Get the prefix and suffix for inline css.
|
|
*
|
|
* @return array
|
|
* An array where the prefix is key 0 and suffix is key 1.
|
|
*/
|
|
function advagg_get_css_prefix_suffix() {
|
|
$embed_prefix = "\n/* <![CDATA[ */\n";
|
|
$embed_suffix = "\n/* ]]> */\n";
|
|
return array($embed_prefix, $embed_suffix);
|
|
}
|
|
|
|
/**
|
|
* A #pre_render callback to add elements needed for CSS tags to be rendered.
|
|
*
|
|
* For production websites, LINK tags are preferable to STYLE tags with @import
|
|
* statements, because:
|
|
* - They are the standard tag intended for linking to a resource.
|
|
* - On Firefox 2 and perhaps other browsers, CSS files included with @import
|
|
* statements don't get saved when saving the complete web page for offline
|
|
* use: http://drupal.org/node/145218.
|
|
* - On IE, if only LINK tags and no @import statements are used, all the CSS
|
|
* files are downloaded in parallel, resulting in faster page load, but if
|
|
* the @import statements are used and span across multiple STYLE tags, all
|
|
* the ones from 1 STYLE tag must be downloaded before downloading begins for
|
|
* the next STYLE tag. Furthermore, IE7 does not support media declaration on
|
|
* the @import statement, so multiple STYLE tags must be used when different
|
|
* files are for different media types. Non-IE browsers always download in
|
|
* parallel, so this is an IE-specific performance quirk:
|
|
* http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
|
|
*
|
|
* However, IE has an annoying limit of 31 total CSS inclusion tags
|
|
* (http://drupal.org/node/228818) and LINK tags are limited to one file per
|
|
* tag, whereas STYLE tags can contain multiple @import statements allowing
|
|
* multiple files to be loaded per tag. When CSS aggregation is disabled, a
|
|
* Drupal site can easily have more than 31 CSS files that need to be loaded, so
|
|
* using LINK tags exclusively would result in a site that would display
|
|
* incorrectly in IE. Depending on different needs, different strategies can be
|
|
* employed to decide when to use LINK tags and when to use STYLE tags.
|
|
*
|
|
* The strategy employed by this function is to use LINK tags for all aggregate
|
|
* files and for all files that cannot be aggregated (e.g., if 'preprocess' is
|
|
* set to FALSE or the type is 'external'), and to use STYLE tags for groups
|
|
* of files that could be aggregated together but aren't (e.g., if the site-wide
|
|
* aggregation setting is disabled). This results in all LINK tags when
|
|
* aggregation is enabled, a guarantee that as many or only slightly more tags
|
|
* are used with aggregation disabled than enabled (so that if the limit were to
|
|
* be crossed with aggregation enabled, the site developer would also notice the
|
|
* problem while aggregation is disabled), and an easy way for a developer to
|
|
* view HTML source while aggregation is disabled and know what files will be
|
|
* aggregated together when aggregation becomes enabled.
|
|
*
|
|
* This function evaluates the aggregation enabled/disabled condition on a group
|
|
* by group basis by testing whether an aggregate file has been made for the
|
|
* group rather than by testing the site-wide aggregation setting. This allows
|
|
* this function to work correctly even if modules have implemented custom
|
|
* logic for grouping and aggregating files.
|
|
*
|
|
* @param array $elements
|
|
* A render array containing:
|
|
* - '#items': The CSS items as returned by drupal_add_css() and altered by
|
|
* drupal_get_css().
|
|
* - '#group_callback': A function to call to group #items to enable the use
|
|
* of fewer tags by aggregating files and/or using multiple @import
|
|
* statements within a single tag.
|
|
* - '#aggregate_callback': A function to call to aggregate the items within
|
|
* the groups arranged by the #group_callback function.
|
|
*
|
|
* @return array
|
|
* A render array that will render to a string of XHTML CSS tags.
|
|
*
|
|
* @see drupal_get_css()
|
|
*/
|
|
function advagg_pre_render_styles(array $elements) {
|
|
// Skip if advagg is disabled.
|
|
if (!advagg_enabled()) {
|
|
return drupal_pre_render_styles($elements);
|
|
}
|
|
|
|
// Don't run it twice.
|
|
if (!empty($elements['#groups'])) {
|
|
return $elements;
|
|
}
|
|
|
|
// Group and aggregate the items.
|
|
if (isset($elements['#group_callback'])) {
|
|
// Call drupal_group_css().
|
|
$elements['#groups'] = $elements['#group_callback']($elements['#items']);
|
|
}
|
|
if (isset($elements['#aggregate_callback'])) {
|
|
// Call _advagg_aggregate_css().
|
|
$elements['#aggregate_callback']($elements['#groups']);
|
|
}
|
|
|
|
// A dummy query-string is added to filenames, to gain control over
|
|
// browser-caching. The string changes on every update or full cache
|
|
// flush, forcing browsers to load a new copy of the files, as the
|
|
// URL changed.
|
|
$query_string = variable_get('css_js_query_string', '0');
|
|
|
|
// For inline CSS to validate as XHTML, all CSS containing XHTML needs to be
|
|
// wrapped in CDATA. To make that backwards compatible with HTML 4, we need to
|
|
// comment out the CDATA-tag.
|
|
// @see https://www.drupal.org/node/1021622
|
|
list($embed_prefix, $embed_suffix) = advagg_get_css_prefix_suffix();
|
|
|
|
// Defaults for LINK and STYLE elements.
|
|
$link_element_defaults = array(
|
|
'#type' => 'html_tag',
|
|
'#tag' => 'link',
|
|
'#attributes' => array(
|
|
'type' => 'text/css',
|
|
'rel' => 'stylesheet',
|
|
),
|
|
);
|
|
$style_element_defaults = array(
|
|
'#type' => 'html_tag',
|
|
'#tag' => 'style',
|
|
'#attributes' => array(
|
|
'type' => 'text/css',
|
|
),
|
|
);
|
|
|
|
// Loop through each group.
|
|
foreach ($elements['#groups'] as $group) {
|
|
switch ($group['type']) {
|
|
// For file items, there are three possibilities.
|
|
// - The group has been aggregated: in this case, output a LINK tag for
|
|
// the aggregate file.
|
|
// - The group can be aggregated but has not been (most likely because
|
|
// the site administrator disabled the site-wide setting): in this case,
|
|
// output as few STYLE tags for the group as possible, using @import
|
|
// statement for each file in the group. This enables us to stay within
|
|
// IE's limit of 31 total CSS inclusion tags.
|
|
// - The group contains items not eligible for aggregation (their
|
|
// 'preprocess' flag has been set to FALSE): in this case, output a LINK
|
|
// tag for each file.
|
|
case 'file':
|
|
// The group has been aggregated into a single file: output a LINK tag
|
|
// for the aggregate file.
|
|
if (isset($group['data'])) {
|
|
$element = $link_element_defaults;
|
|
$element['#attributes']['href'] = advagg_file_create_url($group['data']);
|
|
$element['#attributes']['media'] = $group['media'];
|
|
$element['#browsers'] = $group['browsers'];
|
|
if (!empty($group['attributes'])) {
|
|
$element['#attributes'] += $group['attributes'];
|
|
}
|
|
$elements[] = $element;
|
|
}
|
|
// The group can be aggregated, but hasn't been: combine multiple items
|
|
// into as few STYLE tags as possible.
|
|
elseif ($group['preprocess']) {
|
|
$import = array();
|
|
foreach ($group['items'] as $item) {
|
|
// A theme's .info file may have an entry for a file that doesn't
|
|
// exist as a way of overriding a module or base theme CSS file from
|
|
// being added to the page. Normally, file_exists() calls that need
|
|
// to run for every page request should be minimized, but this one
|
|
// is okay, because it only runs when CSS aggregation is disabled.
|
|
// On a server under heavy enough load that file_exists() calls need
|
|
// to be minimized, CSS aggregation should be enabled, in which case
|
|
// this code is not run. When aggregation is enabled,
|
|
// drupal_load_stylesheet() checks file_exists(), but only when
|
|
// building the aggregate file, which is then reused for many page
|
|
// requests.
|
|
if (file_exists($item['data'])) {
|
|
// The dummy query string needs to be added to the URL to control
|
|
// browser-caching. IE7 does not support a media type on the
|
|
// "@import" statement, so we instead specify the media for the
|
|
// group on the STYLE tag.
|
|
$import[] = '@import url("' . check_plain(advagg_file_create_url($item['data']) . '?' . $query_string) . '");';
|
|
}
|
|
}
|
|
// In addition to IE's limit of 31 total CSS inclusion tags, it also
|
|
// has a limit of 31 @import statements per STYLE tag.
|
|
while (!empty($import)) {
|
|
$import_batch = array_slice($import, 0, 31);
|
|
$import = array_slice($import, 31);
|
|
$element = $style_element_defaults;
|
|
// This simplifies the JavaScript regex, allowing each line
|
|
// (separated by \n) to be treated as a completely different string.
|
|
// This means that we can use ^ and $ on one line at a time, and not
|
|
// worry about style tags since they'll never match the regex.
|
|
$element['#value'] = "\n" . implode("\n", $import_batch) . "\n";
|
|
$element['#attributes']['media'] = $group['media'];
|
|
$element['#browsers'] = $group['browsers'];
|
|
if (!empty($group['attributes'])) {
|
|
$element['#attributes'] += $group['attributes'];
|
|
}
|
|
$elements[] = $element;
|
|
}
|
|
}
|
|
// The group contains items ineligible for aggregation: output a LINK
|
|
// tag for each file.
|
|
else {
|
|
foreach ($group['items'] as $item) {
|
|
$element = $link_element_defaults;
|
|
// We do not check file_exists() here, because this code runs for
|
|
// files whose 'preprocess' is set to FALSE, and therefore, even
|
|
// when aggregation is enabled, and we want to avoid needlessly
|
|
// taxing a server that may be under heavy load. The file_exists()
|
|
// performed above for files whose 'preprocess' is TRUE is done for
|
|
// the benefit of theme .info files, but code that deals with files
|
|
// whose 'preprocess' is FALSE is responsible for ensuring the file
|
|
// exists.
|
|
// The dummy query string needs to be added to the URL to control
|
|
// browser-caching.
|
|
$query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
|
|
$element['#attributes']['href'] = advagg_file_create_url($item['data']) . $query_string_separator . $query_string;
|
|
$element['#attributes']['media'] = $item['media'];
|
|
$element['#browsers'] = $group['browsers'];
|
|
if (!empty($group['attributes'])) {
|
|
$element['#attributes'] += $group['attributes'];
|
|
}
|
|
$elements[] = $element;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// For inline content, the 'data' property contains the CSS content. If
|
|
// the group's 'data' property is set, then output it in a single STYLE
|
|
// tag. Otherwise, output a separate STYLE tag for each item.
|
|
case 'inline':
|
|
if (isset($group['data'])) {
|
|
$element = $style_element_defaults;
|
|
$element['#value'] = $group['data'];
|
|
$element['#value_prefix'] = $embed_prefix;
|
|
$element['#value_suffix'] = $embed_suffix;
|
|
$element['#attributes']['media'] = $group['media'];
|
|
$element['#browsers'] = $group['browsers'];
|
|
if (!empty($group['attributes'])) {
|
|
$element['#attributes'] += $group['attributes'];
|
|
}
|
|
$elements[] = $element;
|
|
}
|
|
else {
|
|
foreach ($group['items'] as $item) {
|
|
$element = $style_element_defaults;
|
|
$element['#value'] = $item['data'];
|
|
$element['#value_prefix'] = $embed_prefix;
|
|
$element['#value_suffix'] = $embed_suffix;
|
|
$element['#attributes']['media'] = $item['media'];
|
|
$element['#browsers'] = $group['browsers'];
|
|
if (!empty($group['attributes'])) {
|
|
$element['#attributes'] += $group['attributes'];
|
|
}
|
|
$elements[] = $element;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Output a LINK tag for each external item. The item's 'data' property
|
|
// contains the full URL.
|
|
case 'external':
|
|
foreach ($group['items'] as $item) {
|
|
$element = $link_element_defaults;
|
|
// Convert to protocol relative path.
|
|
$file_uri = $item['data'];
|
|
if (variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH)) {
|
|
$file_uri = advagg_convert_abs_to_protocol($item['data']);
|
|
}
|
|
$element['#attributes']['href'] = $file_uri;
|
|
$element['#attributes']['media'] = $item['media'];
|
|
$element['#browsers'] = $group['browsers'];
|
|
if (!empty($group['attributes'])) {
|
|
$element['#attributes'] += $group['attributes'];
|
|
}
|
|
$elements[] = $element;
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
return $elements;
|
|
}
|
|
|
|
/**
|
|
* Default callback to group JavaScript items.
|
|
*
|
|
* This function arranges the JavaScript items that are in the #items property
|
|
* of the scripts element into groups. When aggregation is enabled, files within
|
|
* a group are aggregated into a single file, significantly improving page
|
|
* loading performance by minimizing network traffic overhead.
|
|
*
|
|
* This function puts multiple items into the same group if they are groupable
|
|
* and if they are for the same browsers. Items of the 'file' type are groupable
|
|
* if their 'preprocess' flag is TRUE. Items of the 'inline', 'settings', or
|
|
* 'external' type are not groupable.
|
|
*
|
|
* This function also ensures that the process of grouping items does not change
|
|
* their relative order. This requirement may result in multiple groups for the
|
|
* same type and browsers, if needed to accommodate other items in
|
|
* between.
|
|
*
|
|
* @param array $javascript
|
|
* An array of JavaScript items, as returned by drupal_add_js(), but after
|
|
* alteration performed by drupal_get_js().
|
|
*
|
|
* @return array
|
|
* An array of JavaScript groups. Each group contains the same keys (e.g.,
|
|
* 'data', etc.) as a JavaScript item from the $javascript parameter, with the
|
|
* value of each key applying to the group as a whole. Each group also
|
|
* contains an 'items' key, which is the subset of items from $javascript that
|
|
* are in the group.
|
|
*
|
|
* @see drupal_pre_render_scripts()
|
|
*/
|
|
function advagg_group_js(array $javascript) {
|
|
$groups = array();
|
|
// If a group can contain multiple items, we track the information that must
|
|
// be the same for each item in the group, so that when we iterate the next
|
|
// item, we can determine if it can be put into the current group, or if a
|
|
// new group needs to be made for it.
|
|
$current_group_keys = NULL;
|
|
$index = -1;
|
|
foreach ($javascript as $key => $item) {
|
|
if (empty($item)) {
|
|
continue;
|
|
}
|
|
// The browsers for which the JavaScript item needs to be loaded is part of
|
|
// the information that determines when a new group is needed, but the order
|
|
// of keys in the array doesn't matter, and we don't want a new group if all
|
|
// that's different is that order.
|
|
if (isset($item['browsers'])) {
|
|
ksort($item['browsers']);
|
|
}
|
|
else {
|
|
$item['browsers'] = array();
|
|
}
|
|
|
|
// Fix missing types.
|
|
if (empty($item['type'])) {
|
|
// Setting is easy.
|
|
if ($key === 'settings') {
|
|
$item['type'] = 'setting';
|
|
}
|
|
// Check for the schema or // for protocol-relative.
|
|
elseif ((stripos($item['data'], 'http://') === 0
|
|
|| stripos($item['data'], 'https://') === 0
|
|
|| (strpos($item['data'], '//') === 0 && strpos($item['data'], '///') === FALSE))
|
|
) {
|
|
$item['type'] = 'external';
|
|
}
|
|
// See if the data contains a semicolon, new line, $, or quotes.
|
|
elseif (strpos($item['data'], ';') !== FALSE
|
|
|| strpos($item['data'], "\n")
|
|
|| strpos($item['data'], "$")
|
|
|| strpos($item['data'], "'")
|
|
|| strpos($item['data'], '"')
|
|
) {
|
|
$item['type'] = 'inline';
|
|
}
|
|
// Ends in .js.
|
|
elseif (stripos(strrev($item['data']), strrev('.js')) === 0) {
|
|
$item['type'] = 'file';
|
|
}
|
|
}
|
|
|
|
switch ($item['type']) {
|
|
case 'file':
|
|
// Group file items if their 'preprocess' flag is TRUE.
|
|
// Help ensure maximum reuse of aggregate files by only grouping
|
|
// together items that share the same 'group' value and 'every_page'
|
|
// flag. See drupal_add_js() for details about that.
|
|
$group_keys = !empty($item['preprocess']) ? array(
|
|
$item['type'],
|
|
$item['group'],
|
|
$item['every_page'],
|
|
$item['browsers'],
|
|
) : FALSE;
|
|
break;
|
|
|
|
case 'external':
|
|
case 'setting':
|
|
case 'inline':
|
|
// Do not group external, settings, and inline items.
|
|
$group_keys = FALSE;
|
|
break;
|
|
|
|
default:
|
|
// Define this here so we don't get undefined alerts down below.
|
|
$group_keys = NULL;
|
|
// Log the error as well.
|
|
watchdog('advagg', 'Bad javascript was added. Type is unknown. @key - @item', array(
|
|
'@key' => $key,
|
|
'@item' => print_r($item, TRUE),
|
|
), WATCHDOG_NOTICE);
|
|
break;
|
|
|
|
}
|
|
|
|
// If the group keys don't match the most recent group we're working with,
|
|
// then a new group must be made.
|
|
if ($group_keys !== $current_group_keys) {
|
|
++$index;
|
|
// Initialize the new group with the same properties as the first item
|
|
// being placed into it. The item's 'data' and 'weight' properties are
|
|
// unique to the item and should not be carried over to the group.
|
|
$groups[$index] = $item;
|
|
unset($groups[$index]['data'], $groups[$index]['weight']);
|
|
$groups[$index]['items'] = array();
|
|
$current_group_keys = $group_keys ? $group_keys : NULL;
|
|
}
|
|
|
|
// Add the item to the current group.
|
|
$groups[$index]['items'][] = $item;
|
|
}
|
|
|
|
return $groups;
|
|
}
|
|
|
|
/**
|
|
* Stable sort for CSS and JS items.
|
|
*
|
|
* Preserves the order of items with equal sort criteria.
|
|
*
|
|
* The function will sort by:
|
|
* - $item['group'], integer, ascending
|
|
* - $item['every_page'], boolean, first TRUE then FALSE
|
|
* - $item['weight'], integer, ascending
|
|
*
|
|
* @param array &$items
|
|
* Array of JS or CSS items, as in drupal_add_css() and drupal_add_js().
|
|
* The array keys can be integers or strings. The items themselves are arrays.
|
|
*
|
|
* @see drupal_get_css()
|
|
* @see drupal_get_js()
|
|
* @see drupal_add_css()
|
|
* @see drupal_add_js()
|
|
* @see https://drupal.org/node/1388546
|
|
*/
|
|
function advagg_drupal_sort_css_js_stable(array &$items) {
|
|
// Within a group, order all infrequently needed, page-specific files after
|
|
// common files needed throughout the website. Separating this way allows for
|
|
// the aggregate file generated for all of the common files to be reused
|
|
// across a site visit without being cut by a page using a less common file.
|
|
$nested = array();
|
|
foreach ($items as $key => &$item) {
|
|
// If weight is not set, make it 0.
|
|
if (!isset($item['weight'])) {
|
|
$item['weight'] = 0;
|
|
}
|
|
// If every_page is not set, make it FALSE.
|
|
if (!isset($item['every_page'])) {
|
|
$item['every_page'] = FALSE;
|
|
}
|
|
// If group is not set, make it CSS_DEFAULT/JS_DEFAULT (0).
|
|
if (!isset($item['group'])) {
|
|
$item['group'] = 0;
|
|
}
|
|
// If scope is not set, make it header.
|
|
if (!isset($item['scope'])) {
|
|
$item['scope'] = 'header';
|
|
}
|
|
|
|
// Weight cast to string to preserve float.
|
|
$weight = (string) $item['weight'];
|
|
$nested[$item['group']][$item['every_page'] ? 1 : 0][$weight][$key] = $item;
|
|
}
|
|
// First order by group, so that, for example, all items in the CSS_SYSTEM
|
|
// group appear before items in the CSS_DEFAULT group, which appear before
|
|
// all items in the CSS_THEME group. Modules may create additional groups by
|
|
// defining their own constants.
|
|
$sorted = array();
|
|
// Sort group; then iterate over it.
|
|
ksort($nested);
|
|
foreach ($nested as &$group_items) {
|
|
// Reverse sort every_page; then iterate over it.
|
|
krsort($group_items);
|
|
foreach ($group_items as &$ep_items) {
|
|
// Sort weight; then iterate over it.
|
|
ksort($ep_items);
|
|
// Finally, order by weight.
|
|
foreach ($ep_items as &$weight_items) {
|
|
foreach ($weight_items as $key => &$item) {
|
|
$sorted[$key] = $item;
|
|
}
|
|
unset($item);
|
|
}
|
|
}
|
|
unset($ep_items);
|
|
}
|
|
unset($group_items);
|
|
$items = $sorted;
|
|
}
|
|
|
|
/**
|
|
* Converts a PHP variable into its JavaScript equivalent.
|
|
*
|
|
* @param mixed $data
|
|
* Usually an array of data to be converted into a JSON string.
|
|
*
|
|
* @return string
|
|
* If there are no errors, this will return a JSON string. FALSE will be
|
|
* returned on failure.
|
|
*/
|
|
function advagg_json_encode($data) {
|
|
// Different versions of PHP handle json_encode() differently.
|
|
static $php550;
|
|
static $php540;
|
|
static $php530;
|
|
if (!isset($php550)) {
|
|
$php550 = version_compare(PHP_VERSION, '5.5.0', '>=');
|
|
}
|
|
if (!isset($php540)) {
|
|
$php540 = version_compare(PHP_VERSION, '5.4.0', '>=');
|
|
}
|
|
if (!isset($php530)) {
|
|
$php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
|
|
}
|
|
|
|
// Use fallback drupal encoder if PHP < 5.3.0.
|
|
if (!$php530) {
|
|
return @drupal_json_encode($data);
|
|
}
|
|
|
|
// Default json encode options.
|
|
$options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT;
|
|
if ($php550 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) {
|
|
// Output partial json if not in development mode and PHP >= 5.5.0.
|
|
$options |= JSON_PARTIAL_OUTPUT_ON_ERROR;
|
|
}
|
|
if ($php540 && variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
|
|
// Pretty print JSON if in development mode and PHP >= 5.4.0.
|
|
$options |= JSON_PRETTY_PRINT;
|
|
}
|
|
// Encode to JSON.
|
|
$json_data = @json_encode($data, $options);
|
|
|
|
// Uses json_last_error() if in development mode.
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) < 0) {
|
|
$error_number = json_last_error();
|
|
switch ($error_number) {
|
|
case JSON_ERROR_NONE:
|
|
$error_message = '';
|
|
break;
|
|
|
|
case JSON_ERROR_DEPTH:
|
|
$error_message = 'Maximum stack depth exceeded';
|
|
break;
|
|
|
|
case JSON_ERROR_STATE_MISMATCH:
|
|
$error_message = 'Underflow or the modes mismatch';
|
|
break;
|
|
|
|
case JSON_ERROR_CTRL_CHAR:
|
|
$error_message = 'Unexpected control character found';
|
|
break;
|
|
|
|
case JSON_ERROR_SYNTAX:
|
|
$error_message = 'Syntax error, malformed JSON';
|
|
break;
|
|
|
|
case JSON_ERROR_UTF8:
|
|
$error_message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
|
break;
|
|
|
|
default:
|
|
$error_message = 'Unknown error: ' . $error_number;
|
|
break;
|
|
}
|
|
if (!empty($error_message)) {
|
|
if (is_callable('httprl_pr')) {
|
|
$pretty_data = httprl_pr($data);
|
|
}
|
|
elseif (is_callable('kprint_r')) {
|
|
// @codingStandardsIgnoreLine
|
|
$pretty_data = kprint_r($data, TRUE);
|
|
}
|
|
else {
|
|
$pretty_data = '<pre>' . filter_xss(print_r($data, TRUE)) . '</pre>';
|
|
}
|
|
watchdog('advagg_json', 'Error with json encoding the Drupal.settings value. Error Message: %error_message. JSON Data: !data', array(
|
|
'%error_message' => $error_message,
|
|
'!data' => $pretty_data,
|
|
), WATCHDOG_ERROR);
|
|
}
|
|
}
|
|
return $json_data;
|
|
}
|
|
|
|
/**
|
|
* Will scan, flush, use, and report any changes to css/js files in aggregates.
|
|
*/
|
|
function advagg_scan_filesystem_for_changes_live() {
|
|
static $function_has_ran;
|
|
if (isset($function_has_ran)) {
|
|
return;
|
|
}
|
|
$function_has_ran = TRUE;
|
|
|
|
$bypass_cookie = FALSE;
|
|
$cookie_name = 'AdvAggDisabled';
|
|
$key = drupal_hmac_base64('advagg_cookie', drupal_get_private_key() . drupal_get_hash_salt() . variable_get('cron_key', 'drupal'));
|
|
if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) {
|
|
$bypass_cookie = TRUE;
|
|
}
|
|
if ((!advagg_enabled() && !$bypass_cookie) || variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) >= 0) {
|
|
return;
|
|
}
|
|
|
|
// Scan for changes to any CSS/JS files.
|
|
module_load_include('inc', 'advagg', 'advagg.cache');
|
|
$flushed = advagg_push_new_changes();
|
|
|
|
// Report back the results.
|
|
if (empty($flushed) || !user_is_logged_in()) {
|
|
return;
|
|
}
|
|
|
|
list($css_path) = advagg_get_root_files_dir();
|
|
$parts_uri = $css_path[1] . '/parts';
|
|
|
|
foreach ($flushed as $filename => $data) {
|
|
if (strpos($filename, $parts_uri) === 0) {
|
|
// Do not report on css files manged in the parts directory.
|
|
continue;
|
|
}
|
|
if (variable_get('advagg_show_file_changed_message', ADVAGG_SHOW_FILE_CHANGED_MESSAGE)) {
|
|
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
|
drupal_set_message(t('The file %filename has changed. %db_usage aggregates are using this file. %db_count db cache entries and all %type full cache entries have been flushed from the cache bins. Trigger: <code>@changes</code>', array(
|
|
'%filename' => $filename,
|
|
'%db_usage' => count($data[0]),
|
|
'%db_count' => count($data[1]),
|
|
'@changes' => print_r($data[2], TRUE),
|
|
'%type' => $ext,
|
|
)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if the filename matches the advagg file pattern.
|
|
*
|
|
* @param string $filename
|
|
* Path to check.
|
|
*
|
|
* @return int
|
|
* Returns 1 if the pattern matches, 0 if it does not.
|
|
*/
|
|
function advagg_match_file_pattern($filename) {
|
|
return preg_match('/.*(j|cs)s' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}' . ADVAGG_SPACE . '[A-Za-z0-9-_]{43}\.(j|cs)s$/', $filename);
|
|
}
|
|
|
|
/**
|
|
* Converts absolute paths to be self references.
|
|
*
|
|
* @param string $path
|
|
* Path to check.
|
|
* @param bool $strip_base_path
|
|
* Do no add the base path to the given path if TRUE.
|
|
*
|
|
* @return string
|
|
* The path.
|
|
*/
|
|
function advagg_convert_abs_to_rel($path, $strip_base_path = FALSE) {
|
|
$base_url = $GLOBALS['base_url'];
|
|
// Add a slash to end if none is found.
|
|
if (strpos(strrev($base_url), '/') !== 0) {
|
|
$base_url .= '/';
|
|
}
|
|
// Set base path.
|
|
$base_path = $GLOBALS['base_path'];
|
|
if ($strip_base_path) {
|
|
$base_path = '';
|
|
}
|
|
|
|
// Do conversion of https and http to self references.
|
|
$base_url_https = advagg_force_https_path($base_url);
|
|
$path = str_replace($base_url_https, $base_path, $path);
|
|
$base_url_http = advagg_force_http_path($base_url);
|
|
$path = str_replace($base_url_http, $base_path, $path);
|
|
|
|
$base_url = advagg_convert_abs_to_protocol($GLOBALS['base_url']);
|
|
// Add a slash to end if none is found.
|
|
if (strpos(strrev($base_url), '/') !== 0) {
|
|
$base_url .= '/';
|
|
}
|
|
// Do conversion of protocol relative to self references.
|
|
$path = str_replace($base_url, $base_path, $path);
|
|
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Converts absolute paths to be protocol relative paths.
|
|
*
|
|
* @param string $path
|
|
* Path to check.
|
|
*
|
|
* @return string
|
|
* The path.
|
|
*/
|
|
function advagg_convert_abs_to_protocol($path) {
|
|
if (strpos($path, 'http://') === 0) {
|
|
$path = substr($path, 5);
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Convert http:// and // to https://.
|
|
*
|
|
* @param string $path
|
|
* Path to check.
|
|
*
|
|
* @return string
|
|
* The path.
|
|
*/
|
|
function advagg_force_https_path($path) {
|
|
if (strpos($path, 'http://') === 0) {
|
|
$path = 'https://' . substr($path, 7);
|
|
}
|
|
elseif (strpos($path, '//') === 0) {
|
|
$path = 'https:' . $path;
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Convert https:// to http://.
|
|
*
|
|
* @param string $path
|
|
* Path to check.
|
|
*
|
|
* @return string
|
|
* The path.
|
|
*/
|
|
function advagg_force_http_path($path) {
|
|
if (strpos($path, 'https://') === 0) {
|
|
$path = 'http://' . substr($path, 8);
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Wrapper around file_create_url() to do post-processing on the created url.
|
|
*
|
|
* @param string $path
|
|
* Path to check.
|
|
* @param array $aggregate_settings
|
|
* Array of settings used.
|
|
* @param bool $run_file_create_url
|
|
* If TRUE then run the given path through file_create_url().
|
|
* @param string $source_type
|
|
* CSS or JS; if empty url in not embedded in another file.
|
|
*
|
|
* @return string
|
|
* The file uri.
|
|
*/
|
|
function advagg_file_create_url($path, array $aggregate_settings = array(), $run_file_create_url = TRUE, $source_type = '') {
|
|
$file_uri = $path;
|
|
if ($run_file_create_url) {
|
|
// This calls hook_file_url_alter().
|
|
$file_uri = file_create_url($path);
|
|
}
|
|
elseif (strpos($path, '/') !== 0 && !advagg_is_external($path)) {
|
|
$file_uri = '/' . $path;
|
|
}
|
|
// Ideally convert to relative path.
|
|
if ((isset($aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'])
|
|
&& $aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'])
|
|
|| (!isset($aggregate_settings['variables']['advagg_convert_absolute_to_relative_path'])
|
|
&& variable_get('advagg_convert_absolute_to_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_RELATIVE_PATH))
|
|
) {
|
|
$file_uri = advagg_convert_abs_to_rel($file_uri);
|
|
}
|
|
// Next try protocol relative path.
|
|
if ((isset($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'])
|
|
&& $aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'])
|
|
|| (!isset($aggregate_settings['variables']['advagg_convert_absolute_to_protocol_relative_path'])
|
|
&& variable_get('advagg_convert_absolute_to_protocol_relative_path', ADVAGG_CONVERT_ABSOLUTE_TO_PROTOCOL_RELATIVE_PATH))
|
|
) {
|
|
$file_uri = advagg_convert_abs_to_protocol($file_uri);
|
|
}
|
|
if ($source_type === 'css'
|
|
&& !advagg_is_external($file_uri)
|
|
&& ((isset($aggregate_settings['variables']['advagg_css_absolute_path'])
|
|
&& $aggregate_settings['variables']['advagg_css_absolute_path'])
|
|
|| (!isset($aggregate_settings['variables']['advagg_css_absolute_path'])
|
|
&& variable_get('advagg_css_absolute_path', ADVAGG_CSS_ABSOLUTE_PATH)))
|
|
) {
|
|
// Get public dir.
|
|
list($css_path) = advagg_get_root_files_dir();
|
|
$parsed = parse_url($css_path[0]);
|
|
$new_parsed = array();
|
|
if (!empty($parsed['host'])) {
|
|
$new_parsed['host'] = $parsed['host'];
|
|
}
|
|
if (!empty($parsed['path'])) {
|
|
$new_parsed['path'] = $parsed['path'];
|
|
}
|
|
$css_path_0 = advagg_glue_url($new_parsed);
|
|
$parsed = parse_url($css_path[1]);
|
|
$new_parsed = array();
|
|
if (!empty($parsed['host'])) {
|
|
$new_parsed['host'] = $parsed['host'];
|
|
}
|
|
if (!empty($parsed['path'])) {
|
|
$new_parsed['path'] = $parsed['path'];
|
|
}
|
|
$css_path_1 = advagg_glue_url($new_parsed);
|
|
$pos = strpos($css_path_1, $css_path_0);
|
|
if (!empty($pos)) {
|
|
$public_dir = substr($css_path_1, 0, $pos);
|
|
|
|
// If public dir is not in the file uri, use absolute URL.
|
|
if (strpos($file_uri, $public_dir) === FALSE) {
|
|
$file_uri = url($path, array('absolute' => TRUE));
|
|
}
|
|
}
|
|
}
|
|
// Finally force https.
|
|
if ((isset($aggregate_settings['variables']['advagg_force_https_path'])
|
|
&& $aggregate_settings['variables']['advagg_force_https_path'])
|
|
|| (!isset($aggregate_settings['variables']['advagg_force_https_path'])
|
|
&& variable_get('advagg_force_https_path', ADVAGG_FORCE_HTTPS_PATH))
|
|
) {
|
|
$file_uri = advagg_force_https_path($file_uri);
|
|
}
|
|
return $file_uri;
|
|
}
|
|
|
|
/**
|
|
* Loads the stylesheet and resolves all @import commands.
|
|
*
|
|
* Loads a stylesheet and replaces @import commands with the contents of the
|
|
* imported file. Use this instead of file_get_contents when processing
|
|
* stylesheets.
|
|
*
|
|
* The returned contents are compressed removing white space and comments only
|
|
* when CSS aggregation is enabled. This optimization will not apply for
|
|
* color.module enabled themes with CSS aggregation turned off.
|
|
*
|
|
* @param string $file
|
|
* Name of the stylesheet to be processed.
|
|
* @param bool $optimize
|
|
* Defines if CSS contents should be compressed or not.
|
|
* @param bool $reset_basepath
|
|
* Used internally to facilitate recursive resolution of @import commands.
|
|
*
|
|
* @return string
|
|
* Contents of the stylesheet, including any resolved @import commands.
|
|
*
|
|
* @see drupal_load_stylesheet()
|
|
*/
|
|
function advagg_load_stylesheet($file, $optimize = FALSE, $reset_basepath = TRUE, $contents = '') {
|
|
// These static's are not cache variables, so we don't use drupal_static().
|
|
static $_optimize, $basepath;
|
|
if ($reset_basepath) {
|
|
$basepath = '';
|
|
}
|
|
// Store the value of $optimize for preg_replace_callback with nested @import
|
|
// loops.
|
|
if (isset($optimize)) {
|
|
$_optimize = $optimize;
|
|
}
|
|
|
|
// Stylesheets are relative one to each other. Start by adding a base path
|
|
// prefix provided by the parent stylesheet (if necessary).
|
|
if ($basepath && !file_uri_scheme($file)) {
|
|
$file = $basepath . '/' . $file;
|
|
}
|
|
// Store the parent base path to restore it later.
|
|
$parent_base_path = $basepath;
|
|
// Set the current base path to process possible child imports.
|
|
$basepath = dirname($file);
|
|
|
|
// Load the CSS stylesheet. We suppress errors because themes may specify
|
|
// stylesheets in their .info file that don't exist in the theme's path,
|
|
// but are merely there to disable certain module CSS files.
|
|
$content = '';
|
|
if (empty($contents) && !empty($file)) {
|
|
$contents = (string) @advagg_file_get_contents($file);
|
|
}
|
|
if ($contents) {
|
|
// Return the processed stylesheet.
|
|
$content = advagg_load_stylesheet_content($contents, $_optimize);
|
|
}
|
|
|
|
// Restore the parent base path as the file and its children are processed.
|
|
$basepath = $parent_base_path;
|
|
if ($_optimize) {
|
|
$content = trim($content);
|
|
}
|
|
return $content;
|
|
}
|
|
|
|
/**
|
|
* Decodes UTF byte-order mark (BOM) into the encoding's name.
|
|
*
|
|
* @param string $data
|
|
* The data possibly containing a BOM. This can be the entire contents of
|
|
* a file, or just a fragment containing at least the first five bytes.
|
|
*
|
|
* @return string|bool
|
|
* The name of the encoding, or FALSE if no byte order mark was present.
|
|
*
|
|
* @see https://api.drupal.org/api/drupal/core!lib!Drupal!Component!Utility!Unicode.php/function/Unicode%3A%3AencodingFromBOM/8
|
|
*/
|
|
function advagg_get_encoding_from_bom($data) {
|
|
static $bom_map = array(
|
|
"\xEF\xBB\xBF" => 'UTF-8',
|
|
"\xFE\xFF" => 'UTF-16BE',
|
|
"\xFF\xFE" => 'UTF-16LE',
|
|
"\x00\x00\xFE\xFF" => 'UTF-32BE',
|
|
"\xFF\xFE\x00\x00" => 'UTF-32LE',
|
|
"\x2B\x2F\x76\x38" => 'UTF-7',
|
|
"\x2B\x2F\x76\x39" => 'UTF-7',
|
|
"\x2B\x2F\x76\x2B" => 'UTF-7',
|
|
"\x2B\x2F\x76\x2F" => 'UTF-7',
|
|
"\x2B\x2F\x76\x38\x2D" => 'UTF-7',
|
|
);
|
|
|
|
foreach ($bom_map as $bom => $encoding) {
|
|
if (strpos($data, $bom) === 0) {
|
|
return $encoding;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Converts data to UTF-8.
|
|
*
|
|
* Requires the iconv, GNU recode or mbstring PHP extension.
|
|
*
|
|
* @param string $data
|
|
* The data to be converted.
|
|
* @param string $encoding
|
|
* The encoding that the data is in.
|
|
*
|
|
* @return string|bool
|
|
* Converted data or FALSE.
|
|
*/
|
|
function advagg_convert_to_utf8($data, $encoding) {
|
|
if (function_exists('iconv')) {
|
|
return @iconv($encoding, 'utf-8', $data);
|
|
}
|
|
elseif (function_exists('mb_convert_encoding')) {
|
|
return @mb_convert_encoding($data, 'utf-8', $encoding);
|
|
}
|
|
elseif (function_exists('recode_string')) {
|
|
return @recode_string($encoding . '..utf-8', $data);
|
|
}
|
|
// Cannot convert.
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Processes the contents of a stylesheet for aggregation.
|
|
*
|
|
* @param string $contents
|
|
* The contents of the stylesheet.
|
|
* @param bool $optimize
|
|
* (Optional) Boolean whether CSS contents should be minified. Defaults to
|
|
* FALSE.
|
|
*
|
|
* @return string
|
|
* Contents of the stylesheet including the imported stylesheets.
|
|
*
|
|
* @see drupal_load_stylesheet_content()
|
|
*/
|
|
function advagg_load_stylesheet_content($contents, $optimize = FALSE) {
|
|
// If a BOM is found, convert the file to UTF-8. Used for inline CSS here.
|
|
$encoding = advagg_get_encoding_from_bom($contents);
|
|
if (!empty($encoding)) {
|
|
$contents = advagg_convert_to_utf8($contents, $encoding);
|
|
}
|
|
|
|
if ($optimize) {
|
|
// Perform some safe CSS optimizations.
|
|
// Regexp to match comment blocks.
|
|
// Regexp to match double quoted strings.
|
|
// Regexp to match single quoted strings.
|
|
$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
|
|
$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
|
|
$single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
|
|
// Strip all comment blocks, but keep double/single quoted strings.
|
|
$contents = preg_replace(
|
|
"<($double_quot|$single_quot)|$comment>Ss",
|
|
"$1",
|
|
$contents
|
|
);
|
|
|
|
// Remove certain whitespace.
|
|
// There are different conditions for removing leading and trailing
|
|
// whitespace.
|
|
// @see http://php.net/manual/regexp.reference.subpatterns.php
|
|
$contents = preg_replace('<
|
|
# Do not strip any space from within single or double quotes
|
|
(' . $double_quot . '|' . $single_quot . ')
|
|
# Strip leading and trailing whitespace.
|
|
| \s*([@{};,])\s*
|
|
# Strip only leading whitespace from:
|
|
# - Closing parenthesis: Retain "@media (bar) and foo".
|
|
| \s+([\)])
|
|
# Strip only trailing whitespace from:
|
|
# - Opening parenthesis: Retain "@media (bar) and foo".
|
|
# - Colon: Retain :pseudo-selectors.
|
|
| ([\(:])\s+
|
|
>xSs',
|
|
// Only one of the three capturing groups will match, so its reference
|
|
// will contain the wanted value and the references for the
|
|
// two non-matching groups will be replaced with empty strings.
|
|
'$1$2$3$4',
|
|
$contents
|
|
);
|
|
// End the file with a new line.
|
|
$contents = trim($contents);
|
|
$contents .= "\n";
|
|
}
|
|
|
|
// Remove multiple charset declarations for standards compliance (and fixing
|
|
// Safari problems).
|
|
$contents = preg_replace('/^@charset\s+[\'"](\S*?)\b[\'"];/i', '', $contents);
|
|
|
|
// Replaces @import commands with the actual stylesheet content.
|
|
// This happens recursively but omits external files.
|
|
$contents = preg_replace_callback('%@import\s*+(?:url\(\s*+)?+[\'"]?+(?![a-z]++:|/)([^\'"()\s]++)[\'"]?+\s*+\)?+\s*+;%i', '_advagg_load_stylesheet', $contents);
|
|
return $contents;
|
|
}
|
|
|
|
/**
|
|
* Loads stylesheets recursively and returns contents with corrected paths.
|
|
*
|
|
* This function is used for recursive loading of stylesheets and
|
|
* returns the stylesheet content with all url() paths corrected.
|
|
*
|
|
* @param array $matches
|
|
* The matches from preg_replace_callback().
|
|
*
|
|
* @return array
|
|
* String with altered internal url() paths.
|
|
*
|
|
* @see _drupal_load_stylesheet()
|
|
*/
|
|
function _advagg_load_stylesheet(array $matches) {
|
|
$filename = $matches[1];
|
|
// Load the imported stylesheet and replace @import commands in there as well.
|
|
$file = advagg_load_stylesheet($filename, NULL, FALSE);
|
|
|
|
if (empty($file)) {
|
|
if (strpos($matches[0], 'http://') === 0
|
|
|| strpos($matches[0], 'https://') === 0
|
|
|| strpos($matches[0], '//') === 0
|
|
) {
|
|
return $matches[0];
|
|
}
|
|
if (variable_get('advagg_debug', ADVAGG_DEBUG) >= 2) {
|
|
watchdog('advagg-debug', 'Trying to load @file via @import statement but it was not found.', array('@file' => $filename), WATCHDOG_DEBUG);
|
|
}
|
|
if (variable_get('advagg_cache_level', ADVAGG_CACHE_LEVEL) <= 1) {
|
|
return $matches[0];
|
|
}
|
|
else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// Determine the file's directory.
|
|
$directory = dirname($filename);
|
|
// If the file is in the current directory, make sure '.' doesn't appear in
|
|
// the url() path.
|
|
$directory = $directory == '.' ? '' : $directory . '/';
|
|
|
|
// Alter all internal url() paths. Leave external paths alone. We don't need
|
|
// to normalize absolute paths here (i.e. remove folder/... segments) because
|
|
// that will be done later.
|
|
return preg_replace('%url\(\s*+([\'"]?+)(?![a-z]++:|/)([^\'")]+)([\'"]?+)\s*\)%i', 'url(\1' . $directory . '\2\3)', $file);
|
|
}
|
|
|
|
/**
|
|
* Check and see if the aggressive cache can safely be enabled.
|
|
*
|
|
* @return array
|
|
* If there are no conflicts, this will return an empty array.
|
|
*/
|
|
function advagg_aggressive_cache_conflicts() {
|
|
$hooks = array('css_alter' => TRUE, 'js_alter' => TRUE);
|
|
foreach ($hooks as $hook => $values) {
|
|
$hooks[$hook] = module_implements($hook);
|
|
|
|
// Also check themes as drupal_alter() allows for themes to alter things.
|
|
$themes = list_themes();
|
|
$theme_keys = array_keys($themes);
|
|
if (!empty($theme_keys)) {
|
|
foreach ($theme_keys as $theme_key) {
|
|
$function = $theme_key . '_' . $hook;
|
|
// Search loaded themes.
|
|
if (function_exists($function)) {
|
|
$hooks[$hook][] = $theme_key;
|
|
continue;
|
|
}
|
|
// Skip disabled themes.
|
|
if (empty($themes[$theme_key]->status)) {
|
|
continue;
|
|
}
|
|
// Search enabled but not loaded themes.
|
|
$file = dirname($themes[$theme_key]->filename) . '/template.php';
|
|
if (file_exists($file)) {
|
|
$contents = (string) @advagg_file_get_contents($file);
|
|
if (stripos($contents, $function)) {
|
|
$hooks[$hook][] = $theme_key;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$whitelist = array(
|
|
// Core.
|
|
//
|
|
// locale_js_directory variable; default: languages.
|
|
// javascript_parsed variable; default: array().
|
|
'locale',
|
|
|
|
// No control; same every time.
|
|
'simpletest',
|
|
|
|
// No control; same every time.
|
|
'seven',
|
|
|
|
// Popular contrib.
|
|
//
|
|
// No control; same every time.
|
|
'at_commerce',
|
|
|
|
// ais_adaptive_styles variable; Default: array().
|
|
// ais_adaptive_styles_method; Default: 'both-max'.
|
|
// 'ais',
|
|
//
|
|
// No control; same every time.
|
|
'bluecheese',
|
|
|
|
// drupal_static('clientside_validation_settings') array.
|
|
// 'clientside_validation',
|
|
//
|
|
// version_compare(VERSION, '7.14', '<').
|
|
'conditional_fields',
|
|
|
|
// _css_injector_load_rule() function.
|
|
// Changes the weight of all files added in init so no special handling.
|
|
// 'css_injector',
|
|
//
|
|
// disable_css_ . $theme . _all variable; default: FALSE.
|
|
// disable_css_ . $theme . _modules; default: array().
|
|
// disable_css_ . $theme . _files; default: array().
|
|
// 'disable_css',
|
|
//
|
|
// Empty call; commented code is same every time.
|
|
'elfinder',
|
|
|
|
// excluded_css_custom variable; Default: ''.
|
|
// excluded_javascript_custom variable; Default: ''.
|
|
// 'excluded',
|
|
//
|
|
// No control; same every time.
|
|
'fences',
|
|
|
|
// jqmulti_jquery_path() function.
|
|
// jqmulti_get_files() function.
|
|
// jqmulti_load_always variable; Default: FALSE.
|
|
// 'jqmulti',
|
|
//
|
|
// No control; same every time.
|
|
'jquery_dollar',
|
|
|
|
// labjs_suppress() function.
|
|
'labjs_js',
|
|
|
|
// Empty call.
|
|
'panopoly_core',
|
|
|
|
// speedy_js_production variable; Default: TRUE.
|
|
'speedy',
|
|
|
|
// logintoboggan_validating_id() function.
|
|
'logintoboggan',
|
|
);
|
|
// Allow other modules to modify the $whitelist.
|
|
// Call hook_advagg_aggressive_cache_conflicts_alter()
|
|
drupal_alter('advagg_aggressive_cache_conflicts', $whitelist);
|
|
|
|
$questionable_modules = array();
|
|
foreach ($hooks as $hook => $modules) {
|
|
foreach ($modules as $key => $module) {
|
|
// Anything from advagg is ok.
|
|
if (strpos($module, 'advagg') === 0 || strpos($module, '_advagg') === 0) {
|
|
unset($modules[$key]);
|
|
continue;
|
|
}
|
|
|
|
// Remove known modules that should work with aggressive caching.
|
|
if (in_array($module, $whitelist)) {
|
|
unset($modules[$key]);
|
|
}
|
|
else {
|
|
$questionable_modules[$module] = $module;
|
|
}
|
|
}
|
|
}
|
|
return $questionable_modules;
|
|
}
|
|
|
|
/**
|
|
* Alt to http_build_url().
|
|
*
|
|
* @param array $parsed
|
|
* Array from parse_url().
|
|
* @param bool $strip_query_and_fragment
|
|
* If set to TRUE the query and fragment will be removed from the output.
|
|
*
|
|
* @return string
|
|
* URI is returned.
|
|
*
|
|
* @see http://php.net/parse-url#85963
|
|
*/
|
|
function advagg_glue_url(array $parsed, $strip_query_and_fragment = FALSE) {
|
|
$uri = '';
|
|
if (isset($parsed['scheme'])) {
|
|
switch (strtolower($parsed['scheme'])) {
|
|
// Mailto uri.
|
|
case 'mailto':
|
|
$uri .= $parsed['scheme'] . ':';
|
|
break;
|
|
|
|
// Protocol relative uri.
|
|
case '//':
|
|
$uri .= $parsed['scheme'];
|
|
break;
|
|
|
|
// Standard uri.
|
|
default:
|
|
$uri .= $parsed['scheme'] . '://';
|
|
}
|
|
}
|
|
$uri .= isset($parsed['user']) ? $parsed['user'] . (isset($parsed['pass']) ? ':' . $parsed['pass'] : '') . '@' : '';
|
|
$uri .= isset($parsed['host']) ? $parsed['host'] : '';
|
|
$uri .= !empty($parsed['port']) ? ':' . $parsed['port'] : '';
|
|
|
|
if (isset($parsed['path'])) {
|
|
$uri .= (substr($parsed['path'], 0, 1) === '/') ? $parsed['path'] : ((!empty($uri) ? '/' : '') . $parsed['path']);
|
|
}
|
|
|
|
if (!$strip_query_and_fragment) {
|
|
$uri .= isset($parsed['query']) ? '?' . $parsed['query'] : '';
|
|
$uri .= isset($parsed['fragment']) ? '#' . $parsed['fragment'] : '';
|
|
}
|
|
return $uri;
|
|
}
|
|
|
|
/**
|
|
* Clear certain caches on form submit.
|
|
*/
|
|
function advagg_cache_clear_admin_submit() {
|
|
$cache_bins = advagg_flush_caches();
|
|
foreach ($cache_bins as $bin) {
|
|
cache_clear_all('*', $bin, TRUE);
|
|
}
|
|
cache_clear_all('hook_info', 'cache_bootstrap');
|
|
cache_clear_all('advagg_hooks_implemented:', 'cache_bootstrap', TRUE);
|
|
}
|
|
|
|
/**
|
|
* Get the resource hint settings for the preload attribute.
|
|
*
|
|
* @param bool $return_defaults
|
|
* Default FALSE, TRUE returns the default values.
|
|
*
|
|
* @return array
|
|
* Ordered 2 dimensional array.
|
|
*/
|
|
function advagg_get_resource_hints_preload_settings($return_defaults = FALSE) {
|
|
$sub_defaults = array(
|
|
'enabled' => 1,
|
|
'push' => 0,
|
|
'local' => 1,
|
|
'external' => 1,
|
|
);
|
|
// Collect your data.
|
|
$advagg_resource_hints_preload_settings_defaults = array(
|
|
'style' => $sub_defaults + array(
|
|
'#weight' => -10,
|
|
'title' => t('CSS Files'),
|
|
),
|
|
'font' => $sub_defaults + array(
|
|
'#weight' => -9,
|
|
'title' => t('Font Files'),
|
|
),
|
|
'script' => $sub_defaults + array(
|
|
'#weight' => -8,
|
|
'title' => t('JS Files'),
|
|
),
|
|
'svg' => $sub_defaults + array(
|
|
'#weight' => -7,
|
|
'title' => t('SVG Files'),
|
|
),
|
|
'image' => $sub_defaults + array(
|
|
'#weight' => -6,
|
|
'title' => t('Image Files'),
|
|
),
|
|
'all_others' => $sub_defaults + array(
|
|
'#weight' => -5,
|
|
'title' => t('All Other Files'),
|
|
),
|
|
);
|
|
if ($return_defaults) {
|
|
return $advagg_resource_hints_preload_settings_defaults;
|
|
}
|
|
$advagg_resource_hints_preload_settings = variable_get('advagg_resource_hints_preload_settings', $advagg_resource_hints_preload_settings_defaults);
|
|
// Merge in defaults.
|
|
foreach ($advagg_resource_hints_preload_settings as $id => &$entry) {
|
|
if (isset($advagg_resource_hints_preload_settings_defaults[$id])) {
|
|
$entry += $advagg_resource_hints_preload_settings_defaults[$id];
|
|
}
|
|
ksort($entry);
|
|
}
|
|
unset($entry);
|
|
// Sort the rows.
|
|
uasort($advagg_resource_hints_preload_settings, 'element_sort');
|
|
return $advagg_resource_hints_preload_settings;
|
|
}
|
|
|
|
/**
|
|
* See if the .htaccess file uses the RewriteBase directive.
|
|
*
|
|
* @param string $location
|
|
* The location of the .htaccess file.
|
|
*
|
|
* @return string
|
|
* The last active RewriteBase entry in htaccess.
|
|
*/
|
|
function advagg_htaccess_rewritebase($location = DRUPAL_ROOT) {
|
|
if (is_readable($location . '/.htaccess')) {
|
|
$htaccess = advagg_file_get_contents($location . '/.htaccess');
|
|
$matches = array();
|
|
$found = preg_match_all('/\\n\s*RewriteBase\s.*/i', $htaccess, $matches);
|
|
if ($found && !empty($matches[0])) {
|
|
$matches[0] = array_map('trim', $matches[0]);
|
|
return array_pop($matches[0]);
|
|
}
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Get the latest version number for the remote version.
|
|
*
|
|
* @param array $library
|
|
* An associative array containing all information about the library.
|
|
* @param array $options
|
|
* An associative array containing options for the version parser.
|
|
* @param string $url
|
|
* URL for the remote version to lookup.
|
|
*
|
|
* @return string
|
|
* The latest version number as a string or 0 on failure.
|
|
*/
|
|
function advagg_get_github_version_json(array $library, array $options, $url) {
|
|
$http_options = array('timeout' => 2);
|
|
$package = drupal_http_request($url, $http_options);
|
|
if (empty($package->data)) {
|
|
// Try again.
|
|
$package = drupal_http_request($url, array('timeout' => 5));
|
|
}
|
|
if (empty($package->data)) {
|
|
// Try again but force http.
|
|
$url = advagg_force_http_path($url);
|
|
$package = drupal_http_request($url, $http_options);
|
|
}
|
|
if (!empty($package->data)) {
|
|
$package = json_decode($package->data);
|
|
if (isset($package->version)) {
|
|
return (string) $package->version;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get the latest version number for the remote version.
|
|
*
|
|
* @param array $library
|
|
* An associative array containing all information about the library.
|
|
* @param array $options
|
|
* An associative array containing options for the version parser.
|
|
* @param string $url
|
|
* URL for the remote version to lookup.
|
|
*
|
|
* @return string
|
|
* The latest version number as a string or 0 on failure.
|
|
*/
|
|
function advagg_get_github_version_txt(array $library, array $options, $url) {
|
|
$http_options = array('timeout' => 2);
|
|
$request = drupal_http_request($url, $http_options);
|
|
if (empty($request->data)) {
|
|
// Try again.
|
|
$request = drupal_http_request($url, array('timeout' => 5));
|
|
}
|
|
if (empty($request->data)) {
|
|
// Try again but force http.
|
|
$url = advagg_force_http_path($url);
|
|
$request = drupal_http_request($url, $http_options);
|
|
}
|
|
if (!empty($request->data)) {
|
|
$matches = array();
|
|
if (preg_match($options['pattern'], $request->data, $matches)) {
|
|
return $matches[1];
|
|
}
|
|
}
|
|
return '0';
|
|
}
|
|
|
|
/**
|
|
* Update github version numbers to the latest.
|
|
*
|
|
* @param bool $refresh
|
|
* Set to TRUE to skip the cache and force a refresh of the data.
|
|
*
|
|
* @return mixed
|
|
* Key Value pair of the project name and remote version number. If $target is
|
|
* set then that version number is returned.
|
|
*/
|
|
function advagg_get_remote_libraries_versions($refresh = FALSE) {
|
|
$cid = __FUNCTION__;
|
|
$versions = array();
|
|
if (!$refresh) {
|
|
$versions = advagg_get_remote_libraries_versions_cache($cid);
|
|
if (!empty($versions)) {
|
|
return $versions;
|
|
}
|
|
}
|
|
|
|
if (is_callable('libraries_info')) {
|
|
$libraries = libraries_info();
|
|
foreach ($libraries as $key => $library) {
|
|
// Get current version.
|
|
$libraries_detect = libraries_detect($key);
|
|
if (isset($libraries_detect['version'])) {
|
|
$versions[$key]['local'] = $libraries_detect['version'];
|
|
}
|
|
elseif (!empty($libraries_detect['local version'])) {
|
|
$versions[$key]['local'] = $libraries_detect['local version'];
|
|
}
|
|
|
|
// Check if callback is live.
|
|
$remote = advagg_get_remote_libraries_version($key, $library, FALSE);
|
|
if (!empty($remote)) {
|
|
$versions[$key]['remote'] = $remote;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($versions)) {
|
|
cache_set($cid, $versions, 'cache_advagg_info');
|
|
}
|
|
return $versions;
|
|
}
|
|
|
|
/**
|
|
* Get the remote and local versions cache of the available libraries.
|
|
*
|
|
* @param string $cid
|
|
* Cache ID.
|
|
*
|
|
* @return array
|
|
* Library name => (local, remote).
|
|
*/
|
|
function advagg_get_remote_libraries_versions_cache($cid = '') {
|
|
if (empty($cid)) {
|
|
$cid = 'advagg_get_remote_libraries_versions';
|
|
}
|
|
$versions = &drupal_static($cid, array());
|
|
if (empty($versions)) {
|
|
$cache = cache_get($cid, 'cache_advagg_info');
|
|
if (!empty($cache) && !empty($cache->data)) {
|
|
$versions = $cache->data;
|
|
}
|
|
}
|
|
return $versions;
|
|
}
|
|
|
|
/**
|
|
* Get the latest version number for the remote version.
|
|
*
|
|
* @param string $name
|
|
* Name of the library.
|
|
* @param array $library
|
|
* An associative array containing all information about the library.
|
|
* @param bool $use_cache
|
|
* TRUE try the cache first.
|
|
*
|
|
* @return string
|
|
* The latest version number as a string or 0 on failure.
|
|
*/
|
|
function advagg_get_remote_libraries_version($name, array $library, $use_cache = TRUE) {
|
|
if ($use_cache) {
|
|
$cid = 'advagg_get_remote_libraries_versions';
|
|
$versions = advagg_get_remote_libraries_versions_cache($cid);
|
|
if (isset($versions[$name]['remote'])) {
|
|
return $versions[$name]['remote'];
|
|
}
|
|
}
|
|
|
|
// Remote url is not set, see if we can generate it given the current data.
|
|
if (empty($library['remote']['url'])
|
|
&& !empty($library['version arguments'])
|
|
) {
|
|
if (!isset($library['version arguments']['file'])
|
|
&& isset($library['version arguments']['variants'])
|
|
) {
|
|
// Use the first variant.
|
|
$file = reset($library['version arguments']['variants']);
|
|
$library['version arguments']['file'] = $file['file'];
|
|
$library['version arguments']['pattern'] = $file['pattern'];
|
|
}
|
|
|
|
if (!empty($library['version arguments']['file'])) {
|
|
if (!empty($library['vendor url'])) {
|
|
// Use vendor url if it's a github one.
|
|
if (strpos($library['vendor url'], 'https://github.com/') === 0) {
|
|
$parsed_vendor = @parse_url($library['vendor url']);
|
|
// Previously: https://rawgit.com{$parsed_vendor['path']}/master/{$library['version arguments']['file']} .
|
|
$library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_vendor['path']}@master/{$library['version arguments']['file']}";
|
|
}
|
|
}
|
|
if (empty($library['remote']['url']) && !empty($library['download url'])) {
|
|
// Use download url if it's a github one.
|
|
if (strpos($library['download url'], 'https://github.com/') === 0) {
|
|
$parsed_download = @parse_url($library['download url']);
|
|
$paths = explode('/', $parsed_download['path']);
|
|
$parsed_download['path'] = "/{$paths[1]}/{$paths[2]}";
|
|
// Previously: https://rawgit.com{$parsed_download['path']}/master/{$library['version arguments']['file']} .
|
|
$library['remote']['url'] = "https://cdn.jsdelivr.net/gh{$parsed_download['path']}@master/{$library['version arguments']['file']}";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remote callback is not set, try to generate it given the current data.
|
|
if (empty($library['remote']['callback'])
|
|
&& isset($library['version arguments']['file'])
|
|
) {
|
|
if (!empty($library['version callback'])) {
|
|
// Use defined parser.
|
|
$library['remote']['callback'] = $library['version callback'];
|
|
}
|
|
else {
|
|
if ($library['version arguments']['file'] === 'package.json') {
|
|
// JSON parser.
|
|
$library['remote']['callback'] = 'advagg_get_github_version_json';
|
|
}
|
|
else {
|
|
// Text parser.
|
|
$library['remote']['callback'] = 'advagg_get_github_version_txt';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get remote version.
|
|
$return = 0;
|
|
if (!empty($library['remote'])
|
|
&& !empty($library['remote']['callback'])
|
|
&& !empty($library['remote']['url'])
|
|
&& isset($library['version arguments'])
|
|
&& is_callable($library['remote']['callback'])
|
|
) {
|
|
$return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']);
|
|
|
|
// Try package.json on failure.
|
|
if (empty($return) && $library['version arguments']['file'] !== 'package.json') {
|
|
$pos = strrpos($library['remote']['url'], $library['version arguments']['file']);
|
|
$library['remote']['url'] = substr($library['remote']['url'], 0, $pos) . 'package.json';
|
|
$library['remote']['callback'] = 'advagg_get_github_version_json';
|
|
$return = $library['remote']['callback']($library, $library['version arguments'], $library['remote']['url']);
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Get the latest version number for the remote version.
|
|
*
|
|
* @param string $name
|
|
* Name of the library.
|
|
* @param string $module_name
|
|
* Name of the module where the library is registered.
|
|
*
|
|
* @return array
|
|
* The library array.
|
|
*/
|
|
function advagg_get_library($name, $module_name) {
|
|
$library = cache_get($name, 'cache_libraries');
|
|
if ($library) {
|
|
$library = $library->data;
|
|
}
|
|
else {
|
|
if (is_callable('libraries_detect')) {
|
|
$library = libraries_detect($name);
|
|
}
|
|
elseif (is_callable("{$module_name}_libraries_info")) {
|
|
$library = call_user_func("{$module_name}_libraries_info");
|
|
$library = $library[$name];
|
|
}
|
|
}
|
|
return $library;
|
|
}
|
|
|
|
/**
|
|
* Alter the js array fixing the type key if set incorrectly.
|
|
*
|
|
* @param array $array
|
|
* CSS or JS array.
|
|
* @param string $type
|
|
* CSS or JS.
|
|
*/
|
|
function advagg_fix_type(array &$array, $type) {
|
|
// Skip if advagg is disabled.
|
|
if (!advagg_enabled()) {
|
|
return;
|
|
}
|
|
|
|
// Skip if setting is turned off.
|
|
if ($type === 'css' && !variable_get('advagg_css_fix_type', ADVAGG_CSS_FIX_TYPE)) {
|
|
return;
|
|
}
|
|
if ($type === 'js' && !variable_get('advagg_js_fix_type', ADVAGG_JS_FIX_TYPE)) {
|
|
return;
|
|
}
|
|
|
|
// Fix type if it was incorrectly set.
|
|
// Get hostname and base path.
|
|
$mod_base_url = substr($GLOBALS['base_root'] . $GLOBALS['base_path'], strpos($GLOBALS['base_root'] . $GLOBALS['base_path'], '//') + 2);
|
|
$mod_base_url_len = strlen($mod_base_url);
|
|
|
|
foreach ($array as &$value) {
|
|
// Skip if the data is empty or not a string.
|
|
if (empty($value['data']) || !is_string($value['data'])) {
|
|
continue;
|
|
}
|
|
|
|
// Default to file if type is not set.
|
|
if (!isset($value['type'])) {
|
|
$value['type'] = 'file';
|
|
}
|
|
|
|
// If inline, be done with processing.
|
|
if ($value['type'] === 'inline') {
|
|
continue;
|
|
}
|
|
|
|
// Default to file if not file, inline, external, setting.
|
|
if ($value['type'] !== 'file'
|
|
&& $value['type'] !== 'inline'
|
|
&& $value['type'] !== 'external'
|
|
&& $value['type'] !== 'setting'
|
|
) {
|
|
if ($value['type'] === 'settings') {
|
|
$value['type'] = 'setting';
|
|
}
|
|
else {
|
|
$value['type'] = 'file';
|
|
}
|
|
}
|
|
|
|
$lower = strtolower($value['data']);
|
|
$http_pos = strpos($lower, 'http://');
|
|
$https_pos = strpos($lower, 'https://');
|
|
$double_slash_pos = strpos($lower, '//');
|
|
$tripple_slash_pos = strpos($lower, '///');
|
|
$mod_base_url_pos = stripos($value['data'], $mod_base_url);
|
|
|
|
// If type is external but doesn't start with http, https, or // change it
|
|
// to file.
|
|
if ($value['type'] === 'external'
|
|
&& $http_pos !== 0
|
|
&& $https_pos !== 0
|
|
&& $double_slash_pos !== 0
|
|
) {
|
|
if (is_readable($value['data'])) {
|
|
$value['type'] = 'file';
|
|
}
|
|
else {
|
|
// Fix for subdir issues.
|
|
$parsed = parse_url($value['data']);
|
|
if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) {
|
|
$path = substr($parsed['path'], strlen($GLOBALS['base_path']));
|
|
if (is_readable($path)) {
|
|
$value['data'] = $path;
|
|
$value['type'] = 'file';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If type is file but it starts with http, https, or // change it to
|
|
// external. Skip tripple slash for local files.
|
|
if ($value['type'] === 'file'
|
|
&& ($http_pos === 0
|
|
|| $https_pos === 0
|
|
|| ($double_slash_pos === 0 && $tripple_slash_pos === FALSE))
|
|
) {
|
|
$value['type'] = 'external';
|
|
}
|
|
|
|
// If type is external and starts with http, https, or // but points to
|
|
// this host change to file, but move it to the top of the aggregation
|
|
// stack.
|
|
if ($value['type'] === 'external'
|
|
&& $mod_base_url_pos - 2 === $double_slash_pos
|
|
&& ($http_pos === 0
|
|
|| $https_pos === 0
|
|
|| $double_slash_pos === 0
|
|
)
|
|
) {
|
|
$path = substr($value['data'], $mod_base_url_pos + $mod_base_url_len);
|
|
if (is_readable($path)) {
|
|
$value['data'] = $path;
|
|
$value['type'] = 'file';
|
|
$value['group'] = JS_LIBRARY;
|
|
$value['every_page'] = TRUE;
|
|
$value['weight'] = -40000;
|
|
}
|
|
else {
|
|
// Fix for subdir issues.
|
|
$parsed = parse_url($path);
|
|
if (strpos($parsed['path'], $GLOBALS['base_path']) === 0) {
|
|
$path = substr($parsed['path'], strlen($GLOBALS['base_path']));
|
|
if (is_readable($path)) {
|
|
$value['data'] = $path;
|
|
$value['type'] = 'file';
|
|
$value['group'] = JS_LIBRARY;
|
|
$value['every_page'] = TRUE;
|
|
$value['weight'] = -40000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
unset($value);
|
|
}
|
|
|
|
/**
|
|
* Alter the CSS or JS array removing empty files from the aggregates.
|
|
*
|
|
* @param array $array
|
|
* CSS or JS array.
|
|
*/
|
|
function advagg_remove_empty_files(array &$array) {
|
|
if (!variable_get('advagg_js_remove_empty_files', ADVAGG_JS_REMOVE_EMPTY_FILES)) {
|
|
return;
|
|
}
|
|
|
|
if (variable_get('advagg_fast_filesystem', ADVAGG_FAST_FILESYSTEM)) {
|
|
foreach ($array as $key => $value) {
|
|
if ($value['type'] !== 'file') {
|
|
continue;
|
|
}
|
|
// Check locally.
|
|
if (!is_readable($value['data']) || filesize($value['data']) == 0) {
|
|
unset($array[$key]);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
module_load_include('inc', 'advagg', 'advagg');
|
|
$files = array();
|
|
foreach ($array as $key => $value) {
|
|
if ($value['type'] !== 'file') {
|
|
continue;
|
|
}
|
|
$files[$key] = $value['data'];
|
|
}
|
|
// Check cache/db.
|
|
$info = advagg_get_info_on_files($files);
|
|
foreach ($info as $key => $values) {
|
|
if (empty($values['filesize'])) {
|
|
$key = array_search($values['data'], $files);
|
|
if (isset($array[$key])) {
|
|
unset($array[$key]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Alter the CSS or JS array adding in DNS domain info.
|
|
*
|
|
* @param array $array
|
|
* CSS or JS array.
|
|
* @param string $type
|
|
* CSS or JS.
|
|
*/
|
|
function advagg_add_default_dns_lookups(array &$array, $type) {
|
|
// Skip if advagg is disabled.
|
|
if (!advagg_enabled()) {
|
|
return;
|
|
}
|
|
// Remove this return once CSS lookups are needed.
|
|
if ($type !== 'js') {
|
|
return;
|
|
}
|
|
|
|
// Add DNS information for some of the more popular modules.
|
|
foreach ($array as &$value) {
|
|
if (!is_string($value['data'])) {
|
|
continue;
|
|
}
|
|
// Google Ad Manager.
|
|
if (strpos($value['data'], '/google_service.') !== FALSE) {
|
|
if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) {
|
|
$temp = $value['dns_prefetch'];
|
|
unset($value['dns_prefetch']);
|
|
$value['dns_prefetch'] = array($temp);
|
|
}
|
|
// Domains in the google_service.js file.
|
|
$value['dns_prefetch'][] = 'https://csi.gstatic.com';
|
|
$value['dns_prefetch'][] = 'https://pubads.g.doubleclick.net';
|
|
$value['dns_prefetch'][] = 'https://partner.googleadservices.com';
|
|
$value['dns_prefetch'][] = 'https://securepubads.g.doubleclick.net';
|
|
|
|
// Domains in the google_ads.js file.
|
|
$value['dns_prefetch'][] = 'https://pagead2.googlesyndication.com';
|
|
|
|
// Other domains that usually get hit.
|
|
$value['dns_prefetch'][] = 'https://cm.g.doubleclick.net';
|
|
$value['dns_prefetch'][] = 'https://tpc.googlesyndication.com';
|
|
}
|
|
|
|
// Google Analytics.
|
|
if (strpos($value['data'], 'GoogleAnalyticsObject') !== FALSE
|
|
|| strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE
|
|
) {
|
|
if (!empty($value['dns_prefetch']) && is_string($value['dns_prefetch'])) {
|
|
$temp = $value['dns_prefetch'];
|
|
unset($value['dns_prefetch']);
|
|
$value['dns_prefetch'] = array($temp);
|
|
}
|
|
if ($GLOBALS['is_https'] && strpos($value['data'], '.google-analytics.com/ga.js') !== FALSE) {
|
|
$value['dns_prefetch'][] = 'https://ssl.google-analytics.com';
|
|
}
|
|
else {
|
|
$value['dns_prefetch'][] = 'https://www.google-analytics.com';
|
|
}
|
|
$value['dns_prefetch'][] = 'https://stats.g.doubleclick.net';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert element into an array at a specific position.
|
|
*
|
|
* @param array $input_array
|
|
* The original array.
|
|
* @param array $new_value
|
|
* The element that is getting inserted.
|
|
* @param int $location_key
|
|
* The key location.
|
|
*
|
|
* @return array
|
|
* The new array with the element merged in.
|
|
*/
|
|
function advagg_insert_into_array_at_location(array $input_array, array $new_value, $location_key) {
|
|
return array_merge(array_slice($input_array, 0, $location_key), $new_value, array_slice($input_array, $location_key));
|
|
}
|
|
|
|
/**
|
|
* Insert element into an array at a specific key location.
|
|
*
|
|
* @param array $input_array
|
|
* The original array.
|
|
* @param array $insert
|
|
* The element that is getting inserted; array(key => value).
|
|
* @param string $target_key
|
|
* The key name.
|
|
* @param int $location
|
|
* After is 1 , 0 is replace, -1 is before.
|
|
*
|
|
* @return array
|
|
* The new array with the element merged in.
|
|
*/
|
|
function advagg_insert_into_array_at_key(array $input_array, array $insert, $target_key, $location = 1) {
|
|
$output = array();
|
|
$new_value = reset($insert);
|
|
$new_key = key($insert);
|
|
foreach ($input_array as $key => $value) {
|
|
if ($key === $target_key) {
|
|
// Insert before.
|
|
if ($location == -1) {
|
|
$output[$new_key] = $new_value;
|
|
$output[$key] = $value;
|
|
}
|
|
// Replace.
|
|
if ($location == 0) {
|
|
$output[$new_key] = $new_value;
|
|
}
|
|
// After.
|
|
if ($location == 1) {
|
|
$output[$key] = $value;
|
|
$output[$new_key] = $new_value;
|
|
}
|
|
}
|
|
else {
|
|
// Pick next key if there is an number collision.
|
|
if (is_numeric($key)) {
|
|
while (isset($output[$key])) {
|
|
$key++;
|
|
}
|
|
}
|
|
$output[$key] = $value;
|
|
}
|
|
}
|
|
// Add to array if not found.
|
|
if (!isset($output[$new_key])) {
|
|
// Before everything.
|
|
if ($location == -1) {
|
|
$output = $insert + $output;
|
|
}
|
|
// After everything.
|
|
if ($location == 1) {
|
|
$output[$new_key] = $new_value;
|
|
}
|
|
|
|
}
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Given a URL output a filename.
|
|
*
|
|
* @param string $url
|
|
* The url.
|
|
* @param string $strict
|
|
* If FALSE then slashes will be kept.
|
|
*
|
|
* @return string
|
|
* The filename.
|
|
*/
|
|
function advagg_url_to_filename($url, $strict = TRUE) {
|
|
// Keep filtering till there are no more changes.
|
|
$decoded1 = _advagg_url_to_filename_filter($url, $strict);
|
|
$decoded2 = _advagg_url_to_filename_filter($decoded1, $strict);
|
|
while ($decoded1 != $decoded2) {
|
|
$decoded1 = _advagg_url_to_filename_filter($decoded2, $strict);
|
|
$decoded2 = _advagg_url_to_filename_filter($decoded1, $strict);
|
|
}
|
|
$filename = $decoded1;
|
|
|
|
// Shorten filename to a max of 250 characters.
|
|
$filename = mb_strcut($filename, 0, 250, mb_detect_encoding($filename));
|
|
return $filename;
|
|
}
|
|
|
|
/**
|
|
* Given a URL output a filtered filename.
|
|
*
|
|
* @param string $url
|
|
* The url.
|
|
* @param string $strict
|
|
* If FALSE then slashes will be kept.
|
|
*
|
|
* @return string
|
|
* The filename.
|
|
*/
|
|
function _advagg_url_to_filename_filter($url, $strict = TRUE) {
|
|
// URL Decode if needed.
|
|
$decoded1 = $url;
|
|
$decoded2 = rawurldecode($decoded1);
|
|
while ($decoded1 != $decoded2) {
|
|
$decoded1 = rawurldecode($decoded2);
|
|
$decoded2 = rawurldecode($decoded1);
|
|
}
|
|
$url = $decoded1;
|
|
|
|
// Replace url spaces with a dash.
|
|
$filename = str_replace(array('%20', '+'), '-', $url);
|
|
|
|
// Remove file system reserved characters
|
|
// https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
|
// Remove control charters
|
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
|
|
// Remove non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
|
|
// Remove URI reserved characters
|
|
// https://tools.ietf.org/html/rfc3986#section-2.2
|
|
// Remove URL unsafe characters
|
|
// https://www.ietf.org/rfc/rfc1738.txt
|
|
if ($strict) {
|
|
$filename = preg_replace('~[<>:"/\\|?*]|[\x00-\x1F]|[\x7F\xA0\xAD]|[#\[\]@!$&\'()+,;=%]|[{}^\~`]~x', '-', $filename);
|
|
}
|
|
else {
|
|
$filename = preg_replace('~[<>:"\\|?*]|[\x00-\x1F]|[\x7F\xA0\xAD]|[#\[\]@!$&\'()+,;=%]|[{}^\~`]~x', '-', $filename);
|
|
}
|
|
|
|
// Replce all white spaces with a dash.
|
|
$filename = preg_replace('/[\r\n\t -]+/', '-', $filename);
|
|
|
|
// Avoid ".", ".." or ".hiddenFiles".
|
|
$filename = ltrim($filename, '.-');
|
|
|
|
// Compress spaces in a file name and replace with a dash.
|
|
// Compress underscores in a file name and replace with a dash.
|
|
// Compress dashes in a file name and replace with a dash.
|
|
$filename = preg_replace(array('/ +/', '/_+/', '/-+/'), '-', $filename);
|
|
|
|
// Compress dashes and dots in a file name and replace with a dot.
|
|
$filename = preg_replace(array('/-*\.-*/', '/\.{2,}/'), '.', $filename);
|
|
|
|
// Lowercase for windows/unix interoperability
|
|
// http://support.microsoft.com/kb/100625.
|
|
$filename = mb_strtolower($filename, 'UTF-8');
|
|
// Remove ? \ ..
|
|
$filename = str_replace(array('?', '\\', '..'), '', $filename);
|
|
|
|
// ".file-name.-" becomes "file-name".
|
|
$filename = trim($filename, '.-');
|
|
|
|
return $filename;
|
|
}
|
|
|
|
/**
|
|
* Given a URI return TRUE if it is external.
|
|
*
|
|
* @param string $uri
|
|
* The uri.
|
|
*
|
|
* @return bool
|
|
* TRUE if external.
|
|
*/
|
|
function advagg_is_external($uri) {
|
|
$http_pos = strpos($uri, 'http://');
|
|
$https_pos = strpos($uri, 'https://');
|
|
$double_slash_pos = strpos($uri, '//');
|
|
if ($http_pos !== 0
|
|
&& $https_pos !== 0
|
|
&& $double_slash_pos !== 0
|
|
) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Same as file_get_contents() but will convert string to UTF-8 if needed.
|
|
*
|
|
* @return mixed
|
|
* The files contents or FALSE on failure.
|
|
*/
|
|
function advagg_file_get_contents() {
|
|
// Get the file contents.
|
|
$file_contents = call_user_func_array('file_get_contents', func_get_args());
|
|
if ($file_contents === FALSE) {
|
|
return $file_contents;
|
|
}
|
|
|
|
// If a BOM is found, convert the file to UTF-8.
|
|
$encoding = advagg_get_encoding_from_bom($file_contents);
|
|
if (!empty($encoding)) {
|
|
$file_contents = advagg_convert_to_utf8($file_contents, $encoding);
|
|
}
|
|
|
|
return $file_contents;
|
|
}
|
|
|
|
/**
|
|
* Get the description text based off the library version.
|
|
*
|
|
* @param string $library_name
|
|
* Name of the library.
|
|
* @param string $module_name
|
|
* Name of the module that contains hook_libraries_info for this library.
|
|
*
|
|
* @return array
|
|
* Description text and info array.
|
|
*/
|
|
function advagg_get_version_description($library_name, $module_name, $only_remote_ok = FALSE) {
|
|
$t = get_t();
|
|
|
|
// Get local and external library version numbers.
|
|
$versions = &drupal_static(__FUNCTION__);
|
|
if (!isset($versions)) {
|
|
$versions = advagg_get_remote_libraries_versions(TRUE);
|
|
}
|
|
|
|
$description = '';
|
|
if (!empty($versions[$library_name]['remote'])
|
|
&& (empty($versions[$library_name]['local'])
|
|
|| $versions[$library_name]['local'] !== $versions[$library_name]['remote']
|
|
)) {
|
|
$library = advagg_get_library($library_name, $module_name);
|
|
if (empty($versions[$library_name]['local'])) {
|
|
$versions[$library_name]['local'] = 'NULL';
|
|
}
|
|
if (!empty($library['installed'])) {
|
|
$description = $t('Go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the @lib_path folder. An example valid filename is %version_file. Current Version: %version.', array(
|
|
'@name' => $library['name'],
|
|
'@lib_path' => $library['library path'],
|
|
'@url-page' => $library['vendor url'],
|
|
'@url-zip' => $library['download url'],
|
|
'@remote' => $versions[$library_name]['remote'],
|
|
'%version' => $versions[$library_name]['local'],
|
|
'%version_file' => $library['library path'] . '/' . $library['version arguments']['file'],
|
|
));
|
|
}
|
|
elseif (!$only_remote_ok && is_callable('libraries_load')) {
|
|
$description = $t('Go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array(
|
|
'@name' => $library['name'],
|
|
'@url-page' => $library['vendor url'],
|
|
'@url-zip' => $library['download url'],
|
|
'@remote' => $versions[$library_name]['remote'],
|
|
'%version_file' => "sites/all/libraries/$library_name/{$library['version arguments']['file']}",
|
|
));
|
|
}
|
|
elseif (!$only_remote_ok) {
|
|
$description = $t('Install the <a href="@url-lib-api">Libraries API</a> module and then go to the <a href="@url-page">@name</a> page and <a href="@url-zip">download</a> the latest version (@remote) into the libraries folder (usually sites/all/libraries). An example valid filename is %version_file.', array(
|
|
'@name' => $library['name'],
|
|
'@url-page' => $library['vendor url'],
|
|
'@url-zip' => $library['download url'],
|
|
'@remote' => $versions[$library_name]['remote'],
|
|
'%version_file' => "sites/all/libraries/$library_name/{$library['version arguments']['file']}",
|
|
'@url-lib-api' => 'https://www.drupal.org/project/libraries',
|
|
));
|
|
}
|
|
}
|
|
$path = drupal_get_path('module', $module_name);
|
|
$info = drupal_parse_info_file("{$path}/{$module_name}.info");
|
|
|
|
// Check if library was unzipped with -master.
|
|
if (!empty($description) && is_callable('libraries_get_libraries')) {
|
|
$libraries_paths = libraries_get_libraries();
|
|
if (!empty($libraries_paths["{$library_name}-master"])) {
|
|
$description = $t('Rename @lib_dir_master to @lib_dir at this location: @lib_path_master.', array(
|
|
'@lib_dir_master' => "{$library_name}-master",
|
|
'@lib_path_master' => $libraries_paths["{$library_name}-master"],
|
|
'@lib_dir' => $library_name,
|
|
));
|
|
}
|
|
}
|
|
|
|
return array($description, $info);
|
|
}
|
|
|
|
/**
|
|
* Given a advagg type this will return the most recent aggregate from the db.
|
|
*
|
|
* @param string $type
|
|
* String: css or js.
|
|
*
|
|
* @return string
|
|
* Returns advagg filename or an empty string on failure.
|
|
*/
|
|
function advagg_generate_advagg_filename_from_db($type) {
|
|
// Get the most recently accessed file from the database.
|
|
$query = db_select('advagg_aggregates_versions', 'aav');
|
|
$query->join('advagg_aggregates', 'aa', 'aa.aggregate_filenames_hash =
|
|
aav.aggregate_filenames_hash');
|
|
$query->join('advagg_files', 'af', 'af.filename_hash = aa.filename_hash AND
|
|
af.filetype = :type', array(':type' => $type));
|
|
$query = $query->fields('aav', array('aggregate_filenames_hash', 'aggregate_contents_hash'))
|
|
->orderBy('atime', 'DESC')
|
|
->range(0, 1);
|
|
$results = $query->execute();
|
|
if (empty($results)) {
|
|
return '';
|
|
}
|
|
$hooks_hash = advagg_get_current_hooks_hash();
|
|
foreach ($results as $row) {
|
|
return $type . ADVAGG_SPACE . $row->aggregate_filenames_hash . ADVAGG_SPACE . $row->aggregate_contents_hash . ADVAGG_SPACE . $hooks_hash . '.' . $type;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display a message if there are requirement issues with AdvAgg.
|
|
*
|
|
* @param array $requirements
|
|
* Other requirements to list besides the standard ones.
|
|
*/
|
|
function advagg_display_message_if_requirements_not_met(array $requirements = array()) {
|
|
include_once DRUPAL_ROOT . '/includes/install.inc';
|
|
module_load_include('install', 'advagg');
|
|
$requirements += advagg_install_fast_checks();
|
|
if (!empty($requirements)) {
|
|
module_load_include('admin.inc', 'system');
|
|
usort($requirements, '_system_sort_requirements');
|
|
$report = theme('status_report', array('requirements' => $requirements));
|
|
drupal_set_message(t('Go to the <a href="@url">status report page</a> and fix the issues that AdvAgg lists there. Sneak peak: !report', array(
|
|
'@url' => url('admin/reports/status'),
|
|
'!report' => $report,
|
|
)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add in the preload header for CSS and JS external files.
|
|
*
|
|
* @param string $url
|
|
* The url of the external host.
|
|
*
|
|
* @return bool
|
|
* TRUE if it was added to the head.
|
|
*/
|
|
function advagg_add_preload_header($url = '', $as = '') {
|
|
// Get defaults and setup static's.
|
|
$list = &drupal_static(__FUNCTION__ . ':list', array());
|
|
$output = &drupal_static(__FUNCTION__ . ':output');
|
|
$header_strlen = &drupal_static(__FUNCTION__ . ':strlen', 0);
|
|
static $resource_hints_preload_order;
|
|
if (!isset($resource_hints_preload_order)) {
|
|
$resource_hints_preload_order = advagg_get_resource_hints_preload_settings();
|
|
}
|
|
if (!isset($output)) {
|
|
$keys = array_keys($resource_hints_preload_order);
|
|
$output = array_fill_keys($keys, array());
|
|
}
|
|
|
|
// Output headers.
|
|
if (empty($url)) {
|
|
|
|
// Call hook_advagg_preload_header_alter()
|
|
drupal_alter('advagg_preload_header', $output);
|
|
|
|
// Build header string.
|
|
$header_value = '';
|
|
foreach ($output as $value) {
|
|
if (!empty($value)) {
|
|
// Remove empty values.
|
|
$value = array_filter($value);
|
|
foreach ($value as $string) {
|
|
$header_strlen += strlen($string) + 2;
|
|
// Don't add if over the limit.
|
|
if ($header_strlen >= variable_get('advagg_resource_hints_preload_max_size', ADVAGG_RESOURCE_HINTS_PRELOAD_MAX_SIZE)) {
|
|
continue;
|
|
}
|
|
// Add to $header_value string.
|
|
if (empty($header_value)) {
|
|
$header_value = $string;
|
|
}
|
|
else {
|
|
$header_value .= ',' . $string;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!empty($header_value)) {
|
|
drupal_add_http_header('Link', $header_value, TRUE);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Check for duplicates.
|
|
if (isset($list[$url])) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Fill in missing info.
|
|
$payload = "<{$url}>; rel=preload";
|
|
list($as, $type, $crossorigin, $parse) = advagg_get_preload_info_from_url($url, $as);
|
|
if (!empty($as) && $as === 'php') {
|
|
$list[$url] = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
$additional_info = array();
|
|
if (!empty($crossorigin)) {
|
|
$additional_info[] = "crossorigin";
|
|
}
|
|
if (!empty($type)) {
|
|
$additional_info[] = $type;
|
|
}
|
|
$additional_info = implode('; ', $additional_info);
|
|
|
|
// Get settings.
|
|
if (!empty($as) && !empty($resource_hints_preload_order[$as])) {
|
|
$settings = $resource_hints_preload_order[$as];
|
|
}
|
|
elseif (!empty($resource_hints_preload_order['all_others'])) {
|
|
$settings = $resource_hints_preload_order['all_others'];
|
|
}
|
|
|
|
// Apply settings.
|
|
if (!$settings['enabled']) {
|
|
$list[$url] = FALSE;
|
|
return FALSE;
|
|
}
|
|
if (!$settings['local'] && empty($parse['host'])) {
|
|
$list[$url] = FALSE;
|
|
return FALSE;
|
|
}
|
|
if (!$settings['external'] && !empty($parse['host'])) {
|
|
$list[$url] = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
// Add additional info and queue.
|
|
if (!empty($as)) {
|
|
$payload .= "; as={$as}";
|
|
}
|
|
if (!empty($additional_info)) {
|
|
$payload .= "; {$additional_info}";
|
|
}
|
|
if (!$settings['push']) {
|
|
$payload .= "; nopush";
|
|
}
|
|
$list[$url] = TRUE;
|
|
$output[$as][] = $payload;
|
|
}
|
|
|
|
/**
|
|
* Given a link get the as, type, and crossorigin attributes.
|
|
*
|
|
* @param string $url
|
|
* Link to the url that will be preloaded.
|
|
* @param string $as
|
|
* What type of content is this; font, image, video, etc.
|
|
* @param string $type
|
|
* The mime type of the file.
|
|
* @param string $crossorigin
|
|
* Preload cross-origin resources; fonts always need to be CORS.
|
|
*
|
|
* @return array
|
|
* An array containing
|
|
*/
|
|
function advagg_get_preload_info_from_url($url, $as = '', $type = '', $crossorigin = NULL) {
|
|
// Get extension.
|
|
$parse = @parse_url($url);
|
|
if (empty($parse['path'])) {
|
|
return FALSE;
|
|
}
|
|
$file_ext = strtolower(pathinfo($parse['path'], PATHINFO_EXTENSION));
|
|
if (empty($file_ext)) {
|
|
$file_ext = basename($parse['path']);
|
|
}
|
|
|
|
// Detect missing parts.
|
|
$list = advagg_preload_list();
|
|
if (empty($as) && !empty($file_ext)) {
|
|
foreach ($list as $as_key => $list_type) {
|
|
$key = array_search($file_ext, $list_type);
|
|
if ($key !== FALSE) {
|
|
$as = $as_key;
|
|
// Type of font, ext is svg but file doesn't contain string font.
|
|
// This will be treated as an image.
|
|
if ($as === 'font'
|
|
&& $file_ext === 'svg'
|
|
&& stripos($url, 'font') === FALSE
|
|
) {
|
|
$as = '';
|
|
}
|
|
}
|
|
if (!empty($as)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (empty($type) && !empty($as)) {
|
|
$type = "$as/$file_ext";
|
|
if ($file_ext === 'svg') {
|
|
$type .= '+xml';
|
|
}
|
|
if ($file_ext === 'js') {
|
|
$type = 'text/javascript';
|
|
}
|
|
}
|
|
if ($as === 'font' && is_null($crossorigin)) {
|
|
$crossorigin = 'anonymous';
|
|
}
|
|
return array($as, $type, $crossorigin, $parse);
|
|
}
|
|
|
|
/**
|
|
* Add preload link to the top of the html head.
|
|
*
|
|
* @param string $url
|
|
* Link to the url that will be preloaded.
|
|
* @param string $media
|
|
* Media types or media queries, allowing for responsive preloading.
|
|
* @param string $as
|
|
* What type of content is this; font, image, video, etc.
|
|
* @param string $type
|
|
* The mime type of the file.
|
|
* @param string $crossorigin
|
|
* Preload cross-origin resources; fonts always need to be CORS.
|
|
*/
|
|
function advagg_add_preload_link($url, $media = '', $as = '', $type = '', $crossorigin = NULL) {
|
|
static $weight = -2000;
|
|
$weight += 0.0001;
|
|
|
|
$href = advagg_file_create_url($url);
|
|
$key = "advagg_preload:{$href}";
|
|
|
|
// Return here if url has already been added.
|
|
$stored_head = drupal_static('drupal_add_html_head');
|
|
if (isset($stored_head[$key])) {
|
|
return TRUE;
|
|
}
|
|
|
|
// Add basic attributes.
|
|
$attributes = array(
|
|
'rel' => 'preload',
|
|
'href' => $href,
|
|
);
|
|
|
|
// Fill in missing info.
|
|
list($as, $type, $crossorigin) = advagg_get_preload_info_from_url($url, $as, $type, $crossorigin);
|
|
|
|
// Exit if no as.
|
|
if (empty($as)) {
|
|
return FALSE;
|
|
}
|
|
// Build up attributes array.
|
|
$attributes['as'] = $as;
|
|
if (!empty($type)) {
|
|
$attributes['type'] = $type;
|
|
}
|
|
if (!empty($crossorigin)) {
|
|
$attributes['crossorigin'] = $crossorigin;
|
|
}
|
|
if (!empty($media)) {
|
|
$attributes['media'] = $media;
|
|
}
|
|
// Call hook_advagg_preload_link_attributes_alter()
|
|
drupal_alter('advagg_preload_link_attributes', $attributes);
|
|
|
|
// Add to HTML head.
|
|
$element = array(
|
|
'#type' => 'html_tag',
|
|
'#tag' => 'link',
|
|
'#attributes' => $attributes,
|
|
'#weight' => $weight,
|
|
);
|
|
drupal_add_html_head($element, $key);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Generate a list of file types for the as field given the extension.
|
|
*
|
|
* @return array
|
|
* Returns an array of arrays.
|
|
*/
|
|
function advagg_preload_list() {
|
|
$list = array(
|
|
'font' => array(
|
|
'woff2',
|
|
'woff',
|
|
'ttf',
|
|
'otf',
|
|
'eot',
|
|
// Need to check if the svg file is in a font folder.
|
|
'svg',
|
|
),
|
|
'image' => array(
|
|
'gif',
|
|
'jpg',
|
|
'jpeg',
|
|
'jpe',
|
|
'jif',
|
|
'jfif',
|
|
'jfi',
|
|
'png',
|
|
'webp',
|
|
'jp2',
|
|
'jpx',
|
|
'jxr',
|
|
'heif',
|
|
'heic',
|
|
'bpg',
|
|
'svg',
|
|
),
|
|
'style' => array(
|
|
'css',
|
|
),
|
|
'script' => array(
|
|
'js',
|
|
),
|
|
'video' => array(
|
|
'mp4',
|
|
'webm',
|
|
'ogg',
|
|
),
|
|
);
|
|
|
|
// Call hook_advagg_preload_list_alter()
|
|
drupal_alter('advagg_preload_list', $list);
|
|
return $list;
|
|
}
|
|
|
|
/**
|
|
* Save form defaults or recommended values.
|
|
*
|
|
* @param array $element
|
|
* Form element or child element.
|
|
*
|
|
* @return array
|
|
* An array of form names and the recommended value for that setting.
|
|
*/
|
|
function advagg_find_all_recommended_admin_values(array &$element, $key_name = '#recommended_value') {
|
|
$results = array();
|
|
$children = element_children($element);
|
|
foreach ($children as $key) {
|
|
$child = $element[$key];
|
|
if (is_array($child)) {
|
|
if (!empty($child['#type'])
|
|
&& !empty($child['#name'])
|
|
&& isset($child[$key_name])
|
|
) {
|
|
$results[$child['#name']] = $child[$key_name];
|
|
}
|
|
$results = array_merge($results, advagg_find_all_recommended_admin_values($child, $key_name));
|
|
}
|
|
unset($child);
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get form values that have changed.
|
|
*
|
|
* @param array $element
|
|
* Form element or child element.
|
|
*
|
|
* @return array
|
|
* An array of form names and the recommended value for that setting.
|
|
*/
|
|
function advagg_find_all_changed_admin_values(array &$element) {
|
|
$results = array();
|
|
$children = element_children($element);
|
|
foreach ($children as $key) {
|
|
$child = $element[$key];
|
|
if (is_array($child)) {
|
|
if (!empty($child['#type'])
|
|
&& !empty($child['#name'])
|
|
&& isset($child['#default_value'])
|
|
&& isset($child['#value'])
|
|
) {
|
|
if ($child['#type'] === 'checkboxes') {
|
|
// Add in not selected by default values.
|
|
$child['#value'] += array_diff_assoc($child['#default_value'], $child['#value']);
|
|
}
|
|
if ($child['#default_value'] != $child['#value']) {
|
|
$results[$child['#name']] = array($child['#value'], $child['#default_value']);
|
|
}
|
|
}
|
|
$results = array_merge($results, advagg_find_all_changed_admin_values($child));
|
|
}
|
|
unset($child);
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Get form title and description.
|
|
*
|
|
* @param array $element
|
|
* Form element or child element.
|
|
*
|
|
* @return array
|
|
* An array of form names and the recommended value for that setting.
|
|
*/
|
|
function advagg_find_title(array &$element) {
|
|
$results = array();
|
|
$children = element_children($element);
|
|
foreach ($children as $key) {
|
|
$child = $element[$key];
|
|
if (is_array($child)) {
|
|
if (!empty($child['#type'])
|
|
&& !empty($child['#name'])
|
|
&& isset($child['#title'])
|
|
&& isset($child['#default_value'])
|
|
&& !isset($results[$child['#name']])
|
|
&& $child['#type'] !== 'radio'
|
|
) {
|
|
$results[$child['#name']] = $child['#title'];
|
|
}
|
|
$results = array_merge($results, advagg_find_title($child));
|
|
}
|
|
unset($child);
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Save form defaults or recommended values.
|
|
*
|
|
* @param array $form_state
|
|
* Form state array from drupal form submit.
|
|
* @param string $trigger_key
|
|
* The key of the setting from the form that controls this.
|
|
*/
|
|
function advagg_set_admin_form_defaults_recommended(array &$form_state, $trigger_key) {
|
|
$changed = array();
|
|
$recommended_values = array();
|
|
|
|
// Set to recommended values.
|
|
if ($form_state['values'][$trigger_key] == 2) {
|
|
$recommended_values = advagg_find_all_recommended_admin_values($form_state['complete form']);
|
|
foreach ($recommended_values as $key => $value) {
|
|
if (!isset($form_state['values'][$key])) {
|
|
$changed[$key] = array($value);
|
|
}
|
|
elseif ($value != $form_state['values'][$key]) {
|
|
$changed[$key] = array($value, $form_state['values'][$key]);
|
|
}
|
|
$form_state['values'][$key] = $value;
|
|
}
|
|
}
|
|
|
|
// Set to defaults.
|
|
if ($form_state['values'][$trigger_key] == 0 || $form_state['values'][$trigger_key] == 2) {
|
|
// Reset to defaults.
|
|
foreach ($form_state['values'] as $key => &$value) {
|
|
// Skip non advagg settings, trigger key, or if a recommended value.
|
|
if (strpos($key, 'advagg_') !== 0
|
|
|| $key === $trigger_key
|
|
|| isset($changed[$key])
|
|
|| isset($recommended_values[$key])
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// Default to FALSE.
|
|
$default = FALSE;
|
|
// Get easy defaults.
|
|
if (defined(strtoupper($key))) {
|
|
$default = constant(strtoupper($key));
|
|
}
|
|
|
|
// Get more complex default values.
|
|
if ($key === 'advagg_resource_hints_preload_settings') {
|
|
$default = advagg_get_resource_hints_preload_settings(TRUE);
|
|
foreach ($default as $key => &$values) {
|
|
$default[$key]['weight'] = $values['#weight'];
|
|
unset($default[$key]['#weight'], $values['#weight'], $default[$key]['title'], $values['title']);
|
|
ksort($values);
|
|
}
|
|
ksort($default);
|
|
foreach ($value as $key => &$values) {
|
|
ksort($values);
|
|
}
|
|
ksort($value);
|
|
}
|
|
if ($key === 'advagg_relocate_css_inline_import_browsers') {
|
|
$default = array(
|
|
'woff2' => 'woff2',
|
|
'woff' => 'woff',
|
|
'ttf' => 'ttf',
|
|
'eot' => 0,
|
|
'svg' => 0,
|
|
);
|
|
}
|
|
|
|
// See if it changed.
|
|
if ($default != $value) {
|
|
// After, Before.
|
|
$changed[$key] = array($default, $value);
|
|
$value = $default;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($form_state['values'][$trigger_key] == 4) {
|
|
$changed = advagg_find_all_changed_admin_values($form_state['complete form']);
|
|
if (isset($changed[$trigger_key])) {
|
|
unset($changed[$trigger_key]);
|
|
}
|
|
}
|
|
|
|
$all_titles_descriptions = advagg_find_title($form_state['complete form']);
|
|
foreach ($changed as $key => $values) {
|
|
|
|
// Remove things that didn't really change.
|
|
if (isset($values[1])) {
|
|
if ($values[0] == $values[1]) {
|
|
unset($changed[$key]);
|
|
continue;
|
|
}
|
|
if (is_string($values[0])
|
|
&& is_string($values[1])
|
|
&& trim($values[0]) == trim($values[1])
|
|
) {
|
|
unset($changed[$key]);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Make output nicer.
|
|
if (!isset($values[1])) {
|
|
$values[1] = NULL;
|
|
}
|
|
if (is_bool($values[0]) && !is_bool($values[1])
|
|
|| !is_bool($values[0]) && is_bool($values[1])
|
|
) {
|
|
$values[0] = (bool) $values[0];
|
|
$values[1] = (bool) $values[1];
|
|
}
|
|
if (is_int($values[0]) && !is_int($values[1])
|
|
|| !is_int($values[0]) && is_int($values[1])
|
|
) {
|
|
$values[0] = (int) $values[0];
|
|
$values[1] = (int) $values[1];
|
|
}
|
|
|
|
// Let user know what changed.
|
|
if (empty($all_titles_descriptions[$key])) {
|
|
drupal_set_message(t('%before -> %after for %title', array(
|
|
'%title' => $key,
|
|
'%before' => var_export($values[1], TRUE),
|
|
'%after' => var_export($values[0], TRUE),
|
|
)));
|
|
}
|
|
else {
|
|
drupal_set_message(t('%before -> %after for %title', array(
|
|
'%title' => $all_titles_descriptions[$key],
|
|
'%before' => var_export($values[1], TRUE),
|
|
'%after' => var_export($values[0], TRUE),
|
|
)));
|
|
}
|
|
}
|
|
|
|
return $changed;
|
|
}
|
|
|
|
/**
|
|
* Given a list of items see what ones need to be inserted/updated or deleted.
|
|
*
|
|
* @param array $defaults
|
|
* Array of default values, representing a row in the db.
|
|
* @param mixed $new_values
|
|
* Array of edited values, representing a row in the db.
|
|
*
|
|
* @return array
|
|
* Nested array strucutre; only the diff.
|
|
*/
|
|
function advagg_diff_multi(array $defaults, $new_values) {
|
|
$result = array();
|
|
|
|
foreach ($defaults as $key => $val) {
|
|
if (is_array($val) && isset($new_values[$key])) {
|
|
$tmp = advagg_diff_multi($val, $new_values[$key]);
|
|
if ($tmp) {
|
|
$result[$key] = $tmp;
|
|
}
|
|
}
|
|
elseif (!isset($new_values[$key])) {
|
|
$result[$key] = NULL;
|
|
}
|
|
elseif ($val != $new_values[$key]) {
|
|
$result[$key] = $new_values[$key];
|
|
}
|
|
if (isset($new_values[$key])) {
|
|
unset($new_values[$key]);
|
|
}
|
|
}
|
|
|
|
$result = $result + $new_values;
|
|
return $result;
|
|
}
|