'advagg_missing_css', 'type' => MENU_CALLBACK, 'access callback' => TRUE, 'file path' => $file_path, 'file' => 'advagg.missing.inc', ); $items[$js_path . '/%'] = array( 'page callback' => 'advagg_missing_js', 'type' => MENU_CALLBACK, 'access callback' => TRUE, 'file path' => $file_path, 'file' => 'advagg.missing.inc', ); $items['admin/settings/advagg'] = array( 'title' => 'Advanced CSS/JS Aggregation', 'description' => 'Configuration for Advanced CSS/JS Aggregation.', 'page callback' => 'advagg_admin_page', 'type' => MENU_NORMAL_ITEM, 'access arguments' => array('administer site configuration'), 'file path' => $file_path, 'file' => 'advagg.admin.inc', ); $items['admin/settings/advagg/config'] = array( 'title' => 'Configuration', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, ); $items['admin/settings/advagg/info'] = array( 'title' => 'Information', 'description' => 'More detailed information about advagg.', 'page callback' => 'advagg_admin_info_page', 'type' => MENU_LOCAL_TASK, 'access arguments' => array('administer site configuration'), 'file path' => $file_path, 'file' => 'advagg.admin.inc', ); $items['admin_menu/flush-cache/advagg'] = array( 'page callback' => 'advagg_admin_flush_cache', 'type' => MENU_CALLBACK, 'access arguments' => array('administer site configuration'), 'file path' => $file_path, 'file' => 'advagg.admin.inc', ); return $items; } /** * Implementation of hook_admin_menu(). * * Add in a cache flush for advagg. */ function advagg_admin_menu(&$deleted) { $links = array(); $links[] = array( 'title' => 'Adv CSS/JS Agg', 'path' => 'admin_menu/flush-cache/advagg', 'query' => 'destination', 'parent_path' => 'admin_menu/flush-cache', ); return $links; } /** * Implementation of hook_admin_menu_output_alter(). * * Add in a cache flush for advagg. */ function advagg_admin_menu_output_alter(&$content) { if (!empty($content['icon']['icon']['flush-cache']['#access']) && !empty($content['icon']['icon']['flush-cache']['requisites']) && empty($content['icon']['icon']['flush-cache']['advagg'])) { $content['icon']['icon']['flush-cache']['advagg'] = $content['icon']['icon']['flush-cache']['requisites']; $content['icon']['icon']['flush-cache']['advagg']['#title'] = t('Adv CSS/JS Agg'); $content['icon']['icon']['flush-cache']['advagg']['#href'] = 'admin_menu/flush-cache/advagg'; } } /** * Implementation of hook_cron(). */ function advagg_cron() { if (!variable_get('advagg_prune_on_cron', ADVAGG_PRUNE_ON_CRON)) { return; } // Set the oldest file/bundle to keep at 2 weeks. $max_time = module_exists('advagg_bundler') ? variable_get('advagg_bundler_outdated', ADVAGG_BUNDLER_OUTDATED) : 1209600; $max_file_time = time() - $max_time; $max_bundle_time = time() - ($max_time * 3); $bundles_removed = 0; $files_removed = array(); // Prune old files $results = db_query("SELECT filename, filename_md5 FROM {advagg_files}"); while ($row = db_fetch_array($results)) { // If the file exists, do nothing if (file_exists($row['filename'])) { continue; } // Remove bundles referencing missing files, if they are older than 2 weeks. $bundles = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s' AND timestamp < %d", $row['filename_md5'], $max_file_time); while ($bundle_md5 = db_result($bundles)) { $bundles_removed++; db_query("DELETE FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5); } $count = db_result(db_query("SELECT COUNT(*) FROM {advagg_bundles} WHERE filename_md5 = '%s'", $row['filename_md5'])); // If no more bundles reference the missing file then remove the file. if (empty($count)) { db_query("DELETE FROM {advagg_files} WHERE filename_md5 = '%s'", $row['filename_md5']); $files_removed[] = $row['filename']; } } // Prune old bundles $bundles_removed += db_result(db_query(" SELECT COUNT(DISTINCT bundle_md5) AS advagg_count FROM {advagg_bundles} WHERE timestamp < %d ", $max_bundle_time)); $results = db_query("DELETE FROM {advagg_bundles} WHERE timestamp < %d", $max_bundle_time); // Report to watchdog if anything was done. if (!empty($bundles_removed) || !empty($files_removed)) { watchdog('advagg', 'Cron ran and the following files where removed from the database: %files
%count old bundles where also removed from the database.', array( '%files' => implode(', ', $files_removed), '%count' => $bundles_removed, )); } } /** * Implementation of hook_init(). */ function advagg_init() { global $base_path, $conf, $_advagg; // Disable advagg if requested. if (isset($_GET['advagg']) && $_GET['advagg'] == -1 && user_access('bypass advanced aggregation')) { $conf['advagg_enabled'] = FALSE; $conf['advagg_use_full_cache'] = FALSE; } // Enable debugging if requested. if (isset($_GET['advagg-debug']) && $_GET['advagg-debug'] == 1 && user_access('bypass advanced aggregation')) { $conf['advagg_debug'] = TRUE; $conf['advagg_use_full_cache'] = FALSE; } // Enable core preprocessing if requested. if (isset($_GET['advagg-core']) && $_GET['advagg-core'] == 1 && user_access('bypass advanced aggregation')) { $conf['preprocess_css'] = TRUE; $conf['preprocess_js'] = TRUE; $conf['advagg_use_full_cache'] = FALSE; } // Include the ctools_ajax_run_page_preprocess() function. if (function_exists('ctools_include')) { ctools_include('ajax'); } // Disable ctools_ajax_page_preprocess() if this functionality is available. if (variable_get('advagg_enabled', ADVAGG_ENABLED) && function_exists('ctools_ajax_run_page_preprocess')) { ctools_ajax_run_page_preprocess(FALSE); $_advagg['ctools_patched'] = TRUE; } } /** * Implementation of hook_theme_registry_alter(). * * Make sure our preprocess function runs last for page. * * @param $theme_registry * The existing theme registry data structure. */ function advagg_theme_registry_alter(&$theme_registry) { global $_advagg; if (isset($theme_registry['page'])) { // If jquery_update's preprocess function is there already, remove it. if (module_exists('jquery_update') && $key = array_search('jquery_update_preprocess_page', $theme_registry['page']['preprocess functions'])) { unset($theme_registry['page']['preprocess functions'][$key]); } // If ctools hasn't been patched remove it from getting pre-processed. if ( !empty($_advagg['ctools_patched']) && module_exists('ctools') && $key = array_search('ctools_ajax_page_preprocess', $theme_registry['page']['preprocess functions']) ) { unset($theme_registry['page']['preprocess functions'][$key]); } // Add our own preprocessing function to the end of the array. $theme_registry['page']['preprocess functions'][] = 'advagg_processor'; // If labjs's is enabled, move it to the bottom. if (module_exists('labjs') && $key = array_search('labjs_preprocess_page', $theme_registry['page']['preprocess functions'])) { $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key]; unset($theme_registry['page']['preprocess functions'][$key]); } // If designkit is enabled, move it to the bottom. if (module_exists('designkit') && $key = array_search('designkit_preprocess_page', $theme_registry['page']['preprocess functions'])) { $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key]; unset($theme_registry['page']['preprocess functions'][$key]); } // If conditional styles is enabled, move it to the bottom. if (module_exists('conditional_styles') && $key = array_search('conditional_styles_preprocess_page', $theme_registry['page']['preprocess functions'])) { $theme_registry['page']['preprocess functions'][] = $theme_registry['page']['preprocess functions'][$key]; unset($theme_registry['page']['preprocess functions'][$key]); } } } /** * Get the CSS & JS path for advagg. * * @param $reset * reset the static variables. * @return * array($css_path, $js_path) */ function advagg_get_root_files_dir($reset = FALSE) { static $css_path = ''; static $js_path = ''; if ($reset) { $css_path = ''; $js_path = ''; } if (!empty($css_path) && !empty($js_path)) { return array($css_path, $js_path); } $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); if (!$public_downloads) { $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR); } if (empty($custom_path)) { $file_path = file_directory_path(); $css_path = file_create_path($file_path . '/advagg_css'); $js_path = file_create_path($file_path . '/advagg_js'); return array($css_path, $js_path); } file_check_directory($custom_path, FILE_CREATE_DIRECTORY); // Get path name if (!variable_get('advagg_no_conf_path', FALSE)) { $conf_path = conf_path(); if (is_link($conf_path)) { $path = readlink($conf_path); } $conf_path = str_replace("\\", '/', $conf_path); $conf_path = explode('/', $conf_path); $conf_path = array_pop($conf_path); $custom_path = $custom_path . '/' . $conf_path; } file_check_directory($custom_path, FILE_CREATE_DIRECTORY); $css_path = $custom_path . '/advagg_css'; $js_path = $custom_path . '/advagg_js'; file_check_directory($css_path, FILE_CREATE_DIRECTORY); file_check_directory($js_path, FILE_CREATE_DIRECTORY); return array($css_path, $js_path); } /** * Merge 2 css arrays together. * * @param $array1 * first array * @param $array2 * second array * @return * combined array */ function advagg_merge_css($array1, $array2) { if (!empty($array2)) { foreach ($array2 as $media => $types) { foreach ($types as $type => $files) { foreach ($files as $file => $preprocess) { $array1[$media][$type][$file] = $preprocess; } } } } return $array1; } /** * Merge 2 css arrays together. * * @param $array1 * first array * @param $array2 * second array * @return * combined array */ function advagg_merge_inline_css($array1, $array2) { foreach ($array2 as $media => $types) { foreach ($types as $type => $blobs) { foreach ($blobs as $prefix => $data) { foreach ($data as $suffix => $blob) { $array1[$media][$type][$prefix][$suffix] = $blob; } } } } return $array1; } /** * Remove .less files from the array. * * @param $css_func * Drupal CSS array. */ function advagg_css_array_fixer(&$css_func) { if (!module_exists('less')) { return; } // Remove '.css.less' files from the stack. foreach ($css_func as $k => $v) { foreach ($v as $ke => $va) { foreach ($va as $file => $preprocess) { if (substr($file, -5) === '.less') { unset($css_func[$k][$ke][$file]); } } } } } /** * Remove css files from the array if there is a color module equivalent of it. * * @param $css_func * Drupal CSS array. */ function advagg_color_css(&$css) { if (!module_exists('color') || empty($css['all']['theme'])) { return; } $file_path = file_directory_path(); // Find all color css files. $files = array(); foreach ($css['all']['theme'] as $file => $preprocess) { if (strpos($file, $file_path) === FALSE) { continue; } if (strpos($file, '/color/') === FALSE) { continue; } $files[basename($file)] = $file; } // Return if no color css files are found. if (empty($files)) { return; } // Remove css files from the stack if there is a color variant of it. foreach ($css['all']['theme'] as $file => $preprocess) { $basename = basename($file); if (isset($files[$basename]) && !in_array($file, $files)) { unset($css['all']['theme'][$file]); } } } /** * See if a string ends with a substring. * * @param $haystack * The main string being compared. * @param $needle * The secondary string being compared. * @return * bool */ function advagg_string_ends_with($haystack, $needle) { // Define substr_compare if it doesn't exist (PHP 4 fix). if (!function_exists('substr_compare')) { /** * Binary safe comparison of two strings from an offset, up to length * characters. * * Compares main_str from position offset with str up to length characters. * @see http://php.net/substr-compare#53084 * * @param $main_str * The main string being compared. * @param $str * The secondary string being compared. * @param $offset * The start position for the comparison. If negative, it starts counting * from the end of the string. * @param $length * The length of the comparison. The default value is the largest of the * length of the str compared to the length of main_str less the offset. * @param $case_insensitivity * If TRUE, comparison is case insensitive. * @return * Returns < 0 if main_str from position offset is less than str, > 0 if * it is greater than str, and 0 if they are equal. If offset is equal to * or greater than the length of main_str or length is set and is less than * 1, substr_compare() prints a warning and returns FALSE. */ function substr_compare($main_str, $str, $offset, $length = NULL, $case_insensitivity = FALSE) { $offset = (int) $offset; // Throw a warning because the offset is invalid if ($offset >= strlen($main_str)) { trigger_error('The start position cannot exceed initial string length.', E_USER_WARNING); return FALSE; } // We are comparing the first n-characters of each string, so let's use the PHP function to do it if ($offset == 0 && is_int($length) && $case_insensitivity === TRUE) { return strncasecmp($main_str, $str, $length); } // Get the substring that we are comparing if (is_int($length)) { $main_substr = substr($main_str, $offset, $length); $str_substr = substr($str, 0, $length); } else { $main_substr = substr($main_str, $offset); $str_substr = $str; } // Return a case-insensitive comparison of the two strings if ($case_insensitivity === TRUE) { return strcasecmp($main_substr, $str_substr); } // Return a case-sensitive comparison of the two strings return strcmp($main_substr, $str_substr); } } $haystack_len = strlen($haystack); $needle_len = strlen($needle); if ($needle_len > $haystack_len) { return FALSE; } return substr_compare($haystack, $needle, $haystack_len -$needle_len, $needle_len, TRUE) === 0; } /** * Implementation of hook_advagg_disable_processor(). */ function advagg_advagg_disable_processor() { // Disable advagg on the configuration page; in case something bad happened. if (isset($_GET['q']) && ( $_GET['q'] == 'admin/settings/advagg' || $_GET['q'] == 'admin/settings/advagg/config' || $_GET['q'] == 'batch' ) ) { return TRUE; } } /** * Return the server schema (http or https). * * @return string * http OR https. */ function advagg_get_server_schema() { return ( (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') || (isset($_SERVER['HTTP_HTTPS']) && $_SERVER['HTTP_HTTPS'] == 'on') ) ? 'https' : 'http'; } /** * Process variables for page.tpl.php * * @param $variables * The existing theme data structure. */ function advagg_processor(&$variables) { global $_advagg, $conf; // Invoke hook_advagg_disable_processor $disabled = module_invoke_all('advagg_disable_processor'); // If disabled, skip if (!variable_get('advagg_enabled', ADVAGG_ENABLED) || in_array(TRUE, $disabled, TRUE)) { if (module_exists('jquery_update')) { return jquery_update_preprocess_page($variables); } else { return; } } // Do not use the cache if advagg is set in the URL. if (isset($_GET['advagg']) && user_access('bypass advanced aggregation')) { $conf['advagg_use_full_cache'] = FALSE; } // Do not use the cache if the disable cookie is set. $cookie_name = 'AdvAggDisabled'; $key = md5(drupal_get_private_key()); if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { $conf['advagg_use_full_cache'] = FALSE; } // http or https. $schema = advagg_get_server_schema(); // CSS $css_var = $variables['css']; $css_orig = $css_var; if (!variable_get('advagg_only_css_from_variables', ADVAGG_ONLY_CSS_FROM_VARIABLES)) { $css_func = drupal_add_css(); advagg_css_array_fixer($css_func); } else { $css_func = array(); } $css = advagg_merge_css($css_var, $css_func); $css_func_inline = advagg_add_css_inline(); if (!empty($css_func_inline)) { $css = advagg_merge_inline_css($css, $css_func_inline); } $css_conditional_styles = !empty($variables['conditional_styles']) ? $variables['conditional_styles'] : ''; $css_styles = $variables['styles']; advagg_color_css($css); // Try cache. if (variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE)) { // Build the cache ID // md5 of the CSS input // http or https // hostname // the js rendering function // css/js query string $cid = 'advagg_processor:css:' . md5(serialize(array($css, $css_conditional_styles))) . ':' . $schema . ':' . $_SERVER['HTTP_HOST'] . ':' . variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION) . ':' . substr(variable_get('css_js_query_string', '0'), 0, 1); $cache = cache_get($cid, 'cache_advagg_bundle_reuse'); } elseif (isset($cid)) { unset($cid); } if (!empty($cache->data)) { $variables['styles'] = $cache->data; } else { // Build HTML code. $processed_css = advagg_process_css($css); if (!empty($processed_css)) { $variables['styles'] = $processed_css; } $variables['styles'] .= $css_conditional_styles; // Cache output. if (isset($cid) && variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE) && lock_acquire($cid)) { cache_set($cid, $variables['styles'], 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); lock_release($cid); } } // JS $js_code = array(); $js_code['header'] = drupal_add_js(NULL, NULL, 'header'); if (variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) { $js_code['footer'] = drupal_add_js(NULL, NULL, 'footer'); } $skip_keys = variable_get('advagg_region_skip_keys', array('styles', 'scripts', 'zebra', 'id', 'directory', 'layout', 'head_title', 'base_path', 'front_page', 'head', 'body_classes', 'header', 'footer', 'closure')); foreach ($variables as $key => $value) { if (!in_array($key, $skip_keys) && is_string($value) && !empty($value) && !isset($js_code[$key])) { $js_code[$key] = drupal_add_js(NULL, NULL, $key); } } $js_code_orig = $js_code; // Try cache. if (variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE)) { // Build the cache ID // md5 of the JS input // http or https // hostname // the js rendering function // css/js query string $cid = 'advagg_processor:js:' . md5(serialize($js_code)) . ':' . $schema . ':' . $_SERVER['HTTP_HOST'] . ':' . variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION) . ':' . substr(variable_get('css_js_query_string', '0'), 0, 1); $cache = cache_get($cid, 'cache_advagg_bundle_reuse'); } elseif (isset($cid)) { unset($cid); } if (!empty($cache->data)) { $js_code = $cache->data; } else { // Build HTML code. advagg_jquery_updater($js_code['header']); $js_code = advagg_process_js($js_code); // Cache array. if (isset($cid) && variable_get('advagg_use_full_cache', ADVAGG_USE_FULL_CACHE) && lock_acquire($cid)) { cache_set($cid, $js_code, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); lock_release($cid); } } // Place JS in the correct places. foreach ($js_code as $key => $value) { if ($key == 'header') { $variables['scripts'] = $value; } elseif ($key == 'footer' && variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) { $variables['closure'] .= $value; } else { $variables[$key] .= $value; } } // Send requests to server if async enabled. advagg_async_send_http_request(); // Write debug info to watchdog if debugging enabled. if (variable_get('advagg_debug', ADVAGG_DEBUG)) { $data = array( 'css_before_vars' => $css_orig, 'css_before_function' => $css_func, 'css_before_styles' => $css_styles, 'css_before_inline' => $css_func_inline, 'css_before_conditional_styles' => $css_conditional_styles, 'css_merged' => $css, 'css_after' => $processed_css, 'js_before' => $js_code_orig, 'js_after' => $js_code, ); $data['runtime'] = isset($_advagg['debug']) ? $_advagg['debug'] : FALSE; $data = str_replace(' ', '    ', nl2br(htmlentities(iconv('utf-8', 'utf-8//IGNORE', print_r($data, TRUE)), ENT_QUOTES, 'UTF-8'))); watchdog('advagg', 'Debug info: !data', array('!data' => $data), WATCHDOG_DEBUG); } } /** * Special handling for jquery update. * * @param $js * List of files in the header */ function advagg_jquery_updater(&$js) { if (!module_exists('jquery_update') || !variable_get('jquery_update_replace', TRUE) || empty($js)) { return; } // Replace jquery.js first. $new_jquery = array(jquery_update_jquery_path() => $js['core']['misc/jquery.js']); $js['core'] = array_merge($new_jquery, $js['core']); unset($js['core']['misc/jquery.js']); // Loop through each of the required replacements. $replacement_path = drupal_get_path('module', 'jquery_update') . '/replace/'; foreach (jquery_update_get_replacements() as $type => $replacements) { foreach ($replacements as $find => $replace) { // If the file to replace is loaded on this page... if (isset($js[$type][$find])) { // Create a new entry for the replacement file, and unset the original one. $replace = $replacement_path . $replace; $js[$type][$replace] = $js[$type][$find]; unset($js[$type][$find]); } } } } /** * Given a list of files; return back the aggregated filename. * * @param $files * List of files in the proposed bundle. * @param $filetype * css or js. * @param $counter * (optional) Counter value. * @param $bundle_md5 * (optional) Bundle's machine name. * @return * Aggregated filename. */ function advagg_get_filename($files, $filetype, $counter = FALSE, $bundle_md5 = '') { if (empty($files) || empty($filetype)) { return FALSE; } global $_advagg; $filenames = array(); $run_alter = FALSE; if (empty($bundle_md5)) { // Create bundle md5 $schema = advagg_get_server_schema(); $bundle_md5 = md5($schema . implode('', $files)); $run_alter = TRUE; // Record root request in db. // Get counter if there. if (empty($counter)) { $counter = db_result(db_query("SELECT counter FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5)); } // If this is a brand new bundle then insert file/bundle info into database. if ($counter === FALSE) { $counter = 0; advagg_insert_bundle_db($files, $filetype, $bundle_md5, TRUE); } // If bundle should be root and is not, then make it root. // Refresh timestamp if older then 12 hours. $row = db_fetch_array(db_query("SELECT root, timestamp FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5)); if ($row['root'] === 0 || time() - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) { db_query("UPDATE {advagg_bundles} SET root = '1', timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5); } } // Set original array. $filenames[] = array( 'filetype' => $filetype, 'files' => $files, 'counter' => $counter, 'bundle_md5' => $bundle_md5, ); // Invoke hook_advagg_filenames_alter() to give installed modules a chance to // alter filenames. One to many relationships come to mind. // Do not run alter if MD5 was given, we want to generate that file only in // this special case. if ($run_alter) { // Force counter to be looked up later. $filenames[0]['counter'] = FALSE; drupal_alter('advagg_filenames', $filenames); } // Write to DB if needed and create filenames. $output = array(); $used_md5 = array(); if (variable_get('advagg_debug', ADVAGG_DEBUG)) { $_advagg['debug']['get_filename_post_alter'][] = array( 'key' => $bundle_md5, 'filenames' => $filenames, ); } // Get all counters at once $counters = array(); foreach ($filenames as $key => $values) { if (empty($values['counter'])) { $counters[$key] = $values['bundle_md5']; } } $result = advagg_db_multi_select_in('advagg_bundles', 'bundle_md5', "'%s'", $counters, array('counter', 'bundle_md5'), 'GROUP BY bundle_md5, counter'); while ($row = db_fetch_array($result)) { $key = array_search($row['bundle_md5'], $counters); if (empty($filenames[$key]['counter']) && $filenames[$key]['counter'] !== 0) { $filenames[$key]['counter'] = intval($row['counter']); } } foreach ($filenames as $values) { // Get info from array. $filetype = $values['filetype']; $files = $values['files']; $counter = $values['counter']; $bundle_md5 = $values['bundle_md5']; // See if a JS bundle exists that already has the same files in it, just in a // different order. // if ($filetype == 'js' && $run_alter) { // advagg_find_existing_bundle($files, $bundle_md5); // } // Do not add the same bundle twice. if (isset($used_md5[$bundle_md5])) { continue; } $used_md5[$bundle_md5] = TRUE; // If this is a brand new bundle then insert file/bundle info into database. if (empty($counter) && $counter !== 0) { $counter = 0; advagg_insert_bundle_db($files, $filetype, $bundle_md5, FALSE); } // Prefix filename to prevent blocking by firewalls which reject files // starting with "ad*". $output[] = array( 'filename' => advagg_build_filename($filetype, $bundle_md5, $counter), 'files' => $files, 'bundle_md5' => $bundle_md5, ); // Refresh timestamp if older then 12 hours. $row = db_fetch_array(db_query("SELECT timestamp FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5)); if (time() - $row['timestamp'] > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) { db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5); } } return $output; } /** * Get a bundle from the cache & verify it is good. * * @param $cached_data_key * cache key for the cache_advagg_bundle_reuse table. * @param $debug_name * Name to output in the array if debugging is enabled. * @return * data from the cache. */ function advagg_cached_bundle_get($cached_data_key, $debug_name) { global $_advagg; $data = cache_get($cached_data_key, 'cache_advagg_bundle_reuse'); if (!empty($data->data)) { $data = $data->data; $bundle_contents = array(); $good = TRUE; // Debugging. if (variable_get('advagg_debug', ADVAGG_DEBUG)) { // Verify cached data is good. foreach ($data as $filename => $extra) { if (is_numeric($filename)) { continue; } // Get md5 from aggregated filename. $b_md5 = explode('/', $filename); $b_md5 = explode('_', array_pop($b_md5)); $b_md5 = $b_md5[1]; // Lookup bundle and make sure it is valid. if (!empty($b_md5)) { list($b_filetype, $b_files) = advagg_get_files_in_bundle($b_md5); $bundle_contents[$filename] = $b_files; if (empty($b_files)) { $good = FALSE; } } } $_advagg['debug'][$debug_name][] = array( 'key' => $cached_data_key, 'cache' => $data, 'bundle_contents' => $bundle_contents, ); } if ($good) { return $data; } } return FALSE; } /** * Given a list of files, see if a bundle already exists containing all of those * files. If in strict mode then the file count has to be the same. * * @param $files * List of files in the proposed bundle. * @param $bundle_md5 * Bundle's machine name. */ function advagg_find_existing_bundle(&$files, &$bundle_md5) { // Sort files for better cache hits. $temp_files = $files; sort($temp_files); $schema = advagg_get_server_schema(); $cached_data_key = 'advagg_existing_' . md5($schema . implode('', $temp_files)); // Try cache first; cache table is cache_advagg_bundle_reuse. string is debug name. $cached_data = advagg_cached_bundle_get($cached_data_key, 'advagg_find_existing_bundle'); if (!empty($cached_data)) { $files = $cached_data[0]['files']; $bundle_md5 = $cached_data[0]['bundle_md5']; return; } // Build union query. $query = 'SELECT root.bundle_md5 FROM {advagg_bundles} AS root'; $joins = array(); $wheres = array(); $args = array(); $counter = 0; foreach ($files as $filename) { // Use alpha for table aliases; numerics do not work. $key = strtr($counter, '01234567890', 'abcdefghij'); $joins[$key] = "\nINNER JOIN {advagg_bundles} AS $key USING(bundle_md5)\n"; if ($counter == 0) { $wheres[$key] = "WHERE $key.filename_md5 = '%s'"; } else { $wheres[$key] = "AND $key.filename_md5 = '%s'"; } $args[$key] = md5($filename); $counter++; } $query .= implode("\n", $joins); $query .= implode("\n", $wheres); $query .= ' GROUP BY bundle_md5'; // Find matching bundles and select first good one. $files_count = count($files); $results = db_query($query, $args); while ($new_md5 = db_result($results)) { $count = db_result(db_query("SELECT count(*) FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $new_md5)); // Make sure bundle has the same number of files if using strict matching. if (!empty($count) && $count == $files_count) { $bundle_md5 = $new_md5; $data = array(array( 'files' => $files, 'bundle_md5' => $bundle_md5, )); cache_set($cached_data_key, $data, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); return; } } } /** * Build the filename. * * @param $filetype * css or js. * @param $counter * Counter value. * @param $bundle_md5 * Bundle's machine name. */ function advagg_build_filename($filetype, $bundle_md5, $counter) { return $filetype . '_' . $bundle_md5 . '_' . $counter . '.' . $filetype; } /** * Insert info into the advagg_files and advagg_bundles database. * * @param $files * List of files in the proposed bundle. * @param $filetype * css or js. * @param $bundle_md5 * Bundle's machine name. * @param $root * Is this a root bundle. */ function advagg_insert_bundle_db($files, $filetype, $bundle_md5, $root) { $lock_name = 'advagg_insert_bundle_db' . $bundle_md5; if (!lock_acquire($lock_name)) { // If using async, wait before returning to give the other request time // to complete. if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) < 2) { lock_wait($lock_name); } return; } // Double check that the bundle doesn't exist now that we are in a lock. $bundle_exists = db_result(db_query("SELECT 1 FROM {advagg_bundles} WHERE bundle_md5 = '%s'", $bundle_md5)); if ($bundle_exists) { lock_release($lock_name); return; } foreach ($files as $order => $filename) { $filename_md5 = md5($filename); // Insert file into the advagg_files table if it doesn't exist. $checksum = db_result(db_query("SELECT checksum FROM {advagg_files} WHERE filename_md5 = '%s'", $filename_md5)); if (empty($checksum)) { $checksum = advagg_checksum($filename); db_query("INSERT INTO {advagg_files} (filename, filename_md5, checksum, filetype, filesize) VALUES ('%s', '%s', '%s', '%s', %d)", $filename, $filename_md5, $checksum, $filetype, @filesize($filename)); } // Create the entries in the advagg_bundles table. db_query("INSERT INTO {advagg_bundles} (bundle_md5, filename_md5, counter, porder, root, timestamp) VALUES ('%s', '%s', '%d', '%d', '%d', '%d')", $bundle_md5, $filename_md5, 0, $order, $root, time()); } lock_release($lock_name); } /** * Save a string to the specified destination. Verify that file size is not zero. * * @param $data * A string containing the contents of the file. * @param $dest * A string containing the destination location. * @return * Boolean indicating if the file save was successful. */ function advagg_file_saver($data, $dest, $force, $type) { // Make sure the tmp folder is ready for use $tmp = file_directory_temp(); file_check_directory($tmp, FILE_CREATE_DIRECTORY); // Create the JS file. $file_save_data = 'file_save_data'; $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR); if (!empty($custom_path)) { $file_save_data = 'advagg_file_save_data'; } if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) { return FALSE; } // Make sure filesize is not zero. advagg_clearstatcache(TRUE, $dest); if (@filesize($dest) == 0 && !empty($data)) { if (!$file_save_data($data, $dest, FILE_EXISTS_REPLACE)) { return FALSE; } advagg_clearstatcache(TRUE, $dest); if (@filesize($dest) == 0 && !empty($data)) { // Filename is bad, create a new one next time. file_delete($dest); return FALSE; } } if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION) && extension_loaded('zlib')) { $gzip_dest = $dest . '.gz'; advagg_clearstatcache(TRUE, $gzip_dest); if (!file_exists($gzip_dest) || $force) { $gzip_data = gzencode($data, 9, FORCE_GZIP); if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) { return FALSE; } // Make sure filesize is not zero. advagg_clearstatcache(TRUE, $gzip_dest); if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) { if (!$file_save_data($gzip_data, $gzip_dest, FILE_EXISTS_REPLACE)) { return FALSE; } advagg_clearstatcache(TRUE, $gzip_dest); if (@filesize($gzip_dest) == 0 && !empty($gzip_data)) { // Filename is bad, create a new one next time. file_delete($gzip_dest); return FALSE; } } } } // Make sure .htaccess file exists. advagg_htaccess_check_generate($dest); cache_set($dest, time(), 'cache_advagg', CACHE_PERMANENT); return TRUE; } /** * ***MODIFIED CORE FUNCTIONS BELOW*** * * @see file_save_data() * @see file_move() * @see file_copy() */ /** * Save a string to the specified destination. * * @see file_save_data() * * @param $data A string containing the contents of the file. * @param $dest A string containing the destination location. * @param $replace Replace behavior when the destination file already exists. * - FILE_EXISTS_REPLACE - Replace the existing file * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is unique * - FILE_EXISTS_ERROR - Do nothing and return FALSE. * * @return A string containing the resulting filename or 0 on error */ function advagg_file_save_data($data, $dest, $replace = FILE_EXISTS_RENAME) { $temp = file_directory_temp(); // On Windows, tempnam() requires an absolute path, so we use realpath(). $file = tempnam(realpath($temp), 'file'); if (!$fp = fopen($file, 'wb')) { drupal_set_message(t('The file could not be created.'), 'error'); return 0; } fwrite($fp, $data); fclose($fp); if (!advagg_file_move($file, $dest, $replace)) { return 0; } return $file; } /** * Moves a file to a new location. * * @see file_move() * * - Checks if $source and $dest are valid and readable/writable. * - Performs a file move if $source is not equal to $dest. * - If file already exists in $dest either the call will error out, replace the * file or rename the file based on the $replace parameter. * * @param $source * Either a string specifying the file location of the original file or an * object containing a 'filepath' property. This parameter is passed by * reference and will contain the resulting destination filename in case of * success. * @param $dest * A string containing the directory $source should be copied to. If this * value is omitted, Drupal's 'files' directory will be used. * @param $replace * Replace behavior when the destination file already exists. * - FILE_EXISTS_REPLACE: Replace the existing file. * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR: Do nothing and return FALSE. * @return * TRUE for success, FALSE for failure. */ function advagg_file_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { $path_original = is_object($source) ? $source->filepath : $source; if (advagg_file_copy($source, $dest, $replace)) { $path_current = is_object($source) ? $source->filepath : $source; if ($path_original == $path_current || file_delete($path_original)) { return 1; } drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => $path_original)), 'error'); } return 0; } /** * Copies a file to a new location. * * @see file_copy() * * This is a powerful function that in many ways performs like an advanced * version of copy(). * - Checks if $source and $dest are valid and readable/writable. * - Performs a file copy if $source is not equal to $dest. * - If file already exists in $dest either the call will error out, replace the * file or rename the file based on the $replace parameter. * * @param $source * Either a string specifying the file location of the original file or an * object containing a 'filepath' property. This parameter is passed by * reference and will contain the resulting destination filename in case of * success. * @param $dest * A string containing the directory $source should be copied to. If this * value is omitted, Drupal's 'files' directory will be used. * @param $replace * Replace behavior when the destination file already exists. * - FILE_EXISTS_REPLACE: Replace the existing file. * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR: Do nothing and return FALSE. * @return * TRUE for success, FALSE for failure. */ function advagg_file_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) { $directory = dirname($dest); // Process a file upload object. if (is_object($source)) { $file = $source; $source = $file->filepath; if (!$basename) { $basename = $file->filename; } } $source = realpath($source); advagg_clearstatcache(TRUE, $source); if (!file_exists($source)) { drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $source)), 'error'); return 0; } // If the destination file is not specified then use the filename of the source file. $basename = basename($dest); $basename = $basename ? $basename : basename($source); $dest = $directory . '/' . $basename; // Make sure source and destination filenames are not the same, makes no sense // to copy it if they are. In fact copying the file will most likely result in // a 0 byte file. Which is bad. Real bad. if ($source != realpath($dest)) { if (!$dest = file_destination($dest, $replace)) { drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => $source)), 'error'); return FALSE; } // Perform the replace operation. Since there could be multiple processes // writing to the same file, the best option is to create a temporary file in // the same directory and then rename it to the destination. A temporary file // is needed if the directory is mounted on a separate machine; thus ensuring // the rename command stays local. $result = FALSE; if ($replace == FILE_EXISTS_REPLACE) { // Get a temporary filename in the destination directory. $temporary_file = tempnam(dirname($dest), 'file'); // Place contents in the temporary file. if ($temporary_file && @copy($source, $temporary_file)) { if (!$result = @rename($temporary_file, $dest)) { // Unlink and try again for windows. Rename on windows does not replace // the file if it already exists. @unlink($dest); $result = @rename($temporary_file, $dest); } // Remove temporary_file if rename failed. if (!$result) { @unlink($temporary_file); } } } // Perform the copy operation. else { $result = @copy($source, $dest); } if ($result === FALSE) { drupal_set_message(t('The selected file %file could not be copied. ' . $dest, array('%file' => $source)), 'error'); return 0; } // Give everyone read access so that FTP'd users or // non-webserver users can see/read these files, // and give group write permissions so group members // can alter files uploaded by the webserver. @chmod($dest, 0664); } if (isset($file) && is_object($file)) { $file->filename = $basename; $file->filepath = $dest; $source = $file; } else { $source = $dest; } return 1; // Everything went ok. } /** * Generate a checksum for a given filename. * * @param $filename * filename * @return * Checksum value. */ function advagg_checksum($filename) { advagg_clearstatcache(TRUE, $filename); if (file_exists($filename)) { $mode = variable_get('advagg_checksum_mode', ADVAGG_CHECKSUM_MODE); if ($mode == 'mtime') { $checksum = @filemtime($filename); if ($checksum === FALSE) { touch($filename); advagg_clearstatcache(TRUE, $filename); $checksum = @filemtime($filename); // Use md5 as a last option. if ($checksum === FALSE) { $checksum = md5(file_get_contents($filename)); } } } elseif ($mode == 'md5') { $checksum = md5(file_get_contents($filename)); } } else { $checksum = '-1'; } return $checksum; } /** * See if this bundle has been built. * * @param $filepath * filename * @return * Boolean indicating if the bundle already exists. */ function advagg_bundle_built($filepath) { // Don't use the cache if not selected. if (!variable_get('advagg_bundle_built_mode', ADVAGG_BUNDLE_BUILT_MODE)) { advagg_clearstatcache(TRUE, $filepath); return file_exists($filepath); } $data = advagg_get_bundle_from_filename(basename($filepath)); if (is_array($data)) { list($type, $md5, $counter) = $data; } else { return FALSE; } $data = cache_get($filepath, 'cache_advagg'); if (isset($data->data)) { // Refresh timestamp if older then 12 hours. if (time() - $data->data > variable_get('advagg_file_last_used_interval', ADVAGG_FILE_LAST_USED_INTERVAL)) { cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT); db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5); } return TRUE; } // If not in cache check disk. advagg_clearstatcache(TRUE, $filepath); if (file_exists($filepath)) { if (@filesize($filepath) == 0) { return FALSE; } } else { return FALSE; } // File existed on disk; place in cache. cache_set($filepath, time(), 'cache_advagg', CACHE_PERMANENT); db_query("UPDATE {advagg_bundles} SET timestamp = %d WHERE bundle_md5 = '%s'", time(), $md5); return TRUE; } function advagg_get_bundle_from_filename($filename) { // Verify requested filename has the correct pattern. if (!preg_match('/^(j|cs)s_[0-9a-f]{32}_\d+\.(j|cs)s$/', $filename)) { return t('Wrong Pattern.'); } // Get type $type = substr($filename, 0, strpos($filename, '_')); // Get extension $ext = substr($filename, strpos($filename, '.', 37) + 1); // Make sure extension is the same as the type. if ($ext != $type) { return t('Type does not match extension.'); } // Extract info from wanted filename. if ($type == 'css') { $md5 = substr($filename, 4, 32); $counter = substr($filename, 37, strpos($filename, '.', 38) -37); } elseif ($type == 'js') { $md5 = substr($filename, 3, 32); $counter = substr($filename, 36, strpos($filename, '.', 37) -36); } else { return t('Wrong file type.'); } return array($type, $md5, $counter); } /** * Implementation of hook_flush_caches(). */ function advagg_flush_caches() { // Try to allocate enough time to flush the cache if (function_exists('set_time_limit')) { @set_time_limit(variable_get('advagg_set_time_limit', ADVAGG_SET_TIME_LIMIT)); } global $_advagg; // Only one advagg cache flusher can run at a time. if (!lock_acquire('advagg_flush_caches')) { return; } // Only run code below if the advagg db tables exist. if (!db_table_exists('advagg_files')) { return array('cache_advagg_bundle_reuse'); } // Find files that have changed. $needs_refreshing = array(); $results = db_query("SELECT * FROM {advagg_files}"); while ($row = db_fetch_array($results)) { $checksum = advagg_checksum($row['filename']); // Let other modules see if the bundles needs to be rebuilt. // hook_advagg_files_table // Return TRUE in order to increment the counter. $hook_results = module_invoke_all('advagg_files_table', $row, $checksum); // Check each return value; see if an update is needed. $update = FALSE; if (!empty($hook_results)) { foreach ($hook_results as $update) { if ($update === TRUE) { break; } } } // Increment the counter if needed and mark file for bundle refreshment. if ($checksum != $row['checksum'] || $update == TRUE) { $needs_refreshing[$row['filename_md5']] = $row['filename']; // Update checksum; increment counter. db_query("UPDATE {advagg_files} SET checksum = '%s', counter = counter + 1 WHERE filename_md5 = '%s'", $checksum, $row['filename_md5']); } } // Get the bundles. $bundles = array(); foreach ($needs_refreshing as $filename_md5 => $filename) { $results = db_query("SELECT bundle_md5 FROM {advagg_bundles} WHERE filename_md5 = '%s'", $filename_md5); while ($row = db_fetch_array($results)) { $bundles[$row['bundle_md5']] = $row['bundle_md5']; } } foreach ($bundles as $bundle_md5) { // Increment Counter db_query("UPDATE {advagg_bundles} SET counter = counter + 1, timestamp = %d WHERE bundle_md5 = '%s'", time(), $bundle_md5); if (variable_get('advagg_rebuild_on_flush', ADVAGG_REBUILD_ON_FLUSH)) { // Rebuild bundles on shutdown in the background. This is needed so that // the cache_advagg_bundle_reuse table has been cleared. register_shutdown_function('advagg_rebuild_bundle', $bundle_md5, '', TRUE); } } $_advagg['bundles'] = $bundles; $_advagg['files'] = $needs_refreshing; // Garbage collection list($css_path, $js_path) = advagg_get_root_files_dir(); file_scan_directory($css_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE); file_scan_directory($js_path, '.*', array('.', '..', 'CVS'), 'advagg_delete_file_if_stale', TRUE); lock_release('advagg_flush_caches'); return array('cache_advagg_bundle_reuse'); } /** * Rebuild a bundle. * * @param $bundle_md5 * Bundle's machine name. * @param $counter * Counter value. * @param $force * Rebuild even if file already exists. */ function advagg_rebuild_bundle($bundle_md5, $counter = '', $force = FALSE) { global $conf, $_advagg; list($filetype, $files) = advagg_get_files_in_bundle($bundle_md5); $conf['advagg_async_generation'] = FALSE; $good = advagg_css_js_file_builder($filetype, $files, '', $counter, $force, $bundle_md5); if (!$good) { watchdog('advagg', 'This bundle could not be generated correctly. Bundle MD5: %md5', array('%md5' => $bundle_md5)); } else { $_advagg['rebuilt'][] = $bundle_md5; } return $good; } /** * Get list of files and the filetype given a bundle md5. * * @param $bundle_md5 * Bundle's machine name. * @return * array ($filetype, $files) */ function advagg_get_files_in_bundle($bundle_md5) { $files = array(); $filetype = NULL; $results = db_query("SELECT filename, filetype FROM {advagg_files} AS af INNER JOIN {advagg_bundles} AS ab USING ( filename_md5 ) WHERE bundle_md5 = '%s' ORDER BY porder ASC", $bundle_md5); while ($row = db_fetch_array($results)) { $files[] = $row['filename']; $filetype = $row['filetype']; } return array($filetype, $files); } /** * Callback to delete files modified more than a set time ago. * * @param $filename * name of a file to check how old it is. */ function advagg_delete_file_if_stale($filename) { // Do not process .gz files if (strpos($filename, '.gz') !== FALSE) { return; } $now = time(); $file_last_mod = variable_get('advagg_stale_file_threshold', ADVAGG_STALE_FILE_THRESHOLD); $file_last_used = variable_get('advagg_stale_file_last_used_threshold', ADVAGG_STALE_FILE_LAST_USED_THRESHOLD); // Default mtime stale file threshold is 6 days. advagg_clearstatcache(TRUE, $filename); if ($now - filemtime($filename) <= $file_last_mod) { return; } // Check to see if this file is still in use. $data = cache_get($filename, 'cache_advagg'); if (!empty($data->data)) { advagg_clearstatcache(TRUE, $filename); $file_last_a = @fileatime($filename); $file_last_agz = @fileatime($filename . '.gz'); $file_last_a = max($file_last_a, $file_last_agz); if ($now - $data->data > $file_last_used && $now - $file_last_a > $file_last_used) { // Delete file if it hasn't been used in the last 3 days. file_delete($filename); file_delete($filename . '.gz'); } else { // Touch file so we don't check again for another 6 days. touch($filename); } } else { // Delete file if it is not in the cache. file_delete($filename); file_delete($filename . '.gz'); } } /** * Get data about a file. * * @param $filename_md5 * md5 of filename. * @return * data array from database. */ function advagg_get_file_data($filename_md5) { $data = cache_get($filename_md5, 'cache_advagg_files_data'); if (empty($data->data)) { return FALSE; } return $data->data; } /** * Set data about a file. * * @param $filename_md5 * md5 of filename. * @param $data * data to store. */ function advagg_set_file_data($filename_md5, $data) { cache_set($filename_md5, $data, 'cache_advagg_files_data', CACHE_PERMANENT); } /** * Given path output uri to that file * * @param $filename_md5 * md5 of filename. * @param $data * data to store. */ function advagg_build_uri($path) { static $hook_file_url_alter = array(); // If the current path is an absolute path, return immediately. $fragments = parse_url($path); if (isset($fragments['host'])) { return $path; } $original_path = $path; // CDN Support. if (module_exists('cdn')) { $status = variable_get(CDN_STATUS_VARIABLE, CDN_DISABLED); if ($status == CDN_ENABLED || ($status == CDN_TESTING && user_access(CDN_PERM_ACCESS_TESTING))) { // Alter URL when the file_create_url() patch is not there. if (variable_get(CDN_THEME_LAYER_FALLBACK_VARIABLE, FALSE)) { cdn_file_url_alter($path); } // Use the patched version of file_create_url(). else { $path = advagg_file_create_url($path); } // Return here if the path was changed above. if (strcmp($original_path, $path) != 0) { return $path; } } } // Other modules besides CDN might want to use hook_file_url_alter. if (empty($hook_file_url_alter)) { $hook_file_url_alter = module_implements('file_url_alter'); } if (!empty($hook_file_url_alter)) { $path = advagg_file_create_url($path); // Return here if the path was changed above. if (strcmp($original_path, $path) != 0) { return $path; } } // If nothing was altered then use the drupal default. return base_path() . $path; } /** * Create the download path to a file. * * There are two kinds of local files: * - "created files", i.e. those in the files directory (which is stored in * the file_directory_path variable and can be retrieved using * file_directory_path()). These are files that have either been uploaded by * users or were generated automatically (for example through CSS * aggregation). * - "shipped files", i.e. those outside of the files directory, which ship as * part of Drupal core or contributed modules or themes. * * @param $path * A string containing the Drupal path (i.e. path relative to the Drupal * root directory) of the file to generate the URL for. * @return * A string containing a URL that can be used to download the file. */ function advagg_file_create_url($path) { // Clean up Windows paths. $old_path = $path = str_replace('\\', '/', $path); drupal_alter('file_url', $path); // If any module has altered the path, then return the alteration. if ($path != $old_path) { return $path; } // Otherwise serve the file from Drupal's web server. This point will only // be reached when either no custom_file_url_rewrite() function has been // defined, or when that function returned FALSE, thereby indicating that it // cannot (or doesn't wish to) rewrite the URL. This is typically because // the file doesn't match some conditions to be served from a CDN or static // file server, or because the file has not yet been synced to the CDN or // static file server. // Shipped files. if (strpos($path, file_directory_path() . '/') !== 0) { return base_path() . $path; } // Created files. else { switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) { case FILE_DOWNLOADS_PUBLIC: return $GLOBALS['base_url'] . '/' . $path; case FILE_DOWNLOADS_PRIVATE: // Strip file_directory_path from $path. Private downloads' URLs are // rewritten to be served relatively to system/files (which is a menu // callback that streams the file) instead of relatively to the file // directory path. $path = file_directory_strip($path); return url('system/files/' . $path, array('absolute' => TRUE)); } } } /** * ***MODIFIED CORE FUNCTIONS BELOW*** * * @see drupal_get_css() * @see drupal_build_css_cache() * @see drupal_get_js() * @see drupal_build_js_cache() */ /** * Returns an array of values needed for aggregation * * @param $noagg * (optional) Bool indicating that aggregation should be disabled if TRUE. * @param $type * (optional) String. js or css. * @return * array of values to be imported via list() function. */ function advagg_process_css_js_prep($noagg = FALSE, $type = NULL) { global $conf; $preprocess = (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'); // URL bypass. if ($noagg || (isset($_GET['advagg']) && $_GET['advagg'] == 0 && user_access('bypass advanced aggregation'))) { $preprocess = FALSE; $conf['advagg_use_full_cache'] = FALSE; } // Cookie bypass. $cookie_name = 'AdvAggDisabled'; $key = md5(drupal_get_private_key()); if (!empty($_COOKIE[$cookie_name]) && $_COOKIE[$cookie_name] == $key) { $preprocess = FALSE; $conf['advagg_use_full_cache'] = FALSE; } $public_downloads = (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC); if (!$public_downloads) { $custom_path = variable_get('advagg_custom_files_dir', ADVAGG_CUSTOM_FILES_DIR); if (!empty($custom_path)) { $public_downloads = TRUE; } } if ($preprocess) { if ($type == 'js' && !variable_get('advagg_preprocess_js', ADVAGG_PREPROCESS_JS)) { $preprocess = FALSE; } if ($type == 'css' && !variable_get('advagg_preprocess_css', ADVAGG_PREPROCESS_CSS)) { $preprocess = FALSE; } } // A dummy query-string is added to filenames, to gain control over // browser-caching. The string changes on every update or full cache // flush, forcing browsers to load a new copy of the files, as the // URL changed. $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1); return array($preprocess, $public_downloads, $query_string); } /** * Returns a themed representation of all stylesheets that should be attached to * the page. * * @see drupal_get_css() * * 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/garland/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 $css * (optional) An array of CSS files. If no array is provided, the default * stylesheets array is used instead. * @param $noagg * (optional) Bool indicating that aggregation should be disabled if TRUE. * @return * A string of XHTML CSS tags. */ function advagg_process_css($css = NULL, $noagg = FALSE) { global $conf; $original_css = $css; if (!isset($css)) { $css = drupal_add_css(); } if (empty($css)) { return FALSE; } // Get useful info. list($preprocess_css, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg, 'css'); // Invoke hook_advagg_css_pre_alter() to give installed modules a chance to // modify the data in the $javascript array if necessary. drupal_alter('advagg_css_pre', $css, $preprocess_css, $public_downloads); // Set variables. $external_no_preprocess = array(); $module_no_preprocess = array(); $output_no_preprocess = array(); $output_preprocess = array(); $theme_no_preprocess = array(); $inline_no_preprocess = array(); $files_included = array(); $files_aggregates_included = array(); $inline_included = array(); // Process input. foreach ($css as $media => $types) { // Setup some variables $files_included[$media] = array(); $files_aggregates_included[$media] = array(); $inline_included[$media] = array(); // If CSS preprocessing is off, we still need to output the styles. // Additionally, go through any remaining styles if CSS preprocessing is on // and output the non-cached ones. foreach ($types as $type => $files) { if ($type == 'module') { // Setup theme overrides for module styles. $theme_styles = array(); foreach (array_keys($css[$media]['theme']) as $theme_style) { $theme_styles[] = basename($theme_style); } } foreach ($types[$type] as $file => $preprocess) { // If the theme supplies its own style using the name of the module // style, skip its inclusion. This includes any RTL styles associated // with its main LTR counterpart. if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) { // Unset the file to prevent its inclusion when CSS aggregation is enabled. unset($types[$type][$file]); continue; } // If a CSS file is not to be preprocessed and it's an inline CSS blob // it needs to *always* appear at the *very bottom*. if ($type == 'inline') { if (is_array($preprocess)) { foreach ($preprocess as $suffix => $blob) { $blob = advagg_drupal_load_stylesheet_content($blob, $preprocess); // Invoke hook_advagg_css_inline_alter() to give installed modules // a chance to modify the contents of $blob if necessary. drupal_alter('advagg_css_inline', $blob); $inline_no_preprocess[] = array( 'media' => $media, 'data' => $blob, 'prefix' => $file, 'suffix' => $suffix, ); $inline_included[$media][] = $blob; } } // Unset to prevent its inclusion. unset($types[$type][$file]); continue; } $prefix = ''; $suffix = ''; // Invoke hook_advagg_css_extra_alter() to // give installed modules a chance to modify the prefix or suffix for a // given filename. $values = array($file, NULL, $prefix, $suffix); drupal_alter('advagg_css_extra', $values); list($file, $null, $prefix, $suffix) = $values; if ($type == 'inline') { $file = advagg_drupal_load_stylesheet_content($file, $preprocess); // Invoke hook_advagg_css_inline_alter() to give installed modules a // chance to modify the contents of $file if necessary. drupal_alter('advagg_css_inline', $file); $inline_no_preprocess[] = array( 'media' => $media, 'data' => $file, 'prefix' => $prefix, 'suffix' => $suffix, ); $inline_included[$media][] = $file; // Unset to prevent its inclusion. unset($types[$type][$file]); continue; } // If a CSS file is not to be preprocessed and it's an external // CSS file, it needs to *always* appear at the *very top*, // regardless of whether preprocessing is on or off. if ($type == 'external') { $external_no_preprocess[] = array( 'media' => $media, 'href' => $file, 'prefix' => $prefix, 'suffix' => $suffix, ); $files_included[$media][$file] = TRUE; // Unset the file to prevent its inclusion. unset($types[$type][$file]); continue; } // Only include the stylesheet if it exists. if (advagg_file_exists($file)) { if (!$preprocess || !($public_downloads && $preprocess_css)) { // Create URI for file. $file_uri = advagg_build_uri($file) . $query_string; $files_included[$media][$file] = $preprocess; // If a CSS file is not to be preprocessed and it's a module CSS // file, it needs to *always* appear at the *top*, regardless of // whether preprocessing is on or off. if (!$preprocess && $type == 'module') { $module_no_preprocess[] = array( 'media' => $media, 'href' => $file_uri, 'prefix' => $prefix, 'suffix' => $suffix, ); } // If a CSS file is not to be preprocessed and it's a theme CSS // file, it needs to *always* appear at the *bottom*, regardless of // whether preprocessing is on or off. elseif (!$preprocess && $type == 'theme') { $theme_no_preprocess[] = array( 'media' => $media, 'href' => $file_uri, 'prefix' => $prefix, 'suffix' => $suffix, ); } else { $output_no_preprocess[] = array( 'media' => $media, 'href' => $file_uri, 'prefix' => $prefix, 'suffix' => $suffix, ); } } } } } if ($public_downloads && $preprocess_css) { $files_aggregates_included[$media] = $files_included[$media]; $files = array(); foreach ($types as $type) { foreach ($type as $file => $cache) { if ($cache && advagg_file_exists($file)) { $files[] = $file; $files_included[$media][$file] = TRUE; unset($files_aggregates_included[$file]); } } } if (!empty($files)) { $preprocess_files = advagg_css_js_file_builder('css', $files, $query_string); if (!empty($preprocess_files)) { $good = TRUE; foreach ($preprocess_files as $preprocess_file => $extra) { // Empty aggregate, skip if (empty($preprocess_file)) { continue; } if ($extra !== FALSE && is_array($extra)) { $prefix = $extra['prefix']; $suffix = $extra['suffix']; $output_preprocess[] = array( 'media' => $media, 'href' => advagg_build_uri($preprocess_file), 'prefix' => $prefix, 'suffix' => $suffix, ); $files_aggregates_included[$media][$preprocess_file] = $extra; } else { $good = FALSE; break; } } } if (empty($good)) { // Redo with aggregation turned off and return the new value. watchdog('advagg', 'CSS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR); $data = advagg_process_css($original_css, TRUE); return $data; } } } } // Default function called: advagg_unlimited_css_builder $function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION); return $function($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included); } /** * Logic to figure out what kind of css tags to use. * * @param $external_no_preprocess * array of css files ($media, $href, $prefix, $suffix) * @param $module_no_preprocess * array of css files ($media, $href, $prefix, $suffix) * @param $output_no_preprocess * array of css files ($media, $href, $prefix, $suffix) * @param $output_preprocess * array of css files ($media, $href, $prefix, $suffix) * @param $theme_no_preprocess * array of css files ($media, $href, $prefix, $suffix) * @param $inline_no_preprocess * array of css data to inline ($media, $data) * @param $inline_included * array of inline css included. $a[$media][] = $datablob; * @param $files_included * array of css files included. $a[$media][] = $filename * @param $files_aggregates_included * array of css files & aggregates included. $a[$media][] = $filename * @return * html for loading the css. html for the head. */ function advagg_unlimited_css_builder($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $files_included, $files_aggregates_included, $inline_included) { global $user; $styles = ''; $files = array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess); // Select method for css html output if (count($files) < variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD)) { advagg_unlimited_css_traditional($files, $styles); } elseif (variable_get('advagg_css_logged_in_ie_detect', ADVAGG_CSS_LOGGED_IN_IE_DETECT) && $user->uid != 0) { // Detect IE browsers here $is_ie = FALSE; if (isset($_SERVER['HTTP_USER_AGENT'])) { // Strings for testing found via // http://chrisschuld.com/projects/browser-php-detecting-a-users-browser-from-php/ // Test for v1 - v1.5 IE // Test for versions > 1.5 // Test for Pocket IE if ( stristr($_SERVER['HTTP_USER_AGENT'], 'microsoft internet explorer') || stristr($_SERVER['HTTP_USER_AGENT'], 'msie') || stristr($_SERVER['HTTP_USER_AGENT'], 'mspie') ) { $is_ie = TRUE; } } // Play Safe and treat as IE if user agent is not set else { $is_ie = TRUE; } if ($is_ie) { advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles); advagg_unlimited_css_import($output_preprocess, $styles); advagg_unlimited_css_import($theme_no_preprocess, $styles); advagg_unlimited_css_traditional($inline_no_preprocess, $styles); } else { advagg_unlimited_css_traditional($files, $styles); } } else { advagg_unlimited_css_import(array_merge($external_no_preprocess, $module_no_preprocess, $output_no_preprocess), $styles); advagg_unlimited_css_import($output_preprocess, $styles); advagg_unlimited_css_import($theme_no_preprocess, $styles); advagg_unlimited_css_traditional($inline_no_preprocess, $styles); } return $styles; } /** * Use link tags for CSS * * @param $files * array of css files ($media, $href, $prefix, $suffix) * @param &$styles * html string */ function advagg_unlimited_css_traditional($files, &$styles) { $last_prefix = ''; $last_suffix = ''; foreach ($files as $css_file) { $media = $css_file['media']; $prefix = empty($css_file['prefix']) ? '' : $css_file['prefix'] . "\n"; $suffix = empty($css_file['suffix']) ? '' : $css_file['suffix']; // Group prefixes and suffixes. if (isset($css_file['href'])) { $href = $css_file['href']; if ($prefix != $last_prefix) { $styles .= $last_suffix . "\n" . $prefix . '' . "\n"; } else { $styles .= '' . "\n"; } } else { $data = $css_file['data']; if ($prefix != $last_prefix) { $styles .= $last_suffix . "\n" . $prefix . '' . "\n"; } else { $styles .= '' . "\n"; } } $last_prefix = $prefix; $last_suffix = $suffix; } $styles .= empty($last_suffix) ? '' : $last_suffix . "\n"; } /** * Use import tags for CSS * * @param $files * array of css files ($media, $href) * @param &$styles * html string */ function advagg_unlimited_css_import($files, &$styles) { $counter = 0; $media = NULL; $import = ''; foreach ($files as $css_file) { $media_new = $css_file['media']; $href = $css_file['href']; if ($media_new != $media || $counter > variable_get('advagg_css_count_threshold', ADVAGG_CSS_COUNT_THRESHOLD)) { if ($media && !empty($import)) { $styles .= "\n" . ''; $import = ''; } $counter = 0; $media = $media_new; } $import .= '@import "' . $href . '";' . "\n"; $counter++; } if ($media && !empty($import)) { $styles .= "\n" . ''; } } /** * Returns a themed presentation of all JavaScript code for the current page. * * @see drupal_get_js() * * References to JavaScript files are placed in a certain order: first, all * 'core' files, then all 'module' and finally all 'theme' JavaScript files * are added to the page. Then, all settings are output, followed by 'inline' * JavaScript code. If running update.php, all preprocessing is disabled. * * @param $js_code * An array with all JavaScript code. Key it the region * @param $noagg * (optional) Bool indicating that aggregation should be disabled if TRUE. * @return * All JavaScript code segments and includes for the scope as HTML tags. */ function advagg_process_js($master_set, $noagg = FALSE) { global $conf; if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) { locale_update_js_files(); } // Get useful info. list($preprocess_js, $public_downloads, $query_string) = advagg_process_css_js_prep($noagg, 'js'); $advagg_json_encode_function = variable_get('advagg_json_encode_function', 'advagg_drupal_to_js'); $output = array(); foreach ($master_set as $scope => $javascript) { if ($scope != 'header' && $scope != 'footer' && empty($javascript)) { continue; } // Invoke hook_advagg_js_pre_alter() to give installed modules a chance to // modify the data in the $javascript array if necessary. drupal_alter('advagg_js_pre', $javascript, $preprocess_js, $public_downloads, $scope); $master_set[$scope] = $javascript; } // Invoke hook_advagg_js_header_footer_alter() to give installed modules a chance to // modify the data in the header and footer JS if necessary. drupal_alter('advagg_js_header_footer', $master_set, $preprocess_js, $public_downloads); foreach ($master_set as $scope => $javascript) { if (empty($javascript)) { continue; } // Set variables. $setting_no_preprocess = array(); $inline_no_preprocess = array(); $external_no_preprocess = array(); $output_no_preprocess = array( 'core' => array(), 'module' => array(), 'theme' => array(), ); $output_preprocess = array(); $preprocess_list = array(); $js_settings_array = array(); $inline_included = array(); $files_included = array(); $files_aggregates_included = array(); // Process input. foreach ($javascript as $type => $data) { if (empty($data)) { continue; } // Add the prefix and suffix to the info array if not there. if ($type != 'setting') { foreach ($data as &$info) { $info['prefix'] = isset($info['prefix']) ? $info['prefix'] : ''; $info['suffix'] = isset($info['suffix']) ? $info['suffix'] : ''; } // $info needs to be unset, otherwise foreach loops below will break. unset($info); } switch ($type) { case 'setting': $data = call_user_func_array('array_merge_recursive', $data); $js_settings_array[] = $data; $js_settings = $advagg_json_encode_function($data); $js_settings = preg_replace(array('/"DRUPAL_JS_RAW\:/', '/\:DRUPAL_JS_RAW"/'), array('', ''), $js_settings); $setting_no_preprocess[] = 'jQuery.extend(Drupal.settings, ' . $js_settings . ");"; break; case 'inline': foreach ($data as $info) { // Invoke hook_advagg_js_inline_alter() to give installed modules a // chance to modify the contents of $info['code'] if necessary. drupal_alter('advagg_js_inline', $info['code']); $inline_no_preprocess[] = array($info['code'], $info['defer'], $info['prefix'], $info['suffix']); $inline_included[] = $info['code']; } break; case 'external': foreach ($data as $path => $info) { $external_no_preprocess[] = array($path, $info['defer'], $info['prefix'], $info['suffix']); $files_included[$path] = TRUE; } break; default: // If JS preprocessing is off, we still need to output the scripts. // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones. foreach ($data as $path => $info) { if (!$info['preprocess'] || !$public_downloads || !$preprocess_js) { $output_no_preprocess[$type][] = array(advagg_build_uri($path) . ($info['cache'] ? $query_string : '?' . time()), $info['defer'], $info['prefix'], $info['suffix']); $files_included[$path] = $info['preprocess']; } else { $preprocess_list[$path] = $info; } } break; } } // Aggregate any remaining JS files that haven't already been output. if ($public_downloads && $preprocess_js && count($preprocess_list) > 0) { $files_aggregates_included = $files_included; $files = array(); foreach ($preprocess_list as $path => $info) { if ($info['preprocess']) { $files[] = $path; $files_included[$path] = TRUE; } } if (!empty($files)) { $preprocess_files = advagg_css_js_file_builder('js', $files, $query_string); if (!empty($preprocess_files)) { $good = TRUE; foreach ($preprocess_files as $preprocess_file => $extra) { // Empty aggregate, skip if (empty($preprocess_file)) { continue; } if ($extra !== FALSE && is_array($extra)) { $prefix = $extra['prefix']; $suffix = $extra['suffix']; $output_preprocess[] = array(advagg_build_uri($preprocess_file), $prefix, $suffix); $files_aggregates_included[$preprocess_file] = $extra; } else { $good = FALSE; break; } } } if (empty($good)) { // Redo with aggregation turned off and return the new value. watchdog('advagg', 'JS aggregation failed. %filename could not be saved correctly.', array('%filename' => $preprocess_file), WATCHDOG_ERROR); $data = advagg_process_js($master_set, TRUE); return $data; } } } // Default function called: advagg_js_builder $function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION); $output[$scope] = $function($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included); } return $output; } /** * Build and theme JS output for header. * * @param $external_no_preprocess * array(array($src, $defer, $prefix, $suffix)) * @param $output_preprocess * array(array($src, $prefix, $suffix)) * @param $output_no_preprocess * array(array(array($src, $defer, $prefix, $suffix))) * @param $setting_no_preprocess * array(array($code)) * @param $inline_no_preprocess * array(array($code, $defer, $prefix, $suffix)) * @param $scope * header or footer. * @param $js_settings_array * array of settings used. * @param $inline_included * array of inline scripts used. * @param $files_included * array of files used. * @param $files_aggregates_included * array of files and aggregates used. * @return * String of themed JavaScript. */ function advagg_js_builder($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) { $output = ''; // For inline Javascript to validate as XHTML, all Javascript containing // XHTML needs to be wrapped in CDATA. To make that backwards compatible // with HTML 4, we need to comment out the CDATA-tag. $embed_prefix = "\n\n"; // Keep the order of JS files consistent as some are preprocessed and others are not. // Make sure any inline or JS setting variables appear last after libraries have loaded. if (!empty($external_no_preprocess)) { foreach ($external_no_preprocess as $values) { list($src, $defer, $prefix, $suffix) = $values; $output .= $prefix . '' . $suffix . "\n"; } } if (!empty($output_preprocess)) { foreach ($output_preprocess as $values) { list($src, $prefix, $suffix) = $values; $output .= $prefix . '' . $suffix . "\n"; } } foreach ($output_no_preprocess as $type => $list) { if (!empty($list)) { foreach ($list as $values) { list($src, $defer, $prefix, $suffix) = $values; $output .= $prefix . '' . $suffix . "\n"; } } } if (!empty($setting_no_preprocess)) { foreach ($setting_no_preprocess as $code) { $output .= '\n"; } } if (!empty($inline_no_preprocess)) { foreach ($inline_no_preprocess as $values) { list($code, $defer, $prefix, $suffix) = $values; $output .= $prefix . '' . $suffix . "\n"; } } return $output; } /** * Always return TRUE, used for array_map in advagg_css_js_file_builder(). */ function advagg_return_true() { return TRUE; } /** * Disable the page cache if the aggregate is not in the bundle. */ function advagg_disable_page_cache() { global $conf; $conf['advagg_use_full_cache'] = FALSE; if (variable_get('advagg_page_cache_mode', ADVAGG_PAGE_CACHE_MODE)) { $conf['cache'] = CACHE_DISABLED; // Invoke hook_advagg_disable_page_cache(). Allows 3rd party page cache // plugins like boost or varnish to not cache this page. module_invoke_all('advagg_disable_page_cache'); } } /** * Aggregate CSS/JS files, putting them in the files directory. * * @see drupal_build_js_cache() * @see drupal_build_css_cache() * * @param $type * js or css * @param $files * An array of JS files to aggregate and compress into one file. * @param $query_string * (optional) Query string to add on to the file if bundle isn't ready. * @param $counter * (optional) Counter value. * @param $force * (optional) Rebuild even if file already exists. * @param $md5 * (optional) Bundle's machine name. * @return * array with the filepath as the key and prefix and suffix in another array. */ function advagg_css_js_file_builder($type, $files, $query_string = '', $counter = FALSE, $force = FALSE, $md5 = '') { global $_advagg, $base_path; $data = ''; // Try cache first. When ever the counter changes this cache gets reset. $schema = advagg_get_server_schema(); $cached_data_key = 'advagg_file_builder_' . md5($schema . implode('', array_filter(array_unique($files)))); if (!$force) { // Try cache first; cache table is cache_advagg_bundle_reuse. $cached_data = advagg_cached_bundle_get($cached_data_key, 'file_builder_cache_object'); if (!empty($cached_data)) { foreach ($cached_data as $filepath => $values) { // Ping cache. advagg_bundle_built($filepath); } return $cached_data; } } list($css_path, $js_path) = advagg_get_root_files_dir(); if ($type == 'js') { $file_type_path = $js_path; } if ($type == 'css') { $file_type_path = $css_path; } // Send $files, get filename back $filenames = advagg_get_filename($files, $type, $counter, $md5); if (empty($filenames)) { return FALSE; } // Debugging. if (variable_get('advagg_debug', ADVAGG_DEBUG)) { $_advagg['debug']['file_builder_get_filenames'][] = array( 'key' => $cached_data_key, 'filenames' => $filenames, ); } $output = array(); $locks = array(); $cacheable = TRUE; $files_used = array(); foreach ($filenames as $info) { $filename = $info['filename']; $files = $info['files']; $bundle_md5 = $info['bundle_md5']; $prefix = ''; $suffix = ''; $filepath = $file_type_path . '/' . $filename; // Invoke hook_advagg_js_extra_alter() or hook_advagg_css_extra_alter to // give installed modules a chance to modify the prefix or suffix for a // given filename. $values = array($filename, $bundle_md5, $prefix, $suffix); drupal_alter('advagg_' . $type . '_extra', $values); list($filename, $bundle_md5, $prefix, $suffix) = $values; // Check that the file exists & filesize is not zero $built = advagg_bundle_built($filepath); if (!$built || $force) { // Generate on request? if (variable_get('advagg_async_generation', ADVAGG_ASYNC_GENERATION) && !$force && empty($_GET['generator'])) { // Build request. $redirect_counter = isset($_GET['redirect_counter']) ? intval($_GET['redirect_counter']) : 0; $url = _advagg_build_url($filepath . '?generator=1&redirect_counter=' . $redirect_counter); $headers = array( 'Host' => $_SERVER['HTTP_HOST'], 'Connection' => 'close', ); // Request file. if (function_exists('stream_socket_client') && function_exists('stream_select')) { advagg_async_connect_http_request($url, array('headers' => $headers)); } else { // Set timeout. $socket_timeout = ini_set('default_socket_timeout', variable_get('advagg_socket_timeout', ADVAGG_SOCKET_TIMEOUT)); drupal_http_request($url, $headers, 'GET'); ini_set('default_socket_timeout', $socket_timeout); } // Return filepath if we are going to wait for the bundle to be // generated or if the bundle already exists. if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) < 2 || advagg_bundle_built($filepath)) { $output[$filepath] = array( 'prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)), ); } else { // Aggregate isn't built yet, send back the files that where going to // be in it. foreach ($files as $file) { $output[$file . $query_string] = array( 'prefix' => '', 'suffix' => '', 'files' => array($file . $query_string => TRUE), ); } $cacheable = FALSE; advagg_disable_page_cache(); } continue; } // Only generate once. $lock_name = 'advagg_' . $filename; if (!lock_acquire($lock_name) && !$force) { if (variable_get('advagg_aggregate_mode', ADVAGG_AGGREGATE_MODE) == 0 ) { $locks[$lock_name] = $filepath; $output[$filepath] = array( 'prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)), ); } else { // Aggregate isn't built yet, send back the files that where going // to be in it. foreach ($files as $file) { $output[$file . $query_string] = array( 'prefix' => '', 'suffix' => '', 'files' => array($file . $query_string => TRUE), ); } $cacheable = FALSE; advagg_disable_page_cache(); } continue; } if ($type == 'css') { $data = advagg_build_css_bundle($files); } elseif ($type == 'js') { $data = advagg_build_js_bundle($files); } // Invoke hook_advagg_js_alter() or hook_advagg_css_alter to give // installed modules a chance to modify the data in the bundle if // necessary. drupal_alter('advagg_' . $type, $data, $files, $bundle_md5); $files_used = array_merge($files_used, $files); // If data is empty then do not include this bundle in the final output. if (empty($data) && !$force) { lock_release($lock_name); continue; } // Create the advagg_$type/ within the files folder. file_check_directory($file_type_path, FILE_CREATE_DIRECTORY); // Write file. default function called: advagg_file_saver $function = variable_get('advagg_file_save_function', ADVAGG_FILE_SAVE_FUNCTION); $good = $function($data, $filepath, $force, $type); // Release lock. lock_release($lock_name); // If file save was not good then downgrade to non aggregated mode. if (!$good) { $output[$filepath] = FALSE; $cacheable = FALSE; continue; } } else { $files_used = array_merge($files_used, $files); } $output[$filepath] = array( 'prefix' => $prefix, 'suffix' => $suffix, 'files' => array_map('advagg_return_true', array_flip($files)), ); } // Wait for all locks before returning. if (!empty($locks)) { foreach ($locks as $lock_name => $filepath) { lock_wait($lock_name); if (!advagg_bundle_built($filepath)) { $output[$filepath] = FALSE; } } } if (empty($output)) { $output[] = FALSE; return $output; } // Cache the output if (!$force && $cacheable) { $new_cached_data_key = 'advagg_file_builder_' . md5($schema . implode('', array_filter(array_unique($files_used)))); // Verify the files in equals the files out. if ($new_cached_data_key == $cached_data_key) { cache_set($cached_data_key, $output, 'cache_advagg_bundle_reuse', CACHE_TEMPORARY); } } return $output; } /** * Given a list of files, grab their contents and glue it into one big string. * * @param $files * array of filenames. * @return * string containing all the files. */ function advagg_build_css_bundle($files) { // Check if CSS compression is enabled. if (module_exists('advagg_css') && (variable_get('advagg_css_compress_agg_files', ADVAGG_CSS_COMPRESS_AGG_FILES) || variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE))) { $optimize = FALSE; } else { $optimize = TRUE; } // Build aggregate CSS file. $data = ''; foreach ($files as $file) { $contents = advagg_drupal_load_stylesheet($file, $optimize); // Build the base URL of this CSS file: start with the full URL. $css_base_url = advagg_file_create_url($file); // Move to the parent. $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/')); // Simplify to a relative URL if the stylesheet URL starts with the // base URL of the website. if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) { $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root'])); } _drupal_build_css_path(NULL, $css_base_url . '/'); // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths. $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents); } // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, // @import rules must proceed any other style, so we move those to the top. $regexp = '/@import[^;]+;/i'; preg_match_all($regexp, $data, $matches); $data = preg_replace($regexp, '', $data); $data = implode('', $matches[0]) . $data; return $data; } /** * Given a list of files, grab their contents and glue it into one big string. * * @param $files * array of filenames. * @return * string containing all the files. */ function advagg_build_js_bundle($files) { if (empty($files)) { return ''; } $data = ''; // Build aggregate JS file. foreach ($files as $file) { // Append a ';', '/**/', and a newline after each JS file to prevent them // from running together. if (advagg_file_exists($file)) { $data .= @file_get_contents($file) . ";/**/\n"; } } return $data; } /** * Use a cache table to see if a file exists. * * @param $filename * name of file * @return * TRUE or FALSE */ function advagg_file_exists($filename) { static $files = array(); if (empty($files)) { $data = cache_get('advagg_file_checksum', 'cache'); if (empty($data->data)) { $result = db_query("SELECT filename, checksum FROM {advagg_files}"); while ($row = db_fetch_array($result)) { $files[$row['filename']] = $row['checksum']; } cache_set('advagg_file_checksum', $files, 'cache', CACHE_TEMPORARY); } else { $files = $data->data; } } if (!empty($files[$filename]) && $files[$filename] != -1) { return TRUE; } else { advagg_clearstatcache(TRUE, $filename); return file_exists($filename); } } /** * Send out a fast 404 and exit. */ function advagg_missing_fast404($msg = '') { global $base_path; if (!headers_sent()) { header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); header('X-AdvAgg: Failed Validation. ' . $msg); } print '' . "\n"; print ''; print '404 Not Found'; print '

Not Found

'; print '

The requested URL was not found on this server.

'; print '

Home

'; print ''; print ''; exit(); } /** * Generate .htaccess rules and place them in advagg dir * * @param $dest * destination of the file that just got saved. * @param $force * force recreate the .htaccess file. */ function advagg_htaccess_check_generate($dest, $force = FALSE) { global $base_path; if (!$force && !variable_get('advagg_dir_htaccess', ADVAGG_DIR_HTACCESS)) { return TRUE; } $dir = dirname($dest); $htaccess_file = $dir . '/.htaccess'; advagg_clearstatcache(TRUE, $htaccess_file); if (!$force && file_exists($htaccess_file)) { return TRUE; } list($css_path, $js_path) = advagg_get_root_files_dir(); $type = ''; if ($dir == $js_path) { $ext = 'js'; $path = $js_path; $type = 'text/javascript'; } elseif ($dir == $css_path) { $ext = 'css'; $path = $css_path; $type = 'text/css'; } else { return FALSE; } $data = "\n"; if (variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION)) { $data .= "\n"; $data .= " RewriteEngine on\n"; $data .= " RewriteBase ${base_path}${path}\n"; $data .= "\n"; $data .= " # Send 404's back to index.php\n"; $data .= " RewriteCond %{REQUEST_FILENAME} !-s\n"; $data .= " RewriteRule ^(.*)$ ${base_path}index.php?q=$path/$1 [L]\n"; $data .= "\n"; $data .= " # Rules to correctly serve gzip compressed $ext files.\n"; $data .= " # Requires both mod_rewrite and mod_headers to be enabled.\n"; $data .= " \n"; $data .= " # Serve gzip compressed $ext files if they exist and client accepts gzip.\n"; $data .= " RewriteCond %{HTTP:Accept-encoding} gzip\n"; $data .= " RewriteCond %{REQUEST_FILENAME}\.gz -s\n"; $data .= " RewriteRule ^(.*)\.$ext$ $1\.$ext\.gz [QSA]\n"; $data .= "\n"; $data .= " # Serve correct content types, and prevent mod_deflate double gzip.\n"; $data .= " RewriteRule \.$ext\.gz$ - [T=$type,E=no-gzip:1]\n"; $data .= "\n"; $data .= " \n"; $data .= " # Serve correct encoding type.\n"; $data .= " Header set Content-Encoding gzip\n"; $data .= " # Force proxies to cache gzipped & non-gzipped $ext files separately.\n"; $data .= " Header append Vary Accept-Encoding\n"; $data .= " \n"; $data .= " \n"; $data .= "\n"; $data .= "\n"; } $data .= "\n"; $data .= " # No mod_headers\n"; $data .= " \n"; $data .= " # No mod_expires\n"; $data .= " \n"; $data .= " # Use ETags.\n"; $data .= " FileETag MTime Size\n"; $data .= " \n"; $data .= "\n"; $data .= " # Use Expires Directive.\n"; $data .= " \n"; $data .= " # Do not use ETags.\n"; $data .= " FileETag None\n"; $data .= " # Enable expirations.\n"; $data .= " ExpiresActive On\n"; $data .= " # Cache all aggregated $ext files for 480 weeks after access (A).\n"; $data .= " ExpiresDefault A290304000\n"; $data .= " \n"; $data .= " \n"; $data .= "\n"; $data .= " \n"; $data .= " # Set a far future Cache-Control header to 480 weeks.\n"; $data .= " Header set Cache-Control \"max-age=290304000, no-transform, public\"\n"; $data .= " # Set a far future Expires header.\n"; $data .= " Header set Expires \"Tue, 20 Jan 2037 04:20:42 GMT\"\n"; $data .= " # Pretend the file was last modified a long time ago in the past.\n"; $data .= " Header set Last-Modified \"Wed, 20 Jan 1988 04:20:42 GMT\"\n"; $data .= " # Do not use etags for cache validation.\n"; $data .= " Header unset ETag\n"; $data .= " \n"; $data .= "\n"; if (!advagg_file_save_data($data, $htaccess_file, FILE_EXISTS_REPLACE)) { return FALSE; } return TRUE; } /** * Adds a CSS file to the stylesheet queue. * * @param $data * (optional) The CSS data that will be set. If not set then the inline CSS * array will be passed back. * @param $media * (optional) The media type for the stylesheet, e.g., all, print, screen. * @param $prefix * (optional) prefix to add before the inlined css. * @param $suffix * (optional) suffix to add after the inlined css. * @return * An array of CSS files. */ function advagg_add_css_inline($data = NULL, $media = 'all', $prefix = NULL, $suffix = NULL) { static $css = array(); // Store inline data in a static. if (isset($data)) { if (!isset($css[$media]['inline'][$prefix][$suffix])) { $css[$media]['inline'][$prefix][$suffix] = $data; } else { $css[$media]['inline'][$prefix][$suffix] .= "\n" . $data; } return; } else { return $css; } } /** * Converts a PHP variable into its Javascript equivalent. * * We use HTML-safe strings, i.e. with <, > and & escaped. */ function advagg_drupal_to_js($var) { // Different versions of PHP handle json_encode() differently. static $php550; static $php530; if (!isset($php550)) { $php550 = version_compare(PHP_VERSION, '5.5.0', '>='); } if (!isset($php530)) { $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); } // json_encode on PHP prior to PHP 5.3.0 doesn't support options. if ($php530) { // Default json encode options. $options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; if ($php550) { // Output partial json if PHP >= 5.5.0. $options |= JSON_PARTIAL_OUTPUT_ON_ERROR; } // Encode to JSON. return @json_encode($var, $options); } // if json_encode exists, use it. if (function_exists('json_encode')) { return str_replace(array("<", ">", "&"), array('\u003c', '\u003e', '\u0026'), json_encode($var)); } switch (gettype($var)) { case 'boolean': return $var ? 'true' : 'false'; // Lowercase necessary! case 'integer': case 'double': return $var; case 'resource': case 'string': // Always use Unicode escape sequences (\u0022) over JSON escape // sequences (\") to prevent browsers interpreting these as // special characters. $replace_pairs = array( // ", \ and U+0000 - U+001F must be escaped according to RFC 4627. '\\' => '\u005C', '"' => '\u0022', "\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002', "\x03" => '\u0003', "\x04" => '\u0004', "\x05" => '\u0005', "\x06" => '\u0006', "\x07" => '\u0007', "\x08" => '\u0008', "\x09" => '\u0009', "\x0a" => '\u000A', "\x0b" => '\u000B', "\x0c" => '\u000C', "\x0d" => '\u000D', "\x0e" => '\u000E', "\x0f" => '\u000F', "\x10" => '\u0010', "\x11" => '\u0011', "\x12" => '\u0012', "\x13" => '\u0013', "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016', "\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019', "\x1a" => '\u001A', "\x1b" => '\u001B', "\x1c" => '\u001C', "\x1d" => '\u001D', "\x1e" => '\u001E', "\x1f" => '\u001F', // Prevent browsers from interpreting these as as special. "'" => '\u0027', '<' => '\u003C', '>' => '\u003E', '&' => '\u0026', // Prevent browsers from interpreting the solidus as special and // non-compliant JSON parsers from interpreting // as a comment. '/' => '\u002F', // While these are allowed unescaped according to ECMA-262, section // 15.12.2, they cause problems in some JSON parsers. "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator. "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator. ); return '"' . strtr($var, $replace_pairs) . '"'; case 'array': // Arrays in JSON can't be associative. If the array is empty or if it // has sequential whole number keys starting with 0, it's not associative // so we can go ahead and convert it as an array. if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) { $output = array(); foreach ($var as $v) { $output[] = advagg_drupal_to_js($v); } return '[ ' . implode(', ', $output) . ' ]'; } // Otherwise, fall through to convert the array as an object. case 'object': $output = array(); foreach ($var as $k => $v) { $output[] = advagg_drupal_to_js(strval($k)) . ': ' . advagg_drupal_to_js($v); } return '{ ' . implode(', ', $output) . ' }'; default: return 'null'; } } /** * Process the contents of a stylesheet for aggregation. * * @param $contents * The contents of the stylesheet. * @param $optimize * (optional) Boolean whether CSS contents should be minified. Defaults to * FALSE. * @return * Contents of the stylesheet including the imported stylesheets. */ function advagg_drupal_load_stylesheet_content($contents, $optimize = FALSE) { // Remove multiple charset declarations for standards compliance (and fixing Safari problems). $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents); if ($optimize) { // Perform some safe CSS optimizations. // Regexp to match comment blocks. $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; // Regexp to match double quoted strings. $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; // Regexp to match single quoted strings. $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; // Strip all comment blocks, but keep double/single quoted strings. $contents = preg_replace( "<($double_quot|$single_quot)|$comment>Ss", "$1", $contents ); // Remove certain whitespace. // There are different conditions for removing leading and trailing // whitespace. // @see http://php.net/manual/en/regexp.reference.subpatterns.php $contents = preg_replace('< # Strip leading and trailing whitespace. \s*([@{};,])\s* # Strip only leading whitespace from: # - Closing parenthesis: Retain "@media (bar) and foo". | \s+([\)]) # Strip only trailing whitespace from: # - Opening parenthesis: Retain "@media (bar) and foo". # - Colon: Retain :pseudo-selectors. | ([\(:])\s+ >xS', // 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', $contents ); // End the file with a new line. $contents = trim($contents); $contents .= "\n"; } // Replaces @import commands with the actual stylesheet content. // This happens recursively but omits external files. $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_advagg_drupal_load_stylesheet', $contents); return $contents; } /** * Loads stylesheets recursively and returns contents with corrected paths. * * This function is used for recursive loading of stylesheets and * returns the stylesheet content with all url() paths corrected. */ function _advagg_drupal_load_stylesheet($matches) { $filename = $matches[1]; // Load the imported stylesheet and replace @import commands in there as well. $file = advagg_build_css_bundle(array($filename)); // Determine the file's directory. $directory = dirname($filename); // If the file is in the current directory, make sure '.' doesn't appear in // the url() path. $directory = $directory == '.' ? '' : $directory . '/'; // Alter all internal url() paths. Leave external paths alone. We don't need // to normalize absolute paths here (i.e. remove folder/... segments) because // that will be done later. return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1' . $directory, $file); } /** * 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 $file * Name of the stylesheet to be processed. * @param $optimize * Defines if CSS contents should be compressed or not. * @param $reset_basepath * Used internally to facilitate recursive resolution of @import commands. * * @return * Contents of the stylesheet, including any resolved @import commands. */ function advagg_drupal_load_stylesheet($file, $optimize = NULL, $reset_basepath = TRUE) { // These statics 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; } $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. if ($contents = @file_get_contents($file)) { // Return the processed stylesheet. return advagg_drupal_load_stylesheet_content($contents, $_optimize); } return ''; } /** * Perform an HTTP request; does not wait for reply & you will never get it * back. * * @see drupal_http_request() * * This is a flexible and powerful HTTP client implementation. Correctly * handles GET, POST, PUT or any other HTTP requests. * * @param $url * A string containing a fully qualified URI. * @param array $options * (optional) An array that can have one or more of the following elements: * - headers: An array containing request headers to send as name/value pairs. * - method: A string containing the request method. Defaults to 'GET'. * - data: A string containing the request body, formatted as * 'param=value¶m=value&...'. Defaults to NULL. * - max_redirects: An integer representing how many times a redirect * may be followed. Defaults to 3. * - timeout: A float representing the maximum number of seconds the function * call may take. The default is 30 seconds. If a timeout occurs, the error * code is set to the HTTP_REQUEST_TIMEOUT constant. * - context: A context resource created with stream_context_create(). * @return bool * return value from advagg_async_send_http_request(). */ function advagg_async_connect_http_request($url, array $options = array()) { $result = new stdClass(); // Parse the URL and make sure we can handle the schema. $uri = @parse_url($url); if (empty($uri)) { $result->error = 'unable to parse URL'; $result->code = -1001; return $result; } if (!isset($uri['scheme'])) { $result->error = 'missing schema'; $result->code = -1002; return $result; } // Merge the default options. $options += array( 'headers' => array(), 'method' => 'GET', 'data' => NULL, 'max_redirects' => 3, 'timeout' => 30.0, 'context' => NULL, ); // stream_socket_client() requires timeout to be a float. $options['timeout'] = (float) $options['timeout']; switch ($uri['scheme']) { case 'http': case 'feed': $port = isset($uri['port']) ? $uri['port'] : 80; $socket = 'tcp://' . $uri['host'] . ':' . $port; // RFC 2616: "non-standard ports MUST, default ports MAY be included". // We don't add the standard port to prevent from breaking rewrite rules // checking the host that do not take into account the port number. if (empty($options['headers']['Host'])) { $options['headers']['Host'] = $uri['host']; } if ($port != 80) { $options['headers']['Host'] .= ':' . $port; } break; case 'https': // Note: Only works when PHP is compiled with OpenSSL support. $port = isset($uri['port']) ? $uri['port'] : 443; $socket = 'ssl://' . $uri['host'] . ':' . $port; if (empty($options['headers']['Host'])) { $options['headers']['Host'] = $uri['host']; } if ($port != 443) { $options['headers']['Host'] .= ':' . $port; } break; default: $result->error = 'invalid schema ' . $uri['scheme']; $result->code = -1003; return $result; } $flags = STREAM_CLIENT_CONNECT; if (variable_get('advagg_async_socket_connect', ADVAGG_ASYNC_SOCKET_CONNECT)) { $flags = STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT; } if (empty($options['context'])) { $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags); } else { // Create a stream with context. Allows verification of a SSL certificate. $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], $flags, $options['context']); } // Make sure the socket opened properly. if (!$fp) { // When a network error occurs, we use a negative number so it does not // clash with the HTTP status codes. $result->code = -$errno; $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket)); return $result; } // Non blocking stream. stream_set_blocking($fp, 0); // Construct the path to act on. $path = isset($uri['path']) ? $uri['path'] : '/'; if (isset($uri['query'])) { $path .= '?' . $uri['query']; } // Merge the default headers. $options['headers'] += array( 'User-Agent' => 'Drupal (+http://drupal.org/)', ); // Only add Content-Length if we actually have any content or if it is a POST // or PUT request. Some non-standard servers get confused by Content-Length in // at least HEAD/GET requests, and Squid always requires Content-Length in // POST/PUT requests. $content_length = strlen($options['data']); if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') { $options['headers']['Content-Length'] = $content_length; } // If the server URL has a user then attempt to use basic authentication. if (isset($uri['user'])) { $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : '')); } // If the database prefix is being used by SimpleTest to run the tests in a copied // database then set the user-agent header to the database prefix so that any // calls to other Drupal pages will run the SimpleTest prefixed database. The // user-agent is used to ensure that multiple testing sessions running at the // same time won't interfere with each other as they would if the database // prefix were stored statically in a file or database variable. $test_info = &$GLOBALS['drupal_test_info']; if (!empty($test_info['test_run_id'])) { $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']); } $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n"; foreach ($options['headers'] as $name => $value) { $request .= $name . ': ' . trim($value) . "\r\n"; } $request .= "\r\n" . $options['data']; $result->request = $request; return advagg_async_send_http_request($fp, $request, $options['timeout']); } /** * Perform an HTTP request; does not wait for reply & you never will get it * back. * * @see drupal_http_request() * * This is a flexible and powerful HTTP client implementation. Correctly * handles GET, POST, PUT or any other HTTP requests. * * @param $fp * (optional) A file pointer. * @param $request * (optional) A string containing the request headers to send to the server. * @param $timeout * (optional) An integer holding the stream timeout value. * @return bool * TRUE if function worked as planed. */ function advagg_async_send_http_request($fp = NULL, $request = '', $timeout = 30) { static $requests = array(); static $registered = FALSE; // Store data in a static, and register a shutdown function. $args = array($fp, $request, $timeout); if (!empty($fp)) { $requests[] = $args; if (!$registered) { register_shutdown_function(__FUNCTION__); $registered = TRUE; } return TRUE; } // Shutdown function run. if (empty($requests)) { return FALSE; } $streams = array(); foreach ($requests as $id => $values) { list($fp, $request, $timeout) = $values; $streams[$id] = $fp; } $retry_count = 2; // Run the loop as long as we have a stream to write to. while (!empty($streams)) { // Set the read and write vars to the streams var. $read = $write = $streams; $except = array(); // Do some voodoo and open all streams at once. $n = @stream_select($read, $write, $except, $timeout); // We have some streams to write to. if (!empty($n)) { // Write to each stream if it is available. foreach ($write as $id => $w) { fwrite($w, $requests[$id][1]); fclose($w); unset($streams[$id]); } } // Timed out waiting or all $streams are closed at this point. elseif (!empty($retry_count)) { $retry_count--; } else { break; } } // Free memory. $requests = array(); if ($n !== FALSE && empty($streams)) { return TRUE; } else { return FALSE; } } /** * Implement hook_advagg_js_header_footer_alter. */ function advagg_advagg_js_header_footer_alter(&$master_set, $preprocess_js, $public_downloads) { // Don't run the code below if ctools ajax is not loaded. if (!defined('CTOOLS_AJAX_INCLUDED')) { return; } // Get all JS files set to be loaded. $js_files = array(); foreach ($master_set as $scope => $scripts) { if (empty($scripts)) { continue; } advagg_ctools_process_js_files($js_files, $scope, $scripts); } // Add list of CSS & JS files loaded to the settings in the footer. $loaded = array('CToolsAJAX' => array('scripts' => $js_files)); // Save to the js settings array even though we do not reload it in advagg. drupal_add_js($loaded, 'setting', 'footer'); // Add it to the settings array in the footer. if (!isset($master_set['footer']['setting']) || !is_array($master_set['footer']['setting'])) { $master_set['footer']['setting'] = array(); } $master_set['footer']['setting'][] = $loaded; } /** * Implement hook_advagg_css_pre_alter. */ function advagg_advagg_css_pre_alter(&$css, $preprocess_css, $public_downloads) { // Don't run the code below if ctools ajax is not loaded. if (!defined('CTOOLS_AJAX_INCLUDED')) { return; } // Get all CSS files set to be loaded. $css_files = array(); ctools_process_css_files($css_files, $css); // Save to the js settings array. drupal_add_js(array('CToolsAJAX' => array('css' => $css_files)), 'setting', 'footer'); } /** * Create a list of javascript files that are on the page. * * @param $js_files * Array of js files that are loaded on this page. * @param $scope * String usually containing header or footer. * @param $scripts * (Optional) array returned from drupal_add_js(). If NULL then it will load * the array from drupal_add_js for the given scope. * @return array $settings * The JS 'setting' array for the given scope. */ function advagg_ctools_process_js_files(&$js_files, $scope, $scripts = NULL) { // Automatically extract any 'settings' added via drupal_add_js() and make // them the first command. $scripts = drupal_add_js(NULL, NULL, $scope); if (empty($scripts)) { $scripts = drupal_add_js(NULL, NULL, $scope); } // Get replacements that are going to be made by contrib modules and take // them into account so we don't double-load scripts. static $replacements = NULL; if (!isset($replacements)) { $replacements = module_invoke_all('js_replacements'); } $settings = array(); foreach ($scripts as $type => $data) { switch ($type) { case 'setting': $settings = $data; break; case 'inline': case 'theme': // Presently we ignore inline javascript. // Theme JS is already added and because of admin themes, this could add // improper JS to the page. break; default: // If JS preprocessing is off, we still need to output the scripts. // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones. foreach ($data as $path => $info) { // If the script is being replaced, take that replacement into account. $final_path = isset($replacements[$type][$path]) ? $replacements[$type][$path] : $path; $js_files[base_path() . $final_path] = TRUE; } } } return $settings; } /** * Wrapper around clearstatcache so it can use php 5.3's new features. * * @param $clear_realpath_cache * Bool. * @param $filename * String. * @return * value from clearstatcache(). */ function advagg_clearstatcache($clear_realpath_cache = FALSE, $filename = NULL) { static $php530; if (!isset($php530)) { $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); } if ($php530) { return clearstatcache($clear_realpath_cache, $filename); } else { return clearstatcache(); } } /** * Select records in the database matching where IN(...). * * NOTE Be aware of the servers max_packet_size variable. * * @param $table * The name of the table. * @param $field * field name to be compared to * @param $placeholder * db_query placeholders; like %d or '%s' * @param $data * array of values you wish to compare to * @param $returns * array of db fields you return * @return * returns db_query() result. */ function advagg_db_multi_select_in($table, $field, $placeholder, $data, $returns = array(), $groupby = '') { // Set returns if empty if (empty($returns)) { $returns[] = '*'; } // Get the number of rows that will be inserted $rows = count($data); // Create what goes in the IN () $in = $placeholder; // Add the rest of the place holders for ($i = 1; $i < $rows; $i++) { $in .= ', ' . $placeholder; } // Build the query $query = "SELECT " . implode(', ', $returns) . " FROM {" . $table . "} WHERE $field IN ($in) $groupby"; // Run the query return db_query($query, $data); } /** * Return a large array of the CSS & JS files loaded on this page. * * @param $js_files_excluded * array of js files to not include in the output array. * @param $css_files_excluded * array of css files to not include in the output array. * @return * array. */ function advagg_get_js_css_get_array($js_files_excluded = array(), $css_files_excluded = array()) { global $conf, $_advagg; // Setup variables. $variables = array( 'css' => array(), 'js' => array(), ); // Setup render functions. $css_function = variable_get('advagg_css_render_function', ADVAGG_CSS_RENDER_FUNCTION); $js_function = variable_get('advagg_js_render_function', ADVAGG_JS_RENDER_FUNCTION); $conf['advagg_css_render_function'] = 'advagg_css_array'; $conf['advagg_js_render_function'] = 'advagg_js_array'; // Run CSS code. $css_array = array(); $variables['css'] = drupal_add_css(); if (module_exists('less')) { less_preprocess_page($variables, NULL); } $css_func_inline = advagg_add_css_inline(); if (!empty($css_func_inline)) { $variables['css'] = advagg_merge_inline_css($variables['css'], $css_func_inline); } // Remove excluded CSS files. foreach ($variables['css'] as $media => $types) { foreach ($types as $type => $values) { foreach ($values as $filename => $preprocess) { if (in_array($filename, $css_files_excluded)) { unset($variables['css'][$media][$type][$filename]); } } } } $css_array = advagg_process_css($variables['css']); // Run JS code. $js_array = array(); $variables['js']['header'] = drupal_add_js(NULL, NULL, 'header'); if (variable_get('advagg_closure', ADVAGG_CLOSURE) && !empty($_advagg['closure'])) { $variables['js']['footer'] = drupal_add_js(NULL, NULL, 'footer'); } advagg_jquery_updater($variables['js']['header']); // Remove excluded JS files. foreach ($variables['js'] as $scope => $values) { foreach ($values as $type => $data) { foreach ($data as $filename => $info) { if (in_array($filename, $js_files_excluded)) { unset($variables['js'][$scope][$type][$filename]); } } } } $js_array = advagg_process_js($variables['js']); // Set render functions back to defaults. $conf['advagg_css_render_function'] = $css_function; $conf['advagg_js_render_function'] = $js_function; // Return arrays. return array( 'js' => $js_array, 'css' => $css_array, ); } /** * Logic to figure out what kind of css tags to use. * * @param $external_no_preprocess * array of css files ($media, $href) * @param $module_no_preprocess * array of css files ($media, $href) * @param $output_no_preprocess * array of css files ($media, $href) * @param $output_preprocess * array of css files ($media, $href, $prefix, $suffix) * @param $theme_no_preprocess * array of css files ($media, $href) * @param $inline_no_preprocess * array of css data to inline ($media, $data) * @param $files_included * array of css files included. $a[$media][] = $filename * @param $files_aggregates_included * array of css files & aggregates included. $a[$media][] = $filename * @param $inline_included * array of inline css included. $a[$media][] = $datablob; * @return * html for loading the css. html for the head. */ function advagg_css_array($external_no_preprocess, $module_no_preprocess, $output_no_preprocess, $output_preprocess, $theme_no_preprocess, $inline_no_preprocess, $inline_included, $files_included, $files_aggregates_included) { return array( 'inline' => $inline_included, 'files' => $files_included, 'files_aggregates' => $files_aggregates_included, ); } /** * Build and theme JS output for header. * * @param $external_no_preprocess * array(array($src, $defer)) * @param $output_preprocess * array(array($src, $prefix, $suffix)) * @param $output_no_preprocess * array(array(array($src, $defer))) * @param $setting_no_preprocess * array(array($code)) * @param $inline_no_preprocess * array(array($code, $defer)) * @param $scope * header or footer. * @param $js_settings_array * array of settings used. * @param $inline_included * array of inline scripts used. * @param $files_included * array of files used. * @param $files_aggregates_included * array of files and aggregates used. * @return * String of themed JavaScript. */ function advagg_js_array($external_no_preprocess, $output_preprocess, $output_no_preprocess, $setting_no_preprocess, $inline_no_preprocess, $scope, $js_settings_array, $inline_included, $files_included, $files_aggregates_included) { return array( 'settings' => $js_settings_array, 'inline' => $inline_included, 'files' => $files_included, 'files_aggregates' => $files_aggregates_included, ); } /** * Implementation of hook_file_download(). * * Return the correct headers for advagg bundles. */ function advagg_file_download($file, $type = '') { // Do nothing if not an AdvAgg File. if (strpos($file, '/advagg_') === FALSE || empty($type)) { return; } // Set the headers. $return = array(); $return[] = 'Content-Length: ' . filesize($file); // Set a far future Cache-Control header (480 weeks), which prevents // intermediate caches from transforming the data and allows any // intermediate cache to cache it, since it's marked as a public resource. $return[] = "Cache-Control: max-age=290304000, no-transform, public"; // Set a far future Expires header. The maximum UNIX timestamp is somewhere // in 2038. Set it to a date in 2037, just to be safe. $return[] = 'Expires: Tue, 20 Jan 2037 04:20:42 GMT'; // Pretend the file was last modified a long time ago in the past, this will // prevent browsers that don't support Cache-Control nor Expires headers to // still request a new version too soon (these browsers calculate a // heuristic to determine when to request a new version, based on the last // time the resource has been modified). // Also see http://code.google.com/speed/page-speed/docs/caching.html. $return[] = 'Last-Modified: Wed, 20 Jan 1988 04:20:42 GMT'; if ($type == 'css') { $return[] = 'Content-Type: text/css'; } if ($type == 'js') { $return[] = 'Content-Type: text/javascript'; } return $return; } /** * Helper function to build an URL for asynchronous requests. * * @param $filepath * Path to a URI excluding everything to the left and including the base path. */ function _advagg_build_url($filepath = '') { global $base_path; // Server auth. $auth = ''; if (isset($_SERVER['AUTH_TYPE']) && $_SERVER['AUTH_TYPE'] == 'Basic') { $auth = $_SERVER['PHP_AUTH_USER'] . ':' . $_SERVER['PHP_AUTH_PW'] . '@'; } // Host. $ip = variable_get('advagg_server_addr', FALSE); if ($ip == -1) { $ip = $_SERVER['HTTP_HOST']; } elseif (empty($ip)) { $ip = empty($_SERVER['SERVER_ADDR']) ? '127.0.0.1' : $_SERVER['SERVER_ADDR']; } // Port. $port = ''; // if ( isset($_SERVER['SERVER_PORT']) // && is_numeric($_SERVER['SERVER_PORT']) // && ($_SERVER['SERVER_PORT'] != 80 || $_SERVER['SERVER_PORT'] != 443) // ) { // $port = ':' . $_SERVER['SERVER_PORT']; // } return 'http://' . $auth . $ip . $port . $base_path . $filepath; }