'advagg_js_compress_test_file', 'type' => MENU_CALLBACK, 'access callback' => TRUE, ); $items['admin/settings/advagg/js-compress'] = array( 'title' => 'JS Compression', 'description' => 'Adjust JS Compression settings.', 'page callback' => 'advagg_js_compress_admin_page', 'type' => MENU_LOCAL_TASK, 'access arguments' => array('administer site configuration'), 'file path' => $file_path, 'file' => 'advagg_js_compress.admin.inc', 'weight' => 10, ); return $items; } /** * Implement hook_init. */ function advagg_js_compress_init() { global $conf; if (variable_get('advagg_js_compress_packer_enable', ADVAGG_JS_COMPRESS_PACKER_ENABLE)) { $conf['advagg_file_save_function'] = 'advagg_js_compress_file_saver'; } } /** * Implement hook_advagg_files_table. */ function advagg_js_compress_advagg_files_table($row, $checksum) { // IF the file has changed, test it's compressibility. if ($row['filetype'] === 'js' && $checksum !== $row['checksum']) { $files_to_test[] = array( 'md5' => $row['filename_md5'], 'filename' => $row['filename'], ); advagg_js_compress_test_compression($files_to_test); } } /** * Implement hook_advagg_js_pre_alter. */ function advagg_js_compress_advagg_js_pre_alter(&$javascript, $preprocess_js, $public_downloads, $scope) { if (module_exists('jquery_update')) { return; } foreach ($javascript as $type => $data) { if (!$data) { continue; } if ($type == 'setting' || $type == 'inline') { continue; } foreach ($data as $path => $info) { if ($path == 'misc/jquery.form.js') { $new_path = drupal_get_path('module', 'advagg_js_compress') . '/jquery.form.js'; $javascript[$type][$new_path] = $info; unset($javascript[$type][$path]); } } } } /** * Implement hook_advagg_js_alter. */ function advagg_js_compress_advagg_js_alter(&$contents, $files, $bundle_md5) { if (!variable_get('advagg_js_compress_agg_files', ADVAGG_JS_COMPRESS_AGG_FILES)) { return; } advagg_js_compress_prep($contents, $files, $bundle_md5); } /** * Implement hook_advagg_js_inline_alter. */ function advagg_js_compress_advagg_js_inline_alter(&$contents) { if (!variable_get('advagg_js_compress_inline', ADVAGG_JS_COMPRESS_INLINE)) { return; } $compressor = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR); // If using a cache, try to get the contents of it. if (variable_get('advagg_js_compress_inline_cache', ADVAGG_JS_COMPRESS_INLINE_CACHE)) { $key = md5($contents) . $compressor; $table = 'cache_advagg_js_compress_inline'; $data = cache_get($key, $table); if (!empty($data->data)) { $contents = $data->data; return; } } if ($compressor == 0) { $original_contents = $contents; list($before, $after) = advagg_js_compress_jsminplus($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 > variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO)) { $contents = $original_contents; } } if ($compressor == 1) { $contents = jsmin($contents); // Ensure that $contents ends with ; or }. if (strpbrk(substr(trim($contents), -1), ';}') === FALSE) { // ; or } not found, add in ; to the end of $contents. $contents = trim($contents) . ';'; } } // If using a cache set it. if (isset($key)) { cache_set($key, $contents, $table, CACHE_TEMPORARY); } } /** * Compress a JS string * * @param $contents * Javascript string. */ function advagg_js_compress_prep(&$contents, $files, $bundle_md5) { // Make sure every file in this aggregate is compressible. $files_to_test = array(); $list_bad = array(); foreach ($files as $filename) { $filename_md5 = md5($filename); $data = advagg_get_file_data($filename_md5); // File needs to be tested. if (empty($data['advagg_js_compress']['tested'])) { $files_to_test[] = array( 'md5' => $filename_md5, 'filename' => $filename, ); } elseif ($data['advagg_js_compress']['tested']['jsminplus'] != 1) { $list_bad[$filename] = $filename; } } $advagg_js_compress_callback = variable_get('advagg_js_compress_callback', ADVAGG_JS_COMPRESS_CALLBACK); if ($advagg_js_compress_callback) { // Send test files to worker. if (!empty($files_to_test)) { $compressible = advagg_js_compress_test_compression($files_to_test); // If an array then it is a list of files that can not be compressed. if (is_array($compressible)) { // Place filename in an array key. foreach ($compressible as $filedata) { $filename = $filedata['filename']; $list_bad[$filename] = $filename; } } } } $contents = ''; // Do not compress the file that it bombs on. // Compress each file individually. foreach ($files as $file) { if (!empty($list_bad[$file])) { $contents .= advagg_build_js_bundle(array($file)); } else { $data = advagg_build_js_bundle(array($file)); // If using a cache, try to get the contents of it. $cached = FALSE; if (variable_get('advagg_js_compress_file_cache', ADVAGG_JS_COMPRESS_FILE_CACHE)) { $key = $file; $table = 'cache_advagg_js_compress_file'; $cached_data = cache_get($key, $table); if (!empty($cached_data->data)) { $data = $cached_data->data; $cached = TRUE; } } if (!$cached && !empty($data)) { $compressor = variable_get('advagg_js_compressor', ADVAGG_JS_COMPRESSOR); if ($compressor == 0) { list($before, $after) = advagg_js_compress_jsminplus($data); $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($data) || empty($ratio) || $ratio > variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO)) { $data = advagg_build_js_bundle(array($file)); } elseif (isset($key)) { // If using a cache set it. cache_set($key, $data, $table); } } elseif ($compressor == 1) { $contents = jsmin($contents); // Ensure that $contents ends with ; or }. if (strpbrk(substr(trim($contents), -1), ';}') === FALSE) { // ; or } not found, add in ; to the end of $contents. $contents = trim($contents) . ';'; } } } $url = url($file, array('absolute' => TRUE)); $contents .= "/* Source and licensing information for the line(s) below can be found at $url. */\n" . $data . ";\n/* Source and licensing information for the above line(s) can be found at $url. */\n"; } } } /** * Compress a JS string using jsmin+ * * @param $contents * Javascript string. * @return * array with the size before and after. */ function advagg_js_compress_jsminplus(&$contents) { // Try to allocate enough time to run JSMin+. if (function_exists('set_time_limit')) { @set_time_limit(variable_get('advagg_set_time_limit', ADVAGG_SET_TIME_LIMIT)); } // Only include jsminplus.inc if the JSMinPlus class doesn't exist. if (!class_exists('JSMinPlus')) { include(drupal_get_path('module', 'advagg_js_compress') . '/jsminplus.inc'); } // Get the JS string length before the compression operation. $before = strlen($contents); $original_contents = $contents; try { // Strip Byte Order Marks (BOM's) from the file, JSMin+ cannot parse these. $contents = str_replace(pack("CCC", 0xef, 0xbb, 0xbf), "", $contents); ob_start(); // JSMin+ the contents of the aggregated file. $contents = JSMinPlus::minify($contents); $error = trim(ob_get_contents()); if (!empty($error)) { throw new Exception($error); } // Ensure that $contents ends with ; or }. if (strpbrk(substr(trim($contents), -1), ';}') === FALSE) { // ; or } not found, add in ; to the end of $contents. $contents = trim($contents) . ';'; } // Get the JS string length after the compression operation. $after = strlen($contents); } catch (Exception $e) { // Log the exception thrown by JSMin+ and roll back to uncompressed content. watchdog('advagg', $e->getMessage() . '
' . $original_contents . '', NULL, WATCHDOG_WARNING); $contents = $original_contents; $after = $before; } ob_end_clean(); return array($before, $after); } /** * Run various theme functions so the cache is primed. * * @param $files_to_test * array with md5 and filename. * @return * TRUE if all files are compressible. List of files that failed otherwise. */ function advagg_js_compress_test_compression($files_to_test) { global $base_path; $bad_files = array(); // Blacklist jquery.min.js from getting compressed. if (module_exists('jquery_update')) { foreach ($files_to_test as $key => $info) { if (strpos($info['filename'], 'jquery.min.js') !== FALSE) { // Add file to the bad list. $bad_files[] = $info; unset($files_to_test[$key]); // Get file data. $filename_md5 = md5($info['filename']); $lock_name = 'advagg_set_file_data_' . $filename_md5; if (!lock_acquire($lock_name, 10)) { lock_wait($lock_name); continue; } $data = advagg_get_file_data($filename_md5); // Set to -2 if (!isset($data->data['advagg_js_compress']['tested']['jsminplus']) || $data->data['advagg_js_compress']['tested']['jsminplus'] != -2) { $data['advagg_js_compress']['tested']['jsminplus'] = -2; advagg_set_file_data($filename_md5, $data); } lock_release($lock_name); } } } foreach ($files_to_test as $info) { $key = variable_get('advagg_js_compress_url_key', FALSE); if (empty($key)) { $key = mt_rand(); variable_set('advagg_js_compress_url_key', $key); } // Clear the cache for this file cache_clear_all($info['filename'], 'cache_advagg_js_compress_file'); // Setup request URL and headers. $query['values'] = $info; $query['key'] = $key; $query_string = http_build_query($query, '', '&'); $url = _advagg_build_url('advagg/js_compress_test_file'); $headers = array( 'Host' => $_SERVER['HTTP_HOST'], 'Content-Type' => 'application/x-www-form-urlencoded', 'Connection' => 'close', ); $results = drupal_http_request($url, $headers, 'POST', $query_string); // Get file data. $filename_md5 = md5($info['filename']); $data = advagg_get_file_data($filename_md5); // Mark as a bad file. if (empty($data['advagg_js_compress']['tested']['jsminplus']) || $data['advagg_js_compress']['tested']['jsminplus'] != 1) { $bad_files[] = $info; } } if (empty($bad_files)) { return TRUE; } return $bad_files; } /** * Run various theme functions so the cache is primed. * * @param $values * object File info */ function advagg_js_compress_test_file($values = NULL) { // watchdog('debug', str_replace(' ', ' ', nl2br(htmlentities(print_r($values, TRUE) . print_r($_REQUEST, TRUE))))); // Exit if key does not match & called with $file not set. if (is_null($values)) { if (empty($_POST['key']) || empty($_POST['values'])) { return; } $key = variable_get('advagg_js_compress_url_key', FALSE); if ($key != $_POST['key']) { return; } $values = array(); $values['values'] = $_POST['values']; } $filename = $values['values']['filename']; $md5 = $values['values']['md5']; // Compression test file if it exists. advagg_clearstatcache(TRUE, $filename); if (file_exists($filename)) { $contents = file_get_contents($filename); $filesize = filesize($filename); $lock_name = 'advagg_set_file_data_' . $md5; if (!lock_acquire($lock_name, 45)) { lock_wait($lock_name); echo $md5; exit; } $data = advagg_get_file_data($md5); // Set to "-1" so if php bombs out, the file will be marked as bad. $data['advagg_js_compress']['tested']['jsminplus'] = -1; advagg_set_file_data($md5, $data); // Compress the data. list($before, $after) = advagg_js_compress_jsminplus($contents); // Set to "-2" if compression ratio sucks. $ratio = 0; if ($before != 0) { $ratio = ($before - $after) / $before; } if ($ratio < variable_get('advagg_js_compress_ratio', ADVAGG_JS_COMPRESS_RATIO)) { $data['advagg_js_compress']['tested']['jsminplus'] = -2; advagg_set_file_data($md5, $data); lock_release($lock_name); echo $md5; exit; } // Set to "-3" if the compression ratio is way too good. if ($ratio > variable_get('advagg_js_max_compress_ratio', ADVAGG_JS_MAX_COMPRESS_RATIO)) { $data['advagg_js_compress']['tested']['jsminplus'] = -3; advagg_set_file_data($md5, $data); lock_release($lock_name); echo $md5; exit; } // Everything worked, mark this file as compressible. $data['advagg_js_compress']['tested']['jsminplus'] = 1; advagg_set_file_data($md5, $data); // Set the file cache. if (variable_get('advagg_js_compress_file_cache', ADVAGG_JS_COMPRESS_FILE_CACHE)) { $key = $filename; $table = 'cache_advagg_js_compress_file'; cache_set($key, $contents, $table); } } if (isset($lock_name)) { lock_release($lock_name); } echo $md5; exit; } /** * 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_js_compress_file_saver($data, $dest, $force, $type) { if ($type == 'css') { return advagg_file_saver($data, $dest, $force, $type); } if (!variable_get('advagg_gzip_compression', ADVAGG_GZIP_COMPRESSION) || !extension_loaded('zlib')) { return advagg_file_saver($data, $dest, $force, $type); } // Get file save function $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'; } // Gzip first. $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; } } } // Use packer on JS data. advagg_js_compress_jspacker($data); // Write File. 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; } } // Make sure .htaccess file exists. advagg_htaccess_check_generate($dest); cache_set($dest, time(), 'cache_advagg', CACHE_PERMANENT); return TRUE; } /** * Compress a JS string using packer. * * @param $contents * Javascript string. */ function advagg_js_compress_jspacker(&$contents) { // Use Packer on the contents of the aggregated file. require_once(drupal_get_path('module', 'advagg_js_compress') . '/jspacker.inc'); // Add semicolons to the end of lines if missing. $contents = str_replace("}\n", "};\n", $contents); $contents = str_replace("\nfunction", ";\nfunction", $contents); // Remove char returns, looking at you lightbox2. $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(); } /** * Implementation of hook_flush_caches(). */ function advagg_js_compress_flush_caches() { return array('cache_advagg_js_compress_inline'); } /** * Implementation of hook_advagg_master_reset(). */ function advagg_js_compress_advagg_master_reset() { cache_clear_all('*', 'cache_advagg_js_compress_inline', TRUE); cache_clear_all('*', 'cache_advagg_js_compress_file', TRUE); }