' . $readme . ''; } } else { $output = '
' . $readme . '
'; } 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 = ""; $suffix = ""; 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 = ""; $suffix = ""; 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 = ""; $suffix = ""; 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: @info', 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 (module_exists('s3fs') && is_callable('_s3fs_get_config')) { $s3fs_config = _s3fs_get_config(); if (empty($s3fs_config['no_rewrite_cssjs'])) { $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 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', ); $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', ); } $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' => "\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'; } } } /** * 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 !== '' && $key[0] === '#') { 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 performance page. 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 = ""; $suffix = ""; $variables['page'] = $prefix . $variables['page'] . $suffix; $prefix = ""; $suffix = ""; $variables['page_top'] = $prefix . $variables['page_top'] . $suffix; $prefix = ""; $suffix = ""; $variables['page_bottom'] = $prefix . $variables['page_bottom'] . $suffix; $matches = array(); preg_match_all('//', $variables['page_top'], $matches); $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); preg_match_all('//', $variables['page'], $matches); $advagg_script_alt_scope_scripts = array_merge($matches[1], $advagg_script_alt_scope_scripts); preg_match_all('//', $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'], ""); $pos_page = strpos($variables['page'], ""); $pos_page_bottom = strpos($variables['page_bottom'], ""); if ($pos_page_top !== FALSE) { $pos_page_top += strlen(""); $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(""); $variables['page'] = substr_replace($variables['page'], "\n$scripts", $pos_page, 0); $js_cache[$scope] = $scripts; } elseif ($pos_page_bottom !== FALSE) { $pos_page_bottom += strlen(""); $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('//', '', $variables['page_top']); $variables['page'] = preg_replace('//', '', $variables['page']); $variables['page_bottom'] = preg_replace('//', '', $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 = '
' . str_replace(array('<', '>'), array('<', '>'), print_r($debug, TRUE)) . '
'; } 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, '', $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), ' $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 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) { // 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. $setting['ajaxPageState']['js'] = 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'])) { // Get settings from the static. $javascript_static = &drupal_static('drupal_add_js', array()); // Merge in last js ajaxPageState into the current data. $reverse_settings = array_reverse($javascript_static['settings']['data']); foreach ($reverse_settings as $key => $values) { if (is_scalar($values) || empty($values)) { // Skip if not an array or object. continue; } $values = (array) $values; if (!array_key_exists('ajaxPageState', $values)) { // Skip if $values['ajaxPageState'] does not exist. continue; } if (is_scalar($values['ajaxPageState']) || empty($values['ajaxPageState'])) { // Skip if not an array or object. continue; } $values['ajaxPageState'] = (array) $values['ajaxPageState']; if (array_key_exists('js', $values['ajaxPageState'])) { // $values['ajaxPageState']['js'] exists. $javascript['settings']['data'][] = $reverse_settings[$key]; break; } } $items['settings']['data'] = $javascript['settings']['data']; // Add in this round of settings. $items['settings']['data'][] = $setting; } else { // Add the js ajaxPageState to the drupal.settings. drupal_add_js($setting, 'setting'); } // 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 database updates. 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 AdvAgg Operations page and clicking the Toggle the "aggregation bypass cookie" for this browser 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 logging in with a user with the "administer site configuration" permissions and going to the AdvAgg Operations page (located at @advagg_operations) and clicking the Toggle the "aggregation bypass cookie" for this browser 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 .= '\n"; return $output; } } /** * Replace quotes with the html version of it. * * @param string $string * Input string. Convert quotes to html chars. * * @return string * Transformed string. */ function advagg_jsspecialchars($string = '') { $string = str_replace('"', '"', $string); $string = str_replace("'", ''', $string); return $string; } /** * Callback for pre_render to add elements needed for JavaScript to be rendered. * * This function evaluates the aggregation enabled/disabled condition on a group * by group basis by testing whether an aggregate file has been made for the * group rather than by testing the site-wide aggregation setting. This allows * this function to work correctly even if modules have implemented custom * logic for grouping and aggregating files. * * @param array $elements * A render array containing: * - #items: The JavaScript items as returned by drupal_add_js() and * altered by drupal_get_js(). * - #group_callback: A function to call to group #items. Following * this function, #aggregate_callback is called to aggregate items within * the same group into a single file. * - #aggregate_callback: A function to call to aggregate the items within * the groups arranged by the #group_callback function. * * @return array * A render array that will render to a string of JavaScript tags. * * @see drupal_get_js() */ function advagg_pre_render_scripts(array $elements) { // Don't run it twice. if (!empty($elements['#groups'])) { return $elements; } // Group and aggregate the items. if (isset($elements['#group_callback'])) { // Call advagg_group_js(). $elements['#groups'] = $elements['#group_callback']($elements['#items']); } if (isset($elements['#aggregate_callback'])) { // Call _advagg_aggregate_js(). $elements['#aggregate_callback']($elements['#groups']); } // A dummy query-string is added to filenames, to gain control over // browser-caching. The string changes on every update or full cache // flush, forcing browsers to load a new copy of the files, as the // URL changed. Files that should not be cached (see drupal_add_js()) // get REQUEST_TIME as query-string instead, to enforce reload on every // page request. $default_query_string = variable_get('css_js_query_string', '0'); // For inline JavaScript to validate as XHTML, all JavaScript containing // XHTML needs to be wrapped in CDATA. To make that backwards compatible // with HTML 4, we need to comment out the CDATA-tag. $embed_prefix = "\n\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/* */\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 = '
' . filter_xss(print_r($data, TRUE)) . '
'; } 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: @changes', 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']); $library['remote']['url'] = "https://rawgit.com{$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]}"; $library['remote']['url'] = "https://rawgit.com{$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)); } /** * 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 @name page and download 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 @name page and download 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 Libraries API module and then go to the @name page and download 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. */ function advagg_display_message_if_requirements_not_met() { 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 status report page 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']) && $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; if (defined(strtoupper($key))) { $default = constant(strtoupper($key)); } 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, ); } if ($default != $value) { $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; }