diff --git a/advagg.advagg.inc b/advagg.advagg.inc index 0bb690e..95ca3f5 100644 --- a/advagg.advagg.inc +++ b/advagg.advagg.inc @@ -16,8 +16,10 @@ * array($uri => $contents) * @param $aggregate_settings * array of settings. + * @param $other_parameters + * array of containing $files & $type. */ -function advagg_advagg_save_aggregate_alter(&$files_to_save, $aggregate_settings) { +function advagg_advagg_save_aggregate_alter(&$files_to_save, $aggregate_settings, $other_parameters) { // Return if gzip is disabled. if (empty($aggregate_settings['variables']['advagg_gzip'])) { return; diff --git a/advagg.cache.inc b/advagg.cache.inc index 0e3e86e..c52f578 100644 --- a/advagg.cache.inc +++ b/advagg.cache.inc @@ -95,6 +95,10 @@ function advagg_push_new_changes() { advagg_insert_update_files(array($filename => $meta_data), $ext); } + // Let other modules know about the changed files. + // Call hook_advagg_changed_files(). + module_invoke_all('advagg_changed_files', $files, $types); + // Clear out the full aggregates cache. foreach ($types as $ext => $bool) { cache_clear_all('advagg:' . $ext . ':', 'cache_advagg_aggregates', TRUE); diff --git a/advagg.inc b/advagg.inc index 8266bf6..2deb20e 100644 --- a/advagg.inc +++ b/advagg.inc @@ -332,11 +332,23 @@ function advagg_get_aggregate_info_from_files($type, $files) { */ function advagg_get_info_on_file($filename, $bypass_cache = FALSE) { $filename_hash = drupal_hash_base64($filename); + $filename_hashes = &drupal_static('advagg_get_info_on_file'); // Use caching to avoid hitting disk if possible. $cache_id = 'advagg:file:' . $filename_hash; - if (!$bypass_cache && variable_get('advagg_use_file_cache', ADVAGG_USE_FILE_CACHE) && $cache = cache_get($cache_id, 'cache_advagg_info')) { - return $cache->data; + if (!$bypass_cache) { + // Try static cache first. + if ( !empty($filename_hashes) + && array_key_exists($cache_id, $filename_hashes) + && !empty($filename_hashes[$cache_id]) + ) { + return $filename_hashes[$cache_id]; + } + + // Try cache backend next. + if (variable_get('advagg_use_file_cache', ADVAGG_USE_FILE_CACHE) && $cache = cache_get($cache_id, 'cache_advagg_info')) { + return $cache->data; + } } // Clear PHP's internal file status cache. @@ -355,7 +367,7 @@ function advagg_get_info_on_file($filename, $bypass_cache = FALSE) { $linecount = substr_count($file_contents, "\n"); // Build meta data array and set cache. - $data = array( + $filename_hashes[$cache_id] = array( 'filesize' => filesize($filename), 'mtime' => @filemtime($filename), 'filename_hash' => $filename_hash, @@ -363,9 +375,9 @@ function advagg_get_info_on_file($filename, $bypass_cache = FALSE) { 'linecount' => $linecount, ); if (variable_get('advagg_use_file_cache', ADVAGG_USE_FILE_CACHE)) { - cache_set($cache_id, $data, 'cache_advagg_info', CACHE_PERMANENT); + cache_set($cache_id, $filename_hashes[$cache_id], 'cache_advagg_info', CACHE_PERMANENT); } - return $data; + return $filename_hashes[$cache_id]; } /** diff --git a/advagg.missing.inc b/advagg.missing.inc index 7f03b4b..06adb5e 100644 --- a/advagg.missing.inc +++ b/advagg.missing.inc @@ -438,9 +438,9 @@ function advagg_save_aggregate($filename, $files, $type, $aggregate_settings) { $uri => $contents, ); - // Allow other modules to alter the contents and add new files to save. + // Allow other modules to alter the contents and add new files to save (.gz). // Call hook_advagg_save_aggregate_alter(). - drupal_alter('advagg_save_aggregate', $files_to_save, $aggregate_settings); + drupal_alter('advagg_save_aggregate', $files_to_save, $aggregate_settings, array($files, $type)); foreach ($files_to_save as $uri => $data) { advagg_save_data($uri, $data); diff --git a/advagg.module b/advagg.module index cc92ffb..63fb14e 100644 --- a/advagg.module +++ b/advagg.module @@ -59,6 +59,7 @@ function advagg_hook_info() { 'advagg_get_js_aggregate_contents_alter', 'advagg_save_aggregate_alter', 'advagg_build_aggregate_plans_alter', + 'advagg_changed_files', ); $hooks = array(); foreach ($advagg_hooks as $hook) { @@ -784,6 +785,7 @@ function advagg_hooks_implemented($all = TRUE) { if ($all) { $hooks += array( 'advagg_build_aggregate_plans_alter' => array(), + 'advagg_changed_files' => array(), 'js_alter' => array(), 'css_alter' => array(), ); diff --git a/advagg_js_compress/advagg_js_compress.advagg.inc b/advagg_js_compress/advagg_js_compress.advagg.inc index 5eb90f5..f35ed23 100644 --- a/advagg_js_compress/advagg_js_compress.advagg.inc +++ b/advagg_js_compress/advagg_js_compress.advagg.inc @@ -25,8 +25,16 @@ function advagg_js_compress_advagg_get_js_file_contents_alter(&$contents, $filen * array($uri => $contents) * @param $aggregate_settings * array of settings. + * @param $other_parameters + * array of containing $files & $type. */ -function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggregate_settings) { +function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggregate_settings, $other_parameters) { + list($files, $type) = $other_parameters; + + // Return if type is not js. + if ($type != 'js') { + return; + } // Return if gzip is disabled. if (empty($aggregate_settings['variables']['advagg_gzip'])) { return; @@ -35,6 +43,10 @@ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggreg if (empty($aggregate_settings['variables']['advagg_js_compress_packer'])) { return; } + // Make sure all files in this aggregrate are compatible with packer. + foreach ($files as $file) { + + } // See if a .gz file already exists. $gzip_exists = FALSE; @@ -63,46 +75,94 @@ function advagg_js_compress_advagg_save_aggregate_alter(&$files_to_save, $aggreg } // Use packer on non gzip JS data. - advagg_js_compress_jspacker($data); + $aggregate_settings['variables']['advagg_js_compressor'] = 2; + advagg_js_compress_prep($data, $uri, $aggregate_settings, FALSE); $files_to_save[$uri] = $data; } /** + * Implement advagg_changed_files(). + */ +function advagg_js_compress_advagg_changed_files($files, $types) { + // Only care about js files. + if (empty($types['js'])) { + return; + } + $return = array(); + foreach ($files as $filename => $meta_data) { + // Only care about js files. + $ext = pathinfo($filename, PATHINFO_EXTENSION); + if ($ext != 'js') { + continue; + } + + $compressors = array(0 => 'jsminplus', 2 => 'packer'); + if (function_exists('jsmin')) { + $compressors[1] = 'jsmin'; + ksort($compressors); + } + + $return[$filename] = advagg_js_compress_run_test($filename); + } + return $return; +} + +/** * Compress a JS string * * @param $contents * Javascript string. * @param $filename * filename. + * @param $add_licensing + * FALSE to remove Source and licensing information comment. + * @param $log_errors + * FALSE to disable logging to watchdog on failure. + * @param $test_ratios + * FALSE to disable compression ratio testing. */ -function advagg_js_compress_prep(&$contents, $filename, $aggregate_settings) { +function advagg_js_compress_prep(&$contents, $filename, $aggregate_settings, $add_licensing = TRUE, $log_errors = TRUE, $test_ratios = TRUE) { + // Get the info on this file. + module_load_include('inc', 'advagg', 'advagg'); + $info = advagg_get_info_on_file($filename); + $compressor = $aggregate_settings['variables']['advagg_js_compressor']; + + + // Get the JS string length before the compression operation. $contents_before = $contents; $before = strlen($contents); + // Strip Byte Order Marks (BOM's) from the file, preg_* cannot parse these. + $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); + // Use the compressor. - $compressor = $aggregate_settings['variables']['advagg_js_compressor']; if ($compressor == 0) { - advagg_js_compress_jsminplus($contents); + advagg_js_compress_jsminplus($contents, $log_errors); } elseif ($compressor == 1) { $contents = jsmin($contents); } - - // Make sure compression ratios are good. - $after = strlen($contents); - $ratio = 0; - if ($before != 0) { - $ratio = ($before - $after) / $before; - } - // Make sure the returned string is not empty or has a VERY high - // compression ratio. - if (empty($contents) || empty($ratio) || $ratio > $aggregate_settings['variables']['advagg_js_max_compress_ratio']) { - $contents = $contents_before; + elseif ($compressor == 2) { + advagg_js_compress_jspacker($contents); } - else { - $url = url($filename, array('absolute' => TRUE)); - $contents = "/* Source and licensing information for the line(s) below can be found at $url. */\n" . $contents . ";\n/* Source and licensing information for the above line(s) can be found at $url. */\n"; + + if ($test_ratios) { + // Make sure compression ratios are good. + $after = strlen($contents); + $ratio = 0; + if ($before != 0) { + $ratio = ($before - $after) / $before; + } + // Make sure the returned string is not empty or has a VERY high + // compression ratio. + if (empty($contents) || empty($ratio) || $ratio > $aggregate_settings['variables']['advagg_js_max_compress_ratio']) { + $contents = $contents_before; + } + elseif ($add_licensing) { + $url = url($filename, array('absolute' => TRUE)); + $contents = "/* Source and licensing information for the line(s) below can be found at $url. */\n" . $contents . ";\n/* Source and licensing information for the above line(s) can be found at $url. */\n"; + } } } @@ -111,10 +171,10 @@ function advagg_js_compress_prep(&$contents, $filename, $aggregate_settings) { * * @param $contents * Javascript string. - * @return - * array with the size before and after. + * @param $log_errors + * FALSE to disable logging to watchdog on failure. */ -function advagg_js_compress_jsminplus(&$contents) { +function advagg_js_compress_jsminplus(&$contents, $log_errors = TRUE) { $contents_before = $contents; // Only include jsminplus.inc if the JSMinPlus class doesn't exist. @@ -123,8 +183,6 @@ function advagg_js_compress_jsminplus(&$contents) { } ob_start(); try { - // Strip Byte Order Marks (BOM's) from the file, JSMin+ cannot parse these. - $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); // JSMin+ the contents of the aggregated file. $contents = JSMinPlus::minify($contents); @@ -136,7 +194,9 @@ function advagg_js_compress_jsminplus(&$contents) { } catch (Exception $e) { // Log the exception thrown by JSMin+ and roll back to uncompressed content. - watchdog('advagg_js_compress', $e->getMessage() . '
' . $contents_before . '
', NULL, WATCHDOG_WARNING); + if ($log_errors) { + watchdog('advagg_js_compress', $e->getMessage() . '
' . $contents_before . '
', NULL, WATCHDOG_WARNING); + } $contents = $contents_before; } ob_end_clean(); @@ -154,18 +214,103 @@ function advagg_js_compress_jspacker(&$contents) { include(drupal_get_path('module', 'advagg_js_compress') . '/jspacker.inc'); } - // Strip Byte Order Marks (BOM's) from the file, packer cannot parse these. - $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); - // Add semicolons to the end of lines if missing. $contents = str_replace("}\n", "};\n", $contents); $contents = str_replace("\nfunction", ";\nfunction", $contents); - // Remove char returns, - $contents = str_replace("\n\r", "", $contents); - $contents = str_replace("\r", "", $contents); - $contents = str_replace("\n", "", $contents); - $packer = new JavaScriptPacker($contents, 62, TRUE, FALSE); $contents = $packer->pack(); } + +function advagg_js_compress_run_test($filename) { + // Get the info on this file. + module_load_include('inc', 'advagg', 'advagg'); + $info = advagg_get_info_on_file($filename); + // Cache info. + $filename_hashes = &drupal_static('advagg_get_info_on_file'); + $cache_id = 'advagg:file:' . $info['filename_hash']; + $httprl_used = FALSE; + + // Build list of compressors. + $compressors = array(0 => 'jsminplus', 2 => 'packer'); + if (function_exists('jsmin')) { + $compressors[1] = 'jsmin'; + ksort($compressors); + } + + // Set to 0 if file doesn't exist. + if (empty($info['content_hash'])) { + $results = array( + 0 => array('code' => 0, 'ratio' => 0, 'name' => 'jsminplus'), + 1 => array('code' => 0, 'ratio' => 0, 'name' => 'jsmin'), + 2 => array('code' => 0, 'ratio' => 0, 'name' => 'packer'), + ); + } + else { + // Set to "-1" so if php bombs, the file will be marked as bad. + $results = array( + 0 => array('code' => -1, 'ratio' => 0, 'name' => 'jsminplus'), + 1 => array('code' => -1, 'ratio' => 0, 'name' => 'jsmin'), + 2 => array('code' => -1, 'ratio' => 0, 'name' => 'packer'), + ); + // Run this via httprl if possible. + if (!module_exists('httprl') || !httprl_is_background_callback_capable()) { + // Place results into array. + $info['advagg_js_compress'] = $results; + // Save results, so if PHP bombs, this file is marked as bad. + $filename_hashes[$cache_id] = $info; + if (variable_get('advagg_use_file_cache', ADVAGG_USE_FILE_CACHE)) { + cache_set($cache_id, $info, 'cache_advagg_info', CACHE_PERMANENT); + } + + $results = advagg_js_compress_test_file($filename, $compressors); + $httprl_used = FALSE; + } + else { + $httprl_used = TRUE; + // Setup callback options array. + $callback_options = array( + array( + 'function' => 'advagg_js_compress_test_file', + 'return' => &$results, + ), + $filename, $compressors, + ); + // Queue up the request. + httprl_queue_background_callback($callback_options); + // Execute request. + httprl_send_request(); + + // If php bombs out, try each compressor indivudially. + foreach ($results as $key => $value) { + if ($value['code'] == -1) { + $sub_result = array(); + $sub_result[$key] = $value; + // Setup callback options array. + $callback_options = array( + array( + 'function' => 'advagg_js_compress_test_file', + 'return' => &$sub_result, + ), + $filename, array($key => $value['name']), + ); + // Queue up the request. + httprl_queue_background_callback($callback_options); + // Execute request. + httprl_send_request(); + + $results[$key] = $sub_result[$key]; + } + } + } + } + // Place results into array. + $info['advagg_js_compress'] = $results; + + // Save results. + $filename_hashes[$cache_id] = $info; + if (variable_get('advagg_use_file_cache', ADVAGG_USE_FILE_CACHE)) { + cache_set($cache_id, $info, 'cache_advagg_info', CACHE_PERMANENT); + } + return $info; +} diff --git a/advagg_js_compress/advagg_js_compress.module b/advagg_js_compress/advagg_js_compress.module index d74d7ed..a1bb476 100644 --- a/advagg_js_compress/advagg_js_compress.module +++ b/advagg_js_compress/advagg_js_compress.module @@ -24,7 +24,7 @@ define('ADVAGG_JS_COMPRESS_RATIO', 0.1); /** * Default value for the compression ratio test. */ -define('ADVAGG_JS_MAX_COMPRESS_RATIO', 0.98); +define('ADVAGG_JS_MAX_COMPRESS_RATIO', 0.90); /** * Default value to see if this will compress aggregated files. @@ -62,3 +62,53 @@ function advagg_js_compress_advagg_current_hooks_hash_array_alter(&$aggregate_se $aggregate_settings['variables']['advagg_js_compress_packer'] = variable_get('advagg_js_compress_packer', ADVAGG_JS_COMPRESS_PACKER); $aggregate_settings['variables']['advagg_js_max_compress_ratio'] = variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO); } + +/** + * Test a file, making sure it is compressable. + * + * @param $filename + * Path and filename of the js file to test. + * @param $compressors + * List of compressors to test. + * @return + * Array showing the results of the compression tests. + */ +function advagg_js_compress_test_file($filename, $compressors) { + $contents = file_get_contents($filename); + // Get the JS string length before the compression operation. + $contents_before = $contents; + $before = strlen($contents); + + module_load_include('inc', 'advagg_js_compress', 'advagg_js_compress.advagg'); + + $results = array(); + foreach ($compressors as $key => $name) { + $contents = $contents_before; + $aggregate_settings['variables']['advagg_js_compressor'] = $key; + $aggregate_settings['variables']['advagg_js_max_compress_ratio'] = variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO); + + // Compress it. + advagg_js_compress_prep($contents, $filename, $aggregate_settings, FALSE, FALSE, FALSE); + $after = strlen($contents); + + $ratio = 0; + if ($before != 0) { + $ratio = ($before - $after) / $before; + } + // Set to "-2" if compression ratio sucks (it's already compressed). + if ($ratio < variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO)) { + $results[$key] = array('code' => -2, 'ratio' => round($ratio, 5), 'name' => $name); + } + // Set to "-3" if the compression ratio is way too good (bad js output). + elseif ($ratio > variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO)) { + $results[$key] = array('code' => -3, 'ratio' => round($ratio, 5), 'name' => $name); + } + // Set to "1". Everything worked, mark this file as compressible. + else { + $results[$key] = array('code' => 1, 'ratio' => round($ratio, 5), 'name' => $name); + } + } + return $results; +} + +