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('&lt;', '&gt;'), 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('"', '&quot;', $string);
$string = str_replace("'", '&#039;', $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;
}