3988 lines
130 KiB
Text
3988 lines
130 KiB
Text
<?php
|
|
|
|
/**
|
|
* @file
|
|
* Advanced CSS/JS aggregation module
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* Default value to see if advanced CSS/JS aggregation is enabled.
|
|
*/
|
|
define('ADVAGG_ENABLED', TRUE);
|
|
|
|
/**
|
|
* Default stale file threshold is 6 days for mtime.
|
|
*/
|
|
define('ADVAGG_STALE_FILE_THRESHOLD', 518400);
|
|
|
|
/**
|
|
* Default stale file threshold is 3 days for atime.
|
|
*/
|
|
define('ADVAGG_STALE_FILE_LAST_USED_THRESHOLD', 259200);
|
|
|
|
/**
|
|
* Default file last used check-in is 12 hours.
|
|
*/
|
|
define('ADVAGG_FILE_LAST_USED_INTERVAL', 1296000);
|
|
|
|
/**
|
|
* Default gzip compression setting.
|
|
*/
|
|
define('ADVAGG_GZIP_COMPRESSION', FALSE);
|
|
|
|
/**
|
|
* Default generate the aggregate async.
|
|
*/
|
|
define('ADVAGG_ASYNC_GENERATION', FALSE);
|
|
|
|
/**
|
|
* How long to wait for the server to come back with an async opp.
|
|
*/
|
|
define('ADVAGG_SOCKET_TIMEOUT', 1);
|
|
|
|
/**
|
|
* Default file checksum mode.
|
|
*/
|
|
define('ADVAGG_CHECKSUM_MODE', 'mtime');
|
|
|
|
/**
|
|
* Default value for writing debug info to watchdog.
|
|
*/
|
|
define('ADVAGG_DEBUG', FALSE);
|
|
|
|
/**
|
|
* Default value for number of files that can be added before using @import.
|
|
*/
|
|
define('ADVAGG_CSS_COUNT_THRESHOLD', 25);
|
|
|
|
/**
|
|
* Default value for not using @import if logged in and not IE.
|
|
*/
|
|
define('ADVAGG_CSS_LOGGED_IN_IE_DETECT', TRUE);
|
|
|
|
/**
|
|
* Default value for creating a htaccess file in the advagg directories.
|
|
*/
|
|
define('ADVAGG_DIR_HTACCESS', TRUE);
|
|
|
|
/**
|
|
* Default value for a custom files directory just for advagg directories.
|
|
*/
|
|
define('ADVAGG_CUSTOM_FILES_DIR', '');
|
|
|
|
/**
|
|
* Default function used to render css output.
|
|
*/
|
|
define('ADVAGG_CSS_RENDER_FUNCTION', 'advagg_unlimited_css_builder');
|
|
|
|
/**
|
|
* Default function used to render js output.
|
|
*/
|
|
define('ADVAGG_JS_RENDER_FUNCTION', 'advagg_js_builder');
|
|
|
|
/**
|
|
* Default function used to save files.
|
|
*/
|
|
define('ADVAGG_FILE_SAVE_FUNCTION', 'advagg_file_saver');
|
|
|
|
/**
|
|
* Default value for rebuilding the bundle on cache flush.
|
|
*/
|
|
define('ADVAGG_REBUILD_ON_FLUSH', FALSE);
|
|
|
|
/**
|
|
* Default value to see if advanced CSS/JS aggregation is enabled.
|
|
*/
|
|
define('ADVAGG_CLOSURE', TRUE);
|
|
|
|
/**
|
|
* Default value to see if JS bundle matching should be strict.
|
|
*/
|
|
define('ADVAGG_STRICT_JS_BUNDLES', TRUE);
|
|
|
|
/**
|
|
* Default value for how long the request can go before php times out.
|
|
*/
|
|
define('ADVAGG_SET_TIME_LIMIT', 240);
|
|
|
|
/**
|
|
* Default mode for aggregate creation.
|
|
*
|
|
* 0 - Wait for locks.
|
|
* 1 - Do not wait for locks.
|
|
* 2 - Only serve aggregated files if they are already built.
|
|
*/
|
|
define('ADVAGG_AGGREGATE_MODE', 2);
|
|
|
|
/**
|
|
* Default mode of advagg in regards to the page cache.
|
|
*
|
|
* FALSE - Cache all pages.
|
|
* TRUE - Don't cache page if aggregate could be included on that page & it is
|
|
* not.
|
|
*/
|
|
define('ADVAGG_PAGE_CACHE_MODE', TRUE);
|
|
|
|
/**
|
|
* Default mode of advagg_bundle_built() in regards to how file_exists is used.
|
|
*
|
|
* FALSE - use file_exists.
|
|
* TRUE - use cache_get instead of file_exists if possible.
|
|
*/
|
|
define('ADVAGG_BUNDLE_BUILT_MODE', FALSE);
|
|
|
|
/**
|
|
* Default value to see if we can use the STREAM_CLIENT_ASYNC_CONNECT flag.
|
|
*/
|
|
define('ADVAGG_ASYNC_SOCKET_CONNECT', FALSE);
|
|
|
|
/**
|
|
* Default value to see if we removed old files/bundles from the database.
|
|
*/
|
|
define('ADVAGG_PRUNE_ON_CRON', TRUE);
|
|
|
|
/**
|
|
* Default value to see if we cache at the advagg_processor level.
|
|
*/
|
|
define('ADVAGG_USE_FULL_CACHE', TRUE);
|
|
|
|
/**
|
|
* Default value to see if preprocess JavaScript files.
|
|
*/
|
|
define('ADVAGG_PREPROCESS_JS', TRUE);
|
|
|
|
/**
|
|
* Default value to see if preprocess CSS files.
|
|
*/
|
|
define('ADVAGG_PREPROCESS_CSS', TRUE);
|
|
|
|
/**
|
|
* Default value to see if preprocess CSS files.
|
|
*/
|
|
define('ADVAGG_ONLY_CSS_FROM_VARIABLES', FALSE);
|
|
|
|
// Create a closure function that does not add JavaScript.
|
|
if (variable_get('advagg_closure', ADVAGG_CLOSURE)) {
|
|
if (!function_exists('phptemplate_closure')) {
|
|
|
|
/**
|
|
* Execute hook_footer() which is run at the end of the page right before
|
|
* the close of the body tag.
|
|
*
|
|
* @param $main (optional)
|
|
* Whether the current page is the front page of the site.
|
|
* @return
|
|
* A string containing the results of the hook_footer() calls.
|
|
*/
|
|
function phptemplate_closure($main = 0) {
|
|
global $_advagg;
|
|
$_advagg['closure'] = TRUE;
|
|
$footer = implode("\n", module_invoke_all('footer', $main));
|
|
// If advagg is disabled, then include footer JS here.
|
|
if (!variable_get('advagg_enabled', ADVAGG_ENABLED)) {
|
|
$footer .= drupal_get_js('footer');
|
|
}
|
|
return $footer;
|
|
}
|
|
|
|
}
|
|
else {
|
|
global $_advagg;
|
|
$_advagg['closure'] = FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implementation of hook_perm().
|
|
*/
|
|
function advagg_perm() {
|
|
return array('bypass advanced aggregation');
|
|
}
|
|
|
|
/**
|
|
* Implementation of hook_menu().
|
|
*/
|
|
function advagg_menu() {
|
|
list($css_path, $js_path) = advagg_get_root_files_dir();
|
|
$file_path = drupal_get_path('module', 'advagg');
|
|
|
|
$items = array();
|
|
$items[$css_path . '/%'] = array(
|
|
'page callback' => '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 <br /> %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 . '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . $href . '" />' . "\n";
|
|
}
|
|
else {
|
|
$styles .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . $href . '" />' . "\n";
|
|
}
|
|
}
|
|
else {
|
|
$data = $css_file['data'];
|
|
if ($prefix != $last_prefix) {
|
|
$styles .= $last_suffix . "\n" . $prefix . '<style type="text/css" media="' . $media . '">' . "\n" . $data . "\n" . '</style>' . "\n";
|
|
}
|
|
else {
|
|
$styles .= '<style type="text/css" media="' . $media . '">' . "\n" . $data . "\n" . '</style>' . "\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" . '<style type="text/css" media="' . $media . '">' . "\n" . $import . '</style>';
|
|
$import = '';
|
|
}
|
|
$counter = 0;
|
|
$media = $media_new;
|
|
}
|
|
$import .= '@import "' . $href . '";' . "\n";
|
|
$counter++;
|
|
}
|
|
if ($media && !empty($import)) {
|
|
$styles .= "\n" . '<style type="text/css" media="' . $media . '">' . "\n" . $import . '</style>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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<!--//--><![CDATA[//><!--\n";
|
|
$embed_suffix = "\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 . '<script type="text/javascript"' . ($defer ? ' defer="defer"' : '') . ' src="' . $src . '"></script>' . $suffix . "\n";
|
|
}
|
|
}
|
|
|
|
if (!empty($output_preprocess)) {
|
|
foreach ($output_preprocess as $values) {
|
|
list($src, $prefix, $suffix) = $values;
|
|
$output .= $prefix . '<script type="text/javascript" src="' . $src . '"></script>' . $suffix . "\n";
|
|
}
|
|
}
|
|
|
|
foreach ($output_no_preprocess as $type => $list) {
|
|
if (!empty($list)) {
|
|
foreach ($list as $values) {
|
|
list($src, $defer, $prefix, $suffix) = $values;
|
|
$output .= $prefix . '<script type="text/javascript"' . ($defer ? ' defer="defer"' : '') . ' src="' . $src . '"></script>' . $suffix . "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!empty($setting_no_preprocess)) {
|
|
foreach ($setting_no_preprocess as $code) {
|
|
$output .= '<script type="text/javascript">' . $embed_prefix . $code . $embed_suffix . "</script>\n";
|
|
}
|
|
}
|
|
|
|
if (!empty($inline_no_preprocess)) {
|
|
foreach ($inline_no_preprocess as $values) {
|
|
list($code, $defer, $prefix, $suffix) = $values;
|
|
$output .= $prefix . '<script type="text/javascript"' . ($defer ? ' defer="defer"' : '') . '>' . $embed_prefix . $code . $embed_suffix . '</script>' . $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 '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' . "\n";
|
|
print '<html>';
|
|
print '<head><title>404 Not Found</title></head>';
|
|
print '<body><h1>Not Found</h1>';
|
|
print '<p>The requested URL was not found on this server.</p>';
|
|
print '<p><a href="' . $base_path . '">Home</a></p>';
|
|
print '<!-- advagg_missing_fast404 -->';
|
|
print '</body></html>';
|
|
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 .= "<IfModule mod_rewrite.c>\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 .= " <IfModule mod_headers.c>\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 .= " <FilesMatch \"\.$ext\.gz$\">\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 .= " </FilesMatch>\n";
|
|
$data .= " </IfModule>\n";
|
|
$data .= "</IfModule>\n";
|
|
$data .= "\n";
|
|
}
|
|
$data .= "<FilesMatch \"^${ext}_[0-9a-f]{32}_.+\.$ext(\.gz)?\">\n";
|
|
$data .= " # No mod_headers\n";
|
|
$data .= " <IfModule !mod_headers.c>\n";
|
|
$data .= " # No mod_expires\n";
|
|
$data .= " <IfModule !mod_expires.c>\n";
|
|
$data .= " # Use ETags.\n";
|
|
$data .= " FileETag MTime Size\n";
|
|
$data .= " </IfModule>\n";
|
|
$data .= "\n";
|
|
$data .= " # Use Expires Directive.\n";
|
|
$data .= " <IfModule mod_expires.c>\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 .= " </IfModule>\n";
|
|
$data .= " </IfModule>\n";
|
|
$data .= "\n";
|
|
$data .= " <IfModule mod_headers.c>\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 .= " </IfModule>\n";
|
|
$data .= "</FilesMatch>\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;
|
|
}
|